From a494bd244e29ad042742a89b022624359462c5db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Mon, 26 Aug 2024 23:59:21 +0200 Subject: [PATCH 01/69] Add initial setup --- .pre-commit-config.yaml | 19 + poetry.lock | 1867 ---------------------------- pyproject.toml | 52 - requirements/requirements.docs.txt | 2 + requirements/requirements.test.txt | 5 + requirements/requirements.txt | 4 + setup.py | 66 + 7 files changed, 96 insertions(+), 1919 deletions(-) create mode 100644 .pre-commit-config.yaml delete mode 100644 poetry.lock delete mode 100644 pyproject.toml create mode 100644 requirements/requirements.docs.txt create mode 100644 requirements/requirements.test.txt create mode 100644 requirements/requirements.txt create mode 100644 setup.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ed3e8f3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/psf/black + rev: 24.8.0 + hooks: + - id: black +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.11.2 + hooks: + - id: mypy +- repo: https://github.com/PyCQA/flake8 + rev: 7.1.1 + hooks: + - id: flake8 diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 732c059..0000000 --- a/poetry.lock +++ /dev/null @@ -1,1867 +0,0 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. - -[[package]] -name = "babel" -version = "2.13.1" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, - {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, -] - -[package.dependencies] -pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} - -[package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] - -[[package]] -name = "certifi" -version = "2023.11.17" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "contourpy" -version = "1.1.1" -description = "Python library for calculating contours of 2D quadrilateral grids" -optional = false -python-versions = ">=3.8" -files = [ - {file = "contourpy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:46e24f5412c948d81736509377e255f6040e94216bf1a9b5ea1eaa9d29f6ec1b"}, - {file = "contourpy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e48694d6a9c5a26ee85b10130c77a011a4fedf50a7279fa0bdaf44bafb4299d"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a66045af6cf00e19d02191ab578a50cb93b2028c3eefed999793698e9ea768ae"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ebf42695f75ee1a952f98ce9775c873e4971732a87334b099dde90b6af6a916"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6aec19457617ef468ff091669cca01fa7ea557b12b59a7908b9474bb9674cf0"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:462c59914dc6d81e0b11f37e560b8a7c2dbab6aca4f38be31519d442d6cde1a1"}, - {file = "contourpy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6d0a8efc258659edc5299f9ef32d8d81de8b53b45d67bf4bfa3067f31366764d"}, - {file = "contourpy-1.1.1-cp310-cp310-win32.whl", hash = "sha256:d6ab42f223e58b7dac1bb0af32194a7b9311065583cc75ff59dcf301afd8a431"}, - {file = "contourpy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:549174b0713d49871c6dee90a4b499d3f12f5e5f69641cd23c50a4542e2ca1eb"}, - {file = "contourpy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:407d864db716a067cc696d61fa1ef6637fedf03606e8417fe2aeed20a061e6b2"}, - {file = "contourpy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe80c017973e6a4c367e037cb31601044dd55e6bfacd57370674867d15a899b"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e30aaf2b8a2bac57eb7e1650df1b3a4130e8d0c66fc2f861039d507a11760e1b"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3de23ca4f381c3770dee6d10ead6fff524d540c0f662e763ad1530bde5112532"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:566f0e41df06dfef2431defcfaa155f0acfa1ca4acbf8fd80895b1e7e2ada40e"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04c2f0adaf255bf756cf08ebef1be132d3c7a06fe6f9877d55640c5e60c72c5"}, - {file = "contourpy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0c188ae66b772d9d61d43c6030500344c13e3f73a00d1dc241da896f379bb62"}, - {file = "contourpy-1.1.1-cp311-cp311-win32.whl", hash = "sha256:0683e1ae20dc038075d92e0e0148f09ffcefab120e57f6b4c9c0f477ec171f33"}, - {file = "contourpy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:8636cd2fc5da0fb102a2504fa2c4bea3cbc149533b345d72cdf0e7a924decc45"}, - {file = "contourpy-1.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:560f1d68a33e89c62da5da4077ba98137a5e4d3a271b29f2f195d0fba2adcb6a"}, - {file = "contourpy-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24216552104ae8f3b34120ef84825400b16eb6133af2e27a190fdc13529f023e"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56de98a2fb23025882a18b60c7f0ea2d2d70bbbcfcf878f9067234b1c4818442"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:07d6f11dfaf80a84c97f1a5ba50d129d9303c5b4206f776e94037332e298dda8"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1eaac5257a8f8a047248d60e8f9315c6cff58f7803971170d952555ef6344a7"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19557fa407e70f20bfaba7d55b4d97b14f9480856c4fb65812e8a05fe1c6f9bf"}, - {file = "contourpy-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:081f3c0880712e40effc5f4c3b08feca6d064cb8cfbb372ca548105b86fd6c3d"}, - {file = "contourpy-1.1.1-cp312-cp312-win32.whl", hash = "sha256:059c3d2a94b930f4dafe8105bcdc1b21de99b30b51b5bce74c753686de858cb6"}, - {file = "contourpy-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f44d78b61740e4e8c71db1cf1fd56d9050a4747681c59ec1094750a658ceb970"}, - {file = "contourpy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70e5a10f8093d228bb2b552beeb318b8928b8a94763ef03b858ef3612b29395d"}, - {file = "contourpy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8394e652925a18ef0091115e3cc191fef350ab6dc3cc417f06da66bf98071ae9"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bd5680f844c3ff0008523a71949a3ff5e4953eb7701b28760805bc9bcff217"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66544f853bfa85c0d07a68f6c648b2ec81dafd30f272565c37ab47a33b220684"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0c02b75acfea5cab07585d25069207e478d12309557f90a61b5a3b4f77f46ce"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41339b24471c58dc1499e56783fedc1afa4bb018bcd035cfb0ee2ad2a7501ef8"}, - {file = "contourpy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f29fb0b3f1217dfe9362ec55440d0743fe868497359f2cf93293f4b2701b8251"}, - {file = "contourpy-1.1.1-cp38-cp38-win32.whl", hash = "sha256:f9dc7f933975367251c1b34da882c4f0e0b2e24bb35dc906d2f598a40b72bfc7"}, - {file = "contourpy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:498e53573e8b94b1caeb9e62d7c2d053c263ebb6aa259c81050766beb50ff8d9"}, - {file = "contourpy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ba42e3810999a0ddd0439e6e5dbf6d034055cdc72b7c5c839f37a7c274cb4eba"}, - {file = "contourpy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c06e4c6e234fcc65435223c7b2a90f286b7f1b2733058bdf1345d218cc59e34"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6fab080484e419528e98624fb5c4282148b847e3602dc8dbe0cb0669469887"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93df44ab351119d14cd1e6b52a5063d3336f0754b72736cc63db59307dabb718"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eafbef886566dc1047d7b3d4b14db0d5b7deb99638d8e1be4e23a7c7ac59ff0f"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe0fab26d598e1ec07d72cf03eaeeba8e42b4ecf6b9ccb5a356fde60ff08b85"}, - {file = "contourpy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f08e469821a5e4751c97fcd34bcb586bc243c39c2e39321822060ba902eac49e"}, - {file = "contourpy-1.1.1-cp39-cp39-win32.whl", hash = "sha256:bfc8a5e9238232a45ebc5cb3bfee71f1167064c8d382cadd6076f0d51cff1da0"}, - {file = "contourpy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:c84fdf3da00c2827d634de4fcf17e3e067490c4aea82833625c4c8e6cdea0887"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:229a25f68046c5cf8067d6d6351c8b99e40da11b04d8416bf8d2b1d75922521e"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10dab5ea1bd4401c9483450b5b0ba5416be799bbd50fc7a6cc5e2a15e03e8a3"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f9147051cb8fdb29a51dc2482d792b3b23e50f8f57e3720ca2e3d438b7adf23"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a75cc163a5f4531a256f2c523bd80db509a49fc23721b36dd1ef2f60ff41c3cb"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b53d5769aa1f2d4ea407c65f2d1d08002952fac1d9e9d307aa2e1023554a163"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11b836b7dbfb74e049c302bbf74b4b8f6cb9d0b6ca1bf86cfa8ba144aedadd9c"}, - {file = "contourpy-1.1.1.tar.gz", hash = "sha256:96ba37c2e24b7212a77da85004c38e7c4d155d3e72a45eeaf22c1f03f607e8ab"}, -] - -[package.dependencies] -numpy = {version = ">=1.16,<2.0", markers = "python_version <= \"3.11\""} - -[package.extras] -bokeh = ["bokeh", "selenium"] -docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.4.1)", "types-Pillow"] -test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "wurlitzer"] - -[[package]] -name = "cycler" -version = "0.12.1" -description = "Composable style cycles" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, - {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, -] - -[package.extras] -docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] -tests = ["pytest", "pytest-cov", "pytest-xdist"] - -[[package]] -name = "exceptiongroup" -version = "1.2.0" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "filelock" -version = "3.13.1" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.8" -files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] -typing = ["typing-extensions (>=4.8)"] - -[[package]] -name = "fonttools" -version = "4.45.1" -description = "Tools to manipulate font files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fonttools-4.45.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:45fa321c458ea29224067700954ec44493ae869b47e7c5485a350a149a19fb53"}, - {file = "fonttools-4.45.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0dc7617d96b1e668eea9250e1c1fe62d0c78c3f69573ce7e3332cc40e6d84356"}, - {file = "fonttools-4.45.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ed3bda541e86725f6b4e1b94213f13ed1ae51a5a1f167028534cedea38c010"}, - {file = "fonttools-4.45.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f4a5870e3b56788fb196da8cf30d0dfd51a76dc3b907861d018165f76ae4c2"}, - {file = "fonttools-4.45.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a3c11d9687479f01eddef729aa737abcdea0a44fdaffb62a930a18892f186c9b"}, - {file = "fonttools-4.45.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:316cec50581e844c3ab69d7c82455b54c7cf18236b2f09e722faf665fbfcac58"}, - {file = "fonttools-4.45.1-cp310-cp310-win32.whl", hash = "sha256:e2277cba9f0b525e30de2a9ad3cb4219aa4bc697230c1645666b0deee9f914f0"}, - {file = "fonttools-4.45.1-cp310-cp310-win_amd64.whl", hash = "sha256:1b9e9ad2bcded9a1431afaa57c8d3c39143ac1f050862d66bddd863c515464a2"}, - {file = "fonttools-4.45.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff6a698bdd435d24c379f6e8a54908cd9bb7dda23719084d56bf8c87709bf3bd"}, - {file = "fonttools-4.45.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c980d60cd6ec1376206fe55013d166e5627ad0b149b5c81e74eaa913ab6134f"}, - {file = "fonttools-4.45.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a12dee6523c02ca78aeedd0a5e12bfa9b7b29896350edd5241542897b072ae23"}, - {file = "fonttools-4.45.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37cd1ced6efb3dd6fe82e9f9bf92fd74ac58a5aefc284045f59ecd517a5fb9ab"}, - {file = "fonttools-4.45.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3d24248221bd7151dfff0d88b1b5da02dccd7134bd576ce8888199827bbaa19"}, - {file = "fonttools-4.45.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba6c23591427844dfb0a13658f1718489de75de6a46b64234584c0d17573162d"}, - {file = "fonttools-4.45.1-cp311-cp311-win32.whl", hash = "sha256:cebcddbe9351b67166292b4f71ffdbfcce01ba4b07d4267824eb46b277aeb19a"}, - {file = "fonttools-4.45.1-cp311-cp311-win_amd64.whl", hash = "sha256:f22eb69996a0bd49f76bdefb30be54ce8dbb89a0d1246874d610f05c2aa2e69e"}, - {file = "fonttools-4.45.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:794de93e83297db7b4943f2431e206d8b1ea69cb3ae14638a49cc50332bf0db8"}, - {file = "fonttools-4.45.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4ba17822a6681d06849078daaf6e03eccc9f467efe7c4c60280e28a78e8e5df9"}, - {file = "fonttools-4.45.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e50f794d09df0675da8d9dbd7c66bfcab2f74a708343aabcad41936d26556891"}, - {file = "fonttools-4.45.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b07b857d4f9de3199a8c3d1b1bf2078c0f37447891ca1a8d9234106b9a27aff"}, - {file = "fonttools-4.45.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:777ba42b94a27bb7fb2b4082522fccfd345667c32a56011e1c3e105979af5b79"}, - {file = "fonttools-4.45.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:21e96b99878348c74aa58059b8578d7586f9519cbcdadacf56486737038aa043"}, - {file = "fonttools-4.45.1-cp312-cp312-win32.whl", hash = "sha256:5cbf02cda8465b69769d07385f5d11e7bba19954e7787792f46fe679ec755ebb"}, - {file = "fonttools-4.45.1-cp312-cp312-win_amd64.whl", hash = "sha256:800e354e0c3afaeb8d9552769773d02f228e98c37b8cb03041157c3d0687cffc"}, - {file = "fonttools-4.45.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6eb2c54f7a07c92108daabcf02caf31df97825738db02a28270633946bcda4d0"}, - {file = "fonttools-4.45.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43a3d267334109ff849c37cf3629476b5feb392ef1d2e464a167b83de8cd599c"}, - {file = "fonttools-4.45.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e1aefc2bf3c43e0f33f995f828a7bbeff4adc9393a7760b11456dbcf14388f6"}, - {file = "fonttools-4.45.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f53a19dcdd5737440839b8394eeebb35da9ec8109f7926cb6456639b5b58e47"}, - {file = "fonttools-4.45.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a17706b9cc24b27721613fe5773d93331ab7f0ecaca9955aead89c6b843d3a7"}, - {file = "fonttools-4.45.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fb36e5f40191274a95938b40c0a1fa7f895e36935aea8709e1d6deff0b2d0d4f"}, - {file = "fonttools-4.45.1-cp38-cp38-win32.whl", hash = "sha256:46eabddec12066829b8a1efe45ae552ba2f1796981ecf538d5f68284c354c589"}, - {file = "fonttools-4.45.1-cp38-cp38-win_amd64.whl", hash = "sha256:b6de2f0fcd3302fb82f94801002cb473959e998c14c24ec28234adb674aed345"}, - {file = "fonttools-4.45.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:392d0e3cc23daee910193625f7cf1b387aff9dd5b6f1a5f4a925680acb6dcbc2"}, - {file = "fonttools-4.45.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b9544b1346d99848ac0e9b05b5d45ee703d7562fc4c9c48cf4b781de9632e57"}, - {file = "fonttools-4.45.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8717db3e4895e4820ade64ea379187738827ee60748223cb0438ef044ee208c6"}, - {file = "fonttools-4.45.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e29d5f298d616a93a4c5963682dc6cc8cc09f6d89cad2c29019fc5fb3b4d9472"}, - {file = "fonttools-4.45.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cb472905da3049960e80fc1cf808231880d79727a8410e156bf3e5063a1c574f"}, - {file = "fonttools-4.45.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ba299f1fbaa2a1e33210aaaf6fa816d4059e4d3cfe2ae9871368d4ab548c1c6a"}, - {file = "fonttools-4.45.1-cp39-cp39-win32.whl", hash = "sha256:105099968b58a5b4cef6f3eb409db8ea8578b302a9d05e23fecba1b8b0177b5f"}, - {file = "fonttools-4.45.1-cp39-cp39-win_amd64.whl", hash = "sha256:847f3f49dd3423e5a678c098e2ba92c7f4955d4aab3044f6a507b0bb0ecb07e0"}, - {file = "fonttools-4.45.1-py3-none-any.whl", hash = "sha256:3bdd7dfca8f6c9f4779384064027e8477ad6a037d6a327b09381f43e0247c6f3"}, - {file = "fonttools-4.45.1.tar.gz", hash = "sha256:6e441286d55fe7ec7c4fb36812bf914924813776ff514b744b510680fc2733f2"}, -] - -[package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] -graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "scipy"] -lxml = ["lxml (>=4.0,<5)"] -pathops = ["skia-pathops (>=0.5.0)"] -plot = ["matplotlib"] -repacker = ["uharfbuzz (>=0.23.0)"] -symfont = ["sympy"] -type1 = ["xattr"] -ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.1.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] - -[[package]] -name = "fsspec" -version = "2023.10.0" -description = "File-system specification" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fsspec-2023.10.0-py3-none-any.whl", hash = "sha256:346a8f024efeb749d2a5fca7ba8854474b1ff9af7c3faaf636a4548781136529"}, - {file = "fsspec-2023.10.0.tar.gz", hash = "sha256:330c66757591df346ad3091a53bd907e15348c2ba17d63fd54f5c39c4457d2a5"}, -] - -[package.extras] -abfs = ["adlfs"] -adl = ["adlfs"] -arrow = ["pyarrow (>=1)"] -dask = ["dask", "distributed"] -devel = ["pytest", "pytest-cov"] -dropbox = ["dropbox", "dropboxdrivefs", "requests"] -full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] -fuse = ["fusepy"] -gcs = ["gcsfs"] -git = ["pygit2"] -github = ["requests"] -gs = ["gcsfs"] -gui = ["panel"] -hdfs = ["pyarrow (>=1)"] -http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "requests"] -libarchive = ["libarchive-c"] -oci = ["ocifs"] -s3 = ["s3fs"] -sftp = ["paramiko"] -smb = ["smbprotocol"] -ssh = ["paramiko"] -tqdm = ["tqdm"] - -[[package]] -name = "ghp-import" -version = "2.1.0" -description = "Copy your docs directly to the gh-pages branch." -optional = false -python-versions = "*" -files = [ - {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, - {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, -] - -[package.dependencies] -python-dateutil = ">=2.8.1" - -[package.extras] -dev = ["flake8", "markdown", "twine", "wheel"] - -[[package]] -name = "griffe" -version = "0.38.0" -description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." -optional = false -python-versions = ">=3.8" -files = [ - {file = "griffe-0.38.0-py3-none-any.whl", hash = "sha256:6a5bc457320e8e199006aa5fbb03e162f5e21abe31aa6221f7a5c37ea0724c71"}, - {file = "griffe-0.38.0.tar.gz", hash = "sha256:9b97487b583042b543d1e28196caee638ecd766c8c4c98135071806cb5333ac2"}, -] - -[package.dependencies] -colorama = ">=0.4" - -[[package]] -name = "huggingface-hub" -version = "0.19.4" -description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "huggingface_hub-0.19.4-py3-none-any.whl", hash = "sha256:dba013f779da16f14b606492828f3760600a1e1801432d09fe1c33e50b825bb5"}, - {file = "huggingface_hub-0.19.4.tar.gz", hash = "sha256:176a4fc355a851c17550e7619488f383189727eab209534d7cef2114dae77b22"}, -] - -[package.dependencies] -filelock = "*" -fsspec = ">=2023.5.0" -packaging = ">=20.9" -pyyaml = ">=5.1" -requests = "*" -tqdm = ">=4.42.1" -typing-extensions = ">=3.7.4.3" - -[package.extras] -all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "jedi", "mypy (==1.5.1)", "numpy", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-vcr", "pytest-xdist", "ruff (>=0.1.3)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] -cli = ["InquirerPy (==0.3.4)"] -dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "jedi", "mypy (==1.5.1)", "numpy", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-vcr", "pytest-xdist", "ruff (>=0.1.3)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] -docs = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "hf-doc-builder", "jedi", "mypy (==1.5.1)", "numpy", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-vcr", "pytest-xdist", "ruff (>=0.1.3)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)", "watchdog"] -fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] -inference = ["aiohttp", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)"] -quality = ["mypy (==1.5.1)", "ruff (>=0.1.3)"] -tensorflow = ["graphviz", "pydot", "tensorflow"] -testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "jedi", "numpy", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] -torch = ["torch"] -typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] - -[[package]] -name = "idna" -version = "3.6" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, -] - -[[package]] -name = "importlib-metadata" -version = "6.8.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - -[[package]] -name = "importlib-resources" -version = "6.1.1" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"}, - {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, -] - -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "jinja2" -version = "3.1.2" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "kiwisolver" -version = "1.4.5" -description = "A fast implementation of the Cassowary constraint solver" -optional = false -python-versions = ">=3.7" -files = [ - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, - {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, -] - -[[package]] -name = "markdown" -version = "3.5.1" -description = "Python implementation of John Gruber's Markdown." -optional = false -python-versions = ">=3.8" -files = [ - {file = "Markdown-3.5.1-py3-none-any.whl", hash = "sha256:5874b47d4ee3f0b14d764324d2c94c03ea66bee56f2d929da9f2508d65e722dc"}, - {file = "Markdown-3.5.1.tar.gz", hash = "sha256:b65d7beb248dc22f2e8a31fb706d93798093c308dc1aba295aedeb9d41a813bd"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] -testing = ["coverage", "pyyaml"] - -[[package]] -name = "markupsafe" -version = "2.1.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] - -[[package]] -name = "matplotlib" -version = "3.7.4" -description = "Python plotting package" -optional = false -python-versions = ">=3.8" -files = [ - {file = "matplotlib-3.7.4-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:b71079239bd866bf56df023e5146de159cb0c7294e508830901f4d79e2d89385"}, - {file = "matplotlib-3.7.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bf91a42f6274a64cb41189120b620c02e574535ff6671fa836cade7701b06fbd"}, - {file = "matplotlib-3.7.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f757e8b42841d6add0cb69b42497667f0d25a404dcd50bd923ec9904e38414c4"}, - {file = "matplotlib-3.7.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dfee00aa4bd291e08bb9461831c26ce0da85ca9781bb8794f2025c6e925281"}, - {file = "matplotlib-3.7.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3640f33632beb3993b698b1be9d1c262b742761d6101f3c27b87b2185d25c875"}, - {file = "matplotlib-3.7.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff539c4a17ecdf076ed808ee271ffae4a30dcb7e157b99ccae2c837262c07db6"}, - {file = "matplotlib-3.7.4-cp310-cp310-win32.whl", hash = "sha256:24b8f28af3e766195c09b780b15aa9f6710192b415ae7866b9c03dee7ec86370"}, - {file = "matplotlib-3.7.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fa193286712c3b6c3cfa5fe8a6bb563f8c52cc750006c782296e0807ce5e799"}, - {file = "matplotlib-3.7.4-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:b167f54cb4654b210c9624ec7b54e2b3b8de68c93a14668937e7e53df60770ec"}, - {file = "matplotlib-3.7.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7dfe6821f1944cb35603ff22e21510941bbcce7ccf96095beffaac890d39ce77"}, - {file = "matplotlib-3.7.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3c557d9165320dff3c5f2bb99bfa0b6813d3e626423ff71c40d6bc23b83c3339"}, - {file = "matplotlib-3.7.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08372696b3bb45c563472a552a705bfa0942f0a8ffe084db8a4e8f9153fbdf9d"}, - {file = "matplotlib-3.7.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81e1a7ac818000e8ac3ca696c3fdc501bc2d3adc89005e7b4e22ee5e9d51de98"}, - {file = "matplotlib-3.7.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:390920a3949906bc4b0216198d378f2a640c36c622e3584dd0c79a7c59ae9f50"}, - {file = "matplotlib-3.7.4-cp311-cp311-win32.whl", hash = "sha256:62e094d8da26294634da9e7f1856beee3978752b1b530c8e1763d2faed60cc10"}, - {file = "matplotlib-3.7.4-cp311-cp311-win_amd64.whl", hash = "sha256:f8fc2df756105784e650605e024d36dc2d048d68e5c1b26df97ee25d1bd41f9f"}, - {file = "matplotlib-3.7.4-cp312-cp312-macosx_10_12_universal2.whl", hash = "sha256:568574756127791903604e315c11aef9f255151e4cfe20ec603a70f9dda8e259"}, - {file = "matplotlib-3.7.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7d479aac338195e2199a8cfc03c4f2f55914e6a120177edae79e0340a6406457"}, - {file = "matplotlib-3.7.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:32183d4be84189a4c52b4b8861434d427d9118db2cec32986f98ed6c02dcfbb6"}, - {file = "matplotlib-3.7.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0037d066cca1f4bda626c507cddeb6f7da8283bc6a214da2db13ff2162933c52"}, - {file = "matplotlib-3.7.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44856632ebce88abd8efdc0a0dceec600418dcac06b72ae77af0019d260aa243"}, - {file = "matplotlib-3.7.4-cp312-cp312-win_amd64.whl", hash = "sha256:632fc938c22117d4241411191cfb88ac264a4c0a9ac702244641ddf30f0d739c"}, - {file = "matplotlib-3.7.4-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:ce163be048613b9d1962273708cc97e09ca05d37312e670d166cf332b80bbaff"}, - {file = "matplotlib-3.7.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:e680f49bb8052ba3b2698e370155d2b4afb49f9af1cc611a26579d5981e2852a"}, - {file = "matplotlib-3.7.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0604880e4327114054199108b7390f987f4f40ee5ce728985836889e11a780ba"}, - {file = "matplotlib-3.7.4-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1e6abcde6fc52475f9d6a12b9f1792aee171ce7818ef6df5d61cb0b82816e6e8"}, - {file = "matplotlib-3.7.4-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f59a70e2ec3212033ef6633ed07682da03f5249379722512a3a2a26a7d9a738e"}, - {file = "matplotlib-3.7.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a9981b2a2dd9da06eca4ab5855d09b54b8ce7377c3e0e3957767b83219d652d"}, - {file = "matplotlib-3.7.4-cp38-cp38-win32.whl", hash = "sha256:83859ac26839660ecd164ee8311272074250b915ac300f9b2eccc84410f8953b"}, - {file = "matplotlib-3.7.4-cp38-cp38-win_amd64.whl", hash = "sha256:7a7709796ac59fe8debde68272388be6ed449c8971362eb5b60d280eac8dadde"}, - {file = "matplotlib-3.7.4-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:b1d70bc1ea1bf110bec64f4578de3e14947909a8887df4c1fd44492eca487955"}, - {file = "matplotlib-3.7.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c83f49e795a5de6c168876eea723f5b88355202f9603c55977f5356213aa8280"}, - {file = "matplotlib-3.7.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5c9133f230945fe10652eb33e43642e933896194ef6a4f8d5e79bb722bdb2000"}, - {file = "matplotlib-3.7.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:798ff59022eeb276380ce9a73ba35d13c3d1499ab9b73d194fd07f1b0a41c304"}, - {file = "matplotlib-3.7.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1707b20b25e90538c2ce8d4409e30f0ef1df4017cc65ad0439633492a973635b"}, - {file = "matplotlib-3.7.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e6227ca8492baeef873cdd8e169a318efb5c3a25ce94e69727e7f964995b0b1"}, - {file = "matplotlib-3.7.4-cp39-cp39-win32.whl", hash = "sha256:5661c8639aded7d1bbf781373a359011cb1dd09199dee49043e9e68dd16f07ba"}, - {file = "matplotlib-3.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:55eec941a4743f0bd3e5b8ee180e36b7ea8e62f867bf2613937c9f01b9ac06a2"}, - {file = "matplotlib-3.7.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ab16868714e5cc90ec8f7ff5d83d23bcd6559224d8e9cb5227c9f58748889fe8"}, - {file = "matplotlib-3.7.4-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c698b33f9a3f0b127a8e614c8fb4087563bb3caa9c9d95298722fa2400cdd3f"}, - {file = "matplotlib-3.7.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be3493bbcb4d255cb71de1f9050ac71682fce21a56089eadbcc8e21784cb12ee"}, - {file = "matplotlib-3.7.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f8c725d1dd2901b2e7ec6cd64165e00da2978cc23d4143cb9ef745bec88e6b04"}, - {file = "matplotlib-3.7.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:286332f8f45f8ffde2d2119b9fdd42153dccd5025fa9f451b4a3b5c086e26da5"}, - {file = "matplotlib-3.7.4-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:116ef0b43aa00ff69260b4cce39c571e4b8c6f893795b708303fa27d9b9d7548"}, - {file = "matplotlib-3.7.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c90590d4b46458677d80bc3218f3f1ac11fc122baa9134e0cb5b3e8fc3714052"}, - {file = "matplotlib-3.7.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:de7c07069687be64fd9d119da3122ba13a8d399eccd3f844815f0dc78a870b2c"}, - {file = "matplotlib-3.7.4.tar.gz", hash = "sha256:7cd4fef8187d1dd0d9dcfdbaa06ac326d396fb8c71c647129f0bf56835d77026"}, -] - -[package.dependencies] -contourpy = ">=1.0.1" -cycler = ">=0.10" -fonttools = ">=4.22.0" -importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} -kiwisolver = ">=1.0.1" -numpy = ">=1.20,<2" -packaging = ">=20.0" -pillow = ">=6.2.0" -pyparsing = ">=2.3.1" -python-dateutil = ">=2.7" - -[[package]] -name = "mergedeep" -version = "1.3.4" -description = "A deep merge function for 🐍." -optional = false -python-versions = ">=3.6" -files = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] - -[[package]] -name = "mkdocs" -version = "1.5.3" -description = "Project documentation with Markdown." -optional = false -python-versions = ">=3.7" -files = [ - {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"}, - {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"}, -] - -[package.dependencies] -click = ">=7.0" -colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} -ghp-import = ">=1.0" -importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} -jinja2 = ">=2.11.1" -markdown = ">=3.2.1" -markupsafe = ">=2.0.1" -mergedeep = ">=1.3.4" -packaging = ">=20.5" -pathspec = ">=0.11.1" -platformdirs = ">=2.2.0" -pyyaml = ">=5.1" -pyyaml-env-tag = ">=0.1" -watchdog = ">=2.0" - -[package.extras] -i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] - -[[package]] -name = "mkdocs-autorefs" -version = "0.5.0" -description = "Automatically link across pages in MkDocs." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs_autorefs-0.5.0-py3-none-any.whl", hash = "sha256:7930fcb8ac1249f10e683967aeaddc0af49d90702af111a5e390e8b20b3d97ff"}, - {file = "mkdocs_autorefs-0.5.0.tar.gz", hash = "sha256:9a5054a94c08d28855cfab967ada10ed5be76e2bfad642302a610b252c3274c0"}, -] - -[package.dependencies] -Markdown = ">=3.3" -mkdocs = ">=1.1" - -[[package]] -name = "mkdocs-material" -version = "9.4.14" -description = "Documentation that simply works" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs_material-9.4.14-py3-none-any.whl", hash = "sha256:dbc78a4fea97b74319a6aa9a2f0be575a6028be6958f813ba367188f7b8428f6"}, - {file = "mkdocs_material-9.4.14.tar.gz", hash = "sha256:a511d3ff48fa8718b033e7e37d17abd9cc1de0fdf0244a625ca2ae2387e2416d"}, -] - -[package.dependencies] -babel = ">=2.10,<3.0" -colorama = ">=0.4,<1.0" -jinja2 = ">=3.0,<4.0" -markdown = ">=3.2,<4.0" -mkdocs = ">=1.5.3,<2.0" -mkdocs-material-extensions = ">=1.3,<2.0" -paginate = ">=0.5,<1.0" -pygments = ">=2.16,<3.0" -pymdown-extensions = ">=10.2,<11.0" -regex = ">=2022.4" -requests = ">=2.26,<3.0" - -[package.extras] -git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2,<2.0)"] -imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=9.4,<10.0)"] -recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] - -[[package]] -name = "mkdocs-material-extensions" -version = "1.3.1" -description = "Extension pack for Python Markdown and MkDocs Material." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, - {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, -] - -[[package]] -name = "mkdocstrings" -version = "0.24.0" -description = "Automatic documentation from sources, for MkDocs." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocstrings-0.24.0-py3-none-any.whl", hash = "sha256:f4908560c10f587326d8f5165d1908817b2e280bbf707607f601c996366a2264"}, - {file = "mkdocstrings-0.24.0.tar.gz", hash = "sha256:222b1165be41257b494a9d29b14135d2b7ca43f38161d5b10caae03b87bd4f7e"}, -] - -[package.dependencies] -click = ">=7.0" -importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} -Jinja2 = ">=2.11.1" -Markdown = ">=3.3" -MarkupSafe = ">=1.1" -mkdocs = ">=1.4" -mkdocs-autorefs = ">=0.3.1" -mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} -platformdirs = ">=2.2.0" -pymdown-extensions = ">=6.3" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.10\""} - -[package.extras] -crystal = ["mkdocstrings-crystal (>=0.3.4)"] -python = ["mkdocstrings-python (>=0.5.2)"] -python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] - -[[package]] -name = "mkdocstrings-python" -version = "1.7.5" -description = "A Python handler for mkdocstrings." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocstrings_python-1.7.5-py3-none-any.whl", hash = "sha256:5f6246026353f0c0785135db70c3fe9a5d9318990fc7ceb11d62097b8ffdd704"}, - {file = "mkdocstrings_python-1.7.5.tar.gz", hash = "sha256:c7d143728257dbf1aa550446555a554b760dcd40a763f077189d298502b800be"}, -] - -[package.dependencies] -griffe = ">=0.37" -mkdocstrings = ">=0.20" - -[[package]] -name = "numpy" -version = "1.24.4" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, - {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, - {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, - {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, - {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, - {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, - {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, - {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, - {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, - {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, - {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, -] - -[[package]] -name = "opencv-python-headless" -version = "4.8.1.78" -description = "Wrapper package for OpenCV python bindings." -optional = false -python-versions = ">=3.6" -files = [ - {file = "opencv-python-headless-4.8.1.78.tar.gz", hash = "sha256:bc7197b42352f6f865c302a49140b889ec7cd957dd697e2d7fc016ad0d3f28f1"}, - {file = "opencv_python_headless-4.8.1.78-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:f3a33f644249f9ce1c913eac580e4b3ef4ce7cab0a71900274708959c2feb5e3"}, - {file = "opencv_python_headless-4.8.1.78-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:2c7d45721df9801c4dcd34683a15caa0e30f38b185263fec04a6eb274bc720f0"}, - {file = "opencv_python_headless-4.8.1.78-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b6bd6e1132b6f5dcb3a5bfe30fc4d341a7bfb26134da349a06c9255288ded94"}, - {file = "opencv_python_headless-4.8.1.78-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58e70d2f0915fe23e02c6e405588276c9397844a47d38b9c87fac5f7f9ba2dcc"}, - {file = "opencv_python_headless-4.8.1.78-cp37-abi3-win32.whl", hash = "sha256:382f8c7a6a14f80091284eecedd52cee4812231ee0eff1118592197b538d9252"}, - {file = "opencv_python_headless-4.8.1.78-cp37-abi3-win_amd64.whl", hash = "sha256:0a0f1e9f836f7d5bad1dd164694944c8761711cbdf4b36ebbd4815a8ef731079"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.21.0", markers = "python_version <= \"3.9\" and platform_system == \"Darwin\" and platform_machine == \"arm64\""}, - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, - {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, - {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, - {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, -] - -[[package]] -name = "packaging" -version = "23.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] - -[[package]] -name = "paginate" -version = "0.5.6" -description = "Divides large result sets into pages for easier browsing" -optional = false -python-versions = "*" -files = [ - {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"}, -] - -[[package]] -name = "pathspec" -version = "0.11.2" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, -] - -[[package]] -name = "pillow" -version = "10.1.0" -description = "Python Imaging Library (Fork)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"}, - {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"}, - {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"}, - {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"}, - {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"}, - {file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"}, - {file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"}, - {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] - -[[package]] -name = "platformdirs" -version = "4.0.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -optional = false -python-versions = ">=3.7" -files = [ - {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, - {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, -] - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] - -[[package]] -name = "pluggy" -version = "1.3.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pygments" -version = "2.17.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, -] - -[package.extras] -plugins = ["importlib-metadata"] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pymdown-extensions" -version = "10.5" -description = "Extension pack for Python Markdown." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pymdown_extensions-10.5-py3-none-any.whl", hash = "sha256:1f0ca8bb5beff091315f793ee17683bc1390731f6ac4c5eb01e27464b80fe879"}, - {file = "pymdown_extensions-10.5.tar.gz", hash = "sha256:1b60f1e462adbec5a1ed79dac91f666c9c0d241fa294de1989f29d20096cfd0b"}, -] - -[package.dependencies] -markdown = ">=3.5" -pyyaml = "*" - -[package.extras] -extra = ["pygments (>=2.12)"] - -[[package]] -name = "pyparsing" -version = "3.1.1" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.6.8" -files = [ - {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, - {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pytest" -version = "7.4.3" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2023.3.post1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - -[[package]] -name = "pyyaml-env-tag" -version = "0.1" -description = "A custom YAML tag for referencing environment variables in YAML files. " -optional = false -python-versions = ">=3.6" -files = [ - {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, - {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, -] - -[package.dependencies] -pyyaml = "*" - -[[package]] -name = "regex" -version = "2023.10.3" -description = "Alternative regular expression module, to replace re." -optional = false -python-versions = ">=3.7" -files = [ - {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, - {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, - {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, - {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, - {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, - {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, - {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, - {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, - {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, - {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, - {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, - {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, - {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, - {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, - {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, - {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, -] - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.7" -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "safetensors" -version = "0.4.1" -description = "" -optional = false -python-versions = ">=3.7" -files = [ - {file = "safetensors-0.4.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:cba01c6b76e01ec453933b3b3c0157c59b52881c83eaa0f7666244e71aa75fd1"}, - {file = "safetensors-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a8f6f679d97ea0135c7935c202feefbd042c149aa70ee759855e890c01c7814"}, - {file = "safetensors-0.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc2ce1f5ae5143a7fb72b71fa71db6a42b4f6cf912aa3acdc6b914084778e68"}, - {file = "safetensors-0.4.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d87d993eaefe6611a9c241a8bd364a5f1ffed5771c74840363a6c4ed8d868f6"}, - {file = "safetensors-0.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:097e9af2efa8778cd2f0cba451784253e62fa7cc9fc73c0744d27212f7294e25"}, - {file = "safetensors-0.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d10a9f7bae608ccfdc009351f01dc3d8535ff57f9488a58a4c38e45bf954fe93"}, - {file = "safetensors-0.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:270b99885ec14abfd56c1d7f28ada81740a9220b4bae960c3de1c6fe84af9e4d"}, - {file = "safetensors-0.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:285b52a481e7ba93e29ad4ec5841ef2c4479ef0a6c633c4e2629e0508453577b"}, - {file = "safetensors-0.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c3c9f0ca510e0de95abd6424789dcbc879942a3a4e29b0dfa99d9427bf1da75c"}, - {file = "safetensors-0.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:88b4653059c903015284a9722f9a46838c654257173b279c8f6f46dbe80b612d"}, - {file = "safetensors-0.4.1-cp310-none-win32.whl", hash = "sha256:2fe6926110e3d425c4b684a4379b7796fdc26ad7d16922ea1696c8e6ea7e920f"}, - {file = "safetensors-0.4.1-cp310-none-win_amd64.whl", hash = "sha256:a79e16222106b2f5edbca1b8185661477d8971b659a3c814cc6f15181a9b34c8"}, - {file = "safetensors-0.4.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:d93321eea0dd7e81b283e47a1d20dee6069165cc158286316d0d06d340de8fe8"}, - {file = "safetensors-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ff8e41c8037db17de0ea2a23bc684f43eaf623be7d34906fe1ac10985b8365e"}, - {file = "safetensors-0.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39d36f1d88468a87c437a1bc27c502e71b6ca44c385a9117a9f9ba03a75cc9c6"}, - {file = "safetensors-0.4.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ef010e9afcb4057fb6be3d0a0cfa07aac04fe97ef73fe4a23138d8522ba7c17"}, - {file = "safetensors-0.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b287304f2b2220d51ccb51fd857761e78bcffbeabe7b0238f8dc36f2edfd9542"}, - {file = "safetensors-0.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e09000b2599e1836314430f81a3884c66a5cbabdff5d9f175b5d560d4de38d78"}, - {file = "safetensors-0.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9c80ce0001efa16066358d2dd77993adc25f5a6c61850e4ad096a2232930bce"}, - {file = "safetensors-0.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:413e1f6ac248f7d1b755199a06635e70c3515493d3b41ba46063dec33aa2ebb7"}, - {file = "safetensors-0.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3ac139377cfe71ba04573f1cda66e663b7c3e95be850e9e6c2dd4b5984bd513"}, - {file = "safetensors-0.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:04157d008385bea66d12fe90844a80d4a76dc25ec5230b5bd9a630496d1b7c03"}, - {file = "safetensors-0.4.1-cp311-none-win32.whl", hash = "sha256:5f25297148ec665f0deb8bd67e9564634d8d6841041ab5393ccfe203379ea88b"}, - {file = "safetensors-0.4.1-cp311-none-win_amd64.whl", hash = "sha256:b2f8877990a72ff595507b80f4b69036a9a1986a641f8681adf3425d97d3d2a5"}, - {file = "safetensors-0.4.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:eb2c1da1cc39509d1a55620a5f4d14f8911c47a89c926a96e6f4876e864375a3"}, - {file = "safetensors-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:303d2c0415cf15a28f8d7f17379ea3c34c2b466119118a34edd9965983a1a8a6"}, - {file = "safetensors-0.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb4cb3e37a9b961ddd68e873b29fe9ab4a081e3703412e34aedd2b7a8e9cafd9"}, - {file = "safetensors-0.4.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae5497adc68669db2fed7cb2dad81e6a6106e79c9a132da3efdb6af1db1014fa"}, - {file = "safetensors-0.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b30abd0cddfe959d1daedf92edcd1b445521ebf7ddefc20860ed01486b33c90"}, - {file = "safetensors-0.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d784a98c492c751f228a4a894c3b8a092ff08b24e73b5568938c28b8c0e8f8df"}, - {file = "safetensors-0.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57a5ab08b0ec7a7caf30d2ac79bb30c89168431aca4f8854464bb9461686925"}, - {file = "safetensors-0.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:edcf3121890b5f0616aa5a54683b1a5d2332037b970e507d6bb7841a3a596556"}, - {file = "safetensors-0.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fdb58dee173ef33634c3016c459d671ca12d11e6acf9db008261cbe58107e579"}, - {file = "safetensors-0.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:780dc21eb3fd32ddd0e8c904bdb0290f2454f4ac21ae71e94f9ce72db1900a5a"}, - {file = "safetensors-0.4.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:48901bd540f8a3c1791314bc5c8a170927bf7f6acddb75bf0a263d081a3637d4"}, - {file = "safetensors-0.4.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:3b0b7b2d5976fbed8a05e2bbdce5816a59e6902e9e7c7e07dc723637ed539787"}, - {file = "safetensors-0.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f69903ff49cb30b9227fb5d029bea276ea20d04b06803877a420c5b1b74c689"}, - {file = "safetensors-0.4.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0ddd050e01f3e843aa8c1c27bf68675b8a08e385d0045487af4d70418c3cb356"}, - {file = "safetensors-0.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a82bc2bd7a9a0e08239bdd6d7774d64121f136add93dfa344a2f1a6d7ef35fa"}, - {file = "safetensors-0.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6ace9e66a40f98a216ad661245782483cf79cf56eb2b112650bb904b0baa9db5"}, - {file = "safetensors-0.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82cbb8f4d022f2e94498cbefca900698b8ded3d4f85212f47da614001ff06652"}, - {file = "safetensors-0.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:791edc10a3c359a2f5f52d5cddab0df8a45107d91027d86c3d44e57162e5d934"}, - {file = "safetensors-0.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:83c2cfbe8c6304f0891e7bb378d56f66d2148972eeb5f747cd8a2246886f0d8c"}, - {file = "safetensors-0.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:04dd14f53f5500eb4c4149674216ba1000670efbcf4b1b5c2643eb244e7882ea"}, - {file = "safetensors-0.4.1-cp37-none-win32.whl", hash = "sha256:d5b3defa74f3723a388bfde2f5d488742bc4879682bd93267c09a3bcdf8f869b"}, - {file = "safetensors-0.4.1-cp37-none-win_amd64.whl", hash = "sha256:25a043cbb59d4f75e9dd87fdf5c009dd8830105a2c57ace49b72167dd9808111"}, - {file = "safetensors-0.4.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:3f6a520af7f2717c5ecba112041f2c8af1ca6480b97bf957aba81ed9642e654c"}, - {file = "safetensors-0.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3807ac3b16288dffebb3474b555b56fe466baa677dfc16290dcd02dca1ab228"}, - {file = "safetensors-0.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b58ba13a9e82b4bc3fc221914f6ef237fe6c2adb13cede3ace64d1aacf49610"}, - {file = "safetensors-0.4.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dac4bb42f8679aadc59bd91a4c5a1784a758ad49d0912995945cd674089f628e"}, - {file = "safetensors-0.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911b48dc09e321a194def3a7431662ff4f03646832f3a8915bbf0f449b8a5fcb"}, - {file = "safetensors-0.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82571d20288c975c1b30b08deb9b1c3550f36b31191e1e81fae87669a92217d0"}, - {file = "safetensors-0.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da52ee0dc8ba03348ffceab767bd8230842fdf78f8a996e2a16445747143a778"}, - {file = "safetensors-0.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2536b11ce665834201072e9397404170f93f3be10cca9995b909f023a04501ee"}, - {file = "safetensors-0.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:998fbac99ca956c3a09fe07cc0b35fac26a521fa8865a690686d889f0ff4e4a6"}, - {file = "safetensors-0.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:845be0aafabf2a60c2d482d4e93023fecffe5e5443d801d7a7741bae9de41233"}, - {file = "safetensors-0.4.1-cp38-none-win32.whl", hash = "sha256:ce7a28bc8af685a69d7e869d09d3e180a275e3281e29cf5f1c7319e231932cc7"}, - {file = "safetensors-0.4.1-cp38-none-win_amd64.whl", hash = "sha256:e056fb9e22d118cc546107f97dc28b449d88274207dd28872bd668c86216e4f6"}, - {file = "safetensors-0.4.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:bdc0d039e44a727824639824090bd8869535f729878fa248addd3dc01db30eae"}, - {file = "safetensors-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c1b1d510c7aba71504ece87bf393ea82638df56303e371e5e2cf09d18977dd7"}, - {file = "safetensors-0.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bd0afd95c1e497f520e680ea01e0397c0868a3a3030e128438cf6e9e3fcd671"}, - {file = "safetensors-0.4.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f603bdd8deac6726d39f41688ed353c532dd53935234405d79e9eb53f152fbfb"}, - {file = "safetensors-0.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8a85e3e47e0d4eebfaf9a58b40aa94f977a56050cb5598ad5396a9ee7c087c6"}, - {file = "safetensors-0.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0ccb5aa0f3be2727117e5631200fbb3a5b3a2b3757545a92647d6dd8be6658f"}, - {file = "safetensors-0.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d784938534e255473155e4d9f276ee69eb85455b6af1292172c731409bf9adee"}, - {file = "safetensors-0.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a257de175c254d39ccd6a21341cd62eb7373b05c1e618a78096a56a857e0c316"}, - {file = "safetensors-0.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6fd80f7794554091836d4d613d33a7d006e2b8d6ba014d06f97cebdfda744f64"}, - {file = "safetensors-0.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:35803201d980efcf964b75a0a2aee97fe5e9ecc5f3ad676b38fafdfe98e0620d"}, - {file = "safetensors-0.4.1-cp39-none-win32.whl", hash = "sha256:7ff8a36e0396776d3ed9a106fc9a9d7c55d4439ca9a056a24bf66d343041d3e6"}, - {file = "safetensors-0.4.1-cp39-none-win_amd64.whl", hash = "sha256:bfa2e20342b81921b98edba52f8deb68843fa9c95250739a56b52ceda5ea5c61"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ae2d5a31cfb8a973a318f7c4d2cffe0bd1fe753cdf7bb41a1939d45a0a06f964"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1a45dbf03e8334d3a5dc93687d98b6dc422f5d04c7d519dac09b84a3c87dd7c6"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297b359d91126c0f9d4fd17bae3cfa2fe3a048a6971b8db07db746ad92f850c"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda3d98e2bcece388232cfc551ebf063b55bdb98f65ab54df397da30efc7dcc5"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8934bdfd202ebd0697040a3dff40dd77bc4c5bbf3527ede0532f5e7fb4d970f"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:42c3710cec7e5c764c7999697516370bee39067de0aa089b7e2cfb97ac8c6b20"}, - {file = "safetensors-0.4.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53134226053e56bd56e73f7db42596e7908ed79f3c9a1016e4c1dade593ac8e5"}, - {file = "safetensors-0.4.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:257d59e40a1b367cb544122e7451243d65b33c3f34d822a347f4eea6fdf97fdf"}, - {file = "safetensors-0.4.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d54c2f1826e790d1eb2d2512bfd0ee443f0206b423d6f27095057c7f18a0687"}, - {file = "safetensors-0.4.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:645b3f1138fce6e818e79d4128afa28f0657430764cc045419c1d069ff93f732"}, - {file = "safetensors-0.4.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e9a7ffb1e551c6df51d267f5a751f042b183df22690f6feceac8d27364fd51d7"}, - {file = "safetensors-0.4.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:44e230fbbe120de564b64f63ef3a8e6ff02840fa02849d9c443d56252a1646d4"}, - {file = "safetensors-0.4.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:9d16b3b2fcc6fca012c74bd01b5619c655194d3e3c13e4d4d0e446eefa39a463"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5d95ea4d8b32233910734a904123bdd3979c137c461b905a5ed32511defc075f"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:dab431699b5d45e0ca043bc580651ce9583dda594e62e245b7497adb32e99809"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16d8bbb7344e39cb9d4762e85c21df94ebeb03edac923dd94bb9ed8c10eac070"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1faf5111c66a6ba91f85dff2e36edaaf36e6966172703159daeef330de4ddc7b"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:660ca1d8bff6c7bc7c6b30b9b32df74ef3ab668f5df42cefd7588f0d40feadcb"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ae2f67f04ed0bb2e56fd380a8bd3eef03f609df53f88b6f5c7e89c08e52aae00"}, - {file = "safetensors-0.4.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:c8ed5d2c04cdc1afc6b3c28d59580448ac07732c50d94c15e14670f9c473a2ce"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:2b6a2814278b6660261aa9a9aae524616de9f1ec364e3716d219b6ed8f91801f"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3cfd1ca35eacc635f0eaa894e5c5ed83ffebd0f95cac298fd430014fa7323631"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4177b456c6b0c722d82429127b5beebdaf07149d265748e97e0a34ff0b3694c8"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:313e8472197bde54e3ec54a62df184c414582979da8f3916981b6a7954910a1b"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fdb4adb76e21bad318210310590de61c9f4adcef77ee49b4a234f9dc48867869"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:1d568628e9c43ca15eb96c217da73737c9ccb07520fafd8a1eba3f2750614105"}, - {file = "safetensors-0.4.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:573b6023a55a2f28085fc0a84e196c779b6cbef4d9e73acea14c8094fee7686f"}, - {file = "safetensors-0.4.1.tar.gz", hash = "sha256:2304658e6ada81a5223225b4efe84748e760c46079bffedf7e321763cafb36c9"}, -] - -[package.extras] -all = ["safetensors[jax]", "safetensors[numpy]", "safetensors[paddlepaddle]", "safetensors[pinned-tf]", "safetensors[quality]", "safetensors[testing]", "safetensors[torch]"] -dev = ["safetensors[all]"] -jax = ["flax (>=0.6.3)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "safetensors[numpy]"] -numpy = ["numpy (>=1.21.6)"] -paddlepaddle = ["paddlepaddle (>=2.4.1)", "safetensors[numpy]"] -pinned-tf = ["safetensors[numpy]", "tensorflow (==2.11.0)"] -quality = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "isort (>=5.5.4)"] -tensorflow = ["safetensors[numpy]", "tensorflow (>=2.11.0)"] -testing = ["h5py (>=3.7.0)", "huggingface_hub (>=0.12.1)", "hypothesis (>=6.70.2)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "safetensors[numpy]", "setuptools_rust (>=1.5.2)"] -torch = ["safetensors[numpy]", "torch (>=1.10)"] - -[[package]] -name = "scipy" -version = "1.10.1" -description = "Fundamental algorithms for scientific computing in Python" -optional = false -python-versions = "<3.12,>=3.8" -files = [ - {file = "scipy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7354fd7527a4b0377ce55f286805b34e8c54b91be865bac273f527e1b839019"}, - {file = "scipy-1.10.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4b3f429188c66603a1a5c549fb414e4d3bdc2a24792e061ffbd607d3d75fd84e"}, - {file = "scipy-1.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1553b5dcddd64ba9a0d95355e63fe6c3fc303a8fd77c7bc91e77d61363f7433f"}, - {file = "scipy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c0ff64b06b10e35215abce517252b375e580a6125fd5fdf6421b98efbefb2d2"}, - {file = "scipy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:fae8a7b898c42dffe3f7361c40d5952b6bf32d10c4569098d276b4c547905ee1"}, - {file = "scipy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f1564ea217e82c1bbe75ddf7285ba0709ecd503f048cb1236ae9995f64217bd"}, - {file = "scipy-1.10.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d925fa1c81b772882aa55bcc10bf88324dadb66ff85d548c71515f6689c6dac5"}, - {file = "scipy-1.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaea0a6be54462ec027de54fca511540980d1e9eea68b2d5c1dbfe084797be35"}, - {file = "scipy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15a35c4242ec5f292c3dd364a7c71a61be87a3d4ddcc693372813c0b73c9af1d"}, - {file = "scipy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:43b8e0bcb877faf0abfb613d51026cd5cc78918e9530e375727bf0625c82788f"}, - {file = "scipy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5678f88c68ea866ed9ebe3a989091088553ba12c6090244fdae3e467b1139c35"}, - {file = "scipy-1.10.1-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:39becb03541f9e58243f4197584286e339029e8908c46f7221abeea4b749fa88"}, - {file = "scipy-1.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bce5869c8d68cf383ce240e44c1d9ae7c06078a9396df68ce88a1230f93a30c1"}, - {file = "scipy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07c3457ce0b3ad5124f98a86533106b643dd811dd61b548e78cf4c8786652f6f"}, - {file = "scipy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:049a8bbf0ad95277ffba9b3b7d23e5369cc39e66406d60422c8cfef40ccc8415"}, - {file = "scipy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd9f1027ff30d90618914a64ca9b1a77a431159df0e2a195d8a9e8a04c78abf9"}, - {file = "scipy-1.10.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:79c8e5a6c6ffaf3a2262ef1be1e108a035cf4f05c14df56057b64acc5bebffb6"}, - {file = "scipy-1.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51af417a000d2dbe1ec6c372dfe688e041a7084da4fdd350aeb139bd3fb55353"}, - {file = "scipy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b4735d6c28aad3cdcf52117e0e91d6b39acd4272f3f5cd9907c24ee931ad601"}, - {file = "scipy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ff7f37b1bf4417baca958d254e8e2875d0cc23aaadbe65b3d5b3077b0eb23ea"}, - {file = "scipy-1.10.1.tar.gz", hash = "sha256:2cf9dfb80a7b4589ba4c40ce7588986d6d5cebc5457cad2c2880f6bc2d42f3a5"}, -] - -[package.dependencies] -numpy = ">=1.19.5,<1.27.0" - -[package.extras] -dev = ["click", "doit (>=0.36.0)", "flake8", "mypy", "pycodestyle", "pydevtool", "rich-click", "typing_extensions"] -doc = ["matplotlib (>2)", "numpydoc", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] -test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "supervision" -version = "0.17.0rc4" -description = "A set of easy-to-use utils that will come in handy in any Computer Vision project" -optional = false -python-versions = ">=3.8,<3.12.0" -files = [ - {file = "supervision-0.17.0rc4-py3-none-any.whl", hash = "sha256:3e2573b78de8a724a7be8c2248c75daa2003f61834fbb686df1b0700cb87a736"}, - {file = "supervision-0.17.0rc4.tar.gz", hash = "sha256:2442e2f747a1fd3512cb21e3a727e5cf013095f954da1aa7a3cddfb94c0b866c"}, -] - -[package.dependencies] -matplotlib = ">=3.7.1,<4.0.0" -numpy = ">=1.20.0,<2.0.0" -opencv-python-headless = ">=4.8.0.74,<5.0.0.0" -pillow = ">=9.4,<11.0" -pyyaml = ">=6.0,<7.0" -scipy = ">=1.9.0,<2.0.0" - -[package.extras] -assets = ["requests (>=2.31.0,<3.0.0)", "tqdm (>=4.66.1,<5.0.0)"] -desktop = ["opencv-python (>=4.8.0.74,<5.0.0.0)"] - -[[package]] -name = "tokenizers" -version = "0.15.0" -description = "" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tokenizers-0.15.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:cd3cd0299aaa312cd2988957598f80becd04d5a07338741eca076057a2b37d6e"}, - {file = "tokenizers-0.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a922c492c721744ee175f15b91704be2d305569d25f0547c77cd6c9f210f9dc"}, - {file = "tokenizers-0.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:331dd786d02fc38698f835fff61c99480f98b73ce75a4c65bd110c9af5e4609a"}, - {file = "tokenizers-0.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88dd0961c437d413ab027f8b115350c121d49902cfbadf08bb8f634b15fa1814"}, - {file = "tokenizers-0.15.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6fdcc55339df7761cd52e1fbe8185d3b3963bc9e3f3545faa6c84f9e8818259a"}, - {file = "tokenizers-0.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1480b0051d8ab5408e8e4db2dc832f7082ea24aa0722c427bde2418c6f3bd07"}, - {file = "tokenizers-0.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9855e6c258918f9cf62792d4f6ddfa6c56dccd8c8118640f867f6393ecaf8bd7"}, - {file = "tokenizers-0.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de9529fe75efcd54ba8d516aa725e1851df9199f0669b665c55e90df08f5af86"}, - {file = "tokenizers-0.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8edcc90a36eab0705fe9121d6c77c6e42eeef25c7399864fd57dfb27173060bf"}, - {file = "tokenizers-0.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ae17884aafb3e94f34fb7cfedc29054f5f54e142475ebf8a265a4e388fee3f8b"}, - {file = "tokenizers-0.15.0-cp310-none-win32.whl", hash = "sha256:9a3241acdc9b44cff6e95c4a55b9be943ef3658f8edb3686034d353734adba05"}, - {file = "tokenizers-0.15.0-cp310-none-win_amd64.whl", hash = "sha256:4b31807cb393d6ea31926b307911c89a1209d5e27629aa79553d1599c8ffdefe"}, - {file = "tokenizers-0.15.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:af7e9be8c05d30bb137b9fd20f9d99354816599e5fd3d58a4b1e28ba3b36171f"}, - {file = "tokenizers-0.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3d7343fa562ea29661783344a2d83662db0d3d17a6fa6a403cac8e512d2d9fd"}, - {file = "tokenizers-0.15.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:32371008788aeeb0309a9244809a23e4c0259625e6b74a103700f6421373f395"}, - {file = "tokenizers-0.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9db64c7c9954fbae698884c5bb089764edc549731e5f9b7fa1dd4e4d78d77f"}, - {file = "tokenizers-0.15.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dbed5944c31195514669cf6381a0d8d47f164943000d10f93d6d02f0d45c25e0"}, - {file = "tokenizers-0.15.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aab16c4a26d351d63e965b0c792f5da7227a37b69a6dc6d922ff70aa595b1b0c"}, - {file = "tokenizers-0.15.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c2b60b12fdd310bf85ce5d7d3f823456b9b65eed30f5438dd7761879c495983"}, - {file = "tokenizers-0.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0344d6602740e44054a9e5bbe9775a5e149c4dddaff15959bb07dcce95a5a859"}, - {file = "tokenizers-0.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4525f6997d81d9b6d9140088f4f5131f6627e4c960c2c87d0695ae7304233fc3"}, - {file = "tokenizers-0.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:65975094fef8cc68919644936764efd2ce98cf1bacbe8db2687155d2b0625bee"}, - {file = "tokenizers-0.15.0-cp311-none-win32.whl", hash = "sha256:ff5d2159c5d93015f5a4542aac6c315506df31853123aa39042672031768c301"}, - {file = "tokenizers-0.15.0-cp311-none-win_amd64.whl", hash = "sha256:2dd681b53cf615e60a31a115a3fda3980e543d25ca183797f797a6c3600788a3"}, - {file = "tokenizers-0.15.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:c9cce6ee149a3d703f86877bc2a6d997e34874b2d5a2d7839e36b2273f31d3d9"}, - {file = "tokenizers-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a0a94bc3370e6f1cc8a07a8ae867ce13b7c1b4291432a773931a61f256d44ea"}, - {file = "tokenizers-0.15.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:309cfcccfc7e502cb1f1de2c9c1c94680082a65bfd3a912d5a5b2c90c677eb60"}, - {file = "tokenizers-0.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8413e994dd7d875ab13009127fc85633916c71213917daf64962bafd488f15dc"}, - {file = "tokenizers-0.15.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d0ebf9430f901dbdc3dcb06b493ff24a3644c9f88c08e6a1d6d0ae2228b9b818"}, - {file = "tokenizers-0.15.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10361e9c7864b22dd791ec5126327f6c9292fb1d23481d4895780688d5e298ac"}, - {file = "tokenizers-0.15.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:babe42635b8a604c594bdc56d205755f73414fce17ba8479d142a963a6c25cbc"}, - {file = "tokenizers-0.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3768829861e964c7a4556f5f23307fce6a23872c2ebf030eb9822dbbbf7e9b2a"}, - {file = "tokenizers-0.15.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9c91588a630adc88065e1c03ac6831e3e2112558869b9ebcb2b8afd8a14c944d"}, - {file = "tokenizers-0.15.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:77606994e793ca54ecf3a3619adc8a906a28ca223d9354b38df41cb8766a0ed6"}, - {file = "tokenizers-0.15.0-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:6fe143939f3b596681922b2df12a591a5b010e7dcfbee2202482cd0c1c2f2459"}, - {file = "tokenizers-0.15.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:b7bee0f1795e3e3561e9a557061b1539e5255b8221e3f928f58100282407e090"}, - {file = "tokenizers-0.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5d37e7f4439b4c46192ab4f2ff38ab815e4420f153caa13dec9272ef14403d34"}, - {file = "tokenizers-0.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caadf255cf7f951b38d10097836d1f3bcff4aeaaffadfdf748bab780bf5bff95"}, - {file = "tokenizers-0.15.0-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:05accb9162bf711a941b1460b743d62fec61c160daf25e53c5eea52c74d77814"}, - {file = "tokenizers-0.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26a2ef890740127cb115ee5260878f4a677e36a12831795fd7e85887c53b430b"}, - {file = "tokenizers-0.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e54c5f26df14913620046b33e822cb3bcd091a332a55230c0e63cc77135e2169"}, - {file = "tokenizers-0.15.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669b8ed653a578bcff919566631156f5da3aab84c66f3c0b11a6281e8b4731c7"}, - {file = "tokenizers-0.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0ea480d943297df26f06f508dab6e012b07f42bf3dffdd36e70799368a5f5229"}, - {file = "tokenizers-0.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc80a0a565ebfc7cd89de7dd581da8c2b3238addfca6280572d27d763f135f2f"}, - {file = "tokenizers-0.15.0-cp37-none-win32.whl", hash = "sha256:cdd945e678bbdf4517d5d8de66578a5030aeefecdb46f5320b034de9cad8d4dd"}, - {file = "tokenizers-0.15.0-cp37-none-win_amd64.whl", hash = "sha256:1ab96ab7dc706e002c32b2ea211a94c1c04b4f4de48354728c3a6e22401af322"}, - {file = "tokenizers-0.15.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:f21c9eb71c9a671e2a42f18b456a3d118e50c7f0fc4dd9fa8f4eb727fea529bf"}, - {file = "tokenizers-0.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a5f4543a35889679fc3052086e69e81880b2a5a28ff2a52c5a604be94b77a3f"}, - {file = "tokenizers-0.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f8aa81afec893e952bd39692b2d9ef60575ed8c86fce1fd876a06d2e73e82dca"}, - {file = "tokenizers-0.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1574a5a4af22c3def93fe8fe4adcc90a39bf5797ed01686a4c46d1c3bc677d2f"}, - {file = "tokenizers-0.15.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c7982fd0ec9e9122d03b209dac48cebfea3de0479335100ef379a9a959b9a5a"}, - {file = "tokenizers-0.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d16b647032df2ce2c1f9097236e046ea9fedd969b25637b9d5d734d78aa53b"}, - {file = "tokenizers-0.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b3cdf29e6f9653da330515dc8fa414be5a93aae79e57f8acc50d4028dd843edf"}, - {file = "tokenizers-0.15.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7286f3df10de840867372e3e64b99ef58c677210e3ceb653cd0e740a5c53fe78"}, - {file = "tokenizers-0.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aabc83028baa5a36ce7a94e7659250f0309c47fa4a639e5c2c38e6d5ea0de564"}, - {file = "tokenizers-0.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:72f78b0e0e276b1fc14a672fa73f3acca034ba8db4e782124a2996734a9ba9cf"}, - {file = "tokenizers-0.15.0-cp38-none-win32.whl", hash = "sha256:9680b0ecc26e7e42f16680c1aa62e924d58d1c2dd992707081cc10a374896ea2"}, - {file = "tokenizers-0.15.0-cp38-none-win_amd64.whl", hash = "sha256:f17cbd88dab695911cbdd385a5a7e3709cc61dff982351f5d1b5939f074a2466"}, - {file = "tokenizers-0.15.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:3661862df7382c5eb23ac4fbf7c75e69b02dc4f5784e4c5a734db406b5b24596"}, - {file = "tokenizers-0.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3045d191dad49647f5a5039738ecf1c77087945c7a295f7bcf051c37067e883"}, - {file = "tokenizers-0.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9fcaad9ab0801f14457d7c820d9f246b5ab590c407fc6b073819b1573097aa7"}, - {file = "tokenizers-0.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79f17027f24fe9485701c8dbb269b9c713954ec3bdc1e7075a66086c0c0cd3c"}, - {file = "tokenizers-0.15.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:01a3aa332abc4bee7640563949fcfedca4de8f52691b3b70f2fc6ca71bfc0f4e"}, - {file = "tokenizers-0.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05b83896a893cdfedad8785250daa3ba9f0504848323471524d4783d7291661e"}, - {file = "tokenizers-0.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbbf2489fcf25d809731ba2744ff278dd07d9eb3f8b7482726bd6cae607073a4"}, - {file = "tokenizers-0.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab806ad521a5e9de38078b7add97589c313915f6f5fec6b2f9f289d14d607bd6"}, - {file = "tokenizers-0.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a522612d5c88a41563e3463226af64e2fa00629f65cdcc501d1995dd25d23f5"}, - {file = "tokenizers-0.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e58a38c4e6075810bdfb861d9c005236a72a152ebc7005941cc90d1bbf16aca9"}, - {file = "tokenizers-0.15.0-cp39-none-win32.whl", hash = "sha256:b8034f1041fd2bd2b84ff9f4dc4ae2e1c3b71606820a9cd5c562ebd291a396d1"}, - {file = "tokenizers-0.15.0-cp39-none-win_amd64.whl", hash = "sha256:edde9aa964145d528d0e0dbf14f244b8a85ebf276fb76869bc02e2530fa37a96"}, - {file = "tokenizers-0.15.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:309445d10d442b7521b98083dc9f0b5df14eca69dbbfebeb98d781ee2cef5d30"}, - {file = "tokenizers-0.15.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d3125a6499226d4d48efc54f7498886b94c418e93a205b673bc59364eecf0804"}, - {file = "tokenizers-0.15.0-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ed56ddf0d54877bb9c6d885177db79b41576e61b5ef6defeb579dcb803c04ad5"}, - {file = "tokenizers-0.15.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b22cd714706cc5b18992a232b023f736e539495f5cc61d2d28d176e55046f6c"}, - {file = "tokenizers-0.15.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac2719b1e9bc8e8e7f6599b99d0a8e24f33d023eb8ef644c0366a596f0aa926"}, - {file = "tokenizers-0.15.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:85ddae17570ec7e5bfaf51ffa78d044f444a8693e1316e1087ee6150596897ee"}, - {file = "tokenizers-0.15.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:76f1bed992e396bf6f83e3df97b64ff47885e45e8365f8983afed8556a0bc51f"}, - {file = "tokenizers-0.15.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3bb0f4df6dce41a1c7482087b60d18c372ef4463cb99aa8195100fcd41e0fd64"}, - {file = "tokenizers-0.15.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:22c27672c27a059a5f39ff4e49feed8c7f2e1525577c8a7e3978bd428eb5869d"}, - {file = "tokenizers-0.15.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78104f5d035c9991f92831fc0efe9e64a05d4032194f2a69f67aaa05a4d75bbb"}, - {file = "tokenizers-0.15.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a40b73dc19d82c3e3ffb40abdaacca8fbc95eeb26c66b7f9f860aebc07a73998"}, - {file = "tokenizers-0.15.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d801d1368188c74552cd779b1286e67cb9fd96f4c57a9f9a2a09b6def9e1ab37"}, - {file = "tokenizers-0.15.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82641ffb13a4da1293fcc9f437d457647e60ed0385a9216cd135953778b3f0a1"}, - {file = "tokenizers-0.15.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:160f9d1810f2c18fffa94aa98bf17632f6bd2dabc67fcb01a698ca80c37d52ee"}, - {file = "tokenizers-0.15.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:8d7d6eea831ed435fdeeb9bcd26476226401d7309d115a710c65da4088841948"}, - {file = "tokenizers-0.15.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f6456bec6c557d63d8ec0023758c32f589e1889ed03c055702e84ce275488bed"}, - {file = "tokenizers-0.15.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eef39a502fad3bf104b9e1906b4fb0cee20e44e755e51df9a98f8922c3bf6d4"}, - {file = "tokenizers-0.15.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1e4664c5b797e093c19b794bbecc19d2367e782b4a577d8b7c1821db5dc150d"}, - {file = "tokenizers-0.15.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ca003fb5f3995ff5cf676db6681b8ea5d54d3b30bea36af1120e78ee1a4a4cdf"}, - {file = "tokenizers-0.15.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7f17363141eb0c53752c89e10650b85ef059a52765d0802ba9613dbd2d21d425"}, - {file = "tokenizers-0.15.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:8a765db05581c7d7e1280170f2888cda351760d196cc059c37ea96f121125799"}, - {file = "tokenizers-0.15.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:2a0dd641a72604486cd7302dd8f87a12c8a9b45e1755e47d2682733f097c1af5"}, - {file = "tokenizers-0.15.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a1a3c973e4dc97797fc19e9f11546c95278ffc55c4492acb742f69e035490bc"}, - {file = "tokenizers-0.15.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4fab75642aae4e604e729d6f78e0addb9d7e7d49e28c8f4d16b24da278e5263"}, - {file = "tokenizers-0.15.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65f80be77f6327a86d8fd35a4467adcfe6174c159b4ab52a1a8dd4c6f2d7d9e1"}, - {file = "tokenizers-0.15.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a8da7533dbe66b88afd430c56a2f2ce1fd82e2681868f857da38eeb3191d7498"}, - {file = "tokenizers-0.15.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa8eb4584fc6cbe6a84d7a7864be3ed28e23e9fd2146aa8ef1814d579df91958"}, - {file = "tokenizers-0.15.0.tar.gz", hash = "sha256:10c7e6e7b4cabd757da59e93f5f8d1126291d16f8b54f28510825ef56a3e5d0e"}, -] - -[package.dependencies] -huggingface_hub = ">=0.16.4,<1.0" - -[package.extras] -dev = ["tokenizers[testing]"] -docs = ["setuptools_rust", "sphinx", "sphinx_rtd_theme"] -testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "tqdm" -version = "4.66.1" -description = "Fast, Extensible Progress Meter" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, - {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "transformers" -version = "4.35.2" -description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "transformers-4.35.2-py3-none-any.whl", hash = "sha256:9dfa76f8692379544ead84d98f537be01cd1070de75c74efb13abcbc938fbe2f"}, - {file = "transformers-4.35.2.tar.gz", hash = "sha256:2d125e197d77b0cdb6c9201df9fa7e2101493272e448b9fba9341c695bee2f52"}, -] - -[package.dependencies] -filelock = "*" -huggingface-hub = ">=0.16.4,<1.0" -numpy = ">=1.17" -packaging = ">=20.0" -pyyaml = ">=5.1" -regex = "!=2019.12.17" -requests = "*" -safetensors = ">=0.3.1" -tokenizers = ">=0.14,<0.19" -tqdm = ">=4.27" - -[package.extras] -accelerate = ["accelerate (>=0.20.3)"] -agents = ["Pillow (<10.0.0)", "accelerate (>=0.20.3)", "datasets (!=2.5.0)", "diffusers", "opencv-python", "sentencepiece (>=0.1.91,!=0.1.92)", "torch (>=1.10,!=1.12.0)"] -all = ["Pillow (<10.0.0)", "accelerate (>=0.20.3)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune]", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>=2.6,<2.15)", "tensorflow-text (<2.15)", "tf2onnx", "timm", "tokenizers (>=0.14,<0.19)", "torch (>=1.10,!=1.12.0)", "torchaudio", "torchvision"] -audio = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] -codecarbon = ["codecarbon (==1.2.0)"] -deepspeed = ["accelerate (>=0.20.3)", "deepspeed (>=0.9.3)"] -deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.20.3)", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder (>=0.3.0)", "nltk", "optuna", "parameterized", "protobuf", "psutil", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] -dev = ["GitPython (<3.1.19)", "Pillow (<10.0.0)", "accelerate (>=0.20.3)", "av (==9.2.0)", "beautifulsoup4", "black (>=23.1,<24.0)", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "decord (==0.6.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "ray[tune]", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>=2.6,<2.15)", "tensorflow-text (<2.15)", "tf2onnx", "timeout-decorator", "timm", "tokenizers (>=0.14,<0.19)", "torch (>=1.10,!=1.12.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] -dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (<10.0.0)", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>=2.6,<2.15)", "tensorflow-text (<2.15)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.14,<0.19)", "urllib3 (<2.0.0)"] -dev-torch = ["GitPython (<3.1.19)", "Pillow (<10.0.0)", "accelerate (>=0.20.3)", "beautifulsoup4", "black (>=23.1,<24.0)", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "librosa", "nltk", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "ray[tune]", "rhoknp (>=1.1.0,<1.3.1)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "timeout-decorator", "timm", "tokenizers (>=0.14,<0.19)", "torch (>=1.10,!=1.12.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] -docs = ["Pillow (<10.0.0)", "accelerate (>=0.20.3)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.7.0)", "hf-doc-builder", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune]", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>=2.6,<2.15)", "tensorflow-text (<2.15)", "tf2onnx", "timm", "tokenizers (>=0.14,<0.19)", "torch (>=1.10,!=1.12.0)", "torchaudio", "torchvision"] -docs-specific = ["hf-doc-builder"] -flax = ["flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "optax (>=0.0.8,<=0.1.4)"] -flax-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] -ftfy = ["ftfy"] -integrations = ["optuna", "ray[tune]", "sigopt"] -ja = ["fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "rhoknp (>=1.1.0,<1.3.1)", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)"] -modelcreation = ["cookiecutter (==1.7.3)"] -natten = ["natten (>=0.14.6)"] -onnx = ["onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "tf2onnx"] -onnxruntime = ["onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)"] -optuna = ["optuna"] -quality = ["GitPython (<3.1.19)", "black (>=23.1,<24.0)", "datasets (!=2.5.0)", "hf-doc-builder (>=0.3.0)", "isort (>=5.5.4)", "ruff (>=0.0.241,<=0.0.259)", "urllib3 (<2.0.0)"] -ray = ["ray[tune]"] -retrieval = ["datasets (!=2.5.0)", "faiss-cpu"] -sagemaker = ["sagemaker (>=2.31.0)"] -sentencepiece = ["protobuf", "sentencepiece (>=0.1.91,!=0.1.92)"] -serving = ["fastapi", "pydantic (<2)", "starlette", "uvicorn"] -sigopt = ["sigopt"] -sklearn = ["scikit-learn"] -speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] -testing = ["GitPython (<3.1.19)", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder (>=0.3.0)", "nltk", "parameterized", "protobuf", "psutil", "pytest (>=7.2.0)", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "tensorboard", "timeout-decorator"] -tf = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow (>=2.6,<2.15)", "tensorflow-text (<2.15)", "tf2onnx"] -tf-cpu = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow-cpu (>=2.6,<2.15)", "tensorflow-text (<2.15)", "tf2onnx"] -tf-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] -timm = ["timm"] -tokenizers = ["tokenizers (>=0.14,<0.19)"] -torch = ["accelerate (>=0.20.3)", "torch (>=1.10,!=1.12.0)"] -torch-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] -torch-vision = ["Pillow (<10.0.0)", "torchvision"] -torchhub = ["filelock", "huggingface-hub (>=0.16.4,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.14,<0.19)", "torch (>=1.10,!=1.12.0)", "tqdm (>=4.27)"] -video = ["av (==9.2.0)", "decord (==0.6.0)"] -vision = ["Pillow (<10.0.0)"] - -[[package]] -name = "typing-extensions" -version = "4.8.0" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, -] - -[[package]] -name = "urllib3" -version = "2.1.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "watchdog" -version = "3.0.0" -description = "Filesystem events monitoring" -optional = false -python-versions = ">=3.7" -files = [ - {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"}, - {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"}, - {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"}, - {file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"}, - {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"}, - {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"}, - {file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"}, - {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"}, - {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"}, - {file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"}, - {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"}, - {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"}, - {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"}, - {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"}, - {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"}, - {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"}, - {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"}, -] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "zipp" -version = "3.17.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - -[metadata] -lock-version = "2.0" -python-versions = ">=3.8,<3.12.0" -content-hash = "0dc130ed9149c05685afdd1b072e746263e0497e691cab4903ac0af506dcbcb0" diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 26a2983..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,52 +0,0 @@ -[tool.poetry] -name = "maestro" -version = "0.1.1rc1" -description = "Visual Prompting for Large Multimodal Models (LMMs)" -authors = ["Piotr Skalski "] -maintainers = ["Piotr Skalski "] -readme = "README.md" -license = "MIT" -packages = [{include = "maestro"}] -homepage = "https://github.com/roboflow/multimodal-maestro" -repository = "https://github.com/roboflow/multimodal-maestro" -documentation = "https://roboflow.github.io/multimodal-maestro" - -classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Intended Audience :: Education', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3 :: Only', - 'Topic :: Software Development', - 'Topic :: Scientific/Engineering', - "Topic :: Scientific/Engineering :: Artificial Intelligence", - 'Typing :: Typed', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX :: Linux', - 'Operating System :: MacOS' -] - -[tool.poetry.dependencies] -python = ">=3.8,<3.12.0" -supervision = "^0.17.0rc4" -requests = "^2.31.0" -transformers = "^4.35.2" - -[tool.poetry.group.dev.dependencies] -pytest = "^7.2.2" - -[tool.poetry.group.docs.dependencies] -mkdocs-material = "^9.1.4" -mkdocstrings = {extras = ["python"], version = ">=0.20,<0.25"} - -[tool.setuptools] -include-package-data = false - -[tool.setuptools.packages.find] -exclude = ["docs*", "test*","cookbooks*"] \ No newline at end of file diff --git a/requirements/requirements.docs.txt b/requirements/requirements.docs.txt new file mode 100644 index 0000000..877109e --- /dev/null +++ b/requirements/requirements.docs.txt @@ -0,0 +1,2 @@ +mkdocs-material~=9.5.33 +mkdocstrings[python]>=0.20.0,<0.25.2 diff --git a/requirements/requirements.test.txt b/requirements/requirements.test.txt new file mode 100644 index 0000000..615272d --- /dev/null +++ b/requirements/requirements.test.txt @@ -0,0 +1,5 @@ +pytest~=8.3.2 +black~=24.8.0 +pre-commit~=3.8.0 +mypy~=1.11.2 +flake8~=7.1.1 diff --git a/requirements/requirements.txt b/requirements/requirements.txt new file mode 100644 index 0000000..1f0c4ed --- /dev/null +++ b/requirements/requirements.txt @@ -0,0 +1,4 @@ +supervision~=0.22.0 +requests>=2.31.0,<=2.32.3 +transformers~=4.44.2 +torch~=2.4.0 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..9e16b7b --- /dev/null +++ b/setup.py @@ -0,0 +1,66 @@ +from typing import List, Union + +import setuptools +from setuptools import find_packages + + +with open("README.md", "r") as fh: + long_description = fh.read() + + +def read_requirements(path: Union[str, List[str]]) -> List[str]: + if not isinstance(path, list): + path = [path] + requirements = [] + for p in path: + with open(p) as fh: + requirements.extend([line.strip() for line in fh]) + return requirements + + +setuptools.setup( + name="maestro", + version="0.2.0rc1", + author="Roboflow", + author_email="help@roboflow.com", + description="Visual Prompting for Large Multimodal Models (LMMs)", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/roboflow/multimodal-maestro", + packages=find_packages( + where=".", + exclude=( + "cookbooks", + "docs", + "tests", + "tests.*", + "requirements", + ), + ), + install_requires=read_requirements("requirements/requirements.txt"), + extras_require={ + "dev": read_requirements("requirements/requirements.test.txt"), + "docs": read_requirements("requirements/requirements.docs.txt"), + }, + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Software Development", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Typing :: Typed", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS", + ], + python_requires=">=3.9,<3.13", +) From b3eee67276edff248e9e8ed1d30b3bfa9ef988d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Tue, 27 Aug 2024 00:27:34 +0200 Subject: [PATCH 02/69] Add basic utils --- maestro/trainer/__init__.py | 0 maestro/trainer/common/__init__.py | 0 .../trainer/common/configuration/__init__.py | 0 maestro/trainer/common/configuration/env.py | 2 ++ .../trainer/common/data_loaders/__init__.py | 0 maestro/trainer/common/data_loaders/jsonl.py | 31 +++++++++++++++++++ maestro/trainer/common/utils/__init__.py | 0 maestro/trainer/common/utils/file_system.py | 28 +++++++++++++++++ .../trainer/common/utils/reproducibility.py | 24 ++++++++++++++ maestro/trainer/models/__init__.py | 0 maestro/trainer/models/florence_2/__init__.py | 0 maestro/trainer/models/paligemma/__init__.py | 0 mypy.ini | 2 ++ requirements/requirements.txt | 3 ++ 14 files changed, 90 insertions(+) create mode 100644 maestro/trainer/__init__.py create mode 100644 maestro/trainer/common/__init__.py create mode 100644 maestro/trainer/common/configuration/__init__.py create mode 100644 maestro/trainer/common/configuration/env.py create mode 100644 maestro/trainer/common/data_loaders/__init__.py create mode 100644 maestro/trainer/common/data_loaders/jsonl.py create mode 100644 maestro/trainer/common/utils/__init__.py create mode 100644 maestro/trainer/common/utils/file_system.py create mode 100644 maestro/trainer/common/utils/reproducibility.py create mode 100644 maestro/trainer/models/__init__.py create mode 100644 maestro/trainer/models/florence_2/__init__.py create mode 100644 maestro/trainer/models/paligemma/__init__.py create mode 100644 mypy.ini diff --git a/maestro/trainer/__init__.py b/maestro/trainer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/maestro/trainer/common/__init__.py b/maestro/trainer/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/maestro/trainer/common/configuration/__init__.py b/maestro/trainer/common/configuration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/maestro/trainer/common/configuration/env.py b/maestro/trainer/common/configuration/env.py new file mode 100644 index 0000000..99c5e5b --- /dev/null +++ b/maestro/trainer/common/configuration/env.py @@ -0,0 +1,2 @@ +SEED_ENV = "SEED" +DEFAULT_SEED = "42" diff --git a/maestro/trainer/common/data_loaders/__init__.py b/maestro/trainer/common/data_loaders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/maestro/trainer/common/data_loaders/jsonl.py b/maestro/trainer/common/data_loaders/jsonl.py new file mode 100644 index 0000000..4507b80 --- /dev/null +++ b/maestro/trainer/common/data_loaders/jsonl.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import random +from typing import List + +from torch.utils.data import Dataset + +from maestro.trainer.common.utils.file_system import read_jsonl + + +class Data(Dataset): + # TODO: implementation could be better - avoiding loading + # whole files to memory + + @classmethod + def from_jsonl_file(cls, path: str) -> Data: + file_content = read_jsonl(path=path) + random.shuffle(file_content) + return cls(jsons=file_content) + + def __init__(self, jsons: List[dict]): + self.jsons = jsons + + def __getitem__(self, index): + return self.jsons[index] + + def __len__(self): + return len(self.jsons) + + def shuffle(self): + random.shuffle(self.jsons) diff --git a/maestro/trainer/common/utils/__init__.py b/maestro/trainer/common/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/maestro/trainer/common/utils/file_system.py b/maestro/trainer/common/utils/file_system.py new file mode 100644 index 0000000..bf4d72f --- /dev/null +++ b/maestro/trainer/common/utils/file_system.py @@ -0,0 +1,28 @@ +import json +from typing import Union, List + + +def read_jsonl(path: str) -> List[dict]: + file_lines = read_file( + path=path, + split_lines=True, + ) + return [json.loads(line) for line in file_lines] + + +def read_file( + path: str, + split_lines: bool = False, + strip_white_spaces: bool = False, + line_separator: str = "\n", +) -> Union[str, List[str]]: + with open(path, "r") as f: + file_content = f.read() + if strip_white_spaces: + file_content = file_content.strip() + if not split_lines: + return file_content + lines = file_content.split(line_separator) + if not strip_white_spaces: + return lines + return [line.strip() for line in lines] diff --git a/maestro/trainer/common/utils/reproducibility.py b/maestro/trainer/common/utils/reproducibility.py new file mode 100644 index 0000000..f55d646 --- /dev/null +++ b/maestro/trainer/common/utils/reproducibility.py @@ -0,0 +1,24 @@ +import os +import random +from typing import Optional + +import torch +import numpy as np + +from maestro.trainer.common.configuration.env import SEED_ENV, DEFAULT_SEED + + +def set_random_generators_seeds( + seed: Optional[int] = None, + disable_cudnn_benchmark: bool = True, + avoid_non_deterministic_algorithms: bool = True, +) -> None: + if seed is None: + seed = int(os.getenv(SEED_ENV, DEFAULT_SEED)) + random.seed(seed) + torch.manual_seed(seed) + np.random.seed(seed) + if avoid_non_deterministic_algorithms: + torch.use_deterministic_algorithms(True) + if disable_cudnn_benchmark: + torch.backends.cudnn.benchmark = False diff --git a/maestro/trainer/models/__init__.py b/maestro/trainer/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/maestro/trainer/models/florence_2/__init__.py b/maestro/trainer/models/florence_2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/maestro/trainer/models/paligemma/__init__.py b/maestro/trainer/models/paligemma/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..61a34b0 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,2 @@ +[mypy-requests] +ignore_missing_imports = True diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 1f0c4ed..a302f7d 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -2,3 +2,6 @@ supervision~=0.22.0 requests>=2.31.0,<=2.32.3 transformers~=4.44.2 torch~=2.4.0 +accelerate~=0.33.0 +sentencepiece~=0.2.0 +peft~=0.12.0 From 8a0713e744f4542086e3f32ae226c953a59f6508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Tue, 27 Aug 2024 01:24:48 +0200 Subject: [PATCH 03/69] Add basic training utils --- .pre-commit-config.yaml | 2 + maestro/trainer/common/configuration/env.py | 3 + maestro/trainer/common/data_loaders/jsonl.py | 4 +- .../trainer/common/utils/metrics_tracing.py | 28 +++ maestro/trainer/models/paligemma/training.py | 220 ++++++++++++++++++ 5 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 maestro/trainer/common/utils/metrics_tracing.py create mode 100644 maestro/trainer/models/paligemma/training.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ed3e8f3..bf39b30 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,6 +9,7 @@ repos: rev: 24.8.0 hooks: - id: black + args: [--line-length=120] - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.11.2 hooks: @@ -17,3 +18,4 @@ repos: rev: 7.1.1 hooks: - id: flake8 + args: [--max-line-length=120] diff --git a/maestro/trainer/common/configuration/env.py b/maestro/trainer/common/configuration/env.py index 99c5e5b..eeb9105 100644 --- a/maestro/trainer/common/configuration/env.py +++ b/maestro/trainer/common/configuration/env.py @@ -1,2 +1,5 @@ SEED_ENV = "SEED" DEFAULT_SEED = "42" +CUDA_DEVICE_ENV = "CUDA_DEVICE" +DEFAULT_CUDA_DEVICE = "cuda:0" +HF_TOKEN_ENV = "HF_TOKEN" diff --git a/maestro/trainer/common/data_loaders/jsonl.py b/maestro/trainer/common/data_loaders/jsonl.py index 4507b80..3630e11 100644 --- a/maestro/trainer/common/data_loaders/jsonl.py +++ b/maestro/trainer/common/data_loaders/jsonl.py @@ -8,12 +8,12 @@ from maestro.trainer.common.utils.file_system import read_jsonl -class Data(Dataset): +class JSONLDataset(Dataset): # TODO: implementation could be better - avoiding loading # whole files to memory @classmethod - def from_jsonl_file(cls, path: str) -> Data: + def from_jsonl_file(cls, path: str) -> JSONLDataset: file_content = read_jsonl(path=path) random.shuffle(file_content) return cls(jsons=file_content) diff --git a/maestro/trainer/common/utils/metrics_tracing.py b/maestro/trainer/common/utils/metrics_tracing.py new file mode 100644 index 0000000..6c5b6ae --- /dev/null +++ b/maestro/trainer/common/utils/metrics_tracing.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import Dict, Tuple, List + + +class MetricsTracker: + + @classmethod + def init(cls, metrics: List[str]) -> MetricsTracker: + return cls(metrics={metric: [] for metric in metrics}) + + def __init__(self, metrics: Dict[str, List[Tuple[int, int, float]]]): + self._metrics = metrics + + def register(self, metric: str, epoch: int, step: int, value: float) -> None: + self._metrics[metric].append((epoch, step, value)) + + def describe_metrics(self) -> List[str]: + return list(self._metrics.keys()) + + def get_metric_values( + self, + metric: str, + with_index: bool = True, + ) -> list: + if with_index: + return self._metrics[metric] + return [value[2] for value in self._metrics[metric]] diff --git a/maestro/trainer/models/paligemma/training.py b/maestro/trainer/models/paligemma/training.py new file mode 100644 index 0000000..48cb2cc --- /dev/null +++ b/maestro/trainer/models/paligemma/training.py @@ -0,0 +1,220 @@ +import os +from typing import List, Tuple, Optional, Literal, Union, Iterator + +from PIL import Image +from torch import optim +from torch.optim import Optimizer +from torch.optim.lr_scheduler import LRScheduler +from tqdm import tqdm +from transformers import AutoProcessor, PaliGemmaForConditionalGeneration +import torch +from peft import LoraConfig, get_peft_model, PeftModel + +from maestro.trainer.common.configuration.env import ( + CUDA_DEVICE_ENV, + DEFAULT_CUDA_DEVICE, + HF_TOKEN_ENV, +) +from maestro.trainer.common.data_loaders.jsonl import JSONLDataset +from maestro.trainer.common.utils.metrics_tracing import MetricsTracker + +DEFAULT_PALIGEMMA_MODEL_ID = "google/paligemma-3b-pt-224" +DEVICE = torch.device("cpu") if not torch.cuda.is_available() else os.getenv(CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE) + +LoraInitLiteral = Literal["gaussian", "olora", "pissa", "pissa_niter_[number of iters]", "loftq"] + + +def train( + model: PaliGemmaForConditionalGeneration, + processor: AutoProcessor, + train_dataset: JSONLDataset, + dataset_root: str, + batch_size: int, + epochs: int, + learning_rate: float, + device: torch.device = DEVICE, +) -> MetricsTracker: + if device.type == "cup": + raise RuntimeError("PaliGemma training process requires GPU") + metrics_tracker = MetricsTracker.init(metrics=["loss"]) + peft_model = prepare_peft_model(model=model).train() + train_steps = len(train_dataset) // batch_size + with torch.amp.autocast(device.type, torch.float16): + lora_layers = filter(lambda p: p.requires_grad, peft_model.parameters()) + optimizer = optim.SGD(lora_layers, lr=learning_rate) + scheduler = optim.lr_scheduler.CosineAnnealingLR( + optimizer, + epochs * train_steps + 1, + eta_min=learning_rate / 10, + ) + run_training_epochs( + peft_model=peft_model, + processor=processor, + epochs=epochs, + train_steps=train_steps, + train_dataset=train_dataset, + batch_size=batch_size, + dataset_root=dataset_root, + optimizer=optimizer, + scheduler=scheduler, + metrics_tracker=metrics_tracker, + ) + return metrics_tracker + + +def run_training_epochs( + peft_model: PeftModel, + processor: AutoProcessor, + epochs: int, + train_steps: int, + train_dataset: JSONLDataset, + batch_size: int, + dataset_root: str, + optimizer: Optimizer, + scheduler: LRScheduler, + metrics_tracker: MetricsTracker, +) -> None: + for epoch in tqdm(range(epochs), desc="EPOCHS"): + train_dataset.shuffle() + dataset_iterator = iter(train_dataset) + progress_bar = tqdm(range(train_steps), desc="STEPS") + run_training_steps( + peft_model=peft_model, + processor=processor, + epoch=epoch, + train_steps=train_steps, + batch_size=batch_size, + dataset_iterator=dataset_iterator, + dataset_root=dataset_root, + optimizer=optimizer, + scheduler=scheduler, + metrics_tracker=metrics_tracker, + progress_bar=progress_bar, + ) + + +def run_training_steps( + peft_model: PeftModel, + processor: AutoProcessor, + epoch: int, + train_steps: int, + batch_size: int, + dataset_iterator: Iterator[dict], + dataset_root: str, + optimizer: Optimizer, + scheduler: LRScheduler, + metrics_tracker: MetricsTracker, + progress_bar: tqdm[int], +) -> None: + for step in range(1, train_steps + 1): + batch = collect_batch( + batch_size=batch_size, + dataset_iterator=dataset_iterator, + dataset_root=dataset_root, + processor=processor, + ) + loss_tensor = peft_model(**batch)["loss"] + loss_tensor.backward() + loss = loss_tensor.cpu().detach().numpy() + optimizer.step() + optimizer.zero_grad() + scheduler.step() + progress_bar.update(1) + progress_bar.set_description(f"Loss: {loss}") + metrics_tracker.register(metric="loss", epoch=epoch, step=step, value=loss) + + +def collect_batch( + batch_size: int, + dataset_iterator: Iterator[dict], + dataset_root: str, + processor: AutoProcessor, +) -> torch.Tensor: + with torch.no_grad(): + examples = [] + for _ in range(batch_size): + examples.append(next(dataset_iterator)) + return _collate_fn( + examples=examples, + dataset_root=dataset_root, + processor=processor, + ) + + +def load_model( + model_id: str = DEFAULT_PALIGEMMA_MODEL_ID, + revision: str = "float16", + device: torch.device = DEVICE, + hf_token: Optional[str] = None, + cache_dir: Optional[str] = None, +) -> Tuple[AutoProcessor, PaliGemmaForConditionalGeneration]: + if hf_token is None: + hf_token = os.getenv(HF_TOKEN_ENV) + processor = AutoProcessor.from_pretrained(model_id, token=hf_token, cache_dir=cache_dir) + model = PaliGemmaForConditionalGeneration.from_pretrained( + model_id, + revision=revision, + device_map=device, + cache_dir=cache_dir, + token=hf_token, + torch_dtype=torch.float16, + ).eval() + return processor, model + + +def prepare_peft_model( + model: PaliGemmaForConditionalGeneration, + r: int = 12, + lora_alpha: int = 12, + lora_dropout: float = 0.05, + bias: Literal["none", "all", "lora_only"] = "none", + inference_mode: bool = False, + use_rslora: bool = True, + init_lora_weights: Union[bool, LoraInitLiteral] = "gaussian", + revision: str = "float16", + device: torch.device = DEVICE, +) -> PeftModel: + config = LoraConfig( + r=r, + lora_alpha=lora_alpha, + target_modules=["q_proj", "o_proj", "k_proj", "v_proj", "linear"], + task_type="CAUSAL_LM", + lora_dropout=lora_dropout, + bias=bias, + inference_mode=inference_mode, + use_rslora=use_rslora, + init_lora_weights=init_lora_weights, + revision=revision, + ) + return get_peft_model(model, config).to(device) + + +def _collate_fn( + examples: List[dict], + dataset_root: str, + processor: AutoProcessor, + device: torch.device = DEVICE, + image_file_key: str = "image", + prefix_key: str = "prefix", + suffix_key: str = "suffix", +) -> torch.Tensor: + images = [ + _load_image_from_dataset( + image_name=example[image_file_key], + dataset_root=dataset_root, + ) + for example in examples + ] + tokens = processor( + text=[example[prefix_key] for example in examples], + suffix=[example[suffix_key] for example in examples], + images=images, + return_tensors="pt", + padding="longest", + ) + return tokens.to(device) + + +def _load_image_from_dataset(image_name: str, dataset_root: str) -> Image.Image: + image_path = os.path.join(dataset_root, "dataset", image_name) + return Image.open(image_path).convert("RGB") From 2d6e4cfbf57f6c237c33eb50d48600896edcbded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Tue, 27 Aug 2024 09:01:19 +0200 Subject: [PATCH 04/69] Fix wrong typing --- maestro/trainer/models/paligemma/training.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maestro/trainer/models/paligemma/training.py b/maestro/trainer/models/paligemma/training.py index 48cb2cc..695c5a9 100644 --- a/maestro/trainer/models/paligemma/training.py +++ b/maestro/trainer/models/paligemma/training.py @@ -104,7 +104,7 @@ def run_training_steps( optimizer: Optimizer, scheduler: LRScheduler, metrics_tracker: MetricsTracker, - progress_bar: tqdm[int], + progress_bar, ) -> None: for step in range(1, train_steps + 1): batch = collect_batch( From aded4dde1e9816cffe25bfb6618cc94d40d3920d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Tue, 27 Aug 2024 09:43:49 +0200 Subject: [PATCH 05/69] use reproducibility utils --- maestro/trainer/models/paligemma/training.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/maestro/trainer/models/paligemma/training.py b/maestro/trainer/models/paligemma/training.py index 695c5a9..cfd5f1f 100644 --- a/maestro/trainer/models/paligemma/training.py +++ b/maestro/trainer/models/paligemma/training.py @@ -17,6 +17,7 @@ ) from maestro.trainer.common.data_loaders.jsonl import JSONLDataset from maestro.trainer.common.utils.metrics_tracing import MetricsTracker +from maestro.trainer.common.utils.reproducibility import set_random_generators_seeds DEFAULT_PALIGEMMA_MODEL_ID = "google/paligemma-3b-pt-224" DEVICE = torch.device("cpu") if not torch.cuda.is_available() else os.getenv(CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE) @@ -34,6 +35,7 @@ def train( learning_rate: float, device: torch.device = DEVICE, ) -> MetricsTracker: + set_random_generators_seeds() if device.type == "cup": raise RuntimeError("PaliGemma training process requires GPU") metrics_tracker = MetricsTracker.init(metrics=["loss"]) From 263b7febcb48795b2d99df8cb9c4273ae807344d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 11:08:41 +0200 Subject: [PATCH 06/69] Add basic utils for florence --- maestro/trainer/models/florence_2/training.py | 162 ++++++++++++++++++ requirements/requirements.txt | 2 + 2 files changed, 164 insertions(+) create mode 100644 maestro/trainer/models/florence_2/training.py diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py new file mode 100644 index 0000000..4ab7a41 --- /dev/null +++ b/maestro/trainer/models/florence_2/training.py @@ -0,0 +1,162 @@ +import os +from typing import Optional, Tuple, Literal, Union + +import torch +from PIL import Image +from transformers import AutoModelForCausalLM, AutoProcessor +import supervision as sv + +from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE + +DEFAULT_FLORENCE2_MODEL_ID = "microsoft/Florence-2-base-ft" +DEFAULT_FLORENCE2_MODEL_REVISION = "refs/pr/20" +DEVICE = torch.device("cpu") if not torch.cuda.is_available() else os.getenv(CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE) + + +def load_model( + model_id: str = DEFAULT_FLORENCE2_MODEL_ID, + revision: str = DEFAULT_FLORENCE2_MODEL_REVISION, + device: torch.device = DEVICE, + cache_dir: Optional[str] = None, +) -> Tuple[AutoProcessor, AutoModelForCausalLM]: + processor = AutoProcessor.from_pretrained(model_id, trust_remote_code=True, revision=revision) + model = AutoModelForCausalLM.from_pretrained( + model_id, trust_remote_code=True, revision=revision, cache_dir=cache_dir + ).to(device) + return processor, model + + +def caption_image( + image: Image.Image, + processor: AutoProcessor, + model: AutoModelForCausalLM, + task: Literal["", "", ""], + max_new_tokens: int = 1024, + do_sample: bool = False, + num_beams: int = 3, +) -> str: + model_device = model.device + inputs = processor(text=task, images=image, return_tensors="pt").to(model_device) + generated_ids = model.generate( + input_ids=inputs["input_ids"], + pixel_values=inputs["pixel_values"], + max_new_tokens=max_new_tokens, + do_sample=do_sample, + num_beams=num_beams, + ) + generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0] + response = processor.post_process_generation(generated_text, task=task, image_size=(image.width, image.height)) + return response[task] + + +TASKS_THAT_REQUIRE_PROMPT = { + "", + "", + "", +} + + +def segment_objects( + image: Image.Image, + processor: AutoProcessor, + model: AutoModelForCausalLM, + task: Literal[ + "", + "", + ], + prompt: Optional[Union[str, tuple]] = None, + max_new_tokens: int = 1024, + do_sample: bool = False, + num_beams: int = 3, +) -> sv.Detections: + return _prompt_and_retrieve_detections( + image=image, + processor=processor, + model=model, + task=task, + prompt=prompt, + max_new_tokens=max_new_tokens, + do_sample=do_sample, + num_beams=num_beams, + ) + + +def detect_objects( + image: Image.Image, + processor: AutoProcessor, + model: AutoModelForCausalLM, + task: Literal[ + "", + "", + "", + "", + "", + "", + "", + ], + prompt: Optional[Union[str, tuple]] = None, + max_new_tokens: int = 1024, + do_sample: bool = False, + num_beams: int = 3, +) -> sv.Detections: + return _prompt_and_retrieve_detections( + image=image, + processor=processor, + model=model, + task=task, + prompt=prompt, + max_new_tokens=max_new_tokens, + do_sample=do_sample, + num_beams=num_beams, + ) + + +def _prompt_and_retrieve_detections( + image: Image.Image, + processor: AutoProcessor, + model: AutoModelForCausalLM, + task: Literal[ + "", + "", + "", + "", + "", + "", + "", + "", + "", + ], + prompt: Optional[Union[str, tuple]] = None, + max_new_tokens: int = 1024, + do_sample: bool = False, + num_beams: int = 3, +) -> sv.Detections: + if prompt is None: + if task in TASKS_THAT_REQUIRE_PROMPT: + raise ValueError(f"Task {task} requires prompt") + prompt = task + elif isinstance(prompt, tuple): + x_min, y_min, x_max, y_max = prompt + x_min, x_max = round(x_min / image.width), round(x_max / image.width) + y_min, y_max = round(y_min / image.height), round(y_max / image.height) + prompt = f"" + model_device = model.device + inputs = processor(text=prompt, images=image, return_tensors="pt").to(model_device) + generated_ids = model.generate( + input_ids=inputs["input_ids"], + pixel_values=inputs["pixel_values"], + max_new_tokens=max_new_tokens, + do_sample=do_sample, + num_beams=num_beams, + ) + generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0] + response = processor.post_process_generation( + generated_text, + task=task, + image_size=(image.width, image.height), + ) + return sv.Detections.from_lmm( + lmm=sv.LMM.FLORENCE_2, + result=response, + resolution_wh=image.size, + ) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index a302f7d..ce70d29 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -5,3 +5,5 @@ torch~=2.4.0 accelerate~=0.33.0 sentencepiece~=0.2.0 peft~=0.12.0 +flash-attn~=2.6.3 +einops~=0.8.0 From f9abed815ed7e60688252f93ec8046b3796226e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 11:24:52 +0200 Subject: [PATCH 07/69] Fix prompting --- maestro/trainer/models/florence_2/training.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 4ab7a41..21aea4c 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -139,7 +139,9 @@ def _prompt_and_retrieve_detections( x_min, y_min, x_max, y_max = prompt x_min, x_max = round(x_min / image.width), round(x_max / image.width) y_min, y_max = round(y_min / image.height), round(y_max / image.height) - prompt = f"" + prompt = f"{task} " + else: + prompt = f"{task} {prompt}" model_device = model.device inputs = processor(text=prompt, images=image, return_tensors="pt").to(model_device) generated_ids = model.generate( From c65fc442d34d62bbac63d9e165faacf6f8ce127e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 11:37:30 +0200 Subject: [PATCH 08/69] Fix prompting --- maestro/trainer/models/florence_2/training.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 21aea4c..09154c7 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -1,6 +1,7 @@ import os from typing import Optional, Tuple, Literal, Union +import numpy as np import torch from PIL import Image from transformers import AutoModelForCausalLM, AutoProcessor @@ -53,6 +54,8 @@ def caption_image( "", "", "", + "", + "", } @@ -64,7 +67,7 @@ def segment_objects( "", "", ], - prompt: Optional[Union[str, tuple]] = None, + prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, max_new_tokens: int = 1024, do_sample: bool = False, num_beams: int = 3, @@ -94,7 +97,7 @@ def detect_objects( "", "", ], - prompt: Optional[Union[str, tuple]] = None, + prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, max_new_tokens: int = 1024, do_sample: bool = False, num_beams: int = 3, @@ -126,7 +129,7 @@ def _prompt_and_retrieve_detections( "", "", ], - prompt: Optional[Union[str, tuple]] = None, + prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, max_new_tokens: int = 1024, do_sample: bool = False, num_beams: int = 3, @@ -135,11 +138,12 @@ def _prompt_and_retrieve_detections( if task in TASKS_THAT_REQUIRE_PROMPT: raise ValueError(f"Task {task} requires prompt") prompt = task - elif isinstance(prompt, tuple): + elif isinstance(prompt, tuple) or isinstance(prompt, list) or isinstance(prompt, np.ndarray): + if len(prompt) != 4: + raise ValueError("Expected sequence of 4 elements describing (x_min, y_min, x_max, y_max)") x_min, y_min, x_max, y_max = prompt - x_min, x_max = round(x_min / image.width), round(x_max / image.width) - y_min, y_max = round(y_min / image.height), round(y_max / image.height) - prompt = f"{task} " + x_min, x_max = round((x_min / image.width) * 1000), round((x_max / image.width) * 1000) + y_min, y_max = round((y_min / image.height) * 1000), round((y_max / image.height) * 1000) else: prompt = f"{task} {prompt}" model_device = model.device From 0c83e90c150d543db2e355b9d4e82c2a5f9d086f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 11:39:10 +0200 Subject: [PATCH 09/69] Fix prompting --- maestro/trainer/models/florence_2/training.py | 1 + 1 file changed, 1 insertion(+) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 09154c7..2204259 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -144,6 +144,7 @@ def _prompt_and_retrieve_detections( x_min, y_min, x_max, y_max = prompt x_min, x_max = round((x_min / image.width) * 1000), round((x_max / image.width) * 1000) y_min, y_max = round((y_min / image.height) * 1000), round((y_max / image.height) * 1000) + prompt = f"" else: prompt = f"{task} {prompt}" model_device = model.device From 9fbfc10fa28b7d403c7cfc00f712be419de3977c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 11:41:31 +0200 Subject: [PATCH 10/69] Fix prompting --- maestro/trainer/models/florence_2/training.py | 1 + 1 file changed, 1 insertion(+) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 2204259..c27d212 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -162,6 +162,7 @@ def _prompt_and_retrieve_detections( task=task, image_size=(image.width, image.height), ) + print("DEBUG:", prompt, response) return sv.Detections.from_lmm( lmm=sv.LMM.FLORENCE_2, result=response, From 453d4d12f5ef6f4c84fedddd6dfa87daee9fa26d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 11:42:23 +0200 Subject: [PATCH 11/69] Fix prompting --- maestro/trainer/models/florence_2/training.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index c27d212..815ba6e 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -144,7 +144,7 @@ def _prompt_and_retrieve_detections( x_min, y_min, x_max, y_max = prompt x_min, x_max = round((x_min / image.width) * 1000), round((x_max / image.width) * 1000) y_min, y_max = round((y_min / image.height) * 1000), round((y_max / image.height) * 1000) - prompt = f"" + prompt = f"{task} " else: prompt = f"{task} {prompt}" model_device = model.device From 45a04da7aa5a662bcdb8cdfcc97a4a795b64db66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 11:51:05 +0200 Subject: [PATCH 12/69] Fix prompting --- maestro/trainer/models/florence_2/training.py | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 815ba6e..fc2b480 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -31,11 +31,13 @@ def caption_image( image: Image.Image, processor: AutoProcessor, model: AutoModelForCausalLM, - task: Literal["", "", ""], + task: Literal["", "", "", ""], + prompt: Optional[str] = None, max_new_tokens: int = 1024, do_sample: bool = False, num_beams: int = 3, ) -> str: + prompt = _pre_process_prompt(image=image, task=task, prompt=prompt) model_device = model.device inputs = processor(text=task, images=image, return_tensors="pt").to(model_device) generated_ids = model.generate( @@ -56,6 +58,7 @@ def caption_image( "", "", "", + "", } @@ -134,19 +137,7 @@ def _prompt_and_retrieve_detections( do_sample: bool = False, num_beams: int = 3, ) -> sv.Detections: - if prompt is None: - if task in TASKS_THAT_REQUIRE_PROMPT: - raise ValueError(f"Task {task} requires prompt") - prompt = task - elif isinstance(prompt, tuple) or isinstance(prompt, list) or isinstance(prompt, np.ndarray): - if len(prompt) != 4: - raise ValueError("Expected sequence of 4 elements describing (x_min, y_min, x_max, y_max)") - x_min, y_min, x_max, y_max = prompt - x_min, x_max = round((x_min / image.width) * 1000), round((x_max / image.width) * 1000) - y_min, y_max = round((y_min / image.height) * 1000), round((y_max / image.height) * 1000) - prompt = f"{task} " - else: - prompt = f"{task} {prompt}" + prompt = _pre_process_prompt(image=image, task=task, prompt=prompt) model_device = model.device inputs = processor(text=prompt, images=image, return_tensors="pt").to(model_device) generated_ids = model.generate( @@ -162,9 +153,27 @@ def _prompt_and_retrieve_detections( task=task, image_size=(image.width, image.height), ) - print("DEBUG:", prompt, response) return sv.Detections.from_lmm( lmm=sv.LMM.FLORENCE_2, result=response, resolution_wh=image.size, ) + + +def _pre_process_prompt( + image: Image.Image, + task: str, + prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, +) -> str: + if prompt is None: + if task in TASKS_THAT_REQUIRE_PROMPT: + raise ValueError(f"Task {task} requires prompt") + return task + if isinstance(prompt, tuple) or isinstance(prompt, list) or isinstance(prompt, np.ndarray): + if len(prompt) != 4: + raise ValueError("Expected sequence of 4 elements describing (x_min, y_min, x_max, y_max)") + x_min, y_min, x_max, y_max = prompt + x_min, x_max = round((x_min / image.width) * 1000), round((x_max / image.width) * 1000) + y_min, y_max = round((y_min / image.height) * 1000), round((y_max / image.height) * 1000) + return f"{task} " + return f"{task} {prompt}" From 13d9d36c8490bc58b6cc25148dc639c36bb4b476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 11:53:21 +0200 Subject: [PATCH 13/69] Fix prompting --- maestro/trainer/models/florence_2/training.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index fc2b480..79f56bd 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -39,7 +39,7 @@ def caption_image( ) -> str: prompt = _pre_process_prompt(image=image, task=task, prompt=prompt) model_device = model.device - inputs = processor(text=task, images=image, return_tensors="pt").to(model_device) + inputs = processor(text=prompt, images=image, return_tensors="pt").to(model_device) generated_ids = model.generate( input_ids=inputs["input_ids"], pixel_values=inputs["pixel_values"], From e4e9fee026c2d40d9e9dfb7fa6fde5c589d07016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 13:40:18 +0200 Subject: [PATCH 14/69] Scratch of training loop --- .../trainer/common/data_loaders/datasets.py | 50 ++ maestro/trainer/common/utils/leaderboard.py | 41 ++ .../trainer/common/utils/reproducibility.py | 2 +- .../trainer/models/florence_2/inference.py | 159 ++++++ maestro/trainer/models/florence_2/training.py | 504 +++++++++++++----- maestro/trainer/models/paligemma/training.py | 4 +- 6 files changed, 613 insertions(+), 147 deletions(-) create mode 100644 maestro/trainer/common/data_loaders/datasets.py create mode 100644 maestro/trainer/common/utils/leaderboard.py create mode 100644 maestro/trainer/models/florence_2/inference.py diff --git a/maestro/trainer/common/data_loaders/datasets.py b/maestro/trainer/common/data_loaders/datasets.py new file mode 100644 index 0000000..b79cfec --- /dev/null +++ b/maestro/trainer/common/data_loaders/datasets.py @@ -0,0 +1,50 @@ +import json +import os +from typing import List, Dict, Any, Tuple + +from PIL import Image +from transformers.pipelines.base import Dataset + + +class JSONLDataset: + def __init__(self, jsonl_file_path: str, image_directory_path: str): + self.jsonl_file_path = jsonl_file_path + self.image_directory_path = image_directory_path + self.entries = self._load_entries() + + def _load_entries(self) -> List[Dict[str, Any]]: + entries = [] + with open(self.jsonl_file_path, "r") as file: + for line in file: + data = json.loads(line) + entries.append(data) + return entries + + def __len__(self) -> int: + return len(self.entries) + + def __getitem__(self, idx: int) -> Tuple[Image.Image, Dict[str, Any]]: + if idx < 0 or idx >= len(self.entries): + raise IndexError("Index out of range") + + entry = self.entries[idx] + image_path = os.path.join(self.image_directory_path, entry["image"]) + try: + image = Image.open(image_path) + return (image, entry) + except FileNotFoundError: + raise FileNotFoundError(f"Image file {image_path} not found.") + + +class DetectionDataset(Dataset): + def __init__(self, jsonl_file_path: str, image_directory_path: str): + self.dataset = JSONLDataset(jsonl_file_path, image_directory_path) + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, idx): + image, data = self.dataset[idx] + prefix = data["prefix"] + suffix = data["suffix"] + return prefix, suffix, image diff --git a/maestro/trainer/common/utils/leaderboard.py b/maestro/trainer/common/utils/leaderboard.py new file mode 100644 index 0000000..b5b0a61 --- /dev/null +++ b/maestro/trainer/common/utils/leaderboard.py @@ -0,0 +1,41 @@ +from typing import Dict, Tuple, Optional + + +class CheckpointsLeaderboard: + + def __init__( + self, + max_checkpoints: int, + ): + self._max_checkpoints = max(max_checkpoints, 1) + self._leaderboard: Dict[int, Tuple[str, float]] = {} + + def register_checkpoint(self, epoch: int, path: str, loss: float) -> Tuple[bool, Optional[str]]: + if len(self._leaderboard) < self._max_checkpoints: + self._leaderboard[epoch] = (path, loss) + return True, None + max_loss_key, max_loss_in_leaderboard = None, None + for key, (_, loss) in self._leaderboard.items(): + if max_loss_in_leaderboard is None: + max_loss_key = key + max_loss_in_leaderboard = loss + if loss > max_loss_in_leaderboard: # type: ignore + max_loss_key = key + max_loss_in_leaderboard = loss + if loss >= max_loss_in_leaderboard: # type: ignore + return False, None + to_be_removed, _ = self._leaderboard.pop(max_loss_key) # type: ignore + return True, to_be_removed + + def get_best_model(self) -> str: + min_loss_key, min_loss_in_leaderboard = None, None + for key, (_, loss) in self._leaderboard.items(): + if min_loss_in_leaderboard is None: + min_loss_key = key + min_loss_in_leaderboard = loss + if loss < min_loss_in_leaderboard: # type: ignore + min_loss_key = key + min_loss_in_leaderboard = loss + if min_loss_key is None: + raise RuntimeError("Could not retrieve best model") + return min_loss_key # type: ignore diff --git a/maestro/trainer/common/utils/reproducibility.py b/maestro/trainer/common/utils/reproducibility.py index f55d646..69e6f1f 100644 --- a/maestro/trainer/common/utils/reproducibility.py +++ b/maestro/trainer/common/utils/reproducibility.py @@ -8,7 +8,7 @@ from maestro.trainer.common.configuration.env import SEED_ENV, DEFAULT_SEED -def set_random_generators_seeds( +def make_it_reproducible( seed: Optional[int] = None, disable_cudnn_benchmark: bool = True, avoid_non_deterministic_algorithms: bool = True, diff --git a/maestro/trainer/models/florence_2/inference.py b/maestro/trainer/models/florence_2/inference.py new file mode 100644 index 0000000..4f0c154 --- /dev/null +++ b/maestro/trainer/models/florence_2/inference.py @@ -0,0 +1,159 @@ +from typing import Literal, Optional, Union + +import numpy as np +import supervision as sv + +from PIL import Image +from transformers import AutoProcessor, AutoModelForCausalLM + + +def caption_image( + image: Image.Image, + processor: AutoProcessor, + model: AutoModelForCausalLM, + task: Literal["", "", "", ""], + prompt: Optional[str] = None, + max_new_tokens: int = 1024, + do_sample: bool = False, + num_beams: int = 3, +) -> str: + prompt = _pre_process_prompt(image=image, task=task, prompt=prompt) + model_device = model.device + inputs = processor(text=prompt, images=image, return_tensors="pt").to(model_device) + generated_ids = model.generate( + input_ids=inputs["input_ids"], + pixel_values=inputs["pixel_values"], + max_new_tokens=max_new_tokens, + do_sample=do_sample, + num_beams=num_beams, + ) + generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0] + response = processor.post_process_generation(generated_text, task=task, image_size=(image.width, image.height)) + return response[task] + + +TASKS_THAT_REQUIRE_PROMPT = { + "", + "", + "", + "", + "", + "", +} + + +def segment_objects( + image: Image.Image, + processor: AutoProcessor, + model: AutoModelForCausalLM, + task: Literal[ + "", + "", + ], + prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, + max_new_tokens: int = 1024, + do_sample: bool = False, + num_beams: int = 3, +) -> sv.Detections: + return _prompt_and_retrieve_detections( + image=image, + processor=processor, + model=model, + task=task, + prompt=prompt, + max_new_tokens=max_new_tokens, + do_sample=do_sample, + num_beams=num_beams, + ) + + +def detect_objects( + image: Image.Image, + processor: AutoProcessor, + model: AutoModelForCausalLM, + task: Literal[ + "", + "", + "", + "", + "", + "", + "", + ], + prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, + max_new_tokens: int = 1024, + do_sample: bool = False, + num_beams: int = 3, +) -> sv.Detections: + return _prompt_and_retrieve_detections( + image=image, + processor=processor, + model=model, + task=task, + prompt=prompt, + max_new_tokens=max_new_tokens, + do_sample=do_sample, + num_beams=num_beams, + ) + + +def _prompt_and_retrieve_detections( + image: Image.Image, + processor: AutoProcessor, + model: AutoModelForCausalLM, + task: Literal[ + "", + "", + "", + "", + "", + "", + "", + "", + "", + ], + prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, + max_new_tokens: int = 1024, + do_sample: bool = False, + num_beams: int = 3, +) -> sv.Detections: + prompt = _pre_process_prompt(image=image, task=task, prompt=prompt) + model_device = model.device + inputs = processor(text=prompt, images=image, return_tensors="pt").to(model_device) + generated_ids = model.generate( + input_ids=inputs["input_ids"], + pixel_values=inputs["pixel_values"], + max_new_tokens=max_new_tokens, + do_sample=do_sample, + num_beams=num_beams, + ) + generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0] + response = processor.post_process_generation( + generated_text, + task=task, + image_size=(image.width, image.height), + ) + return sv.Detections.from_lmm( + lmm=sv.LMM.FLORENCE_2, + result=response, + resolution_wh=image.size, + ) + + +def _pre_process_prompt( + image: Image.Image, + task: str, + prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, +) -> str: + if prompt is None: + if task in TASKS_THAT_REQUIRE_PROMPT: + raise ValueError(f"Task {task} requires prompt") + return task + if isinstance(prompt, tuple) or isinstance(prompt, list) or isinstance(prompt, np.ndarray): + if len(prompt) != 4: + raise ValueError("Expected sequence of 4 elements describing (x_min, y_min, x_max, y_max)") + x_min, y_min, x_max, y_max = prompt + x_min, x_max = round((x_min / image.width) * 1000), round((x_max / image.width) * 1000) + y_min, y_max = round((y_min / image.height) * 1000), round((y_max / image.height) * 1000) + return f"{task} " + return f"{task} {prompt}" diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 79f56bd..85de6d3 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -1,179 +1,395 @@ +import logging import os -from typing import Optional, Tuple, Literal, Union +import shutil +from dataclasses import dataclass, replace +from functools import partial +from glob import glob +from typing import Optional, Tuple, List, Literal, Union -import numpy as np -import torch from PIL import Image -from transformers import AutoModelForCausalLM, AutoProcessor -import supervision as sv +import torch +from peft import PeftModel, LoraConfig, get_peft_model +from torch.optim import Optimizer, AdamW, Adam, SGD +from torch.optim.lr_scheduler import LRScheduler +from torch.utils.data import DataLoader +from tqdm import tqdm +from transformers import AutoModelForCausalLM, AutoProcessor, get_scheduler from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE +from maestro.trainer.common.data_loaders.datasets import DetectionDataset +from maestro.trainer.common.utils.leaderboard import CheckpointsLeaderboard +from maestro.trainer.common.utils.reproducibility import make_it_reproducible +from maestro.trainer.models.paligemma.training import LoraInitLiteral DEFAULT_FLORENCE2_MODEL_ID = "microsoft/Florence-2-base-ft" DEFAULT_FLORENCE2_MODEL_REVISION = "refs/pr/20" DEVICE = torch.device("cpu") if not torch.cuda.is_available() else os.getenv(CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE) +@dataclass(frozen=True) +class TrainingConfiguration: + dataset_location: str + model_id_or_path: str = DEFAULT_FLORENCE2_MODEL_ID + revision: str = DEFAULT_FLORENCE2_MODEL_REVISION + device: torch.device = DEVICE + transformers_cache_dir: Optional[str] = None + training_epochs: int = 10 + optimiser: Literal["SGD", "adamw", "adam"] = "adamw" + learning_rate: float = 1e-5 + lr_scheduler: Literal["linear", "cosine", "polynomial"] = "linear" + train_batch_size: int = 4 + test_batch_size: Optional[int] = None + loaders_workers: int = 0 + test_loaders_workers: Optional[int] = None + lora_r: int = 8 + lora_alpha: int = 8 + lora_dropout: float = 0.05 + bias: Literal["none", "all", "lora_only"] = "none" + use_rslora: bool = True + init_lora_weights: Union[bool, LoraInitLiteral] = "gaussian" + training_dir = "./training/florence-2" + max_checkpoints_to_keep: int = 3 + + +def train(configuration: TrainingConfiguration) -> None: + make_it_reproducible() + training_run_dir = _establish_training_run_dir( + training_dir=configuration.training_dir, + ) + configuration = replace( + configuration, + training_dir=training_run_dir, # type: ignore + ) + checkpoints_leaderboard = CheckpointsLeaderboard( + max_checkpoints=configuration.max_checkpoints_to_keep, + ) + processor, model = load_model( + model_id_or_path=configuration.model_id_or_path, + revision=configuration.revision, + device=configuration.device, + cache_dir=configuration.transformers_cache_dir, + ) + train_loader, val_loader, test_loader = prepare_data_loaders( + dataset_location=configuration.dataset_location, + train_batch_size=configuration.train_batch_size, + processor=processor, + device=configuration.device, + num_workers=configuration.loaders_workers, + test_loaders_workers=configuration.test_loaders_workers, + ) + if test_loader is None: + test_loader = val_loader + peft_model = prepare_peft_model( + model=model, + r=configuration.lora_r, + lora_alpha=configuration.lora_alpha, + lora_dropout=configuration.lora_dropout, + bias=configuration.bias, + use_rslora=configuration.use_rslora, + init_lora_weights=configuration.init_lora_weights, + revision=configuration.revision, + ) + run_training_loop( + processor=processor, + model=peft_model, + data_loaders=(train_loader, val_loader), + configuration=configuration, + checkpoints_leaderboard=checkpoints_leaderboard, + ) + best_model_path = checkpoints_leaderboard.get_best_model() + print(f"Loading best model from {best_model_path}") + processor, model = load_model( + model_id_or_path=best_model_path, + ) + if test_loader is not None: + run_validation_epoch( + processor=processor, + model=model, + loader=test_loader, + epoch_number=None, + configuration=configuration, + title="Test", + ) + best_model_dir = os.path.join(configuration.training_dir, "best_model") + print(f"Saving best model: {best_model_dir}") + model.save_pretrained(best_model_dir) + processor.save_pretrained(best_model_dir) + + def load_model( - model_id: str = DEFAULT_FLORENCE2_MODEL_ID, + model_id_or_path: str = DEFAULT_FLORENCE2_MODEL_ID, revision: str = DEFAULT_FLORENCE2_MODEL_REVISION, device: torch.device = DEVICE, cache_dir: Optional[str] = None, ) -> Tuple[AutoProcessor, AutoModelForCausalLM]: - processor = AutoProcessor.from_pretrained(model_id, trust_remote_code=True, revision=revision) + processor = AutoProcessor.from_pretrained( + model_id_or_path, + trust_remote_code=True, + revision=revision, + ) model = AutoModelForCausalLM.from_pretrained( - model_id, trust_remote_code=True, revision=revision, cache_dir=cache_dir + model_id_or_path, + trust_remote_code=True, + revision=revision, + cache_dir=cache_dir, ).to(device) return processor, model -def caption_image( - image: Image.Image, - processor: AutoProcessor, - model: AutoModelForCausalLM, - task: Literal["", "", "", ""], - prompt: Optional[str] = None, - max_new_tokens: int = 1024, - do_sample: bool = False, - num_beams: int = 3, -) -> str: - prompt = _pre_process_prompt(image=image, task=task, prompt=prompt) - model_device = model.device - inputs = processor(text=prompt, images=image, return_tensors="pt").to(model_device) - generated_ids = model.generate( - input_ids=inputs["input_ids"], - pixel_values=inputs["pixel_values"], - max_new_tokens=max_new_tokens, - do_sample=do_sample, - num_beams=num_beams, - ) - generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0] - response = processor.post_process_generation(generated_text, task=task, image_size=(image.width, image.height)) - return response[task] - - -TASKS_THAT_REQUIRE_PROMPT = { - "", - "", - "", - "", - "", - "", -} - - -def segment_objects( - image: Image.Image, +def prepare_data_loaders( + dataset_location: str, + train_batch_size: int, processor: AutoProcessor, - model: AutoModelForCausalLM, - task: Literal[ - "", - "", - ], - prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, - max_new_tokens: int = 1024, - do_sample: bool = False, - num_beams: int = 3, -) -> sv.Detections: - return _prompt_and_retrieve_detections( - image=image, + device: torch.device, + num_workers: int = 0, + test_batch_size: Optional[int] = None, + test_loaders_workers: Optional[int] = None, +) -> Tuple[ + DataLoader, + Optional[DataLoader], + Optional[DataLoader], +]: + test_batch_size = test_batch_size or train_batch_size + test_loaders_workers = test_loaders_workers or num_workers + train_data_loader = prepare_detection_data_loader( + dataset_location=dataset_location, + split_name="train", + batch_size=train_batch_size, processor=processor, - model=model, - task=task, - prompt=prompt, - max_new_tokens=max_new_tokens, - do_sample=do_sample, - num_beams=num_beams, + device=device, + num_workers=num_workers, + shuffle=True, + ) + if train_data_loader is None: + raise RuntimeError("Could not initialise train data loader") + valid_data_loader = prepare_detection_data_loader( + dataset_location=dataset_location, + split_name="valid", + batch_size=test_batch_size, + processor=processor, + device=device, + num_workers=test_loaders_workers, + shuffle=False, + ) + test_data_loader = prepare_detection_data_loader( + dataset_location=dataset_location, + split_name="test", + batch_size=test_batch_size, + processor=processor, + device=device, + num_workers=test_loaders_workers, + shuffle=False, ) + return train_data_loader, valid_data_loader, test_data_loader -def detect_objects( - image: Image.Image, +def prepare_detection_data_loader( + dataset_location: str, + split_name: str, + batch_size: int, processor: AutoProcessor, + device: torch.device, + num_workers: int = 0, + shuffle: bool = True, +) -> Optional[DataLoader]: + image_directory_path = os.path.join(dataset_location, split_name) + jsonl_file_path = os.path.join(dataset_location, split_name, "annotations.jsonl") + if os.path.exists(image_directory_path): + logging.warning(f"Could not data directory: {image_directory_path}") + return None + if not os.path.exists(jsonl_file_path): + logging.warning(f"Could not find JSONL file: {jsonl_file_path}") + return None + dataset = DetectionDataset( + jsonl_file_path=jsonl_file_path, + image_directory_path=image_directory_path, + ) + return DataLoader( + dataset, + batch_size=batch_size, + collate_fn=partial(_collate_fn, processor=processor, device=device), + num_workers=num_workers, + shuffle=shuffle, + ) + + +def prepare_peft_model( model: AutoModelForCausalLM, - task: Literal[ - "", - "", - "", - "", - "", - "", - "", - ], - prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, - max_new_tokens: int = 1024, - do_sample: bool = False, - num_beams: int = 3, -) -> sv.Detections: - return _prompt_and_retrieve_detections( - image=image, + r: int = 8, + lora_alpha: int = 8, + lora_dropout: float = 0.05, + bias: Literal["none", "all", "lora_only"] = "none", + inference_mode: bool = False, + use_rslora: bool = True, + init_lora_weights: Union[bool, LoraInitLiteral] = "gaussian", + revision: str = DEFAULT_FLORENCE2_MODEL_REVISION, +) -> PeftModel: + config = LoraConfig( + r=r, + lora_alpha=lora_alpha, + target_modules=["q_proj", "o_proj", "k_proj", "v_proj", "linear", "Conv2d", "lm_head", "fc2"], + task_type="CAUSAL_LM", + lora_dropout=lora_dropout, + bias=bias, + inference_mode=inference_mode, + use_rslora=use_rslora, + init_lora_weights=init_lora_weights, + revision=revision, + ) + peft_model = get_peft_model(model, config) + peft_model.print_trainable_parameters() + return peft_model.to(model.device) + + +def run_training_loop( + processor: AutoProcessor, + model: PeftModel, + data_loaders: Tuple[DataLoader, Optional[DataLoader]], + configuration: TrainingConfiguration, + checkpoints_leaderboard: CheckpointsLeaderboard, +) -> None: + train_loader, val_loader = data_loaders + optimizer = _get_optimizer(model=model, configuration=configuration) + total_num_training_steps = configuration.training_epochs * len(train_loader) + lr_scheduler = get_scheduler( + name=configuration.lr_scheduler, + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=total_num_training_steps, + ) + for epoch in range(configuration.training_epochs): + run_training_epoch( + processor=processor, + model=model, + train_loader=train_loader, + val_loader=val_loader, + epoch_number=epoch, + configuration=configuration, + optimizer=optimizer, + lr_scheduler=lr_scheduler, + checkpoints_leaderboard=checkpoints_leaderboard, + ) + + +def run_training_epoch( + processor: AutoProcessor, + model: PeftModel, + train_loader: DataLoader, + val_loader: Optional[DataLoader], + epoch_number: int, + configuration: TrainingConfiguration, + optimizer: Optimizer, + lr_scheduler: LRScheduler, + checkpoints_leaderboard: CheckpointsLeaderboard, +) -> None: + model.train() + training_losses: List[float] = [] + last_100_losses = training_losses[-100:] + loss_moving_average = sum(last_100_losses) / len(last_100_losses) if len(last_100_losses) > 0 else 0.0 + for inputs, answers in tqdm( + train_loader, + desc=f"Epoch {epoch_number + 1}/{configuration.training_epochs}. " f"Loss: {round(loss_moving_average, 4)}", + ): + input_ids = inputs["input_ids"] + pixel_values = inputs["pixel_values"] + labels = processor.tokenizer( + text=answers, return_tensors="pt", padding=True, return_token_type_ids=False + ).input_ids.to(configuration.device) + outputs = model(input_ids=input_ids, pixel_values=pixel_values, labels=labels) + loss = outputs.loss + loss.backward() + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + training_losses.append(loss.item()) + if len(training_losses) > 0: + avg_train_loss = sum(training_losses) / len(training_losses) + print(f"Average Training Loss: {avg_train_loss}") + if val_loader is None or len(val_loader) == 0: + return None + validation_loss = run_validation_epoch( processor=processor, model=model, - task=task, - prompt=prompt, - max_new_tokens=max_new_tokens, - do_sample=do_sample, - num_beams=num_beams, + loader=val_loader, + epoch_number=epoch_number, + configuration=configuration, + ) + checkpoint_dir = os.path.join(configuration.training_dir, "checkpoints", str(epoch_number)) + should_save, to_remove = checkpoints_leaderboard.register_checkpoint( + epoch=epoch_number, + path=checkpoint_dir, + loss=validation_loss, ) + if should_save: + print(f"Saving checkpoint under {checkpoint_dir}") + os.makedirs(checkpoint_dir, exist_ok=True) + model.save_pretrained(checkpoint_dir) + processor.save_pretrained(checkpoint_dir) + if to_remove is not None: + print(f"Removing checkpoint {to_remove}") + shutil.rmtree(to_remove, ignore_errors=True) + + +def run_validation_epoch( + processor: AutoProcessor, + model: Union[PeftModel, AutoModelForCausalLM], + loader: DataLoader, + epoch_number: Optional[int], + configuration: TrainingConfiguration, + title: str = "Validation", +) -> float: + val_loss = 0.0 + epoch_marker = "" + if epoch_number is not None: + epoch_marker = f"Epoch {epoch_number + 1}/{configuration.training_epochs}" + with torch.no_grad(): + for inputs, answers in tqdm(loader, desc=f"{title} | {epoch_marker}"): + input_ids = inputs["input_ids"] + pixel_values = inputs["pixel_values"] + labels = processor.tokenizer( + text=answers, return_tensors="pt", padding=True, return_token_type_ids=False + ).input_ids.to(configuration.device) + outputs = model(input_ids=input_ids, pixel_values=pixel_values, labels=labels) + loss = outputs.loss + val_loss += loss.item() + avg_val_loss = val_loss / len(loader) + print(f"Average {title} Loss: {avg_val_loss}") + return avg_val_loss -def _prompt_and_retrieve_detections( - image: Image.Image, +def save_model( + target_dir: str, processor: AutoProcessor, model: AutoModelForCausalLM, - task: Literal[ - "", - "", - "", - "", - "", - "", - "", - "", - "", - ], - prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, - max_new_tokens: int = 1024, - do_sample: bool = False, - num_beams: int = 3, -) -> sv.Detections: - prompt = _pre_process_prompt(image=image, task=task, prompt=prompt) - model_device = model.device - inputs = processor(text=prompt, images=image, return_tensors="pt").to(model_device) - generated_ids = model.generate( - input_ids=inputs["input_ids"], - pixel_values=inputs["pixel_values"], - max_new_tokens=max_new_tokens, - do_sample=do_sample, - num_beams=num_beams, - ) - generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0] - response = processor.post_process_generation( - generated_text, - task=task, - image_size=(image.width, image.height), - ) - return sv.Detections.from_lmm( - lmm=sv.LMM.FLORENCE_2, - result=response, - resolution_wh=image.size, - ) - - -def _pre_process_prompt( - image: Image.Image, - task: str, - prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, -) -> str: - if prompt is None: - if task in TASKS_THAT_REQUIRE_PROMPT: - raise ValueError(f"Task {task} requires prompt") - return task - if isinstance(prompt, tuple) or isinstance(prompt, list) or isinstance(prompt, np.ndarray): - if len(prompt) != 4: - raise ValueError("Expected sequence of 4 elements describing (x_min, y_min, x_max, y_max)") - x_min, y_min, x_max, y_max = prompt - x_min, x_max = round((x_min / image.width) * 1000), round((x_max / image.width) * 1000) - y_min, y_max = round((y_min / image.height) * 1000), round((y_max / image.height) * 1000) - return f"{task} " - return f"{task} {prompt}" +) -> None: + os.makedirs(target_dir, exist_ok=True) + processor.save_pretrained(target_dir) + model.save_pretrained(target_dir) + + +def _establish_training_run_dir(training_dir: str) -> str: + training_dir = os.path.abspath(training_dir) + existing_directory_entries = glob(os.path.join(training_dir, "*")) + subdirectories = [path for path in existing_directory_entries if os.path.isdir(path)] + run_id = len(subdirectories) + 1 + training_run_dir = os.path.join(training_dir, str(run_id)) + os.makedirs(training_run_dir, exist_ok=True) + return training_run_dir + + +def _get_optimizer(model: PeftModel, configuration: TrainingConfiguration) -> Optimizer: + if configuration.optimiser == "adamw": + return AdamW(model.parameters(), lr=configuration.learning_rate) + if configuration.optimiser == "adam": + return Adam(model.parameters(), lr=configuration.learning_rate) + return SGD(model.parameters(), lr=configuration.learning_rate) + + +def _collate_fn( + batch: Tuple[List[str], List[str], List[Image.Image]], + processor: AutoProcessor, + device: torch.device, +) -> Tuple[torch.Tensor, torch.Tensor]: + questions, answers, images = zip(*batch) + inputs = processor(text=list(questions), images=list(images), return_tensors="pt", padding=True).to(device) + return inputs, answers diff --git a/maestro/trainer/models/paligemma/training.py b/maestro/trainer/models/paligemma/training.py index cfd5f1f..f0e9737 100644 --- a/maestro/trainer/models/paligemma/training.py +++ b/maestro/trainer/models/paligemma/training.py @@ -17,7 +17,7 @@ ) from maestro.trainer.common.data_loaders.jsonl import JSONLDataset from maestro.trainer.common.utils.metrics_tracing import MetricsTracker -from maestro.trainer.common.utils.reproducibility import set_random_generators_seeds +from maestro.trainer.common.utils.reproducibility import make_it_reproducible DEFAULT_PALIGEMMA_MODEL_ID = "google/paligemma-3b-pt-224" DEVICE = torch.device("cpu") if not torch.cuda.is_available() else os.getenv(CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE) @@ -35,7 +35,7 @@ def train( learning_rate: float, device: torch.device = DEVICE, ) -> MetricsTracker: - set_random_generators_seeds() + make_it_reproducible() if device.type == "cup": raise RuntimeError("PaliGemma training process requires GPU") metrics_tracker = MetricsTracker.init(metrics=["loss"]) From 785014c9cc8078103c7831815ede6db7e0b2b069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 13:48:18 +0200 Subject: [PATCH 15/69] Fix bug --- maestro/trainer/models/florence_2/training.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 85de6d3..9a095f1 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -47,7 +47,7 @@ class TrainingConfiguration: bias: Literal["none", "all", "lora_only"] = "none" use_rslora: bool = True init_lora_weights: Union[bool, LoraInitLiteral] = "gaussian" - training_dir = "./training/florence-2" + training_dir: str = "./training/florence-2" max_checkpoints_to_keep: int = 3 @@ -58,7 +58,7 @@ def train(configuration: TrainingConfiguration) -> None: ) configuration = replace( configuration, - training_dir=training_run_dir, # type: ignore + training_dir=training_run_dir, ) checkpoints_leaderboard = CheckpointsLeaderboard( max_checkpoints=configuration.max_checkpoints_to_keep, From bf2b3e0156338174eb90f6bbe7edeb2548181fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 13:51:21 +0200 Subject: [PATCH 16/69] Fix bug --- maestro/trainer/models/florence_2/training.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 9a095f1..f4a9f47 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -194,7 +194,7 @@ def prepare_detection_data_loader( ) -> Optional[DataLoader]: image_directory_path = os.path.join(dataset_location, split_name) jsonl_file_path = os.path.join(dataset_location, split_name, "annotations.jsonl") - if os.path.exists(image_directory_path): + if not os.path.exists(image_directory_path): logging.warning(f"Could not data directory: {image_directory_path}") return None if not os.path.exists(jsonl_file_path): From d21333e384909926935fd3833e03e26f8a3effa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 13:52:45 +0200 Subject: [PATCH 17/69] Fix bug --- maestro/trainer/models/florence_2/training.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index f4a9f47..07295d8 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -52,7 +52,7 @@ class TrainingConfiguration: def train(configuration: TrainingConfiguration) -> None: - make_it_reproducible() + make_it_reproducible(avoid_non_deterministic_algorithms=False) training_run_dir = _establish_training_run_dir( training_dir=configuration.training_dir, ) From b870667336b021a9ae3a6fb7abbff5abfba727c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 13:57:12 +0200 Subject: [PATCH 18/69] Fix loss display --- maestro/trainer/models/florence_2/training.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 07295d8..66fbda2 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -284,12 +284,8 @@ def run_training_epoch( ) -> None: model.train() training_losses: List[float] = [] - last_100_losses = training_losses[-100:] - loss_moving_average = sum(last_100_losses) / len(last_100_losses) if len(last_100_losses) > 0 else 0.0 - for inputs, answers in tqdm( - train_loader, - desc=f"Epoch {epoch_number + 1}/{configuration.training_epochs}. " f"Loss: {round(loss_moving_average, 4)}", - ): + training_iterator = tqdm(train_loader, desc=f"Epoch {epoch_number + 1}/{configuration.training_epochs}") + for inputs, answers in training_iterator: input_ids = inputs["input_ids"] pixel_values = inputs["pixel_values"] labels = processor.tokenizer( @@ -302,6 +298,11 @@ def run_training_epoch( lr_scheduler.step() optimizer.zero_grad() training_losses.append(loss.item()) + last_100_losses = training_losses[-100:] + loss_moving_average = sum(last_100_losses) / len(last_100_losses) if len(last_100_losses) > 0 else 0.0 + training_iterator.set_description( + f"Epoch {epoch_number + 1}/{configuration.training_epochs}. Loss: {round(loss_moving_average, 4)}" + ) if len(training_losses) > 0: avg_train_loss = sum(training_losses) / len(training_losses) print(f"Average Training Loss: {avg_train_loss}") From d4bb6f9701f6febe564d39d2ff2db4375086c249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 14:06:29 +0200 Subject: [PATCH 19/69] Fix bug with model loading --- maestro/trainer/common/utils/leaderboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maestro/trainer/common/utils/leaderboard.py b/maestro/trainer/common/utils/leaderboard.py index b5b0a61..7d147fa 100644 --- a/maestro/trainer/common/utils/leaderboard.py +++ b/maestro/trainer/common/utils/leaderboard.py @@ -38,4 +38,4 @@ def get_best_model(self) -> str: min_loss_in_leaderboard = loss if min_loss_key is None: raise RuntimeError("Could not retrieve best model") - return min_loss_key # type: ignore + return self._leaderboard[min_loss_key][0] From 4a24e9719ed6664ecbf48e2a40819c0556678028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 15:02:26 +0200 Subject: [PATCH 20/69] Add training summary --- maestro/trainer/common/utils/file_system.py | 12 ++ .../trainer/models/florence_2/data_loading.py | 110 +++++++++++ maestro/trainer/models/florence_2/entities.py | 0 maestro/trainer/models/florence_2/metrics.py | 183 ++++++++++++++++++ maestro/trainer/models/florence_2/training.py | 107 ++-------- 5 files changed, 320 insertions(+), 92 deletions(-) create mode 100644 maestro/trainer/models/florence_2/data_loading.py create mode 100644 maestro/trainer/models/florence_2/entities.py create mode 100644 maestro/trainer/models/florence_2/metrics.py diff --git a/maestro/trainer/common/utils/file_system.py b/maestro/trainer/common/utils/file_system.py index bf4d72f..e258f25 100644 --- a/maestro/trainer/common/utils/file_system.py +++ b/maestro/trainer/common/utils/file_system.py @@ -1,4 +1,5 @@ import json +import os from typing import Union, List @@ -26,3 +27,14 @@ def read_file( if not strip_white_spaces: return lines return [line.strip() for line in lines] + + +def save_json(path: str, content: dict) -> None: + ensure_parent_dir_exists(path=path) + with open(path, "w") as f: + json.dump(content, f, indent=4) + + +def ensure_parent_dir_exists(path: str) -> None: + parent_dir = os.path.dirname(os.path.abspath(path)) + os.makedirs(parent_dir, exist_ok=True) diff --git a/maestro/trainer/models/florence_2/data_loading.py b/maestro/trainer/models/florence_2/data_loading.py new file mode 100644 index 0000000..4fa9708 --- /dev/null +++ b/maestro/trainer/models/florence_2/data_loading.py @@ -0,0 +1,110 @@ +import logging +import os +from functools import partial +from typing import Optional, Tuple, List + +import torch +from PIL import Image +from torch.utils.data import DataLoader +from transformers import AutoProcessor + +from maestro.trainer.common.data_loaders.datasets import DetectionDataset + + +def prepare_data_loaders( + dataset_location: str, + train_batch_size: int, + processor: AutoProcessor, + device: torch.device, + num_workers: int = 0, + test_batch_size: Optional[int] = None, + test_loaders_workers: Optional[int] = None, +) -> Tuple[ + DataLoader, + Optional[DataLoader], + Optional[DataLoader], +]: + test_batch_size = test_batch_size or train_batch_size + test_loaders_workers = test_loaders_workers or num_workers + train_data_loader = prepare_detection_data_loader( + dataset_location=dataset_location, + split_name="train", + batch_size=train_batch_size, + processor=processor, + device=device, + num_workers=num_workers, + shuffle=True, + ) + if train_data_loader is None: + raise RuntimeError("Could not initialise train data loader") + valid_data_loader = prepare_detection_data_loader( + dataset_location=dataset_location, + split_name="valid", + batch_size=test_batch_size, + processor=processor, + device=device, + num_workers=test_loaders_workers, + shuffle=False, + ) + test_data_loader = prepare_detection_data_loader( + dataset_location=dataset_location, + split_name="test", + batch_size=test_batch_size, + processor=processor, + device=device, + num_workers=test_loaders_workers, + shuffle=False, + ) + return train_data_loader, valid_data_loader, test_data_loader + + +def prepare_detection_data_loader( + dataset_location: str, + split_name: str, + batch_size: int, + processor: AutoProcessor, + device: torch.device, + num_workers: int = 0, + shuffle: bool = True, +) -> Optional[DataLoader]: + dataset = prepare_detection_dataset( + dataset_location=dataset_location, + split_name=split_name, + ) + if dataset is None: + return None + return DataLoader( + dataset, + batch_size=batch_size, + collate_fn=partial(_collate_fn, processor=processor, device=device), + num_workers=num_workers, + shuffle=shuffle, + ) + + +def prepare_detection_dataset( + dataset_location: str, + split_name: str, +) -> Optional[DetectionDataset]: + image_directory_path = os.path.join(dataset_location, split_name) + jsonl_file_path = os.path.join(dataset_location, split_name, "annotations.jsonl") + if not os.path.exists(image_directory_path): + logging.warning(f"Could not data directory: {image_directory_path}") + return None + if not os.path.exists(jsonl_file_path): + logging.warning(f"Could not find JSONL file: {jsonl_file_path}") + return None + return DetectionDataset( + jsonl_file_path=jsonl_file_path, + image_directory_path=image_directory_path, + ) + + +def _collate_fn( + batch: Tuple[List[str], List[str], List[Image.Image]], + processor: AutoProcessor, + device: torch.device, +) -> Tuple[torch.Tensor, torch.Tensor]: + questions, answers, images = zip(*batch) + inputs = processor(text=list(questions), images=list(images), return_tensors="pt", padding=True).to(device) + return inputs, answers diff --git a/maestro/trainer/models/florence_2/entities.py b/maestro/trainer/models/florence_2/entities.py new file mode 100644 index 0000000..e69de29 diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py new file mode 100644 index 0000000..7c43118 --- /dev/null +++ b/maestro/trainer/models/florence_2/metrics.py @@ -0,0 +1,183 @@ +import os +import random +import re +from typing import List, Tuple + +import cv2 +import numpy as np +import torch +import supervision as sv +from tqdm import tqdm +from transformers import AutoProcessor, AutoModelForCausalLM + +from maestro.trainer.common.data_loaders.datasets import DetectionDataset +from maestro.trainer.common.utils.file_system import save_json +from maestro.trainer.models.florence_2.data_loading import prepare_detection_dataset + + +DETECTION_CLASS_PATTERN = r"([a-zA-Z0-9 ]+ of [a-zA-Z0-9 ]+)" + + +def prepare_detection_training_summary( + processor: AutoProcessor, + model: AutoModelForCausalLM, + dataset_location: str, + split_name: str, + training_dir: str, + num_samples_to_visualise: int, + device: torch.device, +) -> None: + dataset = prepare_detection_dataset( + dataset_location=dataset_location, + split_name=split_name, + ) + if dataset is None: + return None + targets, predictions, post_processed_text_outputs = get_ground_truths_and_predictions( + dataset=dataset, + processor=processor, + model=model, + split_name=split_name, + device=device, + ) + mean_average_precision = sv.MeanAveragePrecision.from_detections( + predictions=predictions, + targets=targets, + ) + print(f"{split_name} | map50_95: {mean_average_precision.map50_95:.2f}") + print(f"{split_name} | map50: {mean_average_precision.map50:.2f}") + print(f"{split_name} |map75: {mean_average_precision.map75:.2f}") + dump_metrics( + training_dir=training_dir, + split_name=split_name, + metrics=mean_average_precision, + ) + dump_post_processed_outputs( + dataset=dataset, + post_processed_text_outputs=post_processed_text_outputs, + training_dir=training_dir, + split_name=split_name, + ) + dump_visualised_samples( + dataset=dataset, + targets=targets, + predictions=predictions, + num_samples_to_visualise=num_samples_to_visualise, + training_dir=training_dir, + split_name=split_name, + ) + + +def get_ground_truths_and_predictions( + dataset: DetectionDataset, + processor: AutoProcessor, + model: AutoModelForCausalLM, + split_name: str, + device: torch.device, +) -> Tuple[List[sv.Detections], List[sv.Detections], List[str]]: + classes = extract_classes(dataset=dataset) + targets = [] + predictions = [] + post_processed_text_outputs = [] + for idx in tqdm(list(range(len(dataset))), desc=f"Generating {split_name} predictions..."): + image, data = dataset.dataset[idx] + prefix = data["prefix"] + suffix = data["suffix"] + + inputs = processor(text=prefix, images=image, return_tensors="pt").to(device) + generated_ids = model.generate( + input_ids=inputs["input_ids"], pixel_values=inputs["pixel_values"], max_new_tokens=1024, num_beams=3 + ) + generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0] + prediction = processor.post_process_generation(generated_text, task="", image_size=image.size) + post_processed_text_outputs.append(prediction) + prediction = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, prediction, resolution_wh=image.size) + prediction = prediction[np.isin(prediction["class_name"], classes)] + prediction.class_id = np.array([class_name.index(class_name) for class_name in prediction["class_name"]]) + prediction.confidence = np.ones(len(prediction)) + target = processor.post_process_generation(suffix, task="", image_size=image.size) + target = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, target, resolution_wh=image.size) + target.class_id = np.array([class_name.index(class_name) for class_name in target["class_name"]]) + targets.append(target) + predictions.append(prediction) + return targets, predictions, post_processed_text_outputs + + +def extract_classes(dataset: DetectionDataset) -> List[str]: + class_set = set() + for i in range(len(dataset.dataset)): + image, data = dataset.dataset[i] + suffix = data["suffix"] + classes = re.findall(DETECTION_CLASS_PATTERN, suffix) + class_set.update(classes) + return sorted(class_set) + + +def dump_metrics( + training_dir: str, + split_name: str, + metrics: sv.MeanAveragePrecision, +) -> None: + target_path = os.path.join(training_dir, split_name, f"metrics_{split_name}.json") + content = { + "map50_95": metrics.map50_95, + "map50": metrics.map50, + "map75": metrics.map75, + } + save_json(path=target_path, content=content) + + +def dump_post_processed_outputs( + dataset: DetectionDataset, + post_processed_text_outputs: List[str], + training_dir: str, + split_name: str, +) -> None: + result_dict = { + dataset.dataset.entries[idx]["image"]: output for idx, output in enumerate(post_processed_text_outputs) + } + target_path = os.path.join(training_dir, split_name, f"post_processed_text_predictions_{split_name}.json") + save_json(path=target_path, content=result_dict) + + +def dump_visualised_samples( + dataset: DetectionDataset, + targets: List[sv.Detections], + predictions: List[sv.Detections], + num_samples_to_visualise: int, + training_dir: str, + split_name: str, +) -> None: + all_indices = list(range(len(dataset))) + random.shuffle(all_indices) + selected_indices = all_indices[:num_samples_to_visualise] + target_dir = os.path.join(training_dir, "predictions_visualised", split_name) + os.makedirs(target_dir, exist_ok=True) + boxes_annotator = sv.BoxAnnotator(color_lookup=sv.ColorLookup.INDEX) + label_annotator = sv.LabelAnnotator(color_lookup=sv.ColorLookup.INDEX) + for idx in tqdm(selected_indices, desc="Preparing predictions visualisations..."): + image_name = dataset.dataset.entries[idx]["image"] + image = dataset.dataset[idx][0] + prediction_image = boxes_annotator.annotate( + image.copy(), + predictions[idx], + ) + prediction_image = np.asarray( + label_annotator.annotate( + prediction_image, + predictions[idx], + ).convert("BRG") + ) + target_image = boxes_annotator.annotate( + image.copy(), + targets[idx], + ) + target_image = np.asarray( + label_annotator.annotate( + target_image, + targets[idx], + ).convert("BRG") + ) + concatenated = cv2.hconcat([target_image, prediction_image]) + target_image_path = os.path.join(target_dir, image_name) + cv2.imwrite(target_image_path, concatenated) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 66fbda2..c201bfd 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -1,12 +1,9 @@ -import logging import os import shutil -from dataclasses import dataclass, replace -from functools import partial +from dataclasses import replace, dataclass from glob import glob from typing import Optional, Tuple, List, Literal, Union -from PIL import Image import torch from peft import PeftModel, LoraConfig, get_peft_model from torch.optim import Optimizer, AdamW, Adam, SGD @@ -16,11 +13,13 @@ from transformers import AutoModelForCausalLM, AutoProcessor, get_scheduler from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE -from maestro.trainer.common.data_loaders.datasets import DetectionDataset from maestro.trainer.common.utils.leaderboard import CheckpointsLeaderboard from maestro.trainer.common.utils.reproducibility import make_it_reproducible +from maestro.trainer.models.florence_2.data_loading import prepare_data_loaders +from maestro.trainer.models.florence_2.metrics import prepare_detection_training_summary from maestro.trainer.models.paligemma.training import LoraInitLiteral + DEFAULT_FLORENCE2_MODEL_ID = "microsoft/Florence-2-base-ft" DEFAULT_FLORENCE2_MODEL_REVISION = "refs/pr/20" DEVICE = torch.device("cpu") if not torch.cuda.is_available() else os.getenv(CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE) @@ -49,6 +48,7 @@ class TrainingConfiguration: init_lora_weights: Union[bool, LoraInitLiteral] = "gaussian" training_dir: str = "./training/florence-2" max_checkpoints_to_keep: int = 3 + num_samples_to_visualise: int = 64 def train(configuration: TrainingConfiguration) -> None: @@ -114,6 +114,16 @@ def train(configuration: TrainingConfiguration) -> None: print(f"Saving best model: {best_model_dir}") model.save_pretrained(best_model_dir) processor.save_pretrained(best_model_dir) + for split_name in ["valid", "test"]: + prepare_detection_training_summary( + processor=processor, + model=model, + dataset_location=configuration.dataset_location, + split_name=split_name, + training_dir=configuration.training_dir, + num_samples_to_visualise=configuration.num_samples_to_visualise, + device=configuration.device, + ) def load_model( @@ -136,83 +146,6 @@ def load_model( return processor, model -def prepare_data_loaders( - dataset_location: str, - train_batch_size: int, - processor: AutoProcessor, - device: torch.device, - num_workers: int = 0, - test_batch_size: Optional[int] = None, - test_loaders_workers: Optional[int] = None, -) -> Tuple[ - DataLoader, - Optional[DataLoader], - Optional[DataLoader], -]: - test_batch_size = test_batch_size or train_batch_size - test_loaders_workers = test_loaders_workers or num_workers - train_data_loader = prepare_detection_data_loader( - dataset_location=dataset_location, - split_name="train", - batch_size=train_batch_size, - processor=processor, - device=device, - num_workers=num_workers, - shuffle=True, - ) - if train_data_loader is None: - raise RuntimeError("Could not initialise train data loader") - valid_data_loader = prepare_detection_data_loader( - dataset_location=dataset_location, - split_name="valid", - batch_size=test_batch_size, - processor=processor, - device=device, - num_workers=test_loaders_workers, - shuffle=False, - ) - test_data_loader = prepare_detection_data_loader( - dataset_location=dataset_location, - split_name="test", - batch_size=test_batch_size, - processor=processor, - device=device, - num_workers=test_loaders_workers, - shuffle=False, - ) - return train_data_loader, valid_data_loader, test_data_loader - - -def prepare_detection_data_loader( - dataset_location: str, - split_name: str, - batch_size: int, - processor: AutoProcessor, - device: torch.device, - num_workers: int = 0, - shuffle: bool = True, -) -> Optional[DataLoader]: - image_directory_path = os.path.join(dataset_location, split_name) - jsonl_file_path = os.path.join(dataset_location, split_name, "annotations.jsonl") - if not os.path.exists(image_directory_path): - logging.warning(f"Could not data directory: {image_directory_path}") - return None - if not os.path.exists(jsonl_file_path): - logging.warning(f"Could not find JSONL file: {jsonl_file_path}") - return None - dataset = DetectionDataset( - jsonl_file_path=jsonl_file_path, - image_directory_path=image_directory_path, - ) - return DataLoader( - dataset, - batch_size=batch_size, - collate_fn=partial(_collate_fn, processor=processor, device=device), - num_workers=num_workers, - shuffle=shuffle, - ) - - def prepare_peft_model( model: AutoModelForCausalLM, r: int = 8, @@ -384,13 +317,3 @@ def _get_optimizer(model: PeftModel, configuration: TrainingConfiguration) -> Op if configuration.optimiser == "adam": return Adam(model.parameters(), lr=configuration.learning_rate) return SGD(model.parameters(), lr=configuration.learning_rate) - - -def _collate_fn( - batch: Tuple[List[str], List[str], List[Image.Image]], - processor: AutoProcessor, - device: torch.device, -) -> Tuple[torch.Tensor, torch.Tensor]: - questions, answers, images = zip(*batch) - inputs = processor(text=list(questions), images=list(images), return_tensors="pt", padding=True).to(device) - return inputs, answers From 44a2d0be5ee34fac0fe103703569e255b4ca9b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 15:08:24 +0200 Subject: [PATCH 21/69] Fix visualisation bug --- maestro/trainer/models/florence_2/metrics.py | 4 ++-- maestro/trainer/models/florence_2/training.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 7c43118..83a22bc 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -166,7 +166,7 @@ def dump_visualised_samples( label_annotator.annotate( prediction_image, predictions[idx], - ).convert("BRG") + ).convert("BGR") ) target_image = boxes_annotator.annotate( image.copy(), @@ -176,7 +176,7 @@ def dump_visualised_samples( label_annotator.annotate( target_image, targets[idx], - ).convert("BRG") + ).convert("BGR") ) concatenated = cv2.hconcat([target_image, prediction_image]) target_image_path = os.path.join(target_dir, image_name) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index c201bfd..6d9d7c3 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -275,9 +275,9 @@ def run_validation_epoch( val_loss = 0.0 epoch_marker = "" if epoch_number is not None: - epoch_marker = f"Epoch {epoch_number + 1}/{configuration.training_epochs}" + epoch_marker = f"| Epoch {epoch_number + 1}/{configuration.training_epochs}" with torch.no_grad(): - for inputs, answers in tqdm(loader, desc=f"{title} | {epoch_marker}"): + for inputs, answers in tqdm(loader, desc=f"{title} {epoch_marker}"): input_ids = inputs["input_ids"] pixel_values = inputs["pixel_values"] labels = processor.tokenizer( From 5048079c377eb9d007e2717ec534d545ac8962a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 15:13:48 +0200 Subject: [PATCH 22/69] Fix visualisation bug --- maestro/trainer/models/florence_2/metrics.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 83a22bc..f9a835e 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -166,8 +166,8 @@ def dump_visualised_samples( label_annotator.annotate( prediction_image, predictions[idx], - ).convert("BGR") - ) + ) + )[:, :, ::-1] target_image = boxes_annotator.annotate( image.copy(), targets[idx], @@ -176,8 +176,8 @@ def dump_visualised_samples( label_annotator.annotate( target_image, targets[idx], - ).convert("BGR") - ) + ) + )[:, :, ::-1] concatenated = cv2.hconcat([target_image, prediction_image]) target_image_path = os.path.join(target_dir, image_name) cv2.imwrite(target_image_path, concatenated) From 7c62cae6ef0dcacd1232956178e7114831222dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 17:15:41 +0200 Subject: [PATCH 23/69] Add metrics plots --- maestro/trainer/models/florence_2/metrics.py | 46 ++++++++++++++++++- maestro/trainer/models/florence_2/training.py | 35 ++++++++++++-- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index f9a835e..fb0ba29 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -3,6 +3,7 @@ import re from typing import List, Tuple +import matplotlib.pyplot as plt import cv2 import numpy as np import torch @@ -12,6 +13,7 @@ from maestro.trainer.common.data_loaders.datasets import DetectionDataset from maestro.trainer.common.utils.file_system import save_json +from maestro.trainer.common.utils.metrics_tracing import MetricsTracker from maestro.trainer.models.florence_2.data_loading import prepare_detection_dataset @@ -46,7 +48,7 @@ def prepare_detection_training_summary( ) print(f"{split_name} | map50_95: {mean_average_precision.map50_95:.2f}") print(f"{split_name} | map50: {mean_average_precision.map50:.2f}") - print(f"{split_name} |map75: {mean_average_precision.map75:.2f}") + print(f"{split_name} | map75: {mean_average_precision.map75:.2f}") dump_metrics( training_dir=training_dir, split_name=split_name, @@ -118,7 +120,7 @@ def dump_metrics( split_name: str, metrics: sv.MeanAveragePrecision, ) -> None: - target_path = os.path.join(training_dir, split_name, f"metrics_{split_name}.json") + target_path = os.path.join(training_dir, "metrics", split_name, f"metrics_{split_name}.json") content = { "map50_95": metrics.map50_95, "map50": metrics.map50, @@ -181,3 +183,43 @@ def dump_visualised_samples( concatenated = cv2.hconcat([target_image, prediction_image]) target_image_path = os.path.join(target_dir, image_name) cv2.imwrite(target_image_path, concatenated) + + +def summarise_training_metrics( + training_metrics_tracker: MetricsTracker, + validation_metrics_tracker: MetricsTracker, + training_dir: str, +) -> None: + summarise_metrics(metrics_tracker=training_metrics_tracker, training_dir=training_dir, split_name="train") + summarise_metrics(metrics_tracker=validation_metrics_tracker, training_dir=training_dir, split_name="valid") + + +def summarise_metrics( + metrics_tracker: MetricsTracker, + training_dir: str, + split_name: str, +) -> None: + plots_dir_path = os.path.join(training_dir, "metrics", split_name) + for metric_name in metrics_tracker.describe_metrics(): + plot_path = os.path.join(plots_dir_path, f"metric_{metric_name}_plot.png") + plt.clf() + metric_values_with_index = metrics_tracker.get_metric_values( + metric=metric_name, + with_index=True, + ) + xs = np.arange(0, len(metric_values_with_index)) + xticks_xs, xticks_labels = [], [] + previous = None + for v, x in zip(metric_values_with_index, xs): + if v[0] != previous: + xticks_xs.append(x) + xticks_labels.append(v[0]) + previous = v[0] + ys = [e[2] for e in metric_values_with_index] + plt.scatter(xs, ys, marker="x") + plt.plot(xs, ys, linestyle="dashed", linewidth=0.3) + plt.title(f"Value of {metric_name} for {split_name} set") + plt.xticks(xticks_xs, labels=xticks_labels) + plt.xlabel("Epochs") + plt.savefig(plot_path, dpi=120) + plt.clf() diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 6d9d7c3..54993c5 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -14,9 +14,10 @@ from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE from maestro.trainer.common.utils.leaderboard import CheckpointsLeaderboard +from maestro.trainer.common.utils.metrics_tracing import MetricsTracker from maestro.trainer.common.utils.reproducibility import make_it_reproducible from maestro.trainer.models.florence_2.data_loading import prepare_data_loaders -from maestro.trainer.models.florence_2.metrics import prepare_detection_training_summary +from maestro.trainer.models.florence_2.metrics import prepare_detection_training_summary, summarise_training_metrics from maestro.trainer.models.paligemma.training import LoraInitLiteral @@ -89,12 +90,16 @@ def train(configuration: TrainingConfiguration) -> None: init_lora_weights=configuration.init_lora_weights, revision=configuration.revision, ) + training_metrics_tracker = MetricsTracker.init(metrics=["loss"]) + validation_metrics_tracker = MetricsTracker.init(metrics=["loss"]) run_training_loop( processor=processor, model=peft_model, data_loaders=(train_loader, val_loader), configuration=configuration, checkpoints_leaderboard=checkpoints_leaderboard, + training_metrics_tracker=training_metrics_tracker, + validation_metrics_tracker=validation_metrics_tracker, ) best_model_path = checkpoints_leaderboard.get_best_model() print(f"Loading best model from {best_model_path}") @@ -114,6 +119,11 @@ def train(configuration: TrainingConfiguration) -> None: print(f"Saving best model: {best_model_dir}") model.save_pretrained(best_model_dir) processor.save_pretrained(best_model_dir) + summarise_training_metrics( + training_metrics_tracker=training_metrics_tracker, + validation_metrics_tracker=validation_metrics_tracker, + training_dir=configuration.training_dir, + ) for split_name in ["valid", "test"]: prepare_detection_training_summary( processor=processor, @@ -180,6 +190,8 @@ def run_training_loop( data_loaders: Tuple[DataLoader, Optional[DataLoader]], configuration: TrainingConfiguration, checkpoints_leaderboard: CheckpointsLeaderboard, + training_metrics_tracker: MetricsTracker, + validation_metrics_tracker: MetricsTracker, ) -> None: train_loader, val_loader = data_loaders optimizer = _get_optimizer(model=model, configuration=configuration) @@ -201,6 +213,8 @@ def run_training_loop( optimizer=optimizer, lr_scheduler=lr_scheduler, checkpoints_leaderboard=checkpoints_leaderboard, + training_metrics_tracker=training_metrics_tracker, + validation_metrics_tracker=validation_metrics_tracker, ) @@ -214,11 +228,13 @@ def run_training_epoch( optimizer: Optimizer, lr_scheduler: LRScheduler, checkpoints_leaderboard: CheckpointsLeaderboard, + training_metrics_tracker: MetricsTracker, + validation_metrics_tracker: MetricsTracker, ) -> None: model.train() training_losses: List[float] = [] training_iterator = tqdm(train_loader, desc=f"Epoch {epoch_number + 1}/{configuration.training_epochs}") - for inputs, answers in training_iterator: + for step_id, (inputs, answers) in enumerate(training_iterator): input_ids = inputs["input_ids"] pixel_values = inputs["pixel_values"] labels = processor.tokenizer( @@ -230,7 +246,14 @@ def run_training_epoch( optimizer.step() lr_scheduler.step() optimizer.zero_grad() - training_losses.append(loss.item()) + loss = loss.item() + training_metrics_tracker.register( + metric="loss", + epoch=epoch_number, + step=step_id + 1, + value=loss, + ) + training_losses.append(loss) last_100_losses = training_losses[-100:] loss_moving_average = sum(last_100_losses) / len(last_100_losses) if len(last_100_losses) > 0 else 0.0 training_iterator.set_description( @@ -248,6 +271,12 @@ def run_training_epoch( epoch_number=epoch_number, configuration=configuration, ) + validation_metrics_tracker.register( + metric="loss", + epoch=epoch_number, + step=1, + value=validation_loss, + ) checkpoint_dir = os.path.join(configuration.training_dir, "checkpoints", str(epoch_number)) should_save, to_remove = checkpoints_leaderboard.register_checkpoint( epoch=epoch_number, From 783dc886750dd24c6fa3f689538e9d2b16d94be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 17:22:12 +0200 Subject: [PATCH 24/69] Add metrics plots --- maestro/trainer/models/florence_2/metrics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index fb0ba29..d45c0b0 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -200,6 +200,7 @@ def summarise_metrics( split_name: str, ) -> None: plots_dir_path = os.path.join(training_dir, "metrics", split_name) + os.makedirs(plots_dir_path, exist_ok=True) for metric_name in metrics_tracker.describe_metrics(): plot_path = os.path.join(plots_dir_path, f"metric_{metric_name}_plot.png") plt.clf() From dbe7593f9e644d5f203d8421e5297ed6dc768d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 17:31:23 +0200 Subject: [PATCH 25/69] Add metrics plots --- maestro/trainer/models/florence_2/metrics.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index d45c0b0..5746665 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -138,7 +138,12 @@ def dump_post_processed_outputs( result_dict = { dataset.dataset.entries[idx]["image"]: output for idx, output in enumerate(post_processed_text_outputs) } - target_path = os.path.join(training_dir, split_name, f"post_processed_text_predictions_{split_name}.json") + target_path = os.path.join( + training_dir, + "model_debug", + split_name, + f"post_processed_text_predictions_{split_name}.json", + ) save_json(path=target_path, content=result_dict) From 2fd4d7d1ab43891fa1aaa5a0d62d9472c1878283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 29 Aug 2024 17:53:54 +0200 Subject: [PATCH 26/69] Fix minor issues --- maestro/trainer/common/utils/leaderboard.py | 1 + maestro/trainer/models/florence_2/training.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/maestro/trainer/common/utils/leaderboard.py b/maestro/trainer/common/utils/leaderboard.py index 7d147fa..6f381fe 100644 --- a/maestro/trainer/common/utils/leaderboard.py +++ b/maestro/trainer/common/utils/leaderboard.py @@ -25,6 +25,7 @@ def register_checkpoint(self, epoch: int, path: str, loss: float) -> Tuple[bool, if loss >= max_loss_in_leaderboard: # type: ignore return False, None to_be_removed, _ = self._leaderboard.pop(max_loss_key) # type: ignore + self._leaderboard[epoch] = (path, loss) return True, to_be_removed def get_best_model(self) -> str: diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 54993c5..4a0cd5e 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -208,7 +208,7 @@ def run_training_loop( model=model, train_loader=train_loader, val_loader=val_loader, - epoch_number=epoch, + epoch_number=epoch + 1, configuration=configuration, optimizer=optimizer, lr_scheduler=lr_scheduler, @@ -233,7 +233,7 @@ def run_training_epoch( ) -> None: model.train() training_losses: List[float] = [] - training_iterator = tqdm(train_loader, desc=f"Epoch {epoch_number + 1}/{configuration.training_epochs}") + training_iterator = tqdm(train_loader, desc=f"Epoch {epoch_number}/{configuration.training_epochs}") for step_id, (inputs, answers) in enumerate(training_iterator): input_ids = inputs["input_ids"] pixel_values = inputs["pixel_values"] @@ -257,7 +257,7 @@ def run_training_epoch( last_100_losses = training_losses[-100:] loss_moving_average = sum(last_100_losses) / len(last_100_losses) if len(last_100_losses) > 0 else 0.0 training_iterator.set_description( - f"Epoch {epoch_number + 1}/{configuration.training_epochs}. Loss: {round(loss_moving_average, 4)}" + f"Epoch {epoch_number}/{configuration.training_epochs}. Loss: {round(loss_moving_average, 4)}" ) if len(training_losses) > 0: avg_train_loss = sum(training_losses) / len(training_losses) From 6ff192651684f351d8906a5f3e17e4451ca21133 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 3 Sep 2024 13:00:18 +0200 Subject: [PATCH 27/69] fix: current Florence-2 training pipeline is missing `timm` --- requirements/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index ce70d29..c10deb3 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -7,3 +7,4 @@ sentencepiece~=0.2.0 peft~=0.12.0 flash-attn~=2.6.3 einops~=0.8.0 +timm~=1.0.9 \ No newline at end of file From 47aef063789453bbd76bb451d570e0d52fc5d99f Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 4 Sep 2024 11:18:59 +0200 Subject: [PATCH 28/69] update maestro `README.md` --- README.md | 133 ++++++------------------------------------------------ 1 file changed, 13 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index ec1dfa1..9814a8e 100644 --- a/README.md +++ b/README.md @@ -1,142 +1,35 @@
-

multimodal-maestro

+

maestro

-
- - [![version](https://badge.fury.io/py/maestro.svg)](https://badge.fury.io/py/maestro) - [![license](https://img.shields.io/pypi/l/maestro)](https://github.com/roboflow/multimodal-maestro/blob/main/LICENSE) - [![python-version](https://img.shields.io/pypi/pyversions/maestro)](https://badge.fury.io/py/maestro) - [![Gradio](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/Roboflow/SoM) - [![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow/multimodal-maestro/blob/develop/cookbooks/multimodal_maestro_gpt_4_vision.ipynb) +

coming: when it's ready...

## 👋 hello -Multimodal-Maestro gives you more control over large multimodal models to get the -outputs you want. With more effective prompting tactics, you can get multimodal models -to do tasks you didn't know (or think!) were possible. Curious how it works? Try our -[HF space](https://huggingface.co/spaces/Roboflow/SoM)! +**maestro** is a tool designed to streamline and accelerate the fine-tuning process for +multimodal models. It provides ready-to-use recipes for fine-tuning popular +vision-language models (VLMs) such as **Florence-2**, **PaliGemma**, and +**Phi-3.5 Vision** on downstream vision-language tasks. ## 💻 install -⚠️ Our package has been renamed to `maestro`. Install the package in a -[**3.11>=Python>=3.8**](https://www.python.org/) environment. +Pip install the supervision package in a +[**Python>=3.8**](https://www.python.org/) environment. ```bash pip install maestro ``` -## 🔌 API - -🚧 The project is still under construction. The redesigned API is coming soon. - -![maestro-docs-Snap](https://github.com/roboflow/multimodal-maestro/assets/26109316/a787b7c0-527e-465a-9ca9-d46f4d63ea53) - -## 🧑‍🍳 prompting cookbooks - -| Description | Colab | -|:----------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| -| Prompt LMMs with Multimodal Maestro | [![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow/multimodal-maestro/blob/develop/cookbooks/multimodal_maestro_gpt_4_vision.ipynb) | -| Manually annotate ONE image and let GPT-4V annotate ALL of them | [![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow/multimodal-maestro/blob/develop/cookbooks/grounding_dino_and_gpt4_vision.ipynb) | - - ## 🚀 example -``` -Find dog. - ->>> The dog is prominently featured in the center of the image with the label [9]. -``` - -
-👉 read more - -
- -- **load image** - - ```python - import cv2 - - image = cv2.imread("...") - ``` - -- **create and refine marks** - - ```python - import maestro - - generator = maestro.SegmentAnythingMarkGenerator(device='cuda') - marks = generator.generate(image=image) - marks = maestro.refine_marks(marks=marks) - ``` - -- **visualize marks** - - ```python - mark_visualizer = maestro.MarkVisualizer() - marked_image = mark_visualizer.visualize(image=image, marks=marks) - ``` - ![image-vs-marked-image](https://github.com/roboflow/multimodal-maestro/assets/26109316/92951ed2-65c0-475a-9279-6fd344757092) - -- **prompt** - - ```python - prompt = "Find dog." - - response = maestro.prompt_image(api_key=api_key, image=marked_image, prompt=prompt) - ``` - - ``` - >>> "The dog is prominently featured in the center of the image with the label [9]." - ``` - -- **extract related marks** - - ```python - masks = maestro.extract_relevant_masks(text=response, detections=refined_marks) - ``` - - ``` - >>> {'6': array([ - ... [False, False, False, ..., False, False, False], - ... [False, False, False, ..., False, False, False], - ... [False, False, False, ..., False, False, False], - ... ..., - ... [ True, True, True, ..., False, False, False], - ... [ True, True, True, ..., False, False, False], - ... [ True, True, True, ..., False, False, False]]) - ... } - ``` - -
- -![multimodal-maestro](https://github.com/roboflow/multimodal-maestro/assets/26109316/c04f2b18-2a1d-4535-9582-e5d3ec0a926e) +Documentation and Florence-2 fine-tuning examples for object detection and VQA coming +soon. ## 🚧 roadmap -- [ ] Rewriting the `maestro` API. -- [ ] Update [HF space](https://huggingface.co/spaces/Roboflow/SoM). -- [ ] Documentation page. -- [ ] Add GroundingDINO prompting strategy. -- [ ] CovVLM demo. -- [ ] Qwen-VL demo. - -## 💜 acknowledgement - -- [Set-of-Mark Prompting Unleashes Extraordinary Visual Grounding -in GPT-4V](https://arxiv.org/abs/2310.11441) by Jianwei Yang, Hao Zhang, Feng Li, Xueyan -Zou, Chunyuan Li, Jianfeng Gao. -- [The Dawn of LMMs: Preliminary Explorations with GPT-4V(ision)](https://arxiv.org/abs/2309.17421) -by Zhengyuan Yang, Linjie Li, Kevin Lin, Jianfeng Wang, Chung-Ching Lin, Zicheng Liu, -Lijuan Wang - -## 🦸 contribution - -We would love your help in making this repository even better! If you noticed any bug, -or if you have any suggestions for improvement, feel free to open an -[issue](https://github.com/roboflow/multimodal-maestro/issues) or submit a -[pull request](https://github.com/roboflow/multimodal-maestro/pulls). +- [ ] Release a CLI for predefined fine-tuning recipes. +- [ ] Multi-GPU fine-tuning support. +- [ ] Allow multi-dataset fine-tuning and support multiple tasks at the same time. From 36251e581b232f8302419e8d3e8227fd76e89dac Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 4 Sep 2024 14:19:57 +0200 Subject: [PATCH 29/69] wip --- maestro/trainer/models/florence_2/training.py | 69 ++++++++++--------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 4a0cd5e..91239e0 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -101,39 +101,42 @@ def train(configuration: TrainingConfiguration) -> None: training_metrics_tracker=training_metrics_tracker, validation_metrics_tracker=validation_metrics_tracker, ) - best_model_path = checkpoints_leaderboard.get_best_model() - print(f"Loading best model from {best_model_path}") - processor, model = load_model( - model_id_or_path=best_model_path, - ) - if test_loader is not None: - run_validation_epoch( - processor=processor, - model=model, - loader=test_loader, - epoch_number=None, - configuration=configuration, - title="Test", - ) - best_model_dir = os.path.join(configuration.training_dir, "best_model") - print(f"Saving best model: {best_model_dir}") - model.save_pretrained(best_model_dir) - processor.save_pretrained(best_model_dir) - summarise_training_metrics( - training_metrics_tracker=training_metrics_tracker, - validation_metrics_tracker=validation_metrics_tracker, - training_dir=configuration.training_dir, - ) - for split_name in ["valid", "test"]: - prepare_detection_training_summary( - processor=processor, - model=model, - dataset_location=configuration.dataset_location, - split_name=split_name, - training_dir=configuration.training_dir, - num_samples_to_visualise=configuration.num_samples_to_visualise, - device=configuration.device, - ) + + return training_metrics_tracker, validation_metrics_tracker + + # best_model_path = checkpoints_leaderboard.get_best_model() + # print(f"Loading best model from {best_model_path}") + # processor, model = load_model( + # model_id_or_path=best_model_path, + # ) + # if test_loader is not None: + # run_validation_epoch( + # processor=processor, + # model=model, + # loader=test_loader, + # epoch_number=None, + # configuration=configuration, + # title="Test", + # ) + # best_model_dir = os.path.join(configuration.training_dir, "best_model") + # print(f"Saving best model: {best_model_dir}") + # model.save_pretrained(best_model_dir) + # processor.save_pretrained(best_model_dir) + # summarise_training_metrics( + # training_metrics_tracker=training_metrics_tracker, + # validation_metrics_tracker=validation_metrics_tracker, + # training_dir=configuration.training_dir, + # ) + # for split_name in ["valid", "test"]: + # prepare_detection_training_summary( + # processor=processor, + # model=model, + # dataset_location=configuration.dataset_location, + # split_name=split_name, + # training_dir=configuration.training_dir, + # num_samples_to_visualise=configuration.num_samples_to_visualise, + # device=configuration.device, + # ) def load_model( From 5f025afeea7cc74da3c4321020f0423deb179275 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 4 Sep 2024 16:13:24 +0200 Subject: [PATCH 30/69] ready for test --- .../trainer/common/utils/metrics_tracing.py | 70 +++++++++++++++++ maestro/trainer/models/florence_2/metrics.py | 43 ----------- maestro/trainer/models/florence_2/training.py | 76 +++++++++---------- requirements/requirements.txt | 2 +- 4 files changed, 109 insertions(+), 82 deletions(-) diff --git a/maestro/trainer/common/utils/metrics_tracing.py b/maestro/trainer/common/utils/metrics_tracing.py index 6c5b6ae..a69a2ae 100644 --- a/maestro/trainer/common/utils/metrics_tracing.py +++ b/maestro/trainer/common/utils/metrics_tracing.py @@ -1,7 +1,10 @@ from __future__ import annotations +from collections import defaultdict from typing import Dict, Tuple, List +import matplotlib.pyplot as plt + class MetricsTracker: @@ -26,3 +29,70 @@ def get_metric_values( if with_index: return self._metrics[metric] return [value[2] for value in self._metrics[metric]] + + +def aggregate_by_epoch(metric_values: List[Tuple[int, int, float]]) -> Dict[int, float]: + epoch_data = defaultdict(list) + for epoch, step, value in metric_values: + epoch_data[epoch].append(value) + avg_per_epoch = { + epoch: sum(values) / len(values) + for epoch, values + in epoch_data.items() + } + return avg_per_epoch + + +def save_metric_plots( + training_tracker: MetricsTracker, + validation_tracker: MetricsTracker, + output_dir: str +): + training_metrics = training_tracker.describe_metrics() + validation_metrics = validation_tracker.describe_metrics() + all_metrics = set(training_metrics + validation_metrics) + + for metric in all_metrics: + plt.figure(figsize=(8, 6)) + + if metric in training_metrics: + training_values = training_tracker.get_metric_values( + metric=metric, with_index=True) + training_avg_values = aggregate_by_epoch(training_values) + training_epochs = sorted(training_avg_values.keys()) + training_vals = [training_avg_values[epoch] for epoch in training_epochs] + plt.plot( + x=training_epochs, + y=training_vals, + label=f'Training {metric}', + marker='o', + linestyle='-', + color='blue' + ) + + if metric in validation_metrics: + validation_values = validation_tracker.get_metric_values( + metric=metric, with_index=True) + validation_avg_values = aggregate_by_epoch(validation_values) + validation_epochs = sorted(validation_avg_values.keys()) + validation_vals = [ + validation_avg_values[epoch] + for epoch + in validation_epochs + ] + plt.plot( + x=validation_epochs, + y=validation_vals, + label=f'Validation {metric}', + marker='o', + linestyle='--', + color='orange' + ) + + plt.title(f'{metric.capitalize()} over Epochs') + plt.xlabel('Epoch') + plt.ylabel(f'{metric.capitalize()} Value') + plt.legend() + plt.grid(True) + plt.savefig(f'{output_dir}/{metric}_plot.png') + plt.close() diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 5746665..3ed7aab 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -3,7 +3,6 @@ import re from typing import List, Tuple -import matplotlib.pyplot as plt import cv2 import numpy as np import torch @@ -13,7 +12,6 @@ from maestro.trainer.common.data_loaders.datasets import DetectionDataset from maestro.trainer.common.utils.file_system import save_json -from maestro.trainer.common.utils.metrics_tracing import MetricsTracker from maestro.trainer.models.florence_2.data_loading import prepare_detection_dataset @@ -188,44 +186,3 @@ def dump_visualised_samples( concatenated = cv2.hconcat([target_image, prediction_image]) target_image_path = os.path.join(target_dir, image_name) cv2.imwrite(target_image_path, concatenated) - - -def summarise_training_metrics( - training_metrics_tracker: MetricsTracker, - validation_metrics_tracker: MetricsTracker, - training_dir: str, -) -> None: - summarise_metrics(metrics_tracker=training_metrics_tracker, training_dir=training_dir, split_name="train") - summarise_metrics(metrics_tracker=validation_metrics_tracker, training_dir=training_dir, split_name="valid") - - -def summarise_metrics( - metrics_tracker: MetricsTracker, - training_dir: str, - split_name: str, -) -> None: - plots_dir_path = os.path.join(training_dir, "metrics", split_name) - os.makedirs(plots_dir_path, exist_ok=True) - for metric_name in metrics_tracker.describe_metrics(): - plot_path = os.path.join(plots_dir_path, f"metric_{metric_name}_plot.png") - plt.clf() - metric_values_with_index = metrics_tracker.get_metric_values( - metric=metric_name, - with_index=True, - ) - xs = np.arange(0, len(metric_values_with_index)) - xticks_xs, xticks_labels = [], [] - previous = None - for v, x in zip(metric_values_with_index, xs): - if v[0] != previous: - xticks_xs.append(x) - xticks_labels.append(v[0]) - previous = v[0] - ys = [e[2] for e in metric_values_with_index] - plt.scatter(xs, ys, marker="x") - plt.plot(xs, ys, linestyle="dashed", linewidth=0.3) - plt.title(f"Value of {metric_name} for {split_name} set") - plt.xticks(xticks_xs, labels=xticks_labels) - plt.xlabel("Epochs") - plt.savefig(plot_path, dpi=120) - plt.clf() diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 91239e0..d5351ef 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -12,12 +12,14 @@ from tqdm import tqdm from transformers import AutoModelForCausalLM, AutoProcessor, get_scheduler -from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE +from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, \ + DEFAULT_CUDA_DEVICE from maestro.trainer.common.utils.leaderboard import CheckpointsLeaderboard -from maestro.trainer.common.utils.metrics_tracing import MetricsTracker +from maestro.trainer.common.utils.metrics_tracing import MetricsTracker, \ + save_metric_plots from maestro.trainer.common.utils.reproducibility import make_it_reproducible from maestro.trainer.models.florence_2.data_loading import prepare_data_loaders -from maestro.trainer.models.florence_2.metrics import prepare_detection_training_summary, summarise_training_metrics +from maestro.trainer.models.florence_2.metrics import prepare_detection_training_summary from maestro.trainer.models.paligemma.training import LoraInitLiteral @@ -102,41 +104,39 @@ def train(configuration: TrainingConfiguration) -> None: validation_metrics_tracker=validation_metrics_tracker, ) - return training_metrics_tracker, validation_metrics_tracker - - # best_model_path = checkpoints_leaderboard.get_best_model() - # print(f"Loading best model from {best_model_path}") - # processor, model = load_model( - # model_id_or_path=best_model_path, - # ) - # if test_loader is not None: - # run_validation_epoch( - # processor=processor, - # model=model, - # loader=test_loader, - # epoch_number=None, - # configuration=configuration, - # title="Test", - # ) - # best_model_dir = os.path.join(configuration.training_dir, "best_model") - # print(f"Saving best model: {best_model_dir}") - # model.save_pretrained(best_model_dir) - # processor.save_pretrained(best_model_dir) - # summarise_training_metrics( - # training_metrics_tracker=training_metrics_tracker, - # validation_metrics_tracker=validation_metrics_tracker, - # training_dir=configuration.training_dir, - # ) - # for split_name in ["valid", "test"]: - # prepare_detection_training_summary( - # processor=processor, - # model=model, - # dataset_location=configuration.dataset_location, - # split_name=split_name, - # training_dir=configuration.training_dir, - # num_samples_to_visualise=configuration.num_samples_to_visualise, - # device=configuration.device, - # ) + best_model_path = checkpoints_leaderboard.get_best_model() + print(f"Loading best model from {best_model_path}") + processor, model = load_model( + model_id_or_path=best_model_path, + ) + if test_loader is not None: + run_validation_epoch( + processor=processor, + model=model, + loader=test_loader, + epoch_number=None, + configuration=configuration, + title="Test", + ) + best_model_dir = os.path.join(configuration.training_dir, "best_model") + print(f"Saving best model: {best_model_dir}") + model.save_pretrained(best_model_dir) + processor.save_pretrained(best_model_dir) + save_metric_plots( + training_tracker=training_metrics_tracker, + validation_tracker=validation_metrics_tracker, + output_dir=configuration.training_dir, + ) + for split_name in ["valid", "test"]: + prepare_detection_training_summary( + processor=processor, + model=model, + dataset_location=configuration.dataset_location, + split_name=split_name, + training_dir=configuration.training_dir, + num_samples_to_visualise=configuration.num_samples_to_visualise, + device=configuration.device, + ) def load_model( diff --git a/requirements/requirements.txt b/requirements/requirements.txt index c10deb3..d640ad7 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -5,6 +5,6 @@ torch~=2.4.0 accelerate~=0.33.0 sentencepiece~=0.2.0 peft~=0.12.0 -flash-attn~=2.6.3 +flash-attn~=2.6.3 # does not work on mac einops~=0.8.0 timm~=1.0.9 \ No newline at end of file From 166f4ecb205aadcb7342f1b6a197f687a4e77c41 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 4 Sep 2024 16:31:40 +0200 Subject: [PATCH 31/69] small fix --- maestro/trainer/common/utils/metrics_tracing.py | 8 ++++---- maestro/trainer/models/florence_2/training.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/maestro/trainer/common/utils/metrics_tracing.py b/maestro/trainer/common/utils/metrics_tracing.py index a69a2ae..95ea552 100644 --- a/maestro/trainer/common/utils/metrics_tracing.py +++ b/maestro/trainer/common/utils/metrics_tracing.py @@ -62,8 +62,8 @@ def save_metric_plots( training_epochs = sorted(training_avg_values.keys()) training_vals = [training_avg_values[epoch] for epoch in training_epochs] plt.plot( - x=training_epochs, - y=training_vals, + training_epochs, + training_vals, label=f'Training {metric}', marker='o', linestyle='-', @@ -81,8 +81,8 @@ def save_metric_plots( in validation_epochs ] plt.plot( - x=validation_epochs, - y=validation_vals, + validation_epochs, + validation_vals, label=f'Validation {metric}', marker='o', linestyle='--', diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index d5351ef..e993736 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -307,7 +307,7 @@ def run_validation_epoch( val_loss = 0.0 epoch_marker = "" if epoch_number is not None: - epoch_marker = f"| Epoch {epoch_number + 1}/{configuration.training_epochs}" + epoch_marker = f"| Epoch {epoch_number}/{configuration.training_epochs}" with torch.no_grad(): for inputs, answers in tqdm(loader, desc=f"{title} {epoch_marker}"): input_ids = inputs["input_ids"] From 7ab38eb82147b0a89c9e7c0b45b91d340a383e18 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 4 Sep 2024 18:10:25 +0200 Subject: [PATCH 32/69] ready for test --- .../trainer/common/utils/metrics_tracing.py | 27 +++++++++++++++++++ maestro/trainer/models/florence_2/training.py | 9 ++++++- requirements/requirements.txt | 2 +- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/maestro/trainer/common/utils/metrics_tracing.py b/maestro/trainer/common/utils/metrics_tracing.py index 95ea552..d99bbbe 100644 --- a/maestro/trainer/common/utils/metrics_tracing.py +++ b/maestro/trainer/common/utils/metrics_tracing.py @@ -1,5 +1,7 @@ from __future__ import annotations +import json +import os from collections import defaultdict from typing import Dict, Tuple, List @@ -30,6 +32,28 @@ def get_metric_values( return self._metrics[metric] return [value[2] for value in self._metrics[metric]] + def as_json( + self, + output_dir: str = None, + filename: str = None + ) -> Dict[str, List[Dict[str, float]]]: + metrics_data = {} + for metric, values in self._metrics.items(): + metrics_data[metric] = [ + {'epoch': epoch, 'step': step, 'value': value} + for epoch, step, value + in values + ] + + if output_dir and filename: + if not os.path.exists(output_dir): + os.makedirs(output_dir) + filepath = os.path.join(output_dir, filename) + with open(filepath, 'w') as file: + json.dump(metrics_data, file, indent=4) + + return metrics_data + def aggregate_by_epoch(metric_values: List[Tuple[int, int, float]]) -> Dict[int, float]: epoch_data = defaultdict(list) @@ -48,6 +72,9 @@ def save_metric_plots( validation_tracker: MetricsTracker, output_dir: str ): + if not os.path.exists(output_dir): + os.makedirs(output_dir) + training_metrics = training_tracker.describe_metrics() validation_metrics = validation_tracker.describe_metrics() all_metrics = set(training_metrics + validation_metrics) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index e993736..aaa1687 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -125,8 +125,15 @@ def train(configuration: TrainingConfiguration) -> None: save_metric_plots( training_tracker=training_metrics_tracker, validation_tracker=validation_metrics_tracker, - output_dir=configuration.training_dir, + output_dir=os.path.join(configuration.training_dir, "metrics"), ) + training_metrics_tracker.as_json( + output_dir=os.path.join(configuration.training_dir, "metrics"), + filename="training.json") + validation_metrics_tracker.as_json( + output_dir=os.path.join(configuration.training_dir, "metrics"), + filename="validation.json") + for split_name in ["valid", "test"]: prepare_detection_training_summary( processor=processor, diff --git a/requirements/requirements.txt b/requirements/requirements.txt index d640ad7..a181a1a 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,4 +1,4 @@ -supervision~=0.22.0 +supervision~=0.24.0rc1 requests>=2.31.0,<=2.32.3 transformers~=4.44.2 torch~=2.4.0 From 8219f5f5e8cfb675b7ce32961a21109c67027def Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Thu, 5 Sep 2024 15:01:53 +0200 Subject: [PATCH 33/69] initial refactor --- .../utils/{metrics_tracing.py => metrics.py} | 35 +++++- maestro/trainer/models/florence_2/metrics.py | 26 ++++- maestro/trainer/models/florence_2/training.py | 102 ++++++++++-------- 3 files changed, 113 insertions(+), 50 deletions(-) rename maestro/trainer/common/utils/{metrics_tracing.py => metrics.py} (80%) diff --git a/maestro/trainer/common/utils/metrics_tracing.py b/maestro/trainer/common/utils/metrics.py similarity index 80% rename from maestro/trainer/common/utils/metrics_tracing.py rename to maestro/trainer/common/utils/metrics.py index d99bbbe..d5f4ae9 100644 --- a/maestro/trainer/common/utils/metrics_tracing.py +++ b/maestro/trainer/common/utils/metrics.py @@ -2,12 +2,45 @@ import json import os +from abc import ABC, abstractmethod from collections import defaultdict -from typing import Dict, Tuple, List +from typing import Dict, Tuple, List, Any import matplotlib.pyplot as plt +class BaseMetric(ABC): + """ + Abstract base class for custom metrics. Subclasses must implement + the 'describe' and 'compute' methods. + """ + + @abstractmethod + def describe(self) -> List[str]: + """ + Describe the names of the metrics that this class will compute. + + Returns: + List[str]: A list of metric names that will be computed. + """ + pass + + @abstractmethod + def compute(self, targets: List[Any], predictions: List[Any]) -> Dict[str, float]: + """ + Compute the metric based on the targets and predictions. + + Args: + targets (List[Any]): The ground truth. + predictions (List[Any]): The prediction result. + + Returns: + Dict[str, float]: A dictionary of computed metrics with metric names as + keys and their values. + """ + pass + + class MetricsTracker: @classmethod diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 3ed7aab..87f686b 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -1,23 +1,43 @@ import os import random import re -from typing import List, Tuple +from typing import List, Dict +from typing import Tuple import cv2 import numpy as np -import torch import supervision as sv +import torch +from supervision.metrics.mean_average_precision import MeanAveragePrecision from tqdm import tqdm from transformers import AutoProcessor, AutoModelForCausalLM from maestro.trainer.common.data_loaders.datasets import DetectionDataset from maestro.trainer.common.utils.file_system import save_json +from maestro.trainer.common.utils.metrics import BaseMetric from maestro.trainer.models.florence_2.data_loading import prepare_detection_dataset - DETECTION_CLASS_PATTERN = r"([a-zA-Z0-9 ]+ of [a-zA-Z0-9 ]+)" +class MeanAveragePrecisionMetric(BaseMetric): + + def describe(self) -> List[str]: + return ["map50:95", "map50", "map75"] + + def compute( + self, + targets: List[sv.Detections], + predictions: List[sv.Detections] + ) -> Dict[str, float]: + result = MeanAveragePrecision().update(targets, predictions).compute() + return { + "map50:95": result.map50_95, + "map50": result.map50, + "map75": result.map75 + } + + def prepare_detection_training_summary( processor: AutoProcessor, model: AutoModelForCausalLM, diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index aaa1687..3d27bb6 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -1,6 +1,6 @@ import os import shutil -from dataclasses import replace, dataclass +from dataclasses import replace, dataclass, field from glob import glob from typing import Optional, Tuple, List, Literal, Union @@ -15,17 +15,17 @@ from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, \ DEFAULT_CUDA_DEVICE from maestro.trainer.common.utils.leaderboard import CheckpointsLeaderboard -from maestro.trainer.common.utils.metrics_tracing import MetricsTracker, \ - save_metric_plots +from maestro.trainer.common.utils.metrics import MetricsTracker, \ + save_metric_plots, BaseMetric from maestro.trainer.common.utils.reproducibility import make_it_reproducible from maestro.trainer.models.florence_2.data_loading import prepare_data_loaders -from maestro.trainer.models.florence_2.metrics import prepare_detection_training_summary from maestro.trainer.models.paligemma.training import LoraInitLiteral - DEFAULT_FLORENCE2_MODEL_ID = "microsoft/Florence-2-base-ft" DEFAULT_FLORENCE2_MODEL_REVISION = "refs/pr/20" -DEVICE = torch.device("cpu") if not torch.cuda.is_available() else os.getenv(CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE) +DEVICE = torch.device("cpu") \ + if not torch.cuda.is_available() \ + else os.getenv(CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE) @dataclass(frozen=True) @@ -52,6 +52,7 @@ class TrainingConfiguration: training_dir: str = "./training/florence-2" max_checkpoints_to_keep: int = 3 num_samples_to_visualise: int = 64 + metrics: List[BaseMetric] = field(default_factory=list) def train(configuration: TrainingConfiguration) -> None: @@ -80,8 +81,8 @@ def train(configuration: TrainingConfiguration) -> None: num_workers=configuration.loaders_workers, test_loaders_workers=configuration.test_loaders_workers, ) - if test_loader is None: - test_loader = val_loader + # if test_loader is None: + # test_loader = val_loader peft_model = prepare_peft_model( model=model, r=configuration.lora_r, @@ -93,7 +94,11 @@ def train(configuration: TrainingConfiguration) -> None: revision=configuration.revision, ) training_metrics_tracker = MetricsTracker.init(metrics=["loss"]) - validation_metrics_tracker = MetricsTracker.init(metrics=["loss"]) + metrics = ["loss"] + for metric in configuration.metrics: + metrics += metric.describe() + validation_metrics_tracker = MetricsTracker.init(metrics=metrics) + run_training_loop( processor=processor, model=peft_model, @@ -109,15 +114,15 @@ def train(configuration: TrainingConfiguration) -> None: processor, model = load_model( model_id_or_path=best_model_path, ) - if test_loader is not None: - run_validation_epoch( - processor=processor, - model=model, - loader=test_loader, - epoch_number=None, - configuration=configuration, - title="Test", - ) + # if test_loader is not None: + # run_validation_epoch( + # processor=processor, + # model=model, + # loader=test_loader, + # epoch_number=None, + # configuration=configuration, + # title="Test", + # ) best_model_dir = os.path.join(configuration.training_dir, "best_model") print(f"Saving best model: {best_model_dir}") model.save_pretrained(best_model_dir) @@ -134,16 +139,16 @@ def train(configuration: TrainingConfiguration) -> None: output_dir=os.path.join(configuration.training_dir, "metrics"), filename="validation.json") - for split_name in ["valid", "test"]: - prepare_detection_training_summary( - processor=processor, - model=model, - dataset_location=configuration.dataset_location, - split_name=split_name, - training_dir=configuration.training_dir, - num_samples_to_visualise=configuration.num_samples_to_visualise, - device=configuration.device, - ) + # for split_name in ["valid", "test"]: + # prepare_detection_training_summary( + # processor=processor, + # model=model, + # dataset_location=configuration.dataset_location, + # split_name=split_name, + # training_dir=configuration.training_dir, + # num_samples_to_visualise=configuration.num_samples_to_visualise, + # device=configuration.device, + # ) def load_model( @@ -274,19 +279,16 @@ def run_training_epoch( print(f"Average Training Loss: {avg_train_loss}") if val_loader is None or len(val_loader) == 0: return None - validation_loss = run_validation_epoch( + + run_validation_epoch( processor=processor, model=model, loader=val_loader, epoch_number=epoch_number, configuration=configuration, + metrics_tracker=validation_metrics_tracker, ) - validation_metrics_tracker.register( - metric="loss", - epoch=epoch_number, - step=1, - value=validation_loss, - ) + validation_loss = validation_metrics_tracker.get_metric_values("loss")[-1][2] checkpoint_dir = os.path.join(configuration.training_dir, "checkpoints", str(epoch_number)) should_save, to_remove = checkpoints_leaderboard.register_checkpoint( epoch=epoch_number, @@ -307,27 +309,35 @@ def run_validation_epoch( processor: AutoProcessor, model: Union[PeftModel, AutoModelForCausalLM], loader: DataLoader, - epoch_number: Optional[int], configuration: TrainingConfiguration, - title: str = "Validation", -) -> float: + metrics_tracker: MetricsTracker, + epoch_number: int +) -> None: val_loss = 0.0 - epoch_marker = "" - if epoch_number is not None: - epoch_marker = f"| Epoch {epoch_number}/{configuration.training_epochs}" with torch.no_grad(): - for inputs, answers in tqdm(loader, desc=f"{title} {epoch_marker}"): + for inputs, targets in loader: input_ids = inputs["input_ids"] pixel_values = inputs["pixel_values"] labels = processor.tokenizer( - text=answers, return_tensors="pt", padding=True, return_token_type_ids=False + text=targets, + return_tensors="pt", + padding=True, + return_token_type_ids=False ).input_ids.to(configuration.device) - outputs = model(input_ids=input_ids, pixel_values=pixel_values, labels=labels) + outputs = model( + input_ids=input_ids, + pixel_values=pixel_values, + labels=labels + ) loss = outputs.loss val_loss += loss.item() avg_val_loss = val_loss / len(loader) - print(f"Average {title} Loss: {avg_val_loss}") - return avg_val_loss + metrics_tracker.register( + metric="loss", + epoch=epoch_number, + step=1, + value=avg_val_loss, + ) def save_model( From 1b4a22496e83a97341fb8f2656ab4ef07d1a5d95 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Thu, 5 Sep 2024 15:06:58 +0200 Subject: [PATCH 34/69] small fix --- maestro/trainer/models/paligemma/training.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maestro/trainer/models/paligemma/training.py b/maestro/trainer/models/paligemma/training.py index f0e9737..9de0f0c 100644 --- a/maestro/trainer/models/paligemma/training.py +++ b/maestro/trainer/models/paligemma/training.py @@ -16,7 +16,7 @@ HF_TOKEN_ENV, ) from maestro.trainer.common.data_loaders.jsonl import JSONLDataset -from maestro.trainer.common.utils.metrics_tracing import MetricsTracker +from maestro.trainer.common.utils.metrics import MetricsTracker from maestro.trainer.common.utils.reproducibility import make_it_reproducible DEFAULT_PALIGEMMA_MODEL_ID = "google/paligemma-3b-pt-224" From ee1a6fabef26bb07465bf38abebcc211b974bee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20P=C4=99czek?= Date: Thu, 5 Sep 2024 22:24:18 +0200 Subject: [PATCH 35/69] Add first scratch of implementation for maestro CLI --- maestro/cli/__init__.py | 0 maestro/cli/env.py | 2 + maestro/cli/introspection.py | 36 +++++ maestro/cli/main.py | 15 ++ maestro/cli/utils.py | 2 + .../trainer/common/utils/metrics_tracing.py | 61 +++---- maestro/trainer/models/florence_2/entities.py | 41 +++++ .../trainer/models/florence_2/entrypoint.py | 152 ++++++++++++++++++ maestro/trainer/models/florence_2/training.py | 52 ++---- .../trainer/models/paligemma/entrypoint.py | 13 ++ requirements/requirements.txt | 3 +- setup.py | 5 + 12 files changed, 298 insertions(+), 84 deletions(-) create mode 100644 maestro/cli/__init__.py create mode 100644 maestro/cli/env.py create mode 100644 maestro/cli/introspection.py create mode 100644 maestro/cli/main.py create mode 100644 maestro/cli/utils.py create mode 100644 maestro/trainer/models/florence_2/entrypoint.py create mode 100644 maestro/trainer/models/paligemma/entrypoint.py diff --git a/maestro/cli/__init__.py b/maestro/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/maestro/cli/env.py b/maestro/cli/env.py new file mode 100644 index 0000000..b95525e --- /dev/null +++ b/maestro/cli/env.py @@ -0,0 +1,2 @@ +DISABLE_RECIPE_IMPORTS_WARNINGS_ENV = "DISABLE_RECIPE_IMPORTS_WARNINGS" +DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV = "False" diff --git a/maestro/cli/introspection.py b/maestro/cli/introspection.py new file mode 100644 index 0000000..3a62ce3 --- /dev/null +++ b/maestro/cli/introspection.py @@ -0,0 +1,36 @@ +import os + +import typer + +from maestro.cli.env import DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV +from maestro.cli.utils import str2bool + + +def find_training_recipes(app: typer.Typer) -> None: + try: + from maestro.trainer.models.florence_2.entrypoint import florence_2_app + + app.add_typer(florence_2_app, name="florence2") + except Exception: + _warn_about_recipe_import_error(model_name="Florence 2") + + try: + from maestro.trainer.models.paligemma.entrypoint import paligemma_app + + app.add_typer(paligemma_app, name="paligemma") + except Exception: + _warn_about_recipe_import_error(model_name="PaliGemma") + + +def _warn_about_recipe_import_error(model_name: str) -> None: + disable_warnings = str2bool( + os.getenv( + DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, + DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, + ) + ) + if disable_warnings: + return None + warning = typer.style("WARNING", fg=typer.colors.RED, bold=True) + message = "🚧 " + warning + f" cannot import recipe for {model_name}" + typer.echo(message) diff --git a/maestro/cli/main.py b/maestro/cli/main.py new file mode 100644 index 0000000..b600e3a --- /dev/null +++ b/maestro/cli/main.py @@ -0,0 +1,15 @@ +import typer + +from maestro.cli.introspection import find_training_recipes + +app = typer.Typer() +find_training_recipes(app=app) + + +@app.command(help="Display information about maestro") +def info(): + typer.echo("Welcome to maestro CLI. Let's train some VLM! 🏋") + + +if __name__ == "__main__": + app() diff --git a/maestro/cli/utils.py b/maestro/cli/utils.py new file mode 100644 index 0000000..0751fef --- /dev/null +++ b/maestro/cli/utils.py @@ -0,0 +1,2 @@ +def str2bool(value: str) -> bool: + return value.lower() in {"y", "t", "yes", "true"} diff --git a/maestro/trainer/common/utils/metrics_tracing.py b/maestro/trainer/common/utils/metrics_tracing.py index d99bbbe..c7b011e 100644 --- a/maestro/trainer/common/utils/metrics_tracing.py +++ b/maestro/trainer/common/utils/metrics_tracing.py @@ -3,7 +3,7 @@ import json import os from collections import defaultdict -from typing import Dict, Tuple, List +from typing import Dict, Tuple, List, Optional import matplotlib.pyplot as plt @@ -33,23 +33,17 @@ def get_metric_values( return [value[2] for value in self._metrics[metric]] def as_json( - self, - output_dir: str = None, - filename: str = None + self, output_dir: Optional[str] = None, filename: Optional[str] = None ) -> Dict[str, List[Dict[str, float]]]: metrics_data = {} for metric, values in self._metrics.items(): - metrics_data[metric] = [ - {'epoch': epoch, 'step': step, 'value': value} - for epoch, step, value - in values - ] + metrics_data[metric] = [{"epoch": epoch, "step": step, "value": value} for epoch, step, value in values] if output_dir and filename: if not os.path.exists(output_dir): os.makedirs(output_dir) filepath = os.path.join(output_dir, filename) - with open(filepath, 'w') as file: + with open(filepath, "w") as file: json.dump(metrics_data, file, indent=4) return metrics_data @@ -59,19 +53,11 @@ def aggregate_by_epoch(metric_values: List[Tuple[int, int, float]]) -> Dict[int, epoch_data = defaultdict(list) for epoch, step, value in metric_values: epoch_data[epoch].append(value) - avg_per_epoch = { - epoch: sum(values) / len(values) - for epoch, values - in epoch_data.items() - } + avg_per_epoch = {epoch: sum(values) / len(values) for epoch, values in epoch_data.items()} return avg_per_epoch -def save_metric_plots( - training_tracker: MetricsTracker, - validation_tracker: MetricsTracker, - output_dir: str -): +def save_metric_plots(training_tracker: MetricsTracker, validation_tracker: MetricsTracker, output_dir: str): if not os.path.exists(output_dir): os.makedirs(output_dir) @@ -83,43 +69,32 @@ def save_metric_plots( plt.figure(figsize=(8, 6)) if metric in training_metrics: - training_values = training_tracker.get_metric_values( - metric=metric, with_index=True) + training_values = training_tracker.get_metric_values(metric=metric, with_index=True) training_avg_values = aggregate_by_epoch(training_values) training_epochs = sorted(training_avg_values.keys()) training_vals = [training_avg_values[epoch] for epoch in training_epochs] plt.plot( - training_epochs, - training_vals, - label=f'Training {metric}', - marker='o', - linestyle='-', - color='blue' + training_epochs, training_vals, label=f"Training {metric}", marker="o", linestyle="-", color="blue" ) if metric in validation_metrics: - validation_values = validation_tracker.get_metric_values( - metric=metric, with_index=True) + validation_values = validation_tracker.get_metric_values(metric=metric, with_index=True) validation_avg_values = aggregate_by_epoch(validation_values) validation_epochs = sorted(validation_avg_values.keys()) - validation_vals = [ - validation_avg_values[epoch] - for epoch - in validation_epochs - ] + validation_vals = [validation_avg_values[epoch] for epoch in validation_epochs] plt.plot( validation_epochs, validation_vals, - label=f'Validation {metric}', - marker='o', - linestyle='--', - color='orange' + label=f"Validation {metric}", + marker="o", + linestyle="--", + color="orange", ) - plt.title(f'{metric.capitalize()} over Epochs') - plt.xlabel('Epoch') - plt.ylabel(f'{metric.capitalize()} Value') + plt.title(f"{metric.capitalize()} over Epochs") + plt.xlabel("Epoch") + plt.ylabel(f"{metric.capitalize()} Value") plt.legend() plt.grid(True) - plt.savefig(f'{output_dir}/{metric}_plot.png') + plt.savefig(f"{output_dir}/{metric}_plot.png") plt.close() diff --git a/maestro/trainer/models/florence_2/entities.py b/maestro/trainer/models/florence_2/entities.py index e69de29..17f4019 100644 --- a/maestro/trainer/models/florence_2/entities.py +++ b/maestro/trainer/models/florence_2/entities.py @@ -0,0 +1,41 @@ +import os +from dataclasses import dataclass +from typing import Optional, Literal, Union + +import torch + +from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE + + +LoraInitLiteral = Literal["gaussian", "olora", "pissa", "pissa_niter_[number of iters]", "loftq"] + + +DEFAULT_FLORENCE2_MODEL_ID = "microsoft/Florence-2-base-ft" +DEFAULT_FLORENCE2_MODEL_REVISION = "refs/pr/20" +DEVICE = os.getenv(CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE) + + +@dataclass(frozen=True) +class TrainingConfiguration: + dataset_location: str + model_id_or_path: str = DEFAULT_FLORENCE2_MODEL_ID + revision: str = DEFAULT_FLORENCE2_MODEL_REVISION + device: torch.device = torch.device(DEVICE) + transformers_cache_dir: Optional[str] = None + training_epochs: int = 10 + optimiser: Literal["SGD", "adamw", "adam"] = "adamw" + learning_rate: float = 1e-5 + lr_scheduler: Literal["linear", "cosine", "polynomial"] = "linear" + train_batch_size: int = 4 + test_batch_size: Optional[int] = None + loaders_workers: int = 0 + test_loaders_workers: Optional[int] = None + lora_r: int = 8 + lora_alpha: int = 8 + lora_dropout: float = 0.05 + bias: Literal["none", "all", "lora_only"] = "none" + use_rslora: bool = True + init_lora_weights: Union[bool, LoraInitLiteral] = "gaussian" + training_dir: str = "./training/florence-2" + max_checkpoints_to_keep: int = 3 + num_samples_to_visualise: int = 64 diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py new file mode 100644 index 0000000..09c2d87 --- /dev/null +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -0,0 +1,152 @@ +import dataclasses +from typing import Optional, Annotated + +import torch +import typer +import rich + +from maestro.trainer.models.florence_2.entities import ( + TrainingConfiguration, + DEFAULT_FLORENCE2_MODEL_ID, + DEFAULT_FLORENCE2_MODEL_REVISION, + DEVICE, +) + +from maestro.trainer.models.florence_2.training import train as train_fun + +florence_2_app = typer.Typer(help="Fine-tune and evaluate Florence 2 model") + + +@florence_2_app.command( + help="Train Florence 2 model", context_settings={"allow_extra_args": True, "ignore_unknown_options": True} +) +def train( + dataset_location: Annotated[ + str, + typer.Option("--dataset_location", help="Path to directory with dataset"), + ], + model_id_or_path: Annotated[ + str, + typer.Option("--model_id_or_path", help="Model to be used or path to your checkpoint"), + ] = DEFAULT_FLORENCE2_MODEL_ID, + revision: Annotated[ + str, + typer.Option("--revision", help="Revision of Florence2 HF repository"), + ] = DEFAULT_FLORENCE2_MODEL_REVISION, + device: Annotated[ + str, + typer.Option("--device", help="CUDA device ID to be used (in format: 'cuda:0')"), + ] = DEVICE, + transformers_cache_dir: Annotated[ + Optional[str], + typer.Option("--transformers_cache_dir", help="Cache dir for HF weights"), + ] = None, + training_epochs: Annotated[ + int, + typer.Option("--training_epochs", help="Number of training epochs"), + ] = 10, + optimiser: Annotated[ + str, + typer.Option("--optimiser", help="Optimiser to be used"), + ] = "adamw", + learning_rate: Annotated[ + float, + typer.Option("--learning_rate", help="Learning rate"), + ] = 1e-5, + lr_scheduler: Annotated[ + str, + typer.Option("--lr_scheduler", help="LR scheduler"), + ] = "linear", + train_batch_size: Annotated[ + int, + typer.Option("--train_batch_size", help="Batch size for training"), + ] = 4, + test_batch_size: Annotated[ + Optional[int], + typer.Option( + "--train_batch_size", help="Batch size for validation and test. If not given - train will be used." + ), + ] = None, + loaders_workers: Annotated[ + int, + typer.Option("--loaders_workers", help="Number of loaders workers. 0 = # of CPU"), + ] = 0, + test_loaders_workers: Annotated[ + Optional[int], + typer.Option( + "--test_loaders_workers", + help="Number of workers for test and val loaders. If not given - train will be used.", + ), + ] = None, + lora_r: Annotated[ + int, + typer.Option("--lora_r", help="Value of Lora R"), + ] = 8, + lora_alpha: Annotated[ + int, + typer.Option("--lora_alpha", help="Value of Lora Alpha"), + ] = 8, + lora_dropout: Annotated[ + float, + typer.Option("--lora_dropout", help="Value of Lora Dropout"), + ] = 0.05, + bias: Annotated[ + str, + typer.Option("--bias", help="Value of Lora Bias"), + ] = "none", + use_rslora: Annotated[ + bool, + typer.Option( + "--use_rslora/--no_use_rslora", + help="Boolean flag to decide if rslora to be used", + ), + ] = True, + init_lora_weights: Annotated[ + str, + typer.Option("--init_lora_weights", help="Lora weights initialisation"), + ] = "gaussian", + training_dir: Annotated[ + str, + typer.Option("--training_dir", help="Path to directory where training outputs should be preserved"), + ] = "./training/florence-2", + max_checkpoints_to_keep: Annotated[ + int, + typer.Option("--max_checkpoints_to_keep", help="Max checkpoints to keep"), + ] = 3, + num_samples_to_visualise: Annotated[ + int, + typer.Option("--num_samples_to_visualise", help="Number of samples to visualise"), + ] = 64, +) -> None: + configuration = TrainingConfiguration( + dataset_location=dataset_location, + model_id_or_path=model_id_or_path, + revision=revision, + device=torch.device(device), + transformers_cache_dir=transformers_cache_dir, + training_epochs=training_epochs, + optimiser=optimiser, # type: ignore + learning_rate=learning_rate, + lr_scheduler=lr_scheduler, # type: ignore + train_batch_size=train_batch_size, + test_batch_size=test_batch_size, + loaders_workers=loaders_workers, + test_loaders_workers=test_loaders_workers, + lora_r=lora_r, + lora_alpha=lora_alpha, + lora_dropout=lora_dropout, + bias=bias, # type: ignore + use_rslora=use_rslora, + init_lora_weights=init_lora_weights, # type: ignore + training_dir=training_dir, + max_checkpoints_to_keep=max_checkpoints_to_keep, + num_samples_to_visualise=num_samples_to_visualise, + ) + typer.echo(typer.style("Training configuration", fg=typer.colors.BRIGHT_GREEN, bold=True)) + rich.print(dataclasses.asdict(configuration)) + train_fun(configuration=configuration) + + +@florence_2_app.command(help="Evaluate Florence 2 model") +def evaluate() -> None: + pass diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index aaa1687..7e7ae56 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -1,6 +1,6 @@ import os import shutil -from dataclasses import replace, dataclass +from dataclasses import replace from glob import glob from typing import Optional, Tuple, List, Literal, Union @@ -12,48 +12,20 @@ from tqdm import tqdm from transformers import AutoModelForCausalLM, AutoProcessor, get_scheduler -from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, \ - DEFAULT_CUDA_DEVICE from maestro.trainer.common.utils.leaderboard import CheckpointsLeaderboard -from maestro.trainer.common.utils.metrics_tracing import MetricsTracker, \ - save_metric_plots +from maestro.trainer.common.utils.metrics_tracing import MetricsTracker, save_metric_plots from maestro.trainer.common.utils.reproducibility import make_it_reproducible from maestro.trainer.models.florence_2.data_loading import prepare_data_loaders +from maestro.trainer.models.florence_2.entities import ( + DEFAULT_FLORENCE2_MODEL_ID, + DEFAULT_FLORENCE2_MODEL_REVISION, + DEVICE, + TrainingConfiguration, +) from maestro.trainer.models.florence_2.metrics import prepare_detection_training_summary from maestro.trainer.models.paligemma.training import LoraInitLiteral -DEFAULT_FLORENCE2_MODEL_ID = "microsoft/Florence-2-base-ft" -DEFAULT_FLORENCE2_MODEL_REVISION = "refs/pr/20" -DEVICE = torch.device("cpu") if not torch.cuda.is_available() else os.getenv(CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE) - - -@dataclass(frozen=True) -class TrainingConfiguration: - dataset_location: str - model_id_or_path: str = DEFAULT_FLORENCE2_MODEL_ID - revision: str = DEFAULT_FLORENCE2_MODEL_REVISION - device: torch.device = DEVICE - transformers_cache_dir: Optional[str] = None - training_epochs: int = 10 - optimiser: Literal["SGD", "adamw", "adam"] = "adamw" - learning_rate: float = 1e-5 - lr_scheduler: Literal["linear", "cosine", "polynomial"] = "linear" - train_batch_size: int = 4 - test_batch_size: Optional[int] = None - loaders_workers: int = 0 - test_loaders_workers: Optional[int] = None - lora_r: int = 8 - lora_alpha: int = 8 - lora_dropout: float = 0.05 - bias: Literal["none", "all", "lora_only"] = "none" - use_rslora: bool = True - init_lora_weights: Union[bool, LoraInitLiteral] = "gaussian" - training_dir: str = "./training/florence-2" - max_checkpoints_to_keep: int = 3 - num_samples_to_visualise: int = 64 - - def train(configuration: TrainingConfiguration) -> None: make_it_reproducible(avoid_non_deterministic_algorithms=False) training_run_dir = _establish_training_run_dir( @@ -128,11 +100,11 @@ def train(configuration: TrainingConfiguration) -> None: output_dir=os.path.join(configuration.training_dir, "metrics"), ) training_metrics_tracker.as_json( - output_dir=os.path.join(configuration.training_dir, "metrics"), - filename="training.json") + output_dir=os.path.join(configuration.training_dir, "metrics"), filename="training.json" + ) validation_metrics_tracker.as_json( - output_dir=os.path.join(configuration.training_dir, "metrics"), - filename="validation.json") + output_dir=os.path.join(configuration.training_dir, "metrics"), filename="validation.json" + ) for split_name in ["valid", "test"]: prepare_detection_training_summary( diff --git a/maestro/trainer/models/paligemma/entrypoint.py b/maestro/trainer/models/paligemma/entrypoint.py new file mode 100644 index 0000000..6ef6dc6 --- /dev/null +++ b/maestro/trainer/models/paligemma/entrypoint.py @@ -0,0 +1,13 @@ +import typer + +paligemma_app = typer.Typer(help="Fine-tune and evaluate PaliGemma model") + + +@paligemma_app.command(help="Train PaliGemma model") +def train() -> None: + typer.echo("🚧 Just a placeholder - to be implemented 🚧") + + +@paligemma_app.command(help="Evaluate PaliGemma model") +def evaluate() -> None: + typer.echo("🚧 Just a placeholder - to be implemented 🚧") diff --git a/requirements/requirements.txt b/requirements/requirements.txt index a181a1a..e06e303 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -7,4 +7,5 @@ sentencepiece~=0.2.0 peft~=0.12.0 flash-attn~=2.6.3 # does not work on mac einops~=0.8.0 -timm~=1.0.9 \ No newline at end of file +timm~=1.0.9 +typer~=0.12.5 diff --git a/setup.py b/setup.py index 9e16b7b..293f4e9 100644 --- a/setup.py +++ b/setup.py @@ -42,6 +42,11 @@ def read_requirements(path: Union[str, List[str]]) -> List[str]: "dev": read_requirements("requirements/requirements.test.txt"), "docs": read_requirements("requirements/requirements.docs.txt"), }, + entry_points={ + "console_scripts": [ + "maestro=maestro.cli.main:app", + ], + }, classifiers=[ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", From 9e52912cd9e569b94238d86109a9b5c22dff4b42 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 14:15:16 +0200 Subject: [PATCH 36/69] up --- maestro/trainer/models/florence_2/training.py | 1 + 1 file changed, 1 insertion(+) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 3d27bb6..cf3c123 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -338,6 +338,7 @@ def run_validation_epoch( step=1, value=avg_val_loss, ) + print(f"Average Validation Loss: {avg_val_loss}") def save_model( From 4c3fbd08e25c649a8df8f870ebfad884e8d518cf Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 15:33:26 +0200 Subject: [PATCH 37/69] test mAP metic --- maestro/trainer/models/florence_2/metrics.py | 3 +-- maestro/trainer/models/florence_2/training.py | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 87f686b..76de43e 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -92,14 +92,13 @@ def get_ground_truths_and_predictions( dataset: DetectionDataset, processor: AutoProcessor, model: AutoModelForCausalLM, - split_name: str, device: torch.device, ) -> Tuple[List[sv.Detections], List[sv.Detections], List[str]]: classes = extract_classes(dataset=dataset) targets = [] predictions = [] post_processed_text_outputs = [] - for idx in tqdm(list(range(len(dataset))), desc=f"Generating {split_name} predictions..."): + for idx in tqdm(list(range(len(dataset))), desc="Generating predictions..."): image, data = dataset.dataset[idx] prefix = data["prefix"] suffix = data["suffix"] diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index cf3c123..a79977d 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -19,6 +19,7 @@ save_metric_plots, BaseMetric from maestro.trainer.common.utils.reproducibility import make_it_reproducible from maestro.trainer.models.florence_2.data_loading import prepare_data_loaders +from maestro.trainer.models.florence_2.metrics import MeanAveragePrecisionMetric, get_ground_truths_and_predictions from maestro.trainer.models.paligemma.training import LoraInitLiteral DEFAULT_FLORENCE2_MODEL_ID = "microsoft/Florence-2-base-ft" @@ -340,6 +341,29 @@ def run_validation_epoch( ) print(f"Average Validation Loss: {avg_val_loss}") + # TODO: standardize the calculation of metrics input to run inference only once + + for metric in configuration.metrics: + if isinstance(metric, MeanAveragePrecisionMetric): + targets, predictions, _ = get_ground_truths_and_predictions( + dataset=loader.dataset, + processor=processor, + model=model, + device=configuration.device, + ) + map_result = metric.compute(targets=targets, predictions=predictions) + for map_key, map_value in map_result.items(): + metrics_tracker.register( + metric=map_key, + epoch=epoch_number, + step=1, + value=map_value, + ) + print(f"Validation {map_key}: {map_value:.4f}") + else: + # Handle other metric types + pass + def save_model( target_dir: str, From ad6e6c9c1edaf23e0a585a3203bee63956b31963 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 17:34:13 +0200 Subject: [PATCH 38/69] mAP refactor --- maestro/trainer/models/florence_2/metrics.py | 219 +++++++++++------- maestro/trainer/models/florence_2/training.py | 62 +++-- 2 files changed, 177 insertions(+), 104 deletions(-) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 76de43e..563734b 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -11,6 +11,7 @@ from supervision.metrics.mean_average_precision import MeanAveragePrecision from tqdm import tqdm from transformers import AutoProcessor, AutoModelForCausalLM +from PIL import Image from maestro.trainer.common.data_loaders.datasets import DetectionDataset from maestro.trainer.common.utils.file_system import save_json @@ -38,66 +39,122 @@ def compute( } -def prepare_detection_training_summary( - processor: AutoProcessor, - model: AutoModelForCausalLM, - dataset_location: str, - split_name: str, - training_dir: str, - num_samples_to_visualise: int, - device: torch.device, -) -> None: - dataset = prepare_detection_dataset( - dataset_location=dataset_location, - split_name=split_name, - ) - if dataset is None: - return None - targets, predictions, post_processed_text_outputs = get_ground_truths_and_predictions( - dataset=dataset, - processor=processor, - model=model, - split_name=split_name, - device=device, - ) - mean_average_precision = sv.MeanAveragePrecision.from_detections( - predictions=predictions, - targets=targets, - ) - print(f"{split_name} | map50_95: {mean_average_precision.map50_95:.2f}") - print(f"{split_name} | map50: {mean_average_precision.map50:.2f}") - print(f"{split_name} | map75: {mean_average_precision.map75:.2f}") - dump_metrics( - training_dir=training_dir, - split_name=split_name, - metrics=mean_average_precision, - ) - dump_post_processed_outputs( - dataset=dataset, - post_processed_text_outputs=post_processed_text_outputs, - training_dir=training_dir, - split_name=split_name, - ) - dump_visualised_samples( - dataset=dataset, - targets=targets, - predictions=predictions, - num_samples_to_visualise=num_samples_to_visualise, - training_dir=training_dir, - split_name=split_name, - ) +# def prepare_detection_training_summary( +# processor: AutoProcessor, +# model: AutoModelForCausalLM, +# dataset_location: str, +# split_name: str, +# training_dir: str, +# num_samples_to_visualise: int, +# device: torch.device, +# ) -> None: +# dataset = prepare_detection_dataset( +# dataset_location=dataset_location, +# split_name=split_name, +# ) +# if dataset is None: +# return None +# targets, predictions, post_processed_text_outputs = get_ground_truths_and_predictions( +# dataset=dataset, +# processor=processor, +# model=model, +# split_name=split_name, +# device=device, +# ) +# mean_average_precision = sv.MeanAveragePrecision.from_detections( +# predictions=predictions, +# targets=targets, +# ) +# print(f"{split_name} | map50_95: {mean_average_precision.map50_95:.2f}") +# print(f"{split_name} | map50: {mean_average_precision.map50:.2f}") +# print(f"{split_name} | map75: {mean_average_precision.map75:.2f}") +# dump_metrics( +# training_dir=training_dir, +# split_name=split_name, +# metrics=mean_average_precision, +# ) +# dump_post_processed_outputs( +# dataset=dataset, +# post_processed_text_outputs=post_processed_text_outputs, +# training_dir=training_dir, +# split_name=split_name, +# ) +# dump_visualised_samples( +# dataset=dataset, +# targets=targets, +# predictions=predictions, +# num_samples_to_visualise=num_samples_to_visualise, +# training_dir=training_dir, +# split_name=split_name, +# ) + + +def postprocess_florence2_output_for_mean_average_precision( + expected_responses: List[str], + generated_texts: List[str], + images: List[Image.Image], + classes: List[str], + processor: AutoProcessor +) -> Tuple[List[sv.Detections], List[sv.Detections]]: + """ + Postprocess Florence-2 model output for mean average precision calculation. + + Args: + expected_responses (List[str]): List of expected responses (ground truth). + generated_texts (List[str]): List of generated texts from the model. + images (List[Image.Image]): List of input images. + classes (List[str]): List of unique class names. + processor (AutoProcessor): The processor used for text generation. + Returns: + Tuple[List[sv.Detections], List[sv.Detections]]: A tuple containing two lists of Detections objects, + representing the targets (ground truth) and predictions respectively. + """ + targets = [] + predictions = [] + + for image, suffix, generated_text in zip(images, expected_responses, generated_texts): + # Postprocess prediction for mean average precision calculation + prediction = processor.post_process_generation(generated_text, task="", image_size=image.size) + prediction = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, prediction, resolution_wh=image.size) + prediction = prediction[np.isin(prediction["class_name"], classes)] + prediction.class_id = np.array([classes.index(class_name) for class_name in prediction["class_name"]]) + prediction.confidence = np.ones(len(prediction)) # Set confidence for mean average precision calculation + + # Postprocess target for mean average precision calculation + target = processor.post_process_generation(suffix, task="", image_size=image.size) + target = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, target, resolution_wh=image.size) + target.class_id = np.array([classes.index(class_name) for class_name in target["class_name"]]) + + targets.append(target) + predictions.append(prediction) + + return targets, predictions -def get_ground_truths_and_predictions( +def run_predictions( dataset: DetectionDataset, processor: AutoProcessor, model: AutoModelForCausalLM, device: torch.device, -) -> Tuple[List[sv.Detections], List[sv.Detections], List[str]]: - classes = extract_classes(dataset=dataset) - targets = [] - predictions = [] - post_processed_text_outputs = [] +) -> Tuple[List[str], List[str], List[str], List[Image.Image]]: + """ + Run predictions on the given dataset using the provided model and processor. + + Args: + dataset (DetectionDataset): The dataset to run predictions on. + processor (AutoProcessor): The processor used for text generation. + model (AutoModelForCausalLM): The model used for text generation. + device (torch.device): The device to run the model on. + + Returns: + Tuple[List[str], List[str], List[str], List[Image.Image]]: A tuple containing lists of prompts, + expected responses, generated texts, and input images. + """ + prompts = [] + expected_responses = [] + generated_texts = [] + images = [] + for idx in tqdm(list(range(len(dataset))), desc="Generating predictions..."): image, data = dataset.dataset[idx] prefix = data["prefix"] @@ -108,21 +165,25 @@ def get_ground_truths_and_predictions( input_ids=inputs["input_ids"], pixel_values=inputs["pixel_values"], max_new_tokens=1024, num_beams=3 ) generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0] - prediction = processor.post_process_generation(generated_text, task="", image_size=image.size) - post_processed_text_outputs.append(prediction) - prediction = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, prediction, resolution_wh=image.size) - prediction = prediction[np.isin(prediction["class_name"], classes)] - prediction.class_id = np.array([class_name.index(class_name) for class_name in prediction["class_name"]]) - prediction.confidence = np.ones(len(prediction)) - target = processor.post_process_generation(suffix, task="", image_size=image.size) - target = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, target, resolution_wh=image.size) - target.class_id = np.array([class_name.index(class_name) for class_name in target["class_name"]]) - targets.append(target) - predictions.append(prediction) - return targets, predictions, post_processed_text_outputs + + prompts.append(prefix) + expected_responses.append(suffix) + generated_texts.append(generated_text) + images.append(image) + + return prompts, expected_responses, generated_texts, images + +def extract_unique_detection_dataset_classes(dataset: DetectionDataset) -> List[str]: + """ + Extract unique class names from the detection dataset. -def extract_classes(dataset: DetectionDataset) -> List[str]: + Args: + dataset (DetectionDataset): The dataset to extract class names from. + + Returns: + List[str]: A sorted list of unique class names found in the dataset. + """ class_set = set() for i in range(len(dataset.dataset)): image, data = dataset.dataset[i] @@ -132,18 +193,18 @@ def extract_classes(dataset: DetectionDataset) -> List[str]: return sorted(class_set) -def dump_metrics( - training_dir: str, - split_name: str, - metrics: sv.MeanAveragePrecision, -) -> None: - target_path = os.path.join(training_dir, "metrics", split_name, f"metrics_{split_name}.json") - content = { - "map50_95": metrics.map50_95, - "map50": metrics.map50, - "map75": metrics.map75, - } - save_json(path=target_path, content=content) +# def dump_metrics( +# training_dir: str, +# split_name: str, +# metrics: sv.MeanAveragePrecision, +# ) -> None: +# target_path = os.path.join(training_dir, "metrics", split_name, f"metrics_{split_name}.json") +# content = { +# "map50_95": metrics.map50_95, +# "map50": metrics.map50, +# "map75": metrics.map75, +# } +# save_json(path=target_path, content=content) def dump_post_processed_outputs( diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index a79977d..4c3ce27 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -1,25 +1,29 @@ import os import shutil -from dataclasses import replace, dataclass, field +from dataclasses import dataclass, field, replace from glob import glob -from typing import Optional, Tuple, List, Literal, Union +from typing import List, Literal, Optional, Tuple, Union import torch -from peft import PeftModel, LoraConfig, get_peft_model -from torch.optim import Optimizer, AdamW, Adam, SGD +from peft import LoraConfig, PeftModel, get_peft_model +from torch.optim import Adam, AdamW, Optimizer, SGD from torch.optim.lr_scheduler import LRScheduler from torch.utils.data import DataLoader from tqdm import tqdm from transformers import AutoModelForCausalLM, AutoProcessor, get_scheduler -from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, \ - DEFAULT_CUDA_DEVICE +from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE from maestro.trainer.common.utils.leaderboard import CheckpointsLeaderboard -from maestro.trainer.common.utils.metrics import MetricsTracker, \ - save_metric_plots, BaseMetric +from maestro.trainer.common.utils.metrics import BaseMetric, MetricsTracker, save_metric_plots from maestro.trainer.common.utils.reproducibility import make_it_reproducible from maestro.trainer.models.florence_2.data_loading import prepare_data_loaders -from maestro.trainer.models.florence_2.metrics import MeanAveragePrecisionMetric, get_ground_truths_and_predictions +from maestro.trainer.models.florence_2.metrics import ( + MeanAveragePrecisionMetric, + extract_unique_detection_dataset_classes, + get_ground_truths_and_predictions, + postprocess_florence2_output_for_mean_average_precision, + run_predictions, +) from maestro.trainer.models.paligemma.training import LoraInitLiteral DEFAULT_FLORENCE2_MODEL_ID = "microsoft/Florence-2-base-ft" @@ -339,30 +343,38 @@ def run_validation_epoch( step=1, value=avg_val_loss, ) - print(f"Average Validation Loss: {avg_val_loss}") - - # TODO: standardize the calculation of metrics input to run inference only once + # Run inference once for all metrics + prompts, expected_responses, generated_texts, images = run_predictions( + dataset=loader.dataset, + processor=processor, + model=model, + device=configuration.device, + ) + + metrics_results = {"loss": avg_val_loss} + for metric in configuration.metrics: if isinstance(metric, MeanAveragePrecisionMetric): - targets, predictions, _ = get_ground_truths_and_predictions( - dataset=loader.dataset, - processor=processor, - model=model, - device=configuration.device, + classes = extract_unique_detection_dataset_classes(loader.dataset) + targets, predictions = postprocess_florence2_output_for_mean_average_precision( + expected_responses=expected_responses, + generated_texts=generated_texts, + images=images, + classes=classes, + processor=processor ) - map_result = metric.compute(targets=targets, predictions=predictions) - for map_key, map_value in map_result.items(): + result = metric.compute(targets=targets, predictions=predictions) + for key, value in result.items(): metrics_tracker.register( - metric=map_key, + metric=key, epoch=epoch_number, step=1, - value=map_value, + value=value, ) - print(f"Validation {map_key}: {map_value:.4f}") - else: - # Handle other metric types - pass + metrics_results[key] = value + + print("Validation Metrics:", ", ".join([f"{k}: {v:.4f}" for k, v in metrics_results.items()])) def save_model( From 3580ec8aa78f2a17541a6f0e63ae45f2c6be5e81 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 17:42:53 +0200 Subject: [PATCH 39/69] small fix --- maestro/trainer/models/florence_2/training.py | 1 - 1 file changed, 1 deletion(-) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 4c3ce27..742f2a2 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -20,7 +20,6 @@ from maestro.trainer.models.florence_2.metrics import ( MeanAveragePrecisionMetric, extract_unique_detection_dataset_classes, - get_ground_truths_and_predictions, postprocess_florence2_output_for_mean_average_precision, run_predictions, ) From 4543159c08df27213453b42ff6304dcdf51c0a3c Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 17:55:58 +0200 Subject: [PATCH 40/69] debug --- maestro/trainer/common/utils/metrics.py | 21 ++++++++ maestro/trainer/models/florence_2/entities.py | 0 maestro/trainer/models/florence_2/metrics.py | 49 ++++--------------- 3 files changed, 31 insertions(+), 39 deletions(-) delete mode 100644 maestro/trainer/models/florence_2/entities.py diff --git a/maestro/trainer/common/utils/metrics.py b/maestro/trainer/common/utils/metrics.py index d5f4ae9..dcc034b 100644 --- a/maestro/trainer/common/utils/metrics.py +++ b/maestro/trainer/common/utils/metrics.py @@ -89,6 +89,16 @@ def as_json( def aggregate_by_epoch(metric_values: List[Tuple[int, int, float]]) -> Dict[int, float]: + """ + Aggregates metric values by epoch, calculating the average for each epoch. + + Args: + metric_values (List[Tuple[int, int, float]]): A list of tuples containing + (epoch, step, value) for each metric measurement. + + Returns: + Dict[int, float]: A dictionary with epochs as keys and average metric values as values. + """ epoch_data = defaultdict(list) for epoch, step, value in metric_values: epoch_data[epoch].append(value) @@ -105,6 +115,17 @@ def save_metric_plots( validation_tracker: MetricsTracker, output_dir: str ): + """ + Saves plots of training and validation metrics over epochs. + + Args: + training_tracker (MetricsTracker): Tracker containing training metrics. + validation_tracker (MetricsTracker): Tracker containing validation metrics. + output_dir (str): Directory to save the generated plots. + + Returns: + None + """ if not os.path.exists(output_dir): os.makedirs(output_dir) diff --git a/maestro/trainer/models/florence_2/entities.py b/maestro/trainer/models/florence_2/entities.py deleted file mode 100644 index e69de29..0000000 diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 563734b..73b70c3 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -96,20 +96,6 @@ def postprocess_florence2_output_for_mean_average_precision( classes: List[str], processor: AutoProcessor ) -> Tuple[List[sv.Detections], List[sv.Detections]]: - """ - Postprocess Florence-2 model output for mean average precision calculation. - - Args: - expected_responses (List[str]): List of expected responses (ground truth). - generated_texts (List[str]): List of generated texts from the model. - images (List[Image.Image]): List of input images. - classes (List[str]): List of unique class names. - processor (AutoProcessor): The processor used for text generation. - - Returns: - Tuple[List[sv.Detections], List[sv.Detections]]: A tuple containing two lists of Detections objects, - representing the targets (ground truth) and predictions respectively. - """ targets = [] predictions = [] @@ -122,9 +108,16 @@ def postprocess_florence2_output_for_mean_average_precision( prediction.confidence = np.ones(len(prediction)) # Set confidence for mean average precision calculation # Postprocess target for mean average precision calculation - target = processor.post_process_generation(suffix, task="", image_size=image.size) - target = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, target, resolution_wh=image.size) - target.class_id = np.array([classes.index(class_name) for class_name in target["class_name"]]) + try: + target = processor.post_process_generation(suffix, task="", image_size=image.size) + target = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, target, resolution_wh=image.size) + target.class_id = np.array([classes.index(class_name) for class_name in target["class_name"]]) + except Exception as e: + print(f"Exception occurred: {e}") + print(f"Classes: {classes}") + print(f"Suffix: {suffix}") + print(f"Target: {target}") + raise targets.append(target) predictions.append(prediction) @@ -137,19 +130,6 @@ def run_predictions( model: AutoModelForCausalLM, device: torch.device, ) -> Tuple[List[str], List[str], List[str], List[Image.Image]]: - """ - Run predictions on the given dataset using the provided model and processor. - - Args: - dataset (DetectionDataset): The dataset to run predictions on. - processor (AutoProcessor): The processor used for text generation. - model (AutoModelForCausalLM): The model used for text generation. - device (torch.device): The device to run the model on. - - Returns: - Tuple[List[str], List[str], List[str], List[Image.Image]]: A tuple containing lists of prompts, - expected responses, generated texts, and input images. - """ prompts = [] expected_responses = [] generated_texts = [] @@ -175,15 +155,6 @@ def run_predictions( def extract_unique_detection_dataset_classes(dataset: DetectionDataset) -> List[str]: - """ - Extract unique class names from the detection dataset. - - Args: - dataset (DetectionDataset): The dataset to extract class names from. - - Returns: - List[str]: A sorted list of unique class names found in the dataset. - """ class_set = set() for i in range(len(dataset.dataset)): image, data = dataset.dataset[i] From 3fe24ceb9a3a3c31cdc2896bca575b9846ff2578 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 18:05:48 +0200 Subject: [PATCH 41/69] debug --- maestro/trainer/models/florence_2/metrics.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 73b70c3..6b729e1 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -156,10 +156,11 @@ def run_predictions( def extract_unique_detection_dataset_classes(dataset: DetectionDataset) -> List[str]: class_set = set() - for i in range(len(dataset.dataset)): - image, data = dataset.dataset[i] + for i in range(len(dataset)): + _, data = dataset[i] suffix = data["suffix"] classes = re.findall(DETECTION_CLASS_PATTERN, suffix) + print(suffix, classes) class_set.update(classes) return sorted(class_set) From e1554e7577f09846489d8757e175ed793c4daf60 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 18:11:04 +0200 Subject: [PATCH 42/69] debug --- maestro/trainer/models/florence_2/metrics.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 6b729e1..7e4c188 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -157,8 +157,7 @@ def run_predictions( def extract_unique_detection_dataset_classes(dataset: DetectionDataset) -> List[str]: class_set = set() for i in range(len(dataset)): - _, data = dataset[i] - suffix = data["suffix"] + _, suffix, _ = dataset[i] classes = re.findall(DETECTION_CLASS_PATTERN, suffix) print(suffix, classes) class_set.update(classes) From 5b42aece53bca9ccd5b1d709488e936fd6dabe59 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 18:37:38 +0200 Subject: [PATCH 43/69] retest --- maestro/trainer/models/florence_2/metrics.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 7e4c188..65793a4 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -18,7 +18,7 @@ from maestro.trainer.common.utils.metrics import BaseMetric from maestro.trainer.models.florence_2.data_loading import prepare_detection_dataset -DETECTION_CLASS_PATTERN = r"([a-zA-Z0-9 ]+ of [a-zA-Z0-9 ]+)" +DETECTION_CLASS_PATTERN = r"([a-zA-Z0-9 ]+)" class MeanAveragePrecisionMetric(BaseMetric): @@ -108,16 +108,9 @@ def postprocess_florence2_output_for_mean_average_precision( prediction.confidence = np.ones(len(prediction)) # Set confidence for mean average precision calculation # Postprocess target for mean average precision calculation - try: - target = processor.post_process_generation(suffix, task="", image_size=image.size) - target = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, target, resolution_wh=image.size) - target.class_id = np.array([classes.index(class_name) for class_name in target["class_name"]]) - except Exception as e: - print(f"Exception occurred: {e}") - print(f"Classes: {classes}") - print(f"Suffix: {suffix}") - print(f"Target: {target}") - raise + target = processor.post_process_generation(suffix, task="", image_size=image.size) + target = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, target, resolution_wh=image.size) + target.class_id = np.array([classes.index(class_name) for class_name in target["class_name"]]) targets.append(target) predictions.append(prediction) @@ -159,7 +152,6 @@ def extract_unique_detection_dataset_classes(dataset: DetectionDataset) -> List[ for i in range(len(dataset)): _, suffix, _ = dataset[i] classes = re.findall(DETECTION_CLASS_PATTERN, suffix) - print(suffix, classes) class_set.update(classes) return sorted(class_set) From 40a8bfad5bdb3a3ea1842be69610af6b939459e9 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 18:50:10 +0200 Subject: [PATCH 44/69] debug --- maestro/trainer/models/florence_2/metrics.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 65793a4..9355fe0 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -108,9 +108,15 @@ def postprocess_florence2_output_for_mean_average_precision( prediction.confidence = np.ones(len(prediction)) # Set confidence for mean average precision calculation # Postprocess target for mean average precision calculation - target = processor.post_process_generation(suffix, task="", image_size=image.size) - target = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, target, resolution_wh=image.size) - target.class_id = np.array([classes.index(class_name) for class_name in target["class_name"]]) + try: + target = processor.post_process_generation(suffix, task="", image_size=image.size) + target = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, target, resolution_wh=image.size) + target.class_id = np.array([classes.index(class_name) for class_name in target["class_name"]]) + except Exception as e: + print(f"Error processing target: {e}") + print(f"Classes: {classes}") + print(f"Suffix: {suffix}") + raise e targets.append(target) predictions.append(prediction) @@ -152,6 +158,7 @@ def extract_unique_detection_dataset_classes(dataset: DetectionDataset) -> List[ for i in range(len(dataset)): _, suffix, _ = dataset[i] classes = re.findall(DETECTION_CLASS_PATTERN, suffix) + print(suffix, classes) class_set.update(classes) return sorted(class_set) From 1db75adc0d137203f86328b7af708434ab030b86 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 18:58:09 +0200 Subject: [PATCH 45/69] ready for test --- maestro/trainer/models/florence_2/metrics.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 9355fe0..6ad9c99 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -18,7 +18,7 @@ from maestro.trainer.common.utils.metrics import BaseMetric from maestro.trainer.models.florence_2.data_loading import prepare_detection_dataset -DETECTION_CLASS_PATTERN = r"([a-zA-Z0-9 ]+)" +DETECTION_CLASS_PATTERN = r"([a-zA-Z0-9 -]+)" class MeanAveragePrecisionMetric(BaseMetric): @@ -108,15 +108,9 @@ def postprocess_florence2_output_for_mean_average_precision( prediction.confidence = np.ones(len(prediction)) # Set confidence for mean average precision calculation # Postprocess target for mean average precision calculation - try: - target = processor.post_process_generation(suffix, task="", image_size=image.size) - target = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, target, resolution_wh=image.size) - target.class_id = np.array([classes.index(class_name) for class_name in target["class_name"]]) - except Exception as e: - print(f"Error processing target: {e}") - print(f"Classes: {classes}") - print(f"Suffix: {suffix}") - raise e + target = processor.post_process_generation(suffix, task="", image_size=image.size) + target = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, target, resolution_wh=image.size) + target.class_id = np.array([classes.index(class_name) for class_name in target["class_name"]]) targets.append(target) predictions.append(prediction) @@ -158,7 +152,6 @@ def extract_unique_detection_dataset_classes(dataset: DetectionDataset) -> List[ for i in range(len(dataset)): _, suffix, _ = dataset[i] classes = re.findall(DETECTION_CLASS_PATTERN, suffix) - print(suffix, classes) class_set.update(classes) return sorted(class_set) From bc0aff57a3517945308a156028562811cfeab6e9 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 19:07:04 +0200 Subject: [PATCH 46/69] debug --- maestro/trainer/models/florence_2/training.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 742f2a2..17f0669 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -363,6 +363,8 @@ def run_validation_epoch( classes=classes, processor=processor ) + print(f"Targets: {targets}") + print(f"Predictions: {predictions}") result = metric.compute(targets=targets, predictions=predictions) for key, value in result.items(): metrics_tracker.register( From 2498de9896e726f7fe312e08bb498f2f66eccae6 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 19:43:01 +0200 Subject: [PATCH 47/69] am I this stupid? --- maestro/trainer/models/florence_2/metrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 6ad9c99..1f14ac6 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -31,7 +31,7 @@ def compute( targets: List[sv.Detections], predictions: List[sv.Detections] ) -> Dict[str, float]: - result = MeanAveragePrecision().update(targets, predictions).compute() + result = MeanAveragePrecision().update(targets=targets, predictions=predictions).compute() return { "map50:95": result.map50_95, "map50": result.map50, From d363c663a507646510b042b056f78c5a6536a9de Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 19:51:21 +0200 Subject: [PATCH 48/69] clean up --- maestro/trainer/models/florence_2/training.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 17f0669..742f2a2 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -363,8 +363,6 @@ def run_validation_epoch( classes=classes, processor=processor ) - print(f"Targets: {targets}") - print(f"Predictions: {predictions}") result = metric.compute(targets=targets, predictions=predictions) for key, value in result.items(): metrics_tracker.register( From 9aedb29467120fc71d62e03bf063345e05e071c8 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 9 Sep 2024 20:05:13 +0200 Subject: [PATCH 49/69] test validation set result render --- maestro/trainer/models/florence_2/training.py | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 742f2a2..2bbd2ea 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -342,7 +342,6 @@ def run_validation_epoch( step=1, value=avg_val_loss, ) - # Run inference once for all metrics prompts, expected_responses, generated_texts, images = run_predictions( dataset=loader.dataset, @@ -375,6 +374,49 @@ def run_validation_epoch( print("Validation Metrics:", ", ".join([f"{k}: {v:.4f}" for k, v in metrics_results.items()])) + # Display inference results in IPython environments + try: + import IPython + if IPython.get_ipython() is not None: + import io + import base64 + import html + import json + from PIL import Image + import supervision as sv + from IPython.display import display, HTML + + def render_inline(image: Image.Image, resize=(128, 128)): + """Convert image into inline html.""" + image = image.resize(resize) + with io.BytesIO() as buffer: + image.save(buffer, format='jpeg') + image_b64 = str(base64.b64encode(buffer.getvalue()), "utf-8") + return f"data:image/jpeg;base64,{image_b64}" + + def render_example(image: Image.Image, response): + # try: + # detections = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, response, resolution_wh=image.size) + # image = sv.BoundingBoxAnnotator(color_lookup=sv.ColorLookup.INDEX).annotate(image.copy(), detections) + # image = sv.LabelAnnotator(color_lookup=sv.ColorLookup.INDEX).annotate(image, detections) + # except: + # print('failed to render model response') + return f""" +
+ +

{html.escape(json.dumps(response))}

+
+ """ + + html_out = "" + count = min(8, len(images)) # Display up to 8 examples + for i in range(count): + html_out += render_example(images[i], generated_texts[i]) + + display(HTML(html_out)) + except ImportError: + pass # Skip visualization if required libraries are not available + def save_model( target_dir: str, From bb44d1161628cf60567a762fc828d63ddd44f77a Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 00:33:10 +0200 Subject: [PATCH 50/69] updated results display --- maestro/trainer/common/utils/metrics.py | 101 +++++++++++++++++- maestro/trainer/models/florence_2/training.py | 44 +------- 2 files changed, 102 insertions(+), 43 deletions(-) diff --git a/maestro/trainer/common/utils/metrics.py b/maestro/trainer/common/utils/metrics.py index dcc034b..e22cce4 100644 --- a/maestro/trainer/common/utils/metrics.py +++ b/maestro/trainer/common/utils/metrics.py @@ -1,12 +1,16 @@ from __future__ import annotations +import base64 +import html +import io import json import os from abc import ABC, abstractmethod from collections import defaultdict -from typing import Dict, Tuple, List, Any +from typing import Any, Dict, List, Tuple import matplotlib.pyplot as plt +from PIL import Image class BaseMetric(ABC): @@ -177,3 +181,98 @@ def save_metric_plots( plt.grid(True) plt.savefig(f'{output_dir}/{metric}_plot.png') plt.close() + + + +def display_results( + prompts: List[str], + expected_responses: List[str], + generated_texts: List[str], + images: List[Image.Image] +) -> None: + """ + Display the results of model inference in IPython environments. + + This function attempts to display the results (prompts, expected responses, + generated texts, and images) in an HTML format if running in an IPython + environment. If not in IPython or if there's an ImportError, it silently passes. + + Args: + prompts (List[str]): List of input prompts. + expected_responses (List[str]): List of expected responses. + generated_texts (List[str]): List of texts generated by the model. + images (List[Image.Image]): List of input images. + + Returns: + None + """ + try: + import IPython + if IPython.get_ipython() is not None: + from IPython.display import display, HTML + html_out = create_html_output(prompts, expected_responses, generated_texts, images) + display(HTML(html_out)) + except ImportError: + pass # Skip visualization if required libraries are not available + + +def create_html_output( + prompts: List[str], + expected_responses: List[str], + generated_texts: List[str], + images: List[Image.Image] +) -> str: + """ + Create an HTML string to display the results of model inference. + + This function generates an HTML string that includes styled divs for each + result, containing the input image, prompt, expected response, and generated text. + + Args: + prompts (List[str]): List of input prompts. + expected_responses (List[str]): List of expected responses. + generated_texts (List[str]): List of texts generated by the model. + images (List[Image.Image]): List of input images. + + Returns: + str: An HTML string containing the formatted results. + """ + html_out = "" + count = min(8, len(images)) # Display up to 8 examples + for i in range(count): + html_out += f""" +
+
+ +
+
+
Prompt: {html.escape(prompts[i])}
+
Expected: {html.escape(expected_responses[i])}
+
Generated: {html.escape(generated_texts[i])}
+
+
+ """ + return html_out + + +def render_inline(image: Image.Image, resize: Tuple[int, int] = (256, 256)) -> str: + """ + Convert an image into an inline HTML string. + + This function takes an image, resizes it, and converts it to a base64-encoded + string that can be used as the source for an HTML img tag. + + Args: + image (Image.Image): The input image to be converted. + resize (Tuple[int, int], optional): The dimensions to resize the image to. + Defaults to (256, 256). + + Returns: + str: A string containing the data URI for the image, ready to be used + in an HTML img tag's src attribute. + """ + image = image.resize(resize) + with io.BytesIO() as buffer: + image.save(buffer, format='jpeg') + image_b64 = base64.b64encode(buffer.getvalue()).decode() + return f"data:image/jpeg;base64,{image_b64}" \ No newline at end of file diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 2bbd2ea..69eb928 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -14,7 +14,7 @@ from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE from maestro.trainer.common.utils.leaderboard import CheckpointsLeaderboard -from maestro.trainer.common.utils.metrics import BaseMetric, MetricsTracker, save_metric_plots +from maestro.trainer.common.utils.metrics import BaseMetric, MetricsTracker, display_results, save_metric_plots from maestro.trainer.common.utils.reproducibility import make_it_reproducible from maestro.trainer.models.florence_2.data_loading import prepare_data_loaders from maestro.trainer.models.florence_2.metrics import ( @@ -375,47 +375,7 @@ def run_validation_epoch( print("Validation Metrics:", ", ".join([f"{k}: {v:.4f}" for k, v in metrics_results.items()])) # Display inference results in IPython environments - try: - import IPython - if IPython.get_ipython() is not None: - import io - import base64 - import html - import json - from PIL import Image - import supervision as sv - from IPython.display import display, HTML - - def render_inline(image: Image.Image, resize=(128, 128)): - """Convert image into inline html.""" - image = image.resize(resize) - with io.BytesIO() as buffer: - image.save(buffer, format='jpeg') - image_b64 = str(base64.b64encode(buffer.getvalue()), "utf-8") - return f"data:image/jpeg;base64,{image_b64}" - - def render_example(image: Image.Image, response): - # try: - # detections = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, response, resolution_wh=image.size) - # image = sv.BoundingBoxAnnotator(color_lookup=sv.ColorLookup.INDEX).annotate(image.copy(), detections) - # image = sv.LabelAnnotator(color_lookup=sv.ColorLookup.INDEX).annotate(image, detections) - # except: - # print('failed to render model response') - return f""" -
- -

{html.escape(json.dumps(response))}

-
- """ - - html_out = "" - count = min(8, len(images)) # Display up to 8 examples - for i in range(count): - html_out += render_example(images[i], generated_texts[i]) - - display(HTML(html_out)) - except ImportError: - pass # Skip visualization if required libraries are not available + display_results(prompts, expected_responses, generated_texts, images) def save_model( From ec2a3243d6cd294e0330b39eac7cbee118d23f60 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 13:09:24 +0200 Subject: [PATCH 51/69] cleanup --- .../trainer/models/florence_2/data_loading.py | 4 +- maestro/trainer/models/florence_2/metrics.py | 118 +++++++++--------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/maestro/trainer/models/florence_2/data_loading.py b/maestro/trainer/models/florence_2/data_loading.py index 4fa9708..4873003 100644 --- a/maestro/trainer/models/florence_2/data_loading.py +++ b/maestro/trainer/models/florence_2/data_loading.py @@ -76,7 +76,7 @@ def prepare_detection_data_loader( return DataLoader( dataset, batch_size=batch_size, - collate_fn=partial(_collate_fn, processor=processor, device=device), + collate_fn=partial(collate_fn, processor=processor, device=device), num_workers=num_workers, shuffle=shuffle, ) @@ -100,7 +100,7 @@ def prepare_detection_dataset( ) -def _collate_fn( +def collate_fn( batch: Tuple[List[str], List[str], List[Image.Image]], processor: AutoProcessor, device: torch.device, diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 1f14ac6..da6ffe7 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -170,62 +170,62 @@ def extract_unique_detection_dataset_classes(dataset: DetectionDataset) -> List[ # save_json(path=target_path, content=content) -def dump_post_processed_outputs( - dataset: DetectionDataset, - post_processed_text_outputs: List[str], - training_dir: str, - split_name: str, -) -> None: - result_dict = { - dataset.dataset.entries[idx]["image"]: output for idx, output in enumerate(post_processed_text_outputs) - } - target_path = os.path.join( - training_dir, - "model_debug", - split_name, - f"post_processed_text_predictions_{split_name}.json", - ) - save_json(path=target_path, content=result_dict) - - -def dump_visualised_samples( - dataset: DetectionDataset, - targets: List[sv.Detections], - predictions: List[sv.Detections], - num_samples_to_visualise: int, - training_dir: str, - split_name: str, -) -> None: - all_indices = list(range(len(dataset))) - random.shuffle(all_indices) - selected_indices = all_indices[:num_samples_to_visualise] - target_dir = os.path.join(training_dir, "predictions_visualised", split_name) - os.makedirs(target_dir, exist_ok=True) - boxes_annotator = sv.BoxAnnotator(color_lookup=sv.ColorLookup.INDEX) - label_annotator = sv.LabelAnnotator(color_lookup=sv.ColorLookup.INDEX) - for idx in tqdm(selected_indices, desc="Preparing predictions visualisations..."): - image_name = dataset.dataset.entries[idx]["image"] - image = dataset.dataset[idx][0] - prediction_image = boxes_annotator.annotate( - image.copy(), - predictions[idx], - ) - prediction_image = np.asarray( - label_annotator.annotate( - prediction_image, - predictions[idx], - ) - )[:, :, ::-1] - target_image = boxes_annotator.annotate( - image.copy(), - targets[idx], - ) - target_image = np.asarray( - label_annotator.annotate( - target_image, - targets[idx], - ) - )[:, :, ::-1] - concatenated = cv2.hconcat([target_image, prediction_image]) - target_image_path = os.path.join(target_dir, image_name) - cv2.imwrite(target_image_path, concatenated) +# def dump_post_processed_outputs( +# dataset: DetectionDataset, +# post_processed_text_outputs: List[str], +# training_dir: str, +# split_name: str, +# ) -> None: +# result_dict = { +# dataset.dataset.entries[idx]["image"]: output for idx, output in enumerate(post_processed_text_outputs) +# } +# target_path = os.path.join( +# training_dir, +# "model_debug", +# split_name, +# f"post_processed_text_predictions_{split_name}.json", +# ) +# save_json(path=target_path, content=result_dict) + + +# def dump_visualised_samples( +# dataset: DetectionDataset, +# targets: List[sv.Detections], +# predictions: List[sv.Detections], +# num_samples_to_visualise: int, +# training_dir: str, +# split_name: str, +# ) -> None: +# all_indices = list(range(len(dataset))) +# random.shuffle(all_indices) +# selected_indices = all_indices[:num_samples_to_visualise] +# target_dir = os.path.join(training_dir, "predictions_visualised", split_name) +# os.makedirs(target_dir, exist_ok=True) +# boxes_annotator = sv.BoxAnnotator(color_lookup=sv.ColorLookup.INDEX) +# label_annotator = sv.LabelAnnotator(color_lookup=sv.ColorLookup.INDEX) +# for idx in tqdm(selected_indices, desc="Preparing predictions visualisations..."): +# image_name = dataset.dataset.entries[idx]["image"] +# image = dataset.dataset[idx][0] +# prediction_image = boxes_annotator.annotate( +# image.copy(), +# predictions[idx], +# ) +# prediction_image = np.asarray( +# label_annotator.annotate( +# prediction_image, +# predictions[idx], +# ) +# )[:, :, ::-1] +# target_image = boxes_annotator.annotate( +# image.copy(), +# targets[idx], +# ) +# target_image = np.asarray( +# label_annotator.annotate( +# target_image, +# targets[idx], +# ) +# )[:, :, ::-1] +# concatenated = cv2.hconcat([target_image, prediction_image]) +# target_image_path = os.path.join(target_dir, image_name) +# cv2.imwrite(target_image_path, concatenated) From 27239875253bb9ab9c229d5f41a2cca4c6ce09c6 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 14:49:44 +0200 Subject: [PATCH 52/69] test new checkpoint management system --- .../trainer/models/florence_2/checkpoints.py | 105 +++++++++++++++++ maestro/trainer/models/florence_2/training.py | 108 +++--------------- 2 files changed, 119 insertions(+), 94 deletions(-) create mode 100644 maestro/trainer/models/florence_2/checkpoints.py diff --git a/maestro/trainer/models/florence_2/checkpoints.py b/maestro/trainer/models/florence_2/checkpoints.py new file mode 100644 index 0000000..cdffe7b --- /dev/null +++ b/maestro/trainer/models/florence_2/checkpoints.py @@ -0,0 +1,105 @@ +import os +from typing import Optional, Tuple + +import torch +from transformers import AutoModelForCausalLM, AutoProcessor + +from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, \ + DEFAULT_CUDA_DEVICE + +DEFAULT_FLORENCE2_MODEL_ID = "microsoft/Florence-2-base-ft" +DEFAULT_FLORENCE2_MODEL_REVISION = "refs/pr/20" +DEVICE = torch.device("cpu") \ + if not torch.cuda.is_available() \ + else os.getenv(CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE) + + +class CheckpointManager: + """Manages checkpoints for model training. + + This class handles saving and retrieving model checkpoints during training. + + Attributes: + training_dir (str): Directory where checkpoints will be saved. + best_val_loss (float): Best validation loss achieved so far. + latest_checkpoint_dir (str): Directory for the latest checkpoint. + best_checkpoint_dir (str): Directory for the best checkpoint. + """ + + def __init__(self, training_dir: str): + """Initializes the CheckpointManager. + + Args: + training_dir (str): Directory where checkpoints will be saved. + """ + self.training_dir = training_dir + self.best_val_loss = float('inf') + self.latest_checkpoint_dir = os.path.join(training_dir, "checkpoints", "latest") + self.best_checkpoint_dir = os.path.join(training_dir, "checkpoints", "best") + + def save_latest(self, processor: AutoProcessor, model: AutoModelForCausalLM): + """Saves the latest model checkpoint. + + Args: + processor (AutoProcessor): The processor to save. + model (AutoModelForCausalLM): The model to save. + """ + save_model(self.latest_checkpoint_dir, processor, model) + + def save_best(self, processor: AutoProcessor, model: AutoModelForCausalLM, val_loss: float): + """Saves the best model checkpoint if the validation loss improves. + + Args: + processor (AutoProcessor): The processor to save. + model (AutoModelForCausalLM): The model to save. + val_loss (float): The current validation loss. + """ + if val_loss < self.best_val_loss: + self.best_val_loss = val_loss + save_model(self.best_checkpoint_dir, processor, model) + print(f"New best model saved with validation loss: {self.best_val_loss}") + + def get_best_model_path(self): + """Returns the path to the best model checkpoint. + + Returns: + str: Path to the best model checkpoint. + """ + return self.best_checkpoint_dir + + +def save_model( + target_dir: str, + processor: AutoProcessor, + model: AutoModelForCausalLM, +) -> None: + """Saves the model and processor to the specified directory. + + Args: + target_dir (str): Directory where the model and processor will be saved. + processor (AutoProcessor): The processor to save. + model (AutoModelForCausalLM): The model to save. + """ + os.makedirs(target_dir, exist_ok=True) + processor.save_pretrained(target_dir) + model.save_pretrained(target_dir) + + +def load_model( + model_id_or_path: str = DEFAULT_FLORENCE2_MODEL_ID, + revision: str = DEFAULT_FLORENCE2_MODEL_REVISION, + device: torch.device = DEVICE, + cache_dir: Optional[str] = None, +) -> Tuple[AutoProcessor, AutoModelForCausalLM]: + processor = AutoProcessor.from_pretrained( + model_id_or_path, + trust_remote_code=True, + revision=revision, + ) + model = AutoModelForCausalLM.from_pretrained( + model_id_or_path, + trust_remote_code=True, + revision=revision, + cache_dir=cache_dir, + ).to(device) + return processor, model diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 69eb928..702546b 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -1,5 +1,4 @@ import os -import shutil from dataclasses import dataclass, field, replace from glob import glob from typing import List, Literal, Optional, Tuple, Union @@ -12,10 +11,11 @@ from tqdm import tqdm from transformers import AutoModelForCausalLM, AutoProcessor, get_scheduler -from maestro.trainer.common.configuration.env import CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE -from maestro.trainer.common.utils.leaderboard import CheckpointsLeaderboard -from maestro.trainer.common.utils.metrics import BaseMetric, MetricsTracker, display_results, save_metric_plots +from maestro.trainer.common.utils.metrics import BaseMetric, MetricsTracker, \ + display_results, save_metric_plots from maestro.trainer.common.utils.reproducibility import make_it_reproducible +from maestro.trainer.models.florence_2.checkpoints import CheckpointManager, load_model, \ + DEFAULT_FLORENCE2_MODEL_ID, DEFAULT_FLORENCE2_MODEL_REVISION, DEVICE from maestro.trainer.models.florence_2.data_loading import prepare_data_loaders from maestro.trainer.models.florence_2.metrics import ( MeanAveragePrecisionMetric, @@ -25,12 +25,6 @@ ) from maestro.trainer.models.paligemma.training import LoraInitLiteral -DEFAULT_FLORENCE2_MODEL_ID = "microsoft/Florence-2-base-ft" -DEFAULT_FLORENCE2_MODEL_REVISION = "refs/pr/20" -DEVICE = torch.device("cpu") \ - if not torch.cuda.is_available() \ - else os.getenv(CUDA_DEVICE_ENV, DEFAULT_CUDA_DEVICE) - @dataclass(frozen=True) class TrainingConfiguration: @@ -54,7 +48,6 @@ class TrainingConfiguration: use_rslora: bool = True init_lora_weights: Union[bool, LoraInitLiteral] = "gaussian" training_dir: str = "./training/florence-2" - max_checkpoints_to_keep: int = 3 num_samples_to_visualise: int = 64 metrics: List[BaseMetric] = field(default_factory=list) @@ -68,9 +61,8 @@ def train(configuration: TrainingConfiguration) -> None: configuration, training_dir=training_run_dir, ) - checkpoints_leaderboard = CheckpointsLeaderboard( - max_checkpoints=configuration.max_checkpoints_to_keep, - ) + checkpoint_manager = CheckpointManager(training_run_dir) + processor, model = load_model( model_id_or_path=configuration.model_id_or_path, revision=configuration.revision, @@ -85,8 +77,6 @@ def train(configuration: TrainingConfiguration) -> None: num_workers=configuration.loaders_workers, test_loaders_workers=configuration.test_loaders_workers, ) - # if test_loader is None: - # test_loader = val_loader peft_model = prepare_peft_model( model=model, r=configuration.lora_r, @@ -108,29 +98,11 @@ def train(configuration: TrainingConfiguration) -> None: model=peft_model, data_loaders=(train_loader, val_loader), configuration=configuration, - checkpoints_leaderboard=checkpoints_leaderboard, training_metrics_tracker=training_metrics_tracker, validation_metrics_tracker=validation_metrics_tracker, + checkpoint_manager=checkpoint_manager ) - best_model_path = checkpoints_leaderboard.get_best_model() - print(f"Loading best model from {best_model_path}") - processor, model = load_model( - model_id_or_path=best_model_path, - ) - # if test_loader is not None: - # run_validation_epoch( - # processor=processor, - # model=model, - # loader=test_loader, - # epoch_number=None, - # configuration=configuration, - # title="Test", - # ) - best_model_dir = os.path.join(configuration.training_dir, "best_model") - print(f"Saving best model: {best_model_dir}") - model.save_pretrained(best_model_dir) - processor.save_pretrained(best_model_dir) save_metric_plots( training_tracker=training_metrics_tracker, validation_tracker=validation_metrics_tracker, @@ -143,37 +115,6 @@ def train(configuration: TrainingConfiguration) -> None: output_dir=os.path.join(configuration.training_dir, "metrics"), filename="validation.json") - # for split_name in ["valid", "test"]: - # prepare_detection_training_summary( - # processor=processor, - # model=model, - # dataset_location=configuration.dataset_location, - # split_name=split_name, - # training_dir=configuration.training_dir, - # num_samples_to_visualise=configuration.num_samples_to_visualise, - # device=configuration.device, - # ) - - -def load_model( - model_id_or_path: str = DEFAULT_FLORENCE2_MODEL_ID, - revision: str = DEFAULT_FLORENCE2_MODEL_REVISION, - device: torch.device = DEVICE, - cache_dir: Optional[str] = None, -) -> Tuple[AutoProcessor, AutoModelForCausalLM]: - processor = AutoProcessor.from_pretrained( - model_id_or_path, - trust_remote_code=True, - revision=revision, - ) - model = AutoModelForCausalLM.from_pretrained( - model_id_or_path, - trust_remote_code=True, - revision=revision, - cache_dir=cache_dir, - ).to(device) - return processor, model - def prepare_peft_model( model: AutoModelForCausalLM, @@ -208,9 +149,9 @@ def run_training_loop( model: PeftModel, data_loaders: Tuple[DataLoader, Optional[DataLoader]], configuration: TrainingConfiguration, - checkpoints_leaderboard: CheckpointsLeaderboard, training_metrics_tracker: MetricsTracker, validation_metrics_tracker: MetricsTracker, + checkpoint_manager: CheckpointManager, ) -> None: train_loader, val_loader = data_loaders optimizer = _get_optimizer(model=model, configuration=configuration) @@ -231,9 +172,9 @@ def run_training_loop( configuration=configuration, optimizer=optimizer, lr_scheduler=lr_scheduler, - checkpoints_leaderboard=checkpoints_leaderboard, training_metrics_tracker=training_metrics_tracker, validation_metrics_tracker=validation_metrics_tracker, + checkpoint_manager=checkpoint_manager ) @@ -246,9 +187,9 @@ def run_training_epoch( configuration: TrainingConfiguration, optimizer: Optimizer, lr_scheduler: LRScheduler, - checkpoints_leaderboard: CheckpointsLeaderboard, training_metrics_tracker: MetricsTracker, validation_metrics_tracker: MetricsTracker, + checkpoint_manager: CheckpointManager, ) -> None: model.train() training_losses: List[float] = [] @@ -292,21 +233,10 @@ def run_training_epoch( configuration=configuration, metrics_tracker=validation_metrics_tracker, ) - validation_loss = validation_metrics_tracker.get_metric_values("loss")[-1][2] - checkpoint_dir = os.path.join(configuration.training_dir, "checkpoints", str(epoch_number)) - should_save, to_remove = checkpoints_leaderboard.register_checkpoint( - epoch=epoch_number, - path=checkpoint_dir, - loss=validation_loss, - ) - if should_save: - print(f"Saving checkpoint under {checkpoint_dir}") - os.makedirs(checkpoint_dir, exist_ok=True) - model.save_pretrained(checkpoint_dir) - processor.save_pretrained(checkpoint_dir) - if to_remove is not None: - print(f"Removing checkpoint {to_remove}") - shutil.rmtree(to_remove, ignore_errors=True) + + val_loss = validation_metrics_tracker.get_metric_values("loss")[-1][2] + checkpoint_manager.save_latest(processor, model) + checkpoint_manager.save_best(processor, model, val_loss) def run_validation_epoch( @@ -378,16 +308,6 @@ def run_validation_epoch( display_results(prompts, expected_responses, generated_texts, images) -def save_model( - target_dir: str, - processor: AutoProcessor, - model: AutoModelForCausalLM, -) -> None: - os.makedirs(target_dir, exist_ok=True) - processor.save_pretrained(target_dir) - model.save_pretrained(target_dir) - - def _establish_training_run_dir(training_dir: str) -> str: training_dir = os.path.abspath(training_dir) existing_directory_entries = glob(os.path.join(training_dir, "*")) From 27c3cfd40a8451838ed371efcc00fa45fa2e6d6e Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 14:57:52 +0200 Subject: [PATCH 53/69] more cleanup --- maestro/trainer/models/florence_2/metrics.py | 133 +------------------ 1 file changed, 2 insertions(+), 131 deletions(-) diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index da6ffe7..ae01b38 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -1,22 +1,17 @@ -import os -import random import re from typing import List, Dict from typing import Tuple -import cv2 import numpy as np import supervision as sv import torch +from PIL import Image from supervision.metrics.mean_average_precision import MeanAveragePrecision from tqdm import tqdm from transformers import AutoProcessor, AutoModelForCausalLM -from PIL import Image from maestro.trainer.common.data_loaders.datasets import DetectionDataset -from maestro.trainer.common.utils.file_system import save_json from maestro.trainer.common.utils.metrics import BaseMetric -from maestro.trainer.models.florence_2.data_loading import prepare_detection_dataset DETECTION_CLASS_PATTERN = r"([a-zA-Z0-9 -]+)" @@ -39,56 +34,6 @@ def compute( } -# def prepare_detection_training_summary( -# processor: AutoProcessor, -# model: AutoModelForCausalLM, -# dataset_location: str, -# split_name: str, -# training_dir: str, -# num_samples_to_visualise: int, -# device: torch.device, -# ) -> None: -# dataset = prepare_detection_dataset( -# dataset_location=dataset_location, -# split_name=split_name, -# ) -# if dataset is None: -# return None -# targets, predictions, post_processed_text_outputs = get_ground_truths_and_predictions( -# dataset=dataset, -# processor=processor, -# model=model, -# split_name=split_name, -# device=device, -# ) -# mean_average_precision = sv.MeanAveragePrecision.from_detections( -# predictions=predictions, -# targets=targets, -# ) -# print(f"{split_name} | map50_95: {mean_average_precision.map50_95:.2f}") -# print(f"{split_name} | map50: {mean_average_precision.map50:.2f}") -# print(f"{split_name} | map75: {mean_average_precision.map75:.2f}") -# dump_metrics( -# training_dir=training_dir, -# split_name=split_name, -# metrics=mean_average_precision, -# ) -# dump_post_processed_outputs( -# dataset=dataset, -# post_processed_text_outputs=post_processed_text_outputs, -# training_dir=training_dir, -# split_name=split_name, -# ) -# dump_visualised_samples( -# dataset=dataset, -# targets=targets, -# predictions=predictions, -# num_samples_to_visualise=num_samples_to_visualise, -# training_dir=training_dir, -# split_name=split_name, -# ) - - def postprocess_florence2_output_for_mean_average_precision( expected_responses: List[str], generated_texts: List[str], @@ -117,6 +62,7 @@ def postprocess_florence2_output_for_mean_average_precision( return targets, predictions + def run_predictions( dataset: DetectionDataset, processor: AutoProcessor, @@ -154,78 +100,3 @@ def extract_unique_detection_dataset_classes(dataset: DetectionDataset) -> List[ classes = re.findall(DETECTION_CLASS_PATTERN, suffix) class_set.update(classes) return sorted(class_set) - - -# def dump_metrics( -# training_dir: str, -# split_name: str, -# metrics: sv.MeanAveragePrecision, -# ) -> None: -# target_path = os.path.join(training_dir, "metrics", split_name, f"metrics_{split_name}.json") -# content = { -# "map50_95": metrics.map50_95, -# "map50": metrics.map50, -# "map75": metrics.map75, -# } -# save_json(path=target_path, content=content) - - -# def dump_post_processed_outputs( -# dataset: DetectionDataset, -# post_processed_text_outputs: List[str], -# training_dir: str, -# split_name: str, -# ) -> None: -# result_dict = { -# dataset.dataset.entries[idx]["image"]: output for idx, output in enumerate(post_processed_text_outputs) -# } -# target_path = os.path.join( -# training_dir, -# "model_debug", -# split_name, -# f"post_processed_text_predictions_{split_name}.json", -# ) -# save_json(path=target_path, content=result_dict) - - -# def dump_visualised_samples( -# dataset: DetectionDataset, -# targets: List[sv.Detections], -# predictions: List[sv.Detections], -# num_samples_to_visualise: int, -# training_dir: str, -# split_name: str, -# ) -> None: -# all_indices = list(range(len(dataset))) -# random.shuffle(all_indices) -# selected_indices = all_indices[:num_samples_to_visualise] -# target_dir = os.path.join(training_dir, "predictions_visualised", split_name) -# os.makedirs(target_dir, exist_ok=True) -# boxes_annotator = sv.BoxAnnotator(color_lookup=sv.ColorLookup.INDEX) -# label_annotator = sv.LabelAnnotator(color_lookup=sv.ColorLookup.INDEX) -# for idx in tqdm(selected_indices, desc="Preparing predictions visualisations..."): -# image_name = dataset.dataset.entries[idx]["image"] -# image = dataset.dataset[idx][0] -# prediction_image = boxes_annotator.annotate( -# image.copy(), -# predictions[idx], -# ) -# prediction_image = np.asarray( -# label_annotator.annotate( -# prediction_image, -# predictions[idx], -# ) -# )[:, :, ::-1] -# target_image = boxes_annotator.annotate( -# image.copy(), -# targets[idx], -# ) -# target_image = np.asarray( -# label_annotator.annotate( -# target_image, -# targets[idx], -# ) -# )[:, :, ::-1] -# concatenated = cv2.hconcat([target_image, prediction_image]) -# target_image_path = os.path.join(target_dir, image_name) -# cv2.imwrite(target_image_path, concatenated) From dad39bab6ff3335b8843f5fff65538e840ac866a Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 16:00:08 +0200 Subject: [PATCH 54/69] TrainingConfiguration filed names refactoer --- maestro/cli/introspection.py | 3 +- maestro/trainer/models/florence_2/training.py | 166 +++++++++++------- 2 files changed, 100 insertions(+), 69 deletions(-) diff --git a/maestro/cli/introspection.py b/maestro/cli/introspection.py index 3a62ce3..fc6aef9 100644 --- a/maestro/cli/introspection.py +++ b/maestro/cli/introspection.py @@ -2,7 +2,8 @@ import typer -from maestro.cli.env import DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV +from maestro.cli.env import DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, \ + DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV from maestro.cli.utils import str2bool diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/training.py index 702546b..1885a9f 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/training.py @@ -28,68 +28,95 @@ @dataclass(frozen=True) class TrainingConfiguration: - dataset_location: str - model_id_or_path: str = DEFAULT_FLORENCE2_MODEL_ID + """Configuration for training a Florence-2 model. + + This class encapsulates all the parameters needed for training a Florence-2 model, + including dataset paths, model specifications, training hyperparameters, and output settings. + + Attributes: + dataset_path (str): Path to the dataset used for training. + model_id (str): Identifier for the Florence-2 model. Defaults to DEFAULT_FLORENCE2_MODEL_ID. + revision (str): Revision of the model to use. Defaults to DEFAULT_FLORENCE2_MODEL_REVISION. + device (torch.device): Device to use for training. Defaults to DEVICE. + cache_dir (Optional[str]): Directory to cache the model. Defaults to None. + epochs (int): Number of training epochs. Defaults to 10. + optimizer (Literal["sgd", "adamw", "adam"]): Optimizer to use for training. Defaults to "adamw". + lr (float): Learning rate for the optimizer. Defaults to 1e-5. + lr_scheduler (Literal["linear", "cosine", "polynomial"]): Learning rate scheduler. Defaults to "linear". + batch_size (int): Batch size for training. Defaults to 4. + val_batch_size (Optional[int]): Batch size for validation. Defaults to None. + num_workers (int): Number of workers for data loading. Defaults to 0. + val_num_workers (Optional[int]): Number of workers for validation data loading. Defaults to None. + lora_r (int): Rank of the LoRA update matrices. Defaults to 8. + lora_alpha (int): Scaling factor for the LoRA update. Defaults to 8. + lora_dropout (float): Dropout probability for LoRA layers. Defaults to 0.05. + bias (Literal["none", "all", "lora_only"]): Which bias to train. Defaults to "none". + use_rslora (bool): Whether to use RSLoRA. Defaults to True. + init_lora_weights (Union[bool, LoraInitLiteral]): How to initialize LoRA weights. Defaults to "gaussian". + output_dir (str): Directory to save output files. Defaults to "./training/florence-2". + metrics (List[BaseMetric]): List of metrics to track during training. Defaults to an empty list. + """ + dataset_path: str + model_id: str = DEFAULT_FLORENCE2_MODEL_ID revision: str = DEFAULT_FLORENCE2_MODEL_REVISION device: torch.device = DEVICE - transformers_cache_dir: Optional[str] = None - training_epochs: int = 10 - optimiser: Literal["SGD", "adamw", "adam"] = "adamw" - learning_rate: float = 1e-5 + cache_dir: Optional[str] = None + epochs: int = 10 + optimizer: Literal["sgd", "adamw", "adam"] = "adamw" + lr: float = 1e-5 lr_scheduler: Literal["linear", "cosine", "polynomial"] = "linear" - train_batch_size: int = 4 - test_batch_size: Optional[int] = None - loaders_workers: int = 0 - test_loaders_workers: Optional[int] = None + batch_size: int = 4 + val_batch_size: Optional[int] = None + num_workers: int = 0 + val_num_workers: Optional[int] = None lora_r: int = 8 lora_alpha: int = 8 lora_dropout: float = 0.05 bias: Literal["none", "all", "lora_only"] = "none" use_rslora: bool = True init_lora_weights: Union[bool, LoraInitLiteral] = "gaussian" - training_dir: str = "./training/florence-2" - num_samples_to_visualise: int = 64 + output_dir: str = "./training/florence-2" metrics: List[BaseMetric] = field(default_factory=list) -def train(configuration: TrainingConfiguration) -> None: +def train(config: TrainingConfiguration) -> None: make_it_reproducible(avoid_non_deterministic_algorithms=False) - training_run_dir = _establish_training_run_dir( - training_dir=configuration.training_dir, + run_dir = _establish_training_run_dir( + output_dir=config.output_dir, ) - configuration = replace( - configuration, - training_dir=training_run_dir, + config = replace( + config, + output_dir=run_dir, ) - checkpoint_manager = CheckpointManager(training_run_dir) + checkpoint_manager = CheckpointManager(run_dir) processor, model = load_model( - model_id_or_path=configuration.model_id_or_path, - revision=configuration.revision, - device=configuration.device, - cache_dir=configuration.transformers_cache_dir, + model_id_or_path=config.model_id, + revision=config.revision, + device=config.device, + cache_dir=config.cache_dir, ) train_loader, val_loader, test_loader = prepare_data_loaders( - dataset_location=configuration.dataset_location, - train_batch_size=configuration.train_batch_size, + dataset_location=config.dataset_path, + train_batch_size=config.batch_size, processor=processor, - device=configuration.device, - num_workers=configuration.loaders_workers, - test_loaders_workers=configuration.test_loaders_workers, + device=config.device, + num_workers=config.num_workers, + test_loaders_workers=config.val_num_workers, ) peft_model = prepare_peft_model( model=model, - r=configuration.lora_r, - lora_alpha=configuration.lora_alpha, - lora_dropout=configuration.lora_dropout, - bias=configuration.bias, - use_rslora=configuration.use_rslora, - init_lora_weights=configuration.init_lora_weights, - revision=configuration.revision, + r=config.lora_r, + lora_alpha=config.lora_alpha, + lora_dropout=config.lora_dropout, + bias=config.bias, + use_rslora=config.use_rslora, + init_lora_weights=config.init_lora_weights, + revision=config.revision, ) training_metrics_tracker = MetricsTracker.init(metrics=["loss"]) metrics = ["loss"] - for metric in configuration.metrics: + for metric in config.metrics: metrics += metric.describe() validation_metrics_tracker = MetricsTracker.init(metrics=metrics) @@ -97,7 +124,7 @@ def train(configuration: TrainingConfiguration) -> None: processor=processor, model=peft_model, data_loaders=(train_loader, val_loader), - configuration=configuration, + config=config, training_metrics_tracker=training_metrics_tracker, validation_metrics_tracker=validation_metrics_tracker, checkpoint_manager=checkpoint_manager @@ -106,13 +133,13 @@ def train(configuration: TrainingConfiguration) -> None: save_metric_plots( training_tracker=training_metrics_tracker, validation_tracker=validation_metrics_tracker, - output_dir=os.path.join(configuration.training_dir, "metrics"), + output_dir=os.path.join(config.output_dir, "metrics"), ) training_metrics_tracker.as_json( - output_dir=os.path.join(configuration.training_dir, "metrics"), + output_dir=os.path.join(config.output_dir, "metrics"), filename="training.json") validation_metrics_tracker.as_json( - output_dir=os.path.join(configuration.training_dir, "metrics"), + output_dir=os.path.join(config.output_dir, "metrics"), filename="validation.json") @@ -148,28 +175,28 @@ def run_training_loop( processor: AutoProcessor, model: PeftModel, data_loaders: Tuple[DataLoader, Optional[DataLoader]], - configuration: TrainingConfiguration, + config: TrainingConfiguration, training_metrics_tracker: MetricsTracker, validation_metrics_tracker: MetricsTracker, checkpoint_manager: CheckpointManager, ) -> None: train_loader, val_loader = data_loaders - optimizer = _get_optimizer(model=model, configuration=configuration) - total_num_training_steps = configuration.training_epochs * len(train_loader) + optimizer = _get_optimizer(model=model, config=config) + total_steps = config.epochs * len(train_loader) lr_scheduler = get_scheduler( - name=configuration.lr_scheduler, + name=config.lr_scheduler, optimizer=optimizer, num_warmup_steps=0, - num_training_steps=total_num_training_steps, + num_training_steps=total_steps, ) - for epoch in range(configuration.training_epochs): + for epoch in range(config.epochs): run_training_epoch( processor=processor, model=model, train_loader=train_loader, val_loader=val_loader, epoch_number=epoch + 1, - configuration=configuration, + config=config, optimizer=optimizer, lr_scheduler=lr_scheduler, training_metrics_tracker=training_metrics_tracker, @@ -184,7 +211,7 @@ def run_training_epoch( train_loader: DataLoader, val_loader: Optional[DataLoader], epoch_number: int, - configuration: TrainingConfiguration, + config: TrainingConfiguration, optimizer: Optimizer, lr_scheduler: LRScheduler, training_metrics_tracker: MetricsTracker, @@ -193,13 +220,13 @@ def run_training_epoch( ) -> None: model.train() training_losses: List[float] = [] - training_iterator = tqdm(train_loader, desc=f"Epoch {epoch_number}/{configuration.training_epochs}") + training_iterator = tqdm(train_loader, desc=f"Epoch {epoch_number}/{config.epochs}") for step_id, (inputs, answers) in enumerate(training_iterator): input_ids = inputs["input_ids"] pixel_values = inputs["pixel_values"] labels = processor.tokenizer( text=answers, return_tensors="pt", padding=True, return_token_type_ids=False - ).input_ids.to(configuration.device) + ).input_ids.to(config.device) outputs = model(input_ids=input_ids, pixel_values=pixel_values, labels=labels) loss = outputs.loss loss.backward() @@ -217,7 +244,7 @@ def run_training_epoch( last_100_losses = training_losses[-100:] loss_moving_average = sum(last_100_losses) / len(last_100_losses) if len(last_100_losses) > 0 else 0.0 training_iterator.set_description( - f"Epoch {epoch_number}/{configuration.training_epochs}. Loss: {round(loss_moving_average, 4)}" + f"Epoch {epoch_number}/{config.epochs}. Loss: {round(loss_moving_average, 4)}" ) if len(training_losses) > 0: avg_train_loss = sum(training_losses) / len(training_losses) @@ -230,7 +257,7 @@ def run_training_epoch( model=model, loader=val_loader, epoch_number=epoch_number, - configuration=configuration, + config=config, metrics_tracker=validation_metrics_tracker, ) @@ -243,7 +270,7 @@ def run_validation_epoch( processor: AutoProcessor, model: Union[PeftModel, AutoModelForCausalLM], loader: DataLoader, - configuration: TrainingConfiguration, + config: TrainingConfiguration, metrics_tracker: MetricsTracker, epoch_number: int ) -> None: @@ -257,7 +284,7 @@ def run_validation_epoch( return_tensors="pt", padding=True, return_token_type_ids=False - ).input_ids.to(configuration.device) + ).input_ids.to(config.device) outputs = model( input_ids=input_ids, pixel_values=pixel_values, @@ -277,12 +304,12 @@ def run_validation_epoch( dataset=loader.dataset, processor=processor, model=model, - device=configuration.device, + device=config.device, ) metrics_results = {"loss": avg_val_loss} - for metric in configuration.metrics: + for metric in config.metrics: if isinstance(metric, MeanAveragePrecisionMetric): classes = extract_unique_detection_dataset_classes(loader.dataset) targets, predictions = postprocess_florence2_output_for_mean_average_precision( @@ -308,19 +335,22 @@ def run_validation_epoch( display_results(prompts, expected_responses, generated_texts, images) -def _establish_training_run_dir(training_dir: str) -> str: - training_dir = os.path.abspath(training_dir) - existing_directory_entries = glob(os.path.join(training_dir, "*")) +def _establish_training_run_dir(output_dir: str) -> str: + output_dir = os.path.abspath(output_dir) + existing_directory_entries = glob(os.path.join(output_dir, "*")) subdirectories = [path for path in existing_directory_entries if os.path.isdir(path)] run_id = len(subdirectories) + 1 - training_run_dir = os.path.join(training_dir, str(run_id)) - os.makedirs(training_run_dir, exist_ok=True) - return training_run_dir + run_dir = os.path.join(output_dir, str(run_id)) + os.makedirs(run_dir, exist_ok=True) + return run_dir -def _get_optimizer(model: PeftModel, configuration: TrainingConfiguration) -> Optimizer: - if configuration.optimiser == "adamw": - return AdamW(model.parameters(), lr=configuration.learning_rate) - if configuration.optimiser == "adam": - return Adam(model.parameters(), lr=configuration.learning_rate) - return SGD(model.parameters(), lr=configuration.learning_rate) +def _get_optimizer(model: PeftModel, config: TrainingConfiguration) -> Optimizer: + optimizer_type = config.optimizer.lower() + if optimizer_type == "adamw": + return AdamW(model.parameters(), lr=config.lr) + if optimizer_type == "adam": + return Adam(model.parameters(), lr=config.lr) + if optimizer_type == "sgd": + return SGD(model.parameters(), lr=config.lr) + raise ValueError(f"Unsupported optimizer: {config.optimizer}") From 672f27e4397112043cfefab40d3d6eef82e9a117 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 16:52:48 +0200 Subject: [PATCH 55/69] final tests before plugging in CLI --- maestro/trainer/common/utils/file_system.py | 19 +++ maestro/trainer/models/florence_2/__init__.py | 2 + .../florence_2/{training.py => core.py} | 93 +++++----- .../trainer/models/florence_2/entrypoint.py | 14 +- .../trainer/models/florence_2/inference.py | 159 ------------------ maestro/trainer/models/florence_2/metrics.py | 3 +- 6 files changed, 73 insertions(+), 217 deletions(-) rename maestro/trainer/models/florence_2/{training.py => core.py} (85%) delete mode 100644 maestro/trainer/models/florence_2/inference.py diff --git a/maestro/trainer/common/utils/file_system.py b/maestro/trainer/common/utils/file_system.py index e258f25..da8e5a9 100644 --- a/maestro/trainer/common/utils/file_system.py +++ b/maestro/trainer/common/utils/file_system.py @@ -1,5 +1,6 @@ import json import os +from glob import glob from typing import Union, List @@ -38,3 +39,21 @@ def save_json(path: str, content: dict) -> None: def ensure_parent_dir_exists(path: str) -> None: parent_dir = os.path.dirname(os.path.abspath(path)) os.makedirs(parent_dir, exist_ok=True) + + +def create_new_run_directory(base_output_dir: str) -> str: + """ + Creates a new numbered directory for the current training run. + + Args: + base_output_dir (str): The base directory where all run directories are stored. + + Returns: + str: The path to the newly created run directory. + """ + base_output_dir = os.path.abspath(base_output_dir) + existing_run_dirs = [d for d in glob(os.path.join(base_output_dir, "*")) if os.path.isdir(d)] + new_run_number = len(existing_run_dirs) + 1 + new_run_dir = os.path.join(base_output_dir, str(new_run_number)) + os.makedirs(new_run_dir, exist_ok=True) + return new_run_dir diff --git a/maestro/trainer/models/florence_2/__init__.py b/maestro/trainer/models/florence_2/__init__.py index e69de29..836df5c 100644 --- a/maestro/trainer/models/florence_2/__init__.py +++ b/maestro/trainer/models/florence_2/__init__.py @@ -0,0 +1,2 @@ +from maestro.trainer.models.florence_2.core import TrainingConfiguration, train +from maestro.trainer.models.florence_2.metrics import MeanAveragePrecisionMetric diff --git a/maestro/trainer/models/florence_2/training.py b/maestro/trainer/models/florence_2/core.py similarity index 85% rename from maestro/trainer/models/florence_2/training.py rename to maestro/trainer/models/florence_2/core.py index 1885a9f..46ea75b 100644 --- a/maestro/trainer/models/florence_2/training.py +++ b/maestro/trainer/models/florence_2/core.py @@ -1,6 +1,5 @@ import os from dataclasses import dataclass, field, replace -from glob import glob from typing import List, Literal, Optional, Tuple, Union import torch @@ -11,6 +10,7 @@ from tqdm import tqdm from transformers import AutoModelForCausalLM, AutoProcessor, get_scheduler +from maestro.trainer.common.utils.file_system import create_new_run_directory from maestro.trainer.common.utils.metrics import BaseMetric, MetricsTracker, \ display_results, save_metric_plots from maestro.trainer.common.utils.reproducibility import make_it_reproducible @@ -81,8 +81,8 @@ class TrainingConfiguration: def train(config: TrainingConfiguration) -> None: make_it_reproducible(avoid_non_deterministic_algorithms=False) - run_dir = _establish_training_run_dir( - output_dir=config.output_dir, + run_dir = create_new_run_directory( + base_output_dir=config.output_dir, ) config = replace( config, @@ -181,7 +181,7 @@ def run_training_loop( checkpoint_manager: CheckpointManager, ) -> None: train_loader, val_loader = data_loaders - optimizer = _get_optimizer(model=model, config=config) + optimizer = get_optimizer(model=model, config=config) total_steps = config.epochs * len(train_loader) lr_scheduler = get_scheduler( name=config.lr_scheduler, @@ -195,7 +195,7 @@ def run_training_loop( model=model, train_loader=train_loader, val_loader=val_loader, - epoch_number=epoch + 1, + epoch=epoch + 1, config=config, optimizer=optimizer, lr_scheduler=lr_scheduler, @@ -210,7 +210,7 @@ def run_training_epoch( model: PeftModel, train_loader: DataLoader, val_loader: Optional[DataLoader], - epoch_number: int, + epoch: int, config: TrainingConfiguration, optimizer: Optimizer, lr_scheduler: LRScheduler, @@ -220,43 +220,50 @@ def run_training_epoch( ) -> None: model.train() training_losses: List[float] = [] - training_iterator = tqdm(train_loader, desc=f"Epoch {epoch_number}/{config.epochs}") - for step_id, (inputs, answers) in enumerate(training_iterator): - input_ids = inputs["input_ids"] - pixel_values = inputs["pixel_values"] - labels = processor.tokenizer( - text=answers, return_tensors="pt", padding=True, return_token_type_ids=False - ).input_ids.to(config.device) - outputs = model(input_ids=input_ids, pixel_values=pixel_values, labels=labels) - loss = outputs.loss - loss.backward() - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - loss = loss.item() - training_metrics_tracker.register( - metric="loss", - epoch=epoch_number, - step=step_id + 1, - value=loss, - ) - training_losses.append(loss) - last_100_losses = training_losses[-100:] - loss_moving_average = sum(last_100_losses) / len(last_100_losses) if len(last_100_losses) > 0 else 0.0 - training_iterator.set_description( - f"Epoch {epoch_number}/{config.epochs}. Loss: {round(loss_moving_average, 4)}" - ) - if len(training_losses) > 0: - avg_train_loss = sum(training_losses) / len(training_losses) - print(f"Average Training Loss: {avg_train_loss}") + + with tqdm(total=len(train_loader), desc=f"Epoch {epoch}/{config.epochs}", unit="batch") as pbar: + for step_id, (inputs, answers) in enumerate(train_loader): + input_ids = inputs["input_ids"] + pixel_values = inputs["pixel_values"] + labels = processor.tokenizer( + text=answers, + return_tensors="pt", + padding=True, + return_token_type_ids=False + ).input_ids.to(config.device) + outputs = model(input_ids=input_ids, pixel_values=pixel_values, labels=labels) + loss = outputs.loss + loss.backward() + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + loss = loss.item() + training_metrics_tracker.register( + metric="loss", + epoch=epoch, + step=step_id + 1, + value=loss, + ) + training_losses.append(loss) + + # Update progress bar + last_100_losses = training_losses[-100:] + loss_moving_average = sum(last_100_losses) / len(last_100_losses) if last_100_losses else 0.0 + pbar.set_postfix({"Loss": f"{loss_moving_average:.4f}"}) + pbar.update(1) + + # Save checkpoints based on training loss if no validation loader if val_loader is None or len(val_loader) == 0: - return None + train_loss = sum(training_losses) / len(training_losses) + checkpoint_manager.save_latest(processor, model) + checkpoint_manager.save_best(processor, model, train_loss) + return run_validation_epoch( processor=processor, model=model, loader=val_loader, - epoch_number=epoch_number, + epoch_number=epoch, config=config, metrics_tracker=validation_metrics_tracker, ) @@ -335,17 +342,7 @@ def run_validation_epoch( display_results(prompts, expected_responses, generated_texts, images) -def _establish_training_run_dir(output_dir: str) -> str: - output_dir = os.path.abspath(output_dir) - existing_directory_entries = glob(os.path.join(output_dir, "*")) - subdirectories = [path for path in existing_directory_entries if os.path.isdir(path)] - run_id = len(subdirectories) + 1 - run_dir = os.path.join(output_dir, str(run_id)) - os.makedirs(run_dir, exist_ok=True) - return run_dir - - -def _get_optimizer(model: PeftModel, config: TrainingConfiguration) -> Optimizer: +def get_optimizer(model: PeftModel, config: TrainingConfiguration) -> Optimizer: optimizer_type = config.optimizer.lower() if optimizer_type == "adamw": return AdamW(model.parameters(), lr=config.lr) diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py index 09c2d87..fd7f7b7 100644 --- a/maestro/trainer/models/florence_2/entrypoint.py +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -1,18 +1,14 @@ import dataclasses from typing import Optional, Annotated +import rich import torch import typer -import rich - -from maestro.trainer.models.florence_2.entities import ( - TrainingConfiguration, - DEFAULT_FLORENCE2_MODEL_ID, - DEFAULT_FLORENCE2_MODEL_REVISION, - DEVICE, -) -from maestro.trainer.models.florence_2.training import train as train_fun +from maestro.trainer.models.florence_2.checkpoints import DEFAULT_FLORENCE2_MODEL_ID, \ + DEFAULT_FLORENCE2_MODEL_REVISION, DEVICE +from maestro.trainer.models.florence_2.core import TrainingConfiguration +from maestro.trainer.models.florence_2.core import train as train_fun florence_2_app = typer.Typer(help="Fine-tune and evaluate Florence 2 model") diff --git a/maestro/trainer/models/florence_2/inference.py b/maestro/trainer/models/florence_2/inference.py deleted file mode 100644 index 4f0c154..0000000 --- a/maestro/trainer/models/florence_2/inference.py +++ /dev/null @@ -1,159 +0,0 @@ -from typing import Literal, Optional, Union - -import numpy as np -import supervision as sv - -from PIL import Image -from transformers import AutoProcessor, AutoModelForCausalLM - - -def caption_image( - image: Image.Image, - processor: AutoProcessor, - model: AutoModelForCausalLM, - task: Literal["", "", "", ""], - prompt: Optional[str] = None, - max_new_tokens: int = 1024, - do_sample: bool = False, - num_beams: int = 3, -) -> str: - prompt = _pre_process_prompt(image=image, task=task, prompt=prompt) - model_device = model.device - inputs = processor(text=prompt, images=image, return_tensors="pt").to(model_device) - generated_ids = model.generate( - input_ids=inputs["input_ids"], - pixel_values=inputs["pixel_values"], - max_new_tokens=max_new_tokens, - do_sample=do_sample, - num_beams=num_beams, - ) - generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0] - response = processor.post_process_generation(generated_text, task=task, image_size=(image.width, image.height)) - return response[task] - - -TASKS_THAT_REQUIRE_PROMPT = { - "", - "", - "", - "", - "", - "", -} - - -def segment_objects( - image: Image.Image, - processor: AutoProcessor, - model: AutoModelForCausalLM, - task: Literal[ - "", - "", - ], - prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, - max_new_tokens: int = 1024, - do_sample: bool = False, - num_beams: int = 3, -) -> sv.Detections: - return _prompt_and_retrieve_detections( - image=image, - processor=processor, - model=model, - task=task, - prompt=prompt, - max_new_tokens=max_new_tokens, - do_sample=do_sample, - num_beams=num_beams, - ) - - -def detect_objects( - image: Image.Image, - processor: AutoProcessor, - model: AutoModelForCausalLM, - task: Literal[ - "", - "", - "", - "", - "", - "", - "", - ], - prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, - max_new_tokens: int = 1024, - do_sample: bool = False, - num_beams: int = 3, -) -> sv.Detections: - return _prompt_and_retrieve_detections( - image=image, - processor=processor, - model=model, - task=task, - prompt=prompt, - max_new_tokens=max_new_tokens, - do_sample=do_sample, - num_beams=num_beams, - ) - - -def _prompt_and_retrieve_detections( - image: Image.Image, - processor: AutoProcessor, - model: AutoModelForCausalLM, - task: Literal[ - "", - "", - "", - "", - "", - "", - "", - "", - "", - ], - prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, - max_new_tokens: int = 1024, - do_sample: bool = False, - num_beams: int = 3, -) -> sv.Detections: - prompt = _pre_process_prompt(image=image, task=task, prompt=prompt) - model_device = model.device - inputs = processor(text=prompt, images=image, return_tensors="pt").to(model_device) - generated_ids = model.generate( - input_ids=inputs["input_ids"], - pixel_values=inputs["pixel_values"], - max_new_tokens=max_new_tokens, - do_sample=do_sample, - num_beams=num_beams, - ) - generated_text = processor.batch_decode(generated_ids, skip_special_tokens=False)[0] - response = processor.post_process_generation( - generated_text, - task=task, - image_size=(image.width, image.height), - ) - return sv.Detections.from_lmm( - lmm=sv.LMM.FLORENCE_2, - result=response, - resolution_wh=image.size, - ) - - -def _pre_process_prompt( - image: Image.Image, - task: str, - prompt: Optional[Union[str, tuple, list, np.ndarray]] = None, -) -> str: - if prompt is None: - if task in TASKS_THAT_REQUIRE_PROMPT: - raise ValueError(f"Task {task} requires prompt") - return task - if isinstance(prompt, tuple) or isinstance(prompt, list) or isinstance(prompt, np.ndarray): - if len(prompt) != 4: - raise ValueError("Expected sequence of 4 elements describing (x_min, y_min, x_max, y_max)") - x_min, y_min, x_max, y_max = prompt - x_min, x_max = round((x_min / image.width) * 1000), round((x_max / image.width) * 1000) - y_min, y_max = round((y_min / image.height) * 1000), round((y_max / image.height) * 1000) - return f"{task} " - return f"{task} {prompt}" diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index ae01b38..117e031 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -50,7 +50,8 @@ def postprocess_florence2_output_for_mean_average_precision( prediction = sv.Detections.from_lmm(sv.LMM.FLORENCE_2, prediction, resolution_wh=image.size) prediction = prediction[np.isin(prediction["class_name"], classes)] prediction.class_id = np.array([classes.index(class_name) for class_name in prediction["class_name"]]) - prediction.confidence = np.ones(len(prediction)) # Set confidence for mean average precision calculation + # Set confidence for mean average precision calculation + prediction.confidence = np.ones(len(prediction)) # Postprocess target for mean average precision calculation target = processor.post_process_generation(suffix, task="", image_size=image.size) From 4a339a43c6b8c8bae89ecefdecc903981faed31a Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 17:49:40 +0200 Subject: [PATCH 56/69] initial tests of CLI mode --- maestro/cli/__init__.py | 5 + maestro/cli/env.py | 4 +- maestro/cli/introspection.py | 56 +-- maestro/cli/main.py | 18 +- maestro/cli/utils.py | 4 +- .../trainer/models/florence_2/entrypoint.py | 346 ++++++++++-------- 6 files changed, 247 insertions(+), 186 deletions(-) diff --git a/maestro/cli/__init__.py b/maestro/cli/__init__.py index e69de29..d627b18 100644 --- a/maestro/cli/__init__.py +++ b/maestro/cli/__init__.py @@ -0,0 +1,5 @@ +import typer +from maestro.trainer.models.florence_2.entrypoint import app as florence2_app + +app = typer.Typer() +app.add_typer(florence2_app, name="florence2") diff --git a/maestro/cli/env.py b/maestro/cli/env.py index b95525e..5322a5d 100644 --- a/maestro/cli/env.py +++ b/maestro/cli/env.py @@ -1,2 +1,2 @@ -DISABLE_RECIPE_IMPORTS_WARNINGS_ENV = "DISABLE_RECIPE_IMPORTS_WARNINGS" -DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV = "False" +# DISABLE_RECIPE_IMPORTS_WARNINGS_ENV = "DISABLE_RECIPE_IMPORTS_WARNINGS" +# DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV = "False" diff --git a/maestro/cli/introspection.py b/maestro/cli/introspection.py index fc6aef9..6223144 100644 --- a/maestro/cli/introspection.py +++ b/maestro/cli/introspection.py @@ -1,37 +1,37 @@ -import os +# import os -import typer +# import typer -from maestro.cli.env import DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, \ - DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV -from maestro.cli.utils import str2bool +# from maestro.cli.env import DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, \ +# DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV +# from maestro.cli.utils import str2bool -def find_training_recipes(app: typer.Typer) -> None: - try: - from maestro.trainer.models.florence_2.entrypoint import florence_2_app +# def find_training_recipes(app: typer.Typer) -> None: +# try: +# from maestro.trainer.models.florence_2.entrypoint import florence_2_app - app.add_typer(florence_2_app, name="florence2") - except Exception: - _warn_about_recipe_import_error(model_name="Florence 2") +# app.add_typer(florence_2_app, name="florence2") +# except Exception: +# _warn_about_recipe_import_error(model_name="Florence 2") - try: - from maestro.trainer.models.paligemma.entrypoint import paligemma_app +# try: +# from maestro.trainer.models.paligemma.entrypoint import paligemma_app - app.add_typer(paligemma_app, name="paligemma") - except Exception: - _warn_about_recipe_import_error(model_name="PaliGemma") +# app.add_typer(paligemma_app, name="paligemma") +# except Exception: +# _warn_about_recipe_import_error(model_name="PaliGemma") -def _warn_about_recipe_import_error(model_name: str) -> None: - disable_warnings = str2bool( - os.getenv( - DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, - DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, - ) - ) - if disable_warnings: - return None - warning = typer.style("WARNING", fg=typer.colors.RED, bold=True) - message = "🚧 " + warning + f" cannot import recipe for {model_name}" - typer.echo(message) +# def _warn_about_recipe_import_error(model_name: str) -> None: +# disable_warnings = str2bool( +# os.getenv( +# DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, +# DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, +# ) +# ) +# if disable_warnings: +# return None +# warning = typer.style("WARNING", fg=typer.colors.RED, bold=True) +# message = "🚧 " + warning + f" cannot import recipe for {model_name}" +# typer.echo(message) diff --git a/maestro/cli/main.py b/maestro/cli/main.py index b600e3a..9212e72 100644 --- a/maestro/cli/main.py +++ b/maestro/cli/main.py @@ -1,15 +1,15 @@ -import typer +# import typer -from maestro.cli.introspection import find_training_recipes +# from maestro.cli.introspection import find_training_recipes -app = typer.Typer() -find_training_recipes(app=app) +# app = typer.Typer() +# find_training_recipes(app=app) -@app.command(help="Display information about maestro") -def info(): - typer.echo("Welcome to maestro CLI. Let's train some VLM! 🏋") +# @app.command(help="Display information about maestro") +# def info(): +# typer.echo("Welcome to maestro CLI. Let's train some VLM! 🏋") -if __name__ == "__main__": - app() +# if __name__ == "__main__": +# app() diff --git a/maestro/cli/utils.py b/maestro/cli/utils.py index 0751fef..6d31bd9 100644 --- a/maestro/cli/utils.py +++ b/maestro/cli/utils.py @@ -1,2 +1,2 @@ -def str2bool(value: str) -> bool: - return value.lower() in {"y", "t", "yes", "true"} +# def str2bool(value: str) -> bool: +# return value.lower() in {"y", "t", "yes", "true"} diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py index fd7f7b7..80c931f 100644 --- a/maestro/trainer/models/florence_2/entrypoint.py +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -1,148 +1,204 @@ -import dataclasses -from typing import Optional, Annotated +# import dataclasses +# from typing import Optional, Annotated + +# import rich +# import torch +# import typer + +# from maestro.trainer.models.florence_2.checkpoints import DEFAULT_FLORENCE2_MODEL_ID, \ +# DEFAULT_FLORENCE2_MODEL_REVISION, DEVICE +# from maestro.trainer.models.florence_2.core import TrainingConfiguration +# from maestro.trainer.models.florence_2.core import train as train_fun + +# florence_2_app = typer.Typer(help="Fine-tune and evaluate Florence 2 model") + + +# @florence_2_app.command( +# help="Train Florence 2 model", context_settings={"allow_extra_args": True, "ignore_unknown_options": True} +# ) +# def train( +# dataset_location: Annotated[ +# str, +# typer.Option("--dataset_location", help="Path to directory with dataset"), +# ], +# model_id_or_path: Annotated[ +# str, +# typer.Option("--model_id_or_path", help="Model to be used or path to your checkpoint"), +# ] = DEFAULT_FLORENCE2_MODEL_ID, +# revision: Annotated[ +# str, +# typer.Option("--revision", help="Revision of Florence2 HF repository"), +# ] = DEFAULT_FLORENCE2_MODEL_REVISION, +# device: Annotated[ +# str, +# typer.Option("--device", help="CUDA device ID to be used (in format: 'cuda:0')"), +# ] = DEVICE, +# transformers_cache_dir: Annotated[ +# Optional[str], +# typer.Option("--transformers_cache_dir", help="Cache dir for HF weights"), +# ] = None, +# training_epochs: Annotated[ +# int, +# typer.Option("--training_epochs", help="Number of training epochs"), +# ] = 10, +# optimiser: Annotated[ +# str, +# typer.Option("--optimiser", help="Optimiser to be used"), +# ] = "adamw", +# learning_rate: Annotated[ +# float, +# typer.Option("--learning_rate", help="Learning rate"), +# ] = 1e-5, +# lr_scheduler: Annotated[ +# str, +# typer.Option("--lr_scheduler", help="LR scheduler"), +# ] = "linear", +# train_batch_size: Annotated[ +# int, +# typer.Option("--train_batch_size", help="Batch size for training"), +# ] = 4, +# test_batch_size: Annotated[ +# Optional[int], +# typer.Option( +# "--train_batch_size", help="Batch size for validation and test. If not given - train will be used." +# ), +# ] = None, +# loaders_workers: Annotated[ +# int, +# typer.Option("--loaders_workers", help="Number of loaders workers. 0 = # of CPU"), +# ] = 0, +# test_loaders_workers: Annotated[ +# Optional[int], +# typer.Option( +# "--test_loaders_workers", +# help="Number of workers for test and val loaders. If not given - train will be used.", +# ), +# ] = None, +# lora_r: Annotated[ +# int, +# typer.Option("--lora_r", help="Value of Lora R"), +# ] = 8, +# lora_alpha: Annotated[ +# int, +# typer.Option("--lora_alpha", help="Value of Lora Alpha"), +# ] = 8, +# lora_dropout: Annotated[ +# float, +# typer.Option("--lora_dropout", help="Value of Lora Dropout"), +# ] = 0.05, +# bias: Annotated[ +# str, +# typer.Option("--bias", help="Value of Lora Bias"), +# ] = "none", +# use_rslora: Annotated[ +# bool, +# typer.Option( +# "--use_rslora/--no_use_rslora", +# help="Boolean flag to decide if rslora to be used", +# ), +# ] = True, +# init_lora_weights: Annotated[ +# str, +# typer.Option("--init_lora_weights", help="Lora weights initialisation"), +# ] = "gaussian", +# training_dir: Annotated[ +# str, +# typer.Option("--training_dir", help="Path to directory where training outputs should be preserved"), +# ] = "./training/florence-2", +# max_checkpoints_to_keep: Annotated[ +# int, +# typer.Option("--max_checkpoints_to_keep", help="Max checkpoints to keep"), +# ] = 3, +# num_samples_to_visualise: Annotated[ +# int, +# typer.Option("--num_samples_to_visualise", help="Number of samples to visualise"), +# ] = 64, +# ) -> None: +# configuration = TrainingConfiguration( +# dataset_location=dataset_location, +# model_id_or_path=model_id_or_path, +# revision=revision, +# device=torch.device(device), +# transformers_cache_dir=transformers_cache_dir, +# training_epochs=training_epochs, +# optimiser=optimiser, # type: ignore +# learning_rate=learning_rate, +# lr_scheduler=lr_scheduler, # type: ignore +# train_batch_size=train_batch_size, +# test_batch_size=test_batch_size, +# loaders_workers=loaders_workers, +# test_loaders_workers=test_loaders_workers, +# lora_r=lora_r, +# lora_alpha=lora_alpha, +# lora_dropout=lora_dropout, +# bias=bias, # type: ignore +# use_rslora=use_rslora, +# init_lora_weights=init_lora_weights, # type: ignore +# training_dir=training_dir, +# max_checkpoints_to_keep=max_checkpoints_to_keep, +# num_samples_to_visualise=num_samples_to_visualise, +# ) +# typer.echo(typer.style("Training configuration", fg=typer.colors.BRIGHT_GREEN, bold=True)) +# rich.print(dataclasses.asdict(configuration)) +# train_fun(configuration=configuration) + + +# @florence_2_app.command(help="Evaluate Florence 2 model") +# def evaluate() -> None: +# pass -import rich -import torch import typer +from typing import get_type_hints, Optional, Union, Literal +from maestro.trainer.models.florence_2.core import TrainingConfiguration, train + +app = typer.Typer() + + +def create_dynamic_cli_options(config_class): + hints = get_type_hints(config_class) + options = {} + + for field_name, field_type in hints.items(): + if field_name == 'metrics': # Skip complex types like metrics + continue + + if field_type == bool: + options[field_name] = typer.Option(None, help=f"{field_name} parameter") + elif field_type in (int, float, str): + options[field_name] = typer.Option(None, help=f"{field_name} parameter") + elif getattr(field_type, "__origin__", None) == Union: + if type(None) in field_type.__args__: + options[field_name] = typer.Option(None, help=f"{field_name} parameter") + elif getattr(field_type, "__origin__", None) == Literal: + options[field_name] = typer.Option(None, help=f"{field_name} parameter") + + return options + + +dynamic_options = create_dynamic_cli_options(TrainingConfiguration) + + +@app.command() +def florence2( + mode: str = typer.Option(..., help="Mode: 'train' or 'eval'"), + **dynamic_options +): + """Train or evaluate a Florence-2 model.""" + + # Filter out None values + config_overrides = {k: v for k, v in dynamic_options.items() if v is not None} + + # Create configuration with overrides + config = TrainingConfiguration(**config_overrides) + + if mode == "train": + train(config) + elif mode == "eval": + typer.echo("Evaluation not implemented yet.") + else: + typer.echo(f"Invalid mode: {mode}. Use 'train' or 'eval'.") + raise typer.Exit(code=1) + -from maestro.trainer.models.florence_2.checkpoints import DEFAULT_FLORENCE2_MODEL_ID, \ - DEFAULT_FLORENCE2_MODEL_REVISION, DEVICE -from maestro.trainer.models.florence_2.core import TrainingConfiguration -from maestro.trainer.models.florence_2.core import train as train_fun - -florence_2_app = typer.Typer(help="Fine-tune and evaluate Florence 2 model") - - -@florence_2_app.command( - help="Train Florence 2 model", context_settings={"allow_extra_args": True, "ignore_unknown_options": True} -) -def train( - dataset_location: Annotated[ - str, - typer.Option("--dataset_location", help="Path to directory with dataset"), - ], - model_id_or_path: Annotated[ - str, - typer.Option("--model_id_or_path", help="Model to be used or path to your checkpoint"), - ] = DEFAULT_FLORENCE2_MODEL_ID, - revision: Annotated[ - str, - typer.Option("--revision", help="Revision of Florence2 HF repository"), - ] = DEFAULT_FLORENCE2_MODEL_REVISION, - device: Annotated[ - str, - typer.Option("--device", help="CUDA device ID to be used (in format: 'cuda:0')"), - ] = DEVICE, - transformers_cache_dir: Annotated[ - Optional[str], - typer.Option("--transformers_cache_dir", help="Cache dir for HF weights"), - ] = None, - training_epochs: Annotated[ - int, - typer.Option("--training_epochs", help="Number of training epochs"), - ] = 10, - optimiser: Annotated[ - str, - typer.Option("--optimiser", help="Optimiser to be used"), - ] = "adamw", - learning_rate: Annotated[ - float, - typer.Option("--learning_rate", help="Learning rate"), - ] = 1e-5, - lr_scheduler: Annotated[ - str, - typer.Option("--lr_scheduler", help="LR scheduler"), - ] = "linear", - train_batch_size: Annotated[ - int, - typer.Option("--train_batch_size", help="Batch size for training"), - ] = 4, - test_batch_size: Annotated[ - Optional[int], - typer.Option( - "--train_batch_size", help="Batch size for validation and test. If not given - train will be used." - ), - ] = None, - loaders_workers: Annotated[ - int, - typer.Option("--loaders_workers", help="Number of loaders workers. 0 = # of CPU"), - ] = 0, - test_loaders_workers: Annotated[ - Optional[int], - typer.Option( - "--test_loaders_workers", - help="Number of workers for test and val loaders. If not given - train will be used.", - ), - ] = None, - lora_r: Annotated[ - int, - typer.Option("--lora_r", help="Value of Lora R"), - ] = 8, - lora_alpha: Annotated[ - int, - typer.Option("--lora_alpha", help="Value of Lora Alpha"), - ] = 8, - lora_dropout: Annotated[ - float, - typer.Option("--lora_dropout", help="Value of Lora Dropout"), - ] = 0.05, - bias: Annotated[ - str, - typer.Option("--bias", help="Value of Lora Bias"), - ] = "none", - use_rslora: Annotated[ - bool, - typer.Option( - "--use_rslora/--no_use_rslora", - help="Boolean flag to decide if rslora to be used", - ), - ] = True, - init_lora_weights: Annotated[ - str, - typer.Option("--init_lora_weights", help="Lora weights initialisation"), - ] = "gaussian", - training_dir: Annotated[ - str, - typer.Option("--training_dir", help="Path to directory where training outputs should be preserved"), - ] = "./training/florence-2", - max_checkpoints_to_keep: Annotated[ - int, - typer.Option("--max_checkpoints_to_keep", help="Max checkpoints to keep"), - ] = 3, - num_samples_to_visualise: Annotated[ - int, - typer.Option("--num_samples_to_visualise", help="Number of samples to visualise"), - ] = 64, -) -> None: - configuration = TrainingConfiguration( - dataset_location=dataset_location, - model_id_or_path=model_id_or_path, - revision=revision, - device=torch.device(device), - transformers_cache_dir=transformers_cache_dir, - training_epochs=training_epochs, - optimiser=optimiser, # type: ignore - learning_rate=learning_rate, - lr_scheduler=lr_scheduler, # type: ignore - train_batch_size=train_batch_size, - test_batch_size=test_batch_size, - loaders_workers=loaders_workers, - test_loaders_workers=test_loaders_workers, - lora_r=lora_r, - lora_alpha=lora_alpha, - lora_dropout=lora_dropout, - bias=bias, # type: ignore - use_rslora=use_rslora, - init_lora_weights=init_lora_weights, # type: ignore - training_dir=training_dir, - max_checkpoints_to_keep=max_checkpoints_to_keep, - num_samples_to_visualise=num_samples_to_visualise, - ) - typer.echo(typer.style("Training configuration", fg=typer.colors.BRIGHT_GREEN, bold=True)) - rich.print(dataclasses.asdict(configuration)) - train_fun(configuration=configuration) - - -@florence_2_app.command(help="Evaluate Florence 2 model") -def evaluate() -> None: - pass +if __name__ == "__main__": + app() From c7c63b7ffb92557132c032598ea3b81cf6075d82 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 18:30:16 +0200 Subject: [PATCH 57/69] fix --- maestro/cli/__init__.py | 4 ---- maestro/cli/main.py | 9 +++++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/maestro/cli/__init__.py b/maestro/cli/__init__.py index d627b18..8b13789 100644 --- a/maestro/cli/__init__.py +++ b/maestro/cli/__init__.py @@ -1,5 +1 @@ -import typer -from maestro.trainer.models.florence_2.entrypoint import app as florence2_app -app = typer.Typer() -app.add_typer(florence2_app, name="florence2") diff --git a/maestro/cli/main.py b/maestro/cli/main.py index 9212e72..5481b24 100644 --- a/maestro/cli/main.py +++ b/maestro/cli/main.py @@ -13,3 +13,12 @@ # if __name__ == "__main__": # app() + +import typer +from maestro.trainer.models.florence_2.entrypoint import app as florence2_app + +app = typer.Typer() +app.add_typer(florence2_app, name="florence2") + +if __name__ == "__main__": + app() From 5cc422014ea9746bde67b1d1434dcc4c3ffdd5fe Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 19:15:13 +0200 Subject: [PATCH 58/69] fix `No such option: --mode ` --- .../trainer/models/florence_2/entrypoint.py | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py index 80c931f..284ebb6 100644 --- a/maestro/trainer/models/florence_2/entrypoint.py +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -148,8 +148,8 @@ # pass import typer -from typing import get_type_hints, Optional, Union, Literal -from maestro.trainer.models.florence_2.core import TrainingConfiguration, train +from typing import get_type_hints, Union, Literal +from maestro.trainer.models.florence_2.core import TrainingConfiguration, train as train_florence2 app = typer.Typer() @@ -179,25 +179,21 @@ def create_dynamic_cli_options(config_class): @app.command() -def florence2( - mode: str = typer.Option(..., help="Mode: 'train' or 'eval'"), - **dynamic_options -): - """Train or evaluate a Florence-2 model.""" - +def train(**dynamic_options): + """Train a Florence-2 model.""" # Filter out None values config_overrides = {k: v for k, v in dynamic_options.items() if v is not None} # Create configuration with overrides config = TrainingConfiguration(**config_overrides) - if mode == "train": - train(config) - elif mode == "eval": - typer.echo("Evaluation not implemented yet.") - else: - typer.echo(f"Invalid mode: {mode}. Use 'train' or 'eval'.") - raise typer.Exit(code=1) + train_florence2(config) + + +@app.command() +def evaluate(**dynamic_options): + """Evaluate a Florence-2 model.""" + typer.echo("Evaluation not implemented yet.") if __name__ == "__main__": From 518323c2a6731a665ac83ba60491d09ec54bb7d2 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 19:21:03 +0200 Subject: [PATCH 59/69] fix 2 `No such option: --mode ` --- .../trainer/models/florence_2/entrypoint.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py index 284ebb6..da811e6 100644 --- a/maestro/trainer/models/florence_2/entrypoint.py +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -153,7 +153,6 @@ app = typer.Typer() - def create_dynamic_cli_options(config_class): hints = get_type_hints(config_class) options = {} @@ -174,11 +173,19 @@ def create_dynamic_cli_options(config_class): return options - dynamic_options = create_dynamic_cli_options(TrainingConfiguration) - @app.command() +def main(mode: str, **dynamic_options): + """Main entry point for Florence-2 model.""" + if mode == "train": + train(**dynamic_options) + elif mode == "evaluate": + evaluate(**dynamic_options) + else: + typer.echo(f"Unknown mode: {mode}") + raise typer.Exit(code=1) + def train(**dynamic_options): """Train a Florence-2 model.""" # Filter out None values @@ -189,12 +196,9 @@ def train(**dynamic_options): train_florence2(config) - -@app.command() def evaluate(**dynamic_options): """Evaluate a Florence-2 model.""" typer.echo("Evaluation not implemented yet.") - if __name__ == "__main__": - app() + app() \ No newline at end of file From fb212ead9e03c0842e3a83d0bd3bfe8d1d69f877 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 19:39:17 +0200 Subject: [PATCH 60/69] fix 3 `No such option: --mode ` --- .../trainer/models/florence_2/entrypoint.py | 81 ++++++++++++------- 1 file changed, 52 insertions(+), 29 deletions(-) diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py index da811e6..3778f72 100644 --- a/maestro/trainer/models/florence_2/entrypoint.py +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -148,55 +148,78 @@ # pass import typer -from typing import get_type_hints, Union, Literal +from typing import Optional, List, Union, Literal from maestro.trainer.models.florence_2.core import TrainingConfiguration, train as train_florence2 app = typer.Typer() -def create_dynamic_cli_options(config_class): - hints = get_type_hints(config_class) - options = {} - - for field_name, field_type in hints.items(): - if field_name == 'metrics': # Skip complex types like metrics - continue - - if field_type == bool: - options[field_name] = typer.Option(None, help=f"{field_name} parameter") - elif field_type in (int, float, str): - options[field_name] = typer.Option(None, help=f"{field_name} parameter") - elif getattr(field_type, "__origin__", None) == Union: - if type(None) in field_type.__args__: - options[field_name] = typer.Option(None, help=f"{field_name} parameter") - elif getattr(field_type, "__origin__", None) == Literal: - options[field_name] = typer.Option(None, help=f"{field_name} parameter") - - return options - -dynamic_options = create_dynamic_cli_options(TrainingConfiguration) - @app.command() -def main(mode: str, **dynamic_options): +def main( + mode: str = typer.Option(..., help="Mode to run: train or evaluate"), + dataset_path: str = typer.Option(..., help="Path to the dataset used for training"), + model_id: str = typer.Option(None, help="Identifier for the Florence-2 model"), + revision: str = typer.Option(None, help="Revision of the model to use"), + device: str = typer.Option(None, help="Device to use for training"), + cache_dir: Optional[str] = typer.Option(None, help="Directory to cache the model"), + epochs: int = typer.Option(10, help="Number of training epochs"), + optimizer: str = typer.Option("adamw", help="Optimizer to use for training"), + lr: float = typer.Option(1e-5, help="Learning rate for the optimizer"), + lr_scheduler: str = typer.Option("linear", help="Learning rate scheduler"), + batch_size: int = typer.Option(4, help="Batch size for training"), + val_batch_size: Optional[int] = typer.Option(None, help="Batch size for validation"), + num_workers: int = typer.Option(0, help="Number of workers for data loading"), + val_num_workers: Optional[int] = typer.Option(None, help="Number of workers for validation data loading"), + lora_r: int = typer.Option(8, help="Rank of the LoRA update matrices"), + lora_alpha: int = typer.Option(8, help="Scaling factor for the LoRA update"), + lora_dropout: float = typer.Option(0.05, help="Dropout probability for LoRA layers"), + bias: str = typer.Option("none", help="Which bias to train"), + use_rslora: bool = typer.Option(True, help="Whether to use RSLoRA"), + init_lora_weights: str = typer.Option("gaussian", help="How to initialize LoRA weights"), + output_dir: str = typer.Option("./training/florence-2", help="Directory to save output files"), + metrics: List[str] = typer.Option([], help="List of metrics to track during training") +): """Main entry point for Florence-2 model.""" if mode == "train": - train(**dynamic_options) + train( + dataset_path=dataset_path, + model_id=model_id, + revision=revision, + device=device, + cache_dir=cache_dir, + epochs=epochs, + optimizer=optimizer, + lr=lr, + lr_scheduler=lr_scheduler, + batch_size=batch_size, + val_batch_size=val_batch_size, + num_workers=num_workers, + val_num_workers=val_num_workers, + lora_r=lora_r, + lora_alpha=lora_alpha, + lora_dropout=lora_dropout, + bias=bias, + use_rslora=use_rslora, + init_lora_weights=init_lora_weights, + output_dir=output_dir, + metrics=metrics + ) elif mode == "evaluate": - evaluate(**dynamic_options) + evaluate() else: typer.echo(f"Unknown mode: {mode}") raise typer.Exit(code=1) -def train(**dynamic_options): +def train(**kwargs): """Train a Florence-2 model.""" # Filter out None values - config_overrides = {k: v for k, v in dynamic_options.items() if v is not None} + config_overrides = {k: v for k, v in kwargs.items() if v is not None} # Create configuration with overrides config = TrainingConfiguration(**config_overrides) train_florence2(config) -def evaluate(**dynamic_options): +def evaluate(): """Evaluate a Florence-2 model.""" typer.echo("Evaluation not implemented yet.") From f15b7a9134b2a9befc76ed31f184cdd236a6f59c Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 19:45:30 +0200 Subject: [PATCH 61/69] fix 4 `No such option: --mode ` --- maestro/trainer/models/florence_2/entrypoint.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py index 3778f72..92f83d5 100644 --- a/maestro/trainer/models/florence_2/entrypoint.py +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -148,12 +148,13 @@ # pass import typer -from typing import Optional, List, Union, Literal +from typing import Optional, List from maestro.trainer.models.florence_2.core import TrainingConfiguration, train as train_florence2 app = typer.Typer() -@app.command() + +@app.callback() def main( mode: str = typer.Option(..., help="Mode to run: train or evaluate"), dataset_path: str = typer.Option(..., help="Path to the dataset used for training"), @@ -209,6 +210,7 @@ def main( typer.echo(f"Unknown mode: {mode}") raise typer.Exit(code=1) + def train(**kwargs): """Train a Florence-2 model.""" # Filter out None values @@ -219,9 +221,11 @@ def train(**kwargs): train_florence2(config) + def evaluate(): """Evaluate a Florence-2 model.""" typer.echo("Evaluation not implemented yet.") + if __name__ == "__main__": app() \ No newline at end of file From 566d9ca773295f3d6711c197135a0106488bf8ab Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 19:51:54 +0200 Subject: [PATCH 62/69] fix 5 `No such option: --mode ` --- maestro/cli/main.py | 2 +- maestro/trainer/models/florence_2/entrypoint.py | 13 +++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/maestro/cli/main.py b/maestro/cli/main.py index 5481b24..3ef8500 100644 --- a/maestro/cli/main.py +++ b/maestro/cli/main.py @@ -15,7 +15,7 @@ # app() import typer -from maestro.trainer.models.florence_2.entrypoint import app as florence2_app +from maestro.trainer.models.florence_2.entrypoint import florence2_app app = typer.Typer() app.add_typer(florence2_app, name="florence2") diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py index 92f83d5..31ea38c 100644 --- a/maestro/trainer/models/florence_2/entrypoint.py +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -148,13 +148,12 @@ # pass import typer -from typing import Optional, List +from typing import Optional, List, Union, Literal from maestro.trainer.models.florence_2.core import TrainingConfiguration, train as train_florence2 -app = typer.Typer() +florence2_app = typer.Typer() - -@app.callback() +@florence2_app.command() def main( mode: str = typer.Option(..., help="Mode to run: train or evaluate"), dataset_path: str = typer.Option(..., help="Path to the dataset used for training"), @@ -210,7 +209,6 @@ def main( typer.echo(f"Unknown mode: {mode}") raise typer.Exit(code=1) - def train(**kwargs): """Train a Florence-2 model.""" # Filter out None values @@ -221,11 +219,6 @@ def train(**kwargs): train_florence2(config) - def evaluate(): """Evaluate a Florence-2 model.""" typer.echo("Evaluation not implemented yet.") - - -if __name__ == "__main__": - app() \ No newline at end of file From fb1c8268a66b78c68c2942dc4711e304e169915a Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 10 Sep 2024 19:57:14 +0200 Subject: [PATCH 63/69] fix 6 `No such option: --mode ` --- maestro/cli/main.py | 6 ++++-- maestro/trainer/models/florence_2/entrypoint.py | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/maestro/cli/main.py b/maestro/cli/main.py index 3ef8500..8e2edc9 100644 --- a/maestro/cli/main.py +++ b/maestro/cli/main.py @@ -15,10 +15,12 @@ # app() import typer -from maestro.trainer.models.florence_2.entrypoint import florence2_app +from maestro.trainer.models.florence_2.entrypoint import florence2 app = typer.Typer() -app.add_typer(florence2_app, name="florence2") + +# Add the florence2 command to the main app +app.command()(florence2) if __name__ == "__main__": app() diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py index 31ea38c..30204ce 100644 --- a/maestro/trainer/models/florence_2/entrypoint.py +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -151,10 +151,10 @@ from typing import Optional, List, Union, Literal from maestro.trainer.models.florence_2.core import TrainingConfiguration, train as train_florence2 -florence2_app = typer.Typer() +app = typer.Typer() -@florence2_app.command() -def main( +@app.command() +def florence2( mode: str = typer.Option(..., help="Mode to run: train or evaluate"), dataset_path: str = typer.Option(..., help="Path to the dataset used for training"), model_id: str = typer.Option(None, help="Identifier for the Florence-2 model"), @@ -222,3 +222,6 @@ def train(**kwargs): def evaluate(): """Evaluate a Florence-2 model.""" typer.echo("Evaluation not implemented yet.") + +if __name__ == "__main__": + app() From d556a884e4ca7da9d7cdd29984a9e2e9bb243aee Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 11 Sep 2024 00:01:21 +0200 Subject: [PATCH 64/69] bring back Pawel's code with improvements --- maestro/cli/env.py | 4 +- maestro/cli/introspection.py | 56 +-- maestro/cli/main.py | 25 +- maestro/cli/utils.py | 4 +- maestro/trainer/models/florence_2/core.py | 45 +-- .../trainer/models/florence_2/entrypoint.py | 353 +++++++----------- 6 files changed, 196 insertions(+), 291 deletions(-) diff --git a/maestro/cli/env.py b/maestro/cli/env.py index 5322a5d..b95525e 100644 --- a/maestro/cli/env.py +++ b/maestro/cli/env.py @@ -1,2 +1,2 @@ -# DISABLE_RECIPE_IMPORTS_WARNINGS_ENV = "DISABLE_RECIPE_IMPORTS_WARNINGS" -# DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV = "False" +DISABLE_RECIPE_IMPORTS_WARNINGS_ENV = "DISABLE_RECIPE_IMPORTS_WARNINGS" +DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV = "False" diff --git a/maestro/cli/introspection.py b/maestro/cli/introspection.py index 6223144..fc6aef9 100644 --- a/maestro/cli/introspection.py +++ b/maestro/cli/introspection.py @@ -1,37 +1,37 @@ -# import os +import os -# import typer +import typer -# from maestro.cli.env import DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, \ -# DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV -# from maestro.cli.utils import str2bool +from maestro.cli.env import DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, \ + DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV +from maestro.cli.utils import str2bool -# def find_training_recipes(app: typer.Typer) -> None: -# try: -# from maestro.trainer.models.florence_2.entrypoint import florence_2_app +def find_training_recipes(app: typer.Typer) -> None: + try: + from maestro.trainer.models.florence_2.entrypoint import florence_2_app -# app.add_typer(florence_2_app, name="florence2") -# except Exception: -# _warn_about_recipe_import_error(model_name="Florence 2") + app.add_typer(florence_2_app, name="florence2") + except Exception: + _warn_about_recipe_import_error(model_name="Florence 2") -# try: -# from maestro.trainer.models.paligemma.entrypoint import paligemma_app + try: + from maestro.trainer.models.paligemma.entrypoint import paligemma_app -# app.add_typer(paligemma_app, name="paligemma") -# except Exception: -# _warn_about_recipe_import_error(model_name="PaliGemma") + app.add_typer(paligemma_app, name="paligemma") + except Exception: + _warn_about_recipe_import_error(model_name="PaliGemma") -# def _warn_about_recipe_import_error(model_name: str) -> None: -# disable_warnings = str2bool( -# os.getenv( -# DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, -# DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, -# ) -# ) -# if disable_warnings: -# return None -# warning = typer.style("WARNING", fg=typer.colors.RED, bold=True) -# message = "🚧 " + warning + f" cannot import recipe for {model_name}" -# typer.echo(message) +def _warn_about_recipe_import_error(model_name: str) -> None: + disable_warnings = str2bool( + os.getenv( + DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, + DEFAULT_DISABLE_RECIPE_IMPORTS_WARNINGS_ENV, + ) + ) + if disable_warnings: + return None + warning = typer.style("WARNING", fg=typer.colors.RED, bold=True) + message = "🚧 " + warning + f" cannot import recipe for {model_name}" + typer.echo(message) diff --git a/maestro/cli/main.py b/maestro/cli/main.py index 8e2edc9..b600e3a 100644 --- a/maestro/cli/main.py +++ b/maestro/cli/main.py @@ -1,26 +1,15 @@ -# import typer - -# from maestro.cli.introspection import find_training_recipes - -# app = typer.Typer() -# find_training_recipes(app=app) - - -# @app.command(help="Display information about maestro") -# def info(): -# typer.echo("Welcome to maestro CLI. Let's train some VLM! 🏋") +import typer +from maestro.cli.introspection import find_training_recipes -# if __name__ == "__main__": -# app() +app = typer.Typer() +find_training_recipes(app=app) -import typer -from maestro.trainer.models.florence_2.entrypoint import florence2 -app = typer.Typer() +@app.command(help="Display information about maestro") +def info(): + typer.echo("Welcome to maestro CLI. Let's train some VLM! 🏋") -# Add the florence2 command to the main app -app.command()(florence2) if __name__ == "__main__": app() diff --git a/maestro/cli/utils.py b/maestro/cli/utils.py index 6d31bd9..0751fef 100644 --- a/maestro/cli/utils.py +++ b/maestro/cli/utils.py @@ -1,2 +1,2 @@ -# def str2bool(value: str) -> bool: -# return value.lower() in {"y", "t", "yes", "true"} +def str2bool(value: str) -> bool: + return value.lower() in {"y", "t", "yes", "true"} diff --git a/maestro/trainer/models/florence_2/core.py b/maestro/trainer/models/florence_2/core.py index 46ea75b..c4ca286 100644 --- a/maestro/trainer/models/florence_2/core.py +++ b/maestro/trainer/models/florence_2/core.py @@ -31,30 +31,33 @@ class TrainingConfiguration: """Configuration for training a Florence-2 model. This class encapsulates all the parameters needed for training a Florence-2 model, - including dataset paths, model specifications, training hyperparameters, and output settings. + including dataset paths, model specifications, training hyperparameters, and output + settings. Attributes: dataset_path (str): Path to the dataset used for training. - model_id (str): Identifier for the Florence-2 model. Defaults to DEFAULT_FLORENCE2_MODEL_ID. - revision (str): Revision of the model to use. Defaults to DEFAULT_FLORENCE2_MODEL_REVISION. - device (torch.device): Device to use for training. Defaults to DEVICE. - cache_dir (Optional[str]): Directory to cache the model. Defaults to None. - epochs (int): Number of training epochs. Defaults to 10. - optimizer (Literal["sgd", "adamw", "adam"]): Optimizer to use for training. Defaults to "adamw". - lr (float): Learning rate for the optimizer. Defaults to 1e-5. - lr_scheduler (Literal["linear", "cosine", "polynomial"]): Learning rate scheduler. Defaults to "linear". - batch_size (int): Batch size for training. Defaults to 4. - val_batch_size (Optional[int]): Batch size for validation. Defaults to None. - num_workers (int): Number of workers for data loading. Defaults to 0. - val_num_workers (Optional[int]): Number of workers for validation data loading. Defaults to None. - lora_r (int): Rank of the LoRA update matrices. Defaults to 8. - lora_alpha (int): Scaling factor for the LoRA update. Defaults to 8. - lora_dropout (float): Dropout probability for LoRA layers. Defaults to 0.05. - bias (Literal["none", "all", "lora_only"]): Which bias to train. Defaults to "none". - use_rslora (bool): Whether to use RSLoRA. Defaults to True. - init_lora_weights (Union[bool, LoraInitLiteral]): How to initialize LoRA weights. Defaults to "gaussian". - output_dir (str): Directory to save output files. Defaults to "./training/florence-2". - metrics (List[BaseMetric]): List of metrics to track during training. Defaults to an empty list. + model_id (str): Identifier for the Florence-2 model. + revision (str): Revision of the model to use. + device (torch.device): Device to use for training. + cache_dir (Optional[str]): Directory to cache the model. + epochs (int): Number of training epochs. + optimizer (Literal["sgd", "adamw", "adam"]): Optimizer to use for training. + lr (float): Learning rate for the optimizer. + lr_scheduler (Literal["linear", "cosine", "polynomial"]): Learning rate + scheduler. + batch_size (int): Batch size for training. + val_batch_size (Optional[int]): Batch size for validation. + num_workers (int): Number of workers for data loading. + val_num_workers (Optional[int]): Number of workers for validation data loading. + lora_r (int): Rank of the LoRA update matrices. + lora_alpha (int): Scaling factor for the LoRA update. + lora_dropout (float): Dropout probability for LoRA layers. + bias (Literal["none", "all", "lora_only"]): Which bias to train. + use_rslora (bool): Whether to use RSLoRA. + init_lora_weights (Union[bool, LoraInitLiteral]): How to initialize LoRA + weights. + output_dir (str): Directory to save output files. + metrics (List[BaseMetric]): List of metrics to track during training. """ dataset_path: str model_id: str = DEFAULT_FLORENCE2_MODEL_ID diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py index 30204ce..acab19e 100644 --- a/maestro/trainer/models/florence_2/entrypoint.py +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -1,227 +1,140 @@ -# import dataclasses -# from typing import Optional, Annotated - -# import rich -# import torch -# import typer - -# from maestro.trainer.models.florence_2.checkpoints import DEFAULT_FLORENCE2_MODEL_ID, \ -# DEFAULT_FLORENCE2_MODEL_REVISION, DEVICE -# from maestro.trainer.models.florence_2.core import TrainingConfiguration -# from maestro.trainer.models.florence_2.core import train as train_fun - -# florence_2_app = typer.Typer(help="Fine-tune and evaluate Florence 2 model") - - -# @florence_2_app.command( -# help="Train Florence 2 model", context_settings={"allow_extra_args": True, "ignore_unknown_options": True} -# ) -# def train( -# dataset_location: Annotated[ -# str, -# typer.Option("--dataset_location", help="Path to directory with dataset"), -# ], -# model_id_or_path: Annotated[ -# str, -# typer.Option("--model_id_or_path", help="Model to be used or path to your checkpoint"), -# ] = DEFAULT_FLORENCE2_MODEL_ID, -# revision: Annotated[ -# str, -# typer.Option("--revision", help="Revision of Florence2 HF repository"), -# ] = DEFAULT_FLORENCE2_MODEL_REVISION, -# device: Annotated[ -# str, -# typer.Option("--device", help="CUDA device ID to be used (in format: 'cuda:0')"), -# ] = DEVICE, -# transformers_cache_dir: Annotated[ -# Optional[str], -# typer.Option("--transformers_cache_dir", help="Cache dir for HF weights"), -# ] = None, -# training_epochs: Annotated[ -# int, -# typer.Option("--training_epochs", help="Number of training epochs"), -# ] = 10, -# optimiser: Annotated[ -# str, -# typer.Option("--optimiser", help="Optimiser to be used"), -# ] = "adamw", -# learning_rate: Annotated[ -# float, -# typer.Option("--learning_rate", help="Learning rate"), -# ] = 1e-5, -# lr_scheduler: Annotated[ -# str, -# typer.Option("--lr_scheduler", help="LR scheduler"), -# ] = "linear", -# train_batch_size: Annotated[ -# int, -# typer.Option("--train_batch_size", help="Batch size for training"), -# ] = 4, -# test_batch_size: Annotated[ -# Optional[int], -# typer.Option( -# "--train_batch_size", help="Batch size for validation and test. If not given - train will be used." -# ), -# ] = None, -# loaders_workers: Annotated[ -# int, -# typer.Option("--loaders_workers", help="Number of loaders workers. 0 = # of CPU"), -# ] = 0, -# test_loaders_workers: Annotated[ -# Optional[int], -# typer.Option( -# "--test_loaders_workers", -# help="Number of workers for test and val loaders. If not given - train will be used.", -# ), -# ] = None, -# lora_r: Annotated[ -# int, -# typer.Option("--lora_r", help="Value of Lora R"), -# ] = 8, -# lora_alpha: Annotated[ -# int, -# typer.Option("--lora_alpha", help="Value of Lora Alpha"), -# ] = 8, -# lora_dropout: Annotated[ -# float, -# typer.Option("--lora_dropout", help="Value of Lora Dropout"), -# ] = 0.05, -# bias: Annotated[ -# str, -# typer.Option("--bias", help="Value of Lora Bias"), -# ] = "none", -# use_rslora: Annotated[ -# bool, -# typer.Option( -# "--use_rslora/--no_use_rslora", -# help="Boolean flag to decide if rslora to be used", -# ), -# ] = True, -# init_lora_weights: Annotated[ -# str, -# typer.Option("--init_lora_weights", help="Lora weights initialisation"), -# ] = "gaussian", -# training_dir: Annotated[ -# str, -# typer.Option("--training_dir", help="Path to directory where training outputs should be preserved"), -# ] = "./training/florence-2", -# max_checkpoints_to_keep: Annotated[ -# int, -# typer.Option("--max_checkpoints_to_keep", help="Max checkpoints to keep"), -# ] = 3, -# num_samples_to_visualise: Annotated[ -# int, -# typer.Option("--num_samples_to_visualise", help="Number of samples to visualise"), -# ] = 64, -# ) -> None: -# configuration = TrainingConfiguration( -# dataset_location=dataset_location, -# model_id_or_path=model_id_or_path, -# revision=revision, -# device=torch.device(device), -# transformers_cache_dir=transformers_cache_dir, -# training_epochs=training_epochs, -# optimiser=optimiser, # type: ignore -# learning_rate=learning_rate, -# lr_scheduler=lr_scheduler, # type: ignore -# train_batch_size=train_batch_size, -# test_batch_size=test_batch_size, -# loaders_workers=loaders_workers, -# test_loaders_workers=test_loaders_workers, -# lora_r=lora_r, -# lora_alpha=lora_alpha, -# lora_dropout=lora_dropout, -# bias=bias, # type: ignore -# use_rslora=use_rslora, -# init_lora_weights=init_lora_weights, # type: ignore -# training_dir=training_dir, -# max_checkpoints_to_keep=max_checkpoints_to_keep, -# num_samples_to_visualise=num_samples_to_visualise, -# ) -# typer.echo(typer.style("Training configuration", fg=typer.colors.BRIGHT_GREEN, bold=True)) -# rich.print(dataclasses.asdict(configuration)) -# train_fun(configuration=configuration) - - -# @florence_2_app.command(help="Evaluate Florence 2 model") -# def evaluate() -> None: -# pass +import dataclasses +from typing import Optional, Annotated, Literal, Union +import rich +import torch import typer -from typing import Optional, List, Union, Literal -from maestro.trainer.models.florence_2.core import TrainingConfiguration, train as train_florence2 -app = typer.Typer() +from maestro.trainer.models.florence_2.checkpoints import DEFAULT_FLORENCE2_MODEL_ID, \ + DEFAULT_FLORENCE2_MODEL_REVISION, DEVICE +from maestro.trainer.models.florence_2.core import TrainingConfiguration, \ + LoraInitLiteral +from maestro.trainer.models.florence_2.core import train as train_fun + +florence_2_app = typer.Typer(help="Fine-tune and evaluate Florence 2 model") -@app.command() -def florence2( - mode: str = typer.Option(..., help="Mode to run: train or evaluate"), - dataset_path: str = typer.Option(..., help="Path to the dataset used for training"), - model_id: str = typer.Option(None, help="Identifier for the Florence-2 model"), - revision: str = typer.Option(None, help="Revision of the model to use"), - device: str = typer.Option(None, help="Device to use for training"), - cache_dir: Optional[str] = typer.Option(None, help="Directory to cache the model"), - epochs: int = typer.Option(10, help="Number of training epochs"), - optimizer: str = typer.Option("adamw", help="Optimizer to use for training"), - lr: float = typer.Option(1e-5, help="Learning rate for the optimizer"), - lr_scheduler: str = typer.Option("linear", help="Learning rate scheduler"), - batch_size: int = typer.Option(4, help="Batch size for training"), - val_batch_size: Optional[int] = typer.Option(None, help="Batch size for validation"), - num_workers: int = typer.Option(0, help="Number of workers for data loading"), - val_num_workers: Optional[int] = typer.Option(None, help="Number of workers for validation data loading"), - lora_r: int = typer.Option(8, help="Rank of the LoRA update matrices"), - lora_alpha: int = typer.Option(8, help="Scaling factor for the LoRA update"), - lora_dropout: float = typer.Option(0.05, help="Dropout probability for LoRA layers"), - bias: str = typer.Option("none", help="Which bias to train"), - use_rslora: bool = typer.Option(True, help="Whether to use RSLoRA"), - init_lora_weights: str = typer.Option("gaussian", help="How to initialize LoRA weights"), - output_dir: str = typer.Option("./training/florence-2", help="Directory to save output files"), - metrics: List[str] = typer.Option([], help="List of metrics to track during training") -): - """Main entry point for Florence-2 model.""" - if mode == "train": - train( - dataset_path=dataset_path, - model_id=model_id, - revision=revision, - device=device, - cache_dir=cache_dir, - epochs=epochs, - optimizer=optimizer, - lr=lr, - lr_scheduler=lr_scheduler, - batch_size=batch_size, - val_batch_size=val_batch_size, - num_workers=num_workers, - val_num_workers=val_num_workers, - lora_r=lora_r, - lora_alpha=lora_alpha, - lora_dropout=lora_dropout, - bias=bias, - use_rslora=use_rslora, - init_lora_weights=init_lora_weights, - output_dir=output_dir, - metrics=metrics - ) - elif mode == "evaluate": - evaluate() - else: - typer.echo(f"Unknown mode: {mode}") - raise typer.Exit(code=1) -def train(**kwargs): - """Train a Florence-2 model.""" - # Filter out None values - config_overrides = {k: v for k, v in kwargs.items() if v is not None} - - # Create configuration with overrides - config = TrainingConfiguration(**config_overrides) - - train_florence2(config) +@florence_2_app.command( + help="Train Florence 2 model", + context_settings={"allow_extra_args": True, "ignore_unknown_options": True} +) +def train( + dataset: Annotated[ + str, + typer.Option("--dataset", help="Path to the dataset used for training"), + ], + model_id: Annotated[ + str, + typer.Option("--model_id", help="Identifier for the Florence-2 model"), + ] = DEFAULT_FLORENCE2_MODEL_ID, + revision: Annotated[ + str, + typer.Option("--revision", help="Revision of the model to use"), + ] = DEFAULT_FLORENCE2_MODEL_REVISION, + device: Annotated[ + str, + typer.Option("--device", help="Device to use for training"), + ] = DEVICE, + cache_dir: Annotated[ + Optional[str], + typer.Option("--cache_dir", help="Directory to cache the model"), + ] = None, + epochs: Annotated[ + int, + typer.Option("--epochs", help="Number of training epochs"), + ] = 10, + optimizer: Annotated[ + Literal["sgd", "adamw", "adam"], + typer.Option("--optimizer", help="Optimizer to use for training"), + ] = "adamw", + lr: Annotated[ + float, + typer.Option("--lr", help="Learning rate for the optimizer"), + ] = 1e-5, + lr_scheduler: Annotated[ + Literal["linear", "cosine", "polynomial"], + typer.Option("--lr_scheduler", help="Learning rate scheduler"), + ] = "linear", + batch_size: Annotated[ + int, + typer.Option("--batch_size", help="Batch size for training"), + ] = 4, + val_batch_size: Annotated[ + Optional[int], + typer.Option("--val_batch_size", help="Batch size for validation"), + ] = None, + num_workers: Annotated[ + int, + typer.Option("--num_workers", help="Number of workers for data loading"), + ] = 0, + val_num_workers: Annotated[ + Optional[int], + typer.Option("--val_num_workers", help="Number of workers for validation data loading"), + ] = None, + lora_r: Annotated[ + int, + typer.Option("--lora_r", help="Rank of the LoRA update matrices"), + ] = 8, + lora_alpha: Annotated[ + int, + typer.Option("--lora_alpha", help="Scaling factor for the LoRA update"), + ] = 8, + lora_dropout: Annotated[ + float, + typer.Option("--lora_dropout", help="Dropout probability for LoRA layers"), + ] = 0.05, + bias: Annotated[ + Literal["none", "all", "lora_only"], + typer.Option("--bias", help="Which bias to train"), + ] = "none", + use_rslora: Annotated[ + bool, + typer.Option("--use_rslora/--no_use_rslora", help="Whether to use RSLoRA"), + ] = True, + init_lora_weights: Annotated[ + Union[bool, LoraInitLiteral], + typer.Option("--init_lora_weights", help="How to initialize LoRA weights"), + ] = "gaussian", + output_dir: Annotated[ + str, + typer.Option("--output_dir", help="Directory to save output files"), + ] = "./training/florence-2", +) -> None: + config = TrainingConfiguration( + dataset=dataset, + model_id=model_id, + revision=revision, + device=torch.device(device), + cache_dir=cache_dir, + epochs=epochs, + optimizer=optimizer, + lr=lr, + lr_scheduler=lr_scheduler, + batch_size=batch_size, + val_batch_size=val_batch_size, + num_workers=num_workers, + val_num_workers=val_num_workers, + lora_r=lora_r, + lora_alpha=lora_alpha, + lora_dropout=lora_dropout, + bias=bias, + use_rslora=use_rslora, + init_lora_weights=init_lora_weights, + output_dir=output_dir + ) + typer.echo(typer.style( + text="Training configuration", + fg=typer.colors.BRIGHT_GREEN, + bold=True + )) + rich.print(dataclasses.asdict(config)) + train_fun(config=config) -def evaluate(): - """Evaluate a Florence-2 model.""" - typer.echo("Evaluation not implemented yet.") -if __name__ == "__main__": - app() +@florence_2_app.command(help="Evaluate Florence 2 model") +def evaluate() -> None: + typer.echo(typer.style( + "Evaluation command for Florence 2 is not yet implemented.", + fg=typer.colors.YELLOW, + bold=True + )) From f46049ed9a780f8dcca87d4b97de831e661d89bc Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 11 Sep 2024 00:14:02 +0200 Subject: [PATCH 65/69] remove Literal from command definitions --- maestro/trainer/models/florence_2/core.py | 6 +++--- maestro/trainer/models/florence_2/entrypoint.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/maestro/trainer/models/florence_2/core.py b/maestro/trainer/models/florence_2/core.py index c4ca286..068199d 100644 --- a/maestro/trainer/models/florence_2/core.py +++ b/maestro/trainer/models/florence_2/core.py @@ -35,7 +35,7 @@ class TrainingConfiguration: settings. Attributes: - dataset_path (str): Path to the dataset used for training. + dataset (str): Path to the dataset used for training. model_id (str): Identifier for the Florence-2 model. revision (str): Revision of the model to use. device (torch.device): Device to use for training. @@ -59,7 +59,7 @@ class TrainingConfiguration: output_dir (str): Directory to save output files. metrics (List[BaseMetric]): List of metrics to track during training. """ - dataset_path: str + dataset: str model_id: str = DEFAULT_FLORENCE2_MODEL_ID revision: str = DEFAULT_FLORENCE2_MODEL_REVISION device: torch.device = DEVICE @@ -100,7 +100,7 @@ def train(config: TrainingConfiguration) -> None: cache_dir=config.cache_dir, ) train_loader, val_loader, test_loader = prepare_data_loaders( - dataset_location=config.dataset_path, + dataset_location=config.dataset, train_batch_size=config.batch_size, processor=processor, device=config.device, diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py index acab19e..9310046 100644 --- a/maestro/trainer/models/florence_2/entrypoint.py +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -44,7 +44,7 @@ def train( typer.Option("--epochs", help="Number of training epochs"), ] = 10, optimizer: Annotated[ - Literal["sgd", "adamw", "adam"], + str, typer.Option("--optimizer", help="Optimizer to use for training"), ] = "adamw", lr: Annotated[ @@ -52,7 +52,7 @@ def train( typer.Option("--lr", help="Learning rate for the optimizer"), ] = 1e-5, lr_scheduler: Annotated[ - Literal["linear", "cosine", "polynomial"], + str, typer.Option("--lr_scheduler", help="Learning rate scheduler"), ] = "linear", batch_size: Annotated[ @@ -84,7 +84,7 @@ def train( typer.Option("--lora_dropout", help="Dropout probability for LoRA layers"), ] = 0.05, bias: Annotated[ - Literal["none", "all", "lora_only"], + str, typer.Option("--bias", help="Which bias to train"), ] = "none", use_rslora: Annotated[ @@ -92,7 +92,7 @@ def train( typer.Option("--use_rslora/--no_use_rslora", help="Whether to use RSLoRA"), ] = True, init_lora_weights: Annotated[ - Union[bool, LoraInitLiteral], + Union[bool, str], typer.Option("--init_lora_weights", help="How to initialize LoRA weights"), ] = "gaussian", output_dir: Annotated[ From a2850ac9d45fb0282266f2a3d6c4394576659f11 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 11 Sep 2024 00:26:06 +0200 Subject: [PATCH 66/69] remove Union from command definitions --- maestro/trainer/models/florence_2/entrypoint.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py index 9310046..a686350 100644 --- a/maestro/trainer/models/florence_2/entrypoint.py +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -1,5 +1,5 @@ import dataclasses -from typing import Optional, Annotated, Literal, Union +from typing import Optional, Annotated import rich import torch @@ -7,8 +7,7 @@ from maestro.trainer.models.florence_2.checkpoints import DEFAULT_FLORENCE2_MODEL_ID, \ DEFAULT_FLORENCE2_MODEL_REVISION, DEVICE -from maestro.trainer.models.florence_2.core import TrainingConfiguration, \ - LoraInitLiteral +from maestro.trainer.models.florence_2.core import TrainingConfiguration from maestro.trainer.models.florence_2.core import train as train_fun florence_2_app = typer.Typer(help="Fine-tune and evaluate Florence 2 model") @@ -92,7 +91,7 @@ def train( typer.Option("--use_rslora/--no_use_rslora", help="Whether to use RSLoRA"), ] = True, init_lora_weights: Annotated[ - Union[bool, str], + str, typer.Option("--init_lora_weights", help="How to initialize LoRA weights"), ] = "gaussian", output_dir: Annotated[ From d614d254986f44c3506e76412c5f5ab9de2a7e66 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 11 Sep 2024 16:59:42 +0200 Subject: [PATCH 67/69] initial evaluate implementation --- .../trainer/models/florence_2/checkpoints.py | 14 +++++ maestro/trainer/models/florence_2/core.py | 54 ++++++++++++++++ .../trainer/models/florence_2/entrypoint.py | 61 +++++++++++++++++-- 3 files changed, 124 insertions(+), 5 deletions(-) diff --git a/maestro/trainer/models/florence_2/checkpoints.py b/maestro/trainer/models/florence_2/checkpoints.py index cdffe7b..a489f2b 100644 --- a/maestro/trainer/models/florence_2/checkpoints.py +++ b/maestro/trainer/models/florence_2/checkpoints.py @@ -91,6 +91,20 @@ def load_model( device: torch.device = DEVICE, cache_dir: Optional[str] = None, ) -> Tuple[AutoProcessor, AutoModelForCausalLM]: + """Loads a Florence-2 model and its associated processor. + + Args: + model_id_or_path: The identifier or path of the model to load. + revision: The specific model revision to use. + device: The device to load the model onto. + cache_dir: Directory to cache the downloaded model files. + + Returns: + A tuple containing the loaded processor and model. + + Raises: + ValueError: If the model or processor cannot be loaded. + """ processor = AutoProcessor.from_pretrained( model_id_or_path, trust_remote_code=True, diff --git a/maestro/trainer/models/florence_2/core.py b/maestro/trainer/models/florence_2/core.py index 068199d..73f1c56 100644 --- a/maestro/trainer/models/florence_2/core.py +++ b/maestro/trainer/models/florence_2/core.py @@ -354,3 +354,57 @@ def get_optimizer(model: PeftModel, config: TrainingConfiguration) -> Optimizer: if optimizer_type == "sgd": return SGD(model.parameters(), lr=config.lr) raise ValueError(f"Unsupported optimizer: {config.optimizer}") + + +def evaluate(config: TrainingConfiguration) -> None: + processor, model = load_model( + model_id_or_path=config.model_id, + revision=config.revision, + device=config.device, + cache_dir=config.cache_dir, + ) + train_loader, val_loader, test_loader = prepare_data_loaders( + dataset_location=config.dataset, + train_batch_size=config.batch_size, + processor=processor, + device=config.device, + num_workers=config.num_workers, + test_loaders_workers=config.val_num_workers, + ) + evaluation_loader = test_loader if test_loader is not None else val_loader + + metrics = [] + for metric in config.metrics: + metrics += metric.describe() + evaluation_metrics_tracker = MetricsTracker.init(metrics=metrics) + + # Run inference once for all metrics + _, expected_responses, generated_texts, images = run_predictions( + dataset=evaluation_loader.dataset, + processor=processor, + model=model, + device=config.device, + ) + + for metric in config.metrics: + if isinstance(metric, MeanAveragePrecisionMetric): + classes = extract_unique_detection_dataset_classes(train_loader.dataset) + targets, predictions = postprocess_florence2_output_for_mean_average_precision( + expected_responses=expected_responses, + generated_texts=generated_texts, + images=images, + classes=classes, + processor=processor + ) + result = metric.compute(targets=targets, predictions=predictions) + for key, value in result.items(): + evaluation_metrics_tracker.register( + metric=key, + epoch=1, + step=1, + value=value, + ) + + evaluation_metrics_tracker.as_json( + output_dir=os.path.join(config.output_dir, "metrics"), + filename="evaluation.json") diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py index a686350..eb8983c 100644 --- a/maestro/trainer/models/florence_2/entrypoint.py +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -8,7 +8,8 @@ from maestro.trainer.models.florence_2.checkpoints import DEFAULT_FLORENCE2_MODEL_ID, \ DEFAULT_FLORENCE2_MODEL_REVISION, DEVICE from maestro.trainer.models.florence_2.core import TrainingConfiguration -from maestro.trainer.models.florence_2.core import train as train_fun +from maestro.trainer.models.florence_2.core import train as florence2_train +from maestro.trainer.models.florence_2.core import evaluate as florence2_evaluate florence_2_app = typer.Typer(help="Fine-tune and evaluate Florence 2 model") @@ -127,13 +128,63 @@ def train( bold=True )) rich.print(dataclasses.asdict(config)) - train_fun(config=config) + florence2_train(config=config) @florence_2_app.command(help="Evaluate Florence 2 model") -def evaluate() -> None: +def evaluate( + dataset: Annotated[ + str, + typer.Option("--dataset", help="Path to the dataset used for evaluation"), + ], + model_id: Annotated[ + str, + typer.Option("--model_id", help="Identifier for the Florence-2 model"), + ] = DEFAULT_FLORENCE2_MODEL_ID, + revision: Annotated[ + str, + typer.Option("--revision", help="Revision of the model to use"), + ] = DEFAULT_FLORENCE2_MODEL_REVISION, + device: Annotated[ + str, + typer.Option("--device", help="Device to use for evaluation"), + ] = DEVICE, + cache_dir: Annotated[ + Optional[str], + typer.Option("--cache_dir", help="Directory to cache the model"), + ] = None, + batch_size: Annotated[ + int, + typer.Option("--batch_size", help="Batch size for evaluation"), + ] = 4, + num_workers: Annotated[ + int, + typer.Option("--num_workers", help="Number of workers for data loading"), + ] = 0, + val_num_workers: Annotated[ + Optional[int], + typer.Option("--val_num_workers", help="Number of workers for validation data loading"), + ] = None, + output_dir: Annotated[ + str, + typer.Option("--output_dir", help="Directory to save output files"), + ] = "./evaluation/florence-2", +) -> None: + config = TrainingConfiguration( + dataset=dataset, + model_id=model_id, + revision=revision, + device=torch.device(device), + cache_dir=cache_dir, + batch_size=batch_size, + num_workers=num_workers, + val_num_workers=val_num_workers, + output_dir=output_dir + ) typer.echo(typer.style( - "Evaluation command for Florence 2 is not yet implemented.", - fg=typer.colors.YELLOW, + text="Evaluation configuration", + fg=typer.colors.BRIGHT_GREEN, bold=True )) + rich.print(dataclasses.asdict(config)) + florence2_evaluate(config=config) From 5aba6605c128020742b99698ef9bc0a862296d94 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 11 Sep 2024 21:38:21 +0200 Subject: [PATCH 68/69] adding `quickstart` section to `README.md` --- README.md | 45 +++++++++++++++++++---- maestro/trainer/models/florence_2/core.py | 4 ++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9814a8e..68371fb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -

maestro

@@ -23,13 +22,43 @@ Pip install the supervision package in a pip install maestro ``` -## 🚀 example +## 🔥 quickstart + +### CLI + +VLMs can be fine-tuned on downstream tasks directly from the command line with +`maestro` command: + +```bash +maestro florence2 train --dataset='' --epochs=10 --batch-size=8 +``` -Documentation and Florence-2 fine-tuning examples for object detection and VQA coming -soon. +### SDK + +Alternatively, you can fine-tune VLMs using the Python SDK, which accepts the same +arguments as the CLI example above: + +```python +from maestro.trainer.models.florence_2 import ( + train, + TrainingConfiguration, + MeanAveragePrecisionMetric +) + +config = TrainingConfiguration( + dataset='', + epochs=10, + batch_size=8, + metrics=[MeanAveragePrecisionMetric()] +) + +train(config) +``` -## 🚧 roadmap +## 🦸 contribution -- [ ] Release a CLI for predefined fine-tuning recipes. -- [ ] Multi-GPU fine-tuning support. -- [ ] Allow multi-dataset fine-tuning and support multiple tasks at the same time. +We would love your help in making this repository even better! We are especially +looking for contributors with experience in fine-tuning vision-language models (VLMs). +If you notice any bugs or have suggestions for improvement, feel free to open an +[issue](https://github.com/roboflow/multimodal-maestro/issues) or submit a +[pull request](https://github.com/roboflow/multimodal-maestro/pulls). diff --git a/maestro/trainer/models/florence_2/core.py b/maestro/trainer/models/florence_2/core.py index 73f1c56..1be9991 100644 --- a/maestro/trainer/models/florence_2/core.py +++ b/maestro/trainer/models/florence_2/core.py @@ -144,6 +144,10 @@ def train(config: TrainingConfiguration) -> None: validation_metrics_tracker.as_json( output_dir=os.path.join(config.output_dir, "metrics"), filename="validation.json") + + # Log out paths for latest and best checkpoints + print(f"Latest checkpoint saved at: {checkpoint_manager.get_latest_checkpoint_path()}") + print(f"Best checkpoint saved at: {checkpoint_manager.get_best_checkpoint_path()}") def prepare_peft_model( From 50751d5d76e820a29cae01924bb2565f550c1439 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 11 Sep 2024 22:10:31 +0200 Subject: [PATCH 69/69] plug in `mean_average_precision` to Florence-2 CLI --- README.md | 7 +--- maestro/trainer/common/__init__.py | 1 + maestro/trainer/common/utils/metrics.py | 41 +++++++++++++++++++ maestro/trainer/models/florence_2/__init__.py | 1 - maestro/trainer/models/florence_2/core.py | 23 +++++++---- .../trainer/models/florence_2/entrypoint.py | 35 ++++++++++++++-- maestro/trainer/models/florence_2/metrics.py | 22 +--------- 7 files changed, 93 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 68371fb..26ea4f9 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,8 @@ Alternatively, you can fine-tune VLMs using the Python SDK, which accepts the sa arguments as the CLI example above: ```python -from maestro.trainer.models.florence_2 import ( - train, - TrainingConfiguration, - MeanAveragePrecisionMetric -) +from maestro.trainer.common import MeanAveragePrecisionMetric +from maestro.trainer.models.florence_2 import train, TrainingConfiguration config = TrainingConfiguration( dataset='', diff --git a/maestro/trainer/common/__init__.py b/maestro/trainer/common/__init__.py index e69de29..0bfe6b2 100644 --- a/maestro/trainer/common/__init__.py +++ b/maestro/trainer/common/__init__.py @@ -0,0 +1 @@ +from maestro.trainer.common.utils.metrics import MeanAveragePrecisionMetric diff --git a/maestro/trainer/common/utils/metrics.py b/maestro/trainer/common/utils/metrics.py index e22cce4..0d1fb9b 100644 --- a/maestro/trainer/common/utils/metrics.py +++ b/maestro/trainer/common/utils/metrics.py @@ -10,7 +10,9 @@ from typing import Any, Dict, List, Tuple import matplotlib.pyplot as plt +import supervision as sv from PIL import Image +from supervision.metrics.mean_average_precision import MeanAveragePrecision class BaseMetric(ABC): @@ -45,6 +47,45 @@ def compute(self, targets: List[Any], predictions: List[Any]) -> Dict[str, float pass +class MeanAveragePrecisionMetric(BaseMetric): + """ + A class used to compute the Mean Average Precision (mAP) metric. + """ + + def describe(self) -> List[str]: + """ + Returns a list of metric names that this class will compute. + + Returns: + List[str]: A list of metric names. + """ + return ["map50:95", "map50", "map75"] + + def compute( + self, + targets: List[sv.Detections], + predictions: List[sv.Detections] + ) -> Dict[str, float]: + """ + Computes the mAP metrics based on the targets and predictions. + + Args: + targets (List[sv.Detections]): The ground truth detections. + predictions (List[sv.Detections]): The predicted detections. + + Returns: + Dict[str, float]: A dictionary of computed mAP metrics with metric names as + keys and their values. + """ + result = MeanAveragePrecision().update( + targets=targets, predictions=predictions).compute() + return { + "map50:95": result.map50_95, + "map50": result.map50, + "map75": result.map75 + } + + class MetricsTracker: @classmethod diff --git a/maestro/trainer/models/florence_2/__init__.py b/maestro/trainer/models/florence_2/__init__.py index 836df5c..77d45b3 100644 --- a/maestro/trainer/models/florence_2/__init__.py +++ b/maestro/trainer/models/florence_2/__init__.py @@ -1,2 +1 @@ from maestro.trainer.models.florence_2.core import TrainingConfiguration, train -from maestro.trainer.models.florence_2.metrics import MeanAveragePrecisionMetric diff --git a/maestro/trainer/models/florence_2/core.py b/maestro/trainer/models/florence_2/core.py index 1be9991..8ef20ce 100644 --- a/maestro/trainer/models/florence_2/core.py +++ b/maestro/trainer/models/florence_2/core.py @@ -11,14 +11,23 @@ from transformers import AutoModelForCausalLM, AutoProcessor, get_scheduler from maestro.trainer.common.utils.file_system import create_new_run_directory -from maestro.trainer.common.utils.metrics import BaseMetric, MetricsTracker, \ - display_results, save_metric_plots +from maestro.trainer.common.utils.metrics import ( + BaseMetric, + MetricsTracker, + display_results, + save_metric_plots, + MeanAveragePrecisionMetric +) from maestro.trainer.common.utils.reproducibility import make_it_reproducible -from maestro.trainer.models.florence_2.checkpoints import CheckpointManager, load_model, \ - DEFAULT_FLORENCE2_MODEL_ID, DEFAULT_FLORENCE2_MODEL_REVISION, DEVICE +from maestro.trainer.models.florence_2.checkpoints import ( + CheckpointManager, + load_model, + DEFAULT_FLORENCE2_MODEL_ID, + DEFAULT_FLORENCE2_MODEL_REVISION, + DEVICE +) from maestro.trainer.models.florence_2.data_loading import prepare_data_loaders from maestro.trainer.models.florence_2.metrics import ( - MeanAveragePrecisionMetric, extract_unique_detection_dataset_classes, postprocess_florence2_output_for_mean_average_precision, run_predictions, @@ -146,8 +155,8 @@ def train(config: TrainingConfiguration) -> None: filename="validation.json") # Log out paths for latest and best checkpoints - print(f"Latest checkpoint saved at: {checkpoint_manager.get_latest_checkpoint_path()}") - print(f"Best checkpoint saved at: {checkpoint_manager.get_best_checkpoint_path()}") + print(f"Latest checkpoint saved at: {checkpoint_manager.latest_checkpoint_dir}") + print(f"Best checkpoint saved at: {checkpoint_manager.best_checkpoint_dir}") def prepare_peft_model( diff --git a/maestro/trainer/models/florence_2/entrypoint.py b/maestro/trainer/models/florence_2/entrypoint.py index eb8983c..f18d81e 100644 --- a/maestro/trainer/models/florence_2/entrypoint.py +++ b/maestro/trainer/models/florence_2/entrypoint.py @@ -1,5 +1,5 @@ import dataclasses -from typing import Optional, Annotated +from typing import Optional, Annotated, List, Dict, Type import rich import torch @@ -10,10 +10,27 @@ from maestro.trainer.models.florence_2.core import TrainingConfiguration from maestro.trainer.models.florence_2.core import train as florence2_train from maestro.trainer.models.florence_2.core import evaluate as florence2_evaluate +from maestro.trainer.common.utils.metrics import BaseMetric, MeanAveragePrecisionMetric florence_2_app = typer.Typer(help="Fine-tune and evaluate Florence 2 model") +METRIC_CLASSES: Dict[str, Type[BaseMetric]] = { + "mean_average_precision": MeanAveragePrecisionMetric, +} + + +def parse_metrics(metrics: List[str]) -> List[BaseMetric]: + metric_objects = [] + for metric_name in metrics: + metric_class = METRIC_CLASSES.get(metric_name.lower()) + if metric_class: + metric_objects.append(metric_class()) + else: + raise ValueError(f"Unsupported metric: {metric_name}") + return metric_objects + + @florence_2_app.command( help="Train Florence 2 model", context_settings={"allow_extra_args": True, "ignore_unknown_options": True} @@ -99,7 +116,12 @@ def train( str, typer.Option("--output_dir", help="Directory to save output files"), ] = "./training/florence-2", + metrics: Annotated[ + List[str], + typer.Option("--metrics", help="List of metrics to track during training"), + ] = [], ) -> None: + metric_objects = parse_metrics(metrics) config = TrainingConfiguration( dataset=dataset, model_id=model_id, @@ -120,7 +142,8 @@ def train( bias=bias, use_rslora=use_rslora, init_lora_weights=init_lora_weights, - output_dir=output_dir + output_dir=output_dir, + metrics=metric_objects ) typer.echo(typer.style( text="Training configuration", @@ -169,7 +192,12 @@ def evaluate( str, typer.Option("--output_dir", help="Directory to save output files"), ] = "./evaluation/florence-2", + metrics: Annotated[ + List[str], + typer.Option("--metrics", help="List of metrics to track during evaluation"), + ] = [], ) -> None: + metric_objects = parse_metrics(metrics) config = TrainingConfiguration( dataset=dataset, model_id=model_id, @@ -179,7 +207,8 @@ def evaluate( batch_size=batch_size, num_workers=num_workers, val_num_workers=val_num_workers, - output_dir=output_dir + output_dir=output_dir, + metrics=metric_objects ) typer.echo(typer.style( text="Evaluation configuration", diff --git a/maestro/trainer/models/florence_2/metrics.py b/maestro/trainer/models/florence_2/metrics.py index 117e031..09e02bd 100644 --- a/maestro/trainer/models/florence_2/metrics.py +++ b/maestro/trainer/models/florence_2/metrics.py @@ -1,39 +1,19 @@ import re -from typing import List, Dict +from typing import List from typing import Tuple import numpy as np import supervision as sv import torch from PIL import Image -from supervision.metrics.mean_average_precision import MeanAveragePrecision from tqdm import tqdm from transformers import AutoProcessor, AutoModelForCausalLM from maestro.trainer.common.data_loaders.datasets import DetectionDataset -from maestro.trainer.common.utils.metrics import BaseMetric DETECTION_CLASS_PATTERN = r"([a-zA-Z0-9 -]+)" -class MeanAveragePrecisionMetric(BaseMetric): - - def describe(self) -> List[str]: - return ["map50:95", "map50", "map75"] - - def compute( - self, - targets: List[sv.Detections], - predictions: List[sv.Detections] - ) -> Dict[str, float]: - result = MeanAveragePrecision().update(targets=targets, predictions=predictions).compute() - return { - "map50:95": result.map50_95, - "map50": result.map50, - "map75": result.map75 - } - - def postprocess_florence2_output_for_mean_average_precision( expected_responses: List[str], generated_texts: List[str],