├── QwenMNN.pro
├── QwenMNN.pro.user
├── README.md
├── android
├── AndroidManifest.xml
├── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── res
│ ├── drawable-hdpi
│ └── icon.png
│ ├── drawable-ldpi
│ └── icon.png
│ ├── drawable-mdpi
│ └── icon.png
│ └── values
│ └── libs.xml
├── main.cpp
├── pic
├── inference.png
├── llm-export.png
├── llm.png
├── quant.png
├── qwen.png
└── structure.png
├── widget.cpp
├── widget.h
├── widget.ui
└── windows
└── Qwen0.5B.cpp
/QwenMNN.pro:
--------------------------------------------------------------------------------
1 | QT += core gui
2 |
3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
4 |
5 | CONFIG += c++17
6 |
7 | # The following define makes your compiler emit warnings if you use
8 | # any Qt feature that has been marked deprecated (the exact warnings
9 | # depend on your compiler). Please consult the documentation of the
10 | # deprecated API in order to know how to port your code away from it.
11 | DEFINES += QT_DEPRECATED_WARNINGS
12 |
13 | # You can also make your code fail to compile if it uses deprecated APIs.
14 | # In order to do so, uncomment the following line.
15 | # You can also select to disable deprecated APIs only up to a certain version of Qt.
16 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
17 |
18 | SOURCES += \
19 | main.cpp \
20 | widget.cpp
21 |
22 | HEADERS += \
23 | widget.h
24 |
25 | FORMS += \
26 | widget.ui
27 |
28 | # Default rules for deployment.
29 | qnx: target.path = /tmp/$${TARGET}/bin
30 | else: unix:!android: target.path = /opt/$${TARGET}/bin
31 | !isEmpty(target.path): INSTALLS += target
32 |
33 | DISTFILES += \
34 | android/AndroidManifest.xml \
35 | android/build.gradle \
36 | android/gradle/wrapper/gradle-wrapper.jar \
37 | android/gradle/wrapper/gradle-wrapper.properties \
38 | android/gradlew \
39 | android/gradlew.bat \
40 | android/res/values/libs.xml
41 |
42 | ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
43 |
44 | unix:!macx: LIBS += -L$$PWD/../../mnn-llm-android/libs/ -lMNN
45 | INCLUDEPATH += $$PWD/../../mnn-llm-android/include
46 | DEPENDPATH += $$PWD/../../mnn-llm-android/include
47 | unix:!macx: LIBS += -L$$PWD/../../mnn-llm-android/libs/ -lMNN_Express
48 | unix:!macx: LIBS += -L$$PWD/../../mnn-llm-android/android_build/ -lllm
49 |
50 | INCLUDEPATH += $$PWD/../../mnn-llm-android/include
51 | DEPENDPATH += $$PWD/../../mnn-llm-android/include
52 |
53 | ANDROID_EXTRA_LIBS = F:/andriod_env/mnn-llm/project/QwenMNN/../../mnn-llm-android/libs/libMNN.so F:/andriod_env/mnn-llm/project/QwenMNN/../../mnn-llm-android/libs/libMNN_Express.so $$PWD/../../mnn-llm-android/android_build/libllm.so
54 |
55 |
56 | android {
57 | data.files += qwen_model/block_0.mnn
58 | data.files += qwen_model/block_1.mnn
59 | data.files += qwen_model/block_2.mnn
60 | data.files += qwen_model/block_3.mnn
61 | data.files += qwen_model/block_4.mnn
62 | data.files += qwen_model/block_5.mnn
63 | data.files += qwen_model/block_6.mnn
64 | data.files += qwen_model/block_7.mnn
65 | data.files += qwen_model/block_8.mnn
66 | data.files += qwen_model/block_9.mnn
67 | data.files += qwen_model/block_10.mnn
68 | data.files += qwen_model/block_11.mnn
69 | data.files += qwen_model/block_12.mnn
70 | data.files += qwen_model/block_13.mnn
71 | data.files += qwen_model/block_14.mnn
72 | data.files += qwen_model/block_15.mnn
73 | data.files += qwen_model/block_16.mnn
74 | data.files += qwen_model/block_17.mnn
75 | data.files += qwen_model/block_18.mnn
76 | data.files += qwen_model/block_19.mnn
77 | data.files += qwen_model/block_20.mnn
78 | data.files += qwen_model/block_21.mnn
79 | data.files += qwen_model/block_22.mnn
80 | data.files += qwen_model/block_23.mnn
81 | data.files += qwen_model/embedding.mnn
82 |
83 | data.files += qwen_model/embeddings_bf16.bin
84 | data.files += qwen_model/lm.mnn
85 | data.files += qwen_model/tokenizer.txt
86 |
87 | data.path = /assets/models
88 | INSTALLS += data
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/QwenMNN.pro.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | EnvironmentId
7 | {f00823e8-dbf7-4e5c-a8ca-83ceed64900a}
8 |
9 |
10 | ProjectExplorer.Project.ActiveTarget
11 | 0
12 |
13 |
14 | ProjectExplorer.Project.EditorSettings
15 |
16 | true
17 | false
18 | true
19 |
20 | Cpp
21 |
22 | CppGlobal
23 |
24 |
25 |
26 | QmlJS
27 |
28 | QmlJSGlobal
29 |
30 |
31 | 2
32 | UTF-8
33 | false
34 | 4
35 | false
36 | 80
37 | true
38 | true
39 | 1
40 | true
41 | false
42 | 0
43 | true
44 | true
45 | 0
46 | 8
47 | true
48 | 0
49 | true
50 | true
51 | true
52 | false
53 |
54 |
55 |
56 | ProjectExplorer.Project.PluginSettings
57 |
58 |
59 | -fno-delayed-template-parsing
60 |
61 | true
62 |
63 |
64 |
65 | ProjectExplorer.Project.Target.0
66 |
67 | Android for armeabi-v7a,arm64-v8a,x86,x86_64 (Clang Qt 5.14.2 for Android)
68 | Android for armeabi-v7a,arm64-v8a,x86,x86_64 (Clang Qt 5.14.2 for Android)
69 | {ab37937d-cb74-4041-a493-094052e6badb}
70 | 1
71 | 0
72 | 0
73 |
74 | F:/andriod_env/mnn-llm/project/build-QwenMNN-Android_for_armeabi_v7a_arm64_v8a_x86_x86_64_Clang_Qt_5_14_2_for_Android-Debug
75 |
76 |
77 | true
78 | QtProjectManager.QMakeBuildStep
79 | true
80 |
81 | true
82 | false
83 | false
84 |
85 |
86 | true
87 | Qt4ProjectManager.MakeStep
88 |
89 | false
90 |
91 |
92 | false
93 |
94 |
95 | true
96 | Qt4ProjectManager.AndroidPackageInstallationStep
97 |
98 |
99 | android-28
100 |
101 | true
102 | QmakeProjectManager.AndroidBuildApkStep
103 | false
104 | false
105 |
106 | 4
107 | Build
108 | Build
109 | ProjectExplorer.BuildSteps.Build
110 |
111 |
112 |
113 | true
114 | Qt4ProjectManager.MakeStep
115 |
116 | true
117 | clean
118 |
119 | false
120 |
121 | 1
122 | Clean
123 | Clean
124 | ProjectExplorer.BuildSteps.Clean
125 |
126 | 2
127 | false
128 |
129 | Debug
130 | Qt4ProjectManager.Qt4BuildConfiguration
131 | 2
132 |
133 |
134 | F:/andriod_env/mnn-llm/project/build-QwenMNN-Android_for_armeabi_v7a_arm64_v8a_x86_x86_64_Clang_Qt_5_14_2_for_Android-Release
135 |
136 |
137 | true
138 | QtProjectManager.QMakeBuildStep
139 | false
140 |
141 | false
142 | false
143 | true
144 |
145 |
146 | true
147 | Qt4ProjectManager.MakeStep
148 |
149 | false
150 |
151 |
152 | false
153 |
154 |
155 | true
156 | Qt4ProjectManager.AndroidPackageInstallationStep
157 |
158 |
159 | android-28
160 |
161 | true
162 | QmakeProjectManager.AndroidBuildApkStep
163 | false
164 | false
165 |
166 | 4
167 | Build
168 | Build
169 | ProjectExplorer.BuildSteps.Build
170 |
171 |
172 |
173 | true
174 | Qt4ProjectManager.MakeStep
175 |
176 | true
177 | clean
178 |
179 | false
180 |
181 | 1
182 | Clean
183 | Clean
184 | ProjectExplorer.BuildSteps.Clean
185 |
186 | 2
187 | false
188 |
189 | Release
190 | Qt4ProjectManager.Qt4BuildConfiguration
191 | 0
192 |
193 |
194 | F:/andriod_env/mnn-llm/project/build-QwenMNN-Android_for_armeabi_v7a_arm64_v8a_x86_x86_64_Clang_Qt_5_14_2_for_Android-Profile
195 |
196 |
197 | true
198 | QtProjectManager.QMakeBuildStep
199 | true
200 |
201 | false
202 | true
203 | true
204 |
205 |
206 | true
207 | Qt4ProjectManager.MakeStep
208 |
209 | false
210 |
211 |
212 | false
213 |
214 |
215 | true
216 | Qt4ProjectManager.AndroidPackageInstallationStep
217 |
218 |
219 | android-28
220 |
221 | true
222 | QmakeProjectManager.AndroidBuildApkStep
223 | false
224 | false
225 |
226 | 4
227 | Build
228 | Build
229 | ProjectExplorer.BuildSteps.Build
230 |
231 |
232 |
233 | true
234 | Qt4ProjectManager.MakeStep
235 |
236 | true
237 | clean
238 |
239 | false
240 |
241 | 1
242 | Clean
243 | Clean
244 | ProjectExplorer.BuildSteps.Clean
245 |
246 | 2
247 | false
248 |
249 | Profile
250 | Qt4ProjectManager.Qt4BuildConfiguration
251 | 0
252 |
253 | 3
254 |
255 |
256 |
257 | true
258 | Qt4ProjectManager.AndroidDeployQtStep
259 | false
260 |
261 | 1
262 | Deploy
263 | Deploy
264 | ProjectExplorer.BuildSteps.Deploy
265 |
266 | 1
267 | Qt4ProjectManager.AndroidDeployConfiguration2
268 |
269 | 1
270 |
271 |
272 | dwarf
273 |
274 | cpu-cycles
275 |
276 |
277 | 250
278 |
279 | -e
280 | cpu-cycles
281 | --call-graph
282 | dwarf,4096
283 | -F
284 | 250
285 |
286 | -F
287 | true
288 | 4096
289 | false
290 | false
291 | 1000
292 |
293 | true
294 |
295 | false
296 | false
297 | false
298 | false
299 | true
300 | 0.01
301 | 10
302 | true
303 | kcachegrind
304 | 1
305 | 25
306 |
307 | 1
308 | true
309 | false
310 | true
311 | valgrind
312 |
313 | 0
314 | 1
315 | 2
316 | 3
317 | 4
318 | 5
319 | 6
320 | 7
321 | 8
322 | 9
323 | 10
324 | 11
325 | 12
326 | 13
327 | 14
328 |
329 |
330 |
331 |
332 | 0
333 |
334 | QwenMNN
335 | Qt4ProjectManager.AndroidRunConfiguration:F:/andriod_env/mnn-llm/project/QwenMNN/QwenMNN.pro
336 | F:/andriod_env/mnn-llm/project/QwenMNN/QwenMNN.pro
337 |
338 | false
339 |
340 | false
341 | true
342 | false
343 | false
344 | true
345 |
346 | 1
347 |
348 |
349 |
350 | ProjectExplorer.Project.Target.1
351 |
352 | Desktop Qt 5.14.2 MSVC2017 64bit
353 | Desktop Qt 5.14.2 MSVC2017 64bit
354 | qt.qt5.5142.win64_msvc2017_64_kit
355 | 0
356 | 0
357 | 0
358 |
359 | F:/andriod_env/mnn-llm/project/build-QwenMNN-Desktop_Qt_5_14_2_MSVC2017_64bit-Debug
360 |
361 |
362 | true
363 | QtProjectManager.QMakeBuildStep
364 | true
365 |
366 | false
367 | false
368 | false
369 |
370 |
371 | true
372 | Qt4ProjectManager.MakeStep
373 |
374 | false
375 |
376 |
377 | false
378 |
379 | 2
380 | Build
381 | Build
382 | ProjectExplorer.BuildSteps.Build
383 |
384 |
385 |
386 | true
387 | Qt4ProjectManager.MakeStep
388 |
389 | true
390 | clean
391 |
392 | false
393 |
394 | 1
395 | Clean
396 | Clean
397 | ProjectExplorer.BuildSteps.Clean
398 |
399 | 2
400 | false
401 |
402 | Debug
403 | Qt4ProjectManager.Qt4BuildConfiguration
404 | 2
405 |
406 |
407 | F:/andriod_env/mnn-llm/project/build-QwenMNN-Desktop_Qt_5_14_2_MSVC2017_64bit-Release
408 |
409 |
410 | true
411 | QtProjectManager.QMakeBuildStep
412 | false
413 |
414 | false
415 | false
416 | true
417 |
418 |
419 | true
420 | Qt4ProjectManager.MakeStep
421 |
422 | false
423 |
424 |
425 | false
426 |
427 | 2
428 | Build
429 | Build
430 | ProjectExplorer.BuildSteps.Build
431 |
432 |
433 |
434 | true
435 | Qt4ProjectManager.MakeStep
436 |
437 | true
438 | clean
439 |
440 | false
441 |
442 | 1
443 | Clean
444 | Clean
445 | ProjectExplorer.BuildSteps.Clean
446 |
447 | 2
448 | false
449 |
450 | Release
451 | Qt4ProjectManager.Qt4BuildConfiguration
452 | 0
453 |
454 |
455 | F:/andriod_env/mnn-llm/project/build-QwenMNN-Desktop_Qt_5_14_2_MSVC2017_64bit-Profile
456 |
457 |
458 | true
459 | QtProjectManager.QMakeBuildStep
460 | true
461 |
462 | false
463 | true
464 | true
465 |
466 |
467 | true
468 | Qt4ProjectManager.MakeStep
469 |
470 | false
471 |
472 |
473 | false
474 |
475 | 2
476 | Build
477 | Build
478 | ProjectExplorer.BuildSteps.Build
479 |
480 |
481 |
482 | true
483 | Qt4ProjectManager.MakeStep
484 |
485 | true
486 | clean
487 |
488 | false
489 |
490 | 1
491 | Clean
492 | Clean
493 | ProjectExplorer.BuildSteps.Clean
494 |
495 | 2
496 | false
497 |
498 | Profile
499 | Qt4ProjectManager.Qt4BuildConfiguration
500 | 0
501 |
502 | 3
503 |
504 |
505 | 0
506 | Deploy
507 | Deploy
508 | ProjectExplorer.BuildSteps.Deploy
509 |
510 | 1
511 | ProjectExplorer.DefaultDeployConfiguration
512 |
513 | 1
514 |
515 |
516 | dwarf
517 |
518 | cpu-cycles
519 |
520 |
521 | 250
522 |
523 | -e
524 | cpu-cycles
525 | --call-graph
526 | dwarf,4096
527 | -F
528 | 250
529 |
530 | -F
531 | true
532 | 4096
533 | false
534 | false
535 | 1000
536 |
537 | true
538 |
539 | false
540 | false
541 | false
542 | false
543 | true
544 | 0.01
545 | 10
546 | true
547 | kcachegrind
548 | 1
549 | 25
550 |
551 | 1
552 | true
553 | false
554 | true
555 | valgrind
556 |
557 | 0
558 | 1
559 | 2
560 | 3
561 | 4
562 | 5
563 | 6
564 | 7
565 | 8
566 | 9
567 | 10
568 | 11
569 | 12
570 | 13
571 | 14
572 |
573 | 2
574 |
575 | Qt4ProjectManager.Qt4RunConfiguration:F:/andriod_env/mnn-llm/project/QwenMNN/QwenMNN.pro
576 | F:/andriod_env/mnn-llm/project/QwenMNN/QwenMNN.pro
577 |
578 | false
579 |
580 | false
581 | true
582 | true
583 | false
584 | false
585 | true
586 |
587 | F:/andriod_env/mnn-llm/project/build-QwenMNN-Desktop_Qt_5_14_2_MSVC2017_64bit-Debug
588 |
589 | 1
590 |
591 |
592 |
593 | ProjectExplorer.Project.TargetCount
594 | 2
595 |
596 |
597 | ProjectExplorer.Project.Updater.FileVersion
598 | 22
599 |
600 |
601 | Version
602 | 22
603 |
604 |
605 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## mnn-llm手机端部署大语言模型
2 |
3 | ### 以Qwen1.5-0.5B-chat模型为例详细介绍mnn-llm的使用和在Android端的部署
4 |
5 | DataXujing
6 |
7 | mnn-llm原来只支持特定模型的 ChatGLM-MNN 项目已升级并更名为 mnn-llm,并集成到了MNN项目中;该项目支持多个目前主流的开源LLM模型的部署。与此同时,为了简化不同 LLM 模型向 ONNX 格式导出的流程,mnn-llm团队推出了 llm-export 项目。该项目为多种 LLM 模型提供了统一的导出方案,大大地降低了从预训练模型导出的复杂度。本项目实现了基于mnn-llm的模型转换,windows下的编译和基于QT的android端的编译和调用,并测试了Qwen1.5-0.5B模型在android端的部署。
8 |
9 | ### 1.模型导出
10 |
11 | 在将深度学习模型从研究原型转换为实际可部署的产品时,模型导出阶段的顺畅与否对于整个工作流程至关重要。通常,这个过程涉及将模型从训练框架中导出到一个中间表示,如 ONNX(开放神经网络交换格式),然后再转换为目标部署框架——在本例中为 MNN格式。为了简化并标准化这一过程,mnn-llm团队开发了一个名为 llm-export 的工具。
12 |
13 | llm-export 工具的核心思想在于对大型语言模型(LLM)进行了高度抽象,建立了一个统一化的导出框架。**这个项目的目标是消除将各种 LLM 模型导出到 ONNX 格式的障碍,确保无论是何种架构的 LLM 都能通过一个清晰定义的接口进行处理**。在 llm-export 中,我们定义了一套公用的导出逻辑,这意味着对于任何特定的 LLM,开发者只需实现模型的加载逻辑。这极大地减少了从多样化的训练环境向 ONNX 模型迁移的复杂性,并显著提高了整个导出过程的易用性。**模型一旦被成功导出至 ONNX,即可利用现有的mnnconvert工具转换到 MNN 格式,从而使用MNN完成llm模型的推理**。
14 |
15 | 
16 |
17 | 如上图所示: llm-export中将llm模型抽象为4部分:tokenizer, embedding, blocks, lm
18 |
19 | > 模型导出的Trick:模型拆分优化
20 |
21 | 以ChatGLM-6B为例,首先直接使用torch.onnx.export尝试对模型进行ONNX导出,导出过程非常缓慢,导出后模型的权重大小有28G。在将模型转换到MNN时会执行一些图优化;因为模型太大导致占用内存过高速度非常慢;因此考虑将模型进行拆分优化。拆分之后的优化考虑如下:
22 |
23 | + Embedding层的参数大小为`150528 * 4096`, 单个权重使用内存非常大;考虑到输入文字数量较小(相对于150528),使用Gather实现消耗大量内存/显存,直接将参数存储为二进制文件,通过fseekfread实现Gather的操作能够在稍微牺牲速度的情况下节约2.3G内存;
24 | + GLMBlock层的权重总大小为21G,仍然非常大,每个Block的大小为768M;考虑到要在端侧各类设备部署,可以将28层Block分别导出,这样的好处是能够在显存不足的情况下将部分Block放置在GPU,其余部分放置在CPU进行推理,这样能够充分利用设备算力;同时考虑到移动端设备,如:Android,Raspberry等设备上,在4G以下内存也可以逐Block的加载运行。
25 | + 线性层通过一个矩阵乘将hidden_state转换为词语的prob:`[num, 4096] @ [4096, 150528]`;其实这里并不需要num全部参与运算,比如输入序列长度`num = 10`时,实际预测下一个词语时进需要使用最后一个`[1, 4096]`即可。因此可以先对输入变量做一个Gather然后执行矩阵乘:`[1, 4096] @ [4096, 150528]`即可。同时考虑到MNN中在SIMD架构下的矩阵乘的优化,将改矩阵乘改为`[150528, 4096] @ [4096, 1]`。
26 |
27 | > 模型导出的Trick:Dynamic Shape
28 |
29 | 因为模型输入的形状是动态变化的,因此需要在导出时指定动态形状的维度,但是Pytorch涉及到view操作不支持动态形状,指定了动态维度后,在实际测试中发现因为模型实现中有些view相关代码导出后会将形状固定为常量,导致导出后改变输入形状无法正确推理,因此需要对模型中非动态的实现进行修改,将`attention_fn`函数中所有view操作替换为`squeeze`和`unsqueeze`操作,这样导出后与形状无关即可实现动态形状。
30 |
31 | ```python
32 | # 修改前
33 | query_layer = query_layer.view(output_size[2], output_size[0] * output_size[1], -1)
34 | key_layer = key_layer.view(output_size[3], output_size[0] * output_size[1], -1)
35 | # 修改后
36 | query_layer = query_layer.squeeze(1)
37 | key_layer = key_layer.squeeze(1)
38 | ```
39 |
40 | > 模型导出的Trick:kv-cache(将Tuple改为Tensor)
41 |
42 | HF中的transformer原始实现中`layer_past`是Tuple类型,将其类型修改为Tensor方便模型导出。将代码中的Tuple操作替换为Tensor操作,如:
43 |
44 | ```python
45 | # 修改前
46 | past_key, past_value = layer_past[0], layer_past[1]
47 | key_layer = torch.cat((past_key, key_layer), dim=0)
48 | value_layer = torch.cat((past_value, value_layer), dim=0)
49 | present = (key_layer, value_layer)
50 | # 修改后
51 | key_layer = torch.cat((past_key_value[0], key_layer), dim=0)
52 | value_layer = torch.cat((past_key_value[1], value_layer), dim=0)
53 | present = torch.stack((key_layer, value_layer), dim=0)
54 | ```
55 |
56 |
57 |
58 | ### 2.关于mnn和mnn-llm的整体结构
59 |
60 | 在部署大型语言模型(LLM)时,兼容性和易用性是关键因素。为了解决这一挑战,我们开发了一个名为 mnn-llm 的项目。考虑到MNN在跨平台上支持上的优秀表现,该项目基于 MNN 构建,旨在为各种平台提供一个统一的 LLM 模型部署解决方案。mnn-llm 项目使得从 llm-export 导出的模型能够无缝进行端到端的推理,并为开发者提供了一个简易的文本到文本(txt2txt)和多模态的调用接口,目前也只是知识库, 文本嵌入等。
61 |
62 | 在mnn-llm中我们移植实现了目前主流的tokenizer工具:Sentencepiece 和 Tiktoken。这些 tokenizer 组件是处理自然语言输入的关键部分,它们能够将原始文本转换为模型能理解的格式。同时为了轻量化,两种模型都使用文本的方式存储,移除了Sentencepiece中对protobuf的依赖。**此外,考虑到内存占用在移动设备上尤为宝贵,还在 mnn-llm 中引入了 disk embedding 功能**。**这意味着用户可以根据需要选择:在模型推理过程中使用 embedding 模型在内存计算,或者直接从磁盘加载 embedding 值**。这种灵活性不仅降低了运行时的内存需求,也为用户提供了更多的选择来平衡推理性能和资源使用。为了确保 mnn-llm 的通用性和扩展性,我们设计了一种易于扩展的架构。开发者可以通过继承基类并实现特定的配置来支持不同的 LLM 模型。这种架构设计使得整合新的 LLM 模型变得简单快捷,大大降低了将最新的研究成果应用到实际产品中的门槛。
63 |
64 | 
65 |
66 | ### 3.mnn-lmm的核心优化Trick
67 |
68 | + 基于在ARM-CPU上的耗时分析针对于Linear,MatMul和Memory算子进行优化
69 |
70 | LLM的主干网络由一系列连续的block组成,每个block的核心计算部分是Attention。如图所示,Attention的执行涉及到两个主要的计算操作:Linear(绿色表示)和MatMul(黄色表示)。这两种操作不仅要执行复杂的数学计算,还要伴随诸如split、concat和transpose等内存操作,这些统称为Memory算子。因此,我们可以将LLM模型推理过程中的核心操作分为三类:Linear, MatMul和Memory。
71 |
72 | 
73 |
74 | 值得注意的是,这三类算子的计算量和内存访问量会受到输入数据的batch大小和kv-cache长度的影响。而根据batch和kv-cahce的输入特点,LLM推理可以被分为两个阶段:prefill(context)和decode(generate)。在prefill阶段,我们输入了一个包含m个token的提示(prompt),执行batch为m的推理,此时由于是初始输入,没有kv-cache的需求,得到了首个token的输出。接着,在decode阶段,我们以上一轮的输出token作为输入,进行batch的推理,这时kv-cache的长度变为m+n,其中n代表之前已生成的token数量。
75 |
76 | 如上图左的图表所示,通过在ARM-CPU上的测试,可以详细分析在这两个阶段中上述三类核心算子的耗时情况。例如,单个block的耗时分析显示:
77 |
78 | 1. 在prefill阶段,Linear算子的耗时占比相对稳定,超过了93%,而MatMul和Memory算子的耗时占比分别约为3%和2%;
79 | 2. 在decode阶段,随着m+n的增长,Linear算子的时间占比有所下降,而MatMul和Memory算子的占比有所上升。尽管如此,在多数情况下,耗时主要集中在Linear算子上。
80 |
81 | 综合以上分析,mnn-llm将优化工作的重点放在Linear算子上。通过对Linear计算的深入优化,mnn-llm实现整个LLM推理过程的性能提升。这将是提高LLM整体推理效率的关键所在,尤其是在资源受限的设备上,每一次性能的增益都至关重要。
82 |
83 | 在LLM模型推理过程中,线性层(Linear layers)的计算效率对于整体性能至关重要。这个计算主要分为两个阶段:prefill阶段处理大量输入数据时,线性层是矩阵乘法(GEMM),这是一个计算密集的过程;在模型解码(decode)阶段时,线性层的计算是矩阵向量乘法(GEMV),这时候访存的效率变得更加关键。为了优化这两个阶段的性能,mnn-llm采取了不同的技术策略:计算密集型的优化和访存密集型的优化
84 |
85 | + 计算密集型的优化
86 |
87 | 对于计算密集型的优化:mnn-llm关注于使用更强大的计算指令,选择计算峰值更高的**SIMD指令集**来完成核心的计算,同时使用汇编实现多种规模的kernel,以此来加速矩阵乘法操作。
88 |
89 | + 访存密集型的优化
90 |
91 | 对于访存密集型的优化:mnn-llm的策略是通过降低数据的位宽来减少内存访问量(**量化技术**)和**数据重排**来实现。选择了 W4A8 的量化方案,即将模型中的权重(W)量化到 4 位,而将激活值(A,即模型的输入和输出)量化到 8 位。这样做可以大幅减少模型的内存占用,并提升性能,因为较低位宽的数据需要更少的内存带宽来读写;**同时针对W4A8的量化方案按照设备支持的最优SIMD计算指令对数据进行特定的重排以提升内存局部性,提升访存效率**。
92 |
93 | > mnn-llm采用了采取了针对输入的动态量化方案,即W4A8的量化方案
94 |
95 | 在当今流行的大型语言模型(LLM)中,线性层的权重数量达到了惊人的规模,常常包含数十亿个参数。举个例子,**一个70亿参数(7b)的模型,即使采用16位浮点(fp16)来存储,也需要约14GB的内存空间。这种规模的模型在内存受限的移动设备上部署是一大挑战。**为了解决这个问题,mnn-llm必须采取措施来压缩这些权重,使它们占用更少的内存。量化技术为提供了一条可行之路。幸运的是,LLM中的线性层权重之间的差异相对较小,这使得它们非常适合进行低比特量化——即使用较少的比特表示每个权重值。即使经过量化,计算结果仍能保持与原始浮点计算高度一致,这表明量化对模型性能的影响很小。**考虑到移动端设备的内存大小,mnn-llm选择了4位量化作为解决方案。通过这种方法,那个需要14G内存的7b模型现在只需大约3.5GB内存即可运行。**这意味着拥有8GB内存的设备也能够运行这样一个大模型。mnn-llm采用了非对称量化方案,具体的4位量化公式是:
96 |
97 | $$qx=round\frac{15(x-x_{min})}{(x_{max}-x_{min})})$$
98 |
99 | 采用这种量化方法的一个巨大优势在于,计算过程中模型的权重访存量可以减少四倍,从而有效提高访存性能。
100 |
101 | 在量化权重的同时,mnn-llm也审视了模型输入的处理方式。**过去,利用混合精度计算,即结合了4位量化的权重与16位或32位浮点的输入。这要求在计算前将量化后的权重解量化为浮点数,同时从内存中加载浮点型的输入数据,接着进行浮点乘加运算。这种方法在处理输入时需要较多的内存访问,并且由于权重是以行优先的方式存储的,这进一步增加了内存访问量。**另外,由于涉及浮点计算,它使用浮点型的SIMD指令,其峰值性能低于整数型指令。
102 |
103 | **为了应对这些问题,mnn-llm采取了针对输入的动态量化方案,即W4A8的量化方案。**首先需要在运行时统计输入的分布情况,使用`absmax-quant`的方案将输入量化为8bit整形作为线性性层的输入。然后将4bit量化的权重加载并转换为8位整数值,使用8位整数来完成矩阵乘的乘累加操作,使用32位整数来保存累加结果。最终,在整个计算过程结束后,会将这些累加结果反量化回浮点数。采用W4A8技术不仅显著降低了内存访问量,还使mnn-llm能够利用更高算力的整数计算指令,大幅提升了模型的计算性能。
104 |
105 | 
106 |
107 | + Disk Embedding
108 |
109 | 考虑到内存占用在移动设备上尤为宝贵, mnn-llm 中引入了 disk embedding 功能**。**这意味着用户可以根据需要选择:在模型推理过程中使用 embedding 模型在内存计算,或者直接从磁盘加载 embedding 值
110 |
111 | + 词表瘦身
112 |
113 | 以ChatGLM-6B为例,词表大小为150528, 分析词表内容可以发现前20000个词语为一些特殊字符,在Chat中并没有使用,因此可以将结构拆分后的Embedding层和最后的线性层进行删减。简单的方法是将前2层的权重导出onnx模型,使用numpy.fromfile将onnx模型的权重加载,删除前`[20000, 4096]`的部分,在使用numpy.tofile保存即可。代码如下:
114 |
115 | ```python
116 | import numpy as np
117 | embed = np.fromfile('transformer.word_embeddings.weight', dtype=np.float32, count=-1, offset=0)
118 | embed = embed.reshape(-1, 4096) # shape is (150528, 4096)
119 | embed = embed[20000:, :] # shape is (130528, 4096)
120 | embed.tofile('slim_embedding.bin')
121 |
122 | ```
123 |
124 | + MNN低内存模式增强
125 |
126 | **MNN推理时支持对推理时的精度,内存与功耗等进行配置**;打开低精度会在支持的设备上使用fp16进行推理;打开低内存模式则会取消一些占用内存的性能优化,如不会使用[Winograd](https://zhenhuaw.me/blog/2019/gemm-optimization.html)优化卷积;**mnn-llm新增了权值量化的模型的低内存推理**,可以通过内存选项来配置是否使用计算时量化来控制内存占用。对于没有内存瓶颈的中小模型可以使用正常内存模式,即加载阶段反量化来提升推理速度;对于具有内存瓶颈的大模型,则使用低内存模式,计算时反量化来降低内存占用与访存带宽,提升速度。
127 |
128 | 对于weight量化模型的低内存实现,mnn-llm支持了int4和int8两种权值量化的模型的低内存模式。针对不同的硬件做了实现,针对X86 SSE, AVX2实现了int4@fp32, int8@fp32;针对ARM64实现了int4@fp32, int8@fp32和int4@fp16和int8@fp16。具体的是线上需要针对以上列举的情况分别实现对应的矩阵乘Kernel,并且在原来的浮点矩阵乘的输入里增加反量化需要的alpha和bias参数,在矩阵乘计算前需要先从内存中加载常量的int4/int8量化值,然后将其转换为浮点类型,之后再执行浮点矩阵乘操作,实际的矩阵乘基础操作如下公式:
129 |
130 | $$\sum^{m,n}_{i,j}y_{i,j}+=x_{i,k}*(\alpha * w_{k,j}+bias_{j})$$
131 |
132 | ### 4.大模型在mnn-llm中的推理过程
133 |
134 | 我们以ChatGLM-6B为例说明mnn-llm中的推理过程:
135 |
136 | **推理过程:**
137 |
138 | 用户输入n个词语,在前处理转换为n个int后,通过对embedding数据的查询会生成一个`[n, 4096]`的向量,同时根据输入长度和结束符位置,生成`position_ids`和`mask`作为输入;对于非首次生成还会有一个历史信息的输入。其中`position_ids`和`mask`对于28个block完全一样,history每个block都使用上次生成时对应block的当前输出;而输入的input_embedding则使用上一个block的输出hidden_state,具体结构如下:
139 |
140 | 
141 |
142 | **前处理:**
143 |
144 | 官方提供的实现中使用了transformers库,该库提供了模型的前后处理的实现。其中前处理包括了分词,将词语转换为ids;后处理中包含了prob转换为词语,控制模型持续生成的逻辑。在转换到C++之后我们也需要实现相同的前后处理逻辑。
145 |
146 | 前处理逻辑是将用户输入的句子进行分词,然后查询词表将词语转换为id;C++中实现如下:
147 |
148 | - 分词: 在C++上使用cppjieba进行分词,现在支持Sentencepiece和TikToken的分词实现;
149 | - word2id: 将词表文件加载为map,通过查询map将词语转换为id;
150 |
151 | **后处理:**
152 |
153 | 后处理部分是将prob转换为id,然后通过词表将id转换为词语,同时将一些特殊字符进行转义;C++中实现如下:
154 |
155 | - prob2id:在lm层后接一个ArgMax即可将prob转换为id,实测效果与transformers中的实现结果一致;
156 | - id2word: 词表文件加载为vector,直接读取即可获取word;
157 | - 特殊词处理:针对一些特殊词语进行了替换;
158 |
159 | ### 5.编译Windows版本mnn-llm
160 |
161 | + clone mnn-llm项目
162 |
163 | ```
164 | git clone https://github.com/wangzhaode/mnn-llm
165 | ```
166 |
167 | + 使用VS2017编译器进行编译
168 |
169 | ```shell
170 | cd mnn-llm
171 |
172 | # 1. clone MNN
173 | git clone https://github.com/alibaba/MNN.git --depth=1
174 |
175 | # 2. build MNN
176 | cd MNN
177 | mkdir build
178 | cd build
179 | cmake -DMNN_LOW_MEMORY=ON -DMNN_WIN_RUNTIME_MT=ON ..
180 | cmake --build . --config Release -j 4
181 | cd ../..
182 |
183 | # 3. copy headers and libs
184 | cp -r MNN\include\MNN include
185 | cp MNN\build\Release\MNN.lib libs
186 | cp MNN\build\Release\MNN.dll libs
187 |
188 | # 4. copy pthread
189 | Expand-Archive .\resource\win_pthreads.zip
190 | cp .\win_pthreads\Pre-built.2\lib\x64\pthreadVC2.lib libs
191 | cp .\win_pthreads\Pre-built.2\include\*.h .\include\
192 |
193 | # 5. build mnn-llm
194 | mkdir build
195 | cd build
196 | cmake ..
197 | cmake --build . --config Release -j 4
198 | cd ..
199 | ```
200 |
201 | ### 6.编译android版本mnn-llm
202 |
203 | 测试在Ubuntu16.04LTS下进行:
204 |
205 | ```shell
206 | # 1.NDK r21配置
207 | # 下载Linux版 NDK-r21, 并解压
208 | # 设置NDK环境变量和访问权限
209 | export ANDROID_NDK=/home/myuser/xujing/test/android-ndk-r21e
210 | chmod -R 777 /home/myuser/xujing/test/android-ndk-r21e
211 |
212 | # 2.按照如下步骤编译android版mnn-llm
213 |
214 | # 1. clone MNN
215 | git clone https://github.com/alibaba/MNN.git --depth=1
216 |
217 | # 2. build MNN
218 | cd MNN/project/android
219 | mkdir build
220 | cd build
221 | ../build_64.sh -DMNN_LOW_MEMORY=ON
222 | cd ../../../..
223 |
224 | # 3. copy headers and libs
225 | cp -r MNN/include/MNN include
226 | cp MNN/project/android/build/libMNN.so MNN/project/android/build/libMNN_Express.so libs
227 |
228 | # 4. build mnn-llm android
229 | mkdir android_build
230 | cd android_build
231 | cmake .. \
232 | -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
233 | -DANDROID_STL=c++_static \
234 | -DANDROID_ABI="arm64-v8a" \
235 | -DANDROID_NATIVE_API_LEVEL=android-21 \
236 | -DCMAKE_BUILD_TYPE=Release \
237 | -DBUILD_FOR_ANDROID=ON
238 | make -j4
239 | cd ..
240 | ```
241 |
242 | ### 7.mnn-llm windows下调试测试
243 |
244 | 项目在`./windows/Qwen0.5B.cpp`下,其调用方式如下:
245 |
246 | ```C++
247 | #include "llm.hpp"
248 | #include
249 | #include
250 | #include
251 | #include
252 | #include
253 |
254 | using namespace std;
255 |
256 | int main() {
257 |
258 | std::string model_dir ="./qwen_model";
259 |
260 | std::cout << "model path is " << model_dir << std::endl;
261 | std::unique_ptr llm(Llm::createLLM(model_dir,"qwen1.5-0.5b-chat"));
262 |
263 | llm->load(model_dir);
264 | //llm->chat();
265 | //llm->warmup();
266 | std::string prompt = "写一首关于清明的诗";
267 |
268 | string output;
269 | std::cout << prompt << std::endl;
270 | output = llm->response(prompt);
271 | std::cout << "--------------------" << std::endl;
272 |
273 | std::string prompt1 = "马云是谁";
274 | std::cout << prompt1 << std::endl;
275 | output = llm->response(prompt1);
276 |
277 | return 0;
278 | }
279 |
280 | ```
281 |
282 | ### 8.基于QT和mnn-llm的安卓程序测试
283 |
284 | 我们通过QT实现了一个简陋的Android程序,目的是测试mnn-llm的大模型在android下的运行状况,核心代码在`widget.cpp`中,最后我们成功在android手机上部署了Qwen1.5-0.5B模型。
285 |
286 | Reference:
287 |
288 | https://github.com/wangzhaode/mnn-llm
289 |
290 | https://zhuanlan.zhihu.com/p/673006723
291 |
292 | https://blog.csdn.net/qq_45038038/article/details/135668575
293 |
294 | https://zhuanlan.zhihu.com/p/626428648
295 |
296 |
297 | https://zhuanlan.zhihu.com/p/639382117
298 |
299 |
--------------------------------------------------------------------------------
/android/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.5.0'
9 | }
10 | }
11 |
12 | repositories {
13 | google()
14 | jcenter()
15 | }
16 |
17 | apply plugin: 'com.android.application'
18 |
19 | dependencies {
20 | implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
21 | }
22 |
23 | android {
24 | /*******************************************************
25 | * The following variables:
26 | * - androidBuildToolsVersion,
27 | * - androidCompileSdkVersion
28 | * - qt5AndroidDir - holds the path to qt android files
29 | * needed to build any Qt application
30 | * on Android.
31 | *
32 | * are defined in gradle.properties file. This file is
33 | * updated by QtCreator and androiddeployqt tools.
34 | * Changing them manually might break the compilation!
35 | *******************************************************/
36 |
37 | compileSdkVersion androidCompileSdkVersion.toInteger()
38 |
39 | buildToolsVersion '28.0.3'
40 |
41 | sourceSets {
42 | main {
43 | manifest.srcFile 'AndroidManifest.xml'
44 | java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
45 | aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
46 | res.srcDirs = [qt5AndroidDir + '/res', 'res']
47 | resources.srcDirs = ['resources']
48 | renderscript.srcDirs = ['src']
49 | assets.srcDirs = ['assets']
50 | jniLibs.srcDirs = ['libs']
51 | }
52 | }
53 |
54 | lintOptions {
55 | abortOnError false
56 | }
57 |
58 | // Do not compress Qt binary resources file
59 | aaptOptions {
60 | noCompress 'rcc'
61 | }
62 |
63 | defaultConfig {
64 | resConfigs "en"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/Qwen1.5-0.5b-chat-android/2ddcc34390a2a82f249d1a5a6fceb4661c88547c/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/android/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/Qwen1.5-0.5b-chat-android/2ddcc34390a2a82f249d1a5a6fceb4661c88547c/android/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/android/res/drawable-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/Qwen1.5-0.5b-chat-android/2ddcc34390a2a82f249d1a5a6fceb4661c88547c/android/res/drawable-ldpi/icon.png
--------------------------------------------------------------------------------
/android/res/drawable-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/Qwen1.5-0.5b-chat-android/2ddcc34390a2a82f249d1a5a6fceb4661c88547c/android/res/drawable-mdpi/icon.png
--------------------------------------------------------------------------------
/android/res/values/libs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - https://download.qt.io/ministro/android/qt5/qt-5.14
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/main.cpp:
--------------------------------------------------------------------------------
1 | #include "widget.h"
2 |
3 | #include
4 | //#include
5 | #include "llm.hpp"
6 | #include
7 | #include
8 | #include
9 |
10 | std::unique_ptr llm(nullptr);
11 |
12 |
13 | bool copyDirectoryFiles(const QString &fromDir, const QString &toDir, bool coverFileIfExist)
14 | {
15 | QDir sourceDir(fromDir);
16 | QDir targetDir(toDir);
17 | if(!targetDir.exists()){ /**< 如果目标目录不存在,则进行创建 */
18 | if(!targetDir.mkdir(targetDir.absolutePath()))
19 | return false;
20 | }
21 |
22 | QFileInfoList fileInfoList = sourceDir.entryInfoList();
23 | foreach(QFileInfo fileInfo, fileInfoList){
24 | if(fileInfo.fileName() == "." || fileInfo.fileName() == "..")
25 | continue;
26 |
27 | if(fileInfo.isDir()){ /**< 当为目录时,递归的进行copy */
28 | if(!copyDirectoryFiles(fileInfo.filePath(),
29 | targetDir.filePath(fileInfo.fileName()),
30 | coverFileIfExist))
31 | return false;
32 | }
33 | else{ /**< 当允许覆盖操作时,将旧文件进行删除操作 */
34 | if(coverFileIfExist && targetDir.exists(fileInfo.fileName())){
35 | targetDir.remove(fileInfo.fileName());
36 | }
37 |
38 | /// 进行文件copy
39 | if(!QFile::copy(fileInfo.filePath(),
40 | targetDir.filePath(fileInfo.fileName()))){
41 | return false;
42 | }
43 | }
44 | }
45 | return true;
46 | }
47 |
48 |
49 | int main(int argc, char *argv[])
50 | {
51 | // qputenv("QT_IM_MODULE",QByteArray("qtvirtualkeyboard"));
52 | QApplication a(argc, argv);
53 | QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
54 |
55 | //模型加载放在main函数
56 | // std::string model_dir ="assets:/models";
57 | copyDirectoryFiles("assets:/models","models", false);
58 | if (!llm.get()) {
59 | llm.reset(Llm::createLLM("models","qwen1.5-0.5b-chat"));
60 | llm->load("models");
61 | qDebug() << "---------------------------------------->>>>: Qwen1.5-0.5b模型加载完成";
62 |
63 |
64 | }
65 |
66 | Widget w;
67 | // QVBoxLayout layout;
68 | // w.setLayout(&layout);
69 | // w.showFullScreen();
70 | w.show();
71 | return a.exec();
72 | }
73 |
--------------------------------------------------------------------------------
/pic/inference.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/Qwen1.5-0.5b-chat-android/2ddcc34390a2a82f249d1a5a6fceb4661c88547c/pic/inference.png
--------------------------------------------------------------------------------
/pic/llm-export.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/Qwen1.5-0.5b-chat-android/2ddcc34390a2a82f249d1a5a6fceb4661c88547c/pic/llm-export.png
--------------------------------------------------------------------------------
/pic/llm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/Qwen1.5-0.5b-chat-android/2ddcc34390a2a82f249d1a5a6fceb4661c88547c/pic/llm.png
--------------------------------------------------------------------------------
/pic/quant.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/Qwen1.5-0.5b-chat-android/2ddcc34390a2a82f249d1a5a6fceb4661c88547c/pic/quant.png
--------------------------------------------------------------------------------
/pic/qwen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/Qwen1.5-0.5b-chat-android/2ddcc34390a2a82f249d1a5a6fceb4661c88547c/pic/qwen.png
--------------------------------------------------------------------------------
/pic/structure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DataXujing/Qwen1.5-0.5b-chat-android/2ddcc34390a2a82f249d1a5a6fceb4661c88547c/pic/structure.png
--------------------------------------------------------------------------------
/widget.cpp:
--------------------------------------------------------------------------------
1 | #include "widget.h"
2 | #include "ui_widget.h"
3 | #include
4 | //#include
5 | #include "llm.hpp"
6 | #include
7 | #include
8 | #include
9 |
10 | extern std::unique_ptr llm;
11 | static std::stringstream response_buffer;
12 |
13 | Widget::Widget(QWidget *parent)
14 | : QWidget(parent)
15 | , ui(new Ui::Widget)
16 | {
17 | ui->setupUi(this);
18 | ui->textEdit->setReadOnly(true);
19 |
20 |
21 | }
22 |
23 | Widget::~Widget()
24 | {
25 | delete ui;
26 | }
27 |
28 |
29 | void Widget::on_pushButton_clicked()
30 | {
31 |
32 |
33 | qDebug() << "这是button 被click" ;
34 | QString prompt = ui->textEdit_2->toPlainText();
35 | ui->textEdit_2->clear();
36 | // QTextDocumentFragment fragment;
37 | // fragment = QTextDocumentFragment::fromHtml("
");
38 | // ui->textEdit->textCursor().insertFragment(fragment);
39 | // ui->textEdit->setAlignment(Qt::AlignLeft);
40 | ui->textEdit->insertPlainText("Prompt: "+prompt +"\n");
41 | //调用qwen大模型回复 todo
42 |
43 | //llm->chat();
44 | //llm->warmup();
45 | std::string output_str;
46 | std::string prompt_str = prompt.toStdString();
47 | // output_str = llm->response(prompt_str);
48 | // llm->response(prompt_str, &response_buffer, "");
49 | // QString output = QString::fromStdString(output_str);
50 |
51 | // ui->textEdit->setAlignment(Qt::AlignRight);
52 | // ui->textEdit->insertPlainText( "Qwen: "+ output +"\n");
53 | QString output = "sucess";
54 | qDebug() << "--------------->>> 准备调用模型!";
55 | // output_str = llm->response(prompt_str, &response_buffer);
56 | output_str = llm->response(prompt_str);
57 | // output_str = response_buffer.str();
58 | output = QString::fromStdString(output_str);
59 |
60 | // if (llm.get() && llm->load_progress() >= 100) {
61 |
62 |
63 | // //新的调用方式
64 | // const char* input_str = prompt_str.data();
65 | // auto chat = [&](std::string str) {
66 | // llm->response(str, &response_buffer);
67 | // output_str = response_buffer.str();
68 | // output = QString::fromStdString(output_str);
69 | // };
70 | // std::thread chat_thread(chat, input_str);
71 | // chat_thread.detach();
72 |
73 | // }
74 |
75 | // ui->textEdit->setAlignment(Qt::AlignRight);
76 | ui->textEdit->insertPlainText( "Qwen: "+ output +"\n\n");
77 | //清空stringstream
78 | // response_buffer.str("");
79 | llm->reset(); // 清空history,暂时不要多伦对话,真实可以固定多伦对话关联的轮次
80 |
81 | }
82 |
83 | void Widget::on_textEdit_2_selectionChanged()
84 | {
85 | // ui->textEdit_2->setInputMethodHints(Qt::ImhExclusiveInputMask);
86 | // QInputMethod *keyboard = QGuiApplication::inputMethod();
87 | // keyboard->show();
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/widget.h:
--------------------------------------------------------------------------------
1 | #ifndef WIDGET_H
2 | #define WIDGET_H
3 |
4 | #include
5 |
6 | QT_BEGIN_NAMESPACE
7 | namespace Ui { class Widget; }
8 | QT_END_NAMESPACE
9 |
10 | class Widget : public QWidget
11 | {
12 | Q_OBJECT
13 |
14 | public:
15 | Widget(QWidget *parent = nullptr);
16 | ~Widget();
17 |
18 | private slots:
19 | void on_pushButton_clicked();
20 |
21 | void on_textEdit_2_selectionChanged();
22 |
23 | private:
24 | Ui::Widget *ui;
25 | };
26 | #endif // WIDGET_H
27 |
--------------------------------------------------------------------------------
/widget.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Widget
4 |
5 |
6 |
7 | 0
8 | 0
9 | 775
10 | 865
11 |
12 |
13 |
14 |
15 | 775
16 | 0
17 |
18 |
19 |
20 |
21 | 775
22 | 865
23 |
24 |
25 |
26 | Widget
27 |
28 |
29 | -
30 |
31 |
-
32 |
33 |
34 |
35 | 16777215
36 | 150
37 |
38 |
39 |
40 | font: 14pt "微软雅黑";
41 |
42 |
43 |
44 | -
45 |
46 |
47 | font: 16pt "幼圆";
48 |
49 |
50 |
51 | -
52 |
53 |
54 | font: 16pt "幼圆";
55 |
56 |
57 | 发送
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/windows/Qwen0.5B.cpp:
--------------------------------------------------------------------------------
1 | // Qwen0.5B.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
2 | //
3 |
4 | #include "llm.hpp"
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | using namespace std;
12 |
13 | int main() {
14 |
15 | std::string model_dir ="./qwen_model";
16 |
17 | std::cout << "model path is " << model_dir << std::endl;
18 | std::unique_ptr llm(Llm::createLLM(model_dir,"qwen1.5-0.5b-chat"));
19 |
20 | llm->load(model_dir);
21 | //llm->chat();
22 | //llm->warmup();
23 | std::string prompt = "写一首关于清明的诗";
24 |
25 | string output;
26 | std::cout << prompt << std::endl;
27 | output = llm->response(prompt);
28 | std::cout << "--------------------" << std::endl;
29 |
30 | std::string prompt1 = "马云是谁";
31 | std::cout << prompt1 << std::endl;
32 | output = llm->response(prompt1);
33 |
34 | return 0;
35 | }
36 |
--------------------------------------------------------------------------------