Compare commits

...
Author SHA1 Message Date
Vertex MG TeamandCopybara-Service c1b292bf73 feat: Update the model id to a default value, this is not used when the deployed endpoint is passed in
PiperOrigin-RevId: 725755135
2025-02-11 13:41:52 -08:00
Vertex MG TeamandCopybara-Service ae44e54d57 feat: Add Reasoning Engine integration notebook
PiperOrigin-RevId: 725511227
2025-02-11 00:24:28 -08:00
Mend RenovateandGitHub 7835b84082 chore(deps): update dependency isort to v6 (#3810) 2025-02-10 20:39:00 +00:00
ethan-gordonandGitHub 30d6112517 set FeatureView IAM and Service Accounts as version v1 and remove preview note. (#3795) 2025-02-10 20:37:36 +00:00
Vertex MG TeamandCopybara-Service 32737fda1f Sync mistral and mixtral ft notebooks
PiperOrigin-RevId: 725241490
2025-02-10 09:20:36 -08:00
Vertex MG TeamandCopybara-Service e02adc516c feat: Update Reasoning Engine with Llama 3.1 models notebook.
PiperOrigin-RevId: 725038312
2025-02-09 19:53:21 -08:00
Vertex MG TeamandCopybara-Service 80320a9a1b Refactor the notebook
PiperOrigin-RevId: 724288214
2025-02-07 03:55:12 -08:00
Minwoo ParkandCopybara-Service 8c527a89ed Add Model Garden finetuning tutorial notebook.
PiperOrigin-RevId: 724140766
2025-02-06 17:56:55 -08:00
Vertex MG TeamandCopybara-Service 33a2b7eaf9 feat: Add Reasoning Engine with Llama 3.1 models notebook.
PiperOrigin-RevId: 724139178
2025-02-06 17:51:09 -08:00
Minwoo ParkandCopybara-Service dfa461dabe Add Model Garden finetuning tutorial notebook.
PiperOrigin-RevId: 724065750
2025-02-06 14:02:30 -08:00
Vertex MG TeamandCopybara-Service c80de903af Update PaliGemma 2 notebook and handler to use weights from GCS.
PiperOrigin-RevId: 724065613
2025-02-06 14:01:01 -08:00
Vertex MG TeamandCopybara-Service dd333b8fdd feat: Add Reasoning Engine with Llama 3.1 models notebook.
PiperOrigin-RevId: 723785091
2025-02-05 22:12:52 -08:00
Vertex MG TeamandCopybara-Service 911ef0cb69 A minor tweaking in the helper functions and a Bug fix in the Predict Section.
PiperOrigin-RevId: 723779782
2025-02-05 21:46:35 -08:00
Vertex MG TeamandCopybara-Service ac6f4d669d Remove Llama-Guard (llama3.1) models from notebook model list
PiperOrigin-RevId: 723282525
2025-02-04 17:21:55 -08:00
Dustin LuongandCopybara-Service abdd887fa1 Set model_garden_source_model_name for model_garden_phi4_deployment notebook.
PiperOrigin-RevId: 722840147
2025-02-03 16:25:21 -08:00
Dustin LuongandCopybara-Service 711a4f0b8c Set model_garden_source_model_name for deployment notebooks.
PiperOrigin-RevId: 722701496
2025-02-03 10:06:48 -08:00
Aiden010200andGitHub 07f30dfde5 Upload a SGD classifier predictor example (#3783)
This example uses aiplatform and scikit-learn library to provide a SGD classifier.
2025-02-03 16:09:56 +00:00
Mend RenovateandGitHub b33e3287b7 chore(deps): update dependency black to v25 (#3815) 2025-02-03 16:09:21 +00:00
Dustin LuongandCopybara-Service 3c06e4797a Set model_garden_source_model_name for some model garden deployment notebooks.
PiperOrigin-RevId: 721846318
2025-01-31 11:39:27 -08:00
Vertex MG TeamandCopybara-Service a5944510d6 Add a notebook about Model Garden advanced features, including prefix caching and speculative decoding.
PiperOrigin-RevId: 721844683
2025-01-31 11:35:01 -08:00
ethan-gordonandGitHub 6d74f87e22 Add notebook vertex_ai_feature_store_update_feature_monitor_feature_group_iam_and_service_agent.ipynb. (#3814)
This change also inserts a corresponding entry to CODEOWNERS.
2025-01-30 22:04:59 +00:00
Vertex MG TeamandCopybara-Service 14031b238c BiomedCLIP Deployment on Vertex Notebook
PiperOrigin-RevId: 720990850
2025-01-29 08:40:29 -08:00
Vertex MG TeamandCopybara-Service 99e578ca2e Add usage tracking labels for finetuning notebooks
PiperOrigin-RevId: 720826465
2025-01-28 22:03:39 -08:00
Vertex MG TeamandCopybara-Service 539683f573 Adding Phi-4 Colab deployment notebook
PiperOrigin-RevId: 720596224
2025-01-28 09:04:43 -08:00
Dustin LuongandCopybara-Service 6fabc23db6 Set model_garden_source_model_name for vllm deployments.
PiperOrigin-RevId: 720345103
2025-01-27 16:20:59 -08:00
Dustin LuongandCopybara-Service 18305e35ea Set model_garden_source_model_name for hexllm deployments.
PiperOrigin-RevId: 720341876
2025-01-27 16:09:59 -08:00
Dustin LuongandCopybara-Service b71f0ec2dc Set model_garden_source_model_name for optimized vllm deployments.
PiperOrigin-RevId: 720317678
2025-01-27 14:54:29 -08:00
Dustin LuongandCopybara-Service d7fe713f7f Set model_garden_source_model_name for TGI deployments.
PiperOrigin-RevId: 720312091
2025-01-27 14:40:10 -08:00
Dustin LuongandCopybara-Service 52925286ec Set model_garden_source_model_name for pytorch inference deployments.
PiperOrigin-RevId: 720311822
2025-01-27 14:38:52 -08:00
Dustin LuongandCopybara-Service b7b14ba7d8 Set model_garden_source_model_name for llama3
reference implementation deployment.

PiperOrigin-RevId: 720311748
2025-01-27 14:38:41 -08:00
Dustin LuongandCopybara-Service 5e3c07f72e Set model_garden_source_model_name for TEI deployments.
PiperOrigin-RevId: 720311569
2025-01-27 14:37:21 -08:00
Vertex MG TeamandCopybara-Service da630753ef Add Mistral and Llama3.1 8B serving notebooks
PiperOrigin-RevId: 719348288
2025-01-24 10:15:58 -08:00
Vertex MG TeamandCopybara-Service c27751e3aa Distinguish between the train and deploy machine specs for Gemma PEFT Finetuning on HF Notebook
PiperOrigin-RevId: 719309601
2025-01-24 08:16:52 -08:00
mumletandGitHub e9428949f1 fix: Update the model file path (#3778)
* fix: Update model path of model_monitoring_for_custom_model_online_prediction.ipynb

Update the unavailable model path for model_monitoring_for_custom_model_online_prediction.ipynb

* Update model_monitoring_for_custom_model_online_prediction.ipynb

Update the bq dataset uri
2025-01-23 23:45:17 +00:00
Vertex MG TeamandCopybara-Service 180c2834fc Fix minor lint issues
PiperOrigin-RevId: 718941819
2025-01-23 11:15:04 -08:00
Vertex MG TeamandCopybara-Service 77fd06d4c0 Fix MaaS requests in Llama Guard notebook.
PiperOrigin-RevId: 718856137
2025-01-23 07:23:05 -08:00
0943520f8d Update template location in dataset validation (#3799)
Co-authored-by: Rayan Dasoriya <dasoriya@google.com>
2025-01-23 13:26:44 +00:00
Vertex MG TeamandCopybara-Service c14ed381bf Stable Diffusion XL Finetuning Dreambooth Lora
PiperOrigin-RevId: 718724713
2025-01-22 23:26:09 -08:00
1f15188b6e Update common util and dataset validation util (#3796)
Co-authored-by: Rayan Dasoriya <dasoriya@google.com>
2025-01-23 02:27:23 +00:00
Bhaskar GoyalandGitHub 9ef41d55ba feat: Deprecate Claude 3 Sonnet (#3790) 2025-01-21 21:36:16 +00:00
Vertex MG TeamandCopybara-Service d82b5a4066 Enable H100 80GB DWS for 11B and 90B eval.
PiperOrigin-RevId: 717949505
2025-01-21 09:24:17 -08:00
Vertex MG TeamandCopybara-Service fdd4d37275 Paligemma 2 Deployment notebook
PiperOrigin-RevId: 717927000
2025-01-21 08:21:47 -08:00
Vertex MG TeamandCopybara-Service 81f700d508 Rename accelerator variables in the notebook
PiperOrigin-RevId: 717750767
2025-01-20 22:54:59 -08:00
Vertex MG TeamandCopybara-Service 32136a1894 BioGPT serving Notebook
PiperOrigin-RevId: 717710469
2025-01-20 20:13:51 -08:00
Vertex MG TeamandCopybara-Service c93a9c2099 Create Cloud translation and evaluation demo notebook
PiperOrigin-RevId: 715907272
2025-01-15 12:45:59 -08:00
Vertex MG TeamandCopybara-Service 8ece5ef3eb Segment Anything Model (SAM) Serving on Vertex AI Notebook
PiperOrigin-RevId: 715723816
2025-01-15 03:08:36 -08:00
Vertex MG TeamandCopybara-Service dee72afc70 Enable dedicate endpoint for Prompt Guard deployment
PiperOrigin-RevId: 715395374
2025-01-14 08:38:18 -08:00
Vertex MG TeamandCopybara-Service ea45e3dd5c Add usage tracking labels to all the finetuning notebook
PiperOrigin-RevId: 715229459
2025-01-13 21:41:30 -08:00
Vertex MG TeamandCopybara-Service 9ed8896350 vLLM supports GPU HBM + host memory prefix kv caching
PiperOrigin-RevId: 715213919
2025-01-13 20:30:33 -08:00
Vertex MG TeamandCopybara-Service 2709fcd8ed Fix the error when result is list, make it works for both dictionary and list
PiperOrigin-RevId: 715156151
2025-01-13 16:54:47 -08:00
Vertex MG TeamandCopybara-Service a7c1b9af3a Add publisherdb api call response check in case call fails
PiperOrigin-RevId: 715099165
2025-01-13 14:01:37 -08:00
Vertex MG TeamandCopybara-Service 952af223da Update license of the notebooks to 2025
PiperOrigin-RevId: 715041456
2025-01-13 11:17:00 -08:00
Bhaskar GoyalandGitHub d87b6b7463 feat: Add Codestral (25.01) model to mistral docs. (#3779) 2025-01-13 17:05:46 +00:00
Dustin LuongandCopybara-Service 25b3364236 No public description
PiperOrigin-RevId: 713694601
2025-01-09 09:13:27 -08:00
Vertex MG TeamandCopybara-Service 9f80c3cd9b Hex-LLM supports prefix caching as a GA feature
PiperOrigin-RevId: 713503181
2025-01-08 19:47:24 -08:00
Mend RenovateandGitHub 9f6ad8439c Update dependency pyupgrade to v3.19.1 (#3757) 2025-01-08 20:31:26 +00:00
Aiden010200andGitHub 64e9a4ae06 Upload a ResNet predictor example (#3765)
This example uses aiplatform and torch library to provide a ResNet predictor.
2025-01-08 20:30:52 +00:00
Dustin LuongandCopybara-Service 883e1e5fc1 Set system_labels in notebooks
PiperOrigin-RevId: 713040032
2025-01-07 14:20:25 -08:00
Vertex MG TeamandCopybara-Service 847a49f6d0 Update docker images to avoid 'tags' KeyError while loading HF dataset
PiperOrigin-RevId: 711729872
2025-01-03 06:09:28 -08:00
Vertex MG TeamandCopybara-Service 0b9a450b44 MediaPipe Text Classification notebook
PiperOrigin-RevId: 711656574
2025-01-03 00:25:31 -08:00
Vertex MG TeamandCopybara-Service ed72474525 pyTorch IMage Model notebook
PiperOrigin-RevId: 711343028
2025-01-02 00:53:05 -08:00
Vertex MG TeamandCopybara-Service 6b20a30669 TFVision Image segmentation notebook
PiperOrigin-RevId: 710942626
2024-12-31 04:20:47 -08:00
Vertex MG TeamandCopybara-Service f50f5f749d mediapipe face stylizer notebook
PiperOrigin-RevId: 710861510
2024-12-30 20:30:38 -08:00
alicechang0909andGitHub 9621877583 Created using Colab (#3763)
* Created using Colab

* Created using Colab

* Add Featurestore Monitoring functionalities - Fix lint error in import

* Fix lint with commands

* Address comments

* Update restart section to fix lint error.

* fix: fix lint errors

* Fix: Try to fix lint errors

* Fix: try fix lint with python commands

* fix: remove self link

* fix: try submit from workbench
2024-12-30 18:59:13 +00:00
Vertex MG TeamandCopybara-Service 425a5c94c2 Fix PIL issue FreeTypeFont object has no attribute getsize
PiperOrigin-RevId: 710698919
2024-12-30 06:09:47 -08:00
Vertex MG TeamandCopybara-Service 0b8b4b8ad6 movinet action recognition notebook
PiperOrigin-RevId: 709969002
2024-12-26 22:46:35 -08:00
Vertex MG TeamandCopybara-Service f641e5d221 Enable dedicate endpoint for pytorch llama3 deployment
PiperOrigin-RevId: 707697043
2024-12-18 16:07:12 -08:00
Vertex MG TeamandCopybara-Service d89b29f759 Add usage labels to finetuning notebook
PiperOrigin-RevId: 707402297
2024-12-17 22:37:52 -08:00
Vertex MG TeamandCopybara-Service df49e18ce9 Hex-LLM supports disaggregated serving as an experimental feature
PiperOrigin-RevId: 707332453
2024-12-17 18:19:49 -08:00
Changyu ZhuandCopybara-Service dae9e791df Fix missing import in Llama 3 finetuning notebook
PiperOrigin-RevId: 707326361
2024-12-17 18:02:31 -08:00
Changyu ZhuandCopybara-Service 16237de9bb Add fast deployment section to Llama 3.2 deployment notebook
PiperOrigin-RevId: 707274649
2024-12-17 15:32:57 -08:00
Vertex MG TeamandCopybara-Service f5e0394b10 Add H100 80 GB config for Llama 3
PiperOrigin-RevId: 706888765
2024-12-16 17:28:23 -08:00
Vertex MG TeamandCopybara-Service 775aa37b88 Update yolov8 model to use model and endpoint dictionary.
PiperOrigin-RevId: 706750375
2024-12-16 10:13:00 -08:00
Vertex MG TeamandCopybara-Service b43418b17b mediapipe Object detection notebook bug fix and re-formatting
PiperOrigin-RevId: 706673143
2024-12-16 05:26:30 -08:00
Changyu ZhuandCopybara-Service 20138d9333 Add fast deployment section to Llama 3.1 deployment notebook
PiperOrigin-RevId: 705931336
2024-12-13 10:44:11 -08:00
sageof6pathandGitHub fadcb1e618 Peft docker fix (#3751)
* Updated util files to fix peft docker

* fix imports

* fix imports fileutils.py
2024-12-13 13:08:34 +00:00
Vertex MG TeamandCopybara-Service f0ec2acbb8 Update Hex-LLM container URI.
PiperOrigin-RevId: 705656670
2024-12-12 15:53:15 -08:00
Aiden010200andGitHub cc38839e2c Upload xgbranker predictor example (#3737)
* Upload pipeline job example

* Upload pipeline example which can combine other pipeline examples.

* Upload xgbranker predictor example

This example uses aiplatform and xgboost to provide a xgbranker predictor.
2024-12-12 16:03:43 +00:00
Vertex MG TeamandCopybara-Service 94b6a9624b mediapipe Object detection notebook
PiperOrigin-RevId: 705082582
2024-12-11 06:20:13 -08:00
Vertex MG TeamandCopybara-Service 9f8cc0e625 Enable dedicate endpoint for phi3 deployment
PiperOrigin-RevId: 704865877
2024-12-10 15:25:47 -08:00
Vertex MG TeamandCopybara-Service 57cc004855 Update the HF TGI and pytorch-inference notebooks, with the latest container image version.
PiperOrigin-RevId: 704421485
2024-12-09 14:40:05 -08:00
Minwoo ParkandCopybara-Service b9549ccee7 Add Llama 3.3 finetuning notebook.
PiperOrigin-RevId: 703546040
2024-12-06 10:42:19 -08:00
Vertex MG TeamandCopybara-Service 6c7383beaf Adding Qwen2.5-Instruct-32B-AWQ TPU configs to Colab deployment notebook
PiperOrigin-RevId: 703541226
2024-12-06 10:26:35 -08:00
Vertex MG TeamandCopybara-Service 4bec5ef258 Add new Llama 3.3 deployment notebook.
PiperOrigin-RevId: 703537600
2024-12-06 10:15:16 -08:00
Vertex MG TeamandCopybara-Service c2bac62780 tfvision classification notebook
PiperOrigin-RevId: 702659350
2024-12-04 03:25:48 -08:00
Pedro MelendezandGitHub cb7c18e439 Added blog post URL (#3739) 2024-12-03 17:41:39 +00:00
Vertex MG TeamandCopybara-Service e1e4a2ba5e Add vLLM + TPU Llama 3.1 and Qwen 2.5 deployment notebook.
PiperOrigin-RevId: 702091534
2024-12-02 14:46:03 -08:00
Vertex MG TeamandCopybara-Service 2a981b9568 Adding chunked prefill vllm server arg.
PiperOrigin-RevId: 702022997
2024-12-02 11:02:06 -08:00
Vertex MG TeamandCopybara-Service fb10a66d12 A fix in the prediction section
PiperOrigin-RevId: 701847844
2024-12-01 23:09:39 -08:00
Vertex MG TeamandCopybara-Service 2b2019afa6 LLaVA Deployment notebook
PiperOrigin-RevId: 701314497
2024-11-29 10:23:01 -08:00
Vertex MG TeamandCopybara-Service 751b8e7ad5 Avoid copying model artifacts to local GCS and use VERTEX_AI_MODEL_GARDEN_LLAMA_3_1 directly
PiperOrigin-RevId: 700732639
2024-11-27 09:55:49 -08:00
Aiden010200andGitHub 516c395db0 Upload pipeline job example (#3719)
* Upload pipeline example which can combine other pipeline examples.
2024-11-26 15:32:54 +00:00
Vertex MG TeamandCopybara-Service 964c481ed1 Enable dedicate endpoint for timesfm deployment
PiperOrigin-RevId: 700190957
2024-11-25 20:33:57 -08:00
Vertex MG TeamandCopybara-Service 454a90bc33 Enable dedicate endpoint for huggingface tei deployment
PiperOrigin-RevId: 700166368
2024-11-25 18:25:35 -08:00
Vertex MG TeamandCopybara-Service ee43b8c0b7 Add 2H100/4H100 deploy options to llama notebooks
PiperOrigin-RevId: 700107023
2024-11-25 14:45:36 -08:00
Eric DongandGitHub 556f8510f3 fix: Remove example ouptut (#3732) 2024-11-25 21:21:49 +00:00
Eric DongandGitHub c1ba930618 fix: Update the model file path (#3731)
* fix: Update the model file path

* Update the model file path 2
2024-11-25 20:25:48 +00:00
Vertex MG TeamandCopybara-Service 2dc1e96207 Add Llama 3.2 serving notebook.
PiperOrigin-RevId: 700025379
2024-11-25 10:19:14 -08:00
Vertex MG TeamandCopybara-Service 68d5a6d2d2 Enable dedicate endpoint for Llama Guard deployment
PiperOrigin-RevId: 700022732
2024-11-25 10:11:56 -08:00
Vertex MG TeamandCopybara-Service ff2f20fc26 Adding Qwen2/Qwen2.5 TPU configs to Colab deployment notebook
PiperOrigin-RevId: 699209055
2024-11-22 10:07:41 -08:00
Vertex MG TeamandCopybara-Service dd157ca445 A minor fix in the prediction section
PiperOrigin-RevId: 699134138
2024-11-22 05:05:58 -08:00
Vertex MG TeamandCopybara-Service 65d86f57ca Update region suggestion for A100_80GB and H100_80GB gpus
PiperOrigin-RevId: 698841858
2024-11-21 10:55:34 -08:00
0dadbb8400 feat: adding support for Mistral Large 24.11 part2 (#3723)
* feat: adding support for Mistral Large 24.11 part2

* feat: adding support for Mistral Large 24.11 part2

---------

Co-authored-by: denisj3030 <denisj@google.com>
2024-11-21 16:13:13 +00:00
Aaron DietzandGitHub 115413601f Update spark_on_ray_on_vertex_ai.ipynb (#3705)
Fix links for opening the notebook
2024-11-21 15:43:47 +00:00
Vertex MG TeamandCopybara-Service 3e7d427a1b Download only the required files notebook
PiperOrigin-RevId: 698657845
2024-11-20 23:04:15 -08:00
Vertex MG TeamandCopybara-Service 194978fd1c A minor fix in the HexLLM deploy section
PiperOrigin-RevId: 698420315
2024-11-20 09:36:56 -08:00
Vertex MG TeamandCopybara-Service 877305d425 Fix lint issues
PiperOrigin-RevId: 698241868
2024-11-19 20:47:46 -08:00
Vertex MG TeamandCopybara-Service 37c88851bd Enable dedicate endpoint for model_garden_gemma_finetuning_on_vertex.ipynb
PiperOrigin-RevId: 697850787
2024-11-18 20:12:55 -08:00
Vertex MG TeamandCopybara-Service 3b17b30051 Deprecate the Pytorch PEFT notebook. Notebooks such as model_garden_pytorch_llama3_1_finetuning.ipynb demonstrate the usage of the peft in VMG.
PiperOrigin-RevId: 697850024
2024-11-18 20:10:01 -08:00
Vertex MG TeamandCopybara-Service 8883d8e211 Enable dedicate endpoint for model_garden_pytorch_llama3_1_deployment.ipynb
PiperOrigin-RevId: 697498389
2024-11-17 22:16:51 -08:00
Vertex MG TeamandCopybara-Service 0879ef0057 Fix timestamp parameter in prediction section.
PiperOrigin-RevId: 697114085
2024-11-16 00:29:07 -08:00
Vertex MG TeamandCopybara-Service 4adff04d06 Enable dedicate endpoint for model_garden_pytorch_mixtral_deployment.ipynb
PiperOrigin-RevId: 696942601
2024-11-15 11:07:19 -08:00
Vertex MG TeamandCopybara-Service e6a8641896 Enable dedicate endpoint for model_garden_pytorch_qwen2_deployment.ipynb
PiperOrigin-RevId: 696937628
2024-11-15 10:52:28 -08:00
Pedro MelendezandGitHub a740092ae2 Added notebook titled "backoff_and_retry_for_LLMs.ipynb" to the /notebooks/community/generative_ai/ directory (#3706)
* Adding backoff and retry notebook

* Formatted notebook

* Formatted notebook

* Formatted notebook

* Formatted notebook

* Changed URL

* Format changes

* Remoevd URL

* Added formatting

* Added codeowner entry

* Added summary

* Added note about costs

* Lint format
2024-11-15 18:23:02 +00:00
Eric DongandGitHub 6289d1f0a6 feat: exclude model garden dockerfilers from dependabot checks (#3712) 2024-11-15 15:05:21 +00:00
Eric DongandGitHub 1301bd2ad2 Revert "Bump deepspeed (#3658)" (#3711)
This reverts commit 6c9bdba210.
2024-11-14 16:51:13 +00:00
Vertex MG TeamandCopybara-Service 21d5f7edcb Download only the required files notebook
PiperOrigin-RevId: 695745528
2024-11-12 08:31:36 -08:00
Vertex MG TeamandCopybara-Service 7f13964632 Update the notebook to remove the duplicated model agreement step.
PiperOrigin-RevId: 695575396
2024-11-11 20:23:02 -08:00
Vertex MG TeamandCopybara-Service 231dbae9e9 Update the recursion mae local inference notebook, to always download the model weights from HuggingFace Hub, instead of directly load the model weights from HF. The latter, for some reason, cannot find the model.safetensors file from the repo.
PiperOrigin-RevId: 695573672
2024-11-11 20:13:58 -08:00
Vertex MG TeamandCopybara-Service bf0507bfda OWL-ViT2 Notebook
PiperOrigin-RevId: 695315779
2024-11-11 06:46:38 -08:00
Mend RenovateandGitHub 3361dc70d1 chore(deps): update dependency nbqa to v1.9.1 (#3699) 2024-11-11 13:29:15 +00:00
Mend RenovateandGitHub e05c83832d chore(config): migrate config renovate.json (#3677) 2024-11-11 13:28:31 +00:00
Aaron DietzandGitHub e2f1aaed9d Update feature_store_streaming_ingestion_sdk.ipynb (#3694)
Moved mention of Feature Store (Legacy) so that it shows up in our notebook description when we generate the list of notebooks.
2024-11-11 13:26:54 +00:00
5d71616e8b Add ability to copy specific model artifacts (#3700)
Co-authored-by: Rayan Dasoriya <dasoriya@google.com>
2024-11-11 13:25:24 +00:00
57f598052e Update usage tracking metrics in the finetuning notebooks (#3695)
Co-authored-by: Rayan Dasoriya <dasoriya@google.com>
2024-11-11 13:24:27 +00:00
Vertex MG TeamandCopybara-Service 439a686f16 Create a notebook for the image_feature_extraction_mae model.
PiperOrigin-RevId: 694586865
2024-11-08 11:54:42 -08:00
Vertex MG TeamandCopybara-Service b99a1d8f42 Create a notebook for local inference for the partner recursion mae model.
PiperOrigin-RevId: 694248672
2024-11-07 14:28:31 -08:00
Vertex MG TeamandCopybara-Service 2d1339731f Present --data-parallel-size for Hex-LLM deployment; add advanced config for CodeGemma.
PiperOrigin-RevId: 693598038
2024-11-05 22:48:19 -08:00
Vertex MG TeamandCopybara-Service c71edd71e7 OWL-ViT Notebook
PiperOrigin-RevId: 693140455
2024-11-04 17:09:00 -08:00
Vertex MG TeamandCopybara-Service e0d078b6bb The HF TGI notebook should use the TGI 2.3 serving container, which is the latest
PiperOrigin-RevId: 693031093
2024-11-04 11:15:58 -08:00
Sujit KhasnisandGitHub 267b45f6aa feat:claude notebook 3.5 update (#3690) 2024-11-04 18:43:21 +00:00
Vertex MG TeamandCopybara-Service f1bb2f7aea Stable Diffusion 2.1 Dreambooth Finetuning notebook
PiperOrigin-RevId: 691686520
2024-10-30 23:24:39 -07:00
Vertex MG TeamandCopybara-Service c2132249c9 Fix Llama 3.1 deployment notebook vLLM version.
PiperOrigin-RevId: 691669244
2024-10-30 21:58:30 -07:00
Vertex MG TeamandCopybara-Service d4fdc15892 Update vLLM version and embedded links in Llama 3.2 deployment notebook.
PiperOrigin-RevId: 691604159
2024-10-30 17:13:44 -07:00
Sujit KhasnisandGitHub 43bc13ee66 feat: Claude region update(euw1) (#3685) 2024-10-30 16:52:16 +00:00
f11502ec80 Fix pip dependency error (#3683)
Co-authored-by: Rayan Dasoriya <dasoriya@google.com>
2024-10-30 12:38:10 +00:00
Vertex MG TeamandCopybara-Service 7f7cf51dd7 Enable dedicate endpoint for Mistral deployment
PiperOrigin-RevId: 691250786
2024-10-29 19:44:48 -07:00
Vertex MG TeamandCopybara-Service f13e086012 Add optimized vLLM to Mixtral deployment notebook.
PiperOrigin-RevId: 691209007
2024-10-29 16:52:16 -07:00
Vertex MG TeamandCopybara-Service b995ea7adf Add optimized vLLM to Llama 3.1 deployment notebook.
PiperOrigin-RevId: 691207909
2024-10-29 16:48:35 -07:00
Vertex MG TeamandCopybara-Service b183a3d796 Update model_garden_pytorch_sd_xl_finetuning_dreambooth_lora.ipynb
PiperOrigin-RevId: 689953226
2024-10-25 16:47:05 -07:00
Vertex MG TeamandCopybara-Service e500467637 Update Mediapipe Image Generation notebook
PiperOrigin-RevId: 689923141
2024-10-25 14:59:38 -07:00
Vertex MG TeamandCopybara-Service ad6035ae80 No public description
PiperOrigin-RevId: 688364622
2024-10-25 14:59:28 -07:00
Ivan NardiniandGitHub 731bc7a780 fix: Adding autoscaling to RoV cluster management notebook (#3674)
* new notebook

* fix issue

* linter passed

* change pname

* linter passed

* remove script
2024-10-25 17:28:47 +00:00
lee1premiumandGitHub 6dd9c27781 feat: Getting tuned embeddings using text-embedding-005. (#3673)
* feat: Getting tuned embeddings using text-embedding-005.

* feat: Getting tuned embeddings using text-embedding-005.

* feat: Getting tuned embeddings using text-embedding-005.
2024-10-24 00:49:45 +00:00
Mend RenovateandGitHub 830dc9f1c3 chore(deps): update dependency pyupgrade to v3.19.0 (#3667) 2024-10-23 12:55:43 +00:00
lee1premiumandGitHub 23a1398504 feat: Getting embeddings using text-embedding-005. (#3672)
* feat: Getting embeddings using text-embedding-005.

* feat: Getting embeddings using text-embedding-005.

* feat: Getting embeddings using text-embedding-005.

* feat: Getting embeddings using text-embedding-005.
2024-10-23 12:55:15 +00:00
Sujit KhasnisandGitHub 7b86dec7f7 fix: update titles, headers (#3670) 2024-10-22 20:24:40 +00:00
Sujit KhasnisandGitHub 02f1edd942 fix: model ordering in dropdown (#3669) 2024-10-22 16:15:10 +00:00
Sujit KhasnisandGitHub b8a3fafb9a feat: Claude notebook update (#3668) 2024-10-22 15:51:51 +00:00
Tianrui YangandGitHub 26849e7f20 fea: add PSC example code in Feature Store embedding notebook (#3663) 2024-10-22 14:50:02 +00:00
Shawn YangandCopybara-Service a96c2926af fix: Fix Notebook format issue in Preview by removing output.
PiperOrigin-RevId: 688364596
2024-10-21 19:54:06 -07:00
Shawn YangandCopybara-Service 90eaf5c7fe fix: Fix Notebook format issue in Preview.
PiperOrigin-RevId: 688317050
2024-10-21 16:44:52 -07:00
Shawn YangandCopybara-Service d6ad52e2cd feat: Update Reasoning Engine + Llama 3.1 models notebook with Function Calling Agent.
PiperOrigin-RevId: 688245912
2024-10-21 13:09:51 -07:00
Vertex MG TeamandCopybara-Service e24c20b372 Adding Phi-3-medium TPU configs to Colab deployment notebook
PiperOrigin-RevId: 687430227
2024-10-18 14:43:49 -07:00
Vertex MG TeamandCopybara-Service fb1871d100 Update Llama 3.1 MaaS naming.
PiperOrigin-RevId: 687362552
2024-10-18 11:09:18 -07:00
Vertex MG TeamandCopybara-Service a7dd5aa5a9 Adding Phi-3-mini TPU configs to Colab deployment notebook
PiperOrigin-RevId: 687333561
2024-10-18 09:40:46 -07:00
Vertex MG TeamandCopybara-Service 307f1a8d41 Reformat Instant ID notebook
PiperOrigin-RevId: 687309283
2024-10-18 08:20:02 -07:00
Vertex MG TeamandCopybara-Service 53895497c0 Update chat completions URL for dedicated endpoint in Gemma and Llama deployment notebooks.
PiperOrigin-RevId: 687307214
2024-10-18 08:11:33 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6c9bdba210 Bump deepspeed (#3658)
Bumps [deepspeed](https://github.com/microsoft/DeepSpeed) from 0.14.4 to 0.15.1.
- [Release notes](https://github.com/microsoft/DeepSpeed/releases)
- [Commits](https://github.com/microsoft/DeepSpeed/compare/v0.14.4...v0.15.1)

---
updated-dependencies:
- dependency-name: deepspeed
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-18 14:31:08 +00:00
Vertex MG TeamandCopybara-Service 133395a908 Use /tmp as the dataset directory since the notebook executor environment may not use /content as the base directory
PiperOrigin-RevId: 687194370
2024-10-18 00:25:47 -07:00
Vertex MG TeamandCopybara-Service bd29537128 Remove Weaviate and Pinecone notebooks as they are relocated to another directory.
PiperOrigin-RevId: 686788959
2024-10-16 23:39:33 -07:00
Vertex MG TeamandCopybara-Service 3ac1c34633 Reformat stable diffusion gradio notebook
PiperOrigin-RevId: 686463355
2024-10-16 04:53:50 -07:00
Vertex MG TeamandCopybara-Service 2e56046d25 Add Llama 3.2 evaluation notebook.
PiperOrigin-RevId: 686349456
2024-10-15 20:51:39 -07:00
Vertex MG TeamandCopybara-Service f6dd048f98 Update Gemma PEFT notebook to use the new training docker image and add more instructions.
PiperOrigin-RevId: 686347835
2024-10-15 20:44:39 -07:00
Vertex MG TeamandCopybara-Service 9da812bf2e Update model_garden_pytorch_bart_large_cnn.ipynb
PiperOrigin-RevId: 686230074
2024-10-15 14:00:45 -07:00
Sujit KhasnisandGitHub 11d361f727 refactor: verbiage updates, minor code updates (#3655) 2024-10-15 18:13:14 +00:00
Mend RenovateandGitHub de9dd0f850 Update python Docker tag to v3.13 (#3626) 2024-10-15 13:49:03 +00:00
482170765andGitHub f20e9590a5 Update a custom job example (#3639)
* This is a custom job example of kfp v2
2024-10-15 13:48:37 +00:00
Mend RenovateandGitHub 5443944739 chore(deps): update dependency pyupgrade to v3.18.0 (#3640) 2024-10-15 13:48:00 +00:00
Sujit KhasnisandGitHub f2ee8a582f feat: NVIDIA NIM on Vertex Ai walkthrough (#3579)
* feat: NVIDIA NIM on Vertex Ai walkthrough

* fix: PR comments resolved

* fix: PR comments resolved#2

* fix: excep handling

* fix: handle if model not uploaded
2024-10-15 13:47:25 +00:00
Vertex MG TeamandCopybara-Service 1d3d3980e4 Fix the issue that the model and endpoint are not stored in the dictionary.
PiperOrigin-RevId: 685966395
2024-10-14 22:50:05 -07:00
Vertex MG TeamandCopybara-Service f9b371e030 Update the notebook to use A100 80GB for vLLM deployment.
PiperOrigin-RevId: 685934295
2024-10-14 20:42:11 -07:00
Dustin LuongandCopybara-Service a1d4026020 Update the notebook to use the Vertex SDK to send requests to the deployed endpoint instead of openai SDK.
PiperOrigin-RevId: 685713862
2024-10-14 08:23:23 -07:00
Vertex MG TeamandCopybara-Service eb166ed3df Update the default model to whisper-large-v3-turbo and add placeholder for language.
PiperOrigin-RevId: 685618090
2024-10-14 01:37:22 -07:00
Vertex MG TeamandCopybara-Service 08278233ff Update Qwen2 deployment notebook to support A100, H100 and A100 80GB and fix lint issues.
PiperOrigin-RevId: 685555814
2024-10-13 20:41:34 -07:00
Vertex MG TeamandCopybara-Service 51bf2a38ff Minor fix
PiperOrigin-RevId: 685020524
2024-10-11 18:22:58 -07:00
Vertex MG TeamandCopybara-Service 2dede7552b Added more details about notebook parameters for predict section. Also gave storage.objectViewer access service account for buckets used for predict section.
PiperOrigin-RevId: 684700814
2024-10-10 22:12:39 -07:00
Vertex MG TeamandCopybara-Service 905e89ad64 Add new notebook for Knowledge Engine API with Pinecone.
PiperOrigin-RevId: 684666941
2024-10-10 19:56:49 -07:00
Vertex MG TeamandCopybara-Service ec297e663c Add labels to finetuning notebooks
PiperOrigin-RevId: 684655746
2024-10-10 19:11:32 -07:00
Vertex MG TeamandCopybara-Service d15caa9ed5 Enable dedicate endpoint for TGI Gemma2 predict and chat completion
PiperOrigin-RevId: 684623757
2024-10-10 16:55:28 -07:00
Vertex MG TeamandCopybara-Service df55634281 Added more details to deployment section of notebook.
PiperOrigin-RevId: 684343445
2024-10-10 01:19:34 -07:00
Vertex MG TeamandCopybara-Service af84cdc46b Fix the error on stable_diffusion_gradio notebook for text2image models.
PiperOrigin-RevId: 684108916
2024-10-09 11:26:08 -07:00
Shawn YangandCopybara-Service e946e2b304 feat: Add Reasoning Engine with Llama 3.1 models notebook.
PiperOrigin-RevId: 684083427
2024-10-09 10:16:47 -07:00
Vertex MG TeamandCopybara-Service d7550f4756 LLaVA Deployment notebook
PiperOrigin-RevId: 683832899
2024-10-08 18:20:17 -07:00
Vertex MG TeamandCopybara-Service 3088fc896d Update vLLM and chat completions prediction samples.
PiperOrigin-RevId: 683698377
2024-10-08 11:25:16 -07:00
Vertex MG TeamandCopybara-Service 75cf2fe9b9 Add Qwen2.5 related updates to the notebook
PiperOrigin-RevId: 683423186
2024-10-07 19:46:47 -07:00
Vertex MG TeamandCopybara-Service d68494bd3b Update Prompt Guard deployment notebook.
PiperOrigin-RevId: 683352100
2024-10-07 15:42:32 -07:00
02f66799c8 Add README.md for PEFT train docker template (#3625)
Co-authored-by: minwoopark <minwoopark@google.com>
2024-10-07 20:54:53 +00:00
482170765andGitHub 6b10fe9636 Upload classifier predictor sample of sklearn (#3623)
Upload a classifier predictor sample using scikit-learn lib.
2024-10-07 20:54:18 +00:00
Mend RenovateandGitHub 4c3693929d Update dependency black to v24.10.0 (#3624) 2024-10-07 20:49:06 +00:00
Vertex MG TeamandCopybara-Service 6bff6af34a Fix lint issues in the notebooks
PiperOrigin-RevId: 682565175
2024-10-04 22:19:13 -07:00
Vertex MG TeamandCopybara-Service 219474b39c Add new notebook for RAG API with Weaviate.
PiperOrigin-RevId: 682497053
2024-10-04 16:58:17 -07:00
Vertex MG TeamandCopybara-Service bc152ece40 Fix the RAG notebook format.
PiperOrigin-RevId: 682352935
2024-10-04 09:42:36 -07:00
Vertex MG TeamandCopybara-Service 761b918de1 Use 'import datetime' instead of 'from datetime import datetime'
PiperOrigin-RevId: 682309409
2024-10-04 07:14:55 -07:00
Vertex MG TeamandCopybara-Service 6a574ea15d Update the docker image for Gemma finetuning.
PiperOrigin-RevId: 682163952
2024-10-03 21:52:34 -07:00
Vertex MG TeamandCopybara-Service c5212982d0 Fix the RAG notebook link.
PiperOrigin-RevId: 682031626
2024-10-03 14:27:27 -07:00
Vertex MG TeamandCopybara-Service 3986128f78 Autogluon notebook
PiperOrigin-RevId: 681881078
2024-10-03 08:05:26 -07:00
Vertex MG TeamandCopybara-Service 575025a1cd Fix format issues
PiperOrigin-RevId: 681739757
2024-10-02 23:33:38 -07:00
Vertex MG TeamandCopybara-Service fe42990c9d Update Llama 2 evaluation notebook.
PiperOrigin-RevId: 681657733
2024-10-02 17:49:18 -07:00
Dustin LuongandCopybara-Service bd3283b2a6 Internal change.
PiperOrigin-RevId: 681516025
2024-10-02 17:49:03 -07:00
24c001eaf6 Update PEFT train docker code (#3613)
Co-authored-by: minwoopark <minwoopark@google.com>
2024-10-02 19:13:30 +00:00
Aaron DietzandGitHub e500e70580 Update spark_on_ray_on_vertex_ai.ipynb (#3605)
Revised the overview so our github notebook list script will pick up the "last line" in the overview and start including this notebook in the output.
2024-10-02 18:13:15 +00:00
Vertex MG TeamandCopybara-Service bfb3775813 Fix the region for llama3 hex-llm chat completion
PiperOrigin-RevId: 681511627
2024-10-02 10:47:45 -07:00
Vertex MG TeamandCopybara-Service 538432df5c Fix the region for llama3 hex-llm chat completion
PiperOrigin-RevId: 681167715
2024-10-01 14:33:19 -07:00
Minwoo ParkandCopybara-Service 7764173895 Minor copyright year update.
PiperOrigin-RevId: 681141148
2024-10-01 13:21:14 -07:00
Vertex MG TeamandCopybara-Service 142237c1f5 Add region suggestion for A100_80GB and H100_80GB to notebooks.
PiperOrigin-RevId: 681090786
2024-10-01 11:07:54 -07:00
Vertex MG TeamandCopybara-Service 791a66ec54 Fix formatting issue
PiperOrigin-RevId: 681076924
2024-10-01 10:35:31 -07:00
Vertex MG TeamandCopybara-Service 8379361eff Fix typo - Use "prompt" in image captioning sample request.
PiperOrigin-RevId: 681074023
2024-10-01 10:27:17 -07:00
Vertex MG TeamandCopybara-Service a9f6b2d7f0 Adding Phi-3.5-MoE-instruct variant to Phi-3 deployment notebook.
PiperOrigin-RevId: 681057221
2024-10-01 09:44:32 -07:00
Vertex MG TeamandCopybara-Service 8f077f9bc2 This notebook demonstrates deploying prebuilt Whisper Large models.
PiperOrigin-RevId: 681039051
2024-10-01 08:55:51 -07:00
Sujit KhasnisandGitHub c5ec7e5d83 feat: mistral ai sdk support for vertexai (#3601)
* feat: mistral ai sdk support fro vertexai

* feat: mistral ai sdk support fro vertexai, token fix

* feat: mistral ai sdk support fro vertexai, excep handling
2024-10-01 14:59:10 +00:00
Vertex MG TeamandCopybara-Service daf92aa672 Fix lint issue
PiperOrigin-RevId: 680835893
2024-09-30 20:57:00 -07:00
Bhaskar GoyalandGitHub 4070d8b0e5 Update region for Claude Haiku and Sonnet 3.5 (#3606) 2024-09-30 23:06:35 +00:00
Vertex MG TeamandCopybara-Service a31f1e037e Use dedicated endpoint as default for Gemma deployment on vertex
PiperOrigin-RevId: 680645756
2024-09-30 11:09:15 -07:00
sharkeshdandGitHub dc9d1032a2 Update Dockerfile (#3576)
This multi-stage approach keeps your final image clean and lightweight.
2024-09-30 17:45:37 +00:00
Aaron DietzandGitHub d5f93bf8a9 Update spark_on_ray_on_vertex_ai.ipynb (#3594)
Updated branding/name of Vertex AI Workbench, added "Overview" heading.

Why? Not having an "Overview" heading prevents this notebook from getting picked up in our notebook list output.
2024-09-30 17:45:06 +00:00
Aaron DietzandGitHub 6a3bc32e45 Update xai_text_classification_feature_attributions.ipynb (#3596)
Removed bolding that doesn't render properly when we port the content to our docs
2024-09-30 17:44:09 +00:00
Aaron DietzandGitHub 1d1d4a586d Update get_started_with_model_monitoring_setup.ipynb (#3597)
Removed bolding that doesn't render properly when we port the content to our docs
2024-09-30 17:43:26 +00:00
Aaron DietzandGitHub b3c7ecb8cd Update hyperparameter_tuning_xgboost.ipynb (#3598)
Removed bolding that doesn't render properly when we port the content to our docs
2024-09-30 17:42:47 +00:00
Aaron DietzandGitHub cec4e447ab Update chicago_taxi_fare_prediction.ipynb (#3599)
Removed bolding that doesn't render properly when we port the content to our docs
2024-09-30 17:41:55 +00:00
Vertex MG TeamandCopybara-Service e61be64040 Use dedicated endpoint as default for Gemma2 deployment on vertex
PiperOrigin-RevId: 680634868
2024-09-30 10:41:39 -07:00
Minwoo ParkandCopybara-Service 148a6fad99 Add instruction to run TensorBoard in Cloud Shell
PiperOrigin-RevId: 679746165
2024-09-27 15:20:04 -07:00
Aaron DietzandGitHub ca84581ed0 Fixed missing word in predictive_maintenance_usecase.ipynb (#3581)
Added a word to make a sentence parse correctly.
2024-09-26 20:49:03 +00:00
Vertex MG TeamandCopybara-Service 4d1c59cba4 Minor fix in llama3.2 notebook
PiperOrigin-RevId: 679190955
2024-09-26 09:59:49 -07:00
Vertex MG TeamandCopybara-Service 5cd0c0e782 Minor change to the VOT and ZipNeRF notebooks.
PiperOrigin-RevId: 679159158
2024-09-26 08:27:19 -07:00
Vertex MG TeamandCopybara-Service 402231e2b6 Update finetuning notebook with stable_20240909 training image
PiperOrigin-RevId: 679139242
2024-09-26 07:25:59 -07:00
Vertex MG TeamandCopybara-Service 38e1a46a7b Fix typo in vllm args
PiperOrigin-RevId: 679131835
2024-09-26 06:57:15 -07:00
0727e19520 Add vmg templates, dataset_validation_util and update common_util (#3586)
* Add vmg templates, dataset_validation_util and update common_util

* Add name to CODEOWNERS

* Update common_util.py

---------

Co-authored-by: Rayan Dasoriya <dasoriya@google.com>
2024-09-26 13:16:09 +00:00
Vertex MG TeamandCopybara-Service 30c3e627a7 Update the RAG notebook for Llama3 models.
PiperOrigin-RevId: 679007617
2024-09-25 23:31:06 -07:00
Vertex MG TeamandCopybara-Service bdea63ec41 Adding Phi-3.5-mini-instruct variant to Phi-3 deployment notebook.
PiperOrigin-RevId: 678888299
2024-09-25 16:24:22 -07:00
Vertex MG TeamandCopybara-Service 2e5410fe35 No public description
PiperOrigin-RevId: 678885403
2024-09-25 16:15:39 -07:00
Vertex MG TeamandCopybara-Service 6f9813cba5 Update sample requests in Llama 3.2 OpenAI MaaS notebook.
PiperOrigin-RevId: 678828203
2024-09-25 13:35:19 -07:00
Vertex MG TeamandCopybara-Service 115d8f991a Update Llama 3.2 OpenAI MaaS notebook.
PiperOrigin-RevId: 678781425
2024-09-25 11:31:37 -07:00
Vertex MG TeamandCopybara-Service 7fd31a65ae Support deploying llama 3.2 guard models on model garden.
PiperOrigin-RevId: 678760438
2024-09-25 10:44:40 -07:00
Vertex MG TeamandCopybara-Service 883cc93ab7 Add Llama 3.2 OpenAI MaaS notebook.
PiperOrigin-RevId: 678753976
2024-09-25 10:27:22 -07:00
Changyu ZhuandCopybara-Service 1363868542 Add streaming chat completions example to the HF TGI notebook
PiperOrigin-RevId: 678753372
2024-09-25 10:26:02 -07:00
Vertex MG TeamandCopybara-Service 09122d1479 Support deploying llama 3.2 models on model garden.
PiperOrigin-RevId: 678752790
2024-09-25 10:24:34 -07:00
Minwoo ParkandCopybara-Service 2adb19be7c Resolving conflict
PiperOrigin-RevId: 678701565
2024-09-25 08:00:32 -07:00
Aaron DietzandGitHub c588d81d02 Update notebook_template_review.py (#3578)
Removed "external" class for the "open-notebook-in..." links. Style guide indicates we should avoid using the "external" class.
2024-09-25 12:43:15 +00:00
ShunpeIIIandGitHub e51af44898 Remove the link of the notebook that has been moved to the community. (#3571) 2024-09-23 14:39:40 +00:00
Vertex MG TeamandCopybara-Service 0872ce0a87 Fix the pip install command in the notebooks.
PiperOrigin-RevId: 677767387
2024-09-23 06:31:07 -07:00
Vertex MG TeamandCopybara-Service 867d7b7410 Fix max_context_length in Qwen2 deployment notebook.
PiperOrigin-RevId: 677762785
2024-09-23 06:12:36 -07:00
Vertex MG TeamandCopybara-Service d8e628b3ab Add Flux gradio notebook
PiperOrigin-RevId: 676901084
2024-09-20 10:57:44 -07:00
Vertex MG TeamandCopybara-Service dd4767546e Update gradio notebooks for Instant ID and Stable Diffusion
PiperOrigin-RevId: 676476082
2024-09-19 10:45:16 -07:00
Vertex MG TeamandCopybara-Service d185eff6ca Add region option to model garden notebooks.
PiperOrigin-RevId: 676473760
2024-09-19 10:39:10 -07:00
Vertex MG TeamandCopybara-Service f00c9cdeef Update Instant ID notebook
PiperOrigin-RevId: 676466759
2024-09-19 10:21:26 -07:00
Vertex MG TeamandCopybara-Service 12bd07e8fc Fix use dedicated endpoint parameter type in Pytorch Gemma Serving
PiperOrigin-RevId: 676433256
2024-09-19 08:55:18 -07:00
Vertex MG TeamandCopybara-Service 61d866aef3 Update deploy function.
PiperOrigin-RevId: 676408091
2024-09-19 07:37:53 -07:00
Vertex MG TeamandCopybara-Service bb2c8c502d Fix use dedicated endpoint parameter type in Gemma Serving
PiperOrigin-RevId: 676121299
2024-09-18 14:00:08 -07:00
Vertex MG TeamandCopybara-Service ea7915b38a Use standard id as MODEL_ID.
PiperOrigin-RevId: 675778051
2024-09-17 18:04:39 -07:00
Dustin LuongandGitHub b5ee2ea16b Copy Vertex MG files to notebook folder (#3560) 2024-09-18 00:47:12 +00:00
Vertex MG TeamandCopybara-Service 6daebf78bc Support deploying Hex-LLM on multi-hosts TPU, like v5e-16.
PiperOrigin-RevId: 675736856
2024-09-17 15:42:15 -07:00
Changyu ZhuandCopybara-Service 2752658b6d Minor updates to the MoViNet notebooks
PiperOrigin-RevId: 675723676
2024-09-17 15:03:58 -07:00
Vertex MG TeamandCopybara-Service 55ed17d227 Add dedicated endpoint support to Gemma Serving
PiperOrigin-RevId: 675627850
2024-09-17 10:49:28 -07:00
yutatanamotoandGitHub 3808d495fd fix official sample notebook for vector search (#3500)
* fix restriction declaration (allow_list → allow) for vector search index

* fix folder name in CODEOWNERS (/matching_engine → /vector_search)
2024-09-17 12:45:20 +00:00
Vertex MG TeamandCopybara-Service 6c78212f92 Add enable_model_cpu_offload option for flux example in local inference notebook
PiperOrigin-RevId: 675227512
2024-09-16 11:31:55 -07:00
william-ChengChungChuandGitHub b6771091fa Upload classifier predictor sample of xgboost (#3552)
This example uses aiplatform and xgboost to provide a classifier predictor.
2024-09-16 14:57:00 +00:00
Vertex MG TeamandCopybara-Service 6a62443d01 Stable Diffusion v2.1 notebook
PiperOrigin-RevId: 674233624
2024-09-13 03:52:22 -07:00
Vertex MG TeamandCopybara-Service eb7d8456be Add instructions for applying Llama Guard on MaaS.
PiperOrigin-RevId: 674144050
2024-09-12 22:14:50 -07:00
Changyu ZhuandCopybara-Service df4adf877e internal change
PiperOrigin-RevId: 673999030
2024-09-12 22:14:36 -07:00
Dustin LuongandGitHub 75fe45e07c Copy Vertex MG files to notebook folder (#3542) 2024-09-12 21:20:54 +00:00
Ayush AgrawalandCopybara-Service 0f53fec3fd Add Weaviate Vector DB option for corpus creation to rag notebook
PiperOrigin-RevId: 673948076
2024-09-12 12:08:17 -07:00
Vertex MG TeamandCopybara-Service 3acee41659 Use standard id as MODEL_ID.
PiperOrigin-RevId: 673693818
2024-09-11 23:21:44 -07:00
Vertex MG TeamandCopybara-Service e5a016651e Use standard id as MODEL_ID.
PiperOrigin-RevId: 673693121
2024-09-11 23:19:28 -07:00
Vertex MG TeamandCopybara-Service 41add3b060 Stable Diffusion XL 1.0 notebook
PiperOrigin-RevId: 673679901
2024-09-11 22:25:20 -07:00
Vertex MG TeamandCopybara-Service d05c27e077 Stable Diffusion XL Lightning notebook
PiperOrigin-RevId: 673675311
2024-09-11 22:08:20 -07:00
Vertex MG TeamandCopybara-Service 370f6cd0bf Use standard id as MODEL_ID.
PiperOrigin-RevId: 673522391
2024-09-11 13:49:45 -07:00
Sujit KhasnisandGitHub c53d3f215b feat: add support for jamba-large in euw4 (#3540) 2024-09-11 20:01:41 +00:00
Vertex MG TeamandCopybara-Service 760ad8ff32 Update OpenAI chat completions MaaS notebook with new variants.
PiperOrigin-RevId: 673208459
2024-09-10 20:33:24 -07:00
Vertex MG TeamandCopybara-Service f128e42a57 Use standard id as MODEL_ID.
PiperOrigin-RevId: 673163937
2024-09-10 17:16:56 -07:00
Dustin LuongandCopybara-Service fc917137da Use vLLM docker to deploy dolly-v2 model.
PiperOrigin-RevId: 673051532
2024-09-10 12:00:12 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d7225f4ec7 chore(deps): bump tensorflow (#3532)
Bumps [tensorflow](https://github.com/tensorflow/tensorflow) from 2.7.2 to 2.12.1.
- [Release notes](https://github.com/tensorflow/tensorflow/releases)
- [Changelog](https://github.com/tensorflow/tensorflow/blob/master/RELEASE.md)
- [Commits](https://github.com/tensorflow/tensorflow/compare/v2.7.2...v2.12.1)

---
updated-dependencies:
- dependency-name: tensorflow
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 13:57:47 +00:00
Changyu ZhuandCopybara-Service e32337ac46 Add a template HF Pytorch Inference model deployment notebook
PiperOrigin-RevId: 672727375
2024-09-09 17:18:55 -07:00
Vertex MG TeamandCopybara-Service df1e6ea815 Use standard id as MODEL_ID.
PiperOrigin-RevId: 672664027
2024-09-09 13:58:16 -07:00
Dustin LuongandCopybara-Service d6a8ed040a Update deploy function.
PiperOrigin-RevId: 672608260
2024-09-09 11:17:52 -07:00
Mend RenovateandGitHub edeac27417 chore(deps): update dependency flake8 to v7.1.1 (#3382) 2024-09-09 12:32:46 +00:00
Vertex MG TeamandCopybara-Service d4dfcad09e Use standard id as MODEL_ID.
PiperOrigin-RevId: 672348661
2024-09-08 16:41:53 -07:00
Vertex MG TeamandCopybara-Service fcb353af5e Use standard id as MODEL_ID.
PiperOrigin-RevId: 672347208
2024-09-08 16:30:50 -07:00
Vertex MG TeamandCopybara-Service 47d04c117d Fix some formatting issues in the timesfm templated notebook.
PiperOrigin-RevId: 671907130
2024-09-06 15:19:01 -07:00
Vertex MG TeamandCopybara-Service 94168e1e10 Adding Phi-3-mini-128k variant to Phi-3 deployment notebook.
PiperOrigin-RevId: 671736868
2024-09-06 06:38:17 -07:00
Kaushik KoiladaandGitHub 276ff3779e fix: colab enterprise link fix (#3509) 2024-09-06 12:33:04 +00:00
Changyu ZhuandCopybara-Service 6a858a7b1f Add a template HF TEI model deployment notebook
PiperOrigin-RevId: 671513324
2024-09-05 14:34:17 -07:00
255 changed files with 39742 additions and 9073 deletions
+10
View File
@@ -0,0 +1,10 @@
version: 2
updates:
# Ignore model garden dockerfiles:
- package-ecosystem: "npm"
directory: "/community-content/vertex_model_garden"
schedule:
interval: "monthly"
ignore:
- dependency-name: "*"
+1 -1
View File
@@ -4,7 +4,7 @@
# 2. To lint specific notebooks:
# docker run -v ${PWD}:/setup/app gcr.io/python-docs-samples-tests/notebook_linter:latest notebooks/1.ipynb notebooks/2.ipynb
FROM python:3.12
FROM python:3.13
WORKDIR setup
+5 -5
View File
@@ -2,9 +2,9 @@ git+https://github.com/tensorflow/docs
ipython
jupyter
nbconvert
black==24.8.0
pyupgrade==3.17.0
isort==5.13.2
flake8==7.1.0
nbqa==1.9.0
black==25.1.0
pyupgrade==3.19.1
isort==6.0.0
flake8==7.1.1
nbqa==1.9.1
+1
View File
@@ -20,6 +20,7 @@
/vertex_model_garden/model_oss/movinet @KCFindstr
/vertex_model_garden/model_oss/data_converter @KCFindstr
/vertex_model_garden/model_oss/peft @weigary
/vertex_model_garden/model_oss/peft/templates @rayandasoriya
/vertex_model_garden/model_oss/lm-evaluation-harness @kathyyu-google
/vertex_model_garden/model_oss/tfvision @dstnluong-google
/vertex_model_garden/model_oss/fvlm @minwoo33park
@@ -1,16 +1,40 @@
FROM pytorch/pytorch:1.8.1-cuda11.1-cudnn8-runtime
# Stage 1: Build Environment
FROM pytorch/pytorch:1.8.1-cuda11.1-cudnn8-runtime AS builder
# Install necessary tools and dependencies
RUN apt-get update && \
apt-get install -y curl gnupg && \
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && \
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - && \
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - && \
apt-get update -y && \
apt-get install google-cloud-sdk -y
apt-get install -y google-cloud-sdk
# Copy application code
COPY . /trainer
# Set working directory
WORKDIR /trainer
RUN pip install -r requirements.txt
# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
ENTRYPOINT ["python", "-m", "task"]
# Stage 2: Runtime Environment
FROM pytorch/pytorch:1.8.1-cuda11.1-cudnn8-runtime
# Install Google Cloud SDK
RUN apt-get update && \
apt-get install -y curl gnupg && \
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && \
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - && \
apt-get update -y && \
apt-get install -y google-cloud-sdk && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Copy from the builder stage
COPY --from=builder /trainer /trainer
# Set working directory
WORKDIR /trainer
# Set the entry point
ENTRYPOINT ["python", "-m", "task"]
@@ -1,5 +1,5 @@
dataclasses==0.6
google-cloud-aiplatform==1.8.1
tensorflow==2.7.2
tensorflow==2.12.1
pillow==10.3.0
tf-agents==0.8.0
@@ -0,0 +1,33 @@
import numpy as np
import os
import pickle
from google.cloud.aiplatform.constants import prediction
from google.cloud.aiplatform.utils import prediction_utils
from google.cloud.aiplatform.prediction.predictor import Predictor
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import RidgeClassifier
class LinearRegressionPredictor(Predictor):
def __init__(self):
return
def load(self, artifacts_uri: str) -> None:
prediction_utils.download_model_artifacts(artifacts_uri)
if os.path.exists(prediction.MODEL_FILENAME_PKL):
self._model = pickle.load(open(prediction.MODEL_FILENAME_PKL, "rb"))
else:
self._model = RidgeClassifier()
X, y = load_breast_cancer(return_X_y=True)
self._model.fit(X, y)
def preprocess(self, prediction_input: dict) -> np.ndarray:
instances = prediction_input["instances"]
return np.asarray(instances)
def predict(self, instances: np.ndarray) -> np.ndarray:
return self._model.predict(instances)
def postprocess(self, prediction_results: np.ndarray) -> dict:
return {"predictions": prediction_results.tolist()}
@@ -0,0 +1,33 @@
import numpy as np
import os
import pickle
from google.cloud.aiplatform.constants import prediction
from google.cloud.aiplatform.utils import prediction_utils
from google.cloud.aiplatform.prediction.predictor import Predictor
from sklearn.linear_model import SGDClassifier
class SGDClassifierPredictor(Predictor):
def __init__(self):
return
def load(self, artifacts_uri: str) -> None:
prediction_utils.download_model_artifacts(artifacts_uri)
if os.path.exists(prediction.MODEL_FILENAME_PKL):
self._model = pickle.load(open(prediction.MODEL_FILENAME_PKL, "rb"))
else:
self._model = SGDClassifier(max_iter=5)
X = [[0., 0.], [1., 1.]]
y = [0, 1]
self._model.fit(X, y)
def preprocess(self, prediction_input: dict) -> np.ndarray:
instances = prediction_input["instances"]
return np.asarray(instances)
def predict(self, instances: np.ndarray) -> np.ndarray:
return self._model.predict(instances)
def postprocess(self, prediction_results: np.ndarray) -> dict:
return {"predictions": prediction_results.tolist()}
@@ -0,0 +1,34 @@
import os
import torch
from google.cloud.aiplatform.utils import prediction_utils
from google.cloud.aiplatform.prediction.predictor import Predictor
from torchvision.models import detection, resnet50, ResNet50_Weights
from typing import Dict, List
class ResNetPredictor(Predictor):
def __init__(self):
return
def load(self, artifacts_uri: str) -> None:
prediction_utils.download_model_artifacts(artifacts_uri)
if os.path.exists("model.pth.tar"):
self.model = detection.fasterrcnn_resnet50_fpn(pretrained=True)
stat_dic = torch.load("model.pth.tar")
self.model.load_state_dict(stat_dic['state_dict'])
else:
weights = ResNet50_Weights.DEFAULT
self.model = resnet50(weights=weights)
self.model.eval()
def preprocess(self, prediction_input: dict) -> torch.Tensor:
instances = prediction_input["instances"]
return torch.Tensor(instances)
@torch.inference_mode()
def predict(self, instances: torch.Tensor) -> List[str]:
return self._model(instances)
def postprocess(self, prediction_results: List[str]) -> Dict:
return {"predictions": prediction_results}
@@ -0,0 +1,37 @@
import os
import numpy as np
import pickle
import xgboost as xgb
from google.cloud.aiplatform.constants import prediction
from google.cloud.aiplatform.utils import prediction_utils
from google.cloud.aiplatform.prediction.predictor import Predictor
from sklearn.datasets import make_blobs
from xgboost import XGBClassifier
class ClassifierPredictor(Predictor):
def __init__(self):
return
def load(self, artifacts_uri: str) -> None:
prediction_utils.download_model_artifacts(artifacts_uri)
if os.path.exists(prediction.MODEL_FILENAME_PKL):
booster = pickle.load(open(prediction.MODEL_FILENAME_PKL, "rb"))
else:
X, y = make_blobs(n_samples=100, centers=2, n_features=2, random_state=1)
model = XGBClassifier()
model.fit(X, y)
booster = model.get_booster()
self._booster = booster
def preprocess(self, prediction_input: dict) -> xgb.DMatrix:
instances = prediction_input["instances"]
return xgb.DMatrix(instances)
def predict(self, instances: xgb.DMatrix) -> np.ndarray:
return self._booster.predict(instances)
def postprocess(self, prediction_results: np.ndarray) -> dict:
return {"predictions": prediction_results.tolist()}
@@ -0,0 +1,41 @@
import os
import numpy as np
import pandas as pd
import pickle
import xgboost as xgb
from google.cloud.aiplatform.constants import prediction
from google.cloud.aiplatform.utils import prediction_utils
from google.cloud.aiplatform.prediction.predictor import Predictor
class XGBRankerPredictor(Predictor):
def __init__(self):
return
def load(self, artifacts_uri: str) -> None:
prediction_utils.download_model_artifacts(artifacts_uri)
if os.path.exists(prediction.MODEL_FILENAME_PKL):
booster = pickle.load(open(prediction.MODEL_FILENAME_PKL, "rb"))
self._booster = booster
else:
N = 500
dates = pd.date_range(start='2023-01-01', end='2023-01-12', periods=N)
X = pd.DataFrame(np.random.randn(N, 5), columns=list('ABCDE'), index=dates)
y = pd.Series(np.random.randint(0, 10, size=N), index=dates, name='label')
group = X.groupby(dates + pd.offsets.MonthEnd(0)).size()
sample_weight = pd.Series(np.arange(len(group)), index=group.index)
model = xgb.XGBRanker(objective='rank:pairwise', max_depth=3, learning_rate=0.1, booster='gbtree', tree_method='hist', n_jobs=4, n_estimators=50, enable_categorical=False, random_state=42)
model.fit(X=X, y=y, group=group, sample_weight=sample_weight, verbose=True)
booster = model.get_booster()
self._booster = booster
def preprocess(self, prediction_input: dict) -> xgb.DMatrix:
instances = prediction_input["instances"]
return xgb.DMatrix(instances)
def predict(self, instances: xgb.DMatrix) -> np.ndarray:
return self._booster.predict(instances, output_margin=False, ntree_limit=0)
def postprocess(self, prediction_results: np.ndarray) -> dict:
return {"predictions": prediction_results.tolist()}
@@ -230,7 +230,7 @@ def download_image(url: str) -> str:
base64 encoded image.
"""
response = requests.get(url)
return Image.open(io.BytesIO(response.content))
return Image.open(io.BytesIO(response.content)) # pytype: disable=bad-return-type # pillow-102-upgrade
def resize_image(image: Any, new_width: int = 1000) -> Any:
@@ -422,6 +422,41 @@ def detect_predict(
return response.predictions[0].get("response")
def copy_model_artifacts(
model_id: str,
model_source: str,
model_destination: str,
) -> None:
"""Copies model artifacts from model_source to model_destination.
model_source and model_destination should be GCS path.
Args:
model_id: The model id.
model_source: The source of the model artifact.
model_destination: The destination of the model artifact.
"""
if not model_source.startswith(GCS_URI_PREFIX):
raise ValueError(
f"{model_source} is not a GCS path starting with {GCS_URI_PREFIX}."
)
if not model_destination.startswith(GCS_URI_PREFIX):
raise ValueError(
f"{model_destination} is not a GCS path starting with {GCS_URI_PREFIX}."
)
model_source = f"{model_source}/{model_id}"
model_destination = f"{model_destination}/{model_id}"
print("Copying model artifact from ", model_source, " to ", model_destination)
subprocess.check_output([
"gcloud",
"storage",
"cp",
"-r",
model_source,
model_destination,
])
def get_quota(project_id: str, region: str, resource_id: str) -> int:
"""Returns the quota for a resource in a region.
@@ -476,6 +511,7 @@ def get_resource_id(
accelerator_type: str,
is_for_training: bool,
is_restricted_image: bool = False,
is_dynamic_workload_scheduler: bool = False,
) -> str:
"""Returns the resource id for a given accelerator type and the use case.
@@ -484,45 +520,62 @@ def get_resource_id(
is_for_training: Whether the resource is used for training. Set false for
serving use case.
is_restricted_image: Whether the image is hosted in `vertex-ai-restricted`.
is_dynamic_workload_scheduler: Whether the resource is used with Dynamic
Workload Scheduler.
Returns:
The resource id.
"""
accelerator_suffix_map = {
"NVIDIA_TESLA_V100": "nvidia_v100_gpus",
"NVIDIA_TESLA_P100": "nvidia_p100_gpus",
"NVIDIA_L4": "nvidia_l4_gpus",
"NVIDIA_TESLA_A100": "nvidia_a100_gpus",
"NVIDIA_A100_80GB": "nvidia_a100_80gb_gpus",
"NVIDIA_H100_80GB": "nvidia_h100_gpus",
"NVIDIA_TESLA_T4": "nvidia_t4_gpus",
"TPU_V5e": "tpu_v5e",
"TPU_V3": "tpu_v3",
}
default_training_accelerator_map = {
"NVIDIA_TESLA_V100": "custom_model_training_nvidia_v100_gpus",
"NVIDIA_L4": "custom_model_training_nvidia_l4_gpus",
"NVIDIA_TESLA_A100": "custom_model_training_nvidia_a100_gpus",
"NVIDIA_A100_80GB": "custom_model_training_nvidia_a100_80gb_gpus",
"NVIDIA_H100_80GB": "custom_model_training_nvidia_h100_gpus",
"NVIDIA_TESLA_T4": "custom_model_training_nvidia_t4_gpus",
"TPU_V5e": "custom_model_training_tpu_v5e",
"TPU_V3": "custom_model_training_tpu_v3",
key: f"custom_model_training_{accelerator_suffix_map[key]}"
for key in accelerator_suffix_map
}
dws_training_accelerator_map = {
key: f"custom_model_training_preemptible_{accelerator_suffix_map[key]}"
for key in accelerator_suffix_map
}
restricted_image_training_accelerator_map = {
"NVIDIA_A100_80GB": "restricted_image_training_nvidia_a100_80gb_gpus",
}
serving_accelerator_map = {
"NVIDIA_TESLA_V100": "custom_model_serving_nvidia_v100_gpus",
"NVIDIA_L4": "custom_model_serving_nvidia_l4_gpus",
"NVIDIA_TESLA_A100": "custom_model_serving_nvidia_a100_gpus",
"NVIDIA_A100_80GB": "custom_model_serving_nvidia_a100_80gb_gpus",
"NVIDIA_H100_80GB": "custom_model_serving_nvidia_h100_gpus",
"NVIDIA_TESLA_T4": "custom_model_serving_nvidia_t4_gpus",
"TPU_V5e": "custom_model_serving_tpu_v5e",
key: f"custom_model_serving_{accelerator_suffix_map[key]}"
for key in accelerator_suffix_map
}
if is_for_training:
if is_restricted_image and is_dynamic_workload_scheduler:
raise ValueError(
"Dynamic Workload Scheduler does not work for restricted image"
" training."
)
training_accelerator_map = (
restricted_image_training_accelerator_map
if is_restricted_image
else default_training_accelerator_map
)
if accelerator_type in training_accelerator_map:
return training_accelerator_map[accelerator_type]
if is_dynamic_workload_scheduler:
return dws_training_accelerator_map[accelerator_type]
else:
return training_accelerator_map[accelerator_type]
else:
raise ValueError(
f"Could not find accelerator type: {accelerator_type} for training."
)
else:
if is_dynamic_workload_scheduler:
raise ValueError("Dynamic Workload Scheduler does not work for serving.")
if accelerator_type in serving_accelerator_map:
return serving_accelerator_map[accelerator_type]
else:
@@ -538,10 +591,14 @@ def check_quota(
accelerator_count: int,
is_for_training: bool,
is_restricted_image: bool = False,
is_dynamic_workload_scheduler: bool = False,
):
"""Checks if the project and the region has the required quota."""
resource_id = get_resource_id(
accelerator_type, is_for_training, is_restricted_image
accelerator_type,
is_for_training=is_for_training,
is_restricted_image=is_restricted_image,
is_dynamic_workload_scheduler=is_dynamic_workload_scheduler,
)
quota = get_quota(project_id, region, resource_id)
quota_request_instruction = (
@@ -562,3 +619,4 @@ def check_quota(
f"Quota not enough for {resource_id} in {region}: {quota} <"
f" {accelerator_count}. {quota_request_instruction}"
)
@@ -0,0 +1,544 @@
"""Functions for dataset validation.
This tool is used to validate the dataset against the given template.
"""
import json
import multiprocessing
import os
import subprocess
from typing import Any, Callable, Dict, Union
from absl import logging
import accelerate
import datasets
import transformers
GCS_URI_PREFIX = "gs://"
GCSFUSE_URI_PREFIX = "/gcs/"
LOCAL_BASE_MODEL_DIR = "/tmp/base_model_dir"
LOCAL_TEMPLATE_DIR = "/tmp/template_dir"
_TEMPLATE_DIRNAME = "templates"
_VERTEX_AI_SAMPLES_GITHUB_REPO_NAME = "vertex-ai-samples"
_VERTEX_AI_SAMPLES_GITHUB_TEMPLATE_DIR = (
"community-content/vertex_model_garden/model_oss/peft/train/vmg/templates"
)
_MODELS_REQUIRING_PAD_TOKEN = ("llama", "falcon", "mistral", "mixtral")
_MODELS_REQUIRING_EOS_TOEKN = ("gemma-2b", "gemma-7b")
_DESCRIPTION_KEY = "description"
_SOURCE_KEY = "source"
_PROMPT_INPUT_KEY = "prompt_input"
_PROMPT_NO_INPUT_KEY = "prompt_no_input"
_RESPONSE_SEPARATOR = "response_separator"
_INSTRUCTION_SEPARATOR = "instruction_separator"
_CHAT_TEMPLATE_KEY = "chat_template"
_KNOWN_KEYS = (
_DESCRIPTION_KEY,
_SOURCE_KEY,
_PROMPT_INPUT_KEY,
_PROMPT_NO_INPUT_KEY,
_RESPONSE_SEPARATOR,
_INSTRUCTION_SEPARATOR,
_CHAT_TEMPLATE_KEY,
)
def is_gcs_path(input_path: str) -> bool:
"""Checks if the input path is a Google Cloud Storage (GCS) path.
Args:
input_path: The input path to be checked.
Returns:
True if the input path is a GCS path, False otherwise.
"""
return input_path is not None and input_path.startswith(GCS_URI_PREFIX)
def force_gcs_fuse_path(gcs_uri: str) -> str:
"""Converts gs:// uris to their /gcs/ equivalents. No-op for other uris.
Args:
gcs_uri: The GCS URI to convert.
Returns:
The converted GCS URI.
"""
if is_gcs_path(gcs_uri):
return GCSFUSE_URI_PREFIX + gcs_uri[len(GCS_URI_PREFIX) :]
else:
return gcs_uri
def download_gcs_uri_to_local(
gcs_uri: str, destination_dir: str = LOCAL_BASE_MODEL_DIR
) -> str:
"""Downloads GCS URI to local.
If GCS URI is a directory, gs://some/folder is downloaded to
/destination_dir/folder. If GCS URI is a file, gs://some/file is downloaded to
/destination_dir/file.
Args:
gcs_uri: GCS URI to download.
destination_dir: Local directory directory.
Returns:
Local path to target folder/file.
"""
target = os.path.join(
destination_dir,
os.path.basename(os.path.normpath(gcs_uri)),
)
if os.path.exists(target):
logging.info("File %s already exists.", target)
return target
if accelerate.PartialState().is_local_main_process:
logging.info(
"Downloading file(s) from %s to %s...", gcs_uri, destination_dir
)
if not os.path.exists(destination_dir):
os.mkdir(destination_dir)
subprocess.check_output([
"gsutil",
"-m",
"cp",
"-r",
gcs_uri,
destination_dir,
])
logging.info("Downloaded file(s) from %s to %s.", gcs_uri, destination_dir)
# Make sure ALL processes process to next step after data downloading is done.
# It matters for the main process to wait for other processes as well.
accelerate.PartialState().wait_for_everyone()
return target
def get_template(template_path: str) -> Dict[str, str]:
"""Gets the template dictionary given the file path.
Args:
template_path: Path to the template file.
Returns:
A dictionary of the template.
Raises:
ValueError: If the template file does not exist or contains unknown keys.
"""
if is_gcs_path(template_path):
template_path = force_gcs_fuse_path(template_path)
elif not os.path.isfile(template_path):
template_path = os.path.join(
os.path.dirname(__file__),
_TEMPLATE_DIRNAME,
template_path + ".json",
)
if not os.path.isfile(template_path):
raise ValueError(f"Template file {template_path} does not exist.")
with open(template_path, "r") as f:
template_json: dict[str, str] = json.load(f)
for key in template_json:
if key not in _KNOWN_KEYS:
raise ValueError(f"Unknown key {key} in template {template_path}.")
return template_json
def get_response_separator(template_json: Dict[str, str]) -> Union[str, None]:
return template_json.get(_RESPONSE_SEPARATOR, None)
def get_instruction_separator(
template_json: Dict[str, str],
) -> Union[str, None]:
return template_json.get(_INSTRUCTION_SEPARATOR, None)
def _format_template_fn(
template: str,
input_column: str,
tokenizer: transformers.PreTrainedTokenizer | None = None,
) -> Callable[[Dict[str, str]], Dict[str, str]]:
"""Formats a dataset example according to a template.
Args:
template: Name of the JSON template file under `templates/` or GCS path to
the template file.
input_column: The input column in the dataset to be used or updated by the
template. If it does not exist, the template's `prompt_no_input` will be
used, and the input_column will be created.
tokenizer: The tokenizer to use for chat_template templates.
Returns:
A function that formats data according to the template.
"""
template_json = get_template(template)
if _CHAT_TEMPLATE_KEY not in template_json:
def format_fn(example: Dict[str, str]) -> Dict[str, str]:
format_dict = {key: value for key, value in example.items()}
if format_dict.get(input_column):
format_str = template_json[_PROMPT_INPUT_KEY]
elif _PROMPT_NO_INPUT_KEY in template_json:
format_str = template_json[_PROMPT_NO_INPUT_KEY]
else:
raise KeyError(
f"The template {os.path.basename(template)} does not contain"
f" {_PROMPT_INPUT_KEY} or {_PROMPT_NO_INPUT_KEY} key."
)
try:
return {input_column: format_str.format(**format_dict)}
except KeyError as e:
raise KeyError(
f"The template {os.path.basename(template)} contains a key {e} in"
f" {_PROMPT_INPUT_KEY} or {_PROMPT_NO_INPUT_KEY} that does not"
" exist in the dataset example. The dataset example looks like"
f" {format_dict}."
) from e
return format_fn
elif (
_PROMPT_INPUT_KEY in template_json
or _PROMPT_NO_INPUT_KEY in template_json
):
raise ValueError(
f"chat_template templates do not support {_PROMPT_INPUT_KEY} or"
f" {_PROMPT_NO_INPUT_KEY} templates."
)
else:
if tokenizer is None:
raise ValueError("A tokenizer is required for chat_template templates.")
# Assign HuggingFace jinja template.
tokenizer.chat_template = template_json[_CHAT_TEMPLATE_KEY]
def format_fn(example: Dict[str, str]) -> Dict[str, str]:
try:
return {
input_column: tokenizer.apply_chat_template(
example[input_column],
tokenize=False,
add_generation_prompt=False,
)
}
except KeyError as e:
raise KeyError(
f"The template {os.path.basename(template)} contains a key {e} in"
f" {_CHAT_TEMPLATE_KEY} that does not exist in the dataset example."
) from e
return format_fn
def _get_split_string(
split: str,
dataset_percent: int | None = None,
dataset_k_rows: int | None = None,
) -> str:
"""Gets the formatted split string for the dataset.
This is used to format the split string as per
https://huggingface.co/docs/datasets/v2.21.0/loading#slice-splits. Also, this
function will only be used to load the partial dataset for validating the
dataset against the template.
Args:
split: Split of the dataset.
dataset_percent: The percentage of the dataset to load.
dataset_k_rows: The top k sequences to load from the dataset.
Returns:
A formatted split string.
"""
# Validate the dataset_percent and dataset_k_rows values.
if dataset_percent and dataset_k_rows:
raise ValueError(
"You can set either validate_percentage_of_dataset or"
" validate_k_rows_of_dataset, but not both."
)
if dataset_percent:
logging.info("Loading %d percent of the dataset...", dataset_percent)
return f"{split}[:{dataset_percent}%]"
if dataset_k_rows:
logging.info("Loading top %d rows of the dataset...", dataset_k_rows)
return f"{split}[:{dataset_k_rows}]"
return split
def _github_template_path(template: str) -> str:
"""Generates the path to the template in the Vertex AI Samples GitHub repo.
Args:
template: Name of the template.
Returns:
The path to the template in the Vertex AI Samples GitHub repo.
"""
# vertex-ai-samples directory may lie under separate directory depending on
# the scratch_dir parameter in the notebook execution environment.
vertex_ai_samples_abs_path = os.getcwd().split(
_VERTEX_AI_SAMPLES_GITHUB_REPO_NAME
)[0]
return os.path.join(
vertex_ai_samples_abs_path,
_VERTEX_AI_SAMPLES_GITHUB_REPO_NAME,
_VERTEX_AI_SAMPLES_GITHUB_TEMPLATE_DIR,
template + ".json",
)
def _get_dataset(
dataset_name: str,
split: str,
num_proc: int | None = None,
) -> datasets.DatasetDict:
"""Gets a dataset.
Args:
dataset_name: Name of the dataset or path to a custom dataset.
split: Split of the dataset.
num_proc: Number of processors to use.
Returns:
A dataset.
"""
dataset_name = force_gcs_fuse_path(dataset_name)
if os.path.isfile(dataset_name):
# Custom dataset.
return datasets.load_dataset(
"json",
data_files=[dataset_name],
split=split,
num_proc=num_proc,
)
# HF dataset.
return datasets.load_dataset(dataset_name, split=split, num_proc=num_proc)
def should_add_pad_token(model_id: str) -> bool:
"""Returns whether the model requires adding a special pad token.
Args:
model_id: The name of the model.
Returns:
True if the model requires adding a special pad token, False otherwise.
"""
return any(s.lower() in model_id.lower() for s in _MODELS_REQUIRING_PAD_TOKEN)
def should_add_eos_token(model_id: str) -> bool:
"""Returns whether the model requires adding a special eos token.
Args:
model_id: The name of the model.
Returns:
True if the model requires adding a special eos token, False otherwise.
"""
return any(m in model_id for m in _MODELS_REQUIRING_EOS_TOEKN)
def load_tokenizer(
pretrained_model_id: str,
padding_side: str | None = None,
access_token: str | None = None,
) -> transformers.AutoTokenizer:
"""Loads tokenizer based on `pretrained_model_id`.
Args:
pretrained_model_id: The name of the pretrained model.
padding_side: The side to pad the input on.
access_token: The access token to use for the tokenizer.
Returns:
The tokenizer.
"""
tokenizer_kwargs = {}
if should_add_eos_token(pretrained_model_id):
tokenizer_kwargs["add_eos_token"] = True
if padding_side:
tokenizer_kwargs["padding_side"] = padding_side
with accelerate.PartialState().local_main_process_first():
tokenizer = transformers.AutoTokenizer.from_pretrained(
pretrained_model_id,
trust_remote_code=False,
use_fast=True,
token=access_token,
**tokenizer_kwargs,
)
if should_add_pad_token(pretrained_model_id):
tokenizer.add_special_tokens({"pad_token": "[PAD]"})
return tokenizer
def get_filtered_dataset(
dataset: Any,
input_column: str,
max_seq_length: int,
tokenizer: transformers.PreTrainedTokenizer,
) -> Any:
"""Returns the dataset by removing examples that are longer than max_seq_length.
Args:
dataset: The dataset to filter.
input_column: The input column in the dataset to be used.
max_seq_length: The maximum sequence length.
tokenizer: The tokenizer.
"""
actual_dataset_length = len(dataset)
filtered_dataset = dataset.filter(
lambda x: len(tokenizer(x[input_column])["input_ids"]) <= max_seq_length
)
filtered_dataset_length = len(filtered_dataset)
if actual_dataset_length != filtered_dataset_length:
examples_removed_percent = (
(actual_dataset_length - filtered_dataset_length)
* 100
/ actual_dataset_length
)
logging.info(
"(%.2f%%) of examples token length is <= max-seq-length(%d); (%.2f%%) >"
" max-seq-length. Filtering out %d example(s) which are longer than"
" max-seq-length.",
100 - examples_removed_percent,
max_seq_length,
examples_removed_percent,
actual_dataset_length - filtered_dataset_length,
)
return filtered_dataset
def load_dataset_with_template(
dataset_name: str,
split: str,
input_column: str,
template: str = None,
tokenizer: transformers.PreTrainedTokenizer | None = None,
) -> Any:
"""Loads dataset with templates.
Args:
dataset_name: Name of the dataset or path to a custom dataset.
split: Split of the dataset.
input_column: The input column in the dataset to be used or updaded by the
template. If it does not exist, the template's `prompt_no_input` will be
used, and the input_column will be created.
template: Name of the JSON template file under `templates/` or GCS path to
the template file.
tokenizer: The tokenizer to use for chat_template templates.
Returns:
A dataset compatible with the template.
"""
dataset = _get_dataset(dataset_name, split=split)
if template:
dataset = dataset.map(
_format_template_fn(
template,
input_column=input_column,
tokenizer=tokenizer,
)
)
return dataset
def validate_dataset_with_template(
dataset_name: str,
split: str,
input_column: str,
template: str,
tokenizer: transformers.PreTrainedTokenizer | None = None,
max_seq_length: int | None = None,
use_multiprocessing: bool = False,
validate_percentage_of_dataset: int | None = None,
validate_k_rows_of_dataset: int | None = None,
) -> Any:
"""Validates dataset with templates.
This function will be used to load the dataset and validate it against the
template. In case of validation, we also allow the users to load the dataset
partially by allowing them to read x% or top k rows of the dataset. To
validate the dataset, the template file must be available in the GCS bucket
and the dataset must be available either in the GCS bucket or Hugging Face.
Args:
dataset_name: Name of the dataset or path to a custom dataset.
split: Split of the dataset.
input_column: The input column in the dataset to be used or updaded by the
template. If it does not exist, the template's `prompt_no_input` will be
used, and the input_column will be created.
template: Name of the JSON template file under `templates/` or GCS path to
the template file.
tokenizer: The tokenizer to use for chat_template templates.
max_seq_length: The maximum sequence length.
use_multiprocessing: If True, it will use multiprocessing to load the
dataset.
validate_percentage_of_dataset: The percentage of the dataset to load.
validate_k_rows_of_dataset: The top k sequences to load from the dataset.
Returns:
None if the validation is successful, otherwise returns the error message.
"""
if not template:
raise ValueError("template is required for validate_dataset.")
if not dataset_name:
raise ValueError("dataset_name is empty.")
if not split:
raise ValueError("split is empty.")
split = _get_split_string(
split,
validate_percentage_of_dataset,
validate_k_rows_of_dataset,
)
num_proc = multiprocessing.cpu_count() if use_multiprocessing else 1
# gcsfuse cannot be used from the notebook runtime env. Hence, we have
# to download dataset and template from gcs to local.
if is_gcs_path(dataset_name):
dataset_name = download_gcs_uri_to_local(dataset_name, LOCAL_BASE_MODEL_DIR)
if is_gcs_path(template):
template_path = download_gcs_uri_to_local(template, LOCAL_TEMPLATE_DIR)
elif os.path.isfile(_github_template_path(template)):
template_path = _github_template_path(template)
else:
raise ValueError(
f"Template file {template} does not exist. To validate the"
" dataset, please provide a valid GCS path for the template or a valid"
" template name from"
f" https://github.com/GoogleCloudPlatform/{_VERTEX_AI_SAMPLES_GITHUB_REPO_NAME}/tree/main/{_VERTEX_AI_SAMPLES_GITHUB_TEMPLATE_DIR}."
)
dataset = _get_dataset(dataset_name, split, num_proc).map(
_format_template_fn(
template_path,
input_column=input_column,
tokenizer=tokenizer,
)
)
if tokenizer is not None:
get_filtered_dataset(
dataset=dataset,
input_column=input_column,
max_seq_length=max_seq_length,
tokenizer=tokenizer,
)
print(
"Dataset {} is compatible with the {} template.".format(
os.path.basename(dataset_name), os.path.basename(template)
)
)
@@ -1,144 +0,0 @@
"""Causal language modeling with LoRA models."""
# pylint: disable=g-importing-member
from datasets import load_dataset
from peft import get_peft_model
from peft import LoraConfig
import torch
from torch import nn
import transformers
from transformers import AutoModelForCausalLM
from transformers import AutoTokenizer
from transformers import BitsAndBytesConfig
from transformers import TrainingArguments
from typing import List
from util import constants
def finetune_causal_language_modeling(
pretrained_model_id: str,
dataset_name: str,
output_dir: str,
precision_mode: str = None,
lora_rank: int = 16,
lora_alpha: int = 32,
lora_dropout: float = 0.05,
target_modules: List[str] = constants.CAUSAL_LANGUAGE_MODELING_LORA_TARGET_MODULES,
warmup_steps: int = 10,
max_steps: int = 10,
learning_rate: float = 2e-4,
local_pretrained_model_id: str = None,
) -> None:
"""Finetunes causal language modelings."""
if precision_mode == constants.PRECISION_MODE_32:
model = AutoModelForCausalLM.from_pretrained(
local_pretrained_model_id
if local_pretrained_model_id
else pretrained_model_id,
torch_dtype=torch.float32,
device_map="auto",
)
elif precision_mode == constants.PRECISION_MODE_16:
model = AutoModelForCausalLM.from_pretrained(
local_pretrained_model_id
if local_pretrained_model_id
else pretrained_model_id,
torch_dtype=torch.bfloat16,
device_map="auto",
)
elif precision_mode == constants.PRECISION_MODE_8:
quantization_config = BitsAndBytesConfig(
load_in_8bit=True, int8_threshold=0
)
model = AutoModelForCausalLM.from_pretrained(
local_pretrained_model_id
if local_pretrained_model_id
else pretrained_model_id,
torch_dtype=torch.float16,
device_map="auto",
quantization_config=quantization_config,
)
else:
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
)
model = AutoModelForCausalLM.from_pretrained(
local_pretrained_model_id
if local_pretrained_model_id
else pretrained_model_id,
device_map="auto",
torch_dtype=torch.bfloat16,
quantization_config=quantization_config,
)
tokenizer = AutoTokenizer.from_pretrained(
local_pretrained_model_id
if local_pretrained_model_id
else pretrained_model_id
)
if "llama" in pretrained_model_id:
tokenizer.pad_token = "[PAD]"
for param in model.parameters():
# Freezes the model - train adapters later.
param.requires_grad = False
if param.ndim == 1:
# Casts the small parameters (e.g. layernorm) to fp32 for stability.
param.data = param.data.to(torch.float32)
# Reduces the number of stored activations.
model.gradient_checkpointing_enable()
model.enable_input_require_grads()
class CastOutputToFloat(nn.Sequential):
def forward(self, x):
return super().forward(x).to(torch.float32)
model.lm_head = CastOutputToFloat(model.lm_head)
config = LoraConfig(
r=lora_rank,
lora_alpha=lora_alpha,
target_modules=target_modules,
lora_dropout=lora_dropout,
bias="none",
task_type="CAUSAL_LM",
)
model = get_peft_model(model, config)
model.print_trainable_parameters()
data = load_dataset(dataset_name)
data = data.map(
lambda samples: tokenizer(samples["quote"]),
batched=True,
)
trainer = transformers.Trainer(
model=model,
train_dataset=data["train"],
args=TrainingArguments(
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
warmup_steps=warmup_steps,
max_steps=max_steps,
learning_rate=learning_rate,
fp16=True,
logging_steps=1,
output_dir=output_dir,
ddp_find_unused_parameters=False,
),
data_collator=transformers.DataCollatorForLanguageModeling(
tokenizer,
mlm=False,
),
)
# Silence the warnings. Please re-enable for inference!
model.config.use_cache = False
trainer.train()
model.save_pretrained(output_dir)
@@ -0,0 +1,28 @@
# Base on pytorch-cuda image.
FROM pytorch/pytorch:2.0.0-cuda11.7-cudnn8-devel
# Install tools.
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get install -y --no-install-recommends apt-utils
RUN apt-get install -y --no-install-recommends curl
RUN apt-get install -y --no-install-recommends wget
RUN apt-get install -y --no-install-recommends git
# Install libraries.
ENV PIP_ROOT_USER_ACTION=ignore
RUN python3 -m pip install --upgrade pip
RUN pip install tokenizers==0.13.3
RUN pip install accelerate==0.21.0
RUN pip install sentencepiece==0.1.99
RUN pip install datasets==2.14.4
RUN pip install protobuf==4.24.1
# Install transformers
RUN git clone https://github.com/huggingface/transformers.git
WORKDIR transformers
# Pin the commit to add-code-llama 08/25/2023
RUN git reset --hard 015f8e110d270a0ad42de4ae5b98198d69eb1964
RUN pip install -e .
ENTRYPOINT ["python","src/transformers/models/llama/convert_llama_weights_to_hf.py"]
@@ -0,0 +1,22 @@
# Dockerfile for Language Model Conversion.
#
# To build:
# docker build -f model_oss/peft/dockerfile/conversion.Dockerfile . -t ${YOUR_IMAGE_TAG}
#
# To push to gcr:
# docker tag ${YOUR_IMAGE_TAG} gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
# docker push gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
FROM tensorflow/build:2.14-python3.8
RUN git clone https://github.com/facebookresearch/llama-recipes.git && \
cd llama-recipes && \
pip install -r requirements.txt && \
pip freeze | grep transformers && \
git clone https://github.com/huggingface/transformers.git && \
cd transformers && \
pip install protobuf
WORKDIR /llama-recipes/transformers
ENTRYPOINT ["python","src/transformers/models/llama/convert_llama_weights_to_hf.py"]
@@ -7,39 +7,40 @@
# docker tag ${YOUR_IMAGE_TAG} gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
# docker push gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
FROM pytorch/torchserve:0.7.0-gpu
FROM pytorch/torchserve:0.11.0-gpu
USER root
ENV infer_port=7080
ENV mng_port=7081
ENV model_name="peft_serving"
ENV INFER_PORT=7080
ENV MNG_PORT=7081
ENV MODEL="peft_serving"
ENV PATH="/home/model-server/:${PATH}"
RUN apt-get update && apt-get install -y --no-install-recommends \
RUN apt-get update && apt-get -y upgrade && apt-get install -y --no-install-recommends \
curl \
wget \
vim \
git \
git-lfs
RUN git lfs install
RUN apt-get autoremove -y
# Install libraries.
ENV PIP_ROOT_USER_ACTION=ignore
RUN python3 -m pip install --upgrade pip
RUN pip install --upgrade torch==2.0.1
RUN pip install --upgrade torch==2.0.1 --index-url https://download.pytorch.org/whl/cu118
RUN pip install torchvision==0.15.2
RUN pip install tokenizers==0.13.3
RUN pip install accelerate==0.21.0
RUN pip install sentencepiece==0.1.99
RUN pip install grpcio-status==1.33.2
RUN pip install protobuf==3.19.6
RUN python3 -m pip install --no-cache-dir git+https://github.com/huggingface/peft.git
RUN pip install peft==0.5.0
RUN pip install datasets==2.14.4
RUN pip install triton==2.0.0.dev20221120
RUN pip install triton==3.0.0
RUN pip install xformers==0.0.20
RUN pip install google-cloud-storage==2.7.0
RUN pip install absl-py==1.4.0
RUN pip install google-cloud-storage
RUN pip install absl-py
RUN pip install scipy==1.10.1
RUN pip install evaluate==0.4.0
RUN pip install scikit-learn==1.2.2
@@ -47,52 +48,43 @@ RUN pip install loralib==0.1.1
RUN pip install bitsandbytes==0.39.0
RUN pip install trl==0.4.4
RUN pip install einops==0.6.1
# Install diffusers from source.
RUN git clone --depth 1 --branch v0.16.1 https://github.com/huggingface/diffusers.git
WORKDIR diffusers
RUN pip install -e .
WORKDIR /home/model-server
# Install transformers from source.
RUN git clone --depth 1 --branch v4.31.0 https://github.com/huggingface/transformers.git
# The patch is used to change the transformers loading model behavior:
# 1) For models on Huggingface hub: if the model has multiple shards, each shard
# will be downloaded separately and get deleted after loading to GPU.
# 2) For models on local disk: if a model bin file is actually a text file
# recording a GCS path, the model file will be downloaded and get deleted
# after loading to GPU.
COPY model_oss/peft/hf_transformers_lazy_download.patch /home/model-server/hf_transformers_lazy_download.patch
WORKDIR transformers
RUN git apply /home/model-server/hf_transformers_lazy_download.patch
RUN pip install -e .
WORKDIR /home/model-server
RUN pip install optimum==1.13.2
RUN pip install auto-gptq==0.4.2
RUN pip install https://github.com/casper-hansen/AutoAWQ/releases/download/v0.1.7/autoawq-0.1.7+cu118-cp39-cp39-linux_x86_64.whl
RUN pip install diffusers==0.27.2
RUN pip install tiktoken==0.6.0
RUn pip install git+https://github.com/huggingface/transformers.git@76fa17c1663a0efeca7208c20579833365584889
RUN pip install pynvml==11.4.0
RUN pip install -i https://test.pypi.org/simple/ bitsandbytes
# Copy license.
WORKDIR /home/model-server
RUN wget https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/LICENSE
# Copy model artifacts.
COPY model_oss/peft/handler.py /home/model-server/handler.py
COPY model_oss/peft/config.properties /home/model-server/config.properties
COPY model_oss/util/ /home/model-server/util/
COPY model_oss/util/pytorch_startup_prober.sh /model_garden/scripts/pytorch_startup_prober.sh
ENV PYTHONPATH /home/model-server/
# Expose ports.
EXPOSE ${infer_port}
EXPOSE ${mng_port}
EXPOSE ${INFER_PORT}
EXPOSE ${MNG_PORT}
# Set environments.
ENV TASK "causal-language-modeling-lora"
ENV MODEL_ID "openlm-research/open_llama_7b"
ENV BASE_MODEL_ID ""
ENV MODEL_ID ""
ENV PRECISION_LOADING_MODE "float16"
ENV FINETUNED_LORA_MODEL_PATH ""
ENV TRUST_REMOTE_CODE ""
# Archive model artifacts and dependencies.
# Do not set --model-file and --serialized-file because model and checkpoint
# will be dynamically loaded in handler.py.
RUN torch-model-archiver \
--model-name=${model_name} \
--model-name=${MODEL} \
--version=1.0 \
--handler=/home/model-server/handler.py \
--runtime=python3 \
@@ -103,5 +95,5 @@ RUN torch-model-archiver \
# Run Torchserve HTTP serve to respond to prediction requests.
CMD ["torchserve", "--start", \
"--ts-config", "/home/model-server/config.properties", \
"--models", "${model_name}=${model_name}.mar", \
"--models", "${MODEL}=${MODEL}.mar", \
"--model-store", "/home/model-server/model-store"]
@@ -1,111 +0,0 @@
# Dockerfile for PEFT Training.
#
# To build:
# docker build -f model_oss/peft/dockerfile/train.Dockerfile . -t ${YOUR_IMAGE_TAG}
#
# To push to gcr:
# docker tag ${YOUR_IMAGE_TAG} gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
# docker push gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
# Builds GPU docker image of PyTorch
# Uses multi-staged approach to reduce size
# Stage 1
# Use base conda image to reduce time
FROM continuumio/miniconda3:latest AS compile-image
# Specify py version
ENV PYTHON_VERSION=3.8
# Install apt libs - copied from https://github.com/huggingface/accelerate/blob/main/docker/accelerate-gpu/Dockerfile
RUN apt-get update && \
apt-get install -y curl git wget software-properties-common git-lfs && \
apt-get clean && \
rm -rf /var/lib/apt/lists*
# Install audio-related libraries
RUN apt-get update && \
apt install -y ffmpeg
RUN apt install -y libsndfile1-dev
RUN git lfs install
# Create our conda env - copied from https://github.com/huggingface/accelerate/blob/main/docker/accelerate-gpu/Dockerfile
RUN conda create --name peft python=${PYTHON_VERSION} ipython jupyter pip
RUN python3 -m pip install --no-cache-dir --upgrade pip
# Below is copied from https://github.com/huggingface/accelerate/blob/main/docker/accelerate-gpu/Dockerfile
# We don't install pytorch here yet since CUDA isn't available
# instead we use the direct torch wheel
ENV PATH /opt/conda/envs/peft/bin:$PATH
# Activate our bash shell
RUN chsh -s /bin/bash
SHELL ["/bin/bash", "-c"]
# Activate the conda env and install transformers + accelerate from source
RUN source activate peft
RUN python3 -m pip install --no-cache-dir git+https://github.com/huggingface/transformers
RUN python3 -m pip install --no-cache-dir git+https://github.com/huggingface/accelerate
RUN python3 -m pip install --no-cache-dir git+https://github.com/huggingface/peft#egg=peft[test]
RUN python3 -m pip install --no-cache-dir bitsandbytes
# Stage 2
FROM nvidia/cuda:11.2.2-cudnn8-devel-ubuntu20.04 AS build-image
COPY --from=compile-image /opt/conda /opt/conda
ENV PATH /opt/conda/bin:$PATH
# Install apt libs
RUN apt-get update && \
apt-get install -y curl git wget vim && \
apt-get clean && \
rm -rf /var/lib/apt/lists*
# Copy license.
RUN wget https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/LICENSE
RUN echo "source activate peft" >> ~/.profile
# Install libraries.
RUN pip install --upgrade torch==2.0.1
RUN pip install torchvision==0.15.2
RUN pip install git+https://github.com/huggingface/transformers@de9255de27abfcae4a1f816b904915f0b1e23cd9
RUN pip install transformers -U
RUN pip install accelerate==0.21.0
RUN pip install sentencepiece==0.1.99
RUN pip install grpcio-status==1.33.2
RUN pip install protobuf==3.19.6
RUN python3 -m pip install --no-cache-dir git+https://github.com/huggingface/peft.git
RUN pip install datasets==2.9.0
RUN pip install triton==2.0.0.dev20221120
RUN pip install xformers==0.0.20
RUN pip install Jinja2==3.1.2
RUN pip install ftfy==6.1.1
RUN pip install cloudml-hypertune==0.1.0.dev6
RUN pip install tensorboard==2.12.0
RUN pip install scipy==1.10.1
RUN pip install evaluate==0.4.0
RUN pip install scikit-learn==1.2.2
RUN pip install loralib==0.1.1
RUN pip install bitsandbytes==0.39.0
RUN pip install trl==0.4.4
RUN pip install einops==0.6.1
RUN pip install google-cloud-storage==2.7.0
RUN git clone --depth 1 --branch v0.16.1 https://github.com/huggingface/diffusers.git
WORKDIR diffusers
RUN pip install -e .
# Switch to diffusers examples folder.
WORKDIR examples
# NOTE: use 'sed' to modify train_text_to_image_lora.py to
# fix the bug for accelerator.
RUN sed -i \
"s#logging_dir=logging_dir#project_dir=logging_dir#g" \
text_to_image/train_text_to_image_lora.py
# Config accelerate.
RUN mkdir -p ./vertex_vision_model_garden_peft/
COPY model_oss/peft/train.sh ./vertex_vision_model_garden_peft/train.sh
COPY model_oss/peft/*.py ./vertex_vision_model_garden_peft/
COPY model_oss/util /diffusers/examples/util
ENV PYTHONPATH /diffusers/examples/
# Generate accelerate config at the beginning of docker run.
ENTRYPOINT ["python3", "vertex_vision_model_garden_peft/main.py"]
@@ -72,15 +72,39 @@ class PeftHandler(BaseHandler):
"PRECISION_LOADING_MODE", constants.PRECISION_MODE_16
)
self.task = os.environ.get("TASK", CAUSAL_LANGUAGE_MODELING_LORA)
self.base_model_id = os.environ.get("BASE_MODEL_ID", None)
self.model_id = self.base_model_id
if not self.base_model_id:
self.model_id = os.environ.get("MODEL_ID", "")
trust_remote_code = os.environ.get("TRUST_REMOTE_CODE", None)
if trust_remote_code == "false":
self.trust_remote_code = False
else:
self.trust_remote_code = True
# If present, the path of the model in the container.
aip_storage_dir = os.environ.get("AIP_STORAGE_DIR", None)
# If present, the URI of the model in a google owned GCS bucket.
aip_storage_uri = os.environ.get("AIP_STORAGE_URI", None)
model_id = os.environ.get("MODEL_ID", None)
base_model_id = os.environ.get("BASE_MODEL_ID", None)
self.model_id = None
if aip_storage_dir:
self.model_id = aip_storage_dir
logging.info(f"Loaded base model from AIP_STORAGE_DIR: {self.model_id}.")
elif aip_storage_uri:
self.model_id = aip_storage_uri
logging.info(f"Loaded base model from AIP_STORAGE_URI: {self.model_id}.")
elif model_id:
self.model_id = model_id
logging.info(f"Loaded base model from MODEL_ID: {self.model_id}.")
elif base_model_id:
# Note: BASE_MODEL_ID has been unified with MODEL_ID.
# MODEL_ID should be used whenever possible.
self.model_id = base_model_id
logging.info(f"Loaded base model from BASE_MODEL_ID: {self.model_id}.")
self.quantization = os.environ.get("QUANTIZATION", None)
logging.info(f"Load base model id from MODEL_ID:{self.model_id}.")
if not self.model_id:
self.model_id = os.environ.get("AIP_STORAGE_URI", "")
logging.info(f"Load base model id from AIP_STORAGE_URI: {self.model_id}.")
if not self.model_id:
raise ValueError("Base model id is must be set.")
if fileutils.is_gcs_path(self.model_id):
@@ -101,8 +125,7 @@ class PeftHandler(BaseHandler):
logging.info(
f"Using task:{self.task}, base model:{self.model_id}, lora model:"
f" {self.finetuned_lora_model_path}, and precision"
f" {self.precision_mode}."
f" {self.finetuned_lora_model_path}, precision {self.precision_mode}."
)
self.pipeline = None
@@ -145,11 +168,18 @@ class PeftHandler(BaseHandler):
elif (
self.task == CAUSAL_LANGUAGE_MODELING_LORA or self.task == INSTRUCT_LORA
):
tokenizer = AutoTokenizer.from_pretrained(self.model_id)
tokenizer = AutoTokenizer.from_pretrained(
self.model_id,
trust_remote_code=self.trust_remote_code,
)
logging.debug("Initialized the tokenizer.")
if self.task == CAUSAL_LANGUAGE_MODELING_LORA:
if self.quantization == constants.AWQ:
model = AutoAWQForCausalLM.from_quantized(self.model_id)
model = AutoAWQForCausalLM.from_quantized(
self.model_id,
trust_remote_code=self.trust_remote_code,
)
elif self.quantization == constants.GPTQ or not self.quantization:
if self.precision_mode == constants.PRECISION_MODE_32:
model = AutoModelForCausalLM.from_pretrained(
@@ -157,6 +187,7 @@ class PeftHandler(BaseHandler):
return_dict=True,
torch_dtype=torch.float32,
device_map="auto",
trust_remote_code=self.trust_remote_code,
)
elif self.precision_mode == constants.PRECISION_MODE_16B:
model = AutoModelForCausalLM.from_pretrained(
@@ -164,6 +195,7 @@ class PeftHandler(BaseHandler):
return_dict=True,
torch_dtype=torch.bfloat16,
device_map="auto",
trust_remote_code=self.trust_remote_code,
)
elif self.precision_mode == constants.PRECISION_MODE_16:
model = AutoModelForCausalLM.from_pretrained(
@@ -171,6 +203,7 @@ class PeftHandler(BaseHandler):
return_dict=True,
torch_dtype=torch.float16,
device_map="auto",
trust_remote_code=self.trust_remote_code,
)
elif self.precision_mode == constants.PRECISION_MODE_8:
quantization_config = BitsAndBytesConfig(
@@ -182,6 +215,7 @@ class PeftHandler(BaseHandler):
torch_dtype=torch.float16,
device_map="auto",
quantization_config=quantization_config,
trust_remote_code=self.trust_remote_code,
)
else:
quantization_config = BitsAndBytesConfig(
@@ -195,6 +229,7 @@ class PeftHandler(BaseHandler):
device_map="auto",
torch_dtype=torch.bfloat16,
quantization_config=quantization_config,
trust_remote_code=self.trust_remote_code,
)
else:
raise ValueError(f"Invalid QUANTIZATION value: {self.quantization}")
@@ -203,14 +238,14 @@ class PeftHandler(BaseHandler):
model = AutoModelForCausalLM.from_pretrained(
self.model_id,
torch_dtype=torch.bfloat16,
trust_remote_code=True,
trust_remote_code=self.trust_remote_code,
device_map="auto",
)
except: # pylint: disable=bare-except
model = AutoModelForCausalLM.from_pretrained(
self.model_id,
torch_dtype=torch.bfloat16,
trust_remote_code=True,
trust_remote_code=self.trust_remote_code,
device_map="auto",
)
logging.debug("Initialized the base model.")
@@ -329,4 +364,4 @@ class PeftHandler(BaseHandler):
return f"Prompt:\n{prompt.strip()}\nOutput:\n{output}"
# pylint: enable=logging-fstring-interpolation
# pylint: enable=logging-fstring-interpolation
@@ -1,131 +0,0 @@
diff --git a/src/transformers/modeling_utils.py b/src/transformers/modeling_utils.py
index 45459ed..32527f4 100644
--- a/src/transformers/modeling_utils.py
+++ b/src/transformers/modeling_utils.py
@@ -32,6 +32,8 @@ import torch
from packaging import version
from torch import Tensor, nn
from torch.nn import CrossEntropyLoss
+from huggingface_hub import hf_hub_download
+from google.cloud import storage
from .activations import get_activation
from .configuration_utils import PretrainedConfig
@@ -442,6 +444,29 @@ def load_state_dict(checkpoint_file: Union[str, os.PathLike]):
"""
Reads a PyTorch checkpoint file, returning properly formatted errors if they arise.
"""
+ delete_download = False
+ tmp_dir = "/tmp/model"
+ os.makedirs(tmp_dir, exist_ok=True)
+ if isinstance(checkpoint_file, dict):
+ # Download model file from huggingface
+ print(f"==> Download model from HF: {checkpoint_file}")
+ checkpoint_file = hf_hub_download(
+ local_dir=tmp_dir, local_dir_use_symlinks=False, force_download=True, resume_download=True, **checkpoint_file)
+ delete_download = True
+ else:
+ with open(checkpoint_file, "rb") as f:
+ is_gcs_file = (f.read(2) == b"gs")
+ if is_gcs_file:
+ # Download model file from GCS
+ with open(checkpoint_file, "r") as f:
+ gcs_file = f.read()
+ checkpoint_file = os.path.join(tmp_dir, gcs_file.split("/")[-1])
+ print(f"==> Download model from GCS: {gcs_file} to: {checkpoint_file}")
+ client = storage.Client()
+ with open(checkpoint_file, 'wb') as f:
+ client.download_blob_to_file(gcs_file, f)
+ delete_download = True
+
if checkpoint_file.endswith(".safetensors") and is_safetensors_available():
# Check format of the archive
with safe_open(checkpoint_file, framework="pt") as f:
@@ -455,9 +480,9 @@ def load_state_dict(checkpoint_file: Union[str, os.PathLike]):
raise NotImplementedError(
f"Conversion from a {metadata['format']} safetensors archive to PyTorch is not implemented yet."
)
- return safe_load_file(checkpoint_file)
+ state_dict = safe_load_file(checkpoint_file)
try:
- return torch.load(checkpoint_file, map_location="cpu")
+ state_dict = torch.load(checkpoint_file, map_location="cpu")
except Exception as e:
try:
with open(checkpoint_file) as f:
@@ -478,6 +503,10 @@ def load_state_dict(checkpoint_file: Union[str, os.PathLike]):
f"at '{checkpoint_file}'. "
"If you tried to load a PyTorch model from a TF 2.0 checkpoint, please set from_tf=True."
)
+ if delete_download:
+ print(f"==> Delete downloaded model: {checkpoint_file}")
+ os.remove(checkpoint_file)
+ return state_dict
def set_initialized_submodules(model, state_dict_keys):
@@ -3179,7 +3208,10 @@ class PreTrainedModel(nn.Module, ModuleUtilsMixin, GenerationMixin, PushToHubMix
return mismatched_keys
if resolved_archive_file is not None:
- folder = os.path.sep.join(resolved_archive_file[0].split(os.path.sep)[:-1])
+ if isinstance(resolved_archive_file, str):
+ folder = os.path.sep.join(resolved_archive_file[0].split(os.path.sep)[:-1])
+ else:
+ folder = None
else:
folder = None
if device_map is not None and is_safetensors:
diff --git a/src/transformers/utils/hub.py b/src/transformers/utils/hub.py
index ffed743..4b15770 100644
--- a/src/transformers/utils/hub.py
+++ b/src/transformers/utils/hub.py
@@ -414,20 +414,34 @@ def cached_file(
user_agent = http_user_agent(user_agent)
try:
# Load from URL or cache if already cached
- resolved_file = hf_hub_download(
- path_or_repo_id,
- filename,
- subfolder=None if len(subfolder) == 0 else subfolder,
- repo_type=repo_type,
- revision=revision,
- cache_dir=cache_dir,
- user_agent=user_agent,
- force_download=force_download,
- proxies=proxies,
- resume_download=resume_download,
- use_auth_token=use_auth_token,
- local_files_only=local_files_only,
- )
+ if filename.endswith(".bin"):
+ # NOTE: To save disk we do not download bin file eagerly. Do not support safetensors.
+ resolved_file = dict(
+ repo_id=path_or_repo_id,
+ filename=filename,
+ subfolder=None if len(subfolder) == 0 else subfolder,
+ repo_type=repo_type,
+ revision=revision,
+ user_agent=user_agent,
+ proxies=proxies,
+ use_auth_token=use_auth_token,
+ )
+ print(f"--> Apply lazy download to bin file: {resolved_file}")
+ else:
+ resolved_file = hf_hub_download(
+ path_or_repo_id,
+ filename,
+ subfolder=None if len(subfolder) == 0 else subfolder,
+ repo_type=repo_type,
+ revision=revision,
+ cache_dir=cache_dir,
+ user_agent=user_agent,
+ force_download=force_download,
+ proxies=proxies,
+ resume_download=resume_download,
+ use_auth_token=use_auth_token,
+ local_files_only=local_files_only,
+ )
except RepositoryNotFoundError:
raise EnvironmentError(
@@ -1,95 +0,0 @@
"""Instruct/Chat with LoRA models."""
# pylint: disable=g-importing-member
from datasets import load_dataset
from peft import LoraConfig
import torch
from transformers import AutoModelForCausalLM
from transformers import AutoTokenizer
from transformers import BitsAndBytesConfig
from transformers import TrainingArguments
from trl import SFTTrainer
from typing import List
from util import constants
def finetune_instruct(
pretrained_model_id: str,
dataset_name: str,
output_dir: str,
lora_rank: int = 64,
lora_alpha: int = 16,
lora_dropout: float = 0.1,
target_modules: List[str] = constants.INSTRUCT_LORA_TARGET_MODULES,
warmup_ratio: int = 0.03,
max_steps: int = 10,
max_seq_length: int = 512,
learning_rate: float = 2e-4,
) -> None:
"""Finetunes instruct."""
dataset = load_dataset(dataset_name, split="train")
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
)
model = AutoModelForCausalLM.from_pretrained(
pretrained_model_id,
quantization_config=bnb_config,
trust_remote_code=True,
)
model.config.use_cache = False
tokenizer = AutoTokenizer.from_pretrained(
pretrained_model_id, trust_remote_code=True
)
tokenizer.pad_token = tokenizer.eos_token
peft_config = LoraConfig(
lora_alpha=lora_alpha,
lora_dropout=lora_dropout,
r=lora_rank,
bias="none",
task_type="CAUSAL_LM",
target_modules=target_modules,
)
per_device_train_batch_size = 4
gradient_accumulation_steps = 4
optim = "paged_adamw_32bit"
save_steps = 10
logging_steps = 10
max_grad_norm = 0.3
lr_scheduler_type = "constant"
training_arguments = TrainingArguments(
output_dir=output_dir,
per_device_train_batch_size=per_device_train_batch_size,
gradient_accumulation_steps=gradient_accumulation_steps,
optim=optim,
save_steps=save_steps,
logging_steps=logging_steps,
learning_rate=learning_rate,
fp16=True,
max_grad_norm=max_grad_norm,
max_steps=max_steps,
warmup_ratio=warmup_ratio,
group_by_length=True,
lr_scheduler_type=lr_scheduler_type,
)
trainer = SFTTrainer(
model=model,
train_dataset=dataset,
peft_config=peft_config,
dataset_text_field="text",
max_seq_length=max_seq_length,
tokenizer=tokenizer,
args=training_arguments,
)
for name, module in trainer.model.named_modules():
if "norm" in name:
module = module.to(torch.float32)
trainer.train()
@@ -1,185 +0,0 @@
"""Main function to start PEFT finetuning."""
import subprocess
from absl import app
from absl import flags
from absl import logging
from peft import causal_language_modeling_lora
from peft import instruct_lora
from peft import sequence_classification_lora
from util import constants
from util import fileutils
_TASK = flags.DEFINE_string(
'task',
constants.CAUSAL_LANGUAGE_MODELING_LORA,
'The supported PEFT tasks.',
)
_PRETRAINED_MODEL_ID = flags.DEFINE_string(
'pretrained_model_id',
None,
'The pretrained model id. Supported models can be causal language modeling'
' models from https://github.com/huggingface/peft/tree/main.',
required=True,
)
_DATASET_NAME = flags.DEFINE_string(
'dataset_name',
None,
'The dataset name in huggingface.',
required=True,
)
_OUTPUT_DIR = flags.DEFINE_string(
'output_dir',
None,
'The output directory.',
required=True,
)
_PRECISION_MODE = flags.DEFINE_string(
'precision_mode',
constants.PRECISION_MODE_16,
'Supported finetuning precision_modes are `{}` and `{}`.'.format(
constants.PRECISION_MODE_8, constants.PRECISION_MODE_16
),
)
_LORA_RANK = flags.DEFINE_integer(
'lora_rank',
16,
'The rank of the update matrices, expressed in int. Lower rank results in'
' smaller update matrices with fewer trainable parameters, referring to'
' https://huggingface.co/docs/peft/conceptual_guides/lora.',
)
_LORA_ALPHA = flags.DEFINE_integer(
'lora_alpha',
32,
'LoRA scaling factor, referring to'
' https://huggingface.co/docs/peft/conceptual_guides/lora.',
)
_LORA_DROPOUT = flags.DEFINE_float(
'lora_dropout',
0.05,
'dropout probability of the LoRA layers, referring to'
' https://huggingface.co/docs/peft/task_guides/token-classification-lora.',
)
_TARGET_MODULES = flags.DEFINE_list(
'target_modules',
constants.CAUSAL_LANGUAGE_MODELING_LORA_TARGET_MODULES,
'The comma separated list of target modules for LoRa training.',
)
_WARMUP_STEPS = flags.DEFINE_integer(
'warmup_steps',
10,
'Number of steps for the warmup in the learning rate scheduler.',
)
_WARMUP_RATIO = flags.DEFINE_float(
'warmup_ratio',
0.03,
'The warmup ratio in the learning rate scheduler.',
)
_MAX_STEPS = flags.DEFINE_integer(
'max_steps',
10,
'Total number of training steps.',
)
_MAX_SEQ_LENGTH = flags.DEFINE_integer(
'max_seq_length',
512,
'The maximum sequence length.',
)
_NUM_EPOCHS = flags.DEFINE_integer(
'num_epochs',
20,
'The number of training epochs.',
)
_BATCH_SIZE = flags.DEFINE_integer(
'batch_size',
32,
'The batch size.',
)
_LEARNING_RATE = flags.DEFINE_float(
'learning_rate',
2e-4,
'The learning rate after the potential warmup period.',
)
def main(_) -> None:
task = _TASK.value
pretrained_model_id = _PRETRAINED_MODEL_ID.value
local_pretrained_model_id = None
if pretrained_model_id.startswith(constants.GCS_URI_PREFIX):
logging.info(
'Start to copy pretrained models locally: %s.', pretrained_model_id
)
fileutils.download_gcs_dir_to_local(
pretrained_model_id, constants.LOCAL_BASE_MODEL_DIR
)
local_pretrained_model_id = constants.LOCAL_BASE_MODEL_DIR
logging.info(
'Finished copying pretrained models locally to: %s.',
local_pretrained_model_id,
)
if task == constants.TEXT_TO_IMAGE_LORA:
subprocess.run(['/bin/bash', 'train.sh'], check=True)
elif task == constants.SEQUENCE_CLASSIFICATION_LORA:
sequence_classification_lora.finetune_sequence_classification(
pretrained_model_id=pretrained_model_id,
dataset_name=_DATASET_NAME.value,
output_dir=_OUTPUT_DIR.value,
lora_rank=_LORA_RANK.value,
lora_alpha=_LORA_ALPHA.value,
lora_dropout=_LORA_DROPOUT.value,
num_epochs=_NUM_EPOCHS.value,
batch_size=_BATCH_SIZE.value,
learning_rate=_LEARNING_RATE.value,
)
elif task == constants.CAUSAL_LANGUAGE_MODELING_LORA:
causal_language_modeling_lora.finetune_causal_language_modeling(
pretrained_model_id=pretrained_model_id,
dataset_name=_DATASET_NAME.value,
output_dir=_OUTPUT_DIR.value,
precision_mode=_PRECISION_MODE.value,
lora_rank=_LORA_RANK.value,
lora_alpha=_LORA_ALPHA.value,
lora_dropout=_LORA_DROPOUT.value,
target_modules=_TARGET_MODULES.value,
warmup_steps=_WARMUP_STEPS.value,
max_steps=_MAX_STEPS.value,
learning_rate=_LEARNING_RATE.value,
local_pretrained_model_id=local_pretrained_model_id,
)
elif task == constants.INSTRUCT_LORA:
instruct_lora.finetune_instruct(
pretrained_model_id=pretrained_model_id,
dataset_name=_DATASET_NAME.value,
output_dir=_OUTPUT_DIR.value,
lora_rank=_LORA_RANK.value,
lora_alpha=_LORA_ALPHA.value,
lora_dropout=_LORA_DROPOUT.value,
target_modules=_TARGET_MODULES.value,
warmup_ratio=_WARMUP_RATIO.value,
max_steps=_MAX_STEPS.value,
max_seq_length=_MAX_SEQ_LENGTH.value,
learning_rate=_LEARNING_RATE.value,
)
else:
raise ValueError('The task {} is not supported.'.format(task))
if __name__ == '__main__':
app.run(main)
@@ -1,6 +0,0 @@
#!/bin/bash
# Setup accelerate config before running trainer.
python -c "from accelerate.utils import write_basic_config; write_basic_config(mixed_precision='fp16')"
accelerate launch "$@"
@@ -0,0 +1,16 @@
# Dockerfile for axolotl training.
#
# To build:
# docker build -f model_oss/peft/train/axolotol/dockerfile/train.Dockerfile . -t ${YOUR_IMAGE_TAG}
#
# To push to gcr:
# docker tag ${YOUR_IMAGE_TAG} gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
# docker push gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
FROM winglian/axolotl:main-latest
RUN mkdir -p ./vertex_vision_model_garden/
COPY model_oss/peft/train/axolotl/*.py ./vertex_vision_model_garden/
ENTRYPOINT ["python3", "./vertex_vision_model_garden/train_entrypoint.py"]
@@ -0,0 +1,20 @@
#!/bin/bash
# Run copybara first:
# cloud/ml/applications/vision/model_garden/copybara/run_copybara_local.sh
# Run docker build:
# cloud/ml/applications/vision/model_garden/model_oss/peft/train/axolotl/scripts/build_train_docker.sh
set -x
COPYBARA_DIR="/tmp/train_docker/"
pushd "${COPYBARA_DIR}"
PROJECT="cloud-nas-260507"
IMAGE_TAG="gcr.io/${PROJECT}/axolotl-train:${USER}-test"
docker build -f model_oss/peft/train/axolotl/dockerfile/train.Dockerfile . -t "${IMAGE_TAG}"
docker push "${IMAGE_TAG}"
popd
@@ -0,0 +1,88 @@
"""Entrypoint for axolotl train docker."""
import argparse
import json
import os
import subprocess
def _get_multi_node_flags(cluster_spec: str) -> list[str]:
"""Returns the multi-node flags."""
print(f'CLUSTER_SPEC: {cluster_spec}')
cluster_data = json.loads(cluster_spec)
# Get primary node info
primary_node = cluster_data['cluster']['workerpool0'][0]
print(f'primary node: {primary_node}')
primary_node_addr, primary_node_port = primary_node.split(':')
print(f'primary node address: {primary_node_addr}')
print(f'primary node port: {primary_node_port}')
# Determine node rank of this machine
workerpool = cluster_data['task']['type']
if workerpool == 'workerpool0':
node_rank = 0
else:
node_rank = cluster_data['task']['index'] + 1
print(f'node rank: {node_rank}')
# Calculate total nodes
num_worker_nodes = len(cluster_data['cluster']['workerpool1'])
num_nodes = num_worker_nodes + 1 # Add 1 for the primary node
print(f'num nodes: {num_nodes}')
return [
f'--machine_rank={node_rank}',
f'--num_machines={num_nodes}',
f'--main_process_ip={primary_node_addr}',
f'--main_process_port={primary_node_port}',
'--max_restarts=0',
'--monitor_interval=120',
'--dynamo_backend=no',
]
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('--config_file')
parser.add_argument('--huggingface_access_token')
args, unknown = parser.parse_known_args()
accelerate_flags = []
if args.config_file:
accelerate_flags.append(f'--config_file={args.config_file}')
if cluster_spec := os.getenv('CLUSTER_SPEC', default=None):
print('========== Launch on cloud multi nodes ==========')
accelerate_flags.extend(_get_multi_node_flags(cluster_spec))
cmd = (
[
'accelerate',
'launch',
]
+ accelerate_flags
+ [
'-m',
'axolotl.cli.train',
]
+ unknown
)
print(f'{cmd=}', flush=True)
env = os.environ.copy()
if args.huggingface_access_token:
env['HF_TOKEN'] = args.huggingface_access_token
subprocess.run(
cmd,
check=True,
env=env,
)
if __name__ == '__main__':
main()
@@ -0,0 +1,45 @@
"""Class that bundles docker related flags."""
import getpass
import os
import pwd
class DockerCommandBuilder:
"""Bundle docker related flags."""
def __init__(self, docker_uri, shm_size='128gb'):
self._docker_uri = [docker_uri]
self._defaults = [
'docker',
'run',
'--gpus=all',
'--net=host',
'--rm',
f'--shm-size={shm_size}',
]
user = getpass.getuser()
# username ends with `_google_com` is managed by ldap and does not have a
# corresponding entry in /etc/passwd or /etc/group file. We cannot enable
# non-root docker user with below method.
if not user.endswith('_google_com'):
uid = os.getuid()
gid = pwd.getpwuid(uid).pw_gid
self._defaults += [
f'--user={uid}:{gid}',
'--volume=/etc/group:/etc/group:ro',
'--volume=/etc/passwd:/etc/passwd:ro',
]
self._env_vars = []
self._mount_maps = []
def add_env_var(self, var, val):
self._env_vars.append(f'--env={var}={val}')
def add_mount_map(self, host_path, docker_path):
self._mount_maps.append(f'--volume={host_path}:{docker_path}')
def build_cmd(self) -> str:
return self._defaults + self._env_vars + self._mount_maps + self._docker_uri
@@ -0,0 +1,429 @@
# pylint: disable=W,C,R
# DO NOT MODIFY: this file is auto-generated
# See go/vmg-oss-peft-tests#command-builder-genpy
class InstructLoraCommandBuilder:
def __init__(self):
self._config_file = None
self._task = None
self._pretrained_model_id = None
self._dataset_name = None
self._train_split_name = None
self._template = None
self._instruct_column_in_dataset = None
self._output_dir = None
self._merge_base_and_lora_output_dir = None
self._logging_output_dir = None
self._per_device_train_batch_size = None
self._gradient_accumulation_steps = None
self._lora_rank = None
self._lora_alpha = None
self._lora_dropout = None
self._max_steps = None
self._num_epochs = None
self._max_seq_length = None
self._learning_rate = None
self._lr_scheduler_type = None
self._precision_mode = None
self._train_precision = None
self._enable_gradient_checkpointing = None
self._use_example_packing = None
self._attn_implementation = None
self._optimizer = None
self._warmup_ratio = None
self._report_to = None
self._save_steps = None
self._logging_steps = None
self._huggingface_access_token = None
self._eval_dataset_path = None
self._eval_column = None
self._eval_template = None
self._eval_split = None
self._eval_steps = None
self._eval_tasks = None
self._eval_metric_name = None
self._completion_only = None
self._max_grad_norm = None
self._logger_level = None
self._benchmark_out_file = None
self._tuning_data_stats_file = None
self._enable_peft = None
self._merge_model_precision_mode = None
self._target_modules = None
@property
def config_file(self):
return self._config_file
@config_file.setter
def config_file(self, val: str):
self._config_file = val
@property
def task(self):
return self._task
@task.setter
def task(self, val: str):
self._task = val
@property
def pretrained_model_id(self):
return self._pretrained_model_id
@pretrained_model_id.setter
def pretrained_model_id(self, val: str):
self._pretrained_model_id = val
@property
def train_dataset(self):
return self._dataset_name
@train_dataset.setter
def train_dataset(self, val: str):
self._dataset_name = val
@property
def train_split_name(self):
return self._train_split_name
@train_split_name.setter
def train_split_name(self, val: str):
self._train_split_name = val
@property
def template(self):
return self._template
@template.setter
def template(self, val: str):
self._template = val
@property
def instruct_column(self):
return self._instruct_column_in_dataset
@instruct_column.setter
def instruct_column(self, val: str):
self._instruct_column_in_dataset = val
@property
def ckpt_dir(self):
return self._output_dir
@ckpt_dir.setter
def ckpt_dir(self, val: str):
self._output_dir = val
@property
def merged_model_dir(self):
return self._merge_base_and_lora_output_dir
@merged_model_dir.setter
def merged_model_dir(self, val: str):
self._merge_base_and_lora_output_dir = val
@property
def logging_dir(self):
return self._logging_output_dir
@logging_dir.setter
def logging_dir(self, val: str):
self._logging_output_dir = val
@property
def per_device_batch_size(self):
return self._per_device_train_batch_size
@per_device_batch_size.setter
def per_device_batch_size(self, val: int):
self._per_device_train_batch_size = val
@property
def gradient_accumulation_steps(self):
return self._gradient_accumulation_steps
@gradient_accumulation_steps.setter
def gradient_accumulation_steps(self, val: int):
self._gradient_accumulation_steps = val
@property
def lora_rank(self):
return self._lora_rank
@lora_rank.setter
def lora_rank(self, val: int):
self._lora_rank = val
@property
def lora_alpha(self):
return self._lora_alpha
@lora_alpha.setter
def lora_alpha(self, val: int):
self._lora_alpha = val
@property
def lora_dropout(self):
return self._lora_dropout
@lora_dropout.setter
def lora_dropout(self, val: float):
self._lora_dropout = val
@property
def max_steps(self):
return self._max_steps
@max_steps.setter
def max_steps(self, val: int):
self._max_steps = val
@property
def num_epochs(self):
return self._num_epochs
@num_epochs.setter
def num_epochs(self, val: float):
self._num_epochs = val
@property
def max_seq_length(self):
return self._max_seq_length
@max_seq_length.setter
def max_seq_length(self, val: int):
self._max_seq_length = val
@property
def learning_rate(self):
return self._learning_rate
@learning_rate.setter
def learning_rate(self, val: float):
self._learning_rate = val
@property
def lr_scheduler_type(self):
return self._lr_scheduler_type
@lr_scheduler_type.setter
def lr_scheduler_type(self, val: str):
self._lr_scheduler_type = val
@property
def load_precision(self):
return self._precision_mode
@load_precision.setter
def load_precision(self, val: str):
self._precision_mode = val
@property
def train_precision(self):
return self._train_precision
@train_precision.setter
def train_precision(self, val: str):
self._train_precision = val
@property
def gradient_checkpointing(self):
return self._enable_gradient_checkpointing
@gradient_checkpointing.setter
def gradient_checkpointing(self, val: bool):
self._enable_gradient_checkpointing = val
@property
def example_packing(self):
return self._use_example_packing
@example_packing.setter
def example_packing(self, val: bool):
self._use_example_packing = val
@property
def attn_implementation(self):
return self._attn_implementation
@attn_implementation.setter
def attn_implementation(self, val: str):
self._attn_implementation = val
@property
def optimizer(self):
return self._optimizer
@optimizer.setter
def optimizer(self, val: str):
self._optimizer = val
@property
def warmup_ratio(self):
return self._warmup_ratio
@warmup_ratio.setter
def warmup_ratio(self, val: float):
self._warmup_ratio = val
@property
def report_to(self):
return self._report_to
@report_to.setter
def report_to(self, val: str):
self._report_to = val
@property
def save_steps(self):
return self._save_steps
@save_steps.setter
def save_steps(self, val: int):
self._save_steps = val
@property
def logging_steps(self):
return self._logging_steps
@logging_steps.setter
def logging_steps(self, val: int):
self._logging_steps = val
@property
def huggingface_access_token(self):
return self._huggingface_access_token
@huggingface_access_token.setter
def huggingface_access_token(self, val: str):
self._huggingface_access_token = val
@property
def eval_dataset(self):
return self._eval_dataset_path
@eval_dataset.setter
def eval_dataset(self, val: str):
self._eval_dataset_path = val
@property
def eval_instruct_column(self):
return self._eval_column
@eval_instruct_column.setter
def eval_instruct_column(self, val: str):
self._eval_column = val
@property
def eval_template(self):
return self._eval_template
@eval_template.setter
def eval_template(self, val: str):
self._eval_template = val
@property
def eval_split_name(self):
return self._eval_split
@eval_split_name.setter
def eval_split_name(self, val: str):
self._eval_split = val
@property
def eval_steps(self):
return self._eval_steps
@eval_steps.setter
def eval_steps(self, val: int):
self._eval_steps = val
@property
def eval_tasks(self):
return self._eval_tasks
@eval_tasks.setter
def eval_tasks(self, val: str):
self._eval_tasks = val
@property
def eval_metric_name(self):
return self._eval_metric_name
@eval_metric_name.setter
def eval_metric_name(self, val: str):
self._eval_metric_name = val
@property
def completion_only(self):
return self._completion_only
@completion_only.setter
def completion_only(self, val: bool):
self._completion_only = val
@property
def max_grad_norm(self):
return self._max_grad_norm
@max_grad_norm.setter
def max_grad_norm(self, val: float):
self._max_grad_norm = val
@property
def logger_level(self):
return self._logger_level
@logger_level.setter
def logger_level(self, val: str):
self._logger_level = val
@property
def benchmark_out_file(self):
return self._benchmark_out_file
@benchmark_out_file.setter
def benchmark_out_file(self, val: str):
self._benchmark_out_file = val
@property
def tuning_data_stats_file(self):
return self._tuning_data_stats_file
@tuning_data_stats_file.setter
def tuning_data_stats_file(self, val: str):
self._tuning_data_stats_file = val
@property
def enable_peft(self):
return self._enable_peft
@enable_peft.setter
def enable_peft(self, val: bool):
self._enable_peft = val
@property
def merge_model_precision_mode(self):
return self._merge_model_precision_mode
@merge_model_precision_mode.setter
def merge_model_precision_mode(self, val: str):
self._merge_model_precision_mode = val
@property
def target_modules(self):
return self._target_modules
@target_modules.setter
def target_modules(self, val: str):
self._target_modules = val
def build_cmd(self) -> str:
cmd = []
for k, v in self.__dict__.items():
if v is not None:
cmd.append(f'--{k[1:]}={v}')
return cmd
@@ -0,0 +1,142 @@
# pylint: disable=W,C,R
# DO NOT MODIFY: this file is auto-generated
# See go/vmg-oss-peft-tests#command-builder-genpy
class QuantizeModelCommandBuilder:
def __init__(self):
self._task = None
self._pretrained_model_id = None
self._quantization_method = None
self._quantization_precision_mode = None
self._quantization_dataset_name = None
self._text_column_in_quantization_dataset = None
self._quantization_output_dir = None
self._device_map = None
self._max_memory = None
self._group_size = None
self._desc_act = None
self._damp_percent = None
self._cache_examples_on_gpu = None
self._awq_version = None
@property
def task(self):
return self._task
@task.setter
def task(self, val: str):
self._task = val
@property
def pretrained_model_id(self):
return self._pretrained_model_id
@pretrained_model_id.setter
def pretrained_model_id(self, val: str):
self._pretrained_model_id = val
@property
def quantization_method(self):
return self._quantization_method
@quantization_method.setter
def quantization_method(self, val: str):
self._quantization_method = val
@property
def quantization_precision_mode(self):
return self._quantization_precision_mode
@quantization_precision_mode.setter
def quantization_precision_mode(self, val: str):
self._quantization_precision_mode = val
@property
def quantization_dataset_name(self):
return self._quantization_dataset_name
@quantization_dataset_name.setter
def quantization_dataset_name(self, val: str):
self._quantization_dataset_name = val
@property
def text_column_in_quantization_dataset(self):
return self._text_column_in_quantization_dataset
@text_column_in_quantization_dataset.setter
def text_column_in_quantization_dataset(self, val: str):
self._text_column_in_quantization_dataset = val
@property
def quantization_output_dir(self):
return self._quantization_output_dir
@quantization_output_dir.setter
def quantization_output_dir(self, val: str):
self._quantization_output_dir = val
@property
def device_map(self):
return self._device_map
@device_map.setter
def device_map(self, val: str):
self._device_map = val
@property
def max_memory(self):
return self._max_memory
@max_memory.setter
def max_memory(self, val: str):
self._max_memory = val
@property
def group_size(self):
return self._group_size
@group_size.setter
def group_size(self, val: int):
self._group_size = val
@property
def desc_act(self):
return self._desc_act
@desc_act.setter
def desc_act(self, val: bool):
self._desc_act = val
@property
def damp_percent(self):
return self._damp_percent
@damp_percent.setter
def damp_percent(self, val: float):
self._damp_percent = val
@property
def cache_examples_on_gpu(self):
return self._cache_examples_on_gpu
@cache_examples_on_gpu.setter
def cache_examples_on_gpu(self, val: bool):
self._cache_examples_on_gpu = val
@property
def awq_version(self):
return self._awq_version
@awq_version.setter
def awq_version(self, val: str):
self._awq_version = val
def build_cmd(self) -> str:
cmd = []
for k, v in self.__dict__.items():
if v is not None:
cmd.append(f'--{k[1:]}={v}')
return cmd
@@ -0,0 +1,106 @@
# pylint: disable=missing-function-docstring
# pylint: disable=missing-class-docstring
"""Tests adapters of PEFT train docker."""
import inspect
import os
import time
from absl.testing import absltest
from absl.testing import parameterized
import instruct_lora_command_builder as task_cmd_builder
from safetensors import safe_open
import test_util
class AdapterTest(test_util.TestBase):
# Needs to be accessible outside docker to check artifacts.
_TEST_OUTPUT_DIR = os.path.expanduser('~/output')
_MODULES_NEED_TO_BE_EXCLUDED_IN_ADAPTER = ['lm_head', 'embed_tokens']
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.test_suite_output_dir = os.path.join(
cls._TEST_OUTPUT_DIR,
os.path.splitext(os.path.basename(__file__))[0],
cls.__class__.__name__,
)
def setUp(self):
super().setUp()
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '0,1,2,3,4,5,6,7')
self.task_cmd_builder = task_cmd_builder.InstructLoraCommandBuilder()
self.task_cmd_builder.task = 'instruct-lora'
self.task_cmd_builder.per_device_batch_size = 1
self.task_cmd_builder.train_dataset = test_util.get_test_data_path(
'peft_train_sample.jsonl'
)
self.task_cmd_builder.train_split_name = 'train'
self.task_cmd_builder.instruct_column = 'input_text'
self.task_cmd_builder.template = 'llama3-text-bison'
self.task_cmd_builder.gradient_accumulation_steps = 1
self.task_cmd_builder.lora_rank = 16
self.task_cmd_builder.lora_alpha = 32
self.task_cmd_builder.lora_dropout = 0.05
self.task_cmd_builder.max_steps = 1
self.task_cmd_builder.max_seq_length = 256
self.task_cmd_builder.load_precision = '4bit'
self.task_cmd_builder.gradient_checkpointing = True
self.task_cmd_builder.attn_implementation = 'flash_attention_2'
self.task_cmd_builder.save_steps = 10
self.task_cmd_builder.max_steps = 3
self.task_cmd_builder.config_file = (
'vertex_vision_model_garden_peft/llama_fsdp_8gpu.yaml'
)
def setup_output_dir(self, testcase_name: str):
testcase_output_dir = os.path.join(
self.test_suite_output_dir, testcase_name, test_util.get_timestamp()
)
self.task_cmd_builder.ckpt_dir = os.path.join(
testcase_output_dir, 'adapter'
)
self.task_cmd_builder.logging_dir = os.path.join(
testcase_output_dir, 'logs'
)
def check_adapter_for_bad_modules(self, adapter_path):
unwanted_modules = set()
with safe_open(adapter_path, framework='pt', device='cpu') as f:
for key in f.keys():
for module in self._MODULES_NEED_TO_BE_EXCLUDED_IN_ADAPTER:
if module in key:
unwanted_modules.add(key)
assert (
not unwanted_modules
), f'Adapter includes unwanted modules: {unwanted_modules}'
@parameterized.named_parameters(
('llama3.1-8b', 'llama3.1-8b-hf'),
('llama3.1-70b', 'llama3.1-70b-hf'),
('llama2-7b', 'llama2-7b-hf'),
)
def test_llama_adapters(self, model_name):
test_function_name = inspect.stack()[0][3]
self.setup_output_dir(f'{test_function_name}-{model_name}')
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id(model_name)
)
start_time = time.time()
self.assertEqual(self.run_cmd(), 0)
end_time = time.time()
self.assertLess(end_time - start_time, 1000)
adapter = os.path.join(
self.task_cmd_builder.ckpt_dir,
'checkpoint-final/adapter_model.safetensors',
)
self.check_adapter_for_bad_modules(adapter)
if __name__ == '__main__':
absltest.main()
@@ -0,0 +1,290 @@
# pylint: disable=missing-function-docstring
# pylint: disable=missing-class-docstring
"""Tests various features of PEFT train docker."""
import os
import time
from absl.testing import absltest
from absl.testing import parameterized
import instruct_lora_command_builder as task_cmd_builder
import test_util
class GcsUploadDownloadTest(test_util.TestBase):
def setUp(self):
super().setUp()
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '0')
self.task_cmd_builder = task_cmd_builder.InstructLoraCommandBuilder()
self.task_cmd_builder.task = 'instruct-lora'
self.task_cmd_builder.per_device_batch_size = 1
self.task_cmd_builder.train_dataset = test_util.get_test_data_path(
'peft_train_sample.jsonl'
)
self.task_cmd_builder.train_split_name = 'train'
self.task_cmd_builder.instruct_column = 'input_text'
self.task_cmd_builder.template = 'llama3-text-bison'
self.task_cmd_builder.gradient_accumulation_steps = 1
self.task_cmd_builder.lora_rank = 16
self.task_cmd_builder.lora_alpha = 32
self.task_cmd_builder.lora_dropout = 0.05
self.task_cmd_builder.max_steps = 1
self.task_cmd_builder.max_seq_length = 256
self.task_cmd_builder.load_precision = '4bit'
self.task_cmd_builder.gradient_checkpointing = True
self.task_cmd_builder.attn_implementation = 'flash_attention_2'
self.task_cmd_builder.ckpt_dir = '/tmp'
@parameterized.named_parameters(
(
'llama3_8b_gcs',
'gs://vertex-model-garden-public-us/llama3/llama3-8b-hf',
),
('llama2_7b_hf', 'NousResearch/Llama-2-7b-hf'),
)
def test_model_download_single_process(self, pretrained_model_id):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id(pretrained_model_id)
)
start_time = time.time()
self.assertEqual(self.run_cmd(), 0)
end_time = time.time()
self.assertLess(end_time - start_time, 5 * 60.0)
@parameterized.named_parameters(
(
'llama3_8b_gcs',
'gs://vertex-model-garden-public-us/llama3/llama3-8b-hf',
),
('llama2_7b_hf', 'NousResearch/Llama-2-7b-hf'),
)
def test_model_download_multi_process(self, pretrained_model_id):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id(pretrained_model_id)
)
self.task_cmd_builder.config_file = (
'vertex_vision_model_garden_peft/llama_fsdp_8gpu.yaml'
)
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '0,1,2,3,4,5,6,7')
start_time = time.time()
self.assertEqual(self.run_cmd(), 0)
end_time = time.time()
self.assertLess(end_time - start_time, 5 * 60.0)
def test_70b_model_download(self):
self.task_cmd_builder.pretrained_model_id = (
'gs://vertex-model-garden-public-us/llama3/llama3-70b-hf'
)
start_time = time.time()
self.assertEqual(self.run_cmd(), 0)
end_time = time.time()
self.assertLess(end_time - start_time, 10 * 60.0)
@parameterized.named_parameters(
('merged-without-upload', '/tmp/merged'),
('merged-and-upload-to-gcs', 'gs://vmg-test-ttl-1y/tests/merged'),
)
def test_model_merge(self, output_dir):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id('llama3.1-8b-hf')
)
ckpt_dir = os.path.join(
output_dir,
f'output-{test_util.get_timestamp()}',
)
self.task_cmd_builder.ckpt_dir = ckpt_dir
self.task_cmd_builder.merged_model_dir = os.path.join(ckpt_dir, 'merged')
self.task_cmd_builder.logging_dir = '/tmp/logging'
start_time = time.time()
self.assertEqual(self.run_cmd(), 0)
end_time = time.time()
self.assertLess(end_time - start_time, 5 * 60.0)
def test_model_fp8_conversion(self):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id('llama3.1-8b-hf')
)
ckpt_dir = f'/tmp/output/output-{test_util.get_timestamp()}'
self.task_cmd_builder.ckpt_dir = ckpt_dir
self.task_cmd_builder.merged_model_dir = os.path.join(ckpt_dir, 'merged')
self.task_cmd_builder.logging_dir = os.path.join(ckpt_dir, 'logging')
self.task_cmd_builder.merge_model_precision_mode = 'float8'
start_time = time.time()
self.assertEqual(self.run_cmd(), 0)
end_time = time.time()
self.assertLess(end_time - start_time, 5 * 60.0)
@parameterized.named_parameters(
('merged-without-upload', '/tmp/merged'),
('merged-and-upload-to-gcs', 'gs://vmg-test-ttl-1y/tests/merged'),
)
def test_model_merge_and_upload_deepspeed(self, merged_model_dir):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id('llama3.1-8b-hf')
)
self.task_cmd_builder.config_file = (
'vertex_vision_model_garden_peft/deepspeed_zero3_8gpu.yaml'
)
self.task_cmd_builder.merged_model_dir = os.path.join(
merged_model_dir, f'merged-{test_util.get_timestamp()}'
)
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '0,1,2,3,4,5,6,7')
start_time = time.time()
self.assertEqual(self.run_cmd(), 0)
end_time = time.time()
self.assertLess(end_time - start_time, 5 * 60.0)
@parameterized.named_parameters(
('save-only-last', 10),
('save-multiple-times', 1),
)
def test_llama3_8b_save_and_merge_8_gpus_fsdp(self, save_steps):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id('llama3.1-8b-hf')
)
self.task_cmd_builder.save_steps = save_steps
self.task_cmd_builder.max_steps = 3
self.task_cmd_builder.config_file = (
'vertex_vision_model_garden_peft/llama_fsdp_8gpu.yaml'
)
self.task_cmd_builder.merged_model_dir = '/tmp/merged'
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '0,1,2,3,4,5,6,7')
start_time = time.time()
self.assertEqual(self.run_cmd(), 0)
end_time = time.time()
self.assertLess(end_time - start_time, 9 * 60.0)
class TemplateAndDataStatsTest(test_util.TestBase):
def setUp(self):
super().setUp()
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '0')
self.task_cmd_builder = task_cmd_builder.InstructLoraCommandBuilder()
self.task_cmd_builder.task = 'instruct-lora'
self.task_cmd_builder.per_device_batch_size = 1
self.task_cmd_builder.gradient_accumulation_steps = 1
self.task_cmd_builder.lora_rank = 16
self.task_cmd_builder.lora_alpha = 32
self.task_cmd_builder.lora_dropout = 0.05
self.task_cmd_builder.max_steps = 1
self.task_cmd_builder.max_seq_length = 256
self.task_cmd_builder.load_precision = '4bit'
self.task_cmd_builder.gradient_checkpointing = True
self.task_cmd_builder.attn_implementation = 'flash_attention_2'
self.task_cmd_builder.ckpt_dir = '/tmp'
def test_openai_chat_template(self):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id('llama3.1-8b-hf')
)
self.task_cmd_builder.train_dataset = test_util.get_test_data_path(
'openai-multi-chat-example-data.jsonl'
)
self.task_cmd_builder.train_split_name = 'train'
self.task_cmd_builder.instruct_column = 'messages'
self.task_cmd_builder.template = 'llama3'
self.assertEqual(self.run_cmd(), 0)
def test_openai_completion_template(self):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id('llama3.1-8b-hf')
)
self.task_cmd_builder.train_dataset = test_util.get_test_data_path(
'openai-completion-example-data.jsonl'
)
self.task_cmd_builder.train_split_name = 'train'
self.task_cmd_builder.instruct_column = 'prompt'
self.task_cmd_builder.template = 'openai-completion'
self.assertEqual(self.run_cmd(), 0)
def test_data_stats_chat_template(self):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id('llama3.1-8b-hf')
)
self.task_cmd_builder.config_file = (
'vertex_vision_model_garden_peft/llama_fsdp_8gpu.yaml'
)
self.task_cmd_builder.train_dataset = test_util.get_test_data_path(
'openai-multi-chat-example-data.jsonl'
)
self.task_cmd_builder.train_split_name = 'train'
self.task_cmd_builder.instruct_column = 'messages'
self.task_cmd_builder.template = 'llama3'
self.task_cmd_builder.tuning_data_stats_file = '/tmp/data-stats.json'
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '0,1,2,3,4,5,6,7')
self.assertEqual(self.run_cmd(), 0)
def test_data_stats_completion_template(self):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id('llama3.1-8b-hf')
)
self.task_cmd_builder.config_file = (
'vertex_vision_model_garden_peft/llama_fsdp_8gpu.yaml'
)
self.task_cmd_builder.train_dataset = test_util.get_test_data_path(
'openai-completion-example-data.jsonl'
)
self.task_cmd_builder.train_split_name = 'train'
self.task_cmd_builder.instruct_column = 'prompt'
self.task_cmd_builder.template = 'openai-completion'
self.task_cmd_builder.tuning_data_stats_file = '/tmp/data-stats.json'
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '0,1,2,3,4,5,6,7')
self.assertEqual(self.run_cmd(), 0)
class TargetModulesTest(test_util.TestBase):
def setUp(self):
super().setUp()
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '0')
self.task_cmd_builder = task_cmd_builder.InstructLoraCommandBuilder()
self.task_cmd_builder.task = 'instruct-lora'
self.task_cmd_builder.per_device_batch_size = 1
self.task_cmd_builder.train_dataset = test_util.get_test_data_path(
'peft_train_sample.jsonl'
)
self.task_cmd_builder.train_split_name = 'train'
self.task_cmd_builder.instruct_column = 'input_text'
self.task_cmd_builder.template = 'llama3-text-bison'
self.task_cmd_builder.gradient_accumulation_steps = 1
self.task_cmd_builder.lora_rank = 16
self.task_cmd_builder.lora_alpha = 32
self.task_cmd_builder.lora_dropout = 0.05
self.task_cmd_builder.max_steps = 1
self.task_cmd_builder.max_seq_length = 256
self.task_cmd_builder.load_precision = '4bit'
self.task_cmd_builder.gradient_checkpointing = True
self.task_cmd_builder.attn_implementation = 'flash_attention_2'
self.task_cmd_builder.ckpt_dir = '/tmp'
def test_target_modules(self):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id('llama3.1-8b-hf')
)
self.task_cmd_builder.target_modules = 'q_proj, v_proj, k_proj'
self.assertEqual(self.run_cmd(), 0)
if __name__ == '__main__':
absltest.main()
@@ -0,0 +1,180 @@
# pylint: disable=missing-function-docstring
# pylint: disable=missing-class-docstring
"""Tests to check training throughput and GPU memory consumption."""
import os
import pathlib
from absl.testing import absltest
from absl.testing import parameterized
import instruct_lora_command_builder as task_cmd_builder
import test_util
class TrainerThroughputTest(test_util.TestBase):
_TEST_OUTPUT_DIR = os.path.expanduser('~/throughput_tests')
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.test_suite_output_dir = os.path.join(
cls._TEST_OUTPUT_DIR, os.path.splitext(os.path.basename(__file__))[0]
)
if not os.path.isdir(cls.test_suite_output_dir):
pathlib.Path(cls.test_suite_output_dir).mkdir(parents=True)
def setUp(self):
super().setUp()
self.task_cmd_builder = task_cmd_builder.InstructLoraCommandBuilder()
self.task_cmd_builder.task = 'instruct-lora'
self.task_cmd_builder.per_device_batch_size = 1
self.task_cmd_builder.gradient_accumulation_steps = 1
self.task_cmd_builder.lora_rank = 16
self.task_cmd_builder.lora_alpha = 32
self.task_cmd_builder.lora_dropout = 0.05
self.task_cmd_builder.learning_rate = 5e-5
self.task_cmd_builder.warmup_ratio = 0.01
self.task_cmd_builder.max_steps = 10
self.task_cmd_builder.save_steps = 1000
self.task_cmd_builder.logging_steps = 1
self.task_cmd_builder.gradient_checkpointing = True
self.task_cmd_builder.attn_implementation = 'flash_attention_2'
self.task_cmd_builder.example_packing = True
self.task_cmd_builder.train_dataset = 'mlabonne/guanaco-llama2-1k'
self.task_cmd_builder.train_split_name = 'train'
self.task_cmd_builder.instruct_column = 'text'
self.task_cmd_builder.template = 'openassistant-guanaco'
self.task_cmd_builder.ckpt_dir = '/tmp/adapter'
self.task_cmd_builder.logging_dir = '/tmp/logs'
def run_cmd_and_handle_failure(self):
ret = self.run_cmd()
if ret != 0:
with open(self.task_cmd_builder.benchmark_out_file, 'a') as f:
max_seq_length = self.task_cmd_builder.max_seq_length
f.write(f'{max_seq_length/1024.0:.1f}k | failed | n/a\n')
return ret
@parameterized.product(
model_name=[
'llama3-70b-hf',
'llama3.1-70b-hf',
'Mistral-7B-v0.1',
'Mixtral-8x7B-v0.1',
'Gemma2-9b-it',
],
precision=['4bit', '8bit', 'bfloat16'],
max_seq_length=list(range(4 * 1024, 24 * 1024 + 1, 4 * 1024)),
)
def test_model_single_gpu(self, model_name, precision, max_seq_length):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id(model_name)
)
self.task_cmd_builder.max_seq_length = max_seq_length
self.task_cmd_builder.load_precision = precision
self.task_cmd_builder.benchmark_out_file = os.path.join(
self.test_suite_output_dir, f'bm_{model_name}_{precision}.txt'
)
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '0')
self.assertEqual(self.run_cmd_and_handle_failure(), 0)
@parameterized.product(
model_name=[
'llama3-70b-hf',
'llama3.1-70b-hf',
'Mistral-7B-v0.1',
'Mixtral-8x7B-v0.1',
'Gemma2-9b-it',
],
precision=['4bit', '8bit', 'bfloat16'],
max_seq_length=list(range(4 * 1024, 24 * 1024 + 1, 4 * 1024)),
num_gpus=[8],
config=['deepspeed_zero2', 'deepspeed_zero3'],
)
def test_model_multi_gpu_deepspeed(
self, model_name, precision, max_seq_length, num_gpus, config
):
self.assertTrue(num_gpus == 4 or num_gpus == 8)
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id(model_name)
)
self.task_cmd_builder.max_seq_length = max_seq_length
self.task_cmd_builder.load_precision = precision
self.task_cmd_builder.benchmark_out_file = os.path.join(
self.test_suite_output_dir,
f'bm_{config}_{num_gpus}gpu_{model_name}_{precision}.txt',
)
self.task_cmd_builder.config_file = (
f'vertex_vision_model_garden_peft/{config}_{num_gpus}gpu.yaml'
)
self.docker_builder.add_env_var(
'CUDA_VISIBLE_DEVICES', ','.join([str(x) for x in range(0, num_gpus)])
)
self.assertEqual(self.run_cmd_and_handle_failure(), 0)
@parameterized.product(
model_name=['llama3.1-70b-hf'],
precision=['4bit', '8bit', 'bfloat16'],
max_seq_length=list(range(4 * 1024, 24 * 1024 + 1, 4 * 1024)),
num_gpus=[8],
)
def test_model_multi_gpu_fsdp_lora(
self, model_name, precision, max_seq_length, num_gpus
):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id(model_name)
)
self.task_cmd_builder.max_seq_length = max_seq_length
self.task_cmd_builder.load_precision = precision
self.task_cmd_builder.benchmark_out_file = os.path.join(
self.test_suite_output_dir,
f'bm_fsdp_{num_gpus}gpu_{model_name}_{precision}.txt',
)
self.task_cmd_builder.config_file = (
'vertex_vision_model_garden_peft/llama_fsdp_8gpu.yaml'
)
self.docker_builder.add_env_var(
'CUDA_VISIBLE_DEVICES', ','.join([str(x) for x in range(0, num_gpus)])
)
self.assertEqual(self.run_cmd_and_handle_failure(), 0)
@parameterized.product(
model_name=['llama3.1-70b-hf'],
precision=['bfloat16'],
max_seq_length=list(range(4 * 1024, 24 * 1024 + 1, 4 * 1024)),
num_gpus=[8],
)
def test_model_multi_gpu_fsdp_full_finetuning(
self, model_name, precision, max_seq_length, num_gpus
):
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id(model_name)
)
self.task_cmd_builder.max_seq_length = max_seq_length
self.task_cmd_builder.load_precision = precision
self.task_cmd_builder.benchmark_out_file = os.path.join(
self.test_suite_output_dir,
f'bm_fsdp_full_finetuning_{num_gpus}gpu_{model_name}_{precision}.txt',
)
self.task_cmd_builder.config_file = (
'vertex_vision_model_garden_peft/llama_fsdp_8gpu.yaml'
)
self.task_cmd_builder.enable_peft = False
self.docker_builder.add_env_var(
'CUDA_VISIBLE_DEVICES', ','.join([str(x) for x in range(0, num_gpus)])
)
self.assertEqual(self.run_cmd_and_handle_failure(), 0)
if __name__ == '__main__':
absltest.main()
@@ -0,0 +1,152 @@
# pylint: disable=missing-function-docstring
# pylint: disable=missing-class-docstring
"""Tests to make sure trained model achieves decent quality.
Right now, the metric is loss decreasing and we'll eyeball the TB graphs.
"""
import os
from absl.testing import absltest
from absl.testing import parameterized
import instruct_lora_command_builder as task_cmd_builder
import test_util
class TrainedModelQualityTest(test_util.TestBase):
_TEST_OUTPUT_DIR = os.path.expanduser('~/output')
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.test_suite_output_dir = os.path.join(
cls._TEST_OUTPUT_DIR,
os.path.splitext(os.path.basename(__file__))[0],
cls.__class__.__name__,
)
def setUp(self):
super().setUp()
self.task_cmd_builder = task_cmd_builder.InstructLoraCommandBuilder()
self.task_cmd_builder.task = 'instruct-lora'
self.task_cmd_builder.eval_tasks = 'builtin_eval'
self.task_cmd_builder.eval_metric_name = 'loss'
self.task_cmd_builder.per_device_batch_size = 1
self.task_cmd_builder.gradient_accumulation_steps = 8
self.task_cmd_builder.lora_rank = 16
self.task_cmd_builder.lora_alpha = 32
self.task_cmd_builder.lora_dropout = 0.05
self.task_cmd_builder.learning_rate = 5e-5
self.task_cmd_builder.num_epochs = 2.0
self.task_cmd_builder.warmup_ratio = 0.01
self.task_cmd_builder.max_steps = -1
self.task_cmd_builder.save_steps = 10
self.task_cmd_builder.eval_steps = 10
self.task_cmd_builder.max_seq_length = 4096
self.task_cmd_builder.load_precision = '4bit'
self.task_cmd_builder.gradient_checkpointing = True
self.task_cmd_builder.completion_only = True
self.task_cmd_builder.attn_implementation = 'flash_attention_2'
self.task_cmd_builder.report_to = 'tensorboard'
def setup_output_dir(self, testcase_name: str):
testcase_output_dir = os.path.join(
self.test_suite_output_dir, testcase_name
)
self.task_cmd_builder.ckpt_dir = os.path.join(
testcase_output_dir, 'adapter'
)
self.task_cmd_builder.logging_dir = os.path.join(
testcase_output_dir, 'logs'
)
self.task_cmd_builder.merged_model_dir = os.path.join(
testcase_output_dir, 'merged'
)
@parameterized.named_parameters(
('llama3-8b', 'llama3-8b-hf'),
('llama3.1-8b', 'llama3.1-8b-hf'),
)
def test_8b_model_deepspeed(self, model_name):
self.setup_output_dir(f'test_deepspeed_{model_name}')
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id(model_name)
)
self.task_cmd_builder.train_dataset = test_util.get_test_data_path(
'peft_train_sample.jsonl'
)
self.task_cmd_builder.train_split_name = 'train'
self.task_cmd_builder.instruct_column = 'input_text'
self.task_cmd_builder.template = 'llama3-text-bison'
self.task_cmd_builder.eval_dataset = test_util.get_test_data_path(
'peft_eval_sample.jsonl'
)
self.task_cmd_builder.eval_split_name = 'train'
self.task_cmd_builder.eval_instruct_column = (
self.task_cmd_builder.instruct_column
)
self.task_cmd_builder.eval_template = self.task_cmd_builder.template
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '0')
self.assertEqual(self.run_cmd(), 0)
@parameterized.named_parameters(
('llama3-70b', 'llama3-70b-hf'),
('llama3.1-70b', 'llama3.1-70b-hf'),
)
def test_70b_model_deepspeed(self, model_name):
self.setup_output_dir(f'test_deepspeed_{model_name}')
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id(model_name)
)
self.task_cmd_builder.config_file = (
'vertex_vision_model_garden_peft/deepspeed_zero2_8gpu.yaml'
)
self.task_cmd_builder.train_dataset = 'timdettmers/openassistant-guanaco'
self.task_cmd_builder.train_split_name = 'train'
self.task_cmd_builder.instruct_column = 'text'
self.task_cmd_builder.template = 'openassistant-guanaco'
self.task_cmd_builder.eval_dataset = self.task_cmd_builder.train_dataset
self.task_cmd_builder.eval_split_name = 'test'
self.task_cmd_builder.eval_instruct_column = (
self.task_cmd_builder.instruct_column
)
self.task_cmd_builder.eval_template = self.task_cmd_builder.template
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '0,1,2,3,4,5,6,7')
self.assertEqual(self.run_cmd(), 0)
@parameterized.named_parameters(
('llama3-70b', 'llama3-70b-hf'),
('llama3.1-70b', 'llama3.1-70b-hf'),
)
def test_70b_model_fsdp(self, model_name):
self.setup_output_dir(f'test_fsdp_{model_name}')
self.task_cmd_builder.pretrained_model_id = (
test_util.get_pretrained_model_id(model_name)
)
self.task_cmd_builder.config_file = (
'vertex_vision_model_garden_peft/llama_fsdp_8gpu.yaml'
)
self.task_cmd_builder.train_dataset = 'timdettmers/openassistant-guanaco'
self.task_cmd_builder.train_split_name = 'train'
self.task_cmd_builder.instruct_column = 'text'
self.task_cmd_builder.template = 'openassistant-guanaco'
self.task_cmd_builder.eval_dataset = self.task_cmd_builder.train_dataset
self.task_cmd_builder.eval_split_name = 'test'
self.task_cmd_builder.eval_instruct_column = (
self.task_cmd_builder.instruct_column
)
self.task_cmd_builder.eval_template = self.task_cmd_builder.template
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '0,1,2,3,4,5,6,7')
self.assertEqual(self.run_cmd(), 0)
if __name__ == '__main__':
absltest.main()
@@ -0,0 +1,49 @@
# pylint: disable=missing-function-docstring
# pylint: disable=missing-class-docstring
"""Tests quantize model task in PEFT docker."""
import os
import time
from absl.testing import absltest
import quantize_model_command_builder as task_cmd_builder
import test_util
class QuantizeModelTest(test_util.TestBase):
def setUp(self):
super().setUp()
self.docker_builder.add_env_var('CUDA_VISIBLE_DEVICES', '')
self.docker_builder.add_mount_map(
os.path.expanduser('~'), os.path.expanduser('~')
)
self.task_cmd_builder = task_cmd_builder.QuantizeModelCommandBuilder()
self.task_cmd_builder.task = 'quantize-model'
self.task_cmd_builder.pretrained_model_id = (
'gs://vertex-model-garden-public-us/llama3/llama3-8b-hf'
)
self.task_cmd_builder.quantization_method = 'awq'
self.task_cmd_builder.quantization_precision_mode = '4bit'
self.task_cmd_builder.quantization_dataset_name = 'pileval'
self.task_cmd_builder.text_column_in_quantization_dataset = 'text'
self.task_cmd_builder.quantization_output_dir = '~/llama3-8b-hf-quantized'
self.task_cmd_builder.device_map = None
self.task_cmd_builder.max_memory = None
self.task_cmd_builder.group_size = 128
self.task_cmd_builder.desc_act = False
self.task_cmd_builder.damp_percent = 0.1
self.task_cmd_builder.cache_examples_on_gpu = False
self.task_cmd_builder.awq_version = 'GEMM'
def test_llama3_8b_model_awq_quantization(self):
start_time = time.time()
self.assertEqual(self.run_cmd(), 0)
end_time = time.time()
self.assertLess(end_time - start_time, 1.5 * 60 * 60)
if __name__ == '__main__':
absltest.main()
@@ -0,0 +1,134 @@
"""Test util class."""
import datetime
import os
import signal
import subprocess
import sys
from absl import flags
from absl import logging
from absl.testing import parameterized
import docker_command_builder as docker_cmd_builder
_DOCKER_URI = flags.DEFINE_string(
'docker_uri', None, 'docker image uri', required=True
)
_DRY_RUN = flags.DEFINE_bool('dry_run', False, 'dry-run the commands')
_LOCAL_INPUT_DIR = flags.DEFINE_string(
'local_input_dir',
os.path.expanduser('~/test_input'),
'local directory for storing input data.',
)
_LOCAL_OUTPUT_DIR = flags.DEFINE_string(
'local_output_dir',
'/tmp',
'local directory for storing test output.',
)
_GCS_INPUT_DIR = flags.DEFINE_string(
'gcs_input_dir',
'gs://peft-docker-test',
'GCS directory that stores model checkpoint, dataset and etc.',
)
_GCS_OUTPUT_DIR = flags.DEFINE_string(
'gcs_output_dir',
'gs://peft-docker-test/output',
'GCS directory that stores test output.',
)
class TestBase(parameterized.TestCase):
"""Test base class that defines how to run commands."""
def setUp(self):
super().setUp()
self.docker_builder = docker_cmd_builder.DockerCommandBuilder(
_DOCKER_URI.value
)
self.docker_builder.add_mount_map(
os.path.expanduser('~'), os.path.expanduser('~')
)
self.docker_builder.add_mount_map(
self.local_input_dir(), self.local_input_dir()
)
self.task_cmd_builder = None
def cmd(self):
return self.docker_builder.build_cmd() + self.task_cmd_builder.build_cmd()
def run_cmd(self) -> int:
logging.info('running command: \n%s', ' \\\n'.join(self.cmd()))
if _DRY_RUN.value:
return 0
p = subprocess.Popen(self.cmd(), stdout=sys.stdout, stderr=sys.stderr)
try:
unused_output, unused_error = p.communicate()
return p.returncode
except KeyboardInterrupt:
p.send_signal(signal.SIGINT)
return 0
def gcs_output_dir(self):
return _GCS_OUTPUT_DIR.value
def local_output_dir(self):
return _LOCAL_OUTPUT_DIR.value
def local_input_dir(self):
return _LOCAL_INPUT_DIR.value
def get_timestamp():
return datetime.datetime.now(datetime.timezone.utc).strftime(
'%Y%m%d_%H%M%S%Z'
)
def get_test_data_path(name: str, download: bool = True) -> str:
"""Gets test data path.
Args:
name: name of the test data
download: if True, then download data from GCS and returns its local path.
Returns:
test data path.
"""
def _download_from_gcs(name):
if not os.path.exists(_LOCAL_INPUT_DIR.value):
os.mkdir(_LOCAL_INPUT_DIR.value)
subprocess.check_output([
'gsutil',
'-m',
'cp',
'-r',
os.path.join(_GCS_INPUT_DIR.value, name),
_LOCAL_INPUT_DIR.value,
])
if not download:
return os.path.join(_GCS_INPUT_DIR.value, name)
local_data = os.path.join(_LOCAL_INPUT_DIR.value, name)
if not os.path.exists(local_data):
_download_from_gcs(name)
return local_data
def get_pretrained_model_id(model_id: str) -> str:
# If `model_id` contains `/`, it is assumed to be HF model or model from GCS.
if '/' in model_id:
return model_id
return get_test_data_path(model_id, download=True)
@@ -0,0 +1,327 @@
"""Tests validate the dataset with template task in PEFT docker."""
from absl.testing import absltest
from absl.testing import parameterized
import test_util
import validate_dataset_with_template_command_builder as task_cmd_builder
class ValidateDatasetWithTemplateTest(test_util.TestBase):
"""Test the validate dataset with template task in PEFT docker."""
def setUp(self):
super().setUp()
self.task_cmd_builder = (
task_cmd_builder.ValidateDatasetWithTemplateCommandBuilder()
)
self.task_cmd_builder.task = "validate-dataset-with-template"
@parameterized.named_parameters(
dict(
testcase_name="valid_rows",
validate_top_k_rows=100,
expected_result=0,
),
dict(
testcase_name="negative_rows",
validate_top_k_rows=-10,
expected_result=0,
),
dict(
testcase_name="out_of_range_rows",
validate_top_k_rows=100000,
expected_result=1,
),
)
def test_validate_dataset_with_template_top_k_rows(
self,
validate_top_k_rows,
expected_result,
):
self.task_cmd_builder.dataset_name = "timdettmers/openassistant-guanaco"
self.task_cmd_builder.train_split_name = "train"
self.task_cmd_builder.instruct_column_in_dataset = "text"
self.task_cmd_builder.template = (
"gs://cloud-nas-260507-tmp-20240724/openassistant-guanaco.json"
)
self.task_cmd_builder.validate_percentage_of_dataset = None
self.task_cmd_builder.validate_k_rows_of_dataset = validate_top_k_rows
self.task_cmd_builder.use_multiprocessing = True
result = self.run_cmd()
self.assertEqual(result, expected_result)
@parameterized.named_parameters(
dict(
testcase_name="valid_positive_x_percent",
validate_percentage_of_dataset=10,
expected_result=0,
),
dict(
testcase_name="valid_negative_x_percent",
validate_percentage_of_dataset=-10,
expected_result=0,
),
dict(
testcase_name="invalid_positive_x_percent",
validate_percentage_of_dataset=110,
expected_result=1,
),
dict(
testcase_name="invalid_negative_x_percent",
validate_percentage_of_dataset=-110,
expected_result=1,
),
)
def test_validate_dataset_with_template_x_percent(
self,
validate_percentage_of_dataset,
expected_result,
):
self.task_cmd_builder.dataset_name = "timdettmers/openassistant-guanaco"
self.task_cmd_builder.train_split_name = "train"
self.task_cmd_builder.instruct_column_in_dataset = "text"
self.task_cmd_builder.template = (
"gs://cloud-nas-260507-tmp-20240724/openassistant-guanaco.json"
)
self.task_cmd_builder.validate_percentage_of_dataset = (
validate_percentage_of_dataset
)
self.task_cmd_builder.validate_k_rows_of_dataset = None
self.task_cmd_builder.use_multiprocessing = True
result = self.run_cmd()
self.assertEqual(result, expected_result)
@parameterized.named_parameters(
dict(
testcase_name="invalid_default_input_column",
dataset_name="timdettmers/openassistant-guanaco",
split="train",
input_column="",
template=(
"gs://cloud-nas-260507-tmp-20240724/openassistant-guanaco.json"
),
validate_percentage_of_dataset=None,
validate_top_k_rows=None,
use_multiprocessing=True,
expected_result=1,
),
dict(
testcase_name="invalid_percentage",
dataset_name="timdettmers/openassistant-guanaco",
split="train",
input_column="text",
template=(
"gs://cloud-nas-260507-tmp-20240724/openassistant-guanaco.json"
),
validate_percentage_of_dataset=110,
validate_top_k_rows=None,
use_multiprocessing=True,
expected_result=1,
),
dict(
testcase_name="negative_percentage",
dataset_name="timdettmers/openassistant-guanaco",
split="train",
input_column="text",
template=(
"gs://cloud-nas-260507-tmp-20240724/openassistant-guanaco.json"
),
validate_percentage_of_dataset=-110,
validate_top_k_rows=None,
use_multiprocessing=True,
expected_result=1,
),
dict(
testcase_name="empty_dataset",
dataset_name="",
split="train",
input_column="text",
template=(
"gs://cloud-nas-260507-tmp-20240724/openassistant-guanaco.json"
),
validate_percentage_of_dataset=None,
validate_top_k_rows=None,
use_multiprocessing=True,
expected_result=1,
),
dict(
testcase_name="empty_split",
dataset_name="timdettmers/openassistant-guanaco",
split="",
input_column="text",
template=(
"gs://cloud-nas-260507-tmp-20240724/openassistant-guanaco.json"
),
validate_percentage_of_dataset=None,
validate_top_k_rows=None,
use_multiprocessing=True,
expected_result=1,
),
dict(
testcase_name="empty_template",
dataset_name="timdettmers/openassistant-guanaco",
split="train",
input_column="text",
template="",
validate_percentage_of_dataset=None,
validate_top_k_rows=None,
use_multiprocessing=True,
expected_result=1,
),
dict(
testcase_name="wrong_gcs_template",
dataset_name="gs://cloud-nas-260507-tmp-20240724/model-evaluation/peft_train_sample.jsonl",
split="train",
input_column="text",
template="gs://cloud-nas-260507-tmp-20240724/sample-template.json",
validate_percentage_of_dataset=None,
validate_top_k_rows=None,
use_multiprocessing=True,
expected_result=1,
),
dict(
testcase_name="wrong_gcs_dataset_name",
dataset_name="gs://cloud-nas-260507-tmp-20240724/model-evaluation/peft-train_sample.jsonl",
split="train",
input_column="text",
template="gs://cloud-nas-260507-tmp-20240724/sample_template.json",
validate_percentage_of_dataset=None,
validate_top_k_rows=None,
use_multiprocessing=True,
expected_result=1,
),
)
def test_validate_dataset_with_template_invalid_input(
self,
dataset_name,
split,
input_column,
template,
validate_percentage_of_dataset,
validate_top_k_rows,
use_multiprocessing,
expected_result,
):
self.task_cmd_builder.dataset_name = dataset_name
self.task_cmd_builder.train_split_name = split
self.task_cmd_builder.instruct_column_in_dataset = input_column
self.task_cmd_builder.template = template
self.task_cmd_builder.validate_percentage_of_dataset = (
validate_percentage_of_dataset
)
self.task_cmd_builder.validate_k_rows_of_dataset = validate_top_k_rows
self.task_cmd_builder.use_multiprocessing = use_multiprocessing
result = self.run_cmd()
self.assertEqual(result, expected_result)
@parameterized.named_parameters(
dict(
testcase_name="full_hf_dataset_with_multiprocessing",
dataset_name="timdettmers/openassistant-guanaco",
template=(
"gs://cloud-nas-260507-tmp-20240724/openassistant-guanaco.json"
),
validate_percentage_of_dataset=None,
validate_top_k_rows=None,
use_multiprocessing=True,
expected_result=0,
),
dict(
testcase_name="full_gcs_dataset_with_multiprocessing",
dataset_name="gs://cloud-nas-260507-tmp-20240724/model-evaluation/peft_train_sample.jsonl",
template="gs://cloud-nas-260507-tmp-20240724/sample_template.json",
validate_percentage_of_dataset=None,
validate_top_k_rows=None,
use_multiprocessing=True,
expected_result=0,
),
dict(
testcase_name="half_dataset_with_multiprocessing",
dataset_name="timdettmers/openassistant-guanaco",
template=(
"gs://cloud-nas-260507-tmp-20240724/openassistant-guanaco.json"
),
validate_percentage_of_dataset=50,
validate_top_k_rows=None,
use_multiprocessing=True,
expected_result=0,
),
dict(
testcase_name="top_100_rows_with_multiprocessing",
dataset_name="timdettmers/openassistant-guanaco",
template=(
"gs://cloud-nas-260507-tmp-20240724/openassistant-guanaco.json"
),
validate_percentage_of_dataset=None,
validate_top_k_rows=100,
use_multiprocessing=True,
expected_result=0,
),
dict(
testcase_name="full_gcs_dataset_without_multiprocessing",
dataset_name="gs://cloud-nas-260507-tmp-20240724/model-evaluation/peft_train_sample.jsonl",
template="gs://cloud-nas-260507-tmp-20240724/sample_template.json",
validate_percentage_of_dataset=None,
validate_top_k_rows=None,
use_multiprocessing=False,
expected_result=0,
),
dict(
testcase_name="full_hf_dataset_without_multiprocessing",
dataset_name="timdettmers/openassistant-guanaco",
template=(
"gs://cloud-nas-260507-tmp-20240724/openassistant-guanaco.json"
),
validate_percentage_of_dataset=None,
validate_top_k_rows=None,
use_multiprocessing=False,
expected_result=0,
),
dict(
testcase_name="half_dataset_without_multiprocessing",
dataset_name="timdettmers/openassistant-guanaco",
template=(
"gs://cloud-nas-260507-tmp-20240724/openassistant-guanaco.json"
),
validate_percentage_of_dataset=50,
validate_top_k_rows=None,
use_multiprocessing=False,
expected_result=0,
),
dict(
testcase_name="top_100_rows_without_multiprocessing",
dataset_name="timdettmers/openassistant-guanaco",
template=(
"gs://cloud-nas-260507-tmp-20240724/openassistant-guanaco.json"
),
validate_percentage_of_dataset=None,
validate_top_k_rows=100,
use_multiprocessing=True,
expected_result=0,
),
)
def test_validate_dataset_with_template_multiprocessing_option(
self,
dataset_name,
template,
validate_percentage_of_dataset,
validate_top_k_rows,
use_multiprocessing,
expected_result,
):
self.task_cmd_builder.dataset_name = dataset_name
self.task_cmd_builder.train_split_name = "train"
self.task_cmd_builder.instruct_column_in_dataset = "text"
self.task_cmd_builder.template = template
self.task_cmd_builder.validate_percentage_of_dataset = (
validate_percentage_of_dataset
)
self.task_cmd_builder.validate_k_rows_of_dataset = validate_top_k_rows
self.task_cmd_builder.use_multiprocessing = use_multiprocessing
result = self.run_cmd()
self.assertEqual(result, expected_result)
if __name__ == "__main__":
absltest.main()
@@ -0,0 +1,109 @@
"""Tools to generate CommandBuilder class.
See go/vmg-oss-peft-tests#commandbuilder-class-generation for details.
"""
import argparse
import dataclasses
from typing import List
_DO_NOT_MODIFY_WARNING = """
# DO NOT MODIFY: this file is auto-generated
# See go/vmg-oss-peft-tests#command-builder-genpy
"""
_GETTER_TMPL = """
@property
def {}(self):
return self._{}
"""
_SETTER_TMPL = """
@{}.setter
def {}(self, val: {}):
self._{} = val
"""
_INIT_NAME = """
def __init__(self):"""
_INIT_FIELDS = """
self._{} = None"""
_BUILD_CMD = r"""
def build_cmd(self) -> str:
cmd = []
for k, v in self.__dict__.items():
if v is not None:
cmd.append(f'--{k[1:]}={v}')
return cmd
"""
@dataclasses.dataclass
class FlagInfo:
api_name: str
impl_name: str
arg_type: str
def get_flag_info(line: str) -> FlagInfo:
api_name, impl_name, arg_type = [x.strip() for x in line.split(',')]
return FlagInfo(api_name, impl_name, arg_type)
def gen_getter(info: FlagInfo) -> str:
return _GETTER_TMPL.format(info.api_name, info.impl_name)
def gen_setter(info: FlagInfo) -> str:
return _SETTER_TMPL.format(
info.api_name, info.api_name, info.arg_type, info.impl_name
)
def gen_init(infos: List[FlagInfo]) -> str:
fields = [_INIT_FIELDS.format(i.impl_name) for i in infos]
return ''.join([_INIT_NAME] + fields)
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'--flags_def', required=True, help='file path contain flags definition.'
)
parser.add_argument(
'--generated_file',
required=True,
help='file path to the generated command builder.',
)
parser.add_argument(
'--class_name',
required=True,
help='class name for command build',
)
args = parser.parse_args()
flags_info = []
with open(args.flags_def, 'r') as flags_f:
for line in flags_f:
if not line.startswith('#'):
flags_info.append(get_flag_info(line))
with open(args.generated_file, 'w') as gen_f:
# Disables pylint messages.
# See https://stackoverflow.com/a/43510297
print('# pylint: disable=W,C,R', file=gen_f)
print(_DO_NOT_MODIFY_WARNING, file=gen_f)
print(f'class {args.class_name}:', file=gen_f)
print(gen_init(flags_info), file=gen_f)
for info in flags_info:
print(gen_getter(info), file=gen_f)
print(gen_setter(info), file=gen_f)
print(_BUILD_CMD, file=gen_f)
print(f'file generated at {args.generated_file}')
if __name__ == '__main__':
main()
@@ -0,0 +1,50 @@
# api_name, impl_name, value_type
# eval related and etc.
config_file, config_file, str
task, task, str
pretrained_model_id, pretrained_model_id, str
train_dataset, dataset_name, str
train_split_name, train_split_name, str
template, template, str
instruct_column, instruct_column_in_dataset, str
ckpt_dir, output_dir, str
merged_model_dir, merge_base_and_lora_output_dir, str
logging_dir, logging_output_dir, str
per_device_batch_size, per_device_train_batch_size, int
gradient_accumulation_steps, gradient_accumulation_steps, int
lora_rank, lora_rank, int
lora_alpha, lora_alpha, int
lora_dropout, lora_dropout, float
max_steps, max_steps, int
num_epochs, num_epochs, float
max_seq_length, max_seq_length, int
learning_rate, learning_rate, float
lr_scheduler_type, lr_scheduler_type, str
load_precision, precision_mode, str
train_precision, train_precision, str
gradient_checkpointing, enable_gradient_checkpointing, bool
example_packing, use_example_packing, bool
attn_implementation, attn_implementation, str
optimizer, optimizer, str
warmup_ratio, warmup_ratio, float
report_to, report_to, str
save_steps, save_steps, int
logging_steps, logging_steps, int
huggingface_access_token, huggingface_access_token, str
eval_dataset, eval_dataset_path, str
eval_instruct_column, eval_column, str
eval_template, eval_template, str
eval_split_name, eval_split, str
eval_steps, eval_steps, int
eval_tasks, eval_tasks, str
eval_metric_name, eval_metric_name, str
completion_only, completion_only, bool
max_grad_norm, max_grad_norm, float
logger_level, logger_level, str
benchmark_out_file, benchmark_out_file, str
tuning_data_stats_file, tuning_data_stats_file, str
enable_peft, enable_peft, bool
merge_model_precision_mode, merge_model_precision_mode, str
target_modules, target_modules, str
@@ -0,0 +1,15 @@
# api_name, impl_name, value_type
task, task, str
pretrained_model_id, pretrained_model_id, str
quantization_method, quantization_method, str
quantization_precision_mode, quantization_precision_mode, str
quantization_dataset_name, quantization_dataset_name, str
text_column_in_quantization_dataset, text_column_in_quantization_dataset, str
quantization_output_dir, quantization_output_dir, str
device_map, device_map, str
max_memory, max_memory, str
group_size, group_size, int
desc_act, desc_act, bool
damp_percent, damp_percent, float
cache_examples_on_gpu, cache_examples_on_gpu, bool
awq_version, awq_version, str
@@ -0,0 +1,9 @@
# api_name, impl_name, value_type
task, task, str
template, template, str
dataset_name, dataset_name, str
train_split_name, train_split_name, str
instruct_column_in_dataset, instruct_column_in_dataset, str
use_multiprocessing, use_multiprocessing, bool
validate_k_rows_of_dataset, validate_k_rows_of_dataset, int
validate_percentage_of_dataset, validate_percentage_of_dataset, int
@@ -0,0 +1,88 @@
# pylint: disable=W,C,R
# DO NOT MODIFY: this file is auto-generated
# See go/vmg-oss-peft-tests#command-builder-genpy
class ValidateDatasetWithTemplateCommandBuilder:
def __init__(self):
self._task = None
self._template = None
self._dataset_name = None
self._train_split_name = None
self._instruct_column_in_dataset = None
self._use_multiprocessing = None
self._validate_k_rows_of_dataset = None
self._validate_percentage_of_dataset = None
@property
def task(self):
return self._task
@task.setter
def task(self, val: str):
self._task = val
@property
def template(self):
return self._template
@template.setter
def template(self, val: str):
self._template = val
@property
def dataset_name(self):
return self._dataset_name
@dataset_name.setter
def dataset_name(self, val: str):
self._dataset_name = val
@property
def train_split_name(self):
return self._train_split_name
@train_split_name.setter
def train_split_name(self, val: str):
self._train_split_name = val
@property
def instruct_column_in_dataset(self):
return self._instruct_column_in_dataset
@instruct_column_in_dataset.setter
def instruct_column_in_dataset(self, val: str):
self._instruct_column_in_dataset = val
@property
def use_multiprocessing(self):
return self._use_multiprocessing
@use_multiprocessing.setter
def use_multiprocessing(self, val: bool):
self._use_multiprocessing = val
@property
def validate_k_rows_of_dataset(self):
return self._validate_k_rows_of_dataset
@validate_k_rows_of_dataset.setter
def validate_k_rows_of_dataset(self, val: int):
self._validate_k_rows_of_dataset = val
@property
def validate_percentage_of_dataset(self):
return self._validate_percentage_of_dataset
@validate_percentage_of_dataset.setter
def validate_percentage_of_dataset(self, val: int):
self._validate_percentage_of_dataset = val
def build_cmd(self) -> str:
cmd = []
for k, v in self.__dict__.items():
if v is not None:
cmd.append(f'--{k[1:]}={v}')
return cmd
@@ -0,0 +1,86 @@
"""Different trainer callbacks for PEFT Trainer."""
import time
from absl import logging
import accelerate
from transformers import TrainingArguments
from transformers.trainer_callback import TrainerCallback
from transformers.trainer_callback import TrainerControl
from transformers.trainer_callback import TrainerState
from vertex_vision_model_garden_peft.train.vmg import utils
class TrainerStatsCallback(TrainerCallback):
"""Trainer callback to report trainer stats."""
def __init__(self, max_seq_length, filename=None):
self._max_seq_length = max_seq_length
self._filename = filename
self._partial_state = accelerate.PartialState()
self._start_time = float('nan')
self._prev_time = float('nan')
self._peak_mem = 0.0
self._avg_throughput = 0.0
def on_step_end(
self,
args: TrainingArguments,
state: TrainerState,
control: TrainerControl,
**kwargs,
):
if self._partial_state.is_main_process:
if state.global_step == 1:
self._prev_time = time.time()
delta_t = float('nan')
else:
cur_time = time.time()
delta_t = cur_time - self._prev_time
self._prev_time = cur_time
self._avg_throughput += (delta_t - self._avg_throughput) / (
state.global_step - 1
)
gpu_stats = utils.gpu_stats()
self._peak_mem = max(gpu_stats.total_mem, self._peak_mem)
logging.info(
'on_step_end: %s, throughput: %.2f s/it',
utils.gpu_stats_str(gpu_stats),
delta_t,
)
def on_train_begin(
self,
args: TrainingArguments,
state: TrainerState,
control: TrainerControl,
**kwargs,
):
if self._partial_state.is_main_process:
self._start_time = time.time()
logging.info('on_train_begin: %s', utils.gpu_stats_str())
def on_train_end(
self,
args: TrainingArguments,
state: TrainerState,
control: TrainerControl,
**kwargs,
):
if self._partial_state.is_main_process:
train_time = time.time() - self._start_time
logging.info(
'training time %.2f s, throughput: %.2f s/it, peak_mem: %.2f GB',
train_time,
self._avg_throughput,
self._peak_mem,
)
if self._filename:
with open(self._filename, 'a') as out_f:
out_f.write(
f'{self._max_seq_length/1024.0:.1f}k | {self._peak_mem:.2f} |'
f' {self._avg_throughput:.2f}\n'
)
@@ -0,0 +1,18 @@
group:
- vertex
task: custom_loglikelihood
dataset_path: json
dataset_name: null
output_type: loglikelihood
training_split: null
validation_split: null
test_split: test
doc_to_text: "Request: {{prompt}}\nResponse:"
doc_to_target: " {{ground_truth}}"
metric_list:
- metric: perplexity
aggregation: perplexity
higher_is_better: false
- metric: acc
aggregation: mean
higher_is_better: true
@@ -0,0 +1,17 @@
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: MULTI_GPU
downcast_bf16: 'no'
enable_cpu_affinity: false
gpu_ids: all
machine_rank: 0
main_training_function: main
mixed_precision: fp16
num_machines: 1
num_processes: 4
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,17 @@
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: MULTI_GPU
downcast_bf16: 'no'
enable_cpu_affinity: false
gpu_ids: all
machine_rank: 0
main_training_function: main
mixed_precision: fp16
num_machines: 1
num_processes: 8
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,17 @@
compute_environment: LOCAL_MACHINE
debug: false
deepspeed_config:
deepspeed_config_file: /diffusers/examples/vertex_vision_model_garden_peft/zero2.json
zero3_init_flag: true
distributed_type: DEEPSPEED
downcast_bf16: 'no'
machine_rank: 0
main_training_function: main
num_machines: 1
num_processes: 4
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,17 @@
compute_environment: LOCAL_MACHINE
debug: false
deepspeed_config:
deepspeed_config_file: /diffusers/examples/vertex_vision_model_garden_peft/zero2.json
zero3_init_flag: true
distributed_type: DEEPSPEED
downcast_bf16: 'no'
machine_rank: 0
main_training_function: main
num_machines: 1
num_processes: 8
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,17 @@
compute_environment: LOCAL_MACHINE
debug: false
deepspeed_config:
deepspeed_config_file: /diffusers/examples/vertex_vision_model_garden_peft/zero3.json
zero3_init_flag: true
distributed_type: DEEPSPEED
downcast_bf16: 'no'
machine_rank: 0
main_training_function: main
num_machines: 1
num_processes: 4
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,17 @@
compute_environment: LOCAL_MACHINE
debug: false
deepspeed_config:
deepspeed_config_file: /diffusers/examples/vertex_vision_model_garden_peft/zero3.json
zero3_init_flag: true
distributed_type: DEEPSPEED
downcast_bf16: 'no'
machine_rank: 0
main_training_function: main
num_machines: 1
num_processes: 8
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,28 @@
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: FSDP
downcast_bf16: 'no'
enable_cpu_affinity: false
fsdp_config:
fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
fsdp_transformer_layer_cls_to_wrap: LlamaDecoderLayer
fsdp_backward_prefetch: NO_PREFETCH
fsdp_cpu_ram_efficient_loading: true
fsdp_forward_prefetch: false
fsdp_offload_params: true
fsdp_sharding_strategy: FULL_SHARD
fsdp_state_dict_type: SHARDED_STATE_DICT
fsdp_sync_module_states: true
fsdp_use_orig_params: false
fsdp_activation_checkpointing: false
main_training_function: main
mixed_precision: bf16
machine_rank: 0
num_machines: 16
num_processes: 128
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,28 @@
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: FSDP
downcast_bf16: 'no'
enable_cpu_affinity: false
fsdp_config:
fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
fsdp_transformer_layer_cls_to_wrap: LlamaDecoderLayer
fsdp_backward_prefetch: NO_PREFETCH
fsdp_cpu_ram_efficient_loading: true
fsdp_forward_prefetch: false
fsdp_offload_params: true
fsdp_sharding_strategy: FULL_SHARD
fsdp_state_dict_type: SHARDED_STATE_DICT
fsdp_sync_module_states: true
fsdp_use_orig_params: false
fsdp_activation_checkpointing: false
main_training_function: main
mixed_precision: bf16
machine_rank: 0
num_machines: 2
num_processes: 16
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,28 @@
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: FSDP
downcast_bf16: 'no'
enable_cpu_affinity: false
fsdp_config:
fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
fsdp_transformer_layer_cls_to_wrap: LlamaDecoderLayer
fsdp_backward_prefetch: NO_PREFETCH
fsdp_cpu_ram_efficient_loading: true
fsdp_forward_prefetch: false
fsdp_offload_params: true
fsdp_sharding_strategy: FULL_SHARD
fsdp_state_dict_type: SHARDED_STATE_DICT
fsdp_sync_module_states: true
fsdp_use_orig_params: false
fsdp_activation_checkpointing: false
main_training_function: main
mixed_precision: bf16
machine_rank: 0
num_machines: 3
num_processes: 24
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,28 @@
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: FSDP
downcast_bf16: 'no'
enable_cpu_affinity: false
fsdp_config:
fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
fsdp_transformer_layer_cls_to_wrap: LlamaDecoderLayer
fsdp_backward_prefetch: NO_PREFETCH
fsdp_cpu_ram_efficient_loading: true
fsdp_forward_prefetch: false
fsdp_offload_params: true
fsdp_sharding_strategy: FULL_SHARD
fsdp_state_dict_type: SHARDED_STATE_DICT
fsdp_sync_module_states: true
fsdp_use_orig_params: false
fsdp_activation_checkpointing: false
main_training_function: main
mixed_precision: bf16
machine_rank: 0
num_machines: 4
num_processes: 32
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,28 @@
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: FSDP
downcast_bf16: 'no'
enable_cpu_affinity: false
fsdp_config:
fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
fsdp_transformer_layer_cls_to_wrap: LlamaDecoderLayer
fsdp_backward_prefetch: NO_PREFETCH
fsdp_cpu_ram_efficient_loading: true
fsdp_forward_prefetch: false
fsdp_offload_params: true
fsdp_sharding_strategy: FULL_SHARD
fsdp_state_dict_type: SHARDED_STATE_DICT
fsdp_sync_module_states: true
fsdp_use_orig_params: false
fsdp_activation_checkpointing: false
main_training_function: main
mixed_precision: bf16
machine_rank: 0
num_machines: 1
num_processes: 8
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,28 @@
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: FSDP
downcast_bf16: 'no'
enable_cpu_affinity: false
fsdp_config:
fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
fsdp_transformer_layer_cls_to_wrap: LlamaDecoderLayer
fsdp_backward_prefetch: NO_PREFETCH
fsdp_cpu_ram_efficient_loading: true
fsdp_forward_prefetch: false
fsdp_offload_params: true
fsdp_sharding_strategy: HYBRID_SHARD
fsdp_state_dict_type: SHARDED_STATE_DICT
fsdp_sync_module_states: true
fsdp_use_orig_params: false
fsdp_activation_checkpointing: false
main_training_function: main
mixed_precision: bf16
machine_rank: 0
num_machines: 2
num_processes: 16
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,28 @@
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: FSDP
downcast_bf16: 'no'
enable_cpu_affinity: false
fsdp_config:
fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
fsdp_transformer_layer_cls_to_wrap: LlamaDecoderLayer
fsdp_backward_prefetch: NO_PREFETCH
fsdp_cpu_ram_efficient_loading: true
fsdp_forward_prefetch: false
fsdp_offload_params: true
fsdp_sharding_strategy: HYBRID_SHARD
fsdp_state_dict_type: SHARDED_STATE_DICT
fsdp_sync_module_states: true
fsdp_use_orig_params: false
fsdp_activation_checkpointing: false
main_training_function: main
mixed_precision: bf16
machine_rank: 0
num_machines: 3
num_processes: 24
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,28 @@
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: FSDP
downcast_bf16: 'no'
enable_cpu_affinity: false
fsdp_config:
fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
fsdp_transformer_layer_cls_to_wrap: LlamaDecoderLayer
fsdp_backward_prefetch: NO_PREFETCH
fsdp_cpu_ram_efficient_loading: true
fsdp_forward_prefetch: false
fsdp_offload_params: true
fsdp_sharding_strategy: HYBRID_SHARD
fsdp_state_dict_type: SHARDED_STATE_DICT
fsdp_sync_module_states: true
fsdp_use_orig_params: false
fsdp_activation_checkpointing: false
main_training_function: main
mixed_precision: bf16
machine_rank: 0
num_machines: 4
num_processes: 32
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
@@ -0,0 +1,24 @@
{
"zero_optimization": {
"stage": 2,
"contiguous_gradients": false,
"overlap_comm": false
},
"bf16": {
"enabled": "auto"
},
"fp16": {
"enabled": "auto",
"auto_cast": false,
"loss_scale": 0,
"initial_scale_power": 32,
"loss_scale_window": 1000,
"hysteresis": 2,
"min_loss_scale": 1
},
"gradient_accumulation_steps": "auto",
"gradient_clipping": "auto",
"train_batch_size": "auto",
"train_micro_batch_size_per_gpu": "auto",
"wall_clock_breakdown": false
}
@@ -0,0 +1,31 @@
{
"zero_optimization": {
"stage": 3,
"overlap_comm": false,
"contiguous_gradients": false,
"sub_group_size": 0,
"reduce_bucket_size": "auto",
"stage3_prefetch_bucket_size": "auto",
"stage3_param_persistence_threshold": "auto",
"stage3_max_live_parameters": 0,
"stage3_max_reuse_distance": 0,
"stage3_gather_16bit_weights_on_model_save": true
},
"bf16": {
"enabled": "auto"
},
"fp16": {
"enabled": "auto",
"auto_cast": false,
"loss_scale": 0,
"initial_scale_power": 32,
"loss_scale_window": 1000,
"hysteresis": 2,
"min_loss_scale": 1
},
"gradient_accumulation_steps": "auto",
"gradient_clipping": "auto",
"train_batch_size": "auto",
"train_micro_batch_size_per_gpu": "auto",
"wall_clock_breakdown": false
}
@@ -0,0 +1,43 @@
# Doc about format of conda environment file
# https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually
name: merge
channels:
- nodefaults
- conda-forge
dependencies:
- _libgcc_mutex=0.1=conda_forge
- _openmp_mutex=4.5=2_gnu
- bzip2=1.0.8=h4bc722e_7
- ca-certificates=2024.7.4=hbcca054_0
- ld_impl_linux-64=2.40=hf3520f5_7
- libffi=3.4.2=h7f98852_5
- libgcc-ng=14.1.0=h77fa898_0
- libgomp=14.1.0=h77fa898_0
- libnsl=2.0.1=hd590300_0
- libsqlite=3.46.0=hde9e2c9_0
- libuuid=2.38.1=h0b41bf4_0
- libxcrypt=4.4.36=hd590300_1
- libzlib=1.3.1=h4ab18f5_1
- ncurses=6.5=h59595ed_0
- openssl=3.3.1=h4bc722e_2
- pip=24.2=pyhd8ed1ab_0
- python=3.10.14=hd12c33a_0_cpython
- readline=8.2=h8228510_1
- setuptools=72.1.0=pyhd8ed1ab_0
- tk=8.6.13=noxft_h4845f30_101
- tzdata=2024a=h0c530f3_0
- wheel=0.44.0=pyhd8ed1ab_0
- xz=5.2.6=h166bdaf_0
- pip:
- --extra-index-url https://download.pytorch.org/whl/cu121
- absl-py==2.1.0
- accelerate==0.33.0 # Needed for fp8
- datasets==2.19.2
- fbgemm-gpu==0.8.0+cu121 # Needed for fp8
- kfp==2.5.0
- peft==0.12.0
- protobuf==3.20.3
- pynvml==11.5.3
- torch==2.4.0+cu121 # Needed for fp8
- transformers==4.43.1
- trl==0.9.6
@@ -0,0 +1,27 @@
# Doc about format of requirement file
# https://pip.pypa.io/en/stable/reference/requirements-file-format
--extra-index-url https://download.pytorch.org/whl/cu118
--extra-index-url https://huggingface.github.io/autogptq-index/whl/cu118/
# keep sorted
accelerate==0.31.0
auto_gptq==0.7.1+cu118
autoawq==0.2.5
bitsandbytes==0.43.2
cloudml-hypertune==0.1.0.dev6
datasets==2.19.2
deepspeed==0.14.4
diffusers==0.25.1
fsspec==2024.3.1
gcsfs==2024.3.1
lm_eval==0.4.3
ninja==1.11.1 # Needed to avoid `ninja 1.11.1.1 is not supported on this platform` error
optimum==1.17.1
peft==0.12.0
pynvml==11.5.3
torch==2.2.2+cu118
torchvision==0.17.2+cu118
transformers==4.43.1
trl==0.9.6
wandb==0.17.1
@@ -0,0 +1,76 @@
# Dockerfile for PEFT Training.
#
# To build:
# docker build -f model_oss/peft/dockerfile/train.Dockerfile . -t ${YOUR_IMAGE_TAG}
#
# To push to gcr:
# docker tag ${YOUR_IMAGE_TAG} gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
# docker push gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
# Picked from https://cloud.google.com/deep-learning-containers/docs/choosing-container#pytorch
FROM us-docker.pkg.dev/deeplearning-platform-release/gcr.io/pytorch-cu121.2-2.py310:m123
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y curl git wget software-properties-common vim libaio-dev && \
apt-get clean && \
rm -rf /var/lib/apt/lists*
# Copy license.
RUN wget https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/LICENSE
# Install libraries.
ENV PIP_ROOT_USER_ACTION=ignore
RUN pip install --upgrade pip
# Prefer to install with requirement file as much as possible for reasons
# described in b/355034754.
COPY model_oss/peft/train/vmg/dockerfile/requirements.txt /tmp/requirements.txt
RUN pip install -r /tmp/requirements.txt
# flash-attn cannot be installed with the requirement file approach above
# because of the `no-build-isolation` requirement.
#
# It is OK to install it after other packages FOR NOW because it only has
# limited dependencies. And there's no concern about it overwriting previously
# installed packages.
# https://github.com/Dao-AILab/flash-attention/blob/v2.6.3/setup.py#L523
RUN pip install flash-attn==2.6.3 --no-build-isolation
# Install `diffusers` library as editable and in root folder (/) on purpose.
RUN git clone --depth 1 --branch v0.25.1 https://github.com/huggingface/diffusers.git
# Remove `diffusers` (NOTE that the dependency libraries are kept).
RUN pip uninstall -y diffusers
# Using `--no-deps` option to make sure previously installed packages are not
# overwritten.
RUN pip install --no-deps -e /diffusers
# Make sure there's no inconsistent pip libraries.
RUN pip check
# Install merge related packages in a separate env.
COPY model_oss/peft/train/vmg/dockerfile/merge_env.yaml /tmp/merge_env.yaml
RUN conda env create -n merge --yes --file /tmp/merge_env.yaml
RUN conda init
# Switch to diffusers examples folder.
WORKDIR /diffusers/examples
RUN mkdir -p ./vertex_vision_model_garden_peft/
COPY model_oss/peft/train/vmg/configs/* ./vertex_vision_model_garden_peft/
# custom `lm_eval` task.
ARG LM_EVAL_DIR=$(python -c 'import site; print(site.getsitepackages()[0])')/lm_eval
RUN mkdir -p $LM_EVAL_DIR/tasks/vertex && \
mv ./vertex_vision_model_garden_peft/custom_loglikelihood.yaml $LM_EVAL_DIR/tasks/vertex/
COPY model_oss/peft/train/vmg/*.py ./vertex_vision_model_garden_peft/train/vmg/
COPY model_oss/peft/train/vmg/templates /diffusers/examples/util/templates
COPY model_oss/util /diffusers/examples/util
COPY model_oss/notebook_util/dataset_validation_util.py /diffusers/examples/util
COPY model_oss/peft/train/tests/*.py ./vertex_vision_model_garden_peft/tests/
RUN chmod a+rwX -R /diffusers/examples/
ENV PYTHONPATH /diffusers/examples/
# Must disable torch XLA, otherwise runtime uses CPU even if GPU exists.
ENV USE_TORCH_XLA 0
ENTRYPOINT ["python3", "./vertex_vision_model_garden_peft/train/vmg/train_entrypoint.py"]
@@ -0,0 +1,183 @@
"""Library for running evaluations during training."""
import dataclasses
from typing import Any, Optional, Type
from absl import logging
import datasets
from lm_eval import evaluator
from lm_eval import tasks
from lm_eval import utils
from lm_eval.api import model as lm_model
from lm_eval.api import registry
from lm_eval.models import huggingface
from peft import peft_model
import transformers
from transformers import trainer
from util import dataset_validation_util
from util import constants
_DESCRIPTION_EVALUATION = "evaluation"
_BUILTIN_EVAL_TASK = "builtin_eval"
@dataclasses.dataclass(frozen=True)
class EvalConfig:
steps: int
tasks: list[str]
per_device_batch_size: int
num_fewshot: Optional[int]
limit: Optional[float]
metric_name: str
tokenize_dataset: bool
dataset_path: str = ""
split: str = "test"
template: str = ""
column: str = constants.DEFAULT_INSTRUCT_COLUMN_IN_DATASET
class PeftCausalLMModel(huggingface.HFLM):
"""PeftCausalLMModel that supports loading an in-memory model."""
AUTO_MODEL_CLASS = transformers.AutoModelForCausalLM
def __init__(
self,
model: peft_model.PeftModelForCausalLM,
tokenizer: transformers.PreTrainedTokenizerBase,
batch_size_per_gpu: int,
):
lm_model.LM.__init__(self)
self._model = model
self.tokenizer = tokenizer
self.vocab_size = tokenizer.vocab_size
tokenizer.pad_token_id = tokenizer.eos_token_id
self._config = model.config
self.batch_size_per_gpu = batch_size_per_gpu
self._device = model.device
self._max_length = None # Will be automatically determined from config.
self._add_special_tokens = (
None # Will be automatically determined from AUTO_MODEL_CLASS.
)
def create_trainer(
cls: Type[transformers.Trainer],
eval_config: Optional[EvalConfig],
tokenizer: Optional[transformers.PreTrainedTokenizerBase],
args: trainer.TrainingArguments,
**kwargs,
) -> transformers.Trainer:
"""Creates a trainer. If eval config is provided, injects evaluation loop."""
if not eval_config:
return cls(args=args, **kwargs)
args.eval_strategy = "steps"
args.eval_steps = eval_config.steps
args.per_device_eval_batch_size = eval_config.per_device_batch_size
kwargs["tokenizer"] = tokenizer
if eval_config.tasks == [_BUILTIN_EVAL_TASK]:
try:
eval_dataset = dataset_validation_util.load_dataset_with_template(
dataset_name=eval_config.dataset_path,
split=eval_config.split,
input_column=eval_config.column,
template=eval_config.template,
tokenizer=tokenizer,
)
if eval_config.limit is not None:
if eval_config.limit >= 1:
limit = int(eval_config.limit)
else:
limit = int(eval_config.limit * len(eval_dataset))
eval_dataset = eval_dataset.select(range(limit))
if eval_config.tokenize_dataset:
eval_dataset = eval_dataset.map(
lambda samples: tokenizer(samples[eval_config.column])
)
kwargs["eval_dataset"] = eval_dataset
except (OSError, ValueError, IndexError) as e:
logging.warning(
"Failed to load eval dataset %s. Evaluation will be skipped.\n%s",
eval_config.dataset_path,
e,
)
del args.evaluation_strategy
del args.eval_steps
del args.per_device_eval_batch_size
return cls(args=args, **kwargs)
class LMEvalTrainer(cls):
"""Trainer with lm_eval injected as the eval library."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
task_names = utils.pattern_match(eval_config.tasks, registry.ALL_TASKS)
logging.info("Selected Eval Tasks: %s", task_names)
task_args = {}
if eval_config.num_fewshot is not None:
task_args["num_fewshot"] = eval_config.num_fewshot
if eval_config.dataset_path:
task_args["dataset_path"] = "json"
task_args["dataset_kwargs"] = {
"data_files": {"test": eval_config.dataset_path},
}
self._eval_task_dict = tasks.get_task_dict(task_names, **task_args)
def evaluation_loop(
self,
dataloader: trainer.DataLoader,
description: str,
prediction_loss_only: Optional[bool] = None,
ignore_keys: Optional[list[str]] = None,
metric_key_prefix: str = "eval",
) -> trainer.EvalLoopOutput:
"""Custom evaluation loop that invokes lm_eval."""
if description.lower() != _DESCRIPTION_EVALUATION:
return super().evaluation_loop(
dataloader,
description,
prediction_loss_only,
ignore_keys,
metric_key_prefix,
)
model = self._wrap_model(self.model, training=False)
lm = PeftCausalLMModel(
model,
self.tokenizer or self.data_collator.tokenizer,
eval_config.per_device_batch_size,
)
results: dict[str, Any] = evaluator.evaluate(
lm=lm,
task_dict=self._eval_task_dict,
limit=eval_config.limit,
)["results"]
metric_name = eval_config.metric_name
# Compute average value if there are multiple tasks.
metric_values: list[float] = []
for result in results.values():
for key, value in result.items():
if key.split(",")[0] == metric_name:
metric_values.append(value)
if not metric_values:
raise ValueError(
f"Metric {metric_name} not found in eval response: {results}"
)
metric_average = sum(metric_values) / len(metric_values)
logging.info("%s value: %f\n%s", metric_name, metric_average, results)
return trainer.EvalLoopOutput(
# Only metrics field is set. Other fields are dummy values.
predictions=None,
label_ids=None,
metrics={f"{metric_key_prefix}_{metric_name}": metric_average},
num_samples=0,
)
# Use empty eval dataset as a placeholder.
return LMEvalTrainer(
args=args, eval_dataset=datasets.Dataset.from_dict({"test": []}), **kwargs
)
@@ -0,0 +1,794 @@
"""Instruct/Chat with LoRA models."""
import dataclasses
import datetime
import json
import os
from typing import Any, Dict, Optional, Sequence
import warnings
from absl import app
from absl import flags
from absl import logging
from accelerate import DistributedType
from accelerate import PartialState
import bitsandbytes as bnb
import hypertune
from peft import get_peft_model
from peft import LoraConfig
import torch
from transformers import AutoModelForCausalLM
from transformers import TrainingArguments
from trl import DataCollatorForCompletionOnlyLM
from trl import SFTTrainer
import wandb
from util import dataset_validation_util
from vertex_vision_model_garden_peft.train.vmg import callbacks
from vertex_vision_model_garden_peft.train.vmg import eval_lib
from vertex_vision_model_garden_peft.train.vmg import utils
from util import constants
from util import fileutils
_PRETRAINED_MODEL_ID = flags.DEFINE_string(
'pretrained_model_id',
None,
'The pretrained model id. Supported models can be causal language modeling'
' models from https://github.com/huggingface/peft/tree/main. Note, there'
' might be different paddings for different models. This tool assumes the'
' pretrained_model_id contains model name, and then choose proper padding'
' methods. e.g. it must contain `llama` for `Llama2 models`.',
required=True,
)
_HUGGINGFACE_ACCESS_TOKEN = flags.DEFINE_string(
'huggingface_access_token',
None,
'The access token for loading huggingface gated models.',
)
_DATASET_NAME = flags.DEFINE_string(
'dataset_name',
None,
'The dataset name in huggingface.',
)
_OUTPUT_DIR = flags.DEFINE_string(
'output_dir',
None,
'The output directory.',
)
_LOGGING_OUTPUT_DIR = flags.DEFINE_string(
'logging_output_dir',
'',
'The logging output directory, which defaults to same as output_dir.',
)
_PRECISION_MODE = flags.DEFINE_enum(
'precision_mode',
constants.PRECISION_MODE_16,
[
constants.PRECISION_MODE_4,
constants.PRECISION_MODE_8,
constants.PRECISION_MODE_16,
constants.PRECISION_MODE_16B,
constants.PRECISION_MODE_32,
],
'Precision to load model weights for finetuning.',
)
_LORA_RANK = flags.DEFINE_integer(
'lora_rank',
16,
'The rank of the update matrices, expressed in int. Lower rank results in'
' smaller update matrices with fewer trainable parameters, referring to'
' https://huggingface.co/docs/peft/conceptual_guides/lora.',
)
_LORA_ALPHA = flags.DEFINE_integer(
'lora_alpha',
32,
'LoRA scaling factor, referring to'
' https://huggingface.co/docs/peft/conceptual_guides/lora.',
)
_LORA_DROPOUT = flags.DEFINE_float(
'lora_dropout',
0.05,
'dropout probability of the LoRA layers, referring to'
' https://huggingface.co/docs/peft/task_guides/token-classification-lora.',
)
_WARMUP_STEPS = flags.DEFINE_integer(
'warmup_steps',
10,
'Number of steps for the warmup in the learning rate scheduler.',
)
_WARMUP_RATIO = flags.DEFINE_float(
'warmup_ratio',
0.03,
'The warmup ratio in the learning rate scheduler.',
)
_WEIGHT_DECAY = flags.DEFINE_float(
'weight_decay',
0.001,
'The weight decay in the learning rate scheduler.',
)
_NUM_EPOCHS = flags.DEFINE_float(
'num_epochs',
None,
'The number of training epochs. Only used for'
' "sequence-classification-lora" with an integer value and for'
' "instruct-lora" with a float value allowed.',
)
_MAX_STEPS = flags.DEFINE_integer(
'max_steps',
None,
'Total number of training steps. Overrides num_epochs if set. Only used for'
' "instruct-lora."',
)
_MAX_SEQ_LENGTH = flags.DEFINE_integer(
'max_seq_length',
512,
'The maximum sequence length.',
)
_LEARNING_RATE = flags.DEFINE_float(
'learning_rate',
2e-4,
'The learning rate after the potential warmup period.',
)
_INSTRUCT_COLUMN_IN_DATASET = flags.DEFINE_string(
'instruct_column_in_dataset',
constants.DEFAULT_INSTRUCT_COLUMN_IN_DATASET,
'The instruct column in dataset.',
)
_REPORT_TO = flags.DEFINE_string(
'report_to',
constants.REPORT_TO_NONE,
'Where logging is reported to, which can be tensorboard or none.',
)
_PER_DEVICE_TRAIN_BATCH_SIZE = flags.DEFINE_integer(
'per_device_train_batch_size',
4,
'The per device train batch size.',
)
_GRADIENT_ACCUMULATION_STEPS = flags.DEFINE_integer(
'gradient_accumulation_steps',
4,
'The gradient accumulation steps.',
)
_ENABLE_GRADIENT_CHECKPOINTING = flags.DEFINE_boolean(
'enable_gradient_checkpointing',
False,
'Whether to enable gradient checkpointing.',
)
_ENABLE_PEFT = flags.DEFINE_boolean(
'enable_peft',
True,
'Whether to enable peft.',
)
_TEMPLATE = flags.DEFINE_string(
'template',
None,
'Template for formatting language model training data. Must be a filename'
' under `templates` folder, without `.json` extension, e.g. `alpaca`, or a'
' Cloud Storage URI to a JSON file.',
)
_OPTIMIZER = flags.DEFINE_string(
'optimizer',
'adamw_torch',
'The optimizer.',
)
_LR_SCHEDULER_TYPE = flags.DEFINE_string(
'lr_scheduler_type',
'cosine',
'The learning rate scheduler type.',
)
_SAVE_STEPS = flags.DEFINE_integer(
'save_steps',
10,
'The save steps.',
)
_LOGGING_STEPS = flags.DEFINE_integer(
'logging_steps',
10,
'The logging steps.',
)
_EVAL_STEPS = flags.DEFINE_integer(
'eval_steps',
10,
'The number of training steps between evaluations.',
)
_TRAIN_SPLIT_NAME = flags.DEFINE_string(
'train_split_name',
'train',
'The train split name.',
)
_EVAL_TASKS = flags.DEFINE_list(
'eval_tasks',
None,
'List of eval task names (can have wildcards) as in'
' https://github.com/EleutherAI/lm-evaluation-harness. Will not run'
' evaluation if not set. Runs the built-in trainer evaluation loop if set'
' to `builtin_eval`.',
)
_EVAL_PER_DEVICE_BATCH_SIZE = flags.DEFINE_integer(
'eval_per_device_batch_size',
1,
'The per device batch size for model evaluation.',
)
_EVAL_NUM_FEWSHOT = flags.DEFINE_integer(
'eval_num_fewshot',
None,
'Run N-shot language model evaluation. Not implemented in `builtin_eval`.',
)
_EVAL_LIMIT = flags.DEFINE_float(
'eval_limit',
None,
'Limit the number of examples per task. If <1, limit is a percentage of the'
' total number of examples.',
)
_EVAL_METRIC_NAME = flags.DEFINE_string(
'eval_metric_name',
'acc',
'The metric name to aggregate during model evaluation.',
)
_EVAL_DATASET_PATH = flags.DEFINE_string(
'eval_dataset_path',
None,
'Overrides the default evaluation dataset path. In `builtin_eval` mode,'
' this can be any Hugging Face dataset name.',
)
# We set the default eval split as `test`, based on observation from
# https://huggingface.co/datasets/timdettmers/openassistant-guanaco/viewer/default/test.
_EVAL_SPLIT = flags.DEFINE_string(
'eval_split',
'test',
'Eval split name in the eval dataset for `builtin_eval`.',
)
_EVAL_TEMPLATE = flags.DEFINE_string(
'eval_template',
None,
'Template for formatting language model evaluation data for `builtin_eval`.'
' Must be a filename under `templates` folder, without `.json` extension,'
' e.g. `alpaca`, or a Cloud Storage URI to a JSON file.',
)
_EVAL_COLUMN = flags.DEFINE_string(
'eval_column',
None,
'Eval column name in the eval dataset for `builtin_eval`.',
)
_TRAIN_PRECISION = flags.DEFINE_enum(
'train_precision',
constants.PRECISION_MODE_16B,
[
constants.PRECISION_MODE_16,
constants.PRECISION_MODE_16B,
constants.PRECISION_MODE_32,
],
'Precision to train the model.',
)
_USE_EXAMPLE_PACKING = flags.DEFINE_boolean(
'use_example_packing',
False,
'Enables example packing during training, which uses '
'`ConstantLengthDataset` under the hood.',
)
_COMPLETION_ONLY = flags.DEFINE_boolean(
'completion_only',
False,
'If set, it uses DataCollatorForCompletionOnlyLM to train the model on the'
' generated prompts only, i.e., masking out the input',
)
_ATTN_IMPLEMENTATION = flags.DEFINE_string(
'attn_implementation',
None,
'Attention implementation, can be `eager`, `sdpa` or `flash_attention_2`',
)
_MAX_GRAD_NORM = flags.DEFINE_float(
'max_grad_norm',
0.3,
'Maximum gradient norm used for gradient clipping',
)
_WARNINGS_FILTER = flags.DEFINE_string(
'warnings_filter',
'ignore',
'Warning filter as defined in '
'https://docs.python.org/3/library/warnings.html#the-warnings-filter',
)
_LOGGER_LEVEL = flags.DEFINE_string(
'logger_level',
'passive',
'logging level passed to TrainingArguments. Note that this is for python'
' logging module, NOT the one from absl',
)
_BENCHMARK_OUT_FILE = flags.DEFINE_string(
'benchmark_out_file', None, 'file path for writing benchmark result'
)
_NCCL_TIMEOUT = flags.DEFINE_integer(
'nccl_timeout', 6000, 'nccl timeout in seconds'
)
_TUNING_DATA_STATS_FILE = flags.DEFINE_string(
'tuning_data_stats_file', None, 'file path for writing tuning data stats.'
)
_TARGET_MODULES = flags.DEFINE_list(
'target_modules', None, 'The names of the modules to apply LoRA adapter to.'
)
@flags.multi_flags_validator(
[
_COMPLETION_ONLY.name,
_USE_EXAMPLE_PACKING.name,
],
message=(
'`use_example_packing=True` does not work with `completion_only=True`'
),
)
def check_example_packing(flags_dict: Dict[str, Any]) -> bool:
"""Check to make sure example packing is enabled properly.
Args:
flags_dict: Dictionary containing flags to check.
Returns:
If `use_example_packing` is set properly.
"""
if (
flags_dict[_COMPLETION_ONLY.name]
and flags_dict[_USE_EXAMPLE_PACKING.name]
):
return False
return True
@flags.multi_flags_validator(
[
_COMPLETION_ONLY.name,
_TEMPLATE.name,
],
message='`template` should be provided if using `completion_only=True`',
)
def check_completion_only(flags_dict: Dict[str, Any]) -> bool:
"""Check to make sure completion_only is enabled properly.
Args:
flags_dict: Dictionary containing flags to check.
Returns:
If `completion_only` is set properly
"""
if flags_dict[_COMPLETION_ONLY.name] and flags_dict[_TEMPLATE.name] is None:
return False
return True
# References:
# Huggingface SFT trainer example:
# https://github.com/huggingface/trl/blob/main/examples/scripts/sft_trainer.py.
# Huggingface sagemaker example:
# https://github.com/huggingface/notebooks/blob/main/sagemaker/28_train_llms_with_qlora/scripts/run_clm.py.
# Copied from https://github.com/artidoro/qlora/blob/main/qlora.py.
def find_all_linear_names(
model: AutoModelForCausalLM, precision_mode: str
) -> list[str]:
"""Finds all linear module names."""
if precision_mode == constants.PRECISION_MODE_4:
cls = bnb.nn.Linear4bit
elif precision_mode == constants.PRECISION_MODE_8:
cls = bnb.nn.Linear8bitLt
else:
cls = torch.nn.Linear
lora_module_names = set()
for name, module in model.named_modules():
if isinstance(module, cls):
names = name.split('.')
lora_module_names.add(names[0] if len(names) == 1 else names[-1])
if 'lm_head' in lora_module_names: # needed for 16-bit
lora_module_names.remove('lm_head')
return list(lora_module_names)
def finetune_instruct(
pretrained_model_id: str,
dataset_name: str,
output_dir: str,
logging_output_dir: str,
lora_rank: int = 64,
lora_alpha: int = 16,
lora_dropout: float = 0.1,
warmup_ratio: int = 0.03,
num_epochs: Optional[float] = None,
max_steps: Optional[int] = None,
warmup_steps: int = 10,
max_seq_length: int = 512,
learning_rate: float = 2e-4,
precision_mode: str = None,
instruct_column_in_dataset: str = constants.DEFAULT_INSTRUCT_COLUMN_IN_DATASET,
per_device_train_batch_size: int = 4,
gradient_accumulation_steps: int = 4,
optim: str = 'paged_adamw_32bit',
weight_decay: float = 0.001,
enable_gradient_checkpointing: bool = False,
enable_peft: bool = True,
template: str = None,
lr_scheduler_type: str = 'constant',
save_steps: int = 10,
logging_steps: int = 10,
train_split_name: str = 'train',
eval_config: Optional[eval_lib.EvalConfig] = None,
report_to: str = constants.REPORT_TO_NONE,
access_token: Optional[str] = None,
train_precision: str = constants.PRECISION_MODE_16B,
use_example_packing: bool = False,
attn_implementation: Optional[str] = None,
max_grad_norm: float = 0.3,
completion_only: bool = False,
logger_level: str = 'passive',
benchmark_out_file: Optional[str] = None,
tuning_data_stats_file: Optional[str] = None,
target_modules: Optional[str] = None,
) -> None:
"""Finetunes instruct."""
logging.info('on entering instruct_lora, %s', utils.gpu_stats_str())
gradient_checkpointing_kwargs = {}
# DDP provides limited support with the reentrant variant of gradient
# checkpoint [1]. Below is an indirect way of checking whether DDP will be
# used. It is "indirect" because there are complex logic under the hood of
# `SFTTrainer` and since those are not public API, they might change as we
# update the library.
if PartialState().distributed_type == DistributedType.MULTI_GPU:
gradient_checkpointing_kwargs['use_reentrant'] = False
tokenizer = utils.load_tokenizer(
pretrained_model_id,
'right',
access_token=access_token,
)
train_dataset = dataset_validation_util.load_dataset_with_template(
dataset_name,
split=train_split_name,
input_column=instruct_column_in_dataset,
template=template,
tokenizer=tokenizer,
)
if tuning_data_stats_file:
with PartialState().main_process_first():
effective_batch_size = (
per_device_train_batch_size
* gradient_accumulation_steps
* PartialState().num_processes
)
logging.info(
'getting tuning data stats with effective batch size %s',
effective_batch_size,
)
train_dataset_stats = utils.get_dataset_stats(
train_dataset,
tokenizer,
instruct_column_in_dataset,
effective_batch_size,
)
logging.info('stats: %s', train_dataset_stats)
tuning_data_stats_file = dataset_validation_util.force_gcs_fuse_path(
tuning_data_stats_file
)
with open(tuning_data_stats_file, 'w') as out_f:
json.dump(dataclasses.asdict(train_dataset_stats), out_f)
model = utils.load_model(
pretrained_model_id=pretrained_model_id,
tokenizer=tokenizer,
precision_mode=precision_mode,
enable_gradient_checkpointing=enable_gradient_checkpointing,
access_token=access_token,
attn_implementation=attn_implementation,
train_precision=train_precision,
)
if enable_peft:
if target_modules is None:
target_modules = find_all_linear_names(
model, precision_mode=precision_mode
)
logging.info('applying lora adapters to modules: %s', target_modules)
peft_config = LoraConfig(
lora_alpha=lora_alpha,
lora_dropout=lora_dropout,
r=lora_rank,
bias='none',
task_type='CAUSAL_LM',
target_modules=target_modules,
)
# If we pass in `peft_config` to SFTTrainer, it does a lot of magic under
# the hood, e.g., calling `prepare_model_for_kbit_training` before calling
# `get_peft_model`, which may revert other changes we did before. That's why
# we are calling `get_peft_model` explicitly here.
model = get_peft_model(model, peft_config)
# This is to work-around mix-precision training. This issue is not fixed as
# of transformers==4.41.2.
# See b/332760883#comment30 for more details.
if precision_mode in (
constants.PRECISION_MODE_16,
constants.PRECISION_MODE_16B,
):
for param in filter(lambda p: p.requires_grad, model.parameters()):
param.data = param.data.to(torch.float32)
if not logging_output_dir:
logging_output_dir = output_dir
# To use singleton PartialState() without re-initializing it. See
# b/357970482#comment3
accelerator_config = {'use_configured_state': True}
training_arguments = TrainingArguments(
report_to=report_to,
output_dir=output_dir,
per_device_train_batch_size=per_device_train_batch_size,
gradient_accumulation_steps=gradient_accumulation_steps,
optim=optim,
save_steps=save_steps,
save_strategy='steps',
save_total_limit=3,
logging_dir=os.path.join(logging_output_dir, 'logs'),
logging_steps=logging_steps,
learning_rate=learning_rate,
fp16=(train_precision == constants.PRECISION_MODE_16),
bf16=(train_precision == constants.PRECISION_MODE_16B),
max_grad_norm=max_grad_norm,
num_train_epochs=num_epochs if num_epochs else -1,
max_steps=max_steps if max_steps else -1,
warmup_ratio=warmup_ratio,
warmup_steps=warmup_steps,
group_by_length=False,
lr_scheduler_type=lr_scheduler_type,
gradient_checkpointing=enable_gradient_checkpointing,
gradient_checkpointing_kwargs=gradient_checkpointing_kwargs,
weight_decay=weight_decay,
log_level=logger_level,
accelerator_config=accelerator_config,
)
trainer_kwargs = {}
if completion_only and template:
template_json = dataset_validation_util.get_template(template_path=template)
instruction_sep = dataset_validation_util.get_instruction_separator(
template_json
)
response_sep = dataset_validation_util.get_response_separator(template_json)
if not response_sep:
raise ValueError(
'`response_separator` must be provided to use'
' `DataCollatorForCompletionOnlyLM`'
)
trainer_kwargs['data_collator'] = DataCollatorForCompletionOnlyLM(
instruction_template=instruction_sep,
response_template=response_sep,
tokenizer=tokenizer,
)
logging.info('using DataCollatorForCompletionOnlyLM')
trainer_stats_callback = callbacks.TrainerStatsCallback(
max_seq_length, benchmark_out_file
)
trainer = eval_lib.create_trainer(
cls=SFTTrainer,
eval_config=eval_config,
model=model,
train_dataset=train_dataset,
dataset_text_field=instruct_column_in_dataset,
max_seq_length=max_seq_length,
tokenizer=tokenizer,
args=training_arguments,
packing=use_example_packing,
callbacks=[trainer_stats_callback],
**trainer_kwargs,
)
# `eval_lib.create_trainer` might modify the training args. Printing here
# should capture what will be used by the trainer.
if PartialState().is_main_process:
logging.info('training args: %s', trainer.args)
if enable_peft:
trainer.model.print_trainable_parameters()
if trainer.is_fsdp_enabled:
logging.info('Trainer running with FSDP.')
elif trainer.is_deepspeed_enabled:
logging.info('Trainer running with DeepSpeed.')
else:
logging.info('Trainer running without parallelism.')
trainer.train()
# Always save the final checkpoint.
final_checkpoint = utils.get_final_checkpoint_path(output_dir)
logging.info('The final checkpoint is: %s.', final_checkpoint)
if trainer.is_fsdp_enabled:
trainer.accelerator.state.fsdp_plugin.set_state_dict_type('FULL_STATE_DICT')
# This method saves the sharded weights like `accelerator.save_state`, see
# https://huggingface.co/docs/accelerate/en/usage_guides/fsdp#saving-and-loading
trainer.save_model(output_dir)
model = trainer.model.cpu() # Avoids GPU OOM
state_dict = trainer.accelerator.get_state_dict(model)
# To aggregate the weights from all the devices, we need to use
# `state_dict=state_dict`.
model.save_pretrained(
final_checkpoint,
state_dict=state_dict,
is_main_process=PartialState().is_main_process,
save_embedding_layers=False, # Only pad token is added. See go/lora-adapter-pad-token #pylint: disable=line-too-long
)
model.cuda() # Move back to GPU to do eval.
else:
trainer.model.save_pretrained(
final_checkpoint,
is_main_process=PartialState().is_main_process,
save_embedding_layers=False, # Only pad token is added. See go/lora-adapter-pad-token #pylint: disable=line-too-long
)
if eval_config is not None and trainer.eval_dataset is not None:
metrics = trainer.evaluate(metric_key_prefix='eval')
# Both `log_metrics` and `save_metrics` are multiple process safe.
# https://github.com/huggingface/transformers/blob/v4.38.2/src/transformers/trainer_pt_utils.py#L911 #pylint: disable=line-too-long
# https://github.com/huggingface/transformers/blob/v4.38.2/src/transformers/trainer_pt_utils.py#L1001 #pylint: disable=line-too-long
trainer.log_metrics('eval', metrics)
trainer.save_metrics('eval', metrics)
if PartialState().is_main_process:
hp_metric = metrics[f'eval_{eval_config.metric_name}']
hpt = hypertune.HyperTune()
hpt.report_hyperparameter_tuning_metric(
hyperparameter_metric_tag=constants.HP_METRIC_TAG,
metric_value=hp_metric,
)
logging.info('Send HP metric: %f to hyperparameter tuning.', hp_metric)
PartialState().wait_for_everyone()
if not enable_peft:
tokenizer.save_pretrained(
final_checkpoint, is_main_process=PartialState().is_main_process
)
def main(unused_argv: Sequence[str]) -> None:
# This needs to be called before any other PartialState() calls.
utils.init_partial_state(
timeout=datetime.timedelta(seconds=_NCCL_TIMEOUT.value)
)
utils.print_library_versions()
warnings.simplefilter(_WARNINGS_FILTER.value)
pretrained_model_id = fileutils.force_gcs_path(_PRETRAINED_MODEL_ID.value)
if dataset_validation_util.is_gcs_path(pretrained_model_id):
pretrained_model_id = dataset_validation_util.download_gcs_uri_to_local(
pretrained_model_id
)
output_dir = utils.GcsOrLocalDirectory(
_OUTPUT_DIR.value, check_empty=True, upload_from_all_nodes=True
)
# GCS Fuse does not sync flushed files if not closed. See b/361771727.
logging_output_dir = fileutils.force_gcs_path(_LOGGING_OUTPUT_DIR.value)
# Creates evaluation config.
if _EVAL_TASKS.value:
eval_config = eval_lib.EvalConfig(
tasks=_EVAL_TASKS.value,
per_device_batch_size=_EVAL_PER_DEVICE_BATCH_SIZE.value,
num_fewshot=_EVAL_NUM_FEWSHOT.value,
limit=_EVAL_LIMIT.value,
metric_name=_EVAL_METRIC_NAME.value,
steps=_EVAL_STEPS.value,
dataset_path=dataset_validation_util.force_gcs_fuse_path(
_EVAL_DATASET_PATH.value
),
split=_EVAL_SPLIT.value,
template=_EVAL_TEMPLATE.value,
column=_EVAL_COLUMN.value,
tokenize_dataset=False,
)
else:
eval_config = None
if _REPORT_TO.value == constants.REPORT_TO_WANDB:
wandb.login()
finetune_instruct(
pretrained_model_id=pretrained_model_id,
dataset_name=_DATASET_NAME.value,
output_dir=output_dir.local_dir,
logging_output_dir=logging_output_dir,
precision_mode=_PRECISION_MODE.value,
lora_rank=_LORA_RANK.value,
lora_alpha=_LORA_ALPHA.value,
lora_dropout=_LORA_DROPOUT.value,
warmup_ratio=_WARMUP_RATIO.value,
num_epochs=_NUM_EPOCHS.value,
warmup_steps=_WARMUP_STEPS.value,
max_steps=_MAX_STEPS.value,
max_seq_length=_MAX_SEQ_LENGTH.value,
learning_rate=_LEARNING_RATE.value,
instruct_column_in_dataset=_INSTRUCT_COLUMN_IN_DATASET.value,
per_device_train_batch_size=_PER_DEVICE_TRAIN_BATCH_SIZE.value,
optim=_OPTIMIZER.value,
weight_decay=_WEIGHT_DECAY.value,
gradient_accumulation_steps=_GRADIENT_ACCUMULATION_STEPS.value,
enable_gradient_checkpointing=_ENABLE_GRADIENT_CHECKPOINTING.value,
enable_peft=_ENABLE_PEFT.value,
template=_TEMPLATE.value,
lr_scheduler_type=_LR_SCHEDULER_TYPE.value,
save_steps=_SAVE_STEPS.value,
logging_steps=_LOGGING_STEPS.value,
train_split_name=_TRAIN_SPLIT_NAME.value,
eval_config=eval_config,
report_to=_REPORT_TO.value,
access_token=_HUGGINGFACE_ACCESS_TOKEN.value,
train_precision=_TRAIN_PRECISION.value,
use_example_packing=_USE_EXAMPLE_PACKING.value,
attn_implementation=_ATTN_IMPLEMENTATION.value,
max_grad_norm=_MAX_GRAD_NORM.value,
completion_only=_COMPLETION_ONLY.value,
logger_level=_LOGGER_LEVEL.value,
benchmark_out_file=_BENCHMARK_OUT_FILE.value,
tuning_data_stats_file=_TUNING_DATA_STATS_FILE.value,
target_modules=_TARGET_MODULES.value,
)
# Frees the model from GPU.
utils.force_gc()
output_dir.upload_to_gcs(skip_if_exists=True)
if __name__ == '__main__':
app.run(main)
@@ -0,0 +1,132 @@
"""Script to merge PEFT adapter with base model."""
from typing import Any, Dict, Sequence
from absl import app
from absl import flags
from util import dataset_validation_util
from vertex_vision_model_garden_peft.train.vmg import utils
from util import constants
from util import fileutils
_PRETRAINED_MODEL_ID = flags.DEFINE_string(
'pretrained_model_id',
None,
'The pretrained model id. Supported models can be causal language modeling'
' models from https://github.com/huggingface/peft/tree/main. Note, there'
' might be different paddings for different models. This tool assumes the'
' pretrained_model_id contains model name, and then choose proper padding'
' methods. e.g. it must contain `llama` for `Llama2 models`.',
required=True,
)
_MERGE_BASE_AND_LORA_OUTPUT_DIR = flags.DEFINE_string(
'merge_base_and_lora_output_dir',
None,
'The directory to store the merged model with the base and lora adapter.',
)
_MERGE_MODEL_PRECISION_MODE = flags.DEFINE_enum(
'merge_model_precision_mode',
constants.PRECISION_MODE_16,
[
constants.PRECISION_MODE_4,
constants.PRECISION_MODE_8,
constants.PRECISION_MODE_FP8,
constants.PRECISION_MODE_16,
constants.PRECISION_MODE_16B,
constants.PRECISION_MODE_32,
],
'Merging model precision mode.',
)
_FINETUNED_LORA_MODEL_DIR = flags.DEFINE_string(
'finetuned_lora_model_dir',
None,
'The directory storing finetuned LoRA model weights.',
)
_RESTRICT_MODEL_UPLOAD_DOCKER_URI = flags.DEFINE_string(
'restrict_model_upload_docker_uri',
'',
'If set, mark output model as only uploadable to Model Registry with the'
' specified Docker URI.',
)
_EXECUTOR_INPUT = flags.DEFINE_string(
'executor_input',
'',
'For internal use. Kubeflow pipeline context when running trainer as part'
' of an internal pipeline.',
)
_HUGGINGFACE_ACCESS_TOKEN = flags.DEFINE_string(
'huggingface_access_token',
None,
'The access token for loading huggingface gated models.',
)
@flags.multi_flags_validator(
[
_PRETRAINED_MODEL_ID.name,
_FINETUNED_LORA_MODEL_DIR.name,
_MERGE_BASE_AND_LORA_OUTPUT_DIR.name,
],
)
def check_merge_lora_model_flags(flags_dict: Dict[str, Any]) -> bool:
"""Check if required flags are set on merge model LoRA task.
Args:
flags_dict: Dictionary containing task and flags to check.
Returns:
If required flags are not None.
"""
return all(map(lambda x: x is not None, flags_dict.values()))
def main(unused_argv: Sequence[str]) -> None:
pretrained_model_id = fileutils.force_gcs_path(_PRETRAINED_MODEL_ID.value)
if dataset_validation_util.is_gcs_path(pretrained_model_id):
pretrained_model_id = dataset_validation_util.download_gcs_uri_to_local(
pretrained_model_id
)
finetuned_lora_model_dir = utils.GcsOrLocalDirectory(
_FINETUNED_LORA_MODEL_DIR.value
)
merge_base_and_lora_output_dir = utils.GcsOrLocalDirectory(
_MERGE_BASE_AND_LORA_OUTPUT_DIR.value
)
utils.merge_causal_language_model_with_lora(
pretrained_model_id=pretrained_model_id,
precision_mode=_MERGE_MODEL_PRECISION_MODE.value,
finetuned_lora_model_dir=finetuned_lora_model_dir.local_dir,
merged_model_output_dir=merge_base_and_lora_output_dir.local_dir,
access_token=_HUGGINGFACE_ACCESS_TOKEN.value,
)
if _RESTRICT_MODEL_UPLOAD_DOCKER_URI.value:
utils.write_first_party_model_metadata(
merge_base_and_lora_output_dir.local_dir,
_RESTRICT_MODEL_UPLOAD_DOCKER_URI.value,
)
if _EXECUTOR_INPUT.value:
utils.write_kfp_outputs(
_EXECUTOR_INPUT.value,
{
'saved_model': _MERGE_BASE_AND_LORA_OUTPUT_DIR.value,
},
)
merge_base_and_lora_output_dir.upload_to_gcs(skip_if_exists=True)
if __name__ == '__main__':
app.run(main)
@@ -0,0 +1,349 @@
"""Quantizes the model."""
import json
import os
from typing import Any, Dict, List, Sequence, Union
from absl import app
from absl import flags
from absl import logging
from auto_gptq import AutoGPTQForCausalLM
from auto_gptq import BaseQuantizeConfig
from awq import AutoAWQForCausalLM
from optimum.gptq.data import get_dataset
from transformers import AutoTokenizer
from util import dataset_validation_util
from vertex_vision_model_garden_peft.train.vmg import utils
from util import constants
_PRETRAINED_MODEL_ID = flags.DEFINE_string(
'pretrained_model_id',
None,
'The pretrained model id. Supported models can be causal language modeling'
' models from https://github.com/huggingface/peft/tree/main. Note, there'
' might be different paddings for different models. This tool assumes the'
' pretrained_model_id contains model name, and then choose proper padding'
' methods. e.g. it must contain `llama` for `Llama2 models`.',
)
_QUANTIZATION_METHOD = flags.DEFINE_enum(
'quantization_method',
None,
[constants.GPTQ, constants.AWQ],
'The quantization method. Choose from ["gtpq", "awq"].',
)
_QUANTIZATION_PRECISION_MODE = flags.DEFINE_enum(
'quantization_precision_mode',
constants.PRECISION_MODE_4,
[
constants.PRECISION_MODE_8,
constants.PRECISION_MODE_4,
constants.PRECISION_MODE_3,
constants.PRECISION_MODE_2,
],
'Quantization precision mode.',
)
_QUANTIZATION_DATASET_NAME = flags.DEFINE_string(
'quantization_dataset_name',
None,
'The dataset used for quantization. You can provide your own dataset in a'
' list of string or just use the original datasets used in GPTQ paper'
' ["wikitext2","c4","c4-new","ptb","ptb-new"] for GPTQ quantization. Using'
" a dataset more appropriate to the model's training can improve"
' quantisation accuracy. Note that the GPTQ dataset is not the same as the'
' dataset used to train the model.',
)
_TEXT_COLUMN_IN_QUANTIZATION_DATASET = flags.DEFINE_string(
'text_column_in_quantization_dataset',
constants.DEFAULT_TEXT_COLUMN_IN_QUANTIZATION_DATASET,
'The text column in quantization dataset.',
)
_QUANTIZATION_OUTPUT_DIR = flags.DEFINE_string(
'quantization_output_dir',
None,
'The directory to store the quantized model.',
)
_QUANTIZATION_DEVICE_MAP = flags.DEFINE_string(
'device_map', None, 'The device map.'
)
_QUANTIZATION_MAX_MEMORY = flags.DEFINE_string(
'max_memory', None, 'The maximum memory.'
)
_GROUP_SIZE = flags.DEFINE_integer(
'group_size',
None,
'The group size to use for quantization. Recommended value is 128 and -1'
' uses per-column quantization. Higher numbers use less VRAM, but have'
' lower quantisation accuracy. "None" is the lowest possible value.',
)
_DESC_ACT = flags.DEFINE_boolean(
'desc_act',
False,
'Whether to quantize columns in order of decreasing activation size.'
' Setting it to False can significantly speed up inference but the'
' perplexity may become slightly worse. Also known as act-order.',
)
_DAMP_PERCENT = flags.DEFINE_float(
'damp_percent',
0.1,
'The percent of the average Hessian diagonal to use for dampening.',
)
_CACHE_EXAMPLES_ON_GPU = flags.DEFINE_boolean(
'cache_examples_on_gpu',
True,
'Whether to cache the examples on GPU. Disabling will reduce VRAM usage,'
' but increase quantization time.',
)
_AWQ_VERSION = flags.DEFINE_enum(
'awq_version',
constants.GEMM,
[constants.GEMM, constants.GEMV],
'The version of the AWQ to use. It determines how matrix multiplication'
' runs under the hood. GEMV is 20% faster than GEMM, only at batch size 1'
' (not good for large contexts). GEMM is much faster than FP16 at batch'
' sizes below 8 (good with large contexts).',
)
@flags.multi_flags_validator(
[
_PRETRAINED_MODEL_ID.name,
_QUANTIZATION_METHOD.name,
_QUANTIZATION_PRECISION_MODE.name,
_QUANTIZATION_DATASET_NAME.name,
_QUANTIZATION_OUTPUT_DIR.name,
],
)
def check_quantization_flags(flags_dict: Dict[str, Any]) -> bool:
"""Check if required flags are set on quantization task.
Args:
flags_dict: Dictionary containing task and flags to check.
Returns:
If required flags are not None.
"""
required_flags = [
_QUANTIZATION_METHOD.name,
_PRETRAINED_MODEL_ID.name,
_QUANTIZATION_PRECISION_MODE.name,
_QUANTIZATION_DATASET_NAME.name,
_QUANTIZATION_OUTPUT_DIR.name,
]
return all(map(lambda x: flags_dict[x] is not None, required_flags))
def quantize_model(
quantization_method: str,
pretrained_model_id: str,
quantization_output_dir: str,
quantization_precision_mode: str = None,
quantization_dataset_name: Union[List[str]] = None,
text_column_in_quantization_dataset: str = constants.DEFAULT_TEXT_COLUMN_IN_QUANTIZATION_DATASET,
group_size: int = None,
desc_act: bool = True,
damp_percent: float = 0.1,
awq_version: str = 'GEMM',
device_map: str = None,
max_memory: Dict[Any, str] = None,
cache_examples_on_gpu: bool = True,
) -> None:
"""Quantizes the model using `quantization_method`."""
if quantization_method == constants.GPTQ:
gptq_quantize_model(
pretrained_model_id=pretrained_model_id,
gptq_output_dir=quantization_output_dir,
gptq_precision_mode=quantization_precision_mode,
gptq_dataset_name=quantization_dataset_name,
group_size=group_size,
desc_act=desc_act,
damp_percent=damp_percent,
cache_examples_on_gpu=cache_examples_on_gpu,
)
elif quantization_method == constants.AWQ:
awq_quantize_model(
pretrained_model_id=pretrained_model_id,
quantization_output_dir=quantization_output_dir,
quantization_precision_mode=quantization_precision_mode,
quantization_dataset_name=quantization_dataset_name,
text_column_in_quantization_dataset=text_column_in_quantization_dataset,
group_size=group_size,
awq_version=awq_version,
device_map=device_map,
max_memory=max_memory,
)
def awq_quantize_model(
pretrained_model_id: str,
quantization_output_dir: str,
quantization_precision_mode: str = None,
quantization_dataset_name: Union[List[str]] = None,
text_column_in_quantization_dataset: str = constants.DEFAULT_TEXT_COLUMN_IN_QUANTIZATION_DATASET,
group_size: int = None,
awq_version: str = 'GEMM',
device_map: str = None,
max_memory: Dict[Any, str] = None,
) -> None:
"""Quantizes the model using AWQ."""
if quantization_precision_mode != constants.PRECISION_MODE_4:
raise ValueError(
f'Invalid precision mode: {quantization_precision_mode} for AWQ. 4bit'
' quantization must be used.'
)
else:
bits = 4
if not group_size:
group_size = 128
if not device_map:
device_map = 'cpu'
if dataset_validation_util.is_gcs_path(quantization_dataset_name):
logging.info('Using custom dataset: %s', quantization_dataset_name)
with open(
dataset_validation_util.force_gcs_fuse_path(quantization_dataset_name),
'r',
) as f:
quantization_dataset = [line.rstrip('\n') for line in f]
else:
quantization_dataset = quantization_dataset_name
quant_config = {
'zero_point': True,
'q_group_size': group_size,
'w_bit': bits,
'version': awq_version,
}
logging.info('Quantization config: %s', quant_config)
model = AutoAWQForCausalLM.from_pretrained(
pretrained_model_id,
trust_remote_code=True,
device_map=device_map,
max_memory=max_memory,
low_cpu_mem_usage=True,
)
tokenizer = AutoTokenizer.from_pretrained(
pretrained_model_id, trust_remote_code=True
)
model.quantize(
tokenizer,
quant_config=quant_config,
calib_data=quantization_dataset,
text_column=text_column_in_quantization_dataset,
)
model.save_quantized(quantization_output_dir)
tokenizer.save_pretrained(quantization_output_dir)
def gptq_quantize_model(
pretrained_model_id: str,
gptq_output_dir: str,
gptq_precision_mode: str = None,
gptq_dataset_name: Union[List[str]] = None,
group_size: int = -1,
desc_act: bool = False,
damp_percent: float = 0.1,
cache_examples_on_gpu: bool = True,
) -> None:
"""Quantizes the model using GPTQ."""
logging.info(
'PYTORCH_CUDA_ALLOC_CONF: %s',
os.environ.get('PYTORCH_CUDA_ALLOC_CONF', ''),
)
if dataset_validation_util.is_gcs_path(gptq_dataset_name):
logging.info('Using custom dataset: %s', gptq_dataset_name)
with open(
dataset_validation_util.force_gcs_fuse_path(gptq_dataset_name), 'r'
) as f:
gptq_dataset = [line.rstrip('\n') for line in f]
else:
gptq_dataset = gptq_dataset_name
if gptq_precision_mode == constants.PRECISION_MODE_8:
bits = 8
elif gptq_precision_mode == constants.PRECISION_MODE_4:
bits = 4
elif gptq_precision_mode == constants.PRECISION_MODE_3:
bits = 3
elif gptq_precision_mode == constants.PRECISION_MODE_2:
bits = 2
else:
raise ValueError(f'Invalid precision mode: {gptq_precision_mode} for GPTQ.')
if not group_size:
group_size = -1
tokenizer = AutoTokenizer.from_pretrained(pretrained_model_id)
gptq_dataset = get_dataset(gptq_dataset, tokenizer)
quantization_config = BaseQuantizeConfig(
bits=bits,
group_size=group_size,
damp_percent=damp_percent,
desc_act=desc_act,
)
logging.info('Quantization config: %s', quantization_config.to_dict())
model = AutoGPTQForCausalLM.from_pretrained(
pretrained_model_id,
quantization_config,
low_cpu_mem_usage=True,
torch_dtype='auto',
trust_remote_code=True,
)
model.quantize(
examples=gptq_dataset,
cache_examples_on_gpu=cache_examples_on_gpu,
)
if utils.should_add_pad_token(pretrained_model_id):
tokenizer.add_special_tokens({'pad_token': '[PAD]'})
model.resize_token_embeddings(len(tokenizer))
model.save_pretrained(gptq_output_dir)
tokenizer.save_pretrained(gptq_output_dir)
def main(unused_argv: Sequence[str]) -> None:
pretrained_model_id = _PRETRAINED_MODEL_ID.value
if dataset_validation_util.is_gcs_path(pretrained_model_id):
pretrained_model_id = dataset_validation_util.download_gcs_uri_to_local(
pretrained_model_id
)
pretrained_model_id = dataset_validation_util.force_gcs_fuse_path(
pretrained_model_id
)
if _QUANTIZATION_MAX_MEMORY.value:
max_memory = json.loads(_QUANTIZATION_MAX_MEMORY.value)
else:
max_memory = None
quantize_model(
quantization_method=_QUANTIZATION_METHOD.value,
pretrained_model_id=pretrained_model_id,
quantization_output_dir=_QUANTIZATION_OUTPUT_DIR.value,
quantization_precision_mode=_QUANTIZATION_PRECISION_MODE.value,
quantization_dataset_name=_QUANTIZATION_DATASET_NAME.value,
text_column_in_quantization_dataset=_TEXT_COLUMN_IN_QUANTIZATION_DATASET.value,
group_size=_GROUP_SIZE.value,
desc_act=_DESC_ACT.value,
damp_percent=_DAMP_PERCENT.value,
awq_version=_AWQ_VERSION.value,
device_map=_QUANTIZATION_DEVICE_MAP.value,
max_memory=max_memory,
cache_examples_on_gpu=_CACHE_EXAMPLES_ON_GPU.value,
)
if __name__ == '__main__':
app.run(main)
@@ -0,0 +1,21 @@
#!/bin/bash
# Run copybara first:
# cloud/ml/applications/vision/model_garden/copybara/run_copybara_local.sh
# Run docker build:
# cloud/ml/applications/vision/model_garden/model_oss/peft/train/vmg/scripts/build_train_docker.sh
set -x
set -e
COPYBARA_DIR="/tmp/train_docker/"
pushd "${COPYBARA_DIR}"
PROJECT="cloud-nas-260507"
IMAGE_TAG="gcr.io/${PROJECT}/pytorch-peft-train:${USER}-test"
docker build -f model_oss/peft/train/vmg/dockerfile/train.Dockerfile . -t "${IMAGE_TAG}"
docker push "${IMAGE_TAG}"
popd
@@ -1,7 +1,9 @@
"""Sequence classification with LoRA models."""
# pylint: disable=g-importing-member
from typing import Sequence
from absl import app
from absl import flags
from datasets import load_dataset
import evaluate
from peft import get_peft_model
@@ -14,6 +16,71 @@ from transformers import AutoModelForSequenceClassification
from transformers import AutoTokenizer
from transformers import get_linear_schedule_with_warmup
from util import dataset_validation_util
_PRETRAINED_MODEL_ID = flags.DEFINE_string(
"pretrained_model_id",
None,
"The pretrained model id. Supported models can be causal language modeling"
" models from https://github.com/huggingface/peft/tree/main. Note, there"
" might be different paddings for different models. This tool assumes the"
" pretrained_model_id contains model name, and then choose proper padding"
" methods. e.g. it must contain `llama` for `Llama2 models`.",
)
_OUTPUT_DIR = flags.DEFINE_string(
"output_dir",
None,
"The output directory.",
)
_DATASET_NAME = flags.DEFINE_string(
"dataset_name",
None,
"The dataset name in huggingface.",
)
_LORA_RANK = flags.DEFINE_integer(
"lora_rank",
16,
"The rank of the update matrices, expressed in int. Lower rank results in"
" smaller update matrices with fewer trainable parameters, referring to"
" https://huggingface.co/docs/peft/conceptual_guides/lora.",
)
_LORA_ALPHA = flags.DEFINE_integer(
"lora_alpha",
32,
"LoRA scaling factor, referring to"
" https://huggingface.co/docs/peft/conceptual_guides/lora.",
)
_LORA_DROPOUT = flags.DEFINE_float(
"lora_dropout",
0.05,
"dropout probability of the LoRA layers, referring to"
" https://huggingface.co/docs/peft/task_guides/token-classification-lora.",
)
_NUM_EPOCHS = flags.DEFINE_integer(
"num_epochs",
None,
"The number of training epochs.",
)
_BATCH_SIZE = flags.DEFINE_integer(
"batch_size",
32,
"The batch size.",
)
_LEARNING_RATE = flags.DEFINE_float(
"learning_rate",
2e-4,
"The learning rate after the potential warmup period.",
)
def finetune_sequence_classification(
pretrained_model_id: str,
@@ -131,3 +198,32 @@ def finetune_sequence_classification(
print(f"epoch {epoch}:", eval_metric)
model.save_pretrained(output_dir)
def main(unused_argv: Sequence[str]) -> None:
if dataset_validation_util.is_gcs_path(_PRETRAINED_MODEL_ID.value):
pretrained_model_id = dataset_validation_util.download_gcs_uri_to_local(
_PRETRAINED_MODEL_ID.value
)
else:
pretrained_model_id = _PRETRAINED_MODEL_ID.value
pretrained_model_path = dataset_validation_util.force_gcs_fuse_path(
pretrained_model_id
)
output_dir = dataset_validation_util.force_gcs_fuse_path(_OUTPUT_DIR.value)
finetune_sequence_classification(
pretrained_model_id=pretrained_model_path,
dataset_name=_DATASET_NAME.value,
output_dir=output_dir,
lora_rank=_LORA_RANK.value,
lora_alpha=_LORA_ALPHA.value,
lora_dropout=_LORA_DROPOUT.value,
num_epochs=int(_NUM_EPOCHS.value),
batch_size=_BATCH_SIZE.value,
learning_rate=_LEARNING_RATE.value,
)
if __name__ == "__main__":
app.run(main)
@@ -0,0 +1,98 @@
# Vertex Model Garden Training Dataset Template
## Overview
Vertex Model Garden training provides templates for streamlined preprocessing of
datasets. Although datasets often have intricate structures, the supported LLM
models accept only flat strings. A template facilitates parsing a dataset and
preprocessing it to be compatible with the model.
When fine-tuning a pretrained model, it is advisable to maintain the same format
as the original training data. A template helps replicate the format, ensuring
consistency and potentially enhancing the fine-tuning process.
Both multi-turn messages and single instruction-response pairs are supported.
Multi-turn messages are accommodated using a more general `chat_template` field,
whereas simple instruction-response pair datasets are supported through the
`prompt_input` field.
A template is a JSON file consisting of string key-value pairs. Refer to the
following for the definitions of the supported fields.
## Template field documentation
**description**: An explanation of the template.
**source**: Information about the origin of the template.
**chat_template**: A
[jinja template](https://jinja.palletsprojects.com/en/3.1.x/templates/) that can
be used to parse a chat dataset. This is the same format as
[HF chat templates](https://huggingface.co/docs/transformers/main/en/chat_templating).
To create a chat_template, use the `messages` variable to be filled with the
sample. The flag `--instruct_column_in_dataset` identifies which column will be
passed to the `messages` variable in the chat_template. This field is mutually
exclusive with `prompt_input` and `prompt_no_input`.
**prompt_input**: A string template that is used when value for the input column
exists in the sample. It should be able to be formatted with the
[str.format](https://docs.python.org/3/library/stdtypes.html#str.format) method.
The input column is specified with the flag `--instruct_column_in_dataset`. Used
for instruction dataset. This field is mutually exclusive with `chat_template`.
**prompt_no_input**: A string template that is used when value for the input
column does not exist in the sample. It should be able to be formatted with the
[str.format](https://docs.python.org/3/library/stdtypes.html#str.format) method.
The input column is specified with the flag `--instruct_column_in_dataset`. Used
for instruction dataset. This field is mutually exclusive with `chat_template`.
**instruction_separator**: A unique string used to indicate the start of the
instructions. If not specified, every token after response_separator will be
treated as a response, and every token before the first response_separator will
be treated as instruction.
**response_separator**: A unique string used to indicate the start of the
response. This field is required if `--completion_only` flag is set to `True`.
## Example templates
- See the list of all supported templates [here](https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/main/community-content/vertex_model_garden/model_oss/peft/train/vmg/templates).
- For an example with `chat_template` see the JSON template below.
```
{
"description": "Chat template used by Llama 3.",
"source": "https://huggingface.co/meta-llama/Meta-Llama-3-70B-Instruct/blob/a5a71a7527eac1d651bb145436c72026887fb68e/tokenizer_config.json#L2053",
"chat_template": "{% set loop_messages = messages %}{% for message in loop_messages %}{% set content = '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n\n'+ message['content'] | trim + '<|eot_id|>' %}{% if loop.index0 == 0 %}{% set content = bos_token + content %}{% endif %}{{ content }}{% endfor %}{% if add_generation_prompt %}{{ '<|start_header_id|>assistant<|end_header_id|>\n\n' }}{% endif %}",
"instruction_separator": "<|start_header_id|>user<|end_header_id|>\n\n",
"response_separator": "<|start_header_id|>assistant<|end_header_id|>\n\n"
}
```
- For an example with `prompt_input` see the JSON template below. In this case
the flag `--instruct_column_in_dataset=text` should be set, and there must
be a column named `text` in the dataset.
```
{
"description": "Template for openassistant-guanaco dataset.",
"source": "https://huggingface.co/datasets/timdettmers/openassistant-guanaco",
"prompt_input": "{text}",
"instruction_separator": "### Human:",
"response_separator": "### Assistant:"
}
```
- For an example with `prompt_no_input` see the JSON template below. In this
case the flag `--instruct_column_in_dataset=input` should be set, and there
must be columns named `input` and `instruction` in the dataset.
```
{
"description": "Template used by Alpaca-LoRA.",
"source": "https://github.com/tloen/alpaca-lora/blob/main/templates/alpaca.json",
"prompt_input": "Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.\n\n### Instruction:\n{instruction}\n\n### Input:\n{input}\n\n### Response:\n",
"prompt_no_input": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\n### Instruction:\n{instruction}\n\n### Response:\n",
"response_separator": "### Response:"
}
```
@@ -0,0 +1,7 @@
{
"description": "Template used by Alpaca-LoRA.",
"source": "https://github.com/tloen/alpaca-lora/blob/main/templates/alpaca.json",
"prompt_input": "Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.\n\n### Instruction:\n{instruction}\n\n### Input:\n{input}\n\n### Response:\n",
"prompt_no_input": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\n### Instruction:\n{instruction}\n\n### Response:\n",
"response_separator": "### Response:"
}
@@ -0,0 +1,7 @@
{
"description": "A shorter template to experiment with.",
"source": "https://github.com/tloen/alpaca-lora/blob/main/templates/alpaca_short.json",
"prompt_input": "### Instruction:\n{instruction}\n\n### Input:\n{input}\n\n### Response:\n",
"prompt_no_input": "### Instruction:\n{instruction}\n\n### Response:\n",
"response_separator": "### Response:"
}
@@ -0,0 +1,7 @@
{
"description": "Chat template used by Gemma. 'assistant' role is replaced by 'model'",
"source": "https://huggingface.co/google/gemma-1.1-2b-it/blob/bf4924f313df5166dee1467161e886e55f2eb4d4/tokenizer_config.json#L1507",
"chat_template": "{{ bos_token }}{% if messages[0]['role'] == 'system' %}{{ raise_exception('System role not supported') }}{% endif %}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if (message['role'] == 'assistant') %}{% set role = 'model' %}{% else %}{% set role = message['role'] %}{% endif %}{{ '<start_of_turn>' + role + '\n' + message['content'] | trim + '<end_of_turn>\n' }}{% endfor %}{% if add_generation_prompt %}{{'<start_of_turn>model\n'}}{% endif %}",
"instruction_separator": "<start_of_turn>user\n",
"response_separator": "<start_of_turn>model\n"
}
@@ -0,0 +1,7 @@
{
"description": "Template used by Llama 3, accepting text-bison format.",
"source": "https://cloud.google.com/vertex-ai/generative-ai/docs/models/tune-text-models-supervised#dataset-format",
"prompt_input": "\n\n<|start_header_id|>user<|end_header_id|>\n\n{input_text}<|eot_id|>\n\n<|start_header_id|>assistant<|end_header_id|>\n\n{output_text}<|eot_id|>",
"instruction_separator": "<|start_header_id|>user<|end_header_id|>\n\n",
"response_separator": "<|start_header_id|>assistant<|end_header_id|>\n\n"
}
@@ -0,0 +1,7 @@
{
"description": "Chat template used by Llama 3.",
"source": "https://huggingface.co/meta-llama/Meta-Llama-3-70B-Instruct/blob/a5a71a7527eac1d651bb145436c72026887fb68e/tokenizer_config.json#L2053",
"chat_template": "{% set loop_messages = messages %}{% for message in loop_messages %}{% set content = '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n\n'+ message['content'] | trim + '<|eot_id|>' %}{% if loop.index0 == 0 %}{% set content = bos_token + content %}{% endif %}{{ content }}{% endfor %}{% if add_generation_prompt %}{{ '<|start_header_id|>assistant<|end_header_id|>\n\n' }}{% endif %}",
"instruction_separator": "<|start_header_id|>user<|end_header_id|>\n\n",
"response_separator": "<|start_header_id|>assistant<|end_header_id|>\n\n"
}
@@ -0,0 +1,7 @@
{
"description": "Chat template used by Mistral.",
"source": "https://github.com/OpenAccess-AI-Collective/axolotl/blob/main/src/axolotl/utils/chat_templates.py",
"chat_template": "{{ bos_token }}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if message['role'] == 'user' %}{{ '[INST] ' + message['content'] + ' [/INST]' }}{% elif message['role'] == 'assistant' %}{{ message['content'] + eos_token}}{% else %}{{ raise_exception('Only user and assistant roles are supported!') }}{% endif %}{% endfor %}",
"instruction_separator": "[INST]",
"response_separator": "[/INST]"
}
@@ -0,0 +1,7 @@
{
"description": "Template used by openai completion.",
"source": "https://platform.openai.com/docs/api-reference/fine-tuning/completions-input",
"prompt_input": "\n\n<|start_header_id|>user<|end_header_id|>\n\n{prompt}<|eot_id|>\n\n<|start_header_id|>assistant<|end_header_id|>\n\n{completion}<|eot_id|>",
"instruction_separator": "<|start_header_id|>user<|end_header_id|>\n\n",
"response_separator": "<|start_header_id|>assistant<|end_header_id|>\n\n"
}
@@ -0,0 +1,7 @@
{
"description": "Template for openassistant-guanaco dataset.",
"source": "https://huggingface.co/datasets/timdettmers/openassistant-guanaco",
"prompt_input": "{text}",
"instruction_separator": "### Human:",
"response_separator": "### Assistant:"
}
@@ -0,0 +1,7 @@
{
"description": "Template used for chat based models.",
"source": "https://huggingface.co/meta-llama/Meta-Llama-3-70B-Instruct/blob/a5a71a7527eac1d651bb145436c72026887fb68e/tokenizer_config.json#L2053",
"chat_template": "{% set loop_messages = messages %}{% for message in loop_messages %}{% set content = '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n\n'+ message['content'] | trim + '<|eot_id|>' %}{% if loop.index0 == 0 %}{% set content = bos_token + content %}{% endif %}{{ content }}{% endfor %}{% if add_generation_prompt %}{{ '<|start_header_id|>model<|end_header_id|>\n\n' }}{% endif %}",
"instruction_separator": "<|start_header_id|>user<|end_header_id|>\n\n",
"response_separator": "<|start_header_id|>model<|end_header_id|>\n\n"
}
@@ -0,0 +1,242 @@
"""Entrypoint for peft train docker.
Dispatches to different scripts based on `task` type.
For task type in `_TASK_TO_SCRIPT`, if `--config_file` is specified, the script
will dispatch the call to `accelerate`, which is friendly for multi-GPU
environment. Otherwise, `python3` is used.
"""
import argparse
import json
import os
import subprocess
from typing import List, Optional, Sequence
from absl import app
from absl import flags
from absl import logging
from util import dataset_validation_util
from vertex_vision_model_garden_peft.train.vmg import utils
from util import constants
from util import hypertune_utils
_TEXT_TO_IMAGE_TASKS_SCRIPTS = {
constants.TEXT_TO_IMAGE: 'text_to_image/train_text_to_image.py',
constants.TEXT_TO_IMAGE_LORA: 'text_to_image/train_text_to_image_lora.py',
constants.TEXT_TO_IMAGE_DREAMBOOTH: 'dreambooth/train_dreambooth.py',
constants.TEXT_TO_IMAGE_DREAMBOOTH_LORA: (
'dreambooth/train_dreambooth_lora.py'
),
constants.TEXT_TO_IMAGE_DREAMBOOTH_LORA_SDXL: (
'dreambooth/train_dreambooth_lora_sdxl.py'
),
}
_TASK_TO_SCRIPT = {
constants.INSTRUCT_LORA: (
'vertex_vision_model_garden_peft/train/vmg/instruct_lora.py'
),
constants.MERGE_CAUSAL_LANGUAGE_MODEL_LORA: 'vertex_vision_model_garden_peft/train/vmg/merge_causal_language_model_lora.py',
constants.QUANTIZE_MODEL: (
'vertex_vision_model_garden_peft/train/vmg/quantize_model.py'
),
constants.SEQUENCE_CLASSIFICATION_LORA: 'vertex_vision_model_garden_peft/train/vmg/sequence_classification_lora.py',
constants.VALIDATE_DATASET_WITH_TEMPLATE: 'vertex_vision_model_garden_peft/train/vmg/validate_dataset_with_template.py',
}
def launch_script_cmd(
script: str,
config_file: Optional[str],
accelerate_args: argparse.Namespace = argparse.Namespace(),
) -> List[str]:
"""Returns the command to launch the script."""
if config_file:
cmd = [
'accelerate',
'launch',
'--config_file={}'.format(config_file),
]
else:
cmd = ['python3']
_append_args_to_command_in_place(accelerate_args, cmd)
cmd.append(script)
return cmd
def _get_accelerate_args() -> argparse.Namespace:
"""Returns the accelerate args."""
# For the format of the cluster spec, see
# https://cloud.google.com/vertex-ai/docs/training/distributed-training#cluster-spec-format # pylint: disable=line-too-long
cluster_spec = os.getenv('CLUSTER_SPEC', default=None)
if not cluster_spec:
return argparse.Namespace()
logging.info('CLUSTER_SPEC: %s', cluster_spec)
cluster_data = json.loads(cluster_spec)
if (
'workerpool1' not in cluster_data['cluster']
or not cluster_data['cluster']['workerpool1']
):
return argparse.Namespace()
# Get primary node info
primary_node = cluster_data['cluster']['workerpool0'][0]
logging.info('primary node: %s', primary_node)
primary_node_addr, primary_node_port = primary_node.split(':')
logging.info('primary node address: %s', primary_node_addr)
logging.info('primary node port: %s', primary_node_port)
# Determine node rank of this machine
workerpool = cluster_data['task']['type']
if workerpool == 'workerpool0':
node_rank = 0
elif workerpool == 'workerpool1':
# Add 1 for the primary node, since `index` is the index of workerpool1.
node_rank = cluster_data['task']['index'] + 1
else:
raise ValueError(
'Only workerpool0 and workerpool1 are supported. Unknown workerpool:'
f' {workerpool}'
)
logging.info('node rank: %s', node_rank)
# Calculate total nodes
num_worker_nodes = len(cluster_data['cluster']['workerpool1'])
num_nodes = num_worker_nodes + 1 # Add 1 for the primary node
logging.info('num nodes: %s', num_nodes)
accelerate_args = argparse.Namespace()
accelerate_args.machine_rank = node_rank
accelerate_args.num_machines = num_nodes
accelerate_args.main_process_ip = primary_node_addr
accelerate_args.main_process_port = primary_node_port
accelerate_args.max_restarts = 0
accelerate_args.monitor_interval = 120
return accelerate_args
def _append_args_to_command_in_place(
args: argparse.Namespace, command: List[str]
):
for key, value in vars(args).items():
# If not specified, skip.
if value is not None:
command.append(f'--{key}={value}')
def _get_train_cmd_and_maybe_merge_cmd(
task: str, config_file: str, unknown: Sequence[str]
) -> Sequence[Sequence[str]]:
"""Returns the training command and maybe the merge command if applicable."""
# Only populated when multi-node is used.
accelerate_args = _get_accelerate_args()
training_cmd = launch_script_cmd(
_TASK_TO_SCRIPT[task],
config_file,
accelerate_args=accelerate_args,
)
# Training only flag.
train_parser = argparse.ArgumentParser()
train_parser.add_argument('--output_dir', required=True)
training_args, unknown = train_parser.parse_known_args(unknown)
# Checks for `hypertune_utils._ENVIRONMENT_VARIABLE_FOR_TRIAL_ID` env var and
# appends the trial id if it exists.
training_args.output_dir = hypertune_utils.maybe_append_trial_id(
dataset_validation_util.force_gcs_fuse_path(training_args.output_dir)
)
# Merge only flags.
merge_parser = argparse.ArgumentParser()
merge_parser.add_argument('--merge_model_precision_mode')
merge_parser.add_argument('--executor_input')
merge_parser.add_argument('--restrict_model_upload_docker_uri')
merge_parser.add_argument('--merge_base_and_lora_output_dir')
merge_args, unknown = merge_parser.parse_known_args(unknown)
# Common flags shared by merging and training.
common_parser = argparse.ArgumentParser()
common_parser.add_argument('--pretrained_model_id', required=True)
common_parser.add_argument('--huggingface_access_token')
common_args, remaining = common_parser.parse_known_args(unknown)
# Add flags for training.
_append_args_to_command_in_place(training_args, training_cmd)
_append_args_to_command_in_place(common_args, training_cmd)
training_cmd.extend(remaining) # Remaining args are passed to training cmd.
commands = [training_cmd]
# Only the main node runs merging.
if (
merge_args.merge_base_and_lora_output_dir
and getattr(accelerate_args, 'machine_rank', 0) == 0
):
lora_dir = utils.get_final_checkpoint_path(training_args.output_dir)
merge_cmd = [
'WORLD_SIZE=1', # To ignore other nodes in multi-node setting.
'python3',
_TASK_TO_SCRIPT[constants.MERGE_CAUSAL_LANGUAGE_MODEL_LORA],
f'--finetuned_lora_model_dir={lora_dir}',
]
_append_args_to_command_in_place(merge_args, merge_cmd)
_append_args_to_command_in_place(common_args, merge_cmd)
# Run in a conda environment.
conda_run_cmd = [
'/bin/bash',
'-c',
f'conda run -n merge {" ".join(merge_cmd)}',
]
commands.append(conda_run_cmd)
return commands
def main(unused_argv: Sequence[str]) -> None:
parser = argparse.ArgumentParser()
parser.add_argument('--config_file')
parser.add_argument('--task')
args, unknown = parser.parse_known_args()
task = args.task
if task in _TEXT_TO_IMAGE_TASKS_SCRIPTS:
# Setup accelerate config before running trainer.
config_gen_cmd = [
'python',
'-c',
(
'from accelerate.utils import write_basic_config;'
' write_basic_config(mixed_precision="fp16")'
),
]
task_cmd = [
'accelerate',
'launch',
_TEXT_TO_IMAGE_TASKS_SCRIPTS[task],
] + list(map(dataset_validation_util.force_gcs_fuse_path, unknown))
commands = [config_gen_cmd, task_cmd]
elif task in [constants.INSTRUCT_LORA]:
commands = _get_train_cmd_and_maybe_merge_cmd(
task=task, config_file=args.config_file, unknown=unknown
)
else:
assert task in _TASK_TO_SCRIPT
cmd = launch_script_cmd(_TASK_TO_SCRIPT[task], args.config_file)
cmd.extend(unknown)
commands = [cmd]
for cmd in commands:
logging.info('launching task=%s with cmd: \n%s', task, ' \\\n'.join(cmd))
subprocess.run(cmd, check=True)
if __name__ == '__main__':
app.run(main, flags_parser=lambda _args: flags.FLAGS(_args, known_only=True))
@@ -0,0 +1,742 @@
"""Common libraries for PEFT."""
import dataclasses
import datetime
import gc
import multiprocessing as mp
import os
import subprocess
from typing import Any, Dict, Optional, Sequence
from absl import logging
import accelerate
from accelerate import DistributedType
from accelerate import PartialState
from google.protobuf import json_format
from kfp.pipeline_spec import pipeline_spec_pb2
import numpy as np
import peft
from peft import PeftModel
from peft import prepare_model_for_kbit_training
import pynvml
import torch
import transformers
from transformers import AutoModelForCausalLM
from transformers import AutoTokenizer
from transformers import BitsAndBytesConfig
from transformers import FbgemmFp8Config
from transformers.integrations import is_deepspeed_zero3_enabled
import trl
from util import dataset_validation_util
from util import constants
from util import fileutils
_MODELS_REQUIRING_PAD_TOKEN = ("llama", "falcon", "mistral", "mixtral")
_MODELS_REQUIRING_EOS_TOEKN = ("gemma-2b", "gemma-7b")
_LLAMA_3_1_405B_MODEL_ID = "Meta-Llama-3.1-405B"
_LOCAL_MERGED_MODEL_DIR = "/tmp/merged_model"
class GcsOrLocalDirectory(os.PathLike):
"""A class to represent a directory with upload support if GCS path is given.
This class is used to represent a directory. It can be used for a temporary
local directory and for uploading files to the GCS directory later if the
given path is a GCS directory. If the given path is a local directory, a call
to gcs_dir attribute will raise an error. This class has multi-node and
multi-process support with accelerate.
Attributes:
local_dir: The local directory to store the files.
gcs_dir: The path to the GCS directory.
"""
def __init__(
self,
path: str,
check_empty: bool = False,
upload_from_all_nodes: bool = False,
):
"""Initializes the GcsOrLocalDirectory.
Args:
path: The path to the directory.
check_empty: If True, check if the GCS directory is empty. No-op for local
directory.
upload_from_all_nodes: If True, upload the local directory to GCS from all
nodes.
"""
if len(path) > 1:
path = path.rstrip("/")
self._upload_from_all_nodes = upload_from_all_nodes
if path.startswith(constants.GCS_URI_PREFIX) or path.startswith(
constants.GCSFUSE_URI_PREFIX
):
self._is_gcs_path = True
self._local_dir = _get_local_dir_from_gcs_dir(path)
self._gcs_dir = fileutils.force_gcs_path(path)
os.makedirs(self.local_dir, exist_ok=True)
with PartialState().main_process_first():
if (
check_empty
and PartialState().is_main_process
and not _is_gcs_dir_empty(self._gcs_dir)
):
raise ValueError(f"{self._gcs_dir} needs to be empty.")
else:
self._is_gcs_path = False
self._local_dir = path
self._gcs_dir = path
def __fspath__(self) -> str:
return self.local_dir
@property
def local_dir(self) -> str:
return self._local_dir
@property
def gcs_dir(self) -> str:
"""Returns the GCS directory path.
Returns:
The GCS directory path.
Raises:
ValueError: If the path is not a GCS path.
"""
if not self._is_gcs_path:
raise ValueError(f"{self._gcs_dir} is not a GCS path.")
return self._gcs_dir
def upload_to_gcs(
self,
skip_if_exists: bool = True,
force_upload: bool = False,
):
"""Uploads the local directory to GCS."""
if not self._is_gcs_path:
logging.info(
"Not uploading to GCS since %s is not a GCS path.", self.local_dir
)
return
if not os.listdir(self.local_dir):
logging.info("Not uploading to GCS since %s is empty.", self.local_dir)
return
target = os.path.dirname(self.gcs_dir) + "/"
# Avoid race condition uploading the same file from multiple processes.
with PartialState().main_process_first():
if not PartialState().is_local_main_process:
# Non local main processes don't upload.
pass
elif self._upload_from_all_nodes or PartialState().is_main_process:
logging.info("Uploading %s to %s...", self.local_dir, target)
cmd = [
"gsutil",
"-m",
"cp",
"-r",
]
if skip_if_exists:
cmd.append("-n")
if force_upload:
cmd.append("-f")
cmd.extend([self.local_dir, target])
subprocess.check_output(cmd)
logging.info("%s uploaded.", self.local_dir)
def _get_local_dir_from_gcs_dir(path: str) -> str:
return os.path.join(
constants.LOCAL_OUTPUT_DIR,
dataset_validation_util.force_gcs_fuse_path(path)[1:],
)
def _is_gcs_dir_empty(path: str) -> bool:
"""Checks if a GCS directory is empty.
Args:
path: The GCS directory path.
Returns:
True if the directory is empty.
Raises:
subprocess.CalledProcessError: If the gsutil command failure reason is not
because the dir is empty.
"""
path = path.rstrip("/") + "/"
try:
subprocess.check_output(["gsutil", "ls", path], stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
if (
str(e.output, encoding="utf-8")
== "CommandException: One or more URLs matched no objects.\n"
):
return True
else:
logging.info(str(e.output, encoding="utf-8"))
raise
else:
return False
def load_tokenizer(
pretrained_model_id: str,
padding_side: Optional[str] = None,
access_token: Optional[str] = None,
) -> AutoTokenizer:
"""Loads tokenizer based on `pretrained_model_id`."""
tokenizer_kwargs = {}
if should_add_eos_token(pretrained_model_id):
tokenizer_kwargs["add_eos_token"] = True
if padding_side:
tokenizer_kwargs["padding_side"] = padding_side
with PartialState().local_main_process_first():
tokenizer = AutoTokenizer.from_pretrained(
pretrained_model_id,
trust_remote_code=False,
use_fast=True,
token=access_token,
**tokenizer_kwargs,
)
if should_add_pad_token(pretrained_model_id):
tokenizer.add_special_tokens({"pad_token": "[PAD]"})
return tokenizer
def load_model(
pretrained_model_id: str,
tokenizer: AutoTokenizer,
precision_mode: str = None,
enable_gradient_checkpointing: bool = False,
gradient_checkpointing_kwargs: Optional[Dict[str, Any]] = None,
access_token: Optional[str] = None,
attn_implementation: Optional[str] = None,
train_precision: Optional[str] = None,
device_map: Optional[str] = None,
is_training: bool = True,
) -> AutoModelForCausalLM:
"""Loads models from the local dir if specified or from huggingface."""
# The `distributed_type` we got through `PartialState` is incorrect for FSDP.
# And that's why `Accelerator` is used here.
# See b/357138252 for more details.
accelerator = accelerate.Accelerator()
logging.info("using distributed_type %s", accelerator.distributed_type)
if device_map is None:
if accelerator.distributed_type == DistributedType.MULTI_GPU:
# https://github.com/artidoro/qlora/issues/186#issuecomment-1943045599
# and b/342038175.
device_map = {"": accelerator.process_index}
elif accelerator.distributed_type == DistributedType.DEEPSPEED:
# Deepspeed Zero3 does not allow setting device_map.
# https://github.com/huggingface/transformers/blob/v4.38.2/src/transformers/modeling_utils.py#L2941-L2943
device_map = None
elif accelerator.distributed_type == DistributedType.FSDP:
if precision_mode in [
constants.PRECISION_MODE_4,
constants.PRECISION_MODE_8,
]:
device_map = trl.get_kbit_device_map()
else:
device_map = None
elif (
accelerator.distributed_type == DistributedType.NO
and torch.cuda.device_count() > 1
):
# Setting device map to None to avoid using model parallelism (MP) when
# there are multiple GPUs, which can have very inefficient GPU utilization
# (b/342252819). This setting should trigger torch's nn.DataParallel
# instead, which has better GPU utilization.
device_map = None
else:
device_map = "auto"
logging.info("using device_map %s", device_map)
if train_precision == constants.PRECISION_MODE_32:
train_dtype = torch.float32
elif train_precision == constants.PRECISION_MODE_16:
train_dtype = torch.float16
elif train_precision == constants.PRECISION_MODE_16B:
train_dtype = torch.bfloat16
else:
train_dtype = "auto"
quantization_config = None
# Note: use_cache is False when enable gradient checkpointing.
if precision_mode == constants.PRECISION_MODE_32:
torch_dtype = torch.float32
elif precision_mode == constants.PRECISION_MODE_16:
torch_dtype = torch.float16
elif precision_mode == constants.PRECISION_MODE_16B:
torch_dtype = torch.bfloat16
elif precision_mode == constants.PRECISION_MODE_8:
quantization_config = BitsAndBytesConfig(
load_in_8bit=True, int8_threshold=0
)
torch_dtype = train_dtype
elif precision_mode == constants.PRECISION_MODE_4:
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=train_dtype,
)
# `bnb_4bit_quant_storage` must be set when using FSDP.
# https://huggingface.co/docs/bitsandbytes/main/en/fsdp_qlora
if accelerator.distributed_type == DistributedType.FSDP:
quantization_config.bnb_4bit_quant_storage = train_dtype
torch_dtype = train_dtype
else:
raise ValueError(f"Invalid precision mode: {precision_mode}")
logging.info("using torch_type=%s", torch_dtype)
model = AutoModelForCausalLM.from_pretrained(
pretrained_model_id,
use_cache=not enable_gradient_checkpointing,
device_map=device_map,
torch_dtype=torch_dtype,
quantization_config=quantization_config,
trust_remote_code=True,
token=access_token,
attn_implementation=attn_implementation,
)
if precision_mode in (constants.PRECISION_MODE_4, constants.PRECISION_MODE_8):
model = prepare_model_for_kbit_training(
model,
use_gradient_checkpointing=enable_gradient_checkpointing,
gradient_checkpointing_kwargs=gradient_checkpointing_kwargs,
)
if enable_gradient_checkpointing:
model.gradient_checkpointing_enable(
gradient_checkpointing_kwargs=gradient_checkpointing_kwargs
)
# Flash attention only supports fp16 or bf16 [1].
# prepare_model_for_kbit_training will force cast some layers to float32 [2]
#
# [1]: https://github.com/Dao-AILab/flash-attention/issues/882
# [2]: https://github.com/huggingface/peft/blob/v0.10.0/src/peft/utils/other.py#L79-L81 # pylint: disable=line-too-long
if attn_implementation == "flash_attention_2" and precision_mode in (
constants.PRECISION_MODE_4,
constants.PRECISION_MODE_8,
):
for _, param in model.named_parameters():
if param.dtype == torch.float32:
param.data = param.data.to(torch_dtype)
if is_training:
# KV cache is useless during training
# https://stackoverflow.com/a/77408076
model.config.use_cache = False
if should_add_pad_token(pretrained_model_id):
model.resize_token_embeddings(len(tokenizer))
if is_training:
# The following is needed since we added a new token that needs to be
# learned.
# https://github.com/QwenLM/Qwen/issues/405#issuecomment-1751680291
model.enable_input_require_grads()
return model
def _merge_causal_language_model_with_lora_internal(
pretrained_model_id: str,
merge_precision_mode: str,
finetuned_lora_model_dir: str,
merged_model_output_dir: str,
access_token: Optional[str] = None,
) -> None:
"""Internal function to merges the base model with the lora adapter."""
logging.info("loading tokenizer...")
tokenizer = load_tokenizer(pretrained_model_id)
# Note: merging peft adapter requires loading model in 16 bits, so merging
# is done on CPU on purpose in case one GPU cannot hold the base model.
logging.info("loading model %s...", pretrained_model_id)
device_map = "cpu"
model = load_model(
pretrained_model_id=pretrained_model_id,
tokenizer=tokenizer,
precision_mode=merge_precision_mode,
access_token=access_token,
device_map=device_map,
is_training=False,
)
logging.info("loading LoRA model...")
model = PeftModel.from_pretrained(
model, finetuned_lora_model_dir, device_map=device_map
)
logging.info("merging base model with finetuned LoRA model...")
model = model.merge_and_unload()
logging.info("saving model to %s...", merged_model_output_dir)
model.save_pretrained(
merged_model_output_dir,
safe_serialization=False,
is_main_process=PartialState().is_main_process,
)
logging.info("saving tokenizer to %s...", merged_model_output_dir)
tokenizer.save_pretrained(
merged_model_output_dir,
is_main_process=PartialState().is_main_process,
)
def merge_causal_language_model_with_lora_fsdp(
pretrained_model_id: str,
merge_precision_mode: str,
finetuned_lora_model_dir: str,
merged_model_output_dir: str,
access_token: Optional[str] = None,
) -> None:
"""Merges the base model with the lora adapter for FSDP.
Only the main process should call this function.
Args:
pretrained_model_id: Predefined base model name or path to directory
containing model checkpoints.
merge_precision_mode: Precision mode for saving model weights.
finetuned_lora_model_dir: Path to directory containing PEFT-finetuned model
weights.
merged_model_output_dir: Path to directory to save the merged model.
access_token: Access token for accessing the model.
"""
assert PartialState().is_main_process
_merge_causal_language_model_with_lora_internal(
pretrained_model_id=pretrained_model_id,
merge_precision_mode=merge_precision_mode,
finetuned_lora_model_dir=finetuned_lora_model_dir,
merged_model_output_dir=merged_model_output_dir,
access_token=access_token,
)
def merge_causal_language_model_with_lora(
pretrained_model_id: str,
precision_mode: str,
finetuned_lora_model_dir: str,
merged_model_output_dir: str,
access_token: Optional[str] = None,
) -> None:
"""Merges the base model with the lora adapter."""
# Set merge related variables.
if precision_mode == constants.PRECISION_MODE_FP8:
# Merge as FP16. FP8 requires conversion after merge.
merge_precision_mode = constants.PRECISION_MODE_16
local_merged_model_dir = _LOCAL_MERGED_MODEL_DIR
else:
merge_precision_mode = precision_mode
local_merged_model_dir = merged_model_output_dir
if PartialState().is_main_process:
logging.info("Starting merging job...")
# When deepspeed Zero3 is enabled, users are not allowed to specify
# `device_map` when loading the model (even on CPU).
#
# To work-around this, we kick off another process (from the
# is_main_process) and set up the environment to avoid using Deepspeed when
# doing the merging.
if is_deepspeed_zero3_enabled():
ctx = mp.get_context("spawn")
os.environ["ACCELERATE_USE_DEEPSPEED"] = "false"
merge_job = ctx.Process(
target=_merge_causal_language_model_with_lora_internal,
args=(
pretrained_model_id,
merge_precision_mode,
finetuned_lora_model_dir,
local_merged_model_dir,
),
kwargs={
"access_token": access_token,
},
)
merge_job.start()
merge_job.join()
os.environ["ACCELERATE_USE_DEEPSPEED"] = "true"
else:
_merge_causal_language_model_with_lora_internal(
pretrained_model_id=pretrained_model_id,
merge_precision_mode=merge_precision_mode,
finetuned_lora_model_dir=finetuned_lora_model_dir,
merged_model_output_dir=local_merged_model_dir,
access_token=access_token,
)
logging.info("merging job is done")
# Wait for all processes to sync here.
PartialState().wait_for_everyone()
if precision_mode == constants.PRECISION_MODE_FP8:
convert_model_to_fp8(
pretrained_model_name_or_path=pretrained_model_id,
merged_model_output_dir=local_merged_model_dir,
quantized_model_output_dir=merged_model_output_dir,
access_token=access_token,
)
def convert_model_to_fp8(
pretrained_model_name_or_path: str,
merged_model_output_dir: str,
quantized_model_output_dir: str,
access_token: Optional[str] = None,
) -> None:
"""Converts the model to fp8.
Args:
pretrained_model_name_or_path: Original base model name or path.
merged_model_output_dir: Path to directory containing the merged model.
quantized_model_output_dir: Path to directory to save the quantized model.
access_token: Access token for accessing the model.
"""
if PartialState().is_main_process:
quantization_config = FbgemmFp8Config(
modules_to_not_convert=_maybe_get_modules_to_not_convert_by_model_id(
pretrained_model_name_or_path
)
)
quantized_model = AutoModelForCausalLM.from_pretrained(
merged_model_output_dir,
device_map="cpu",
quantization_config=quantization_config,
trust_remote_code=False,
token=access_token,
)
tokenizer = AutoTokenizer.from_pretrained(merged_model_output_dir)
quantized_model.save_pretrained(quantized_model_output_dir)
tokenizer.save_pretrained(quantized_model_output_dir)
PartialState().wait_for_everyone()
@dataclasses.dataclass
class TuningDataStats:
tuning_dataset_example_count: int
total_billable_token_count: int
tuning_step_count: int
def get_dataset_stats(
dataset: Any,
tokenizer: transformers.PreTrainedTokenizer,
column: str,
effective_batch_size: int,
) -> TuningDataStats:
"""Calculates dataset statistics, e.g., total number of tokens."""
tokenized_dataset = dataset.map(lambda x: tokenizer(x[column]))
inputs = tokenized_dataset["input_ids"]
tuning_dataset_example_count = int(len(inputs))
total_billable_token_count = int(np.sum([len(ex) for ex in inputs]))
tuning_step_count = (
tuning_dataset_example_count + effective_batch_size - 1
) // effective_batch_size
return TuningDataStats(
tuning_dataset_example_count,
total_billable_token_count,
tuning_step_count,
)
def force_gc():
"""Collects garbage immediately to release unused CPU/GPU resources."""
gc.collect()
torch.cuda.empty_cache()
def should_add_pad_token(model_id: str) -> bool:
"""Returns whether the model requires adding a special pad token."""
return any(s.lower() in model_id.lower() for s in _MODELS_REQUIRING_PAD_TOKEN)
def should_add_eos_token(model_id: str) -> bool:
"""Returns whether the model requires adding a special eos token."""
return any(m in model_id for m in _MODELS_REQUIRING_EOS_TOEKN)
def write_kfp_outputs(
executor_input: str, output_artifacts: Dict[str, str]
) -> None:
"""Writes KFP outputs given a dict of output artifact names and URIs."""
# Only the main process writes to avoid race condition.
if PartialState().is_main_process:
executor_input = json_format.Parse(
executor_input, pipeline_spec_pb2.ExecutorInput()
)
outputs = executor_input.outputs
# set all artifacts
for name, uri in output_artifacts.items():
artifact_list = outputs.artifacts.get(name)
if not artifact_list or not artifact_list.artifacts:
raise ValueError(f"Artifact name={name} does not exist.")
artifact_list.artifacts[0].uri = uri
# write output file
executor_output = pipeline_spec_pb2.ExecutorOutput(
artifacts=outputs.artifacts
)
os.makedirs(os.path.dirname(outputs.output_file), exist_ok=True)
with open(outputs.output_file, "w") as f:
f.write(json_format.MessageToJson(executor_output, indent=None))
# Wait for the main process to finish before moving on to the next task.
PartialState().wait_for_everyone()
def upload_local_dir_to_gcs(local_dir: str, gcs_path: str):
"""Uploads local dir to GCS."""
if PartialState().is_main_process:
logging.info("uploading %s to %s...", local_dir, gcs_path)
subprocess.check_output([
"gsutil",
"-m",
"cp",
"-r",
local_dir,
gcs_path,
])
logging.info("%s uploaded.", local_dir)
PartialState().wait_for_everyone()
def write_first_party_model_metadata(output_dir: str, docker_uri: str) -> None:
"""Multi-process friendly version of fileutils.write_first_party_model_metadata."""
if PartialState().is_main_process:
fileutils.write_first_party_model_metadata(output_dir, docker_uri)
PartialState().wait_for_everyone()
@dataclasses.dataclass
class GpuStats:
"""Holds information about GPU usage stats.
For memory related, see
https://pytorch.org/docs/stable/notes/cuda.html#cuda-memory-management
"""
# total memory
total_mem: float
# memory occupied.
occupied: float
# memory reserved, but not used.
unused: float
# nvidia-smi usually reports more memory usages than pytorch (for driver,
# kernel and etc). `smi_diff` tracks this difference.
smi_diff: float
# Gpu utilization.
util: float
# Allows unpacking operation like
# total_mem, occupied, unused, smi_diff, util = GpuStats(...)
# See https://stackoverflow.com/a/70753113
def __iter__(self):
return iter(dataclasses.astuple(self))
def gpu_stats() -> GpuStats:
"""Reports GPU memory usage and utilization."""
# See https://pytorch.org/docs/stable/notes/cuda.html#memory-management
bytes_per_gb = 1024.0**3
device = torch.cuda.current_device()
occupied = torch.cuda.memory_allocated(device) / bytes_per_gb
reserved = torch.cuda.memory_reserved(device) / bytes_per_gb
unused = reserved - occupied
def smi_mem(device):
try:
pynvml.nvmlInit()
handle = pynvml.nvmlDeviceGetHandleByIndex(device)
info = pynvml.nvmlDeviceGetMemoryInfo(handle)
return info.used / bytes_per_gb
except pynvml.NVMLError:
return 0.0
mem_used_smi = smi_mem(device)
smi_diff = mem_used_smi - reserved
util = torch.cuda.utilization(device)
return GpuStats(mem_used_smi, occupied, unused, smi_diff, util)
def gpu_stats_str(stats: Optional[GpuStats] = None) -> str:
if stats is None:
stats = gpu_stats()
total, occupied, unused, smi_diff, util = stats
return (
f"GPU memory: {total:.2f}({occupied=:.2f}, {unused=:.2f},"
f" {smi_diff=:.2f}) GB. Utilization: {util:.2f}%"
)
def init_partial_state(
timeout: datetime.timedelta = datetime.timedelta(seconds=600),
) -> None:
"""Initializes the partial state with timeout."""
# This needs to be called before any other PartialState() calls, and
# TrainingArguments needs `use_configured_state`. See b/357970482#comment3
# for more details.
PartialState(timeout=timeout)
def print_library_versions():
if PartialState().is_main_process:
logging.info("======================")
logging.info("library versions")
logging.info("======================")
logging.info("accelerate: %s", accelerate.__version__)
logging.info("peft: %s", peft.__version__)
logging.info("transformers: %s", transformers.__version__)
logging.info("trl: %s", trl.__version__)
PartialState().wait_for_everyone()
def get_final_checkpoint_path(output_dir: str) -> str:
"""Returns the final checkpoint path."""
return os.path.join(output_dir, constants.FINAL_CHECKPOINT_DIRNAME)
def _maybe_get_modules_to_not_convert_by_model_id(
pretrained_model_name_or_path: str,
) -> Optional[Sequence[str]]:
"""Returns the modules to not convert for the model."""
if _LLAMA_3_1_405B_MODEL_ID in pretrained_model_name_or_path:
return _get_llama_3_1_405b_modules_to_not_convert()
else:
return None
def _get_llama_3_1_405b_modules_to_not_convert() -> Sequence[str]:
"""Returns the modules to not convert for Llama 3.1 405B model."""
modules_to_not_convert = ["lm_head"]
for idx in range(126):
for proj_name in ["k_proj", "o_proj", "q_proj", "v_proj"]:
modules_to_not_convert.append(f"model.layers.{idx}.self_attn.{proj_name}")
for proj_name in ["down_proj", "gate_proj", "up_proj"]:
modules_to_not_convert.append(f"model.layers.0.mlp.{proj_name}")
modules_to_not_convert.append(f"model.layers.125.mlp.{proj_name}")
return tuple(modules_to_not_convert)
@@ -0,0 +1,77 @@
"""Validate the dataset with the template."""
from typing import Sequence
from absl import app
from absl import flags
from util import dataset_validation_util
from vertex_vision_model_garden_peft.train.vmg import utils
from util import constants
_DATASET_NAME = flags.DEFINE_string(
'dataset_name',
None,
'The dataset name in huggingface.',
required=True,
)
_TRAIN_SPLIT_NAME = flags.DEFINE_string(
'train_split_name',
'train',
'The train split name.',
)
_INSTRUCT_COLUMN_IN_DATASET = flags.DEFINE_string(
'instruct_column_in_dataset',
constants.DEFAULT_INSTRUCT_COLUMN_IN_DATASET,
'The instruct column in dataset.',
)
_TEMPLATE = flags.DEFINE_string(
'template',
None,
'Template for formatting language model training data. Must be a filename'
' under `templates` folder, without `.json` extension, e.g. `alpaca`, or a'
' Cloud Storage URI to a JSON file.',
required=True,
)
_VALIDATE_PERCENTAGE_OF_DATASET = flags.DEFINE_integer(
'validate_percentage_of_dataset',
None,
'The percentage of the dataset to validate with the template. If set to'
' -1, it loads the full dataset.',
)
_VALIDATE_K_ROWS_OF_DATASET = flags.DEFINE_integer(
'validate_k_rows_of_dataset',
None,
'The top k rows of the dataset to validate with the template. If set to -1,'
' it loads the full dataset.',
)
_USE_MULTIPROCESSING = flags.DEFINE_boolean(
'use_multiprocessing',
False,
'Whether to use multiprocessing for loading the dataset.',
)
def main(unused_argv: Sequence[str]) -> None:
utils.print_library_versions()
dataset_validation_util.validate_dataset_with_template(
dataset_name=_DATASET_NAME.value,
split=_TRAIN_SPLIT_NAME.value,
input_column=_INSTRUCT_COLUMN_IN_DATASET.value,
template=_TEMPLATE.value,
use_multiprocessing=_USE_MULTIPROCESSING.value,
validate_percentage_of_dataset=_VALIDATE_PERCENTAGE_OF_DATASET.value,
validate_k_rows_of_dataset=_VALIDATE_K_ROWS_OF_DATASET.value,
)
if __name__ == '__main__':
app.run(main)
@@ -1,6 +1,6 @@
"""Common utility lib for prediction on images."""
from typing import Any, Dict, List
from typing import Any, Dict, List, Tuple
import numpy as np
from PIL import Image
@@ -10,6 +10,28 @@ import yaml
from util import image_format_converter
def convert_list_to_label_map(
input_list: List[str],
) -> Tuple[Dict[str, Dict[int, str]], List[int]]:
"""Converts a list of labels to a dictionary and numerical encoding.
Args:
input_list: A list of strings representing class labels.
Returns:
A tuple containing:
label_map: A dictionary mapping unique labels to integer indices.
encoded_list: A list of integers corresponding to the labels in the input
list.
"""
unique_labels = set(input_list)
label_map_reverse = {label: idx for idx, label in enumerate(unique_labels)}
label_map = {idx: label for idx, label in enumerate(unique_labels)}
encoded_list = [label_map_reverse[label] for label in input_list]
return {"label_map": label_map}, encoded_list
def get_prediction_instances(image: Image.Image) -> List[Dict[str, Any]]:
"""Gets prediction instances.
@@ -40,14 +62,14 @@ def get_label_map(label_map_yaml_filepath: str) -> Dict[str, Any]:
def get_object_detection_endpoint_predictions(
detection_endpoint: ...,
detector_endpoint: ...,
input_image: np.ndarray,
detection_thresh: float = 0.2,
) -> np.ndarray:
"""Gets endpoint predictions.
Args:
detection_endpoint: image object detection endpoint.
detector_endpoint: image object detection endpoint.
input_image: Input image.
detection_thresh: Detection threshold.
@@ -55,9 +77,10 @@ def get_object_detection_endpoint_predictions(
Object detection predictions from endpoints.
"""
height, width, _ = input_image.shape
predictions = detection_endpoint.predict(
predictions = detector_endpoint.predict(
get_prediction_instances(Image.fromarray(input_image))
).predictions
detection_scores = np.array(predictions[0]["detection_scores"])
detection_classes = np.array(predictions[0]["detection_classes"])
detection_boxes = np.array(
@@ -66,6 +89,29 @@ def get_object_detection_endpoint_predictions(
for b in predictions[0]["detection_boxes"]
]
)
return merge_boxes_and_classes(
detection_scores, detection_boxes, detection_classes, detection_thresh
)
def merge_boxes_and_classes(
detection_scores: np.ndarray,
detection_boxes: np.ndarray,
detection_classes: np.ndarray,
detection_thresh: float = 0.2,
) -> np.ndarray:
"""Merges prediction boxes and classes.
Args:
detection_scores: array of detection scores.
detection_boxes: array of detection boxes.
detection_classes: array of detection classes.
detection_thresh: float indicating the detection threshold.
Returns:
preds_merge_cls: a numpy array containing the detection boxes, scores and
classes.
"""
thresh_indices = [
x for x, val in enumerate(detection_scores) if val > detection_thresh
]
@@ -76,4 +122,5 @@ def get_object_detection_endpoint_predictions(
preds_merge_cls = np.column_stack(
(preds_merge_conf, detection_classes[thresh_indices])
)
return preds_merge_cls
@@ -36,6 +36,12 @@ BEST_CKPT_METRIC_COMP = 'higher'
# Reported hyperparameter tuning metric tag.
HP_METRIC_TAG = 'model_performance'
HP_LOSS_TAG = 'model_loss'
# Reported places.
REPORT_TO_NONE = 'none'
REPORT_TO_WANDB = 'wandb'
REPORT_TO_TENSORBOARD = 'tensorboard'
# HPT trial prefix.
TRIAL_PREFIX = 'trial_'
@@ -45,7 +51,7 @@ ML_USE_TRAINING = 'training'
ML_USE_VALIDATION = 'validation'
ML_USE_TEST = 'test'
# COCO json keys
# COCO json keys.
COCO_JSON_ANNOTATIONS = 'annotations'
COCO_JSON_ANNOTATION_IMAGE_ID = 'image_id'
COCO_JSON_ANNOTATION_CATEGORY_ID = 'category_id'
@@ -60,36 +66,88 @@ COCO_JSON_IMAGE_HEIGHT = 'height'
COCO_JSON_IMAGE_COCO_URL = 'coco_url'
COCO_ANNOTATION_BBOX = 'bbox'
# GCS prefixes
# GCS prefixes.
GCS_URI_PREFIX = 'gs://'
GCSFUSE_URI_PREFIX = '/gcs/'
LOCAL_EVALUATION_RESULT_DIR = '/tmp/evaluation_result_dir'
LOCAL_MODEL_DIR = '/tmp/model_dir'
LOCAL_LORA_DIR = '/tmp/lora_dir'
LOCAL_BASE_MODEL_DIR = '/tmp/base_model_dir'
LOCAL_DATA_DIR = '/tmp/data'
LOCAL_OUTPUT_DIR = '/tmp/output_dir'
LOCAL_PREDICTION_RESULT_DIR = '/tmp/prediction_result_dir'
SHARED_MEM_DIR = '/dev/shm'
# Huggingface files.
HF_MODEL_WEIGHTS_SUFFIX = '.bin'
# PEFT finetuning constants.
TEXT_TO_IMAGE = 'text-to-image'
TEXT_TO_IMAGE_LORA = 'text-to-image-lora'
TEXT_TO_IMAGE_DREAMBOOTH = 'text-to-image-dreambooth'
TEXT_TO_IMAGE_DREAMBOOTH_LORA = 'text-to-image-dreambooth-lora'
TEXT_TO_IMAGE_DREAMBOOTH_LORA_SDXL = 'text-to-image-dreambooth-lora-sdxl'
SEQUENCE_CLASSIFICATION_LORA = 'sequence-classification-lora'
CAUSAL_LANGUAGE_MODELING_LORA = 'causal-language-modeling-lora'
MERGE_CAUSAL_LANGUAGE_MODEL_LORA = 'merge-causal-language-model-lora'
QUANTIZE_MODEL = 'quantize-model'
INSTRUCT_LORA = 'instruct-lora'
CAUSAL_LANGUAGE_MODELING_LORA_TARGET_MODULES = [
"q_proj",
"v_proj",
]
INSTRUCT_LORA_TARGET_MODULES = [
"query_key_value",
"dense",
"dense_h_to_4h",
"dense_4h_to_h",
]
VALIDATE_DATASET_WITH_TEMPLATE = 'validate-dataset-with-template'
DEFAULT_TEXT_COLUMN_IN_DATASET = 'quote'
DEFAULT_TEXT_COLUMN_IN_QUANTIZATION_DATASET = 'text'
DEFAULT_INSTRUCT_COLUMN_IN_DATASET = 'text'
FINAL_CHECKPOINT_DIRNAME = 'checkpoint-final'
# ImageBind inference constants.
FEATURE_EMBEDDING_GENERATION = 'feature-embedding-generation'
ZERO_SHOT_CLASSIFICATION = 'zero-shot-classification'
# Precision modes for loading model weights.
PRECISION_MODE_2 = '2bit'
PRECISION_MODE_3 = '3bit'
PRECISION_MODE_4 = '4bit'
PRECISION_MODE_8 = '8bit'
PRECISION_MODE_FP8 = 'float8' # to use fbgemm_fp8 quantization
PRECISION_MODE_16 = 'float16'
PRECISION_MODE_16B = 'bfloat16'
PRECISION_MODE_32 = 'float32'
# Quantization modes.
GPTQ = 'gptq'
AWQ = 'awq'
# AWQ versions.
GEMM = 'GEMM'
GEMV = 'GEMV'
# Environment variable keys.
PRIVATE_BUCKET_ENV_KEY = 'AIP_PRIVATE_BUCKET_NAME'
# Kfp pipeline constants.
TFVISION_TRAIN_OUTPUT_ARTIFACT_NAME = 'checkpoint_dir'
# Vertex IOD type.
AUTOML = 'AUTOML'
MODEL_GARDEN = 'MODEL_GARDEN'
# LRU Disk Cache constants.
MD5_HASHMAP_FILENAME = 'md5_hashmap.json'
# Prediction request keys.
PREDICT_INSTANCE_KEY = 'instances'
PREDICT_INSTANCE_IMAGE_KEY = 'image'
PREDICT_INSTANCE_POSE_IMAGE_KEY = 'pose_image'
PREDICT_INSTANCE_TEXT_KEY = 'text'
PREDICT_INSTANCE_PROMPT_KEY = 'prompt'
PREDICT_PARAMETERS_KEY = 'parameters'
PREDICT_PARAMETERS_NUM_INFERENCE_STEPS_KEY = 'num_inference_steps'
PREDICT_PARAMETERS_HEIGHT_KEY = 'height'
PREDICT_PARAMETERS_WIDTH_KEY = 'width'
PREDICT_PARAMETERS_GUIDANCE_SCALE_KEY = 'guidance_scale'
PREDICT_PARAMETERS_NEGATIVE_PROMPT_KEY = 'negative_prompt'
PREDICT_PARAMETERS_LORA_ID_KEY = 'lora_id'
PREDICT_PARAMETERS_IGNORE_LORA_CACHE_KEY = 'ignore_lora_cache'
PREDICT_OUTPUT_KEY = 'output'
@@ -1,10 +1,10 @@
"""Fileutil lib to copy files between gcs and local."""
import glob
import fnmatch
import os
import pathlib
import shutil
from typing import Tuple
from typing import List, Optional, Tuple
import uuid
from absl import logging
@@ -13,6 +13,17 @@ from google.cloud import storage
from util import constants
_GCS_CLIENT = None
def _get_gcs_client() -> storage.Client:
"""Gets the default GCS client."""
global _GCS_CLIENT
if _GCS_CLIENT is None:
_GCS_CLIENT = storage.Client()
return _GCS_CLIENT
def generate_tmp_path(extension: str = '') -> str:
"""Generates a temporary file path with UUID.
@@ -36,6 +47,16 @@ def force_gcs_fuse_path(gcs_uri: str) -> str:
return gcs_uri
def force_gcs_path(uri: str) -> str:
"""Converts /gcs/ uris to their gs:// equivalents. No-op for other uris."""
if uri.startswith(constants.GCSFUSE_URI_PREFIX):
return uri.replace(
constants.GCSFUSE_URI_PREFIX, constants.GCS_URI_PREFIX, 1
)
else:
return uri
def download_gcs_file_to_local_dir(gcs_uri: str, local_dir: str):
"""Download a gcs file to a local dir.
@@ -62,15 +83,47 @@ def download_gcs_file_to_local(gcs_uri: str, local_path: str):
raise ValueError(
f'{gcs_uri} is not a GCS path starting with {constants.GCS_URI_PREFIX}.'
)
client = storage.Client()
client = _get_gcs_client()
os.makedirs(os.path.dirname(local_path), exist_ok=True)
with open(local_path, 'wb') as f:
client.download_blob_to_file(gcs_uri, f)
def download_gcs_file_list_to_local(
gcs_uri_list: List[str], local_dir: str
) -> List[str]:
"""Downloads a list of GCS files to a local directory.
Args:
gcs_uri_list: A list of GCS file paths.
local_dir: Local directory in which the GCS files are saved.
Returns:
The local file paths corresponding to the input GCS file paths.
Raises:
ValueError: An input file path is not a GCS path.
"""
local_paths = []
for gcs_uri in gcs_uri_list:
if not is_gcs_path(gcs_uri):
raise ValueError(
f'{gcs_uri} is not a GCS path starting with'
f' {constants.GCS_URI_PREFIX}.'
)
local_path = os.path.join(local_dir, gcs_uri.replace('gs://', ''))
download_gcs_file_to_local(gcs_uri, local_path)
local_paths.append(local_path)
return local_paths
def download_gcs_dir_to_local(
gcs_dir: str, local_dir: str, skip_hf_model_bin: bool = False
):
gcs_dir: str,
local_dir: str,
skip_hf_model_bin: bool = False,
allow_patterns: Optional[List[str]] = None,
log: bool = True,
) -> None:
"""Downloads files in a GCS directory to a local directory.
For example:
@@ -78,16 +131,21 @@ def download_gcs_dir_to_local(
gs://bucket/foo/a -> /tmp/bar/a
gs://bucket/foo/b/c -> /tmp/bar/b/c
Arguments:
Args:
gcs_dir: A string of directory path on GCS.
local_dir: A string of local directory path.
skip_hf_model_bin: True to skip downloading HF model bin files.
allow_patterns: A list of allowed patterns. If provided, only files matching
one or more patterns are downloaded.
log: True to log each downloaded file.
"""
if not is_gcs_path(gcs_dir):
raise ValueError(f'{gcs_dir} is not a GCS path starting with gs://.')
bucket_name = gcs_dir.split('/')[2]
prefix = gcs_dir[len(constants.GCS_URI_PREFIX + bucket_name) :].strip('/')
client = storage.Client()
prefix = (
gcs_dir[len(constants.GCS_URI_PREFIX + bucket_name) :].strip('/') + '/'
)
client = _get_gcs_client()
blobs = client.list_blobs(bucket_name, prefix=prefix)
for blob in blobs:
if blob.name[-1] == '/':
@@ -95,43 +153,63 @@ def download_gcs_dir_to_local(
file_path = blob.name[len(prefix) :].strip('/')
local_file_path = os.path.join(local_dir, file_path)
os.makedirs(os.path.dirname(local_file_path), exist_ok=True)
if allow_patterns and all(
[not fnmatch.fnmatch(file_path, p) for p in allow_patterns]
):
continue
if (
file_path.endswith(constants.HF_MODEL_WEIGHTS_SUFFIX)
and skip_hf_model_bin
):
logging.info('Skip downloading model bin %s', file_path)
if log:
logging.info('Skip downloading model bin %s', file_path)
with open(local_file_path, 'w') as f:
f.write(f'{constants.GCS_URI_PREFIX}{bucket_name}/{prefix}/{file_path}')
f.write(f'{constants.GCS_URI_PREFIX}{bucket_name}/{prefix}{file_path}')
else:
logging.info('Downloading %s to %s', file_path, local_file_path)
if log:
logging.info('Downloading %s to %s', file_path, local_file_path)
blob.download_to_filename(local_file_path)
def _get_relative_paths(base_dir: str) -> List[str]:
"""Gets relative paths of all files in a local base directory."""
path = pathlib.Path(base_dir)
relative_paths = []
for local_file in path.rglob('*'):
if os.path.isfile(local_file):
relative_path = os.path.relpath(local_file, base_dir)
relative_paths.append(relative_path)
return relative_paths
def _upload_local_files_to_gcs(
relative_paths: List[str], local_dir: str, gcs_dir: str
):
"""Uploads local files to gcs."""
bucket_name = gcs_dir.split('/')[2]
blob_dir = '/'.join(gcs_dir.split('/')[3:])
client = _get_gcs_client()
bucket = client.bucket(bucket_name)
for relative_path in relative_paths:
blob = bucket.blob(os.path.join(blob_dir, relative_path))
blob.upload_from_filename(os.path.join(local_dir, relative_path))
def upload_local_dir_to_gcs(local_dir: str, gcs_dir: str):
"""Uploads local dir to gcs.
For example:
upload_local_dir_to_gcs(/tmp/bar, gs://bucket/foo)
gs://bucket/foo/a -> /tmp/bar/a
gs://bucket/foo/b/c -> /tmp/bar/b/c
/tmp/bar/a -> gs://bucket/foo/a
/tmp/bar/b/c -> gs://bucket/foo/b/c
Arguments:
local_dir: A string of local directory path.
gcs_dir: A string of directory path on GCS.
"""
bucket_name = gcs_dir.split('/')[2]
blob_dir = '/'.join(gcs_dir.split('/')[3:])
client = storage.Client()
bucket = client.bucket(bucket_name)
for local_file in glob.glob(local_dir + '/**'):
if os.path.isfile(local_file):
logging.info(
'Uploading %s to %s',
local_file,
os.path.join(constants.GCS_URI_PREFIX, bucket_name, blob_dir),
)
blob = bucket.blob(os.path.join(blob_dir, os.path.basename(local_file)))
blob.upload_from_filename(local_file)
# Relative paths of all files in local_dir.
relative_paths = _get_relative_paths(local_dir)
_upload_local_files_to_gcs(relative_paths, local_dir, gcs_dir)
def upload_file_to_gcs_path(
@@ -155,7 +233,7 @@ def upload_file_to_gcs_path(
if not source_path_obj.exists():
raise RuntimeError(f'Source path does not exist: {source_path}')
storage_client = storage.Client()
storage_client = _get_gcs_client()
source_file_path = source_path
destination_file_uri = destination_uri
logging.info('Uploading "%s" to "%s"', source_file_path, destination_file_uri)
@@ -174,7 +252,9 @@ def is_gcs_path(input_path: str) -> bool:
Returns:
True if the input path is a GCS path, False otherwise.
"""
return input_path.startswith(constants.GCS_URI_PREFIX)
return input_path is not None and input_path.startswith(
constants.GCS_URI_PREFIX
)
def release_text_assets(
@@ -232,13 +312,10 @@ def download_video_from_gcs_to_local(video_file_path: str) -> Tuple[str, str]:
"""
_, local_video_file_name = os.path.split(video_file_path)
file_extension = os.path.splitext(video_file_path)[1]
if file_extension:
remote_video_file_name = local_video_file_name.replace(
file_extension, '_overlay.mp4'
)
else:
remote_video_file_name = local_video_file_name + '_overlay.mp4'
local_file_path = generate_tmp_path(file_extension)
remote_video_file_name = local_video_file_name.replace(
file_extension, '_overlay.mp4'
)
local_file_path = generate_tmp_path(os.path.splitext(video_file_path)[1])
logging.info('Downloading %s to %s...', video_file_path, local_file_path)
download_gcs_file_to_local(video_file_path, local_file_path)
return local_file_path, remote_video_file_name
@@ -254,10 +331,28 @@ def get_output_video_file(video_output_file_path: str) -> str:
str: Local video output file path.
"""
file_extension = os.path.splitext(video_output_file_path)[1]
if file_extension:
out_local_video_file_name = video_output_file_path.replace(
file_extension, '_overlay' + file_extension
)
else:
out_local_video_file_name = video_output_file_path + '_overlay'
out_local_video_file_name = video_output_file_path.replace(
file_extension, '_overlay' + file_extension
)
return out_local_video_file_name
def write_first_party_model_metadata(
output_path: str, required_container_uri: str
) -> None:
"""Write Vertex internal model metadata for first party artifacts."""
model_metadata_fname = 'model_metadata.jsonl'
if len(required_container_uri) > 126:
raise ValueError(f'Docker URI exceeds 126 chars: {required_container_uri}')
payload = '\n{}{}'.format( # serialized proto
chr(len(required_container_uri)),
required_container_uri,
)
os.makedirs(output_path, exist_ok=True)
output_dirs = [output_path]
if output_path.startswith('/gcs'):
# include all parent dirs, except "/", "/gcs"
output_dirs.extend([str(p) for p in pathlib.Path(output_path).parents][:-2])
for output_dir in output_dirs:
with open(os.path.join(output_dir, model_metadata_fname), 'w') as f:
f.write(payload)
@@ -20,3 +20,11 @@ def get_trial_id_from_environment() -> str:
_ENVIRONMENT_VARIABLE_FOR_TRIAL_ID,
)
return os.environ.get(_ENVIRONMENT_VARIABLE_FOR_TRIAL_ID, '0')
def maybe_append_trial_id(path: str) -> str:
"""Appends trial_N to path if running in a Hyperparameter Tuning Job."""
trial_id = os.environ.get(_ENVIRONMENT_VARIABLE_FOR_TRIAL_ID)
if trial_id is None:
return path
return os.path.join(path, f'trial_{trial_id}')
@@ -0,0 +1,41 @@
from kfp.v2 import dsl
@dsl.component(base_image='python:3.8',packages_to_install=['google-cloud-aiplatform==1.36.0'])
def customjob(
project_id: str,
location: str,
staging_bucket: str,
experiment: str,
job_name: str,
script_path: str,
container_uri: str,
machine_type: str,
):
import os
from google.cloud import aiplatform
aiplatform.init(
project=project_id,
location=location,
staging_bucket=staging_bucket,
experiment=experiment,
)
job = aiplatform.CustomJob.from_local_script(
display_name=job_name,
script_path=os.path.join(os.getcwd(), script_path),
container_uri=container_uri,
machine_type=machine_type,
)
job.run()
@dsl.pipeline(name='run-customjob')
def pipeline_customjob():
customjob("990000000009", "us-west1", "gs://staging-bucket/customjob",
"run-experiment", "custom-job", "customjob.py",
"gcr.io/path/to/model_name:latest", "n1-standard-4")
if __name__ == "__main__":
from kfp.v2 import compiler
compiler.Compiler().compile(
pipeline_func=pipeline_customjob,
package_path='customjob.json')
@@ -0,0 +1,43 @@
from kfp.v2 import dsl
@dsl.component(base_image='python:3.8',packages_to_install=['google-cloud-aiplatform==1.36.0'])
def pipelineJob(
project_id: str,
location: str,
display_name: str,
json_file: str,
pipeline_root: str,
):
import os
from google.cloud import aiplatform
aiplatform.init(
project=project_id,
location=location,
)
job = aiplatform.PipelineJob(
display_name=display_name,
template_path=json_file,
pipeline_root=pipeline_root,
enable_caching=False,
).run()
job.delete()
@dsl.pipeline(name='pipelineJobs')
def pipeline_run_jobs():
# 1. create endpoint
pipelineJob("990000000009", "us-west1", "Pipeline-create endpoint",
"create_endpoint.json", "gs://pipeline-root-bucket/pipelines")
# 2. deploy model to endpoint
pipelineJob("990000000009", "us-west1", "Pipeline-deploy model",
"deploy_model.json", "gs://pipeline-root-bucket/pipelines")
if __name__ == "__main__":
from kfp.v2 import compiler
compiler.Compiler().compile(
pipeline_func=pipeline_run_jobs,
package_path='pipelineJobs.json')
+1
View File
@@ -98,6 +98,7 @@
/notebooks/community/model_garden/model_garden_pytorch_text_to_video.ipynb @KCFindstr
/notebooks/community/generative_ai/text_embedding_api_cloud_next_new_models.ipynb @xqr-g
/notebooks/community/generative_ai/text_embedding_api_semantic_search_with_scann.ipynb @henrytansetiawan
/notebooks/community/generative_ai/backoff_and_retry_for_LLMs.ipynb @pemujo
/notebooks/community/bigquery_ml_inference/bq_ml_with_vision_translation_nlp.ipynb @deaconsmith
/notebooks/community/model_garden/model_garden_keras_stable_diffusion.ipynb @genquan9
/notebooks/community/model_garden/model_garden_keras_yolov8.ipynb @@dstnluong-google
@@ -0,0 +1,663 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "63c7b05c4717"
},
"source": [
"<a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/generative_ai/backoff_and_retry_for_LLMs.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"id": "670bbc2007a2"
},
"outputs": [],
"source": [
"# Copyright 2024 Google LLC\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
"# You may obtain a copy of the License at\n",
"#\n",
"# https://www.apache.org/licenses/LICENSE-2.0\n",
"#\n",
"# Unless required by applicable law or agreed to in writing, software\n",
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "890ac0f4e121"
},
"source": [
"# Backoff and retry for LLM\n",
"\n",
"<table align=\"left\">\n",
" <td style=\"text-align: center\">\n",
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/generative_ai/backoff_and_retry_for_LLMs.ipynb\">\n",
" <img src=\"https://cloud.google.com/ml-engine/images/colab-logo-32px.png\" alt=\"Google Colaboratory logo\"><br> Open in Colab\n",
" </a>\n",
" </td>\n",
" <td style=\"text-align: center\">\n",
" <a href=\"https://console.cloud.google.com/vertex-ai/colab/import/https:%2F%2Fraw.githubusercontent.com%2FGoogleCloudPlatform%2Fvertex-ai-samples%2Fmain%2Fnotebooks%2Fcommunity%2Fgenerative_ai%2Fbackoff_and_retry_for_LLMs.ipynb\">\n",
" <img width=\"32px\" src=\"https://cloud.google.com/ml-engine/images/colab-enterprise-logo-32px.png\" alt=\"Google Cloud Colab Enterprise logo\"><br> Open in Colab Enterprise\n",
" </a>\n",
" </td> \n",
" <td style=\"text-align: center\">\n",
" <a href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/generative_ai/backoff_and_retry_for_LLMs.ipynb\">\n",
" <img src=\"https://lh3.googleusercontent.com/UiNooY4LUgW_oTvpsNhPpQzsstV5W8F7rYgxgGBD85cWJoLmrOzhVs_ksK_vgx40SHs7jCqkTkCk=e14-rj-sc0xffffff-h130-w32\" alt=\"Vertex AI logo\"><br> Open in Workbench\n",
" </a>\n",
" </td>\n",
" <td style=\"text-align: center\">\n",
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/generative_ai/backoff_and_retry_for_LLMs.ipynb\">\n",
" <img src=\"https://cloud.google.com/ml-engine/images/github-logo-32px.png\" alt=\"GitHub logo\"><br> View on GitHub\n",
" </a>\n",
" </td>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "81ce710a5836"
},
"source": [
"NOTE: This notebook has been tested in the following environment:\n",
"\n",
"Python version = 3.10"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ff76ac47eb9b"
},
"source": [
"## Overview\n",
"\n",
"This notebook demonstrates how sending large amounts of traffic to Gemini-1.5-Pro can cause \"429 Quota Exceeded Errors\" and how implementing a backoff-and-retry strategy can help complete jobs without interrupting operations.\n",
"\n",
"This notebook provides examples for the blog post: [Don't let resource exhaustion leave your users hanging: A guide to handling 429 errors](https://cloud.google.com/blog/products/ai-machine-learning/learn-how-to-handle-429-resource-exhaustion-errors-in-your-llms?e=48754805)\n",
"\n",
"This tutorial uses the following Google Cloud ML service:\n",
"\n",
"- Vertex LLM SDK\n",
"\n",
"The steps performed include:\n",
"\n",
"- Installation and imports\n",
"- Asynchronously calling the Gemini model\n",
"- Using the Tenacity retry decorator to implement backoff and retry"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "4e3e949c0bdd"
},
"source": [
"### Costs\n",
"\n",
"This tutorial uses billable components of Google Cloud:\n",
"\n",
"* Vertex AI\n",
"\n",
"Learn about [Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing),\n",
"and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n",
"to generate a cost estimate based on your projected usage.\n",
"\n",
"**This notebook sends large amount of tokens to Gemini for inference, reduce the number of attempts or use smaller video to reduce costs.**"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "f0316df526f8"
},
"source": [
"## Get started"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "FyyMdUeAJIVv"
},
"source": [
"## Install Vertex AI SDK for Python and other required packages\n",
"\n",
"Install the following packages required to execute this notebook.\n",
"\n",
"**Remember to restart the runtime after installation.**"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"id": "snBUuUamoJPz"
},
"outputs": [],
"source": [
"!pip install --upgrade --quiet google-cloud-aiplatform tenacity google-cloud-storage"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "WX3CHZitmSJM"
},
"source": [
"### Restart runtime (Colab only)\n",
"To use the newly installed packages, you must restart the runtime on Google Colab."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "f09b4dff629a"
},
"outputs": [],
"source": [
"import sys\n",
"\n",
"if \"google.colab\" in sys.modules:\n",
"\n",
" import IPython\n",
"\n",
" app = IPython.Application.instance()\n",
" app.kernel.do_shutdown(True)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "SbmM4z7FOBpM"
},
"source": [
"<div class=\"alert alert-block alert-warning\">\n",
"<b>⚠️ The kernel is going to restart. Wait until it's finished before continuing to the next step. ⚠️</b>\n",
"</div>\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "sBCra4QMA2wR"
},
"source": [
"### Authenticate your Google Cloud account\n",
"\n",
"Depending on your Jupyter environment, you may have to manually authenticate. Follow the relevant instructions below."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "74ccc9e52986"
},
"source": [
"**1. Vertex AI Workbench**\n",
"* Do nothing as you are already authenticated."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "de775a3773ba"
},
"source": [
"**2. Local JupyterLab instance, uncomment and run:**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "254614fa0c46"
},
"outputs": [],
"source": [
"# ! gcloud auth login"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ef21552ccea8"
},
"source": [
"**3. Colab, uncomment and run:**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "603adbbf0532"
},
"outputs": [],
"source": [
"# import sys\n",
"\n",
"# if \"google.colab\" in sys.modules:\n",
"\n",
"# from google.colab import auth\n",
"\n",
"# auth.authenticate_user()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "dae340cb-0583-4e7e-a562-6817ee4d7f6d"
},
"source": [
"### Import libraries"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"id": "412d00f1-08db-4880-8ced-52a9583757b8"
},
"outputs": [],
"source": [
"import asyncio\n",
"import time\n",
"\n",
"import nest_asyncio\n",
"import vertexai\n",
"\n",
"nest_asyncio.apply()\n",
"from google.cloud import storage\n",
"from tenacity import retry, wait_random_exponential\n",
"from vertexai.generative_models import GenerationConfig, GenerativeModel, Part"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "DF4l8DTdWgPY"
},
"source": [
"### Set Google Cloud project information and initialize Vertex AI SDK for Python\n",
"\n",
"To get started using Vertex AI, you must have an existing Google Cloud project and [enable the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com). Learn more about [setting up a project and a development environment](https://cloud.google.com/vertex-ai/docs/start/cloud-environment)."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"id": "3EdtdqnoldX4"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Updated property [core/project].\n"
]
}
],
"source": [
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}\n",
"DEFAUL_MODEL_NAME = \"gemini-1.5-pro-001\" # @param {type:\"string\"}\n",
"REGION = \"us-central1\" # @param {type:\"string\"}\n",
"\n",
"\n",
"# Initiate Vertex AI\n",
"vertexai.init(project=PROJECT_ID, location=REGION)\n",
"config = GenerationConfig(temperature=0.5, max_output_tokens=512)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "f50f22f3-ec85-463e-b6fe-5c8e6b80b07b"
},
"source": [
"### Helper functions"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"id": "b18a366df00b"
},
"outputs": [],
"source": [
"def get_images_uri_from_bucket(bucket_name, prefix, delimiter=None):\n",
" \"\"\"Lists all the images with extension '.jpg', 'jpeg' or 'png' in the bucket that begin with the prefix (folder).\"\"\"\n",
" storage_client = storage.Client()\n",
" blobs = storage_client.list_blobs(bucket_name, prefix=prefix, delimiter=delimiter)\n",
" images = [\n",
" f\"gs://{bucket_name}/{blob.name}\"\n",
" for blob in blobs\n",
" if blob.name.endswith(tuple([\".jpg\", \"jpeg\", \"png\"]))\n",
" ]\n",
" return images\n",
"\n",
"\n",
"async def async_ask_gemini(contents, model_name=DEFAUL_MODEL_NAME):\n",
" # This basic function calls Gemini asynchronously without a retry logic\n",
" multimodal_model = GenerativeModel(model_name)\n",
" response = await multimodal_model.generate_content_async(\n",
" contents=contents, generation_config=config\n",
" )\n",
" return response.text\n",
"\n",
"\n",
"@retry(wait=wait_random_exponential(multiplier=1, max=60))\n",
"async def retry_async_ask_gemini(contents, model_name=DEFAUL_MODEL_NAME):\n",
" \"\"\"This is the same code as the async_ask_gemini function but implements a retry logic using tenacity decorator.\n",
" wait_random_exponential(multiplier=1, max=60) means that it will\n",
" Retry “Randomly wait up to 2^x * 1 seconds between each retry until the range reaches 60 seconds, then randomly up to 60 seconds afterwards.\n",
" \"\"\"\n",
"\n",
" multimodal_model = GenerativeModel(model_name)\n",
" response = await multimodal_model.generate_content_async(\n",
" contents=contents, generation_config=config\n",
" )\n",
" return response.text\n",
"\n",
"\n",
"async def load_test_gemini(function, model_name, attempts=5):\n",
" failed_attempts = 0\n",
" print(f\"Testing with model: {model_name} and function: {function.__name__}\")\n",
" for i in range(attempts):\n",
" try:\n",
" time_start = time.time()\n",
" get_gemini_responses = [\n",
" function(\n",
" [\n",
" prompt,\n",
" video_part,\n",
" Part.from_uri(image_uri, mime_type=\"image/jpeg\"),\n",
" ],\n",
" model_name=MODEL_NAME,\n",
" )\n",
" for image_uri in images_list\n",
" ]\n",
" async_poems = await asyncio.gather(*get_gemini_responses)\n",
" time_taken = time.time() - time_start\n",
" print(f\"{len(async_poems)} Poems written in {time_taken:.0f} seconds\")\n",
" except Exception as error:\n",
" failed_attempts += 1\n",
" print(\"An error occurred:\", error)\n",
"\n",
" print(\n",
" f\"{failed_attempts} out of {attempts} failed\"\n",
" ) if failed_attempts > 0 else print(f\"All {attempts} attempts succeded\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "c01755042c4b"
},
"source": [
"### Getting images and videos used for testing"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"id": "204228ea941e"
},
"outputs": [],
"source": [
"# The images and video used for this test are stored in a public GCS bucket: \"cloud-samples-data\"\n",
"bucket_name = \"cloud-samples-data\"\n",
"image_prefix = \"generative-ai/image/\"\n",
"images_list = get_images_uri_from_bucket(bucket_name, image_prefix, delimiter=\"/\")\n",
"\n",
"prompt = \"Get the elements from the image, get all the animals from the video, print all the animals and elements found on a numbered list, and then write a poem about them\\n\"\n",
"small_video_uri = \"gs://cloud-samples-data/generative-ai/video/animals.mp4\"\n",
"large_video_uri = (\n",
" \"gs://cloud-samples-data/generative-ai/video/behind_the_scenes_pixel.mp4\"\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "4f08049c98e5"
},
"source": [
"## Load testing Gemini "
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "91ffee55f2d7"
},
"source": [
"### Test without retry and default quota for Gemini-1.5-pro-001 of 60 QPM\n",
"\n",
"4 out of 5 tests fail due to 429 Quota exceeded"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"id": "7add4399f0a9"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing with model: gemini-1.5-pro-001 and function: async_ask_gemini\n",
"72 Poems written in 23 seconds\n",
"An error occurred: 429 Quota exceeded for aiplatform.googleapis.com/generate_content_requests_per_minute_per_project_per_base_model with base model: gemini-1.5-pro. Please submit a quota increase request. https://cloud.google.com/vertex-ai/docs/generative-ai/quotas-genai.\n",
"An error occurred: 429 Quota exceeded for aiplatform.googleapis.com/generate_content_requests_per_minute_per_project_per_base_model with base model: gemini-1.5-pro. Please submit a quota increase request. https://cloud.google.com/vertex-ai/docs/generative-ai/quotas-genai.\n",
"An error occurred: 429 Quota exceeded for aiplatform.googleapis.com/generate_content_requests_per_minute_per_project_per_base_model with base model: gemini-1.5-pro. Please submit a quota increase request. https://cloud.google.com/vertex-ai/docs/generative-ai/quotas-genai.\n",
"An error occurred: 429 Quota exceeded for aiplatform.googleapis.com/generate_content_input_tokens_per_minute_per_base_model with base model: gemini-1.5-pro. Please submit a quota increase request. https://cloud.google.com/vertex-ai/docs/generative-ai/quotas-genai.\n",
"4 out of 5 failed\n"
]
}
],
"source": [
"video_part = Part.from_uri(small_video_uri, mime_type=\"video/mp4\")\n",
"MODEL_NAME = \"gemini-1.5-pro-001\"\n",
"# Uncomment line below to re-run the test. Beware of costs since it will make multiple calls to Gemini\n",
"# await (load_test_gemini(async_ask_gemini, MODEL_NAME, attempts=5))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "4121c249d591"
},
"source": [
"### Re-testing with backoff and retry mechanism enabled \n",
"\n",
"All tests finallize correctly"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"id": "2338b49fd72d"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing with model: gemini-1.5-pro-001 and function: retry_async_ask_gemini\n",
"72 Poems written in 21 seconds\n",
"72 Poems written in 167 seconds\n",
"72 Poems written in 18 seconds\n",
"72 Poems written in 149 seconds\n",
"72 Poems written in 22 seconds\n",
"All 5 attempts succeded\n"
]
}
],
"source": [
"MODEL_NAME = \"gemini-1.5-pro-001\"\n",
"# Uncomment line below to re-run the test. Beware of costs since it will make multiple calls to Gemini\n",
"# await (load_test_gemini(retry_async_ask_gemini, MODEL_NAME, attempts=5))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "d26a20615065"
},
"source": [
"### Testing without retry but with Dynamic Shared Quota using Gemini-1.5-pro-002 \n",
"\n",
"All 5 attempts succeded with a small video as input"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"id": "37cd3facf381"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing with model: gemini-1.5-pro-002 and function: async_ask_gemini\n",
"72 Poems written in 23 seconds\n",
"72 Poems written in 21 seconds\n",
"72 Poems written in 19 seconds\n",
"72 Poems written in 17 seconds\n",
"72 Poems written in 22 seconds\n",
"All 5 attempts succeded\n"
]
}
],
"source": [
"video_part = Part.from_uri(small_video_uri, mime_type=\"video/mp4\")\n",
"MODEL_NAME = \"gemini-1.5-pro-002\"\n",
"# Uncomment line below to re-run the test. Beware of costs since it will make multiple calls to Gemini\n",
"# await (load_test_gemini(async_ask_gemini, MODEL_NAME, attempts=5))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "a5fa686b7c52"
},
"source": [
"### Re-testing Dynamic Shared quota with larger video\n",
"\n",
"Without backoff and retry, testing Gemini-1.5-pro-002 with larger context window caused all tests to fail with 429 reason code."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"id": "d241524f5071"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing with model: gemini-1.5-pro-002 and function: async_ask_gemini\n",
"An error occurred: 429 Resource exhausted. Please try again later. Please refer to https://cloud.google.com/vertex-ai/generative-ai/docs/quotas#error-code-429 for more details.\n",
"An error occurred: 429 Resource exhausted. Please try again later. Please refer to https://cloud.google.com/vertex-ai/generative-ai/docs/quotas#error-code-429 for more details.\n",
"An error occurred: 429 Resource exhausted. Please try again later. Please refer to https://cloud.google.com/vertex-ai/generative-ai/docs/quotas#error-code-429 for more details.\n",
"An error occurred: 429 Resource exhausted. Please try again later. Please refer to https://cloud.google.com/vertex-ai/generative-ai/docs/quotas#error-code-429 for more details.\n",
"An error occurred: 429 Resource exhausted. Please try again later. Please refer to https://cloud.google.com/vertex-ai/generative-ai/docs/quotas#error-code-429 for more details.\n",
"5 out of 5 failed\n"
]
}
],
"source": [
"# Larger video used to increase token input size\n",
"video_part = Part.from_uri(large_video_uri, mime_type=\"video/mp4\")\n",
"MODEL_NAME = \"gemini-1.5-pro-002\"\n",
"# Uncomment line below to re-run the test. Beware of costs since it will make multiple calls to Gemini\n",
"# await (load_test_gemini(async_ask_gemini, MODEL_NAME, attempts=5))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "999a6cd90e5d"
},
"source": [
"### Adding Backoff and Retry to Dynamic Shared Quota Testing\n",
"\n",
"Adding backoff and retry mechanisms significantly increased inference time, but all tests completed successfully even with much larger context window.\n",
"\n",
"Provisioned Throughput should be used to guarantee the capacity and therefore reduce latency.\n"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"id": "91c4d04ab9b4"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Testing with model: gemini-1.5-pro-002 and function: retry_async_ask_gemini\n",
"72 Poems written in 188 seconds\n",
"72 Poems written in 205 seconds\n",
"72 Poems written in 216 seconds\n",
"All 3 attempts succeded\n"
]
}
],
"source": [
"video_part = Part.from_uri(large_video_uri, mime_type=\"video/mp4\")\n",
"MODEL_NAME = \"gemini-1.5-pro-002\"\n",
"# Uncomment line below to re-run the test. Beware of costs since it will make multiple calls to Gemini\n",
"# await (load_test_gemini(retry_async_ask_gemini, MODEL_NAME, attempts=3))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "9cabeb585f56"
},
"source": [
"## Summary\n",
"\n",
"These basic tests demonstrate how Dynamic Shared Quota reduces the frequency of \"429 Resource Exhausted\" errors. The results highlight the importance of always using backoff and retry mechanisms when calling LLMs, regardless of the model version. Combining this with Provisioned Throughput further enhances reliability by guaranteeing capacity."
]
}
],
"metadata": {
"colab": {
"name": "backoff_and_retry_for_LLMs.ipynb",
"toc_visible": true
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
@@ -109,27 +109,27 @@
"id": "3Sq3sGfdt89E"
},
"source": [
"## Before you begin\r\n",
"\r\n",
"### GPU run-time\r\n",
"\r\n",
"*Make sure you're running this notebook in a GPU runtime if you have that option. In Colab, select* **Runtime > Change Runtime Type > GPU**\r\n",
"\r\n",
"### Set up your GCP project\r\n",
"\r\n",
"**The following steps are required, regardless of your notebook environment.**\r\n",
"\r\n",
"1. [Select or create a GCP project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 free credit towards your compute/storage costs.\r\n",
"\r\n",
"2. [Make sure that billing is enabled for your project.](https://cloud.google.com/billing/docs/how-to/modify-project)\r\n",
"\r\n",
"3. [Enable the Vertex APIs and Compute Engine APIs.](https://console.cloud.google.com/flows/enableapi?apiid=ml.googleapis.com,compute_component)\r\n",
"\r\n",
"4. [Google Cloud SDK](https://cloud.google.com/sdk) is already installed in Google Cloud Notebooks.\r\n",
"\r\n",
"5. Enter your project ID in the cell below. Then run the cell to make sure the\r\n",
"Cloud SDK uses the right project for all the commands in this notebook.\r\n",
"\r\n",
"## Before you begin\n",
"\n",
"### GPU run-time\n",
"\n",
"*Make sure you're running this notebook in a GPU runtime if you have that option. In Colab, select* **Runtime > Change Runtime Type > GPU**\n",
"\n",
"### Set up your GCP project\n",
"\n",
"**The following steps are required, regardless of your notebook environment.**\n",
"\n",
"1. [Select or create a GCP project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 free credit towards your compute/storage costs.\n",
"\n",
"2. [Make sure that billing is enabled for your project.](https://cloud.google.com/billing/docs/how-to/modify-project)\n",
"\n",
"3. [Enable the Vertex APIs and Compute Engine APIs.](https://console.cloud.google.com/flows/enableapi?apiid=ml.googleapis.com,compute_component)\n",
"\n",
"4. [Google Cloud SDK](https://cloud.google.com/sdk) is already installed in Google Cloud Notebooks.\n",
"\n",
"5. Enter your project ID in the cell below. Then run the cell to make sure the\n",
"Cloud SDK uses the right project for all the commands in this notebook.\n",
"\n",
"**Note**: Jupyter runs lines prefixed with `!` as shell commands, and it interpolates Python variables prefixed with `$` into these commands."
]
},
@@ -240,11 +240,11 @@
"id": "9zpjPUOhvRQz"
},
"source": [
"### Authenticate your GCP account\r\n",
"\r\n",
"**If you are using Google Cloud Notebooks**, your environment is already\r\n",
"authenticated. Skip this step.\r\n",
"\r\n",
"### Authenticate your GCP account\n",
"\n",
"**If you are using Google Cloud Notebooks**, your environment is already\n",
"authenticated. Skip this step.\n",
"\n",
"*Note: If you are on an Vertex notebook and run the cell, the cell knows to skip executing the authentication steps.*"
]
},
@@ -1459,8 +1459,7 @@
"id": "gM-YixlLmDy9"
},
"source": [
"### Make a batch prediction file\r\n",
"\r\n"
"### Make a batch prediction file\n"
]
},
{
@@ -1489,19 +1488,6 @@
"! gsutil cat $gcs_test_item"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "sPupiwqN_jAB"
},
"source": [
"*Example output*:\n",
"```\n",
"{\"content\": \"gs://migration-ucaip-trainingaip-20210301154552/test.txt\", \"mime_type\": \"text/plain\"}\n",
"Molecular basis of hexosaminidase A deficiency and pseudodeficiency in the Berks County Pennsylvania Dutch.\\tFollowing the birth of two infants with Tay-Sachs disease ( TSD ) , a non-Jewish , Pennsylvania Dutch kindred was screened for TSD carriers using the biochemical assay . A high frequency of individuals who appeared to be TSD heterozygotes was detected ( Kelly et al . , 1975 ) . Clinical and biochemical evidence suggested that the increased carrier frequency was due to at least two altered alleles for the hexosaminidase A alpha-subunit . We now report two mutant alleles in this Pennsylvania Dutch kindred , and one polymorphism . One allele , reported originally in a French TSD patient ( Akli et al . , 1991 ) , is a GT-- > AT transition at the donor splice-site of intron 9 . The second , a C-- > T transition at nucleotide 739 ( Arg247Trp ) , has been shown by Triggs-Raine et al . ( 1992 ) to be a clinically benign \" pseudodeficient \" allele associated with reduced enzyme activity against artificial substrate . Finally , a polymorphism [ G-- > A ( 759 ) ] , which leaves valine at codon 253 unchanged , is described\n",
"```\n"
]
},
{
"cell_type": "markdown",
"metadata": {
@@ -1557,45 +1543,6 @@
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "sPupiwqN_jAB"
},
"source": [
"*Example output*:\n",
"```\n",
"{\n",
" \"parent\": \"projects/migration-ucaip-training/locations/us-central1\",\n",
" \"batchPredictionJob\": {\n",
" \"displayName\": \"ten_20210301154552\",\n",
" \"model\": \"projects/116273516712/locations/us-central1/models/4400738115568795648\",\n",
" \"inputConfig\": {\n",
" \"instancesFormat\": \"jsonl\",\n",
" \"gcsSource\": {\n",
" \"uris\": [\n",
" \"gs://migration-ucaip-trainingaip-20210301154552/test.jsonl\"\n",
" ]\n",
" }\n",
" },\n",
" \"outputConfig\": {\n",
" \"predictionsFormat\": \"jsonl\",\n",
" \"gcsDestination\": {\n",
" \"outputUriPrefix\": \"gs://migration-ucaip-trainingaip-20210301154552/batch_output/\"\n",
" }\n",
" },\n",
" \"dedicatedResources\": {\n",
" \"machineSpec\": {\n",
" \"machineType\": \"n1-standard-2\"\n",
" },\n",
" \"startingReplicaCount\": 1,\n",
" \"maxReplicaCount\": 1\n",
" }\n",
" }\n",
"}\n",
"```\n"
]
},
{
"cell_type": "markdown",
"metadata": {
@@ -1638,42 +1585,6 @@
"print(MessageToJson(request.__dict__[\"_pb\"]))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "sPupiwqN_jAB"
},
"source": [
"*Example output*:\n",
"```\n",
"{\n",
" \"name\": \"projects/116273516712/locations/us-central1/batchPredictionJobs/3588251799200464896\",\n",
" \"displayName\": \"ten_20210301154552\",\n",
" \"model\": \"projects/116273516712/locations/us-central1/models/4400738115568795648\",\n",
" \"inputConfig\": {\n",
" \"instancesFormat\": \"jsonl\",\n",
" \"gcsSource\": {\n",
" \"uris\": [\n",
" \"gs://migration-ucaip-trainingaip-20210301154552/test.jsonl\"\n",
" ]\n",
" }\n",
" },\n",
" \"outputConfig\": {\n",
" \"predictionsFormat\": \"jsonl\",\n",
" \"gcsDestination\": {\n",
" \"outputUriPrefix\": \"gs://migration-ucaip-trainingaip-20210301154552/batch_output/\"\n",
" }\n",
" },\n",
" \"state\": \"JOB_STATE_PENDING\",\n",
" \"completionStats\": {\n",
" \"incompleteCount\": \"-1\"\n",
" },\n",
" \"createTime\": \"2021-03-01T17:59:42.777083Z\",\n",
" \"updateTime\": \"2021-03-01T17:59:42.777083Z\"\n",
"}\n",
"```\n"
]
},
{
"cell_type": "code",
"execution_count": null,
@@ -1739,42 +1650,6 @@
"print(MessageToJson(request.__dict__[\"_pb\"]))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "sPupiwqN_jAB"
},
"source": [
"*Example output*:\n",
"```\n",
"{\n",
" \"name\": \"projects/116273516712/locations/us-central1/batchPredictionJobs/3588251799200464896\",\n",
" \"displayName\": \"ten_20210301154552\",\n",
" \"model\": \"projects/116273516712/locations/us-central1/models/4400738115568795648\",\n",
" \"inputConfig\": {\n",
" \"instancesFormat\": \"jsonl\",\n",
" \"gcsSource\": {\n",
" \"uris\": [\n",
" \"gs://migration-ucaip-trainingaip-20210301154552/test.jsonl\"\n",
" ]\n",
" }\n",
" },\n",
" \"outputConfig\": {\n",
" \"predictionsFormat\": \"jsonl\",\n",
" \"gcsDestination\": {\n",
" \"outputUriPrefix\": \"gs://migration-ucaip-trainingaip-20210301154552/batch_output/\"\n",
" }\n",
" },\n",
" \"state\": \"JOB_STATE_PENDING\",\n",
" \"completionStats\": {\n",
" \"incompleteCount\": \"-1\"\n",
" },\n",
" \"createTime\": \"2021-03-01T17:59:42.777083Z\",\n",
" \"updateTime\": \"2021-03-01T17:59:42.777083Z\"\n",
"}\n",
"```\n"
]
},
{
"cell_type": "code",
"execution_count": null,
@@ -1798,19 +1673,6 @@
" time.sleep(60)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "trainingpipelines_create:migration,new,response,icn"
},
"source": [
"*Example output*:\n",
"```\n",
"gs://migration-ucaip-trainingaip-20210301154552/batch_output/prediction-ten_20210301154552-2021-03-01T17:59:42.638222Z/predictions_00001.jsonl\n",
"{\"instance\":{\"content\":\"gs://migration-ucaip-trainingaip-20210301154552/test.txt\",\"mimeType\":\"text/plain\"},\"prediction\":{\"ids\":[\"7806436899697983488\",\"7806436899697983488\",\"7806436899697983488\",\"4347672385877442560\",\"4347672385877442560\",\"4347672385877442560\"],\"displayNames\":[\"SpecificDisease\",\"SpecificDisease\",\"SpecificDisease\",\"Modifier\",\"Modifier\",\"Modifier\"],\"textSegmentStartOffsets\":[\"149\",\"19\",\"169\",\"236\",\"688\",\"330\"],\"textSegmentEndOffsets\":[\"165\",\"45\",\"171\",\"238\",\"690\",\"332\"],\"confidences\":[0.99957836,0.9995628,0.9995044,0.9993287,0.9993144,0.99927235]}}\n",
"```\n"
]
},
{
"cell_type": "markdown",
"metadata": {
@@ -2338,11 +2200,11 @@
"id": "bQ-VVaSxJjkd"
},
"source": [
"# Cleaning up\r\n",
"\r\n",
"To clean up all GCP resources used in this project, you can [delete the GCP\r\n",
"project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\r\n",
"\r\n",
"# Cleaning up\n",
"\n",
"To clean up all GCP resources used in this project, you can [delete the GCP\n",
"project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n",
"\n",
"Otherwise, you can delete the individual resources you created in this tutorial."
]
},
@@ -2404,7 +2266,7 @@
],
"metadata": {
"colab": {
"name": "UJ7 unified AutoML for natural language with Vertex AI Text Entity Extraction.ipynb",
"name": "UJ7 AutoML for natural language with Vertex AI Text Entity Extraction.ipynb",
"toc_visible": true
},
"kernelspec": {
@@ -0,0 +1,253 @@
# ViT PyTorch vs JAX training benchmarks on Vertex AI Training Platform
Lav Rai, Software Engineer, Google Cloud
Xiang Xu, Software Engineer, Google Cloud
Andreas Steiner, Software Engineer, Google DeepMind
Tao Wang, Software Engineer, Google DeepMind
Alexander Kolesnikov, Research Engineer, Google DeepMind
## Introduction
Many repositories now offer both PyTorch and JAX versions of a model. For
example, [Hugging Face offers many models such as GPT2, BERT][1]
etc. Other examples are [OpenLLaMa][2] and [ViT][3]
models which were first developed in JAX and then their corresponding PyTorch
versions were made available. **Given both the PyTorch and JAX options for a
model, it may not be obvious as to which option to choose**. To make such a
decision, it is important for one to know about the training cost, effectiveness
and efficiency for each choice.
Apart from the framework choice, the other choice that one faces on Vertex AI
training platform is the type and count of the accelerators. Although the
[Vertex AI pricing table][4] lists the price per hour for each
machine, **one may not know beforehand about the training speed of JAX and
PyTorch frameworks for different types and count of the accelerators**.
If one has access to some training benchmark numbers for the same model
under (a) PyTorch and JAX frameworks and (b) for different types and count of
the accelerators, then it will be easier for them to make a cost effective
decision. Such a benchmark will also aid the developers in identifying strength
and weakness of different choices and then figure out recipes to remove those
weaknesses if possible.
This blog uses the ViT [classification models][5] of varying sizes
to benchmark the training performance of PyTorch and JAX versions on the Vertex
AI Platform under different machine configurations. The goal is to:
- Benchmark OSS ViT training for both PyTorch and JAX frameworks.
- Benchmark OSS ViT L16, H14, g14, and G14 models.
- Benchmark OSS ViT PyTorch training with A100 GPUs.
- Benchmark OSS ViT JAX training with A100 GPUs and TPU V3 accelerators.
## Benchmarking setup
This section lays out the benchmarking set up for the [PyTorch][6] and [JAX][7]
frameworks and provides a reasoning for choosing those settings.
### PyTorch GPU
#### Machine configuration
We run training jobs on [Vertex AI Custom Training][8] using 1
single node with 8 A100-40GB GPUs.
- Machine type: [a2-highgpu-8g][9]
- Machine count: 1
- Accelerator type: [NVIDIA_TESLA_A100 (40GB)][10]
- Accelerator count: 8
#### Modeling
We benchmark 4 variants of ViT model in different sizes:
- [ViT-L16, 300M params][11]
- [ViT-H14, 630M params][12]
- [ViT-g14, 1B params][13]
- [ViT-G14, 1.8B params][14]
We use the Huggingface [transformers library][15] for ViT L16 and
H14 variants, and the [TIMM library][16] for ViT g14 and G14
variants.
#### Dataset
We run training against the [cifar10][17] dataset with 50K training
images and 10K test images. To factor out network communication overhead for
data loading, we copy the whole dataset to the local disk then load data from
the local disk during training.
#### Training parameters
- Trainer
- We use [PyTorch Lightning][18] as the trainer for the
boilerplate data loading and train loop coding.
- Precision
- Float16
- Input resolution
- 224 x 224
- Strategy
- We use [DDP][19] for models which can be entirely loaded to one
GPU, use [Deepspeed-ZeRO][20] otherwise:
- ViT-L16: DDP
- ViT-H14: DDP
- ViT-g14: DDP
- ViT-G14: Deepspeed-ZeRO stage-3
- Batch size
- We use the max batch size as power of 2 without CUDA OOM for each model:
- ViT-L16: 64 per GPU
- ViT-H14: 16 per GPU
- ViT-g14: 16 per GPU
- ViT-G14: 32 per GPU
- Compilation
- We apply [torch.compile][21] to model whenever it's applicable:
- ViT-L16: torch.compile
- ViT-H14: torch.compile
- ViT-g14: torch.compile
- ViT-G14: N/A
### JAX TPU and GPU
#### Machine configuration
All the TPU and GPU training jobs are run on [Vertex AI Custom
Training][8]. The following machine configurations were used for the
TPU and GPU experiments:
**Note**: TPU V3 POD requires multi-host supporting training code. For example,
a 32 core POD runs on 4 hosts with each host using 8 cores.
**Note**: 8 A100 are similar to TPU V3 32 cores in terms of [Vertex AI
pricing][4].
**Note**: [Each TPU v3 chip has 2 cores which can use 32 GB high-bandwidth
memory][22] (16 GB per core) so total memory for 32 cores is 16x32 =
512 GB. Therefore for the same price, TPUs offer more memory than 8 A100-40GB
GPUs.
#### Modeling
We decided to use an OSS code repository for model implementation. Using an OSS
repository helps anyone to independently verify the benchmarking results and
also relate to the results well. For JAX, we selected the
[Big Vision][23] code repository.
Same as the PyTorch modeling, we benchmark 4 variants of ViT model in different
sizes:
- [ViT-L16, 300M params][24]
- [ViT-H14, 630M params][24]
- [ViT-g14, 1B params][24]
- [ViT-G14, 1.8B params][24]
**Note**: The [Big Vision code repo][23] has not made the
checkpoints publicly available for the models larger than the ViT-L16. Therefore
for the rest of the three variants, the experiments only used random
initialization for benchmarking the training speed.
#### Dataset
We use training against the [cifar10 TensorFlow dataset][25] with
50K training images and 10K test images. This dataset is the same as the one
used for PyTorch experiments except that it is loaded as a TensorFlow dataset.
Similar to the PyTorch experiments, we copy the whole dataset to the docker
image to factor out network communication overhead for data loading.
#### Training parameters
- Precision
- "bfloat16" setting was used.
- Input resolution
- 224 x 224 after resize (to 448x448) and random crop (to 224x224) before
training.
- This resolution for training was the same as the PyTorch settings.
- Strategy
- Used DDP for all models except ViT-G14. ViT-G14 used the FSDP strategy.
- Batch size
- We use the max batch size as power of 2 without OOM for each model. The
[Benchmarking results][26] section shows the final
batch size for each experiment.
- Once a maximum batch-size for TPU V3 8 cores was determined, we just scaled
it linearly for 32 cores.
- Once a maximum batch-size for 1 A100 GPU was determined, we just scaled it
linearly for 8 A100 GPUs.
- Compilation
- [jax.jit() compilation][27] is used in JAX codes for efficient
execution in XLA.
- GPU related flags
- The following flags are set in the dockerfile for the GPU runs.
- Note: _xla_gpu_enable_pipelined_collectives_ is set to false for the
ViT-G14 FSDP run.
### Evaluation metric
For both the PyTorch and JAX experiments, the following evaluation metrics are
collected:
- Throughput: Images-per-second observed for training.
- Cost: The training-cost-per-epoch (USD).
**Note**: The above metrics are not biased against any framework or machine
configurations. In addition, these metrics will help one decide the most
efficient training configurations on Vertex AI.
## Benchmarking results
The lowest cost experiment for each model is marked in **bold** in the last
column.
![vit_benchmarking_table](images/vit_benchmarking_table.png)
The following bar charts summarize the performance visually:
![vit_training_time](images/vit_training_time.png)
![vit_training_cost](images/vit_training_cost.png)
The following section provides observations and conclusions for these results.
## Observation and Conclusions
- Training with JAX TPU V3 POD with 32 cores costs 33% less than the PyTorch GPU
8 A100-40GBs runs.
- Training with JAX GPU 8 A100-40GBs costs 23% less than the PyTorch GPU 8
A100-40GBs runs.
- JAX TPU V3 POD with 32 cores was 4x faster and slightly more cost-effective
than the JAX TPU V3 8 core run for the ViT-large model. This indicates that it
might be better to use more cores. The JAX TPU V3 speed scales very well with
the number of cores.
- Cloud TPU VM training speed numbers were the same as the Vertex AI for
TPU V3 8 cores. The dataset was copied to the docker in both the cases.
- The training-cost-per-epoch increases with the model size irrespective of the
framework.
[1]: https://github.com/huggingface/transformers/blob/main/examples/research_projects/jax-projects/README.md#quickstart-flax-and-jax-in-transformers
[2]: https://github.com/openlm-research/open_llama
[3]: https://github.com/google-research/vision_transformer
[4]: https://cloud.google.com/vertex-ai/pricing#custom-trained_models
[5]: https://arxiv.org/abs/2010.11929
[6]: #pytorch-gpu
[7]: #jax-tpu-and-gpu
[8]: https://cloud.google.com/vertex-ai/docs/training/overview
[9]: https://cloud.google.com/vertex-ai/docs/training/configure-compute#machine-types
[10]: https://cloud.google.com/vertex-ai/docs/training/configure-compute#specifying_gpus
[11]: https://huggingface.co/google/vit-large-patch16-224-in21k
[12]: https://huggingface.co/google/vit-huge-patch14-224-in21k
[13]: https://github.com/huggingface/pytorch-image-models/blob/v0.9.2/timm/models/vision_transformer.py#L1308
[14]: https://github.com/huggingface/pytorch-image-models/blob/v0.9.2/timm/models/vision_transformer.py#L1312
[15]: https://huggingface.co/docs/transformers/main/model_doc/vit#transformers.ViTModel
[16]: https://github.com/huggingface/pytorch-image-models
[17]: https://huggingface.co/datasets/cifar10
[18]: https://lightning.ai/docs/pytorch/stable/
[19]: https://pytorch.org/docs/stable/notes/ddp.html
[20]: https://www.deepspeed.ai/tutorials/zero/
[21]: https://pytorch.org/tutorials/intermediate/torch_compile_tutorial.html
[22]: https://cloud.google.com/tpu/docs/system-architecture-tpu-vm#tpu_v3
[23]: https://github.com/google-research/big_vision
[24]: https://screenshot.googleplex.com/BximJgxsgvBVu38
[25]: https://www.tensorflow.org/datasets/catalog/cifar10
[26]: #benchmarking-results
[27]: https://jax.readthedocs.io/en/latest/jax-101/02-jitting.html
@@ -0,0 +1,188 @@
# Benchmark report on hyperparameter tuning the OpenLLaMA models on Google Cloud Vertex Model Garden
Changyu Zhu, Software Engineer, Google Cloud
Dustin Luong, Software Engineer, Google Cloud
Gary Wei, Software Engineer, Google Cloud
Genquan Duan, Software Engineer, Google Cloud
## Introduction
Fine-tuning of LLMs can be non-trivial to find an optimal configuration of
machine types, training parameters, and other hyperparameters that achieves a
good balance between cost efficiency and model performance. To facilitate users
in conducting tuning experiments, this report benchmarks fine-tuning OpenLLaMA
models with [Vertex AI Hyperparameter Tuning Service](https://cloud.google.com/vertex-ai/docs/training/hyperparameter-tuning-overview), demonstrating both efficiency
and effectiveness. Similar hyperparameter tuning techniques can apply to other models as well.
## Key takeaways
- **The hyperparameter tuning service finds good parameters**: The best model found by the hyperparameter tuning service has an average improvement of around 4% in accuracy in *ARC*, *HellaSwag*, and *TruthfulQA* datasets, while only tuning the learning rate.
- **Hyperparameter tuning works with QLoRA on limited resources**: 4bit QLoRA is sufficient for hyperparameter tuning to find a set of good parameters. In this way, all OpenLLaMA models can run on 1 single `NVIDIA_L4` GPU. It is also possible to train for more steps on the good parameters discovered by hyperparameter tuning, avoiding the waste of computing resources on fine-tuning with suboptimal hyperparameters.
- **Hyperparameter tuning is cost-effective**: While `NVIDIA_L4` is slower than `NVIDIA_TESLA_V100`, it costs less and avoids the overhead of multi-GPU training since it has more GPU memory. Finding a good 3B/7B/13B OpenLLaMA model costs $28.5671, $47.8016, and $87.9208, respectively.
## Benchmarking setup
This section describes the experiment setup of the hyperparameter tuning experiments. The default tuning parameters are:
### Machine configuration
- Machine type: g2-standard-8
- Machine count: 1
- Accelerator type: NVIDIA_L4
- Accelerator count: 1
### Modeling
We benchmark all 3 OpenLLaMA models:
- [open_llama_3b](https://huggingface.co/openlm-research/open_llama_3b)
- [open_llama_7b](https://huggingface.co/openlm-research/open_llama_7b)
- [open_llama_13b](https://huggingface.co/openlm-research/open_llama_13b)
We use the Huggingface [PEFT](https://github.com/huggingface/peft) library for fine-tuning.
### Training dataset
We use the dataset [timdettmers/openassistant-guanaco](https://huggingface.co/datasets/timdettmers/openassistant-guanaco) loaded directly via Huggingface.
### Training parameters
The set of training parameters used during benchmarking:
- Batch size: 4
- Precision mode: 4bit QLoRA
- LoRA rank: 32
- LoRA alpha: 64
- Max sequence length: 512
- Max train steps: 1000
### Evaluation dataset
We use the [lm-evaluation-harness](https://github.com/EleutherAI/lm-evaluation-harness) library injected into the training loop for evaluation. The hyperparameter tuning job will pick the model according to the evaluation metrics.
- Eval task: [ARC Challenge](https://huggingface.co/datasets/ai2_arc)
- Eval metric: acc_norm
- Max eval examples: 10000
### Standalone evaluation dataset
After finding the best model with Vertex hyperparameter tuning service, we run standalone evaluations with the model on the following datasets:
- [ARC Challenge](https://huggingface.co/datasets/ai2_arc)
- [HellaSwag](https://huggingface.co/datasets/Rowan/hellaswag)
- [TruthfulQA](https://huggingface.co/datasets/EleutherAI/truthful_qa_mc)
### Hyperparameter tuning
We only tune the learning rate hyperparameter. It is considered a floating point value in the continuous range [1e-5, 1e-4]. We run 8 trials in total, with a parallelism of 1 or 2.
### Code example
The following code example launches an example hyperparameter tuning job of OpenLLaMA 7B model.
```py
from google.cloud import aiplatform
from google.cloud.aiplatform import hyperparameter_tuning as hpt
TRAIN_DOCKER_URI = 'us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-peft-train:20231130_0936_RC00'
output_dir = "gs://path/to/output/dir"
base_model_id = "openlm-research/open_llama_7b"
dataset_name = "timdettmers/openassistant-guanaco"
hpt_precision_mode = "4bit"
machine_type = "g2-standard-8"
accelerator_type = "NVIDIA_L4"
accelerator_count = 1
eval_task = "arc_challenge"
eval_metric_name = "acc_norm"
max_steps = 1000
eval_limit = 10000
flags = {
"learning_rate": 1e-5,
"precision_mode": hpt_precision_mode,
"task": "instruct-lora",
"pretrained_model_id": base_model_id,
"output_dir": output_dir,
"warmup_steps": 10,
"max_steps": max_steps,
"lora_rank": 32,
"lora_alpha": 64,
"lora_dropout": 0.05,
"dataset_name": dataset_name,
"eval_steps": max_steps + 1, # Only evaluates at the end.
"eval_tasks": eval_task,
"eval_limit": eval_limit,
"eval_metric_name": eval_metric_name,
}
worker_pool_specs = [
{
"machine_spec": {
"machine_type": machine_type,
"accelerator_type": accelerator_type,
"accelerator_count": accelerator_count,
},
"replica_count": 1,
"container_spec": {
"image_uri": TRAIN_DOCKER_URI,
"args": ["--{}={}".format(k, v) for k, v in flags.items()],
},
}
]
metric_spec = {"model_performance": "maximize"}
parameter_spec = {
"learning_rate": hpt.DoubleParameterSpec(
min=1e-5, max=1e-4, scale="linear"
),
}
train_job = aiplatform.CustomJob(
display_name=job_name,
worker_pool_specs=worker_pool_specs,
staging_bucket=STAGING_BUCKET,
)
train_hpt_job = aiplatform.HyperparameterTuningJob(
display_name=f"{job_name}_hpt",
custom_job=train_job,
metric_spec=metric_spec,
parameter_spec=parameter_spec,
max_trial_count=8,
parallel_trial_count=2,
)
train_hpt_job.run()
```
## Benchmark results
### Fine-tuning cost
The fine-tuning cost is calculated from `us-central1` pricing and may be subject to changes.
| Model | Train time | Trials | Parallel Trials | Hourly cost | Cost | Eval acc_norm (ARC-Challenge) |
|---------------|------------|--------|-----------------|-------------|----------|-------------------------------|
| OpenLLaMA 3B | 16 hrs | 8 | 2 | $1.7072 | $28.5671 | 39.9% |
| OpenLLaMA 7B | 28 hrs | 8 | 2 | $1.7072 | $47.8016 | 45.8% |
| OpenLLaMA 13B | 103 hrs | 8 | 1 | $0.8536 | $87.9208 | 47.6% |
### Fine-tuning performance
Here are the evaluation results of the best model found by hyperparameter tuning, compared with the baseline model. The column `Eval acc_norm` is calculated during training, which is always lower than that during standalone evaluation, because the model is loaded and evaluated at a lower precision (4bit during training / float16 during standalone evaluation).
| Model | Eval acc_norm (ARC-Challenge) | ARC | hellaswag | Truthfulqa_mc | ∆ARC | ∆Hellaswag | ∆Truthfulqa_mc | ∆Average |
|---------------|-------------------------------|--------|-----------|---------------|--------|------------|----------------|----------|
| OpenLLaMA 3B | 39.9% | 41.47% | 69.97% | 38.31% | +1.62% | +7.32% | +3.34% | +4.09% |
| OpenLLaMA 7B | 45.8% | 49.83% | 75.53% | 41.53% | +2.82% | +3.55% | +6.68% | +4.35% |
| OpenLLaMA 13B | 47.6% | 52.20% | 78.90% | 44.27% | +1.01% | +3.67% | +6.19% | +3.62% |
## Related documents
1. [Benchmark report on fine tuning the OpenLLaMA 7B model on Google Cloud Vertex Model Garden
](
https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/community-content/vertex_model_garden/benchmarking_reports/pytorch_openllama_7b_finetune_benchmark_report.md)
@@ -0,0 +1,218 @@
# Benchmark Stable Diffusion v1-5 Fine Tuning and Serving With Google Cloud Vertex Model Garden
Dustin Luong, Software Engineer, Google Cloud
Gary Wei, Software Engineer, Google Cloud
Changyu Zhu, Software Engineer, Google Cloud
Genquan Duan, Software Engineer, Google Cloud
## Introduction
[The public notebook][1] shows the full examples of fine tuning and serving of Stable diffusion v1-5. [The github repo][2] contains examples of building training and serving dockers for Google Cloud Vertex Model Garden. This report benchmarks Stable diffusion v1-5 fine tuning and serving in Google Cloud Vertex AI, showing both efficiencies and effectiveness.
### Benchmark Highlights
- Fine tuning
- Stable diffusion v1-5 with LoRA and Gradient checkpointing only requires ~10G GPU memory. Larger batch sizes, or larger resolutions require more GPU memories, but not does not change much for different LoRA ranks.
- The fine tuning speed is fast in ~11 minutes for 1k steps, and costs less than $1 in 1 A100. The fine tuning speed increases with batch sizes, decreases with resolution, but is not affected much by LoRA ranks.
- LoRA tunes a few percent (only 0.1% with LoRA rank=8) of all parameters, and the tuned models are very small (only 3.1MB with LoRA rank=8).
- Dreambooth+LoRA and Dreambooth can achieve similar performances, but Dreambooth LoRA can require much less GPU.
- Increasing batch size, reducing training steps, and increasing learning rate can result in models with the same performance for less cost.
- Inference
- The optimized serving docker pytorch-peft-serve can speed up inference by 2x than current pytorch-diffuser-serve, and support both base models and fine tuned lora models.
- The optimized serving docker pytorch-peft-serve can generate 4 512*512 images in 4.1 seconds on 1 V100 and 1.7 seconds on 1 A100.
Benchmark details are below.
## Fine Tuning Benchmarks
### Experiment Setup
We mainly compare two tuning algorithms:
- parameter efficient finetuning based on [dreambooth][3] and [LoRA][4] (shorten as Dreambooth+LoRA below)
- full parameter fine tuning based on [dreambooth][3] (shorten as Dreambooth below)
And then report benchmark results on GPU memories, tuning parameters, tuning speeds, costs and accuracy, using the public oxford flowers dataset: [train][5] and [test][6], where the column blip_caption as texts, and column image as images. We also benchmark subject and prompt fidelity using the [dataset][7] from the Dreambooth paper.
The default tuning parameters during benchmark are:
- Hardware: 1 A100 40G
- batch size: 4
- lora_rank: 8
- resolution: 512
- max_train_steps: 10
- use_lora: False
- gradient_checkpointing: False
```
# Examples to start finetuning dockers.
MODEL_NAME="runwayml/stable-diffusion-v1-5"
OUTPUT_DIR=<OUTPUT_DIR>
INSTANCE_DATA_DIR=<INSTANCE_DATA_DIR>
INSTANCE_PROMPT=<INSTANCE_PROMPT>
IMAGE="us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-peft-train"
docker run \
--runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=0 \
--rm --name "test_gpu" \
-it ${IMAGE} \
--task=text-to-image-dreambooth-lora-peft \
--pretrained_model_name_or_path=$MODEL_NAME \
--resolution=512 \
--instance_data_dir=$INSTANCE_DATA_DIR \
--instance_prompt=$INSTANCE_PROMPT \
--train_batch_size=4 \
--max_train_steps=10 \
--output_dir=${OUTPUT_DIR} \
--use_lora \
--lora_r=8 \
--gradient_checkpointing
```
### GPU Memories
Many various factors will impact GPU memory usages. In this benchmark, we mainly benchmark with different finetuning algorithms, batch sizes, lora rank, resolution, and then recommended max batch size on different GPUs.
![sd_v1-5_peak_gpu_algorithm](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_peak_gpu_algorithm.png)
![sd_v1-5_peak_gpu_batch_size](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_peak_gpu_batch_size.png)
![sd_v1-5_peak_gpu_lora_rank](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_peak_gpu_lora_rank.png)
![sd_v1-5_peak_gpu_resolution](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_peak_gpu_resolution.png)
- LoRA tuning reduced about 47% peak RAM and 42% peak VRAM for GPU memory, compared to full parameter fine tuning.
- Gradient checkpointing decreases about 1% peak RAM and 31% peak VRAM for GPU memory further, compared without gradient checkpointing.
- The GPU memory does not change much for different LoRA ranks.
- Larger batch sizes require more GPU memories.
- Larger resolutions require more GPU memories.
- Dreambooth+LoRA+Gradient_Checkpointing can support max batch size as 32, or max resolution as 2048, but Dreambooth can only support max batch size as 8, or max resolution as 1024.
### Fine Tuning Parameters
This section shows the percentage of trainable parameters, and tuned model sizes.
- LoRA tunes quite a few percent (only 0.1% with LoRA rank=8) of all parameters, and the tuned models are very small (only 3.1MB with LoRA rank=8).
| LoRA Rank | Trainable parameters | Total parameters | Trainable Parameter Percentage | Fine tuned model size (MB) |
|---|---|---|---|---|
| 4 | 398592 | 859919556 | 0.05% | 1.57 |
|8 | 797184 | 860318148 | 0.09% | 3.09 |
| 16 | 1594368| 861115332| 0.19%| 6.13|
| 32| 3188736| 862709700| 0.37%| 12.21|
### Fine Tuning Speed And Costs
Fine tuning speeds and costs are affected by many different factors, such as batch size, tuning parameters, image resolutions, GPUs, and datasets. In order to make the report easy to understand, we set the following values in this section:
- Hardware: 1 A100 40G
- use_lora: True
- gradient_checkpointing: True
![sd_v1-5_training_speed_batch_size](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_training_speed_batch_size.png)
![sd_v1-5_training_speed_lora_rank](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_training_speed_lora_rank.png)
![sd_v1-5_training_speed_resolution](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_training_speed_resolution.png)
![sd_v1-5_training_cost_max_steps](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_training_cost_max_steps.png)
- The fine tuning speed increases with batch sizes, decreases with resolution, but is not affected much by LoRA ranks.
- The fine tuning speed is about 11 minutes for 1k steps, and costs less than $1 in 1 A100.
### Fine Tuning Quality
In this benchmark, we mainly benchmark Dreambooth and Dreambooth+LoRA to compare fine tuning quality. We compare [subject fidelity scored (DINO)][8], how well the subject is represented in the generated images, and [prompt fidelity scores (CoCa)][9], how well the generated images match the given prompt, for a single subject, a [dog][10] from the dataset released with the original Dreambooth paper. In practice, we recommend saving checkpoints periodically and inspecting validation prompts visually. We fine tuned the unet without fine tuning the text encoder and used the following hyperparameters:
Dreambooth
- Learning rate: 5e-6
- Batch size: 1
Dreambooth+LoRA
- Learning rate: 1e-4
- Batch size: 1
![sd_v1-5_finetuning_quality_subject_fidelity](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_finetuning_quality_subject_fidelity.png)
![sd_v1-5_finetuning_quality_prompt_fidelity](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_finetuning_quality_prompt_fidelity.png)
- Fine tuning with Dreambooth or Dreambooth+LoRA can result in models with comparable performance. The base model produced images of the class rather than the instance.
- Dreambooth+LoRA is able to achieve the same subject fidelity score as Dreambooth if trained for more epochs.
- Increasing the number of training steps results in better subject fidelity but at the cost of prompt fidelity.
### Suggested Max Batch Sizes By Resolutions
We benchmarked and suggested max batch sizes by resolutions on 1 A100 and 1 V100 as below. This is with LoRA and gradient checkpointing enabled.
![sd_v1-5_batch_size_by_resolution](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_batch_size_by_resolution.png)
### Fine Tuning Cost Optimization
Increasing batch size allows for more images to be considered at each training step for fine tuning. This allows models to be trained in fewer training steps. In this benchmark, we aim to show how batch size can be increased to reduce training costs while still preserving subject and prompt fidelity.
Since the training dataset consists of 5 images, we train with a batch size of 5 and reduce the number of training steps from 400 to 80. Doing so results in a model that has not learned the subject since we’ve decreased the number of training steps. Conceptually, the model is taking a more precise step at each iteration, but it is taking fewer steps. To compensate for this, we increased the learning rate from 5e-6 and observed the best results at 1e-5 for full parameter finetuning.
![sd_v1-5_subject_fidelity_batch_size_5_learning_rate](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_subject_fidelity_batch_size_5_learning_rate.png)
![sd_v1-5_prompt_fidelity_batch_size_5_learning_rate](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_prompt_fidelity_batch_size_5_learning_rate.png)
Comparing cost of training the “best” model for batch size 1 vs. batch size 5
![sd_v1-5_cost_batch_size](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_cost_batch_size.png)
| Train method| Training parameters| Sample image| CoCa (prompt fidelity)| DINO (subject fidelity) | Cost of training on A100 |
|---|---|---|---|---|---|
| dreambooth| dreambooth, num_train_steps=400, batch_size=1, lr=5e-6| ![dog1](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_dog1.png) | 0.12215| 0.76531| $0.26 |
| dreambooth | dreambooth, num_train_steps=80, batch_size=5,lr=1e-5| ![dog2](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_dog2.png)| 0.12644| 0.74697 | $0.15 |
| dreambooth-lora| num_train_steps=500, batch_size=1, lr=1e-4, gc|![dog3](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_dog3.png)| 0.12856| 0.78148 | $0.26|
| dreambooth-lora | num_train_steps=50, batch_size=5, lr=1e-3, gc | ![dog4](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_dog4.png) | 0.12566 | 0.75479 | $0.09 |
A followup question is that since finetuning can be run on a single GPU, should finetuning be run on 1 V100 or A100?
Setup:
- num_train_steps=800 / batch_size
- Resolution=512
![sd_v1-5_cost_training_method_batch_size](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_cost_training_method_batch_size.png)
- Although V100 has a lower $/hr cost than an A100, the same training setup takes longer. Even given the longer training time, the cost on V100 is still lower.
- Dreambooth+LoRA enables training with larger batch sizes, however, larger batch sizes will not necessarily mean faster training time.
- It is possible to fine tune with 1 V100 on 512 resolution with Dreambooth+LoRA.
- Dreambooth fine tuning must be run on 1 A100 at 512 resolution.
## Inference Benchmarks
We provide two serving dockers in vertex model garden for stable diffusion:
- pytorch-diffuser-serve:
- us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-diffusers-serve
- This serving docker only serves base stable diffusion models and does not contain any optimizations yet.
- pytorch-peft-serve:
- us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-peft-serve
- This serving docker can serve base stable diffusion models, and base stable diffusion models with fine tuned lora models, and contains optimization for serving.
We run the two serving dockers on T4/V100/A100 to generate 4 512*512 images, and compare the inference speed without network considerations as:
![sd_v1-5_inference_speed_gpu](images/stable_diffusion_v1-5_benchmarking_report/sd_v1-5_inference_speed_gpu.png)
The speed up of optimized pytorch-peft-serve is about 2x than current pytorch-diffuser-serve.
### Serving cost comparison
Pytorch-diffuser-serve (without any optimizations)
| GPU type| Time required to generate 4 512x512 images | Machine unit price ($ / hour) | Cost per image ($) |
|---|---|---|---|
| T4 | 28.6 | 0.4025| 0.00080 |
| V100 | 8.8 | 2.852| 0.00174|
| A100 | 4.2 | 4.2245 | 0.00123 |
Pytorch-peft-serve (with optimizations)
| GPU type | Time required to generate 4 512x512 images | Machine unit price ($ / hour) | Cost per image ($) |
|--- |---|---|---|
| T4 | 12.6 | 0.4025 | 0.00035 |
| V100 | 4.1 | 2.852 | 0.00081 |
| A100 | 1.7 | 4.2245 | 0.00050 |
- The optimized pytorch-peft-serve has approximately half the price per image, compared with the un-optimized pytorch-diffuser-serve.
- Serving the model with a T4 is most cost effective, however, serving with an A100 still has the best throughput and fastest predictions.
[1]: https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_pytorch_stable_diffusion.ipynb
[2]: https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/main/community-content/vertex_model_garden/model_oss
[3]: https://arxiv.org/abs/2208.12242
[4]: https://arxiv.org/abs/2106.09685
[5]: https://huggingface.co/datasets/Multimodal-Fatima/OxfordFlowers_train
[6]: https://huggingface.co/datasets/Multimodal-Fatima/OxfordFlowers_test_facebook_opt_6.7b_Attributes_ns_6149
[7]: https://github.com/google/dreambooth
[8]: https://arxiv.org/abs/2104.14294
[9]: https://arxiv.org/abs/2205.01917
[10]: https://github.com/google/dreambooth/tree/main/dataset/dog6
File diff suppressed because it is too large Load Diff
@@ -127,6 +127,7 @@
"# @markdown 2. [Optional] [Create a Cloud Storage bucket](https://cloud.google.com/storage/docs/creating-buckets) for storing experiment outputs. Set the BUCKET_URI for the experiment environment. The specified Cloud Storage bucket (`BUCKET_URI`) should be located in the same region as where the notebook was launched. Note that a multi-region bucket (eg. \"us\") is not considered a match for a single region covered by the multi-region range (eg. \"us-central1\"). If not set, a unique GCS bucket will be created instead.\n",
"\n",
"import os\n",
"import uuid\n",
"from datetime import datetime\n",
"\n",
"from google.cloud import aiplatform\n",
@@ -142,14 +143,16 @@
"# prefer using your own GCS bucket, please change the value yourself below.\n",
"now = datetime.now().strftime(\"%Y%m%d%H%M%S\")\n",
"BUCKET_URI = \"gs://\" # @param {type:\"string\"}\n",
"BUCKET_NAME = \"/\".join(BUCKET_URI.split(\"/\")[:3])\n",
"assert BUCKET_URI.startswith(\"gs://\"), \"BUCKET_URI must start with `gs://`.\"\n",
"\n",
"# Create a unique GCS bucket for this notebook, if not specified by the user.\n",
"if BUCKET_URI is None or BUCKET_URI.strip() == \"\" or BUCKET_URI == \"gs://\":\n",
" BUCKET_URI = f\"gs://{PROJECT_ID}-tmp-{now}\"\n",
" BUCKET_URI = f\"gs://{PROJECT_ID}-tmp-{now}-{str(uuid.uuid4())[:4]}\"\n",
" BUCKET_NAME = \"/\".join(BUCKET_URI.split(\"/\")[:3])\n",
" ! gsutil mb -l {REGION} {BUCKET_URI}\n",
"else:\n",
" shell_output = ! gsutil ls -Lb {BUCKET_URI} | grep \"Location constraint:\" | sed \"s/Location constraint://\"\n",
" shell_output = ! gsutil ls -Lb {BUCKET_NAME} | grep \"Location constraint:\" | sed \"s/Location constraint://\"\n",
" bucket_region = shell_output[0].strip().lower()\n",
" if bucket_region != REGION:\n",
" raise ValueError(\n",
@@ -160,7 +163,6 @@
"print(f\"Using this GCS Bucket: {BUCKET_URI}\")\n",
"\n",
"# Provision permissions to the SERVICE_ACCOUNT with the GCS bucket\n",
"BUCKET_NAME = \"/\".join(BUCKET_URI.split(\"/\")[:3])\n",
"! gsutil iam ch serviceAccount:{SERVICE_ACCOUNT}:roles/storage.admin $BUCKET_NAME\n",
"\n",
"! gcloud config set project $PROJECT_ID\n",
@@ -176,7 +178,6 @@
"RENDERING_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/jax-cloudnerf-render:latest\"\n",
"\n",
"import subprocess\n",
"from datetime import datetime\n",
"from typing import Any, List\n",
"\n",
"IMAGE_EXTENSIONS = (\".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\")\n",
@@ -658,7 +659,7 @@
"\n",
"delete_bucket = False # @param {type:\"boolean\"}\n",
"if delete_bucket:\n",
" ! gsutil -m rm -r $BUCKET_URI\n",
" ! gsutil -m rm -r $BUCKET_NAME\n",
"\n",
"# Delete pose estimation, training and rendering custom jobs.\n",
"if data_calibration_custom_job.list(\n",
@@ -401,7 +401,9 @@
" else (\n",
" \"BOOL\"\n",
" if key == \"Use_Hierarchical_Mapper\"\n",
" else \"FLOAT64\" if key == \"Render_Focal\" else \"TIMESTAMP\"\n",
" else \"FLOAT64\"\n",
" if key == \"Render_Focal\"\n",
" else \"TIMESTAMP\"\n",
" )\n",
" )\n",
" ),\n",
@@ -421,7 +423,6 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"id": "j1evctm2h34g"
},
"outputs": [],
@@ -484,7 +485,7 @@
" level=logging.INFO, format=\"%(asctime)s - %(levelname)s - %(message)s\"\n",
")\n",
"\n",
"# track unique experiments\n",
"# Track unique experiments\n",
"unique_experiments = set()\n",
"\n",
"# Define dataset and table IDs\n",
@@ -959,7 +960,7 @@
" ]\n",
" return pd.DataFrame(data).sort_values(by=\"Experiment ID\").reset_index(drop=True)\n",
" except Exception as e:\n",
" logging.info(f\"Please upload a dataset to obtain an {e}.\", exc_info=True)\n",
" logging.info(f\"Exception encountered in {e}.\", exc_info=True)\n",
" return pd.DataFrame()\n",
"\n",
"\n",
@@ -985,7 +986,7 @@
" ]\n",
" return pd.DataFrame(data).sort_values(by=\"Experiment ID\").reset_index(drop=True)\n",
" except Exception as e:\n",
" logging.info(f\"Please upload a dataset to obtain an {e}.\", exc_info=True)\n",
" logging.info(f\"Exception encountered in {e}.\", exc_info=True)\n",
" return pd.DataFrame()\n",
"\n",
"\n",
@@ -1020,7 +1021,7 @@
" ]\n",
" return pd.DataFrame(data).sort_values(by=\"Experiment ID\").reset_index(drop=True)\n",
" except Exception as e:\n",
" logging.info(f\"Please upload a dataset to obtain an {e}.\", exc_info=True)\n",
" logging.info(f\"Exception encountered in {e}.\", exc_info=True)\n",
" return pd.DataFrame()\n",
"\n",
"\n",
@@ -1313,7 +1314,6 @@
"\n",
"\n",
"def create_pose_estimation_workshop():\n",
"\n",
" def get_worker_pool_specs(docker_uri, args, machine_type, accelerator_type):\n",
" return [\n",
" {\n",
@@ -1444,10 +1444,6 @@
" frame_rate=1,\n",
" progress=gr.Progress(),\n",
" ):\n",
" if not scene_name:\n",
" gr.Warning(\"Please provide a name for the scene. This field is required.\")\n",
" return get_bq_folders_dataframe_colmap(colmap_table_id)\n",
"\n",
" if gcs_folder:\n",
" prepare_instance_images_from_gcs(\n",
" scene_name, experiment_name, gcs_folder, progress\n",
@@ -1839,7 +1835,6 @@
"\n",
"\n",
"def create_training_workshop():\n",
"\n",
" def get_worker_pool_specs(docker_uri, args, machine_type, accelerator_type):\n",
" return [\n",
" {\n",
@@ -2194,7 +2189,6 @@
"\n",
"\n",
"def create_rendering_workshop():\n",
"\n",
" def get_worker_pool_specs(docker_uri, args, machine_type, accelerator_type):\n",
" return [\n",
" {\n",
@@ -2601,7 +2595,6 @@
"\n",
"\n",
"def create_pipeline_workshop():\n",
"\n",
" def get_worker_pool_specs(docker_uri, args, machine_type, accelerator_type):\n",
" return [\n",
" {\n",
@@ -3127,9 +3120,10 @@
"\n",
" with gr.Tabs():\n",
" with gr.TabItem(\"Pose Estimation\") as tab1:\n",
" pose_estimation_workshop, folders_dataframe_colmap = (\n",
" create_pose_estimation_workshop()\n",
" )\n",
" (\n",
" pose_estimation_workshop,\n",
" folders_dataframe_colmap,\n",
" ) = create_pose_estimation_workshop()\n",
" gr.on(\n",
" [tab1.select],\n",
" get_bq_folders_dataframe_colmap,\n",
@@ -3145,9 +3139,10 @@
" outputs=[folders_dataframe_training],\n",
" )\n",
" with gr.TabItem(\"Rendering\") as tab3:\n",
" rendering_workshop, folders_dataframe_rendering = (\n",
" create_rendering_workshop()\n",
" )\n",
" (\n",
" rendering_workshop,\n",
" folders_dataframe_rendering,\n",
" ) = create_rendering_workshop()\n",
" gr.on(\n",
" [tab3.select],\n",
" get_bq_folders_dataframe_rendering,\n",
@@ -4,11 +4,12 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"id": "7d9bbf86da5e"
},
"outputs": [],
"source": [
"# Copyright 2024 Google LLC\n",
"# Copyright 2025 Google LLC\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
@@ -94,17 +95,25 @@
"\n",
"# @markdown 1. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n",
"\n",
"# @markdown 2. [Optional] [Create a Cloud Storage bucket](https://cloud.google.com/storage/docs/creating-buckets) for storing experiment outputs. Set the BUCKET_URI for the experiment environment. The specified Cloud Storage bucket (`BUCKET_URI`) should be located in the same region as where the notebook was launched. Note that a multi-region bucket (eg. \"us\") is not considered a match for a single region covered by the multi-region range (eg. \"us-central1\"). If not set, a unique GCS bucket will be created instead.\n",
"# @markdown 2. By default, the quota for TPU deployment `Custom model serving TPU v5e cores per region` is 4. TPU quota is only available in `us-west1`. You can request for higher TPU quota following the instructions at [\"Request a higher quota\"](https://cloud.google.com/docs/quota/view-manage#requesting_higher_quota).\n",
"\n",
"# @markdown 3. By default, the quota for TPU deployment `Custom model serving TPU v5e cores per region` is 4. TPU quota is only available in `us-west1`. You can request for higher TPU quota following the instructions at [\"Request a higher quota\"](https://cloud.google.com/docs/quota/view-manage#requesting_higher_quota).\n",
"# @markdown 3. **[Optional]** [Create a Cloud Storage bucket](https://cloud.google.com/storage/docs/creating-buckets) for storing experiment outputs. Set the BUCKET_URI for the experiment environment. The specified Cloud Storage bucket (`BUCKET_URI`) should be located in the same region as where the notebook was launched. Note that a multi-region bucket (eg. \"us\") is not considered a match for a single region covered by the multi-region range (eg. \"us-central1\"). If not set, a unique GCS bucket will be created instead.\n",
"\n",
"BUCKET_URI = \"gs://\" # @param {type:\"string\"}\n",
"\n",
"# @markdown 4. **[Optional]** Set region. If not set, the region will be set automatically according to Colab Enterprise environment.\n",
"\n",
"REGION = \"\" # @param {type:\"string\"}\n",
"\n",
"# Import the necessary packages\n",
"# Upgrade Vertex AI SDK.\n",
"! pip3 install --upgrade --quiet 'google-cloud-aiplatform>=1.64.0'\n",
"! git clone https://github.com/GoogleCloudPlatform/vertex-ai-samples.git\n",
"\n",
"import datetime\n",
"import importlib\n",
"import os\n",
"import uuid\n",
"from datetime import datetime\n",
"from typing import Tuple\n",
"\n",
"from google.cloud import aiplatform\n",
@@ -114,12 +123,15 @@
")\n",
"\n",
"models, endpoints = {}, {}\n",
"# Dedicated endpoint not supported yet\n",
"use_dedicated_endpoint = False\n",
"\n",
"# Get the default cloud project id.\n",
"PROJECT_ID = os.environ[\"GOOGLE_CLOUD_PROJECT\"]\n",
"\n",
"# Get the default region for launching jobs.\n",
"REGION = os.environ[\"GOOGLE_CLOUD_REGION\"]\n",
"if not REGION:\n",
" REGION = os.environ[\"GOOGLE_CLOUD_REGION\"]\n",
"\n",
"# Enable the Vertex AI API and Compute Engine API, if not already.\n",
"print(\"Enabling Vertex AI API and Compute Engine API.\")\n",
@@ -128,8 +140,7 @@
"# Cloud Storage bucket for storing the experiment artifacts.\n",
"# A unique GCS bucket will be created for the purpose of this notebook. If you\n",
"# prefer using your own GCS bucket, change the value yourself below.\n",
"now = datetime.now().strftime(\"%Y%m%d%H%M%S\")\n",
"BUCKET_URI = \"gs://\" # @param {type:\"string\"}\n",
"now = datetime.datetime.now().strftime(\"%Y%m%d%H%M%S\")\n",
"BUCKET_NAME = \"/\".join(BUCKET_URI.split(\"/\")[:3])\n",
"\n",
"if BUCKET_URI is None or BUCKET_URI.strip() == \"\" or BUCKET_URI == \"gs://\":\n",
@@ -165,13 +176,14 @@
"# Provision permissions to the SERVICE_ACCOUNT with the GCS bucket\n",
"! gsutil iam ch serviceAccount:{SERVICE_ACCOUNT}:roles/storage.admin $BUCKET_NAME\n",
"\n",
"! gcloud config set project $PROJECT_ID"
"! gcloud config set project $PROJECT_ID\n",
"! gcloud projects add-iam-policy-binding --no-user-output-enabled {PROJECT_ID} --member=serviceAccount:{SERVICE_ACCOUNT} --role=\"roles/storage.admin\"\n",
"! gcloud projects add-iam-policy-binding --no-user-output-enabled {PROJECT_ID} --member=serviceAccount:{SERVICE_ACCOUNT} --role=\"roles/aiplatform.user\""
]
},
{
"cell_type": "code",
"execution_count": null,
"language": "python",
"metadata": {
"cellView": "form",
"id": "45c8c5438737"
@@ -200,7 +212,7 @@
"\n",
"# @markdown *--- Or ---*\n",
"\n",
"# @markdown ##Enable the Vertex AI API## Access CodeGemma models on HuggingFace\n",
"# @markdown #### Access CodeGemma models on HuggingFace\n",
"# @markdown You must provide a Hugging Face User Access Token (read) to access the CodeGemma models. You can follow the [Hugging Face documentation](https://huggingface.co/docs/hub/en/security-tokens) to create a **read** access token and put it in the `HF_TOKEN` field below.\n",
"HF_TOKEN = \"\" # @param {type:\"string\", isTemplate:true}\n",
"if LOAD_MODEL_FROM == \"Hugging Face\":\n",
@@ -233,7 +245,7 @@
"source": [
"## Deploy CodeGemma models with Hex-LLM on TPU\n",
"\n",
"**Hex-LLM** is a **H**igh-**E**fficiency **L**arge **L**anguage **M**odel (LLM) TPU serving solution built with **XLA**, which is being developed by Google Cloud."
"**Hex-LLM** is a **H**igh-**E**fficiency **L**arge **L**anguage **M**odel (LLM) TPU serving solution built with **XLA**, which is being developed by Google Cloud. Learn more about Hex-LLM [here](https://cloud.google.com/vertex-ai/generative-ai/docs/open-models/use-hex-llm)."
]
},
{
@@ -249,38 +261,54 @@
"\n",
"# @markdown Set the model to deploy.\n",
"\n",
"MODEL_ID = \"codegemma-7b-it\" # @param [\"codegemma-2b\", \"codegemma-7b\", \"codegemma-7b-it\"]\n",
"MODEL_ID = \"codegemma-2b\" # @param [\"codegemma-2b\", \"codegemma-7b\", \"codegemma-7b-it\"]\n",
"IMPROVE_2B_LATENCY_AND_THROUGHPUT_WITH_4CHIPS = True # @param {type:\"boolean\"}\n",
"TPU_DEPLOYMENT_REGION = \"us-west1\" # @param [\"us-west1\"] {isTemplate:true}\n",
"model_id = os.path.join(model_path_prefix, MODEL_ID)\n",
"hf_model_id = \"google/\" + MODEL_ID\n",
"\n",
"# The pre-built serving docker image for Hex-LLM.\n",
"HEXLLM_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/hex-llm-serve:deploy\"\n",
"HEXLLM_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/hex-llm-serve:20241210_2323_RC00\"\n",
"\n",
"# @markdown Find Vertex AI prediction TPUv5e machine types in\n",
"# @markdown https://cloud.google.com/vertex-ai/docs/predictions/use-tpu#deploy_a_model.\n",
"if \"2b\" in model_id:\n",
" # Sets ct5lp-hightpu-1t (1 TPU chip) to deploy CodeGemma 2B models.\n",
" machine_type = \"ct5lp-hightpu-1t\"\n",
" accelerator_type = \"TPU_V5e\"\n",
" # Note: 1 TPU V5 chip has only one core.\n",
" accelerator_count = 1\n",
" if IMPROVE_2B_LATENCY_AND_THROUGHPUT_WITH_4CHIPS:\n",
" # Sets ct5lp-hightpu-4t (4 TPU chips) to deploy CodeGemma 2B models.\n",
" # Using a tensor parallel size of 2 improves latency.\n",
" # Using a data parallel size of 2 approximately increases throughput by a factor of 2.\n",
" machine_type = \"ct5lp-hightpu-4t\"\n",
" accelerator_type = \"TPU_V5e\"\n",
" # Note: 1 TPU V5 chip has only one core.\n",
" data_parallel_size = 2\n",
" tensor_parallel_size = 2\n",
" else:\n",
" # Sets ct5lp-hightpu-1t (1 TPU chip) to deploy CodeGemma 2B models.\n",
" machine_type = \"ct5lp-hightpu-1t\"\n",
" accelerator_type = \"TPU_V5e\"\n",
" # Note: 1 TPU V5 chip has only one core.\n",
" data_parallel_size = 1\n",
" tensor_parallel_size = 1\n",
"else:\n",
" # Sets ct5lp-hightpu-4t (4 TPU chips) to deploy CodeGemma 7B models.\n",
" machine_type = \"ct5lp-hightpu-4t\"\n",
" accelerator_type = \"TPU_V5e\"\n",
" # Note: 1 TPU V5 chip has only one core.\n",
" accelerator_count = 4\n",
" data_parallel_size = 1\n",
" tensor_parallel_size = 4\n",
"\n",
"common_util.check_quota(\n",
" project_id=PROJECT_ID,\n",
" region=REGION,\n",
" accelerator_type=accelerator_type,\n",
" accelerator_count=accelerator_count,\n",
" accelerator_count=int(data_parallel_size * tensor_parallel_size),\n",
" is_for_training=False,\n",
")\n",
"\n",
"# @markdown Set enable_prefix_cache_hbm to False if you don't want to use [prefix caching](https://cloud.google.com/vertex-ai/generative-ai/docs/open-models/use-hex-llm#prefix-caching).\n",
"enable_prefix_cache_hbm = True # @param {type:\"boolean\"}\n",
"\n",
"# Server parameters.\n",
"tensor_parallel_size = accelerator_count\n",
"hbm_utilization_factor = 0.6 # A larger value improves throughput but gives higher risk of TPU out-of-memory errors with long prompts.\n",
"max_running_seqs = 256\n",
"\n",
@@ -292,15 +320,23 @@
"def deploy_model_hexllm(\n",
" model_name: str,\n",
" model_id: str,\n",
" publisher: str,\n",
" publisher_model_id: str,\n",
" service_account: str,\n",
" base_model_id: str = None,\n",
" data_parallel_size: int = 1,\n",
" tensor_parallel_size: int = 1,\n",
" machine_type: str = \"ct5lp-hightpu-1t\",\n",
" tpu_topology: str = \"1x1\",\n",
" disagg_topology: str = None,\n",
" hbm_utilization_factor: float = 0.6,\n",
" max_running_seqs: int = 256,\n",
" max_model_len: int = 4096,\n",
" enable_prefix_cache_hbm: bool = False,\n",
" endpoint_id: str = \"\",\n",
" min_replica_count: int = 1,\n",
" max_replica_count: int = 1,\n",
" use_dedicated_endpoint: bool = False,\n",
") -> Tuple[aiplatform.Model, aiplatform.Endpoint]:\n",
" \"\"\"Deploys models with Hex-LLM on TPU in Vertex AI.\"\"\"\n",
" if endpoint_id:\n",
@@ -312,6 +348,7 @@
" endpoint = aiplatform.Endpoint.create(\n",
" display_name=f\"{model_name}-endpoint\",\n",
" location=TPU_DEPLOYMENT_REGION,\n",
" dedicated_endpoint_enabled=use_dedicated_endpoint,\n",
" )\n",
"\n",
" if not base_model_id:\n",
@@ -320,26 +357,32 @@
" if not tensor_parallel_size:\n",
" tensor_parallel_size = int(machine_type[-2])\n",
"\n",
" num_hosts = int(tpu_topology.split(\"x\")[0])\n",
"\n",
" # Learn more about the supported arguments and environment variables at https://cloud.google.com/vertex-ai/generative-ai/docs/open-models/use-hex-llm#config-server.\n",
" hexllm_args = [\n",
" \"--host=0.0.0.0\",\n",
" \"--port=7080\",\n",
" \"--log_level=INFO\",\n",
" f\"--model={model_id}\",\n",
" f\"--data_parallel_size={data_parallel_size}\",\n",
" f\"--tensor_parallel_size={tensor_parallel_size}\",\n",
" \"--enable_jit\",\n",
" \"--load_format=auto\",\n",
" f\"--num_hosts={num_hosts}\",\n",
" f\"--hbm_utilization_factor={hbm_utilization_factor}\",\n",
" f\"--max_running_seqs={max_running_seqs}\",\n",
" f\"--max_model_len={max_model_len}\",\n",
" ]\n",
" if disagg_topology:\n",
" hexllm_args.append(f\"--disagg_topo={disagg_topology}\")\n",
" if enable_prefix_cache_hbm and not disagg_topology:\n",
" hexllm_args.append(\"--enable_prefix_cache_hbm\")\n",
"\n",
" env_vars = {\n",
" \"MODEL_ID\": base_model_id,\n",
" \"PJRT_DEVICE\": \"TPU\",\n",
" \"RAY_DEDUP_LOGS\": \"0\",\n",
" \"RAY_USAGE_STATS_ENABLED\": \"0\",\n",
" \"HEX_LLM_LOG_LEVEL\": \"info\",\n",
" \"DEPLOY_SOURCE\": \"notebook\",\n",
" }\n",
"\n",
" # HF_TOKEN is not a compulsory field and may not be defined.\n",
" try:\n",
" if HF_TOKEN:\n",
" env_vars.update({\"HF_TOKEN\": HF_TOKEN})\n",
@@ -358,28 +401,42 @@
" serving_container_shared_memory_size_mb=(16 * 1024), # 16 GB\n",
" serving_container_deployment_timeout=7200,\n",
" location=TPU_DEPLOYMENT_REGION,\n",
" model_garden_source_model_name=(\n",
" f\"publishers/{publisher}/models/{publisher_model_id}\"\n",
" ),\n",
" )\n",
"\n",
" model.deploy(\n",
" endpoint=endpoint,\n",
" machine_type=machine_type,\n",
" tpu_topology=tpu_topology if num_hosts > 1 else None,\n",
" deploy_request_timeout=1800,\n",
" service_account=service_account,\n",
" min_replica_count=min_replica_count,\n",
" max_replica_count=max_replica_count,\n",
" system_labels={\n",
" \"NOTEBOOK_NAME\": \"model_garden_codegemma_deployment_on_vertex.ipynb\",\n",
" },\n",
" )\n",
" return model, endpoint\n",
"\n",
"\n",
"models[\"hexllm_tpu\"], endpoints[\"hexllm_tpu\"] = deploy_model_hexllm(\n",
" model_name=common_util.get_job_name_with_datetime(prefix=MODEL_ID),\n",
" model_id=model_id,\n",
" publisher=\"google\",\n",
" publisher_model_id=\"codegemma\",\n",
" base_model_id=hf_model_id,\n",
" service_account=SERVICE_ACCOUNT,\n",
" machine_type=machine_type,\n",
" data_parallel_size=data_parallel_size,\n",
" tensor_parallel_size=tensor_parallel_size,\n",
" hbm_utilization_factor=hbm_utilization_factor,\n",
" max_running_seqs=max_running_seqs,\n",
" enable_prefix_cache_hbm=enable_prefix_cache_hbm,\n",
" min_replica_count=min_replica_count,\n",
" max_replica_count=max_replica_count,\n",
" use_dedicated_endpoint=use_dedicated_endpoint,\n",
")\n",
"\n",
"# @markdown Click \"Show code\" to see more details."
@@ -428,7 +485,9 @@
" \"top_k\": top_k,\n",
" },\n",
"]\n",
"response = endpoints[\"hexllm_tpu\"].predict(instances=instances)\n",
"response = endpoints[\"hexllm_tpu\"].predict(\n",
" instances=instances, use_dedicated_endpoint=use_dedicated_endpoint\n",
")\n",
"\n",
"# \"<|file_separator|>\" is the end of the file token.\n",
"for prediction in response.predictions:\n",
@@ -455,7 +514,6 @@
{
"cell_type": "code",
"execution_count": null,
"language": "python",
"metadata": {
"cellView": "form",
"id": "tQIEisUajS6t"
@@ -507,6 +565,8 @@
"def deploy_model_vllm(\n",
" model_name: str,\n",
" model_id: str,\n",
" publisher: str,\n",
" publisher_model_id: str,\n",
" service_account: str,\n",
" base_model_id: str = None,\n",
" machine_type: str = \"g2-standard-8\",\n",
@@ -515,28 +575,69 @@
" gpu_memory_utilization: float = 0.9,\n",
" max_model_len: int = 4096,\n",
" dtype: str = \"auto\",\n",
" enable_trust_remote_code: bool = False,\n",
" enforce_eager: bool = False,\n",
" enable_lora: bool = False,\n",
" enable_chunked_prefill: bool = False,\n",
" enable_prefix_cache: bool = False,\n",
" host_prefix_kv_cache_utilization_target: float = 0.0,\n",
" max_loras: int = 1,\n",
" max_cpu_loras: int = 8,\n",
" use_dedicated_endpoint: bool = False,\n",
" max_num_seqs: int = 256,\n",
" model_type: str = None,\n",
") -> Tuple[aiplatform.Model, aiplatform.Endpoint]:\n",
" \"\"\"Deploys trained models with vLLM into Vertex AI.\"\"\"\n",
" endpoint = aiplatform.Endpoint.create(display_name=f\"{model_name}-endpoint\")\n",
" endpoint = aiplatform.Endpoint.create(\n",
" display_name=f\"{model_name}-endpoint\",\n",
" dedicated_endpoint_enabled=use_dedicated_endpoint,\n",
" )\n",
"\n",
" if not base_model_id:\n",
" base_model_id = model_id\n",
"\n",
" # See https://docs.vllm.ai/en/latest/models/engine_args.html for a list of possible arguments with descriptions.\n",
" vllm_args = [\n",
" \"python\",\n",
" \"-m\",\n",
" \"vllm.entrypoints.api_server\",\n",
" \"--host=0.0.0.0\",\n",
" \"--port=7080\",\n",
" \"--port=8080\",\n",
" f\"--model={model_id}\",\n",
" f\"--tensor-parallel-size={accelerator_count}\",\n",
" \"--swap-space=16\",\n",
" f\"--gpu-memory-utilization={gpu_memory_utilization}\",\n",
" f\"--max-model-len={max_model_len}\",\n",
" f\"--dtype={dtype}\",\n",
" f\"--max-loras={max_loras}\",\n",
" f\"--max-cpu-loras={max_cpu_loras}\",\n",
" f\"--max-num-seqs={max_num_seqs}\",\n",
" \"--disable-log-stats\",\n",
" ]\n",
"\n",
" if enable_trust_remote_code:\n",
" vllm_args.append(\"--trust-remote-code\")\n",
"\n",
" if enforce_eager:\n",
" vllm_args.append(\"--enforce-eager\")\n",
"\n",
" if enable_lora:\n",
" vllm_args.append(\"--enable-lora\")\n",
"\n",
" if enable_chunked_prefill:\n",
" vllm_args.append(\"--enable-chunked-prefill\")\n",
"\n",
" if enable_prefix_cache:\n",
" vllm_args.append(\"--enable-prefix-caching\")\n",
"\n",
" if 0 < host_prefix_kv_cache_utilization_target < 1:\n",
" vllm_args.append(\n",
" f\"--host-prefix-kv-cache-utilization-target={host_prefix_kv_cache_utilization_target}\"\n",
" )\n",
"\n",
" if model_type:\n",
" vllm_args.append(f\"--model-type={model_type}\")\n",
"\n",
" env_vars = {\n",
" \"MODEL_ID\": base_model_id,\n",
" \"DEPLOY_SOURCE\": \"notebook\",\n",
@@ -553,12 +654,15 @@
" display_name=model_name,\n",
" serving_container_image_uri=VLLM_DOCKER_URI,\n",
" serving_container_args=vllm_args,\n",
" serving_container_ports=[7080],\n",
" serving_container_ports=[8080],\n",
" serving_container_predict_route=\"/generate\",\n",
" serving_container_health_route=\"/ping\",\n",
" serving_container_environment_variables=env_vars,\n",
" serving_container_shared_memory_size_mb=(16 * 1024), # 16 GB\n",
" serving_container_deployment_timeout=7200,\n",
" model_garden_source_model_name=(\n",
" f\"publishers/{publisher}/models/{publisher_model_id}\"\n",
" ),\n",
" )\n",
" print(\n",
" f\"Deploying {model_name} on {machine_type} with {accelerator_count} {accelerator_type} GPU(s).\"\n",
@@ -570,20 +674,27 @@
" accelerator_count=accelerator_count,\n",
" deploy_request_timeout=1800,\n",
" service_account=service_account,\n",
" system_labels={\n",
" \"NOTEBOOK_NAME\": \"model_garden_codegemma_deployment_on_vertex.ipynb\",\n",
" },\n",
" )\n",
" print(\"endpoint_name:\", endpoint.name)\n",
"\n",
" return model, endpoint\n",
"\n",
"\n",
"models[\"vllm_gpu\"], endpoints[\"vllm_gpu\"] = deploy_model_vllm(\n",
" model_name=common_util.get_job_name_with_datetime(prefix=\"codegemma-serve-vllm\"),\n",
" model_id=model_id,\n",
" publisher=\"google\",\n",
" publisher_model_id=\"codegemma\",\n",
" service_account=SERVICE_ACCOUNT,\n",
" machine_type=machine_type,\n",
" accelerator_type=accelerator_type,\n",
" accelerator_count=accelerator_count,\n",
" max_model_len=max_model_len,\n",
" gpu_memory_utilization=gpu_memory_utilization,\n",
" use_dedicated_endpoint=use_dedicated_endpoint,\n",
")\n",
"\n",
"# @markdown Click \"Show code\" to see more details."
@@ -630,6 +741,7 @@
"temperature = 1.0 # @param {type:\"number\"}\n",
"top_p = 1.0 # @param {type:\"number\"}\n",
"top_k = 1 # @param {type:\"integer\"}\n",
"# @markdown Set `raw_response` to `True` to obtain the raw model output. Set `raw_response` to `False` to apply additional formatting in the structure of `\"Prompt:\\n{prompt.strip()}\\nOutput:\\n{output}\"`.\n",
"raw_response = True # @param {type:\"boolean\"}\n",
"\n",
"instances = [\n",
@@ -642,7 +754,9 @@
" \"raw_response\": raw_response,\n",
" },\n",
"]\n",
"response = endpoints[\"vllm_gpu\"].predict(instances=instances)\n",
"response = endpoints[\"vllm_gpu\"].predict(\n",
" instances=instances, use_dedicated_endpoint=use_dedicated_endpoint\n",
")\n",
"\n",
"# \"<|file_separator|>\" is the end of the file token.\n",
"for prediction in response.predictions:\n",
@@ -211,6 +211,7 @@
" serving_container_args=tei_args,\n",
" serving_container_ports=[7080],\n",
" serving_container_environment_variables=serving_env,\n",
" model_garden_source_model_name=\"publishers/intfloat/models/e5\"\n",
" )\n",
"\n",
" model.deploy(\n",
@@ -220,6 +221,9 @@
" accelerator_count=accelerator_count,\n",
" deploy_request_timeout=1800,\n",
" service_account=service_account,\n",
" system_labels={\n",
" \"NOTEBOOK_NAME\": \"model_garden_e5.ipynb\"\n",
" },\n",
" )\n",
" return model, endpoint"
]
@@ -5,11 +5,12 @@
"execution_count": null,
"language": "python",
"metadata": {
"cellView": "form",
"id": "7d9bbf86da5e"
},
"outputs": [],
"source": [
"# Copyright 2024 Google LLC\n",
"# Copyright 2025 Google LLC\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
@@ -110,16 +111,24 @@
"\n",
"# @markdown 1. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n",
"\n",
"# @markdown 2. [Optional] [Create a Cloud Storage bucket](https://cloud.google.com/storage/docs/creating-buckets) for storing experiment outputs. Set the BUCKET_URI for the experiment environment. The specified Cloud Storage bucket (`BUCKET_URI`) should be located in the same region as where the notebook was launched. Note that a multi-region bucket (eg. \"us\") is not considered a match for a single region covered by the multi-region range (eg. \"us-central1\"). If not set, a unique GCS bucket will be created instead.\n",
"# @markdown 2. **[Optional]** [Create a Cloud Storage bucket](https://cloud.google.com/storage/docs/creating-buckets) for storing experiment outputs. Set the BUCKET_URI for the experiment environment. The specified Cloud Storage bucket (`BUCKET_URI`) should be located in the same region as where the notebook was launched. Note that a multi-region bucket (eg. \"us\") is not considered a match for a single region covered by the multi-region range (eg. \"us-central1\"). If not set, a unique GCS bucket will be created instead.\n",
"\n",
"BUCKET_URI = \"gs://\" # @param {type:\"string\"}\n",
"\n",
"# @markdown 3. **[Optional]** Set region. If not set, the region will be set automatically according to Colab Enterprise environment.\n",
"\n",
"REGION = \"\" # @param {type:\"string\"}\n",
"\n",
"# Import the necessary packages\n",
"\n",
"# Upgrade Vertex AI SDK.\n",
"! pip3 install --upgrade --quiet 'google-cloud-aiplatform>=1.64.0'\n",
"! git clone https://github.com/GoogleCloudPlatform/vertex-ai-samples.git\n",
"\n",
"import datetime\n",
"import importlib\n",
"import os\n",
"import uuid\n",
"from datetime import datetime\n",
"from typing import Tuple\n",
"\n",
"from google.cloud import aiplatform\n",
@@ -134,7 +143,8 @@
"PROJECT_ID = os.environ[\"GOOGLE_CLOUD_PROJECT\"]\n",
"\n",
"# Get the default region for launching jobs.\n",
"REGION = os.environ[\"GOOGLE_CLOUD_REGION\"]\n",
"if not REGION:\n",
" REGION = os.environ[\"GOOGLE_CLOUD_REGION\"]\n",
"\n",
"# Enable the Vertex AI API and Compute Engine API, if not already.\n",
"print(\"Enabling Vertex AI API and Compute Engine API.\")\n",
@@ -143,8 +153,7 @@
"# Cloud Storage bucket for storing the experiment artifacts.\n",
"# A unique GCS bucket will be created for the purpose of this notebook. If you\n",
"# prefer using your own GCS bucket, change the value yourself below.\n",
"now = datetime.now().strftime(\"%Y%m%d%H%M%S\")\n",
"BUCKET_URI = \"gs://\" # @param {type:\"string\"}\n",
"now = datetime.datetime.now().strftime(\"%Y%m%d%H%M%S\")\n",
"BUCKET_NAME = \"/\".join(BUCKET_URI.split(\"/\")[:3])\n",
"\n",
"if BUCKET_URI is None or BUCKET_URI.strip() == \"\" or BUCKET_URI == \"gs://\":\n",
@@ -181,6 +190,8 @@
"! gsutil iam ch serviceAccount:{SERVICE_ACCOUNT}:roles/storage.admin $BUCKET_NAME\n",
"\n",
"! gcloud config set project $PROJECT_ID\n",
"! gcloud projects add-iam-policy-binding --no-user-output-enabled {PROJECT_ID} --member=serviceAccount:{SERVICE_ACCOUNT} --role=\"roles/storage.admin\"\n",
"! gcloud projects add-iam-policy-binding --no-user-output-enabled {PROJECT_ID} --member=serviceAccount:{SERVICE_ACCOUNT} --role=\"roles/aiplatform.user\"\n",
"\n",
"# @markdown ## Access Gemma 2 Models\n",
"\n",
@@ -191,141 +202,7 @@
" HF_TOKEN\n",
"), \"Provide a read HF_TOKEN to load models from Hugging Face, or select a different model source.\"\n",
"\n",
"model_path_prefix = \"google/\"\n",
"\n",
"# The pre-built serving docker images.\n",
"HEXLLM_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/hex-llm-serve:gemma2\"\n",
"TGI_DOCKER_URI = \"us-docker.pkg.dev/deeplearning-platform-release/gcr.io/huggingface-text-generation-inference-cu121.2-1.ubuntu2204.py310\"\n",
"\n",
"def deploy_model_hexllm(\n",
" model_name: str,\n",
" model_id: str,\n",
" service_account: str,\n",
" base_model_id: str = None,\n",
" tensor_parallel_size: int = 1,\n",
" machine_type: str = \"ct5lp-hightpu-1t\",\n",
" hbm_utilization_factor: float = 0.6,\n",
" max_running_seqs: int = 256,\n",
" endpoint_id: str = \"\",\n",
" min_replica_count: int = 1,\n",
" max_replica_count: int = 1,\n",
") -> Tuple[aiplatform.Model, aiplatform.Endpoint]:\n",
" \"\"\"Deploys models with Hex-LLM on TPU in Vertex AI.\"\"\"\n",
" if endpoint_id:\n",
" aip_endpoint_name = (\n",
" f\"projects/{PROJECT_ID}/locations/{REGION}/endpoints/{endpoint_id}\"\n",
" )\n",
" endpoint = aiplatform.Endpoint(aip_endpoint_name)\n",
" else:\n",
" endpoint = aiplatform.Endpoint.create(\n",
" display_name=f\"{model_name}-endpoint\",\n",
" location=TPU_DEPLOYMENT_REGION,\n",
" )\n",
"\n",
" if not base_model_id:\n",
" base_model_id = model_id\n",
"\n",
" if not tensor_parallel_size:\n",
" tensor_parallel_size = int(machine_type[-2])\n",
"\n",
" hexllm_args = [\n",
" \"--host=0.0.0.0\",\n",
" \"--port=7080\",\n",
" \"--log_level=INFO\",\n",
" f\"--model={model_id}\",\n",
" f\"--tensor_parallel_size={tensor_parallel_size}\",\n",
" \"--enable_jit\",\n",
" \"--load_format=auto\",\n",
" f\"--hbm_utilization_factor={hbm_utilization_factor}\",\n",
" f\"--max_running_seqs={max_running_seqs}\",\n",
" ]\n",
"\n",
" env_vars = {\n",
" \"MODEL_ID\": base_model_id,\n",
" \"PJRT_DEVICE\": \"TPU\",\n",
" \"RAY_DEDUP_LOGS\": \"0\",\n",
" \"RAY_USAGE_STATS_ENABLED\": \"0\",\n",
" \"DEPLOY_SOURCE\": \"notebook\",\n",
" }\n",
"\n",
" try:\n",
" if HF_TOKEN:\n",
" env_vars.update({\"HF_TOKEN\": HF_TOKEN})\n",
" except:\n",
" pass\n",
"\n",
" model = aiplatform.Model.upload(\n",
" display_name=model_name,\n",
" serving_container_image_uri=HEXLLM_DOCKER_URI,\n",
" serving_container_command=[\"python\", \"-m\", \"hex_llm.server.api_server\"],\n",
" serving_container_args=hexllm_args,\n",
" serving_container_ports=[7080],\n",
" serving_container_predict_route=\"/generate\",\n",
" serving_container_health_route=\"/ping\",\n",
" serving_container_environment_variables=env_vars,\n",
" serving_container_shared_memory_size_mb=(16 * 1024), # 16 GB\n",
" serving_container_deployment_timeout=7200,\n",
" location=TPU_DEPLOYMENT_REGION,\n",
" )\n",
"\n",
" model.deploy(\n",
" endpoint=endpoint,\n",
" machine_type=machine_type,\n",
" deploy_request_timeout=1800,\n",
" service_account=service_account,\n",
" min_replica_count=min_replica_count,\n",
" max_replica_count=max_replica_count,\n",
" )\n",
" return model, endpoint\n",
"\n",
"\n",
"def deploy_model_tgi(\n",
" model_name: str,\n",
" model_id: str,\n",
" service_account: str,\n",
" machine_type: str = \"g2-standard-8\",\n",
" accelerator_type: str = \"NVIDIA_L4\",\n",
" accelerator_count: int = 1,\n",
" max_input_length: int = 2047,\n",
" max_total_tokens: int = 2048,\n",
" max_batch_prefill_tokens: int = 2048,\n",
") -> Tuple[aiplatform.Model, aiplatform.Endpoint]:\n",
" \"\"\"Deploys models with TGI on GPU in Vertex AI.\"\"\"\n",
" endpoint = aiplatform.Endpoint.create(display_name=f\"{model_name}-endpoint\")\n",
"\n",
" env_vars = {\n",
" \"MODEL_ID\": model_id,\n",
" \"NUM_SHARD\": f\"{accelerator_count}\",\n",
" \"MAX_INPUT_LENGTH\": f\"{max_input_length}\",\n",
" \"MAX_TOTAL_TOKENS\": f\"{max_total_tokens}\",\n",
" \"MAX_BATCH_PREFILL_TOKENS\": f\"{max_batch_prefill_tokens}\",\n",
" \"DEPLOY_SOURCE\": \"notebook\",\n",
" }\n",
"\n",
" # HF_TOKEN is not a compulsory field and may not be defined.\n",
" try:\n",
" if HF_TOKEN:\n",
" env_vars[\"HF_TOKEN\"] = HF_TOKEN\n",
" except NameError:\n",
" pass\n",
"\n",
" model = aiplatform.Model.upload(\n",
" display_name=model_name,\n",
" serving_container_image_uri=TGI_DOCKER_URI,\n",
" serving_container_ports=[8080],\n",
" serving_container_environment_variables=env_vars,\n",
" serving_container_shared_memory_size_mb=(16 * 1024), # 16 GB\n",
" )\n",
"\n",
" model.deploy(\n",
" endpoint=endpoint,\n",
" machine_type=machine_type,\n",
" accelerator_type=accelerator_type,\n",
" accelerator_count=accelerator_count,\n",
" deploy_request_timeout=1800,\n",
" service_account=service_account,\n",
" )\n",
" return model, endpoint"
"model_path_prefix = \"google/\""
]
},
{
@@ -336,7 +213,7 @@
"source": [
"## Deploy Gemma 2 models with Hex-LLM on TPU\n",
"\n",
"**Hex-LLM** is a **H**igh-**E**fficiency **L**arge **L**anguage **M**odel (LLM) TPU serving solution built with **XLA**, which is being developed by Google Cloud.\n",
"**Hex-LLM** is a **H**igh-**E**fficiency **L**arge **L**anguage **M**odel (LLM) TPU serving solution built with **XLA**, which is being developed by Google Cloud. Learn more about Hex-LLM [here](https://cloud.google.com/vertex-ai/generative-ai/docs/open-models/use-hex-llm).\n",
"\n",
"Refer to the \"Request for TPU quota\" section for TPU quota."
]
@@ -353,6 +230,9 @@
"# @title Deploy\n",
"# @markdown Set the model ID. Model weights can be loaded from HuggingFace or from a GCS bucket.\n",
"\n",
"# The pre-built serving docker images.\n",
"HEXLLM_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/hex-llm-serve:20241210_2323_RC00\"\n",
"\n",
"# @markdown Select one of the four model variations.\n",
"MODEL_ID = \"gemma-2-2b-it\" # @param [\"gemma-2-2b\", \"gemma-2-2b-it\", \"gemma-2-9b\", \"gemma-2-9b-it\", \"gemma-2-27b\", \"gemma-2-27b-it\"] {allow-input: true, isTemplate: true}\n",
"TPU_DEPLOYMENT_REGION = \"us-west1\" # @param [\"us-west1\"] {isTemplate:true}\n",
@@ -392,13 +272,123 @@
"hbm_utilization_factor = 0.6 # Fraction of HBM memory allocated for KV cache after model loading. A larger value improves throughput but gives higher risk of TPU out-of-memory errors with long prompts.\n",
"max_running_seqs = 256 # Maximum number of running sequences in a continuous batch.\n",
"\n",
"# @markdown Set use_dedicated_endpoint to False if you don't want to use [dedicated endpoint](https://cloud.google.com/vertex-ai/docs/general/deployment#create-dedicated-endpoint).\n",
"use_dedicated_endpoint = True # @param {type:\"boolean\"}\n",
"\n",
"# Endpoint configurations.\n",
"min_replica_count = 1\n",
"max_replica_count = 1\n",
"\n",
"\n",
"def deploy_model_hexllm(\n",
" model_name: str,\n",
" model_id: str,\n",
" publisher: str,\n",
" publisher_model_id: str,\n",
" service_account: str,\n",
" base_model_id: str = None,\n",
" data_parallel_size: int = 1,\n",
" tensor_parallel_size: int = 1,\n",
" machine_type: str = \"ct5lp-hightpu-1t\",\n",
" tpu_topology: str = \"1x1\",\n",
" disagg_topology: str = None,\n",
" hbm_utilization_factor: float = 0.6,\n",
" max_running_seqs: int = 256,\n",
" max_model_len: int = 4096,\n",
" enable_prefix_cache_hbm: bool = False,\n",
" endpoint_id: str = \"\",\n",
" min_replica_count: int = 1,\n",
" max_replica_count: int = 1,\n",
" use_dedicated_endpoint: bool = False,\n",
") -> Tuple[aiplatform.Model, aiplatform.Endpoint]:\n",
" \"\"\"Deploys models with Hex-LLM on TPU in Vertex AI.\"\"\"\n",
" if endpoint_id:\n",
" aip_endpoint_name = (\n",
" f\"projects/{PROJECT_ID}/locations/{REGION}/endpoints/{endpoint_id}\"\n",
" )\n",
" endpoint = aiplatform.Endpoint(aip_endpoint_name)\n",
" else:\n",
" endpoint = aiplatform.Endpoint.create(\n",
" display_name=f\"{model_name}-endpoint\",\n",
" location=TPU_DEPLOYMENT_REGION,\n",
" dedicated_endpoint_enabled=use_dedicated_endpoint,\n",
" )\n",
"\n",
" if not base_model_id:\n",
" base_model_id = model_id\n",
"\n",
" if not tensor_parallel_size:\n",
" tensor_parallel_size = int(machine_type[-2])\n",
"\n",
" num_hosts = int(tpu_topology.split(\"x\")[0])\n",
"\n",
" # Learn more about the supported arguments and environment variables at https://cloud.google.com/vertex-ai/generative-ai/docs/open-models/use-hex-llm#config-server.\n",
" hexllm_args = [\n",
" \"--host=0.0.0.0\",\n",
" \"--port=7080\",\n",
" f\"--model={model_id}\",\n",
" f\"--data_parallel_size={data_parallel_size}\",\n",
" f\"--tensor_parallel_size={tensor_parallel_size}\",\n",
" f\"--num_hosts={num_hosts}\",\n",
" f\"--hbm_utilization_factor={hbm_utilization_factor}\",\n",
" f\"--max_running_seqs={max_running_seqs}\",\n",
" f\"--max_model_len={max_model_len}\",\n",
" ]\n",
" if disagg_topology:\n",
" hexllm_args.append(f\"--disagg_topo={disagg_topology}\")\n",
" if enable_prefix_cache_hbm and not disagg_topology:\n",
" hexllm_args.append(\"--enable_prefix_cache_hbm\")\n",
"\n",
" env_vars = {\n",
" \"MODEL_ID\": base_model_id,\n",
" \"HEX_LLM_LOG_LEVEL\": \"info\",\n",
" \"DEPLOY_SOURCE\": \"notebook\",\n",
" }\n",
"\n",
" # HF_TOKEN is not a compulsory field and may not be defined.\n",
" try:\n",
" if HF_TOKEN:\n",
" env_vars.update({\"HF_TOKEN\": HF_TOKEN})\n",
" except:\n",
" pass\n",
"\n",
" model = aiplatform.Model.upload(\n",
" display_name=model_name,\n",
" serving_container_image_uri=HEXLLM_DOCKER_URI,\n",
" serving_container_command=[\"python\", \"-m\", \"hex_llm.server.api_server\"],\n",
" serving_container_args=hexllm_args,\n",
" serving_container_ports=[7080],\n",
" serving_container_predict_route=\"/generate\",\n",
" serving_container_health_route=\"/ping\",\n",
" serving_container_environment_variables=env_vars,\n",
" serving_container_shared_memory_size_mb=(16 * 1024), # 16 GB\n",
" serving_container_deployment_timeout=7200,\n",
" location=TPU_DEPLOYMENT_REGION,\n",
" model_garden_source_model_name=(\n",
" f\"publishers/{publisher}/models/{publisher_model_id}\"\n",
" ),\n",
" )\n",
"\n",
" model.deploy(\n",
" endpoint=endpoint,\n",
" machine_type=machine_type,\n",
" tpu_topology=tpu_topology if num_hosts > 1 else None,\n",
" deploy_request_timeout=1800,\n",
" service_account=service_account,\n",
" min_replica_count=min_replica_count,\n",
" max_replica_count=max_replica_count,\n",
" system_labels={\n",
" \"NOTEBOOK_NAME\": \"model_garden_gemma2_deployment_on_vertex.ipynb\",\n",
" },\n",
" )\n",
" return model, endpoint\n",
"\n",
"\n",
"models[\"hexllm_tpu\"], endpoints[\"hexllm_tpu\"] = deploy_model_hexllm(\n",
" model_name=common_util.get_job_name_with_datetime(prefix=MODEL_ID),\n",
" model_id=model_id,\n",
" publisher=\"google\",\n",
" publisher_model_id=\"gemma2\",\n",
" service_account=SERVICE_ACCOUNT,\n",
" machine_type=machine_type,\n",
" tensor_parallel_size=tensor_parallel_size,\n",
@@ -406,6 +396,7 @@
" max_running_seqs=max_running_seqs,\n",
" min_replica_count=min_replica_count,\n",
" max_replica_count=max_replica_count,\n",
" use_dedicated_endpoint=use_dedicated_endpoint,\n",
")"
]
},
@@ -463,7 +454,9 @@
" \"top_k\": top_k,\n",
" },\n",
"]\n",
"response = endpoints[\"hexllm_tpu\"].predict(instances=instances)\n",
"response = endpoints[\"hexllm_tpu\"].predict(\n",
" instances=instances, use_dedicated_endpoint=use_dedicated_endpoint\n",
")\n",
"\n",
"for prediction in response.predictions:\n",
" print(prediction)"
@@ -494,6 +487,10 @@
"outputs": [],
"source": [
"# @title Deploy\n",
"\n",
"# The pre-built serving docker image.\n",
"TGI_DOCKER_URI = \"us-docker.pkg.dev/deeplearning-platform-release/gcr.io/huggingface-text-generation-inference-cu121.2-1.ubuntu2204.py310\"\n",
"\n",
"MODEL_ID = \"gemma-2-2b\" # @param [\"gemma-2-2b\", \"gemma-2-2b-it\", \"gemma-2-9b\", \"gemma-2-9b-it\", \"gemma-2-27b\", \"gemma-2-27b-it\"] {allow-input: true, isTemplate: true}\n",
"model_id = os.path.join(model_path_prefix, MODEL_ID)\n",
"\n",
@@ -550,9 +547,73 @@
"max_total_tokens = 2048\n",
"max_batch_prefill_tokens = 2048\n",
"\n",
"\n",
"def deploy_model_tgi(\n",
" model_name: str,\n",
" model_id: str,\n",
" publisher: str,\n",
" publisher_model_id: str,\n",
" service_account: str,\n",
" machine_type: str = \"g2-standard-8\",\n",
" accelerator_type: str = \"NVIDIA_L4\",\n",
" accelerator_count: int = 1,\n",
" max_input_length: int = 2047,\n",
" max_total_tokens: int = 2048,\n",
" max_batch_prefill_tokens: int = 2048,\n",
" use_dedicated_endpoint: bool = False,\n",
") -> Tuple[aiplatform.Model, aiplatform.Endpoint]:\n",
" \"\"\"Deploys models with TGI on GPU in Vertex AI.\"\"\"\n",
" endpoint = aiplatform.Endpoint.create(\n",
" display_name=f\"{model_name}-endpoint\",\n",
" dedicated_endpoint_enabled=use_dedicated_endpoint,\n",
" )\n",
"\n",
" env_vars = {\n",
" \"MODEL_ID\": model_id,\n",
" \"NUM_SHARD\": f\"{accelerator_count}\",\n",
" \"MAX_INPUT_LENGTH\": f\"{max_input_length}\",\n",
" \"MAX_TOTAL_TOKENS\": f\"{max_total_tokens}\",\n",
" \"MAX_BATCH_PREFILL_TOKENS\": f\"{max_batch_prefill_tokens}\",\n",
" \"DEPLOY_SOURCE\": \"notebook\",\n",
" }\n",
"\n",
" # HF_TOKEN is not a compulsory field and may not be defined.\n",
" try:\n",
" if HF_TOKEN:\n",
" env_vars[\"HF_TOKEN\"] = HF_TOKEN\n",
" except NameError:\n",
" pass\n",
"\n",
" model = aiplatform.Model.upload(\n",
" display_name=model_name,\n",
" serving_container_image_uri=TGI_DOCKER_URI,\n",
" serving_container_ports=[8080],\n",
" serving_container_environment_variables=env_vars,\n",
" serving_container_shared_memory_size_mb=(16 * 1024), # 16 GB\n",
" model_garden_source_model_name=(\n",
" f\"publishers/{publisher}/models/{publisher_model_id}\"\n",
" ),\n",
" )\n",
"\n",
" model.deploy(\n",
" endpoint=endpoint,\n",
" machine_type=machine_type,\n",
" accelerator_type=accelerator_type,\n",
" accelerator_count=accelerator_count,\n",
" deploy_request_timeout=1800,\n",
" service_account=service_account,\n",
" system_labels={\n",
" \"NOTEBOOK_NAME\": \"model_garden_gemma2_deployment_on_vertex.ipynb\",\n",
" },\n",
" )\n",
" return model, endpoint\n",
"\n",
"\n",
"models[\"tgi\"], endpoints[\"tgi\"] = deploy_model_tgi(\n",
" model_name=common_util.get_job_name_with_datetime(prefix=MODEL_ID),\n",
" model_id=model_id,\n",
" publisher=\"google\",\n",
" publisher_model_id=\"gemma2\",\n",
" service_account=SERVICE_ACCOUNT,\n",
" machine_type=machine_type,\n",
" accelerator_type=accelerator_type,\n",
@@ -560,6 +621,7 @@
" max_input_length=max_input_length,\n",
" max_total_tokens=max_total_tokens,\n",
" max_batch_prefill_tokens=max_batch_prefill_tokens,\n",
" use_dedicated_endpoint=use_dedicated_endpoint,\n",
")"
]
},
@@ -617,7 +679,9 @@
" },\n",
" },\n",
"]\n",
"response = endpoints[\"tgi\"].predict(instances=instances)\n",
"response = endpoints[\"tgi\"].predict(\n",
" instances=instances, use_dedicated_endpoint=use_dedicated_endpoint\n",
")\n",
"\n",
"for prediction in response.predictions:\n",
" print(prediction)"
@@ -5,6 +5,7 @@
"execution_count": null,
"language": "python",
"metadata": {
"cellView": "form",
"id": "7d9bbf86da5e"
},
"outputs": [],
@@ -66,6 +67,9 @@
"- Finetune and deploy Gemma 2 models with Vertex AI Custom Training Jobs.\n",
"- Send prediction requests to your finetuned Gemma 2 model.\n",
"\n",
"### File a bug\n",
"\n",
"File a bug on [GitHub](https://github.com/GoogleCloudPlatform/vertex-ai-samples/issues/new) if you encounter any issue with the notebook.\n",
"\n",
"### Costs\n",
"\n",
@@ -86,6 +90,30 @@
"## Before you begin"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"id": "aS52SK74gDoB"
},
"outputs": [],
"source": [
"# @title Install Python Packages for Finetuning\n",
"\n",
"# @markdown 1. Install google-cloud-aiplatform package and restart the session if instructed.\n",
"! pip install --upgrade --quiet 'google-cloud-aiplatform>=1.66.0'\n",
"\n",
"# @markdown 2. Install packages to validate dataset with template.\n",
"! pip install --upgrade --quiet gcsfs==2024.3.1\n",
"! pip install --upgrade --quiet accelerate==0.31.0\n",
"! pip install --upgrade --quiet transformers==4.43.1\n",
"! pip install --upgrade --quiet datasets==2.19.2\n",
"\n",
"# Load local tensorboard.\n",
"%load_ext tensorboard"
]
},
{
"cell_type": "code",
"execution_count": null,
@@ -97,19 +125,39 @@
"outputs": [],
"source": [
"# @title Setup Google Cloud project\n",
"\n",
"# @markdown 1. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n",
"\n",
"# @markdown 2. [Optional] [Create a Cloud Storage bucket](https://cloud.google.com/storage/docs/creating-buckets) for storing experiment outputs. Set the BUCKET_URI for the experiment environment. The specified Cloud Storage bucket (`BUCKET_URI`) should be located in the same region as where the notebook was launched. Note that a multi-region bucket (eg. \"us\") is not considered a match for a single region covered by the multi-region range (eg. \"us-central1\"). If not set, a unique GCS bucket will be created instead.\n",
"# @markdown 2. For finetuning, **[click here](https://console.cloud.google.com/iam-admin/quotas?location=us-central1&metric=aiplatform.googleapis.com%2Frestricted_image_training_nvidia_a100_80gb_gpus)** to check if your project already has the required 8 Nvidia A100 80 GB GPUs in the us-central1 region. If yes, then run this notebook in the us-central1 region. If you do not have 8 Nvidia A100 80 GPUs or have more GPU requirements than this, then schedule your job with Nvidia H100 GPUs via Dynamic Workload Scheduler using [these instructions](https://cloud.google.com/vertex-ai/docs/training/schedule-jobs-dws). For Dynamic Workload Scheduler, check the [us-central1](https://console.cloud.google.com/iam-admin/quotas?location=us-central1&metric=aiplatform.googleapis.com%2Fcustom_model_training_preemptible_nvidia_h100_gpus) or [europe-west4](https://console.cloud.google.com/iam-admin/quotas?location=europe-west4&metric=aiplatform.googleapis.com%2Fcustom_model_training_preemptible_nvidia_h100_gpus) quota for Nvidia H100 GPUs. If you do not have enough GPUs, then you can follow [these instructions](https://cloud.google.com/docs/quotas/view-manage#viewing_your_quota_console) to request quota.\n",
"\n",
"! git clone https://github.com/GoogleCloudPlatform/vertex-ai-samples.git\n",
"# @markdown 3. For serving, **[click here](https://console.cloud.google.com/iam-admin/quotas?location=us-central1&metric=aiplatform.googleapis.com%2Fcustom_model_serving_nvidia_l4_gpus)** to check if your project already has the required 1 L4 GPU in the us-central1 region. If yes, then run this notebook in the us-central1 region. If you need more L4 GPUs for your project, then you can follow [these instructions](https://cloud.google.com/docs/quotas/view-manage#viewing_your_quota_console) to request more. Alternatively, if you want to run predictions with A100 80GB or H100 GPUs, we recommend using the regions listed below. **NOTE:** Make sure you have associated quota in selected regions. Click the links to see your current quota for each GPU type: [Nvidia A100 80GB](https://console.cloud.google.com/iam-admin/quotas?metric=aiplatform.googleapis.com%2Fcustom_model_serving_nvidia_a100_80gb_gpus), [Nvidia H100 80GB](https://console.cloud.google.com/iam-admin/quotas?metric=aiplatform.googleapis.com%2Fcustom_model_serving_nvidia_h100_gpus).\n",
"\n",
"# @markdown > | Machine Type | Accelerator Type | Recommended Regions |\n",
"# @markdown | ----------- | ----------- | ----------- |\n",
"# @markdown | a2-ultragpu-1g | 1 NVIDIA_A100_80GB | us-central1, us-east4, europe-west4, asia-southeast1, us-east4 |\n",
"# @markdown | a3-highgpu-8g | 8 NVIDIA_H100_80GB | us-central1, us-west1, europe-west4, asia-southeast1 |\n",
"\n",
"# @markdown 4. **[Optional]** [Create a Cloud Storage bucket](https://cloud.google.com/storage/docs/creating-buckets) for storing experiment outputs. Set the BUCKET_URI for the experiment environment. The specified Cloud Storage bucket (`BUCKET_URI`) should be located in the same region as where the notebook was launched. Note that a multi-region bucket (eg. \"us\") is not considered a match for a single region covered by the multi-region range (eg. \"us-central1\"). If not set, a unique GCS bucket will be created instead.\n",
"\n",
"BUCKET_URI = \"gs://\" # @param {type:\"string\"}\n",
"\n",
"# @markdown 5. **[Optional]** Set region. If not set, the region will be set automatically according to Colab Enterprise environment.\n",
"\n",
"REGION = \"\" # @param {type:\"string\"}\n",
"\n",
"# Import the necessary packages\n",
"! rm -rf vertex-ai-samples && git clone https://github.com/GoogleCloudPlatform/vertex-ai-samples.git\n",
"! cd vertex-ai-samples && git reset --hard 0727e19520cf7957bceb701c248221bd3dbe4f1f\n",
"\n",
"import datetime\n",
"import importlib\n",
"import os\n",
"import uuid\n",
"from datetime import datetime\n",
"from typing import Tuple\n",
"\n",
"from google.cloud import aiplatform\n",
"from google.cloud.aiplatform.compat.types import \\\n",
" custom_job as gca_custom_job_compat\n",
"\n",
"common_util = importlib.import_module(\n",
" \"vertex-ai-samples.community-content.vertex_model_garden.model_oss.notebook_util.common_util\"\n",
@@ -121,7 +169,8 @@
"PROJECT_ID = os.environ[\"GOOGLE_CLOUD_PROJECT\"]\n",
"\n",
"# Get the default region for launching jobs.\n",
"REGION = os.environ[\"GOOGLE_CLOUD_REGION\"]\n",
"if not REGION:\n",
" REGION = os.environ[\"GOOGLE_CLOUD_REGION\"]\n",
"\n",
"# Enable the Vertex AI API and Compute Engine API, if not already.\n",
"print(\"Enabling Vertex AI API and Compute Engine API.\")\n",
@@ -130,8 +179,7 @@
"# Cloud Storage bucket for storing the experiment artifacts.\n",
"# A unique GCS bucket will be created for the purpose of this notebook. If you\n",
"# prefer using your own GCS bucket, change the value yourself below.\n",
"now = datetime.now().strftime(\"%Y%m%d%H%M%S\")\n",
"BUCKET_URI = \"gs://\" # @param {type:\"string\"}\n",
"now = datetime.datetime.now().strftime(\"%Y%m%d%H%M%S\")\n",
"BUCKET_NAME = \"/\".join(BUCKET_URI.split(\"/\")[:3])\n",
"\n",
"if BUCKET_URI is None or BUCKET_URI.strip() == \"\" or BUCKET_URI == \"gs://\":\n",
@@ -168,6 +216,8 @@
"! gsutil iam ch serviceAccount:{SERVICE_ACCOUNT}:roles/storage.admin $BUCKET_NAME\n",
"\n",
"! gcloud config set project $PROJECT_ID\n",
"! gcloud projects add-iam-policy-binding --no-user-output-enabled {PROJECT_ID} --member=serviceAccount:{SERVICE_ACCOUNT} --role=\"roles/storage.admin\"\n",
"! gcloud projects add-iam-policy-binding --no-user-output-enabled {PROJECT_ID} --member=serviceAccount:{SERVICE_ACCOUNT} --role=\"roles/aiplatform.user\"\n",
"\n",
"# @markdown ## Access Gemma 2 Models\n",
"\n",
@@ -265,6 +315,81 @@
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"id": "NoueJWi72OSo"
},
"outputs": [],
"source": [
"# @title Set model\n",
"\n",
"# @markdown Select a model variant of Gemma 2.\n",
"base_model_id = \"gemma-2-2b-it\" # @param [\"gemma-2-2b\", \"gemma-2-2b-it\", \"gemma-2-9b\", \"gemma-2-9b-it\", \"gemma-2-27b\", \"gemma-2-27b-it\"] {isTemplate: true}\n",
"pretrained_model_id = os.path.join(model_path_prefix, base_model_id)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"id": "-NlLSiCOvru1"
},
"outputs": [],
"source": [
"# @title Validate Dataset with Template\n",
"\n",
"# @markdown This section validates the train and eval datasets with the template before starting the fine tuning process.\n",
"\n",
"import transformers\n",
"\n",
"dataset_validation_util = importlib.import_module(\n",
" \"vertex-ai-samples.community-content.vertex_model_garden.model_oss.notebook_util.dataset_validation_util\"\n",
")\n",
"\n",
"if dataset_validation_util.is_gcs_path(pretrained_model_id):\n",
" # Download tokenizer.\n",
" ! mkdir tokenizer\n",
" ! gsutil cp {pretrained_model_id}/tokenizer.json ./tokenizer\n",
" ! gsutil cp {pretrained_model_id}/config.json ./tokenizer\n",
" tokenizer_path = \"./tokenizer\"\n",
" access_token = \"\"\n",
"else:\n",
" tokenizer_path = pretrained_model_id\n",
" access_token = HF_TOKEN\n",
"\n",
"tokenizer = transformers.AutoTokenizer.from_pretrained(\n",
" tokenizer_path,\n",
" trust_remote_code=False,\n",
" use_fast=True,\n",
" token=access_token,\n",
")\n",
"\n",
"# Validate the train dataset.\n",
"dataset_validation_util.validate_dataset_with_template(\n",
" dataset_name=train_dataset_name,\n",
" split=train_split_name,\n",
" input_column=instruct_column_in_dataset,\n",
" template=template,\n",
" use_multiprocessing=False,\n",
" tokenizer=tokenizer,\n",
")\n",
"\n",
"# Validate the eval dataset.\n",
"dataset_validation_util.validate_dataset_with_template(\n",
" dataset_name=eval_dataset_name,\n",
" split=eval_split_name,\n",
" input_column=instruct_column_in_dataset,\n",
" template=template,\n",
" use_multiprocessing=False,\n",
" tokenizer=tokenizer,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"language": "python",
"metadata": {
"cellView": "form",
"id": "ivVGS9dHXPOz"
@@ -272,21 +397,48 @@
"outputs": [],
"source": [
"# @title Finetune\n",
"# @markdown Use the Vertex AI SDK to create and run the custom training jobs.\n",
"\n",
"# @markdown This section demonstrates how to finetune the Gemma 2 model and merge the finetuned LoRA adapter with the base model on Vertex AI. It uses the Vertex AI SDK to create and run the custom training jobs.\n",
"\n",
"# @markdown The training job takes approximately between 10 to 20 mins to set-up. Once done, the training job is expected to take around 20 mins with the default configuration. To find the training time, throughput, and memory usage of your training job, you can go to the training logs and check the log line of the last training epoch.\n",
"\n",
"# @markdown **Note**:\n",
"# @markdown 1. We recommend setting `finetuning_precision_mode` to `4bit` because it enables using fewer hardware resources for finetuning.\n",
"# @markdown 1. If `max_steps > 0`, it takes precedence over `epochs`. One can set a small `max_steps` value to quickly check the pipeline.\n",
"# @markdown 1. With the default setting, training takes between 1 ~ 1.5 hours.\n",
"\n",
"# @markdown This section demonstrates how to finetune the Gemma 2 model and merge the finetuned LoRA adapter with the base model on Vertex AI.\n",
"\n",
"# @markdown Select one of the four model variations.\n",
"base_model_id = \"gemma-2-2b-it\" # @param [\"gemma-2-2b\", \"gemma-2-2b-it\", \"gemma-2-9b\", \"gemma-2-9b-it\", \"gemma-2-27b\", \"gemma-2-27b-it\"] {allow-input: true, isTemplate: true}\n",
"pretrained_model_id = os.path.join(model_path_prefix, base_model_id)\n",
"# @markdown Accelerator type to use for training.\n",
"accelerator_type = \"NVIDIA_A100_80GB\" # @param [\"NVIDIA_A100_80GB\", \"NVIDIA_H100_80GB\"]\n",
"\n",
"# The pre-built training docker image.\n",
"TRAIN_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-peft-train:20240724_0936_RC00\"\n",
"if accelerator_type == \"NVIDIA_A100_80GB\":\n",
" repo = \"us-docker.pkg.dev/vertex-ai-restricted\"\n",
" is_restricted_image = True\n",
" is_dynamic_workload_scheduler = False\n",
" dws_kwargs = {}\n",
"else:\n",
" repo = \"us-docker.pkg.dev/vertex-ai\"\n",
" is_restricted_image = False\n",
" is_dynamic_workload_scheduler = True\n",
" dws_kwargs = {\n",
" \"max_wait_duration\": 1800, # 30 minutes\n",
" \"scheduling_strategy\": gca_custom_job_compat.Scheduling.Strategy.FLEX_START,\n",
" }\n",
"\n",
"TRAIN_DOCKER_URI = (\n",
" f\"{repo}/vertex-vision-model-garden-dockers/pytorch-peft-train:stable_20240909\"\n",
")\n",
"\n",
"# Worker pool spec.\n",
"if accelerator_type == \"NVIDIA_A100_80GB\":\n",
" per_node_accelerator_count = 8\n",
" machine_type = \"a2-ultragpu-8g\"\n",
"elif accelerator_type == \"NVIDIA_H100_80GB\":\n",
" per_node_accelerator_count = 8\n",
" machine_type = \"a3-highgpu-8g\"\n",
"else:\n",
" raise ValueError(\n",
" f\"Recommended machine settings not found for: {accelerator_type}. To use another accelerator type, edit this code block to pass in an appropriate `machine_type`, `accelerator_type`, and `per_node_accelerator_count` to the deploy_model_vllm function by clicking `Show Code` and then modifying the code.\"\n",
" )\n",
"\n",
"# @markdown Batch size for finetuning.\n",
"per_device_train_batch_size = 1 # @param{type:\"integer\"}\n",
@@ -324,23 +476,16 @@
"# Train precision of the model.\n",
"train_precision = \"bfloat16\"\n",
"\n",
"# Worker pool spec for 4bit finetuning.\n",
"accelerator_type = \"NVIDIA_A100_80GB\" # @param[\"NVIDIA_A100_80GB\"]\n",
"\n",
"if accelerator_type == \"NVIDIA_A100_80GB\":\n",
" accelerator_count = 8\n",
" machine_type = \"a2-ultragpu-8g\"\n",
"else:\n",
" raise ValueError(f\"Unsupported accelerator type: {accelerator_type}\")\n",
"\n",
"replica_count = 1\n",
"\n",
"common_util.check_quota(\n",
" project_id=PROJECT_ID,\n",
" region=REGION,\n",
" accelerator_type=accelerator_type,\n",
" accelerator_count=accelerator_count,\n",
" accelerator_count=per_node_accelerator_count * replica_count,\n",
" is_for_training=True,\n",
" is_restricted_image=is_restricted_image,\n",
" is_dynamic_workload_scheduler=is_dynamic_workload_scheduler,\n",
")\n",
"\n",
"job_name = common_util.get_job_name_with_datetime(\"gemma2-lora-train\")\n",
@@ -352,6 +497,16 @@
"# finetuned LORA adapter.\n",
"merged_model_output_dir = os.path.join(base_output_dir, \"merged-model\")\n",
"\n",
"# Add labels for the finetuning job.\n",
"labels = {\n",
" \"mg-source\": \"notebook\",\n",
" \"mg-notebook-name\": \"model_garden_gemma2_finetuning_on_vertex.ipynb\".split(\".\")[0],\n",
"}\n",
"\n",
"labels[\"mg-tune\"] = \"publishers-google-models-gemma-2\"\n",
"versioned_model_id = base_model_id.lower().replace(\".\", \"-\")\n",
"labels[\"versioned-mg-tune\"] = f\"{labels['mg-tune']}-{versioned_model_id}\"\n",
"\n",
"eval_args = [\n",
" f\"--eval_dataset_path={eval_dataset_name}\",\n",
" f\"--eval_column={instruct_column_in_dataset}\",\n",
@@ -363,7 +518,7 @@
"]\n",
"\n",
"train_job_args = [\n",
" \"--config_file=vertex_vision_model_garden_peft/deepspeed_zero2_4gpu.yaml\",\n",
" \"--config_file=vertex_vision_model_garden_peft/deepspeed_zero2_8gpu.yaml\",\n",
" \"--task=instruct-lora\",\n",
" \"--completion_only=True\",\n",
" f\"--pretrained_model_id={pretrained_model_id}\",\n",
@@ -396,33 +551,33 @@
" f\"--huggingface_access_token={HF_TOKEN}\",\n",
"] + eval_args\n",
"\n",
"# Create TensorBoard\n",
"tensorboard = aiplatform.Tensorboard.create(job_name)\n",
"exp = aiplatform.TensorboardExperiment.create(\n",
" tensorboard_experiment_id=job_name, tensorboard_name=tensorboard.name\n",
")\n",
"\n",
"# Pass training arguments and launch job.\n",
"train_job = aiplatform.CustomContainerTrainingJob(\n",
" display_name=job_name,\n",
" container_uri=TRAIN_DOCKER_URI,\n",
" labels=labels,\n",
")\n",
"\n",
"print(\"Running training job with args:\")\n",
"print(\" \\\\\\n\".join(train_job_args))\n",
"train_job.run(\n",
" args=train_job_args,\n",
" environment_variables={\"WANDB_DISABLED\": True},\n",
" replica_count=replica_count,\n",
" machine_type=machine_type,\n",
" accelerator_type=accelerator_type,\n",
" accelerator_count=accelerator_count,\n",
" accelerator_count=per_node_accelerator_count,\n",
" boot_disk_size_gb=500,\n",
" service_account=SERVICE_ACCOUNT,\n",
" tensorboard=tensorboard.resource_name,\n",
" base_output_dir=base_output_dir,\n",
" sync=False, # Non-blocking call to run.\n",
" **dws_kwargs,\n",
")\n",
"\n",
"print(\"LoRA adapter was saved in:\", lora_output_dir)\n",
"print(\"Trained and merged models were saved in:\", merged_model_output_dir)\n",
"# Wait until resource has been created.\n",
"train_job.wait_for_resource_creation()\n",
"\n",
"print(\"LoRA adapter will be saved in:\", lora_output_dir)\n",
"print(\"Trained and merged models will be saved in:\", merged_model_output_dir)\n",
"\n",
"# @markdown Click \"Show Code\" to see more details."
]
@@ -430,6 +585,27 @@
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"id": "lu-uxrFBmZ0s"
},
"outputs": [],
"source": [
"# @title Run TensorBoard\n",
"# @markdown This section shows how to launch TensorBoard in a [Cloud Shell](https://cloud.google.com/shell/docs).\n",
"# @markdown 1. Click the Cloud Shell icon(![terminal](https://github.com/google/material-design-icons/blob/master/png/action/terminal/materialicons/24dp/1x/baseline_terminal_black_24dp.png?raw=true)) on the top right to open the Cloud Shell.\n",
"# @markdown 2. Copy the `tensorboard` command shown below by running this cell.\n",
"# @markdown 3. Paste and run the command in the Cloud Shell to launch TensorBoard.\n",
"# @markdown 4. Once the command runs (You may have to click `Authorize` if prompted), click the link starting with `http://localhost`.\n",
"\n",
"# @markdown Note: You may need to wait around 10 minutes after the job starts in order for the TensorBoard logs to be written to the GCS bucket.\n",
"print(f\"Command to copy: tensorboard --logdir {base_output_dir}/logs\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"language": "python",
"metadata": {
"cellView": "form",
"id": "qmHW6m8xG_4U"
@@ -439,16 +615,20 @@
"# @title Deploy\n",
"# @markdown This section uploads the model to Model Registry and deploys it on the Endpoint. It takes 15 minutes to 1 hour to finish.\n",
"\n",
"if train_job.end_time is None:\n",
" print(\"Waiting for the training job to finish...\")\n",
" train_job.wait()\n",
" print(\"The training job has finished.\")\n",
"\n",
"print(\"Deploying models in:\", merged_model_output_dir)\n",
"\n",
"# The pre-built serving docker image for vLLM.\n",
"VLLM_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-vllm-serve:20240815_1634_RC00\"\n",
"\n",
"# Find Vertex AI prediction supported accelerators and regions [here](https://cloud.google.com/vertex-ai/docs/predictions/configure-compute).\n",
"# @markdown Accelerator type to use for serving.\n",
"accelerator_type = \"NVIDIA_L4\" # @param [\"NVIDIA_L4\"] {isTemplate: true}\n",
"\n",
"# Find Vertex AI prediction supported accelerators and regions in\n",
"# https://cloud.google.com/vertex-ai/docs/predictions/configure-compute.\n",
"\n",
"if \"2b\" in base_model_id:\n",
" if accelerator_type == \"NVIDIA_L4\":\n",
" # Sets 1 L4 (24G) to deploy Gemma 2 2B models.\n",
@@ -492,6 +672,9 @@
" is_for_training=False,\n",
")\n",
"\n",
"# @markdown Set use_dedicated_endpoint to True if the endpoint is [dedicated endpoint](https://cloud.google.com/vertex-ai/docs/general/deployment#create-dedicated-endpoint) enabled.\n",
"use_dedicated_endpoint = False # @param {type:\"boolean\"}\n",
"\n",
"gpu_memory_utilization = 0.85\n",
"max_model_len = 4096 # Maximum context length.\n",
"\n",
@@ -507,28 +690,55 @@
" gpu_memory_utilization: float = 0.9,\n",
" max_model_len: int = 4096,\n",
" dtype: str = \"auto\",\n",
" enable_trust_remote_code: bool = False,\n",
" enforce_eager: bool = False,\n",
" enable_lora: bool = False,\n",
" max_loras: int = 1,\n",
" max_cpu_loras: int = 8,\n",
" use_dedicated_endpoint: bool = False,\n",
" max_num_seqs: int = 256,\n",
" model_type: str = None,\n",
") -> Tuple[aiplatform.Model, aiplatform.Endpoint]:\n",
" \"\"\"Deploys trained models with vLLM into Vertex AI.\"\"\"\n",
" endpoint = aiplatform.Endpoint.create(display_name=f\"{model_name}-endpoint\")\n",
" endpoint = aiplatform.Endpoint.create(\n",
" display_name=f\"{model_name}-endpoint\",\n",
" dedicated_endpoint_enabled=use_dedicated_endpoint,\n",
" )\n",
"\n",
" if not base_model_id:\n",
" base_model_id = model_id\n",
"\n",
" # See https://docs.vllm.ai/en/latest/models/engine_args.html for a list of possible arguments with descriptions.\n",
" vllm_args = [\n",
" \"python\",\n",
" \"-m\",\n",
" \"vllm.entrypoints.api_server\",\n",
" \"--host=0.0.0.0\",\n",
" \"--port=7080\",\n",
" \"--port=8080\",\n",
" f\"--model={model_id}\",\n",
" f\"--tensor-parallel-size={accelerator_count}\",\n",
" \"--swap-space=16\",\n",
" f\"--gpu-memory-utilization={gpu_memory_utilization}\",\n",
" f\"--max-model-len={max_model_len}\",\n",
" f\"--dtype={dtype}\",\n",
" f\"--max-loras={max_loras}\",\n",
" f\"--max-cpu-loras={max_cpu_loras}\",\n",
" f\"--max-num-seqs={max_num_seqs}\",\n",
" \"--disable-log-stats\",\n",
" ]\n",
"\n",
" if enable_trust_remote_code:\n",
" vllm_args.append(\"--trust-remote-code\")\n",
"\n",
" if enforce_eager:\n",
" vllm_args.append(\"--enforce-eager\")\n",
"\n",
" if enable_lora:\n",
" vllm_args.append(\"--enable-lora\")\n",
"\n",
" if model_type:\n",
" vllm_args.append(f\"--model-type={model_type}\")\n",
"\n",
" env_vars = {\n",
" \"MODEL_ID\": base_model_id,\n",
" \"DEPLOY_SOURCE\": \"notebook\",\n",
@@ -545,7 +755,7 @@
" display_name=model_name,\n",
" serving_container_image_uri=VLLM_DOCKER_URI,\n",
" serving_container_args=vllm_args,\n",
" serving_container_ports=[7080],\n",
" serving_container_ports=[8080],\n",
" serving_container_predict_route=\"/generate\",\n",
" serving_container_health_route=\"/ping\",\n",
" serving_container_environment_variables=env_vars,\n",
@@ -577,6 +787,7 @@
" accelerator_count=accelerator_count,\n",
" gpu_memory_utilization=gpu_memory_utilization,\n",
" max_model_len=max_model_len,\n",
" use_dedicated_endpoint=use_dedicated_endpoint,\n",
")\n",
"\n",
"# @markdown Click \"Show code\" to see more details."
@@ -618,11 +829,12 @@
"# endpoint = aiplatform.Endpoint(aip_endpoint_name)\n",
"\n",
"prompt = \"What is a car?\" # @param {type: \"string\"}\n",
"# @markdown If you encounter the issue like `ServiceUnavailable: 503 Took too long to respond when processing`, you can reduce the maximum number of output tokens, such as set `max_tokens` as 20.\n",
"# @markdown If you encounter an issue like `ServiceUnavailable: 503 Took too long to respond when processing`, you can reduce the maximum number of output tokens, by lowering `max_tokens`.\n",
"max_tokens = 50 # @param {type:\"integer\"}\n",
"temperature = 1.0 # @param {type:\"number\"}\n",
"top_p = 1.0 # @param {type:\"number\"}\n",
"top_k = 1 # @param {type:\"integer\"}\n",
"# @markdown Set `raw_response` to `True` to obtain the raw model output. Set `raw_response` to `False` to apply additional formatting in the structure of `\"Prompt:\\n{prompt.strip()}\\nOutput:\\n{output}\"`.\n",
"raw_response = False # @param {type:\"boolean\"}\n",
"\n",
"# Overrides parameters for inferences.\n",
@@ -636,7 +848,9 @@
" \"raw_response\": raw_response,\n",
" },\n",
"]\n",
"response = endpoints[\"vllm_gpu\"].predict(instances=instances)\n",
"response = endpoints[\"vllm_gpu\"].predict(\n",
" instances=instances, use_dedicated_endpoint=use_dedicated_endpoint\n",
")\n",
"\n",
"for prediction in response.predictions:\n",
" print(prediction)\n",

Some files were not shown because too many files have changed in this diff Show More