From c136dfc47bb9239a293693b760e3290e84c1f207 Mon Sep 17 00:00:00 2001 From: jujimeizuo Date: Tue, 23 Jan 2024 08:41:15 +0800 Subject: [PATCH] feat: add web_internlm2 and upload s.t. scripts --- .gitignore | 167 ++++++++++- README.md | 65 +++-- app.py | 2 + assets/logo.jpeg | Bin 0 -> 34313 bytes assets/robot.jpeg | Bin 0 -> 34313 bytes assets/user.png | Bin 0 -> 3809 bytes demo/cli_internlm2.py | 24 ++ demo/cli_qwen.py | 2 +- demo/web_qwen.py | 2 +- .../ChatGLM3-6b.md | 4 +- .../InternLM2-7b.md | 0 .../Qwen-7b.md | 0 .../说明.md => model_config/images/README.md | 0 .../image-1.png => model_config/images/autodl.png | Bin .../images/internstudio.png | Bin requirements.txt | 8 + scripts/Gen/SparkApi.py | 136 --------- scripts/Gen/gen_Chat.py | 60 ---- scripts/Gen/gen_data.py | 60 ---- scripts/Gen/prompt.py | 151 ---------- scripts/Gen/说明.txt | 3 - scripts/gen_metafile.py | 20 ++ scripts/qwen_gen_data.py | 147 ---------- scripts/run_qwen.bash | 27 -- scripts/upload_openxlab.py | 3 + scripts/zhipuai_gen_data.py | 124 --------- web_internlm2.py | 263 ++++++++++++++++++ .../internlm2_7b_chat_qlora_e3.py | 0 .../qwen_7b_chat_qlora_e3.py | 0 29 files changed, 534 insertions(+), 734 deletions(-) create mode 100644 app.py create mode 100644 assets/logo.jpeg create mode 100644 assets/robot.jpeg create mode 100644 assets/user.png create mode 100644 demo/cli_internlm2.py rename 配置要求/ChatGLM3-6B环境配置依赖.md => model_config/ChatGLM3-6b.md (97%) rename 配置要求/InternLM模型环境依赖.md => model_config/InternLM2-7b.md (100%) rename 配置要求/qwen环境配置依赖.md => model_config/Qwen-7b.md (100%) rename 配置要求/Images/说明.md => model_config/images/README.md (100%) rename 配置要求/Images/image-1.png => model_config/images/autodl.png (100%) rename 配置要求/Images/image.png => model_config/images/internstudio.png (100%) create mode 100644 requirements.txt delete mode 100644 scripts/Gen/SparkApi.py delete mode 100644 scripts/Gen/gen_Chat.py delete mode 100644 scripts/Gen/gen_data.py delete mode 100644 scripts/Gen/prompt.py delete mode 100644 scripts/Gen/说明.txt create mode 100644 scripts/gen_metafile.py delete mode 100644 scripts/qwen_gen_data.py delete mode 100644 scripts/run_qwen.bash create mode 100644 scripts/upload_openxlab.py delete mode 100644 scripts/zhipuai_gen_data.py create mode 100644 web_internlm2.py rename config/ft_config.py => xtuner_config/internlm2_7b_chat_qlora_e3.py (100%) rename {config => xtuner_config}/qwen_7b_chat_qlora_e3.py (100%) diff --git a/.gitignore b/.gitignore index df8d9c9..092eab3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,168 @@ ESConv.json .DS_Store -__pycache__/ tmp/ -zhipuai/ \ No newline at end of file +zhipuai/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# merged_weights +hf_merge/ + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/README.md b/README.md index 446837f..ef93b1d 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,21 @@ # EmoLLM-心理健康大模型 -该EmoLLM-心理健康大模型主要基于... - [![Contributors][contributors-shield]][contributors-url] [![Forks][forks-shield]][forks-url] [![Stargazers][stars-shield]][stars-url] [![Issues][issues-shield]][issues-url] [![MIT License][license-shield]][license-url] -[![LinkedIn][linkedin-shield]][linkedin-url] + +

- Logo + Logo

EmoLLM

@@ -35,22 +34,44 @@

- 本篇README.md面向开发者 + + +该模型基于InternLM2-7B-chat进行微调,从而构建一个能够理解用户-支持用户-帮助用户提供解决问题思路的心理AI助手。 心理健康大模型(Mental Health Grand Model)是一个综合性的概念,它旨在全面理解和促进个体、群体乃至整个社会的心理健康状态。这个模型通常包含以下几个关键组成部分: + +认知因素:涉及个体的思维模式、信念系统、认知偏差以及解决问题的能力。认知因素对心理健康有重要影响,因为它们影响个体如何解释和应对生活中的事件。 + +情感因素:包括情绪调节、情感表达和情感体验。情感健康是心理健康的重要组成部分,涉及个体如何管理和表达自己的情感,以及如何从负面情绪中恢复。 + +行为因素:涉及个体的行为模式、习惯和应对策略。这包括应对压力的技巧、社交技能以及自我效能感,即个体对自己能力的信心。 + +社会环境:包括家庭、工作、社区和文化背景等外部因素,这些因素对个体的心理健康有着直接和间接的影响。 + +生理健康:身体健康与心理健康紧密相关。良好的身体健康可以促进心理健康,反之亦然。 + +心理韧性:指个体在面对逆境时的恢复力和适应能力。心理韧性强的人更能够从挑战中恢复,并从中学习和成长。 + +预防和干预措施:心理健康大模型还包括预防心理问题和促进心理健康的策略,如心理教育、心理咨询、心理治疗和社会支持系统。 + +评估和诊断工具:为了有效促进心理健康,需要有科学的工具来评估个体的心理状态,以及诊断可能存在的心理问题。 ## 目录 -- [上手指南](#上手指南) - - [开发前的配置要求](#开发前的配置要求) - - [安装步骤](#安装步骤) -- [文件目录说明](#文件目录说明) -- [开发的架构](#开发的架构) -- [部署](#部署) -- [使用到的框架](#使用到的框架) -- [贡献者](#贡献者) - - [如何参与开源项目](#如何参与开源项目) -- [版本控制](#版本控制) -- [作者](#作者) -- [鸣谢](#鸣谢) +- [EmoLLM-心理健康大模型](#emollm-心理健康大模型) + - [目录](#目录) + - [开发前的配置要求](#开发前的配置要求) + - [**安装步骤**](#安装步骤) + - [文件目录说明](#文件目录说明) + - [开发的架构](#开发的架构) + - [demo部署](#demo部署) + - [使用到的框架](#使用到的框架) + - [贡献者](#贡献者) + - [如何参与开源项目](#如何参与开源项目) + - [版本控制](#版本控制) + - [作者](#作者) + - [版权说明](#版权说明) + - [鸣谢](#鸣谢) + - [Star History](#star-history) + - [🌟 Contributors](#-contributors) ###### 开发前的配置要求 @@ -159,9 +180,9 @@ filetree [issues-shield]: https://img.shields.io/github/issues/aJupyter/EmoLLM.svg?style=flat-square [issues-url]: https://img.shields.io/github/issues/aJupyter/EmoLLM.svg [license-shield]: https://img.shields.io/github/license/aJupyter/EmoLLM.svg?style=flat-square -[license-url]: https://github.com/aJupyter/EmoLLM/blob/master/LICENSE -[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555 -[linkedin-url]: https://linkedin.com/in/aJupyter +[license-url]: https://github.com/aJupyter/EmoLLM/blob/main/LICENSE + + @@ -172,3 +193,7 @@ filetree ## Star History [![Star History Chart](https://api.star-history.com/svg?repos=aJupyter/EmoLLM&type=Date)](https://star-history.com/#aJupyter/EmoLLM&Date) + +## 🌟 Contributors + +[![EmoLLM contributors](https://contrib.rocks/image?repo=aJupyter/EmoLLM&max=50)](https://github.com/aJupyter/EmoLLM/graphs/contributors) \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..7e5d424 --- /dev/null +++ b/app.py @@ -0,0 +1,2 @@ +import os +os.system('streamlit run web_internlm2.py --server.address=0.0.0.0 --server.port 7860') diff --git a/assets/logo.jpeg b/assets/logo.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..591718d9a5a9b435ff296b56e74f139159a7243c GIT binary patch literal 34313 zcmce71zc6nw*Nkd?(Xh}LzgJs-F4{h1_AMa(jXE_r*wlfqKGJ=fHWc{QWAoopcwr3 z!T9O#-uv!-pZ|Mr_;AkbJ+o%kTC>)CXRW=@+1%L*K&Y;)rVKzJ5Woif1I~7YUa9MT z1-=)dS+_t>JFrdvU3iR`m3Ji30 zkP>x}5R(*y+4G4wz+ikLjzYG4lD2k|d}5B0!Z2ZR5eFeL7&sRy<&WXGz7J>X{|}S$ z+xq)E`USXpd-;O|D2zV{F6J6Z=j>*fE>z{(brgN z4zQJtSjzJjbP?m!nDoWg$2GsqV>M6Pj^c_*X76L`Ap4WfK|x4Y?wn8#__IMyR~PnU z#Io+9w6krhjz*JHmm#fMtOHtir7Ig7&Sgt%+np9I)c-&z_=E5c{-AZ->NltIn92(+ z?;bvxJrHJ4S}qqAoKR2y@(i2g4?=rIJ;NV#celGJNMol{S7~jqpME=v?s>!6T>41o zA?s68m9$#am;Z_md-Z|_PMo@SWGhupp?&?5%szLEB>jrB4aMZ!Q@=T_Kj<6`bd-Ne zsF2Ncl+gA_P<|jsw1OwEokWv?&YL|Eh5Sg#D&p~`(I15NimE>uO}w(AZdWCRi|W?j zM9`y3XLUM9HbKj7%y#i<5qjIQ&%dJMc5SyGl|p@|^K6PU)s^jIj!(_khzD-kJ<|;K zDzSU7{}tVlNe}_oRI+CpaM_1{s3i_>K!k+-9e>4#o#Vj$@#5WoMQ3-_B}Cd<*X-V? z!=mmg9Zo?<(KR#%Lbw~E!e)fqSo=>p1091O3!y!E!r&N3>9h*(3es#f%x@glc z`S6odV^neUa`Z!v`F#M;JNox!{au~CYy$%Q978ny9X*^_kTW>RASDv`Z*5xw=SuiBnwzVa ze}Ju*y`!3m~s+=b?^#?hmOS^qZ)I92Z7+C0Wf1h92gcliV-S03Dn3ykRC>hyucY{XyzePW$Hu^-}mm73q>U{_A2JZ*>ns43GSW-t3^|g4&>F#jV z6}yp^8AYd^$k2-|)a0Opf?%_txy@NzJTiMGsZOP>!)OPSx+50Qwy%=_HxwHC6bEy}1q_EbQLLrP)PxKvD%Wa-)#XMV8Qf3V z)V}pEZO9&Hh~uN{x?u<1u4pMZ+rWSJCTM1#BSr4L-L+-elDr0E_JD*Z0zL{TxGB`9 z`PbD=u?IRA_RD8-5U3GV_@q8h`tMwg&PuV%?axhYTa2#%Eb*>mh%`QI_$l+5KNJN5 zK<-3AG{F^7g;9a4Psavc6~umjT9rY-T~Fgpngi6YFxmqy&zP;w8J*g=?1D7{6#y zgjpjyFrb>kjA4dF`bD}i+AaYBK2ib#!NI}&oUp~Ml z1OvSQ{QTtyU>)=XQ0{_P)bt>C|`n+~cM#G(*@4tr{M51D7w zEZyquP%g6Yi*VRaS8uB!!nlNRQ7Yv`(U%EX#z1tMr1$Z;$!1Z`#NSGz=@&cuRE@bh zU0~>!pYZB!gk$N_wnR6dG@LAO76Ua=>g2dCJ_B~}DQk=D#8 zDT~k~xDFcyV)32Xp@Y;q(MfuVtm%G8;?rlwBkB}#(|%$CS6!!rOrEMlaDLtJA?lNj ze^+;R@PM4sz!v}Ex3@*;{tHZsheXrGMFG93lwn6?ukPZ&Gf!>p@O8dPV3jXh@bsfA z3`Z#Lz6yMLp{%zgz#j2tQsC|N*B+OwDsGquY7pBY!ZKj5EFLmGv1YAIM~Q_?#4bK# z$&tieWf2V=$=sw0QV({ltEdy?&SNU0`Y4MhQ(@dZnOlSFk<~#3 zQ=aj>T(w(=k_lS!V~3t*=rbTzOn_L?^&3{8j?cUb#b&l6IL8XqzZnk9`p-T4-(dv| z#t)hyh!q^a{DjdU(8tH)GNS?#u+E`E-`hI?#0y67s3vp{8em0G90n5+6%qqML*&P$ zDDv`u4XmP|%)iTe4hv8M5Eh`=P^7oavaP;6x*_f{kNUX0?h{w~b5V#Dk#&{w?JM-9 zdyLn%AmqWRK#iuj-hNH(q)2Mj!Sz1;Brj24c{=7$oWKrhI$bNY0n~d?ttjqO&K$El z@4gt{u^<=7QRlMuGyX0$0nK={iXsb$d|KV$RSp%odl`M0% znbq(!8BJMg;$l%=Q8L&kRn@_L+*6D}{E{y$u;&kMVrbkSWlCR*Sd4Boggbw}w|IKY zu0Of*uD!|HDoL2$b%Z9{@dVv2kL8p|^A_H7lO=XdIz-M+6}D_2w42*e++{=Q!cKAiwE|y19Iwu8HN3u68tz`*mK0?n zXcj=wu~Rb3fRXj20M>X_b0gMhlv$`%BS9VY1cQ>QhwRw3uawA~K(Xgr-q!ZBD^Ie6 zuNkxoC}m8@3x4H==k?2k<|*S(Sf&sbIru)?yhC@}nN6B~ZB7LL)uS7S<^lMZqQmP7 zoEzb%%P%bXpwpC?lqKz{ZhR&@@AEYjWc=$VzL5AXFQmS%Q(m!0G|gTV(md;$w1h{B zH*NOr=rDTu6|kB=KFYY??Iih8Te{BD{7acn--v7cz!>VT*Tiw7rK%*HQO!0UYP|8l zR#zP@lgEU-gzBk07hjxXq^Qvc*PpO-19?#R6MG;rFhX>!AJ9Yw0nrEo{DwyV6l{=3 z9Xdu-Y#1gC9UT>{0wC1=9D@{!ic60T7yyBQ9iRZn!3cisfHc?}{P{NGOekI42PPaW{P&;A59*M1dVQdF6u!V2p}md7tz zMoQf)gp9nqi97e6`63GS#ZPhq4O(@2o7{9EE>bvOPS+#f11gaw!)166ZWh_hBwp=dvU?tw)MT*x{orJ0P!?IrP@9tg{DU&6gjW`p8*0wFw6e7te97RzMl z8Okjh)6x{FHYx6r2VW&>Bx0=5!W&p`c=y29qk?$ZvJNyei}M*y`DCat#%PQ?ERG#c zrW=!XQM4~9U^?kLU65)l;Ou|2+9u($y%8l6CWNW0bg+f{od2@y?j}1~;3t~Ri}g$D zCKtY`e8EAlyJ5IQ^Ez9Eg|cSb|K+@zG|ln&YK1pkS11F-y!Lk+ri#>dgS;tU56SI}pGn@w8f( z!m+ESx^l0y2Zu?6;d=h-YZslE5~wXI2D~bya@OpE^dFm{-GxP=HNc`!GQR^Gp$q4< zW`yN@kAh@9?n85K(Z5;E|0${o3JIU1n)r8+6B2|;n*Z-J@bBQQfDPhA#o!?FFkh0z zj?OpHc$;k^mtmg+67?W>E<^YlrE0EjLD+4FzUPmumCl6nwkRliLiPf)tffF#rxnjt zR=8Rx75b$vR+^>T^`k5gG{t=9Bb9V-49AR~89eGxxXbz$BebODbO4(!?1{dp;4UV^ zbwrxQ$EJD7nW4?~c+?2JtM51598}b{Kkk+v7bzHgn}5W@I>$Co!pq%$DPt*^Rx^aw z?Nd(5jvHBUaGl!qQ9{{)Y*m8k+w_P$|6++^6z@qb#^-o&wZ zu(Zv=k?rtNGTdKghH2vM%#i^RztN+G?RQ)zOM1m2{x@+1J`4ql?Icfl-nb0!qLw&Y zrh-&X435tn?j(HRAAgoklACuk#L@mTYX7@}!X&992i$L2bm^-p#%1;fEFE6{zFtgChm2%#zY0w+N;O*>6>yFa)D_4DaWC-g25>mt2{cuzOT7)kc}ak;|^D z?w2`z*MhtBtF=JHhTRm>Y(;+4el!bJwR@Fv<7tt6RjGzDFl7 z+6jff8c>|+7BDhqJ1D?iBOrd+d$qwB{v;mXx5}7!tuwFEfxcDcR$BdYpTYDi;pV3A z(B7$cc=%CP)iao6vj;eRpd(m@A8d+SSa%(??628lL@)?Zx>os^r+vDdXF3(}zz=Sq z%;R+@+h!&@$tjpTsA2UnrNt^7Q*6dboWk*df2?@@-bON>V~eo?yl}8V+e>dO?ta=f z>@)9X;3avRe&5Z^LjQrn;qeX+mTg^5I(&s^AF60%cgnNY*9jh{36$L)m_5Bf`Ie5x z7UgTqCaauq&)iX)P52RQ6w0(s!}5E^(zQr*X7gv!!dDzVu5~;>P)vPhx@2F!nbJ<7 zxa-F+I)w8)lJ1Qk3YOf9Wh~W=k@jX<8#XHaBN<|~z5ER38@`+$A7U8#FG^sxM5mB? zR09pNHLbCuZIYJ$pKdyV=`BP5fMVAG{{Ta8cSkS($YT2-TMsh|QCJj(Ang296-B}M zD|dwfEsCO`0^7)-5EMaVdH~BNMit_Sf(ivs@30VboF7RpEJ6rskt-@_+)xPQd#9MM z9vBdq;NNIa8L2KTL=@-g$|mv@^gk=lG`oa8I_yTg8IaLXse&1E+XPAkoRO)wbXi1Q zIeA{iR)4d2jgkO728qA~g+v5}1tr1ka#0is78qFdfq_-^5Bxz>Y&gou$CNC8@bZ%N zx-I$d_=5xTYX4_cMFAs2Morix_=>L1t_tA!h?h~#%bs6Q8YYQsKp_$O*}!P%7wC`7 zT&sv6V<{1sq!^=tsvP({iP6y2)6pQn*3$=>8UK}z6BiVLofj~&5*87J2?`4di%N=u z7^M0iGQkL=|IP%(Urc~gfmvuzA7l(C48~0Vbu4wqKq;2)+6{}^i-vbuR>lfv*C#gN z#rRkY0;+d1L#4U%oEPlKOdnRI6IF~gG5=)(8YlhNgEJSc=M^Tee|SD{tjcGl6nH!E+ah5x&9#Re z8~V3eRc1rDB;8j+%rajq@~BVW8r{dF*p710I%9b~9P_rNl*z$oXL`M8-u^EC8??98 z$E+Xd?&Z=pw_7hCybZb{nC+-yl&{VzP|K5fzi{nqnEyqgxB4QVr@D8hig-nGUoUw% zP+^EeQVD2V^+u{Sovy{X*md8NqY0t%Tzf&VVk-A}u8H@RUPDVA89M2<9VW@7D}A_w zRzAV<=(trxc3FflqTCYOR1>%u?HV|6&0!{R zB!xK0xQz95w%?tpYl}f7OF(Md9nyI6nLB1TOkbiZ3#-_-*~7QtVu$zb=~sj#-)vQ| zlfE6a4NMO#d9WTlkdGQJ@|DR;OGj;S_2IzNX>S!9AF{?Q^|`Z){f>y=VdW_^&+t1= z5+*gh2y~}O>HDWUsS>;`3q02XaRq&pd5j~Bsa#C zf?J@)5cYff_IjtU#r2)kuxx$8o=~mIz?JV8Zl}=A{j83C(Q2v zdZs<-ndkYypN{au8C67nxZ-zD{M`|Mqyi&v{uBj_m?E#iA`vjwpLbx87|I_rdE5K@ z{4o>E^j9xvjOI^%Y>|xk|Itz4zsb@cffTqUokMaY^~_RHRcjW-J)dNqgbOZtb8Kf% zdDlj^;mm2fNzBlyjxj{#i_O=M_t>Knd<7^MwcD0FW?$Ll@$Bu%sNU#s?+S`3~W1hyNiXL&?xvU@^ z<4*m)wtR`I$W2c<%OuzFPM-cJVPkoxA*<=sK_#w%le%k)#P=ET3aS*Je_U4!#x!So z+%lGRi`uZO^WH;Os;)8e!)xLqUX60>i%aI2I>O0Q$-!3wm5R$u+1*f-mR{(#>LxW9 z_h>$TrTmz4vTjoW@L776RX`PwvOSe5zOSW!G-SinZ>+L6Po$9~^eAq_bhYi}#ZT6a z@-r!~DN@I)dyQVlJgDX1m3y8etX?TL)#-OTBJ;9@s`YT+f{f5|;Eb5FdHl!Yih*?< z(F;J&{XA}u%aDb6#SN(?326)~Nz?b?{(?qD2%@K?xDoaDzLoA=cC2=pVIG#=$%zs* z;Us_RdMK3BJTWKL(WD&X-bL}XKiGrJqrRrWUusKDxqbTTvo?v|xn0^1ZN(i5yvy2} zSLj`QdV{g2pevR(-JE9<>-y$1wG_-O+V6KKDL(7EpJ4635g@k^B*#8NV{yTho5+jPPcK4x-pPHW002@mGVXAmlre5*P#I^AP z%@WU|#%I?)nI97sE0W1um&P5roL)J~i^sW|&rm=h%%Dqx!|s3E^P@>6^vYL-mh1CG z@BEwYzU4t&Phh58-kF!_r?^@}#dA@9!TH9vp3K3F@zf?K-_B%HYv3jWx?^$%*W`;` z*X0*CW0irhmx^-n3*l}ip$FoZ3*og-x2`+1=pP2DoY-EYTC{F4#9?yr5@+g`t+j4m zQol0#L6X>cAHU4rW3Bu#-G~^T<@5$kXBzr$K!^$*g-(;zqUYtaU`*t^F(n=$USTB7j z30K-8fw}S_JLV@+LSmFF)-2r)ukBaLUtK)z3uwMXj}^3A16K}QwYg)81>pqnuN%G{ zeFGn~f)V#97qm_Wbxz>BXX8!?w~RFX#5XvIZ)PxJB#xXvhyinbVDj$I?BG8|rr)7! zclLGEgfHAdbD7`y(q>myJuQLGpO^{eACaC$$Ar;SB;eXgjytHFoV$tl%EH5;j0>4pM4VWs<~XhxIP@C!+3MiW^fK? z-O^&#Gxqd4Bb$hz^GF-9%{asJtX-yN3IUXCcm`fLuG8diGthwV$;JI4i0m$M`97{}CLD-A98Cl_sI zeg0gY@7K^j@bICV?~5lS2(13L!jafm)^43UYMsGOsqg&q-e^GeE5swx4Qi$7Fx3b$ zMkGTQ&)DpDBruNGOp(s51>eA;@KW&N3FF5*x*KaiK6gO~lK zH(3}xRiao&`qaiDMYh?n&w==ZEMS?DT!-VcjWc<%Q`e$VuM}5IV^$H*-YvOyscEt- z!4^9#0_>j17bM>vl#U;nyts#Y8nq;}j2eFcjKKRoJ!iQJ;H(8f*qq1z1ilC%_!npi3W9mRpP==B0i6F1vmR{R^mke6UW2P&fHA{dg?IZZ*!YK-WDXPN z%=%t&Rmhmu1m%p>htXWe>xVHM(UeM%?-)PH^rWk4zI-@LG_htqgLXlmK%e1B0a;nZ z{lY2}ax){p2A6c7zUHpLY&UF8o1?+|K+u<$)Kwt98*$!Kd@-Rtq;$5c6%RS+X}TKMEOWYwB-xqm)1)OrDz(FXczO689nMB4Mge zjZu`G(CkZ8e{Qc-NH~+`)V+Edlt6fh(+ep<#T;ST2bi;{K7}5B7%b{F&JRaj!Py2>KDP>1_Fys|M1}+m_vmqB-yvI7ezMe|JefHWmxxl5+ zTI=LXL-!`-h=?uL33Yb_9}oMHGh;1>YX)Aj-(|AyXVi1|ZmNk%)ajd1vd(AxQcVB2 zH>IR00-HVi!oh+xOA5p+v44Ev^jFgFpW@N)fTgrk((pd*X_O$J zUSdTBQ~r_XAi+NX*598P{b$jS!f)6l{u5LFtLW#4GpZ>fUGY5j3VCMS*WR`xHG;ur%6~ur9h2KqJ#eK6~=O1fwUX zwm6n|H`=O-x|{i9EVZj4^ImCOuxAf7>3b6OPyxPniT(Fyh|Z72Cx?DZtoQU$2DP7a zFP-ukk9~T7QVR>sNo=;78XH$o%vE68x$Rf1+LHY=U#9{cg(R3maccAGmRaS&0|A`! zH-{n@c27_SVk;*!WvN7!-CPYDJBp2O?3PZ;Y&9)>rJZ{pY;I|o^|8_`Kp-HtB=W1t$U=&Ia_#nm)%)G&Z|4;HYUUy?&ghgI$;Av zy9VvEy1BzXnE3Cb3kY(;*kK^VpfXMzuX*kTFh3igk~MLW6Z4Krm^aF)7bb7eqRyGZ z4dMJ9*udl`Y7`WRQt-1flqnxfrgK%H8=5yP*^3%DHYKg~5l|0WSyJ#eKmk?tn#y4P}xY}AXg*NEAVyuNHq5~5^ym&)fUc?QQK_p19wI=+%G znR18e*qU?Ujv4=~u%qe4E#6MGEWHNusu@+ zNmXWOQ8$aiuTQ9%7E~rv$$r(Ow^c3e5D{oX@P^-3dNt%bLt6Nm;$d0vt53I@`BzGJ zP+ckS2J5s)v!!EeweTpLlTV6Yq3(?iu;A&R<;^Tbf+!o%x1o`s8GtM&a z^$s6A%aUr7ga<0Yn+fJBxwst}=Fz1NLX0jpWhb20ybCOFtWxO=nO0;n$hLZTWC@!NuPglg!w%~m%elN_FZ0PuaY2aDLhBC+y5G|zkD1db56Ow#(@y*;w{^tIKh!k7nY>kK4H?FG-?lAC?JUQ+pMG zcyf2tvGz$^mU)+yp_*f{fht2jF_l*1;d{fBt+DFC@gA8cRRN=pvQ@8Bj2d3tXzuLs z5+AK7$yhds`>Yq9Idc5++Tkjrq(I@WU!QTH#shXZ_9txBDc%zduPg0 zD=zJA=Gbp??bdVGl(Siz`56mu=9Y8U6eKHkUu^?=Xdyz-wCbB)pW|Y%TJqPAu8Ab6 z^}Oc7>{3?bJ;Om&>0Z5zRz?e(%Y2_t&N%GeY%7fk2=US}BpS=BYIwdLMI#~~MBN*F zl7Hz!-g8-v)T58fYO!*J3%H>*?y++)2VR)IwUcy7cD-;73przkBa(o@uJI=9)4 zN=RzbrZEDdFX?}HYLY3bXn&zQSNB#xYw0dWDr5EasbqhHw*&XG$wEYI60oD-)Ayh6 z>WHr4-gmrgs}ZMv@ts#vr(GYDV)%#a1+)8=VqS*mq{91m9nz)FUI9`{A+AmUprr+H zgKtz}0w{Wf00gXo*Z=@}L;xzX4*mmr#DCS1*Jm%mS3fY&(9qFPG0@S_F)=VOv59c7 zv9Peo2ncbBD9Na)D9I=(Xy`eZXz1AJC@7ivnb|nGczAiJ83lv|xP>^ldAN~-Krk>d zv9Yj8ad1evX(?&B|J&uP7a+ofUWdL#fiMG5A_xi*-zgay**jd1?wfq&cTEQy_67wXZ+rvcP*;d zciLO&0(wm-*NR_bBW<8T)c2NkTN{b(_L2e=qaM-&NmEmNsk$wY9_)*6OpbB`*Mh^G=|BP)hu7h$b8u9uUtN#f@e-J5yT%8k%|JPE` z{65nkM0!6(PySu>J4n%g&Q$qRlmBh@kV^Yci2h*jpA%I*{A~e2&LBvo{ZFn8Xf@}? z0gesb2GxlM{LN~RlOk0?3M`7|KgAii0t3TWh5mmLf(@FWJ>{E@b9;R zK*g3v0Fj2s9uPnYl@n|wL_P2Hoe0?%6Ho%av&{&-Sqy+9fZNnR6NO2Zu#{64AO(kl zKyMZSzwrVEz@Hm%%91jv5^`Y!04b9aA{u~T0|+1i0I!iH`ur0UA`bvX$Z8|99V|a5 zgCJS`qcB3C2q2mf{MF5GlVO4f9!vl_2lgyJA0HExsR+En#IXTCzZWP(4ipOUOB>OD zlMEYVmk^Edm#&bCLn;LEeRn1$1nEf;pw8q#7RdkzDJV2p8j+I_VauX@*B7QfsyR4( z$oI2e{+d>v6L9&cUga}5g{yZ6N}$b~bd~YfVx+tV?3+Dj|=n{T64o1>wMp*z2 zX?Uo7ak|k6@HQcIj+qoF1M4>DRD=ML95(51#`VL_!G-Omtv0$+{)p=gNK9T+xRix* zOQ*NPeU{}iA)e2wUO#Bq$c6G^l0B@til=%yB_PBg8(ejCntAXnrMB;g={-j1Ep7yq z3tOHOz(g)#C5{a$(&9j#6F}w>04gW=1wc6g@LT>Sb0TQEsvwxD8qV>OVMp~My#uod zv@cNDqG+VO$|6961}lW6fIcZBC@vvR2VO6-1=7)bp*jNfkyR(naLg4L1$P7DV7XrD zkdb=%N|84xI8GEPz!VHwpSVwR^FyUoz(vIV#jEVq|~J6J$N@RmP1XpP)#3tZ=F!X6?tt84yv@UkzYEZxR_2A_qs>3iA7Vfr8FU%7v`| zViT-`yQ4gUN!AcR)s-bfdjIzV?dt`8StvNtUwg3v#IO1z0p96?HB+HsuVwXplt&Tm z6oUx&5P(zuCfL=$7CRR>d~?|t$yYW(5m)=k_Qi-v^64tFSHc9(VxRMUeZ`Fs2QZ=y zYYi&Xv~z36o(?{3_b`i#dveTNKfNue%(BiC?drB=Nab3+X1p6ll6@^~ch=8cz~4x! z_t|w5X7xzfdTp*M?HYpukx@`yGF_>w3axJbx4QlhsBX-n zOaoV&DdTcen}`PU2OT_Qh9la!Y6wX5*4=Q2BtJRxVpNbB*Ee$?yItRLhTQPEpAS>! z2xDw3Z5gVlOS3APx?cStCD8e#Vah>From0}h6+pCR#pCFoCyqR4xl%{6C%9glH5AC zlii%EyGcNK8!su%X*8gayyn-cwICN=T&HLYLO5-uWYlaNQhk`yc3!66VDp@r6j&IWz zy}!Z|5N(*1?nZRV+_S#}=rRakL7!=Ll0hEs5_YADo-H!(Jx0bA}+$GbcSz>^nuGj2ou$LYw+ z@c07#OJ{~&I$AgG8h#aa`Bt8p`B+G|w#_idMAp#y46qZKc`NQX`Qa@XMIs-Io4Y$%S4wg(gl``*#w*)H~?M)kYW;8Z%elbz6#sEgL%OGZ2gUv2gpcv<#x}|6YZ?V+mp^E(IgL}%IIZ>M4~!eNeWn{{ z$f8Anx|h{Q1&^h+OJkT?~ zM24z~S-scdxW3%RD@62S{;&znuwYVtq(-4~L+Qt~w=tW6@Os71_^;L0b=Xf>+_UWL zQ1pc3IfUk_Yd+R1zwF0Dpvx4yJ_Tltl49YpY;;2rPL~tWEaunWhtmen3BGzJ=U_@i zMNTrY7e{^E3g|{NB4H6Dm9?$%DYw^+$KMVa zMc(WR3^qn--mPOp2i^SmU(%CV*!>w+Exu1@(5YPY+s;5*GH^P`_j0+NRK zMA6+)Se`zKrN%YBQ`{f3=5LwUq-VgUeRs2O(fhIX%gFbcaR<0dt>m6|YCIoG235L? z`tN@ZkKWM;y#C|>rNre#Lxhg;slT81F_}KK72ieabnCij@Dy)zr`&P=%ze(9d)PP~ zZ>TjUj4r8Uh$PbyyOjwgr1`1t@G8BLzDB~+UO6^!(7M4qXoX(b6jFER3bgbxTF)#QHli#3gBvf2VVZ%CY9v0v_Hg_S)m^ad^>47k$* z69TZK`Q1nt@*au4QjP-EmMd0aJ)dwn=9bH>+6Oq@^oJG6qkyb&B4<{z7ngY4o4DvbHIGj*p7ROJMLwhyqTWm^2Zx<{d;3kTy=9VH7RSTyLhp(TW?lK8czr%)VWo> zl9b^%S)W3GY)>a)i_6-?HsR~mite~;My9gefN}A(Znx(&i7nxilHKX*eKLydqT#Ns zcnC48qjtv)#rCyUGKs4Abk?W*eUP;&Zjx5B)|~25O&v{1uO=tEE|ze;X1yt?}C{6n$q~`YPAfGx|(VB@s^VwcboJO=*kMu5Uoo~ z@+M_IX=%xh$#rHmFnTl7ILi^#YS1{EWldxI#gX$arC$ZQFU|fdYh%`n6@ph-4@8s) z_Shsvwew@ewX>6Xgz>xvpVQa+n^ft^+iwYhzwIAC1GunWa44h>_~c8Yu$L*r<45m) zVkLql;)t4!ECg;p|P)BrnS{sfr>H*O7N>702;)?`GJ%D z=(m%PCfabt3a|K%Z}|Da2Rz3a0{Vb}jb_@=?E|0`6#zPZ4gp3CTmS`=``%bfG|KVq zoD>dGY$yE-Y9R-fDY4`vx9aWF?MliYis`Bp{Z&z@C!d^9!2s%LB`JZzatki~7lfJ!-ou zvs{Okyt|?$kAwQP;=QDQG(-T<)fI4k`7zNtm}CK< z6u{<}MSIyUy!7gVv*kl0Zx7g)qZOIqy!RaN=dVk=!Xiw(rtYi9mVlO|Mg68ACB*_? z*_KALu7tlOPX*rOkzLCXUN1JEy;hkiq;zyKR3Yx8pcy&NB0j4)OV+WZd7UAsv+~DE ziWa#vC3qKD=X)Q`t=iURSqK@Ie->>b@wa>H_EyEiqJ%F~l;zU3Yw*D4Ga&NWPXojR zBem}l8hA;ql5(sBoN!N#A@QYdNFsXP(NtzpC%nW;ma>rl|9V{Oe$w z)^-me!h!=8K!9^Gg42N^#d+ZZz`PH?LA-1mj-!r-$PRSHIUnmy@dOduL7PDK%q=5T zKjC-qz^J`=Z03fcg~Zlwit&?f)*JU9UkvqXt?SONJ=;W+**ssI z?8LP0^WW$5{o5l~N!c4)@-n8CJ=0p=m`?f=$*ggOxD$=;DlZxiWrd~Bh}Q`= zRTOFH74h9ZO7$kv3%)~nW30-<6P>0nfR>BBxg#iBfDP};=VGz_tFJr#{f?^(wg=I% z$`0WreQtHmgkxG#pTdY~7+a-!_yVm-r9~<@@)U1Wdhy0x(hr-tUffgf$E7}KjkO*p)fJ33UJT{e z_BU}io-u084CVG^&aFv)uM_?5nYdDwS{Qjz-CZ~0HV2CjZ$kqmDJo}Vm|X4~3E1MWP(F_G#5Dyqh%?7cJ$3RG zSPH$MFm$7b1!DJVyT8uFh|HMOC1T*Jy;t@0zQ4RX<{Z7Je)=WFD&>mlb><;$ISa)| zsTdSFgq8ne1Jn4wUk$Gsvu%WLwo7dDhdo(&seW z8FHn%ripODlfq?2LnQ;>l_+(4k~2|SBEB`^c)8cB|8;v6mKA#9dl|J}p~g)PVmkTz z$6seWhqfMeInAkdmRPKZt#50lvoSj;Dx~URLrB4m#XFoah|^x_#zvPUmprz3t4{Yr|B?Cs}!U2tP9qo8HCM@FaJhpM-?C3n{|Dzu|(8#%Uh#$#IU)f!nUHzVf=0*KIf(bWsPEG=h!Wyt|R_s zEbm8d*1_gz_9di9l_()%S?3g9+UD?TDkW59xNz46yq{#`na`YZ>lzLAgNGrWg<_7g zwpyVGdU0f`9_H-qg_mmZ*j2;Hd0zJL-&S09gx|hD$jnyfk?#9?a7bgalx?lFn3d~t zh-Nfad{dp9*~Nav3#IbSaZ}CA%xrBIzJdBL9CVce;9M?CI;s+$2dPQX#<%Ud)Lr$5 zZ){11a(9OZ1XSLN6tid2>BxIo7vab=VG4a(P+f6h* zDOS{Bb?t6u&xp}JcheBIJSMoU1Kdzjpo0-KjA(KwbPf!oek**4=th7S0Kjc{J;%hSL&Juf!a>_Y{emI`gpII_V^fWpAyAuWT}iY&sPNpH)LuY-BZ01 z@?DCqU~0VMnhJ1C%D&Oa8Vxrhc3XJ%nq=*Xm-buBynJnic4SWK_neawjDx#j;~Oow z?JCG*5g;+lAH*6NT3i#wSe194DjANF8ZVmePxYu*xUeB6VqJn& zd53&zw;NV!D6YPaIFSgu`37YpU*Tb4i`U3aQdfaa{7auIbkBfK-=3c^PhgBl#a_GQ zvRH(k#vb?X>lu(^#85ppFZmnQAd0$0@gAeJ6KTP|sp}cgu`k6i9>iDtM*EgQgYKQ7 zp{HWPi{vbGl?jb`{Tj^mCs$&|h=+Qkz=sB;2&I#1H*YT5mX>~<&q5RhOl|H>TAJ%@ z@7l)hFkN0U6%+)sugJt5m=}}<%9u&fB6nXucUrNH=RV|+WD%ujt?nUSSTJVhujw}5 zsE#l}nThNtMPT}|ArN4)=tt2fBY&~i$WY7W(p03Jh?Q`x(aJz(u-0lsV$r-EzZARv zc`b+*;}Vz76F)9PH^uUi>LG1G90GzCMIvsj4ck^uE}u zD^xgRgfErm+?=^CC1-&d$<Nn*8>}Yw_(^V)Egfzul@$%H^{_{3yd6FI4=z{!Z8FZfn@+S{kXkDn!gr|4dwkqbbyA-5S* zaO**vQ@E+vIx!TnSAKcomheaKVh_obvg}dqVGSYLqpaodr`s}D*|`P$GkP~h4?}Y^ z`^`D4ZHE)uc)3j9IWD3F9TBCQU9fDrfL&(??=hJtaF5>-Y`o7^rMq#&FC?gR%*vXQUw=Ef#wf3bbxefWe3=H)aklFno34HpW3H)DYMm#S74SaHj zEgz{aOSacovug|BssDp)04>8=GHv$&Z_^{hBqmsziB;pniF^~shi7gXGT z(Hj!JTYd(3=mg$FJ8I7U+F2Tn64WzuKruS(e7xm%V%~d6+VyH4Q>pzuyoD1p6LBisq+SFWCPQc85qUryvZh}D?#YT%${)B#pS>xw4=6LpZbUbS`q1|FEzfr z!eiODCR!@;n(bPGetMz#ZQ=fo1kR?T@Mn9~#|V%h59O#W^HU$ec?G1TR$02w0zQZv z``JRW=nZ_?EBodXlNMaLZ%qiw;SLE$-nh2tjI2cX#H5}y#7oci<1K=`{@`u8kpamY zpGa&^xJu~9$nKi5B-C7APk}C5eEf;GfYXAR_S<-WH3Ut=+lP0~fcyY3GyPyKjGO*q z-^aA#U1~Bv{ZL2BhK*wBiD@N`hIyCP(`Pk9j|IxM+&UXt-yQ9PNiZ)k%;5s~ABzfS z)SH`Ldv7UlLBZpMI(OLQwH?N|&%Jj*|1}@1#-EW8l(Y3vo)oursO=8EfqKL3xb<(B zbQ002ef@ZtuU;d4$*S1A-ZH*NK7BjB%tKNlr*irKwer<*QFdLsL#mW?gD}#iAdM)} z-9yR%Lr5d7fCxy3NDN4K4c!ji-Q6J4t(2taM&IXs-sk(ycg`Q@?B8#Oxr00R+H0-r zy4E%?M}zm3MS86k%tt^30yz`qxs~H96|kwgJ**{7jHj~W%`;Y`!&E-4&F>`?Lpx?; z$2W&zeO61b zXUgdGo{w_Ki|socA!^`3)7s^H=O5J78no#^xh`n}U^ z7T=eVpUl&bO2M`^(hGWs(D5QogDsog+z1m+3v=m+@ca|SJHh1nC2ucG&2>xSLW06V z!XR_b(rh)b?K+>)?}O+C%ffS6R)-}R+XbBbOQ%jajpJ8boU_Sq*+wR1)$uiFB?km& zo_BM#m4EGje5EUy`avGXY5sX4!KT(WA#VD#=I|>bW6oVv!&?38q)(5UfT3;FrN>V$ zNg#dnN58uoV87a+sUuihB{!j@izHPnfID4AkK`p^{U~wp!#}vdNs*)BQBC?8iW?cr zOQyeO{lcB^S#=)GP|P}|oT7|E`)m4H>SIRu$Gp^0p6vD zG6T|7O6qxDB_vltJ^5LQL!Q7;CE)x3dM;(-t`^b#Z^C@KgHH54gRS928dhbr30sSE zxo@{VA?>Sbn=eR1CpAWvd$aTwG1JG_UrtFU<~E0u=$~~-NIAYiq#PFd)5`)h@fW&z z{97UomHc<4@;lr9ne!isW4%8efIFjp$^)PSRk1r@AQ-=;>i*l8yaNk5PYKi2as5yx z(BVUI)XltCA;Qn&eI5bh4+*h9J@Gu?-VMWp9!Rwwwe$D5vgX%kH}b9Bg!*p; zVKUq+1?Bzm381^wLEfx|>uf?FwUCIADe#P%2JAZE@~hRD0~1lUmQK)kYD^%F&O~Eo z(zvz&(k{Snjx%85hjlxv#z?=O$YLmE1n;JMeobOf{WplmB-oYr;p3=oRZNO8Qh(!> z_F#k7!}R8&+-zD*C7W&6UF)Aht_Lett8d?oMsu6x(mz9QCo2HAge+)tcUNm_6%PUd z(Dl7?@+SFN>d3Cq{XF$aVt;q4cjSXA;=*9l>7c~DC=n}{G|G~If-yluB8`G50i99q zM8fXIYh-5p^m;+0hthaz*4JyW(WX+eZ_l=wIJQZPR~J)--BgupX4EfGzI}YwLj276a= zMZZ9ge2HPNSTt;=h40C zxx3tDtVgnuuZ&vstAch%pS;I#J8T$ZpTnHgF!r&18rpApJn$6NxjX@7MHKjmg z%hBSbNeJnd;+B8zc}U{kAX)wxiA_bhgAwBy z`{$x2mw~$~?s30B@lD6)n-F=)$}7Yo&OD~H|KwB^b*ga&$rh;wH;r;CM(?I$w1W|M zUjqe!UaH9pS`;j?bSw@OY!BQ1AbnHJZ=1AEGkWCdJCZVugwg>!oF}#vo53e8o~qqB z^s8e$(-&;P7)2AhdNCGuP_`nIrk0*%vsHF_S%Y@Xe=B-Wh?_kTHz;jhNV4u%9e+~Y z4ItG;DQX4;HDVaze<*T*KUrD*olS@@;{)+m9v!MLhy@Ayy;cl^r-^APeCeA1c>+ZU ze(zPHm%#Jm?i&kZcH2+r7=SE5%pe4?Li_Ny?=yf~= zjzfCG1Kd4*_Lrf~not2rv`u-=vzYB!;m*fpgP6HmGl=OC-F!aWrjbrN73IG5WcfhL zyIGnFTv8wxYLCoXuIE{oq(%4W2gIdLd3Sl0UD2A(_}r=-q4NVK1z-DB(x2MX>wNYf z5|0i0=p%4XJ0ae@i(7jYsqd`$@~BA{$*-U{^ZKb?GM99jQaHUP+@Ft~rnZb;%64}3 z`jl(=IVBBD1z&ZIt+Km*H*21$Bj6@`z9CU0=IBJdFX|NlXAxm2E(>k@b4gpzkh>b+ z^hEg+Y?m=C7HqX%F>#J`*Tv~=xlBS2j2@H1ozd{#!KQ!;`4}M0dp!ihY3H}B8kf0J56HcV1TsmajUb{93J)El1 z<$KSml+yV|UV_qCx0X|hfQqDccqPMK6~^4)?vimGr?mbV_VkHi^TcGYmRW|JaA2k1c$9SyD6bTw+)rV@pEnL&(D-+67t4{x`v)tDmllXN|=xh4@y)|X9U9s}${za*WFdQkew$^G zZj1ZdGBY0EC~aGJmJuC4RvZqXSZz(H<;}AlEFn97tlHnLCH^VvKMV}#UT?v9Q=sTA zK5F$lZ!#V-_=76HFakU~8IUfS>vQ!PxVmlJNKH7}TID+WwCh;&@gtbu)N*x59o)gj zCgV1{L)h9e0mb6e_VrUh-LCTu*M6-^SJs#KUdk(PeQDTj0xjI9FEJSS@raKo?ErCS z9nW)I`9mY(qviFTcMvphp(DFZRz8|xRl(SfacD>!WP#*RRd8CTwxC5S&ew;7L;vQK z4P4xbI0gHE3xTd{uP%JAO?I9J@~1YEBfIkAg6I;6Vo6Ucj*3gAW0e|VGL6<{0~7N( zrmgK~5ZkA$yFlP564TUKSu^tb7f23&j$}F~*D>zrWb6gkgDo&dnDnh23}__4ZG-$# zL+vEGZJ*g5*Nn$=o%OVZd@>}dqi;~({P@+mA>RSZlj0 zN5yx?bX~+TH;`BZv6+Z7S;~u*LyAE3clUM4CD9TKYv{U+y}_`%SpY<`cGJfG>z>4%CT9Am27FyC%s zN4ZkKvzb7AT2=$(O#XBvcV_V$rqJx(iYN$U24I!fp8KjH9px+t z3#A?%L`T{IrjE8VHt3SHXxy2$Et=hGQYfnZh^)N`uU1m54W}6?Tq>>14R~nw-X^V8 z|Ii~cq-jy(%|Yv$46SWwRrn$G`16LE<_*~N!=rWMcU!5DJ5R6=Y%cP=VoAZ@ZddL7 z=PI;6Yc!o6H=Yh50=JBfo~C%Xj}@SArJEcRy_jHH*cvEL{29 zCrmJ3ei>1-RrUeQ8v~HGieZ;2DX%^HUc`C)^7*JFe+LkV&S&57WjBTfUdVdG9s>+1 z(Ae;Sa1>pL9z;5J$Se5Ld$~(v`LgXGJfCuw5Nq%U~dfd}Tnn zQvj=hmGv|r;<;`}HKhc|s4d#?OuESm<&QLq>& zO@HYiZ|Btqw@t=k#9y|B>}hM@3JJMWd;2_ZTeZwLS}5^&y$->2gg;40SCuO}gd>01s~t^} zyPDDqzjH#5TDmx^@ckFWRi5%d=yIMr(eoeaZE|>a5y5HP1P_WBALk`rACbJT4?w}9 zSM|o}2;X_U`c}j!1c$jUB~>auD5z?JZ+KP45SDAJ%j5RNF_Shx7`nx+UVP6i$= zCDmuB0cAd$&!2bsLxX z;&U#0mWhkiqx9)4s^{vMooj>MAd~oa@crht!NG;>9nx7ZQXnXsrQ7(Rlg)UI=|bJFh~E< zv{|vrm+y}Uxm`%pNvM5%$$Ry?fnbLG^G-34X2^!qX5gJpOnh%Ll zC!E7)zx@nwX8?+d&VrU{`2o<(-gDO0iLrLcZ2qvDC*=9XE7l+{BzYaq?eh8KeBL|k zV8De4Q_X&~o&|7-O$%kFEKW4ii7mXQ@2uUWiId}yni-=Kom*rYNY6>`YNF1aX^1+$ zOe>Ucu*a7)87V(ZWW)e~eWxjd(;Z2h=fy_)s|9Vr$XU`hcK>5-`Pi^=oBla1R})65 zsn9Jj8&`+)c9r`q!IEm0IGh;tkG|xIXau#T+-3*^>$izI<}KCmo8bj+38elB9Wqnh z`D%3{luE8s2`K#dzjmIv9XkmStC7#YgFPTPWS23r9ze;B{*pedEO$Hew{?dtf;sI8SionIhXQEF(PK~U;B%P&x= zF(A)Oui9hae5MDk)e6O)p0U}0+Y@*eT}6{Dyvd{TOG5etjaS}Qe537%I#u=x&z^#T-Fk+-gq&S|nGJwf`~p!0A3MM2 zeVoESj*p>E?1S5x>lpiU0tlJNw!l;-U&a^zBuW5!fz|GPi&p$LDh?DpN#(ZV510&Z z4ww7dJax)w!vZ>U97r6kzzz^l`FC|lG*-vDA;IwM$#k(5A4EirdWp!Fv0O@1WHLH$ z_%O?ziPU8J=kd0?bh6g_NfV)$zd)E-yvkV;89vpN_@f2f{`8f?^B&r!y0&gbla7=& z@6H;CmfS?#=Q#HO!LpS`Wk^hec>EJWJv-e^K#`G9)pPTdvkVxS88z&j)v}Qs@0H}_ zCj42UarO&qk&9ubDqHkRd{zfadG(Hvhm1NpD<4;Cgn(Cy4rqfg&_SrhVkhJgL>jN* zsG_hY-Wf|B)`XHqry9QehQ5~Pkml1G!*MHS7I{65$~r!pyr9{zEQ&12T^;MwwoB!u z%!S7gZmkr`Dwc@Y98Dw17$>JP{zZg~>{ zVAutL?$ray_Wx)~-T=iUo+d>qPWn}sNe2q4mP^rX7RW?!^_XLE8N&Ng0%Qm^SnXJ32Y zW`JmzBW!kg^F5=#)6+7jIGki(yFF-aU>~B7^nj@JgVJun10jGsaCJBM1sXFsPyd|O zN1gagfM!B5d?SFcdKjdsPVA@Q@EfWFA|wE+vyBA8rN-NE30Sz>Zggu1n)Co_fZ~Qt zW<FjqJc8-^}%DL;zvB@Z5w&#~LjgGnJrp!)k9`t*|dKPQ|NwJ~UdR>J16y zTG)52l_WE*A~vMH2=7^&jIFGtX+Uz`xc#iVxP5n)=H7ElIb<+Jw}zwIN?(7A<1=Cl zWlaz2L=uuG$a4_Cf^<^)T5cj1JmLYKx~;Z44Uj}*GUGWpksU^l7+mDI`cp)vmoqR| z9WIVLy_~28#$@HpjT-aCB7|!{Rn`^6Cn+p_{N)y}eyzM&0V(P!)1;St8dWRK!+^I+ z!^|#pD+$g6V4l^o^mmJ7Rx<=W)8WuDrs1+hvi71^l`dNpUF|B;MBog`1K5S!q7RKX z0a;rRq#RvUVZ*-kaGS*a9h3eHiQLv(LF{XWcC9vh8btmV_5sGzn^W!_`my~x06M<5 zkukAOz6Z5Sy$7Apw+pI<<(k9*Q*Ojavy@bg9^bW1>uKC7x;OiMG4(q~(8QHytpl2f zC7OcZdysrB^}KTuLMYB(#rHtz4g1Bt$%gKnyl*evUUs>>f2F<76M%^NQMy0vxYaHU2%r3u6y*&>jWD{(@InV9t)l0qN;8L7=*`!rD zE#()E#j7T>lg#7eV%+CgVrl3gef&HO5n8lXBJvFiIn_0Ie;~>15qCoBu2GFZU5%p) zg2n+tfY@8GvTgLObl;`&yDrOb(VAo%u>STiYS8E2qx}2%PTE3(2=L{&YFOMuMoyJT zA4bM|rDkhitBF}`rzfj8g6D$*V}c_)D1$2=Mgn}k6kw45j^2pg-fF5){$K`wG*$oh zxVQC47+B(dSYjkSqnBu}*j}q$7l|9F;{}(mVWiGIDM@q-H}CY&68)uY2^fhwc1iOWW!f zQuEAA$tyE{l#Rs)P~wgT=X(l8a4v#Ix|V5?m@3+|N%?QA+M`Ua9OF!dvUoc+P4;F< zNf4Y4`JyrQa;2niXsGsXdecbZ>m4n{^~y_Hgxl#WqdB z6Re~+JiJ>h&}evVk%9quvqeV$ns5iKzdRc2x61|)8 z$^PT*I7j3R(n*_Z;(!$pLDN+)HZ&LkA%J5;g+bu)aSkdy48S2^jG5M|va=m5r zFZ_n0#&;I$Cban=jbnr2<>@H0=|G(y9ROPWrE>m1iX8#NR0XUUs3N2G0o^}pl!>HQ zP0m*0w~80xW~8YD_jNi8FvF_s-mF}K06mPLB_SL)EYQ$-oXIn|8z;1A_C(3Pa`%U4 zu7e-3p}G1Nky68mI<>5|9hnRpx%JtXQtl)&I1{)gGtkna={t<|e9$qi{kvHdHJmh# zlgd-3P&Rmeu6mKpScDlwL_wLITSPaWE}Amm79DV0UahW2rH$+jS>2ODjED)ssk3W`i8?~lmiFt#t^u0+2iKSa3a1X4IVID76t!5wuPLd+7 zpPVCfyH0pR)!vt_8Vmm@$4_;Aq1n4*?|D5tSEGwpCHJws3VAP}C>hI&XpD37*l_%n zEsuqSTA2ghov2##tzKv19f;1Su`9jgX6wy+$wyMv$AD1CYZx8l)|!Ea`Uwyl68kO# zy2BQrO1K&YHWx5rCc<>L8J^_vQr?9WH@!^F+*VEbaKh{yT=h9M%?HHv zH++e#;jh-Bi!nO;15vvT@B__-wve?+fZ%DVtj7&l9@#vcf;7fh?5ik?Oj$2X)_nnk zAEexAK2E|AGvf-!wX1e+@0YyHayw3Hf(ba%!11Let*Aetv!vmN+}4`@2blJ^fL7w! z-~8xrC6_-PAXfr(iROP885Ds2B=-ItJEJapV*%Yxry3QTb_wsR)g*gTD0|@6cV?3p zzMJ1Ea5Y9u-XMx9YQans27v{^xQDP>VlAN(Si#FFMmRtZXs+C$qOgvJ>gfd^o7z8p zLZs98G?D0B|M${kc(p8u?Y0ILAZ`BRwf;9M^>)BR2Z0>XS+tqhs@X{dM9KD^eb~;f zHZ$3%Q~yq$1RcLrNoPq30k3Ns+p=vkVKR8?t>fFU%4Zh;{P9uKMktr-*dWaPz#tU9 zS2>yBFYE|ci+HFG4`t=!+S4h0$fvjd2t(?7W5rGGFQvYy{ z$*m<(p?yYu+^XB3)9SKZ;qLUYFA17e;d;4xuh1k@6i>_H%0i=pa(tUmWIy_&eJ@y|@Kn7sU_M zF>d1uKpIX|SXa^ah5tcVQ%JnW+X6+ zJsX3hl)|2liXnSkINVVz{2l$OvRuE$hKan2LVIkyFYr2GReGD&Eox56M~9`FzZLGl zQv}+-1WOn`Hg7c%nkRPYc+wH9w60dn4gM&#`_TGoW@z(MO_9^^N;$nzYS(`LAH?P* z3$GUEc64ou0#Z5W)VHEuxilmoMPxja(iMbp6=WX;a+98!v0<0s7S!w)pDX-ijnysq zH1wKrxlf@jF4k}Qu`wS79yRZTHISJ}N&-%j=(S94>u2T24;G)cG+8BmFpv#9i7O4R z4g0feP?2i-Vcx3rw=Z8|avI*}ALx2%U;O2m~ zaeU=B!R7TdTpY?!IbJtOt;4N82+JkD9&wGAhz3do*_C&4I*%ltc*XFlDUD(X!P=zd zw9@%Uk1XPam%w1=MtkUFu_b)O0nhjKwF>T5r&YW`v}e{TmW_4QOlCCnLDqyKT#(Ix z_yLtTFi>TIe%o2oU&H6W?9A&g$pRz2EHNnv!y6F2QAacLy%7Se%rB?C>}d2He3B|Y zmY0{&jPKydFZ$7XdWx^-6*zlDqp*E?DteK!J!y3UrSCM@( z5pNT#OeHp&WSH^P_=8aQ1f|u09Jlt*UHD3C4HM+DW+o&dKD?hz0!KDR>5^Am6a7 zJjFQjRF#<)(alj8mM^}#^UuY-&3%ZZqt(8MBn?cw{^e+I1^``~!TQ?W^KxwD0bm56M>>cBOem z;$BMflKR+x6=nJbI@x@XCE+!kxBYoY`lmguLKVE{!8iOl3ys%g>bzVXi^=jK$${sg_Xa zq3v7cByqGSFogdV3zHqb&W+2a_cs5)fS@t)IZ4ke0iA*8~PnH8#{#g)C09Lgedf~l{J#; z2(8f4m&&y5U-K>N=;WJC=dTbw9plB$H-=5e+s@ZJ0+l8+Zr8y#W&sny1mx{g?z`6N z+jj^|Dn=`9!`Zn4Vy?I~oM0L3FiG#ybcBoOX85J@*^AY&P^+i&^5gU2v_uU@Bz?4O zUJCCX9QaMrEX-B->{@*GDht{W4Gr9ihTNT!)N1xCmcHRx4L{)jfcwL-rScUIpY{yo z%U;dp*a^KMf{N4m%1nc0Y%7X4pI=^sVOg$ELV*Y8u0>}}84;bWbqfCKkZOXKl8Jn% zsnHCHX%_qe$$N_Jgra>J*YuRvop@=>TNp8ImD4;n`qmBkZXHlA_uEdgIIRl`3IZl^ z@JF@EMeBMetjs7)^5Vzvq#cfcL?DgEs5On5liC}$D$)g+C}4sWfOVn@S6XBJUUml& zcPoi~s39bGWh((diz#&Ep98@nd@0kjvXuv6Q ze5*SsB&5jsO8=3Tya$EBU5kC?=c4y<#XY`AN0736QlCgP>7ZRsIP5} z$6Gj-JMNA9V-o5XDK+d=LA0$9#iQ+$$;JN10jU$Z#hb(LZBkY)B+d)?t}7qR2`Hvn z!Ys69L$4w6H;ec8;I%}fY`;K|%v_sxV5`F87rB+;SeAIu8hY*rc#YI78mbwheNRwa z^dh7nJ==Bo^TaFw6$6Zv{@$^I$Z!t`FpWV$6$w`$J4pCi0>xN3UYhsdAnKA^=< z43}R6H}!76qqx(5$Sqs5pSBu=$P1T(Kq}PgAofvUd(~pnzIQcdUB>fkl`kUJm&-YD zIE4_aQj#9&Wlq~pQA>@o=1Mr5N|mNQ!47`q&vL3gvqDF=*ke`RlO3u=&s7LzkWd=# zvuCL!R6L(+xh~sh%`!|FyZu?=ECy_0&Vt+&D0(@doenAu2SmnQl#z&-r@j9{m2#JN zlJ)~{c=5zb!Bt7khV^By*~F+sd1TI8Kgln^js0n)fU^#UdDDR~LV-S+0Tc!PYr|iE z{)UI|8gW5bZ*todTJ3n=y8y$y9Rx>Zd7sw3D6(RN@VfNi!h3Q~r$?A^gm>~2^L4VS zOyAdYw^!~C&`Y$o~m!E}V;sqBiMK0H+M!GmfU1mh8t+g9j|PXjnr zkftQ7DySNqRwjmx4NRtVE8hf^_oSeEMruZMtRU3sSP+(&50GMcgMeP=4`KmQ6@1iP z4fS)`n)8=Z^OfDt-lVSDW*5iu$;5<`TxiExt3ifx#3!V)C$y77VkH;TVEk1OH3Gh* z0`n?<#G&M(&7^-NR}5xL`{!B%8i?b=Y=#vk^3ls5yGzMz9P_vPV zS4@?KiKAQ0O}ag29dq`;Tjk4iYCOCBF>JW}TB;j|HQ|cr^mkZ8+%P-&4qFG1BCN!v%Ye`crpuRH%^`FEkk zE%uo9_e1+LQ`7I=ZtGkD_~Re{0{ZK0|6KcjTe=+;{-fQR42P=sZI}AHq5La80sfXw;se|dEO-0A;e3CI-5VC#~qpzPa6 zh3yTB17Y;N1`bHLi~>BkMUm#)53sD@jdAc_&qMcHOK|&;F%D>!LBBtL_MroD>3aj2 mGt%R)|NqyqZZ~rQAHtPo5K;`)kmt-By+%iK2Xz0}%>M%K5lg!O literal 0 HcmV?d00001 diff --git a/assets/robot.jpeg b/assets/robot.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..591718d9a5a9b435ff296b56e74f139159a7243c GIT binary patch literal 34313 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 literal 0 HcmV?d00001 diff --git a/assets/user.png b/assets/user.png new file mode 100644 index 0000000000000000000000000000000000000000..1b2cf2e8defc6df1d326d869299d6bad9c327da0 GIT binary patch literal 3809 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 JY8{uD{{aEA0G$8; literal 0 HcmV?d00001 diff --git a/demo/cli_internlm2.py b/demo/cli_internlm2.py new file mode 100644 index 0000000..6886802 --- /dev/null +++ b/demo/cli_internlm2.py @@ -0,0 +1,24 @@ +import torch +from transformers import AutoTokenizer, AutoModelForCausalLM + + +model_name_or_path = "./model" + +tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, trust_remote_code=True) +model = AutoModelForCausalLM.from_pretrained(model_name_or_path, trust_remote_code=True, torch_dtype=torch.bfloat16, device_map='auto') +model = model.eval() + +system_prompt = "你是一个由aJupyter、Farewell、jujimeizuo、Smiling&Weeping研发(排名按字母顺序排序,不分先后)、散步提供技术支持、上海人工智能实验室提供支持开发的心理健康大模型。现在你是一个心理专家,我有一些心理问题,请你用专业的知识帮我解决。" + +messages = [(system_prompt, '')] + +print("=============Welcome to InternLM chatbot, type 'exit' to exit.=============") + +while True: + input_text = input("User >>> ") + input_text.replace(' ', '') + if input_text == "exit": + break + response, history = model.chat(tokenizer, input_text, history=messages) + messages.append((input_text, response)) + print(f"robot >>> {response}") \ No newline at end of file diff --git a/demo/cli_qwen.py b/demo/cli_qwen.py index 210b6fe..7dcc8f6 100644 --- a/demo/cli_qwen.py +++ b/demo/cli_qwen.py @@ -16,7 +16,7 @@ from transformers import AutoModelForCausalLM, AutoTokenizer from transformers.generation import GenerationConfig from transformers.trainer_utils import set_seed -DEFAULT_CKPT_PATH = './merged' +DEFAULT_CKPT_PATH = './model' _WELCOME_MSG = '''\ Welcome to use Emo-Chat model, type text to start chat, type :h to show command help. diff --git a/demo/web_qwen.py b/demo/web_qwen.py index 9769f81..b47ebcd 100644 --- a/demo/web_qwen.py +++ b/demo/web_qwen.py @@ -15,7 +15,7 @@ from transformers import AutoModelForCausalLM, AutoTokenizer from transformers.generation import GenerationConfig -DEFAULT_CKPT_PATH = './merged' +DEFAULT_CKPT_PATH = './model' def _get_args(): diff --git a/配置要求/ChatGLM3-6B环境配置依赖.md b/model_config/ChatGLM3-6b.md similarity index 97% rename from 配置要求/ChatGLM3-6B环境配置依赖.md rename to model_config/ChatGLM3-6b.md index 13dbc4d..7ffca43 100644 --- a/配置要求/ChatGLM3-6B环境配置依赖.md +++ b/model_config/ChatGLM3-6b.md @@ -2,11 +2,11 @@ ## 环境准备 我们实践了两种平台进行选择 * 在[autodl](https://www.autodl.com/)平台中租一个3090等24G显存的显卡机器,如下图所示镜像选择`PyTorch`-->`2.0.0`-->`3.8(ubuntu20.04)`-->`11.8` -![Alt text](Images/image-1.png) +![autodl](Images/autodl.png) * 在 [InternStudio](https://studio.intern-ai.org.cn/) 平台中选择 A100(1/4) 的配置,如下图所示镜像选择 `Cuda11.7-conda`,如下图所示: -![Alt text](Images/image.png) +![internstudio](Images/internstudio.png) 在Terminal中,进行pip换源和安装依赖包 ```shell diff --git a/配置要求/InternLM模型环境依赖.md b/model_config/InternLM2-7b.md similarity index 100% rename from 配置要求/InternLM模型环境依赖.md rename to model_config/InternLM2-7b.md diff --git a/配置要求/qwen环境配置依赖.md b/model_config/Qwen-7b.md similarity index 100% rename from 配置要求/qwen环境配置依赖.md rename to model_config/Qwen-7b.md diff --git a/配置要求/Images/说明.md b/model_config/images/README.md similarity index 100% rename from 配置要求/Images/说明.md rename to model_config/images/README.md diff --git a/配置要求/Images/image-1.png b/model_config/images/autodl.png similarity index 100% rename from 配置要求/Images/image-1.png rename to model_config/images/autodl.png diff --git a/配置要求/Images/image.png b/model_config/images/internstudio.png similarity index 100% rename from 配置要求/Images/image.png rename to model_config/images/internstudio.png diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..de40ca1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +transformers==4.36.2 +streamlit==1.24.0 +sentencepiece==0.1.99 +accelerate==0.24.1 +transformers_stream_generator==0.0.4 +openxlab +tiktoken +einops \ No newline at end of file diff --git a/scripts/Gen/SparkApi.py b/scripts/Gen/SparkApi.py deleted file mode 100644 index d0edbfc..0000000 --- a/scripts/Gen/SparkApi.py +++ /dev/null @@ -1,136 +0,0 @@ -import _thread as thread -import base64 -import datetime -import hashlib -import hmac -import json -from urllib.parse import urlparse -import ssl -from datetime import datetime -from time import mktime -from urllib.parse import urlencode -from wsgiref.handlers import format_date_time - -import websocket # 使用websocket_client -answer = "" - -class Ws_Param(object): - # 初始化 - def __init__(self, APPID, APIKey, APISecret, Spark_url): - self.APPID = APPID - self.APIKey = APIKey - self.APISecret = APISecret - self.host = urlparse(Spark_url).netloc - self.path = urlparse(Spark_url).path - self.Spark_url = Spark_url - - # 生成url - def create_url(self): - # 生成RFC1123格式的时间戳 - now = datetime.now() - date = format_date_time(mktime(now.timetuple())) - - # 拼接字符串 - signature_origin = "host: " + self.host + "\n" - signature_origin += "date: " + date + "\n" - signature_origin += "GET " + self.path + " HTTP/1.1" - - # 进行hmac-sha256进行加密 - signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'), - digestmod=hashlib.sha256).digest() - - signature_sha_base64 = base64.b64encode(signature_sha).decode(encoding='utf-8') - - authorization_origin = f'api_key="{self.APIKey}", algorithm="hmac-sha256", headers="host date request-line", signature="{signature_sha_base64}"' - - authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8') - - # 将请求的鉴权参数组合为字典 - v = { - "authorization": authorization, - "date": date, - "host": self.host - } - # 拼接鉴权参数,生成url - url = self.Spark_url + '?' + urlencode(v) - # 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释,比对相同参数时生成的url与自己代码生成的url是否一致 - return url - - -# 收到websocket错误的处理 -def on_error(ws, error): - print("### error:", error) - - -# 收到websocket关闭的处理 -def on_close(ws,one,two): - print(" ") - - -# 收到websocket连接建立的处理 -def on_open(ws): - thread.start_new_thread(run, (ws,)) - - -def run(ws, *args): - data = json.dumps(gen_params(appid=ws.appid, domain= ws.domain,question=ws.question)) - ws.send(data) - - -# 收到websocket消息的处理 -def on_message(ws, message): - # print(message) - data = json.loads(message) - code = data['header']['code'] - if code != 0: - print(f'请求错误: {code}, {data}') - ws.close() - else: - choices = data["payload"]["choices"] - status = choices["status"] - content = choices["text"][0]["content"] - print(content,end ="") - global answer - answer += content - # print(1) - if status == 2: - ws.close() - - -def gen_params(appid, domain,question): - """ - 通过appid和用户的提问来生成请参数 - """ - data = { - "header": { - "app_id": appid, - "uid": "1234" - }, - "parameter": { - "chat": { - "domain": domain, - "temperature": 0.5, - "max_tokens": 2048 - } - }, - "payload": { - "message": { - "text": question - } - } - } - return data - - -def main(appid, api_key, api_secret, Spark_url,domain, question): - # print("星火:") - wsParam = Ws_Param(appid, api_key, api_secret, Spark_url) - websocket.enableTrace(False) - wsUrl = wsParam.create_url() - ws = websocket.WebSocketApp(wsUrl, on_message=on_message, on_error=on_error, on_close=on_close, on_open=on_open) - ws.appid = appid - ws.question = question - ws.domain = domain - ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE}) - - diff --git a/scripts/Gen/gen_Chat.py b/scripts/Gen/gen_Chat.py deleted file mode 100644 index 945fde9..0000000 --- a/scripts/Gen/gen_Chat.py +++ /dev/null @@ -1,60 +0,0 @@ -import SparkApi -from prompt import * -from tqdm import tqdm - -# 以下密钥信息从控制台获取 -appid = "" # 填写控制台中获取的 APPID 信息 -api_secret = "" # 填写控制台中获取的 APISecret 信息 -api_key = "" # 填写控制台中获取的 APIKey 信息 - -# 用于配置大模型版本,默认“general/generalv2” -domain = "general" # v1.5版本 -# domain = "generalv2" # v2.0版本 -# 云端环境的服务地址 -Spark_url = "ws://spark-api.xf-yun.com/v1.1/chat" # v1.5环境的地址 -# Spark_url = "ws://spark-api.xf-yun.com/v2.1/chat" # v2.0环境的地址 - - -text = [] - - -# length = 0 - -def getText(role, content): - jsoncon = {} - jsoncon["role"] = role - jsoncon["content"] = content - text.append(jsoncon) - return text - - -def getlength(text): - length = 0 - for content in text: - temp = content["content"] - leng = len(temp) - length += leng - return length - - -def checklen(text): - while (getlength(text) > 8000): - del text[0] - return text - - -if __name__ == '__main__': - text.clear - file_name = 'train3.jsonl' - conversations = [] - for i in tqdm(range(200)): - Input = prompt(random.randint(0, 16)) - question = checklen(getText("user", Input)) - SparkApi.answer = "" - SparkApi.main(appid, api_key, api_secret, Spark_url, domain, question) - getText("assistant", SparkApi.answer) - conversations.append(ChatGLM3_6B(SparkApi.answer)) - for item in conversations: - save_jsonl(item, file_name) - conversations.clear() - diff --git a/scripts/Gen/gen_data.py b/scripts/Gen/gen_data.py deleted file mode 100644 index c73381c..0000000 --- a/scripts/Gen/gen_data.py +++ /dev/null @@ -1,60 +0,0 @@ -import SparkApi -from prompt import * -from tqdm import tqdm - - -# 以下密钥信息从控制台获取 -appid = "" # 填写控制台中获取的 APPID 信息 -api_secret = "" # 填写控制台中获取的 APISecret 信息 -api_key = "" # 填写控制台中获取的 APIKey 信息 - -#用于配置大模型版本,默认“general/generalv2” -domain = "general" # v1.5版本 -# domain = "generalv2" # v2.0版本 -#云端环境的服务地址 -Spark_url = "ws://spark-api.xf-yun.com/v1.1/chat" # v1.5环境的地址 -# Spark_url = "ws://spark-api.xf-yun.com/v2.1/chat" # v2.0环境的地址 - - -text =[] - -# length = 0 - -def getText(role,content): - jsoncon = {} - jsoncon["role"] = role - jsoncon["content"] = content - text.append(jsoncon) - return text - -def getlength(text): - length = 0 - for content in text: - temp = content["content"] - leng = len(temp) - length += leng - return length - -def checklen(text): - while (getlength(text) > 8000): - del text[0] - return text - - - -if __name__ == '__main__': - text.clear - file_name = 'a2.jsonl' - conversations = [] - for i in range(1): - for j in tqdm(range(10)): - Input = prompt(i) - question = checklen(getText("user",Input)) - SparkApi.answer ="" - SparkApi.main(appid,api_key, api_secret, Spark_url, domain, question) - getText("assistant", SparkApi.answer) - conversations.append(xinghuo_api(SparkApi.answer)) - if i % 2 == 0 : - save_jsonl(conversations, file_name) - conversations.clear() - diff --git a/scripts/Gen/prompt.py b/scripts/Gen/prompt.py deleted file mode 100644 index 2236d63..0000000 --- a/scripts/Gen/prompt.py +++ /dev/null @@ -1,151 +0,0 @@ -import json -import random -import re -import copy -# new_data = [{'role': 'user', 'content': '你好'}, {'role': 'assistant', 'content': '你好!有什么我可以帮助您的吗?'}, {'role': 'user', 'content': '生成一段心理医生和病人的对话'}, {'role': 'assistant', 'content': '心理医生:你好,我是你的心理医生。请问你的名字是?\n\n病人:我叫李明。\n\n心理医生:很高兴见到你,李明先生。你来咨询是因为什么问题呢?\n\n病人:最近我总是感觉很焦虑,睡眠也不好。\n\n心理医生:好的,可以跟我具体说说你的情况吗?你有什么压力或者担忧的事情吗?\n\n病人:其实我一直在担心工作上的表现,觉得自己做得不够好,还有家庭的问题。\n\n心理医生:这些都是很常见的问题。你可以告诉我你在工作中遇到了什么困难吗?我们可以一起探讨一下如何解决。\n\n病人:我觉得自己的工作能力不够强,经常被领导批评。而且我家里的情况也不是很好,父母经常吵架,让我很难受。\n\n心理医生:我理解你的感受。这些问题确实会让人感到压力和焦虑。不过我们可以通过一些方法来缓解这种情况。比如说,你可以尝试一些放松的活动,比如瑜伽或者冥想,来减轻压力和焦虑。同时,你也可以考虑寻求家人或者朋友的帮助,让他们给你提供一些支持和鼓励。\n\n病人:好的,我会试试的。谢谢你的建议。\n\n心理医生:不用客气,如果你有任何问题或者需要进一步的帮助,随时可以联系我。'}] -# text2 = [] -# data = {'system':'现在你是一个心理专家,我有一些心理问题,请你用专业的知识帮我解决。', 'input':'', 'output':''} -# for val in new_data: -# if val['role'] == 'user': -# continue -# -# print(text2) - -def save_jsonl(conversations, path_file): - # 把对话写入文件 - with open(path_file, 'a+', encoding='utf-8') as f: - for conversation in conversations: - Json_String = json.dumps(conversation, ensure_ascii=False) + '\n' - f.write(Json_String) - - -# 生成输入提示词 -def prompt(life_type=0): - emotions_lis = [ - "钦佩", - "崇拜", - "欣赏", - "娱乐", - "焦虑", - "敬畏", - "尴尬", - "厌倦", - "冷静", - "困惑", - "渴望", - "厌恶", - "同情", - "痛苦", - "着迷", - "嫉妒", - "兴奋", - "恐惧", - "痛恨", - "有趣", - "快乐", - "怀旧", - "浪漫", - "悲伤", - "满意", - "性欲", - "同情", - "满足" - ] - areas_of_life = [ - "工作", - "学业(小学,初中,高中,大学,研究生,博士)", - "生活(衣,食,住,行等等)", - "身体", - "家人", - "朋友", - "社交", - "恋爱", - "就业", - "责任", - "爱好", - "环境", - "隐私", - "安全", - "梦想", - "自由" - ] - - # 输入数据处理 - if life_type < 0: - raise ValueError('life_type must > 0') - - emo = random.choice(emotions_lis) - life_type %= 16 - - Input = f'''你是一个研究过无数具有心理健康问题的病人与心理健康医生对话的专家,请你构造一些符合实际情况的具有心理健 - 康问题的病人和心理健康医生的连续的一段多轮对话记录。要求病人的问题属于{areas_of_life[life_type]}场景,具有{emo}情感,医生的回复尽可能包含心理辅导知识,并且能够一步步诱导病人说出自己的问题进而提供解决问题的可行方案。注意,构造的数据必须以医生的陈述为结束语,请只返回完整的对话内容。请以如下格式返回生成的数据: - 病人:病人的咨询或陈述 - 医生:医生的安抚和建议 - ''' - return Input - -def xinghuo_api(content): - # 对话格式 - conversation1 = {'system':'现在你是一个心理专家,我有一些心理问题,请你用专业的知识帮我解决。', 'input':'', 'output':''} - conversation = {'input':'', 'output':''} - conversations = {'conversation':[]} - # temp = {'system':'现在你是一个心理专家,我有一些心理问题,请你用专业的知识帮我解决。', 'input':'', 'output':''} - # 划分对话形式 - dialogue = re.split('医生:|病人:', content) - # 对话前的数据处理 - if dialogue[0] == '': - dialogue.pop(0) - # 一次对话 - flag = False - for ind, item in enumerate(dialogue): - if flag == False: - if (ind + 1) % 2 == 1: - conversation1['input'] = dialogue[ind] - else: - conversation1['output'] = dialogue[ind] - - if (ind + 1) % 2 == 0 or ind + 1 == len(dialogue): - temp = copy.deepcopy(conversation1) - conversations['conversation'].append(temp) - flag = True - continue - - else: - if (ind+1)%2 == 1: - conversation['input'] = dialogue[ind] - else: - conversation['output'] = dialogue[ind] - if (ind+1)%2 == 0 or ind+1 == len(dialogue): - # 浅赋值只会是同一个变量,必须要copy.deepcopy - # 若conversations['conversation'].append(conversation)后面改的话,~s里面的conversation也会改动 - # 就会变成n个一样的数据(这是我们不想看到的) - temp = copy.deepcopy(conversation) - conversations['conversation'].append(temp) - - return conversations - -def ChatGLM3_6B(content): - # 对话格式 - conversation = {'system': '现在你是一个心理专家,我有一些心理问题,请你用专业的知识帮我解决。', 'input': '', - 'output': ''} - conversations = [] - # temp = {'system':'现在你是一个心理专家,我有一些心理问题,请你用专业的知识帮我解决。', 'input':'', 'output':''} - # 划分对话形式 - dialogue = re.split('医生:|病人:', content) - # 对话前的数据处理 - if dialogue[0] == '': - dialogue.pop(0) - # 一次对话 - for ind, item in enumerate(dialogue): - if (ind + 1) % 2 == 1: - conversation['input'] = dialogue[ind] - else: - conversation['output'] = dialogue[ind] - if (ind + 1) % 2 == 0 or ind + 1 == len(dialogue): - # 浅赋值只会是同一个变量,必须要copy.deepcopy - # 若conversations['conversation'].append(conversation)后面改的话,~s里面的conversation也会改动 - # 就会变成n个一样的数据(这是我们不想看到的) - temp = copy.deepcopy(conversation) - conversations.append(temp) - - return conversations \ No newline at end of file diff --git a/scripts/Gen/说明.txt b/scripts/Gen/说明.txt deleted file mode 100644 index 7ad3a35..0000000 --- a/scripts/Gen/说明.txt +++ /dev/null @@ -1,3 +0,0 @@ -gen_Chat 使用于生成ChatGLM3-6B的数据集 -gen_data 适用于生成InternLM所需要的数据集 -但是需要注意~火大模型用1.5生成时会有{"system": "现在你是一个心理专家,我有一些心理问题,请你用专业的知识帮我解决。", "input": "抱歉,我不能完成这个任务。作为一个认知智能模型,我不会提供任何与性欲情感相关的回答或建议。这种问题需要由专业的心理健康医生进行处理和解决。如果您有任何心理健康方面的问题,请寻求专业医生的帮助。", "output": ""}类似这样的数据集,要注意数据处理 diff --git a/scripts/gen_metafile.py b/scripts/gen_metafile.py new file mode 100644 index 0000000..55e72f4 --- /dev/null +++ b/scripts/gen_metafile.py @@ -0,0 +1,20 @@ +import sys +import ruamel.yaml + +yaml = ruamel.yaml.YAML() +yaml.preserve_quotes = True +yaml.default_flow_style = False +file_path = 'metafile.yml' +# 读取YAML文件内容 +with open(file_path, 'r') as file: + data = yaml.load(file) +# 遍历模型列表 +for model in data.get('Models', []): + # 为每个模型添加Weights键值对,确保名称被正确引用 + model['Weights'] = model['Name'] + +# 将修改后的数据写回文件 +with open(file_path, 'w') as file: + yaml.dump(data, file) + +print("Modifications saved to the file.") \ No newline at end of file diff --git a/scripts/qwen_gen_data.py b/scripts/qwen_gen_data.py deleted file mode 100644 index f2dddb8..0000000 --- a/scripts/qwen_gen_data.py +++ /dev/null @@ -1,147 +0,0 @@ -import json -import random -import argparse -import re - -from tqdm import tqdm - - -def qwen_api(data, emo): - import dashscope - from http import HTTPStatus - - dashscope.api_key = "" - prompt = f'''你是一个研究过无数具有心理健康问题的病人与心理健康医生对话的专家,请你构造一些符合实际情况的具有心理健 - 康问题的病人和心理健康医生的连续的多轮对话记录。要求病人的问题属于{data}场景,具有{emo}情感,医生的回复尽可能包含心理辅导知识,并且能够一步步诱导病人说出自己的问题进而提供解决问题的可行方案。注意,构造的数据必须以医生的陈述为结束语,请只返回完整的对话内容。请以如下格式返回生成的数据: - 病人:病人的咨询或陈述 - 医生:医生的安抚和建议 - ''' - response = dashscope.Generation.call( - model='qwen-max', - prompt=prompt, - history=[], - ) - - if response.status_code == HTTPStatus.OK: - result = response.output.text - print(result) - else: - result = 'ERROR' - return result - - -def save_jsonl(data_lis, file_path): - import json - - # 将字典列表写入文件,每一行一个字典 - with open(file_path, 'at', encoding='utf-8') as file: - for item in data_lis: - json_string = json.dumps(item, ensure_ascii=False) + '\n' - file.write(json_string) - - -if __name__ == '__main__': - idx = 0 - parser = argparse.ArgumentParser(description='数据生成参数') - - parser.add_argument('--data', type=str, help='生活场景') - - # 解析命令行参数 - args = parser.parse_args() - - emotions_lis = [ - "钦佩", - "崇拜", - "欣赏", - "娱乐", - "焦虑", - "敬畏", - "尴尬", - "厌倦", - "冷静", - "困惑", - "渴望", - "厌恶", - "同情", - "痛苦" - "着迷", - "嫉妒", - "兴奋", - "恐惧", - "痛恨", - "有趣", - "快乐", - "怀旧", - "浪漫", - "悲伤", - "满意", - "性欲", - "同情", - "满足" - ] - areas_of_life = [ - "工作", - "学业", - "生活", - "身体", - "家人", - "朋友", - "社交", - "恋爱", - "就业", - "责任", - "爱好", - "环境", - "隐私", - "安全", - "梦想", - "自由" - ] - - conversation_lis = [] - for i in tqdm(range(100)): - one_conversation = { - "conversation": [] - } - - dia_tuple = [] - emo = random.choice(emotions_lis) - res = qwen_api(data=args.data, emo=emo) - print(res) - - # 一次会话 - doctor_pattern = r'医生:(.*?)(病人:|$)' - - doctor_matches = re.findall(doctor_pattern, res, re.DOTALL) - doctor_conversations = [match[0] for match in doctor_matches] - - patient_pattern = r'病人:(.*?)医生:' - patient_matches = re.findall(patient_pattern, res, re.DOTALL) - patient_conversations = [match for match in patient_matches] - - for doc, pat in zip(doctor_conversations, patient_conversations): - if len(one_conversation['conversation']) == 0: - one_conversation['conversation'].append( - { - "system": "现在你是一个心理专家,我有一些心理问题,请你用专业的知识帮我解决。", - "input": pat, - "output": doc - }, - ) - - else: - one_conversation['conversation'].append( - { - "input": pat, - "output": doc - }, - ) - conversation_lis.append(one_conversation) - - idx += 1 - - # 每生成10条数据存储一次 - if (idx % 10 == 0): - path = f'./{args.data}.jsonl' - save_jsonl(data_lis=conversation_lis, file_path=path) - conversation_lis = [] # 清空 diff --git a/scripts/run_qwen.bash b/scripts/run_qwen.bash deleted file mode 100644 index cf07df9..0000000 --- a/scripts/run_qwen.bash +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -# 定义生活领域的列表 -areas_of_life=( - "工作" - "学业" - "生活" - "身体" - "家人" - "朋友" - "社交" - "恋爱" - "就业" - "责任" - "爱好" - "环境" - "隐私" - "安全" - "梦想" - "自由" -) - -# 使用for循环遍历数组 -for area in "${areas_of_life[@]}"; do - echo "当前生活领域: $area" - python qwen_gen_data.py --data $area -done diff --git a/scripts/upload_openxlab.py b/scripts/upload_openxlab.py new file mode 100644 index 0000000..252fd3b --- /dev/null +++ b/scripts/upload_openxlab.py @@ -0,0 +1,3 @@ +import os + +os.system("openxlab model create --model-repo='jujimeizuo/EmoLLM_Model' -s ./metafile.yml") \ No newline at end of file diff --git a/scripts/zhipuai_gen_data.py b/scripts/zhipuai_gen_data.py deleted file mode 100644 index d8287a4..0000000 --- a/scripts/zhipuai_gen_data.py +++ /dev/null @@ -1,124 +0,0 @@ -import os -import random -import json -from tqdm import tqdm -from dotenv import load_dotenv -from zhipuai import ZhipuAI - -load_dotenv() -client = ZhipuAI(api_key=os.getenv('ZHIPUAI_API_KEY')) - -def zhipu_api(data, emo): - - def getText(role, content, text = []): - jsoncon = {} - jsoncon['role'] = role - jsoncon['content'] = content - text.append(jsoncon) - return text - - prompt = f'''你是一个研究过无数具有心理健康问题的病人与心理健康医生对话的专家,请你构造一些符合实际情况的具有心理健 -康问题的病人和心理健康医生的连续的多轮对话记录。要求病人的问题属于{data}场景,具有{emo}情感,医生的回复尽可能包含心理辅导知识,并且能够一步步诱导病人说出自己的问题进而提供解决问题的可行方案。注意,构造的数据必须以医生的陈述为结束语,每次只需要构造一个案例并且不需要写案例一、二等等,请只返回完整的对话内容。请以如下格式返回生成的数据: -病人:病人的咨询或陈述 -医生:医生的安抚和建议 - ''' - - top_p = round(random.uniform(0.1, 0.9), 2) - messages = getText('user', prompt) - response = client.chat.completions.create( - model='glm-4', - messages=messages, - top_p=top_p, - ) - - return response.choices[0].message.content - - -def convert(conversation): - ret, one_conversation = {}, {} - ret['conversation'] = [] - one_conversation['system'] = '现在你是一个心理专家,我有一些心理问题,请你用专业的知识帮我解决。' - - while '病人:' in conversation and '医生:' in conversation: - one_conversation['input'] = conversation.split('病人:')[1].split('医生:')[0] - one_conversation['output'] = conversation.split('病人:')[1].split('医生:')[1].split('病人:')[0] - conversation = '病人:' + '病人:'.join(conversation.split('病人:')[2:]) - ret['conversation'].append(one_conversation) - one_conversation = {} - - return ret - - -def save_jsonl(data_lis, file_path): - if not os.path.exists(os.path.dirname(file_path)): - os.makedirs(os.path.dirname(file_path)) - with open(file_path, 'w', encoding='utf-8') as f: - for item in data_lis: - f.write(json.dumps(item, ensure_ascii=False) + '\n') - - -if __name__ == '__main__': - emotions_lis = [ - "钦佩", - "崇拜", - "欣赏", - "娱乐", - "焦虑", - "敬畏", - "尴尬", - "厌倦", - "冷静", - "困惑", - "渴望", - "厌恶", - "同情", - "痛苦", - "着迷", - "嫉妒", - "兴奋", - "恐惧", - "痛恨", - "有趣", - "快乐", - "怀旧", - "浪漫", - "悲伤", - "满意", - "性欲", - "满足" - ] - areas_of_life = [ - "工作", - "学业", - "生活", - "身体", - "家人", - "朋友", - "社交", - "恋爱", - "就业", - "责任", - "爱好", - "环境", - "隐私", - "安全", - "梦想", - "自由" - ] - - conversation_lis = [] - for emo in emotions_lis: - for area in areas_of_life: - if os.path.exists(f'./zhipuai/{area}/{emo}.jsonl'): - print(f'./zhipuai/{area}/{emo}.jsonl exists') - continue - for i in tqdm(range(5), desc='{emo}, {area}'.format(emo=emo, area=area)): - res = zhipu_api(area, emo) - print(res) - if res == 'null': - print(area, emo, 'error') - continue - conversation_lis.append(convert(res)) - save_jsonl(conversation_lis, f'./zhipuai/{area}/{emo}.jsonl') - print(f'generate ./zhipuai/{area}/{emo}.jsonl') - conversation_lis = [] diff --git a/web_internlm2.py b/web_internlm2.py new file mode 100644 index 0000000..0b4abec --- /dev/null +++ b/web_internlm2.py @@ -0,0 +1,263 @@ +""" +This script refers to the dialogue example of streamlit, the interactive generation code of chatglm2 and transformers. +We mainly modified part of the code logic to adapt to the generation of our model. +Please refer to these links below for more information: + 1. streamlit chat example: https://docs.streamlit.io/knowledge-base/tutorials/build-conversational-apps + 2. chatglm2: https://github.com/THUDM/ChatGLM2-6B + 3. transformers: https://github.com/huggingface/transformers +Please run with the command `streamlit run path/to/web_demo.py --server.address=0.0.0.0 --server.port 7860`. +Using `python path/to/web_demo.py` may cause unknown problems. +""" +import copy +import warnings +from dataclasses import asdict, dataclass +from typing import Callable, List, Optional + +import streamlit as st +import torch +from torch import nn +from transformers.generation.utils import LogitsProcessorList, StoppingCriteriaList +from transformers.utils import logging + +from transformers import AutoTokenizer, AutoModelForCausalLM # isort: skip +from openxlab.model import download + +logger = logging.get_logger(__name__) + +download(model_repo='jujimeizuo/EmoLLM_Model', + output='model') + +@dataclass +class GenerationConfig: + # this config is used for chat to provide more diversity + max_length: int = 32768 + top_p: float = 0.8 + temperature: float = 0.8 + do_sample: bool = True + repetition_penalty: float = 1.005 + + +@torch.inference_mode() +def generate_interactive( + model, + tokenizer, + prompt, + generation_config: Optional[GenerationConfig] = None, + logits_processor: Optional[LogitsProcessorList] = None, + stopping_criteria: Optional[StoppingCriteriaList] = None, + prefix_allowed_tokens_fn: Optional[Callable[[int, torch.Tensor], List[int]]] = None, + additional_eos_token_id: Optional[int] = None, + **kwargs, +): + inputs = tokenizer([prompt], padding=True, return_tensors="pt") + input_length = len(inputs["input_ids"][0]) + for k, v in inputs.items(): + inputs[k] = v.cuda() + input_ids = inputs["input_ids"] + batch_size, input_ids_seq_length = input_ids.shape[0], input_ids.shape[-1] # noqa: F841 # pylint: disable=W0612 + if generation_config is None: + generation_config = model.generation_config + generation_config = copy.deepcopy(generation_config) + model_kwargs = generation_config.update(**kwargs) + bos_token_id, eos_token_id = ( # noqa: F841 # pylint: disable=W0612 + generation_config.bos_token_id, + generation_config.eos_token_id, + ) + if isinstance(eos_token_id, int): + eos_token_id = [eos_token_id] + if additional_eos_token_id is not None: + eos_token_id.append(additional_eos_token_id) + has_default_max_length = kwargs.get("max_length") is None and generation_config.max_length is not None + if has_default_max_length and generation_config.max_new_tokens is None: + warnings.warn( + f"Using `max_length`'s default ({generation_config.max_length}) to control the generation length. " + "This behaviour is deprecated and will be removed from the config in v5 of Transformers -- we" + " recommend using `max_new_tokens` to control the maximum length of the generation.", + UserWarning, + ) + elif generation_config.max_new_tokens is not None: + generation_config.max_length = generation_config.max_new_tokens + input_ids_seq_length + if not has_default_max_length: + logger.warn( # pylint: disable=W4902 + f"Both `max_new_tokens` (={generation_config.max_new_tokens}) and `max_length`(=" + f"{generation_config.max_length}) seem to have been set. `max_new_tokens` will take precedence. " + "Please refer to the documentation for more information. " + "(https://huggingface.co/docs/transformers/main/en/main_classes/text_generation)", + UserWarning, + ) + + if input_ids_seq_length >= generation_config.max_length: + input_ids_string = "input_ids" + logger.warning( + f"Input length of {input_ids_string} is {input_ids_seq_length}, but `max_length` is set to" + f" {generation_config.max_length}. This can lead to unexpected behavior. You should consider" + " increasing `max_new_tokens`." + ) + + # 2. Set generation parameters if not already defined + logits_processor = logits_processor if logits_processor is not None else LogitsProcessorList() + stopping_criteria = stopping_criteria if stopping_criteria is not None else StoppingCriteriaList() + + logits_processor = model._get_logits_processor( + generation_config=generation_config, + input_ids_seq_length=input_ids_seq_length, + encoder_input_ids=input_ids, + prefix_allowed_tokens_fn=prefix_allowed_tokens_fn, + logits_processor=logits_processor, + ) + + stopping_criteria = model._get_stopping_criteria( + generation_config=generation_config, stopping_criteria=stopping_criteria + ) + logits_warper = model._get_logits_warper(generation_config) + + unfinished_sequences = input_ids.new(input_ids.shape[0]).fill_(1) + scores = None + while True: + model_inputs = model.prepare_inputs_for_generation(input_ids, **model_kwargs) + # forward pass to get next token + outputs = model( + **model_inputs, + return_dict=True, + output_attentions=False, + output_hidden_states=False, + ) + + next_token_logits = outputs.logits[:, -1, :] + + # pre-process distribution + next_token_scores = logits_processor(input_ids, next_token_logits) + next_token_scores = logits_warper(input_ids, next_token_scores) + + # sample + probs = nn.functional.softmax(next_token_scores, dim=-1) + if generation_config.do_sample: + next_tokens = torch.multinomial(probs, num_samples=1).squeeze(1) + else: + next_tokens = torch.argmax(probs, dim=-1) + + # update generated ids, model inputs, and length for next step + input_ids = torch.cat([input_ids, next_tokens[:, None]], dim=-1) + model_kwargs = model._update_model_kwargs_for_generation(outputs, model_kwargs, is_encoder_decoder=False) + unfinished_sequences = unfinished_sequences.mul((min(next_tokens != i for i in eos_token_id)).long()) + + output_token_ids = input_ids[0].cpu().tolist() + output_token_ids = output_token_ids[input_length:] + for each_eos_token_id in eos_token_id: + if output_token_ids[-1] == each_eos_token_id: + output_token_ids = output_token_ids[:-1] + response = tokenizer.decode(output_token_ids) + + yield response + # stop when each sentence is finished, or if we exceed the maximum length + if unfinished_sequences.max() == 0 or stopping_criteria(input_ids, scores): + break + + +def on_btn_click(): + del st.session_state.messages + + +@st.cache_resource +def load_model(): + model = ( + AutoModelForCausalLM.from_pretrained("model", trust_remote_code=True) + .to(torch.bfloat16) + .cuda() + ) + tokenizer = AutoTokenizer.from_pretrained("model", trust_remote_code=True) + return model, tokenizer + + +def prepare_generation_config(): + with st.sidebar: + max_length = st.slider("Max Length", min_value=8, max_value=32768, value=32768) + top_p = st.slider("Top P", 0.0, 1.0, 0.8, step=0.01) + temperature = st.slider("Temperature", 0.0, 1.0, 0.7, step=0.01) + st.button("Clear Chat History", on_click=on_btn_click) + + generation_config = GenerationConfig(max_length=max_length, top_p=top_p, temperature=temperature) + + return generation_config + + +user_prompt = "<|im_start|>user\n{user}<|im_end|>\n" +robot_prompt = "<|im_start|>assistant\n{robot}<|im_end|>\n" +cur_query_prompt = "<|im_start|>user\n{user}<|im_end|>\n<|im_start|>assistant\n" + + +def combine_history(prompt): + messages = st.session_state.messages + meta_instruction = ( + "你是一个由aJupyter、Farewell、jujimeizuo、Smiling&Weeping研发(排名按字母顺序排序,不分先后)、散步提供技术支持、上海人工智能实验室提供支持开发的心理健康大模型。现在你是一个心理专家,我有一些心理问题,请你用专业的知识帮我解决。" + ) + total_prompt = f"<|im_start|>system\n{meta_instruction}<|im_end|>\n" + for message in messages: + cur_content = message["content"] + if message["role"] == "user": + cur_prompt = user_prompt.format(user=cur_content) + elif message["role"] == "robot": + cur_prompt = robot_prompt.format(robot=cur_content) + else: + raise RuntimeError + total_prompt += cur_prompt + total_prompt = total_prompt + cur_query_prompt.format(user=prompt) + return total_prompt + + +def main(): + # torch.cuda.empty_cache() + print("load model begin.") + model, tokenizer = load_model() + print("load model end.") + + user_avator = "assets/user.png" + robot_avator = "assets/robot.jpeg" + + st.title("EmoLLM") + + generation_config = prepare_generation_config() + + # Initialize chat history + if "messages" not in st.session_state: + st.session_state.messages = [] + + # Display chat messages from history on app rerun + for message in st.session_state.messages: + with st.chat_message(message["role"], avatar=message.get("avatar")): + st.markdown(message["content"]) + + # Accept user input + if prompt := st.chat_input("What is up?"): + # Display user message in chat message container + with st.chat_message("user", avatar=user_avator): + st.markdown(prompt) + real_prompt = combine_history(prompt) + # Add user message to chat history + st.session_state.messages.append({"role": "user", "content": prompt, "avatar": user_avator}) + + with st.chat_message("robot", avatar=robot_avator): + message_placeholder = st.empty() + for cur_response in generate_interactive( + model=model, + tokenizer=tokenizer, + prompt=real_prompt, + additional_eos_token_id=92542, + **asdict(generation_config), + ): + # Display robot response in chat message container + message_placeholder.markdown(cur_response + "▌") + message_placeholder.markdown(cur_response) # pylint: disable=undefined-loop-variable + # Add robot response to chat history + st.session_state.messages.append( + { + "role": "robot", + "content": cur_response, # pylint: disable=undefined-loop-variable + "avatar": robot_avator, + } + ) + torch.cuda.empty_cache() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/config/ft_config.py b/xtuner_config/internlm2_7b_chat_qlora_e3.py similarity index 100% rename from config/ft_config.py rename to xtuner_config/internlm2_7b_chat_qlora_e3.py diff --git a/config/qwen_7b_chat_qlora_e3.py b/xtuner_config/qwen_7b_chat_qlora_e3.py similarity index 100% rename from config/qwen_7b_chat_qlora_e3.py rename to xtuner_config/qwen_7b_chat_qlora_e3.py