From b8c06d4398f06a04fadc4c334949b589f279d749 Mon Sep 17 00:00:00 2001 From: "Nam D. Tran" <42194884+namtranase@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:54:57 +0700 Subject: [PATCH 01/18] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d5529ff..3039af4 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Special thanks to the creators and contributors of [gemma.cpp](https://github.co ## 🛠 Installation `Prerequisites`: Ensure Python 3.8+ and pip are installed. +`System requirements`: For now, I only tested it on the Unix-like Platforms and the MacOS ### Install from PyPI For a quick setup, install directly from PyPI: From db3e9f5bab3ac39613a8fe3d8c15c5fa92ea9663 Mon Sep 17 00:00:00 2001 From: "Nam D. Tran" <42194884+namtranase@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:55:20 +0700 Subject: [PATCH 02/18] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3039af4..30e0ea5 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Special thanks to the creators and contributors of [gemma.cpp](https://github.co ## 🛠 Installation `Prerequisites`: Ensure Python 3.8+ and pip are installed. + `System requirements`: For now, I only tested it on the Unix-like Platforms and the MacOS ### Install from PyPI From 2ec7cb0e352c5afa27cbfcd246243e1042de4250 Mon Sep 17 00:00:00 2001 From: namtranase Date: Mon, 18 Mar 2024 10:40:04 +0700 Subject: [PATCH 03/18] update: release script --- .gitignore | 5 ++++- scripts/release.sh | 20 +++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 551b2a3..f69533b 100644 --- a/.gitignore +++ b/.gitignore @@ -162,5 +162,8 @@ cython_debug/ # Vscode .vscode/ -#p Precommit +# Precommit .pre-commit-config.yaml + +# Models +models/ diff --git a/scripts/release.sh b/scripts/release.sh index 6b0caf0..697bc30 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -1,3 +1,21 @@ +#!/bin/bash + +# Remove previous distribution files rm -rf dist/* + +# Build the distribution python3 setup.py sdist bdist_wheel -delocate-wheel -w fixed_wheels -v dist/*.whl + +# Check the operating system +OS="$(uname)" +if [ "$OS" = "Darwin" ]; then + # macOS specific commands + delocate-wheel -w fixed_wheels -v dist/*.whl + mv fixed_wheels/*.whl dist/ +elif [ "$OS" = "Linux" ]; then + # Linux specific commands (if any can be added here) + echo "Linux OS detected. No additional steps required for Linux." +fi + +# Upload the distribution to PyPI +twine upload dist/* \ No newline at end of file From c81f68e2c3892c51298ddc334650c7ab53dfdd78 Mon Sep 17 00:00:00 2001 From: "Nam D. Tran" <42194884+namtranase@users.noreply.github.com> Date: Mon, 18 Mar 2024 22:15:34 +0700 Subject: [PATCH 04/18] update: model installation --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 30e0ea5..fd9019e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,9 @@ Special thanks to the creators and contributors of [gemma.cpp](https://github.co ## 🛠 Installation `Prerequisites`: Ensure Python 3.8+ and pip are installed. -`System requirements`: For now, I only tested it on the Unix-like Platforms and the MacOS +`System requirements`: For now, I only tested it on the Unix-like Platforms and the MacOS. Please visit the [gemma.cpp installation](https://github.com/google/gemma.cpp?tab=readme-ov-file#system-requirements) for more details. + +`Models`: pygemma supported 2b-it-sfp model for now, to install model, [please visit here](https://github.com/google/gemma.cpp?tab=readme-ov-file#step-1-obtain-model-weights-and-tokenizer-from-kaggle-or-hugging-face-hub) ### Install from PyPI For a quick setup, install directly from PyPI: From 578ea98cc70531608291cb3896835046050623aa Mon Sep 17 00:00:00 2001 From: namtranase Date: Tue, 19 Mar 2024 09:15:04 +0700 Subject: [PATCH 05/18] update: readme for fixed verison --- README.md | 3 ++- setup.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fd9019e..d8ecb43 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # gemma-cpp-python: Python Bindings for [gemma.cpp](https://github.com/google/gemma.cpp) -**Latest Version: v0.1.3** +**Latest Version: v0.1.3.post3** +- Fixed absolute path for libsentencepiece.0.0.0.dylib - Interface changes due to updates in gemma.cpp. - Enhanced user experience for ease of use 🙏. Give it a try! diff --git a/setup.py b/setup.py index 030cffe..4edc814 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ def build_extension(self, ext): setup( name="pygemma", - version="0.1.3", + version="0.1.3.post3", author="Nam Tran", author_email="namtran.ase@gmail.com", description="A Python package with a C++ backend using gemma.cpp", From 68bf620d5e42ec6ed3737af3ea2123ca6b9253d8 Mon Sep 17 00:00:00 2001 From: namtranase Date: Fri, 22 Mar 2024 00:20:37 +0700 Subject: [PATCH 06/18] update: add example chat streamlit --- .gitignore | 6 +-- README.md | 11 +++++ asset/demo_chat.png | Bin 0 -> 458839 bytes examples/requirements.txt | 1 + examples/streamlit_demo.py | 91 +++++++++++++++++++++++++++++++++++++ src/gemma_binding.cpp | 10 ++-- src/gemma_binding.h | 3 +- tests/test_chat.py | 4 +- 8 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 asset/demo_chat.png create mode 100644 examples/requirements.txt create mode 100644 examples/streamlit_demo.py diff --git a/.gitignore b/.gitignore index f69533b..c5f0e85 100644 --- a/.gitignore +++ b/.gitignore @@ -162,8 +162,8 @@ cython_debug/ # Vscode .vscode/ -# Precommit +# Project .pre-commit-config.yaml - -# Models models/ +fixed_wheels +playground diff --git a/README.md b/README.md index d8ecb43..93bb303 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,10 @@ ## 🙏 Acknowledgments Special thanks to the creators and contributors of [gemma.cpp](https://github.com/google/gemma.cpp) for their foundational work. +## 💬 Demo Chat +Check out the new chat demo included in the examples directory! This interactive interface showcases how you can engage in real-time conversations with the Gemma model. + +![Gemma Cpp Python Chat Demo](asset/demo_chat.png) ## 🛠 Installation `Prerequisites`: Ensure Python 3.8+ and pip are installed. @@ -54,6 +58,13 @@ gemma.load_model("/path/to/tokenizer", "/path/to/compressed_weight/", "model_typ gemma.completion("Write a poem") ``` +To run the demo on your local machine: +```bash +cd gemma-cpp-python/examples +pip install -r requirements.txt +streamlit run streamlit_demo.py +``` + ## 🤝 Contributing Contributions are welcome. Please clone the repository, push your changes to a new branch, and submit a pull request. diff --git a/asset/demo_chat.png b/asset/demo_chat.png new file mode 100644 index 0000000000000000000000000000000000000000..64590a83d458d0054e6720afcde50a5bf58f65d1 GIT binary patch literal 458839 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