From c8ce6cf67afa9b367999fe4afbd24b6ff3a8c726 Mon Sep 17 00:00:00 2001 From: Matteo Bunino <48362942+matbun@users.noreply.github.com> Date: Fri, 31 May 2024 16:39:21 +0200 Subject: [PATCH] Dev - itwinai 0.0.2 (#138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Backend (#59) * WIP: Tensorflow MNIST use-case * UPDATE: Tensorflow MNIST version * ADD: Backend * ADD: Use-case init * FIX: Paths and downloading of the data * FIX: Paths and downloading of the data * ADD: Setup, Config update * ADD: Setup, Config update * UPDATE: File movement into itwinai * FIX: Move utils from tensorflow to global folder * FIX: Add setup into torch Executable * ADD: MNIST Torch Use-case * FIX: Formatting * ADD: Lib * ADD: Lib * ADD: Tests, Fix Loggers * Update README.md * ADD: Tests * ADD: MLCC * ADD: Cyclones, Cyclones-pipe * ADD: TensorflowTrainer * UPDATE: Move TensorflowTrainer into Backend * FIX: Dependencies * ADD: Number of devices * ADD: initial version of TorchTrainer * update * update * ADD: distributed torch Trainer and decorator * ADD: New version of torch distribtued trainer and tests * ADD: load torch dist trainer form config file * ADD: multi-gpu pytorch trainer * ADD: download on login node * FIX: dataloaders in Trainer * FIX: add dataloaders into trainer * FIX: clear load and save state * ADD: Loggers * FIX: Log in a distributed environment * TensorFlow backend (#63) * UPDATE: Remove experimental distribution * ADD: Mnist distributed * ADD: Optional strategy * UPDATE: Conditional distribution * FIX: Dataloader for mnist * FIX: Model cloning lambda function for distributed scope * ADD: CycleGAN * UPDATE: Types * UPDATE: Types * ADD: Local distr * FIX: learning rates * ADD: CycleGAN distributed * FIX: Reduction * FIX: Distribution * ADD: tmp.py * FIX: Distribution * FIX: Distribution * FIX: Distribution * FIX: Distribution * FIX: Distribution * FIX: Distribution * FIX: Distribution * FIX: Distribution * UPDATE: Executors * FIX: Distributed Dataset * FIX: Distributed Dataset * FIX: Distributed Dataset * FIX: Distributed Dataset * FIX: Distributed Dataset * FIX: Distributed Dataset * FIX: Distributed Dataset * FIX: Distributed Dataset * FIX: Distributed Dataset * FIX: Distributed Dataset * FIX: Distributed Dataset * FIX: Distributed Dataset * FIX: Distributed Dataset * FIX: Distributed Dataset * FIX: Distributed Dataset * ADD: Ray * ADD: Ray * ADD: Ray * ADD: Ray * ADD: Ray * ADD: Ray * ADD:Initial VIRGO * UPDATE: Optional distribution, tensorflow-gpu * UPDATE: tensorflow-gpu dependency * ADD: Unify branches --------- Co-authored-by: User3574 * Refacto entire code base * ADD: workflows folder * FIX: refactor * FIX: linting * ADD: how to run use case doc * ADD: workflows doc * FIX: MD linter * Pipe MNIST lightning (#86) * ADD: lightning distributed + pipeline * UPDATE: jscpd threshold * UPDATE: super linter ignore use cases * ADD: jscpd ignore loggers * Functional tests for MNIST (#87) * ADD: use case tests * FIX: move use case models out of itwinai * FIX: rearrange modules * ADD: ConsoleLogger and LoggersCollection * FIX: loggers filter * FIX: add TF env creation * UPDATE: test flag * ADD: early pytest on slurm * FIX: duplicated code in TF Trainer * Sqaaas code (#88) * Create sqaaas.yml * Update sqaaas.yml * Update sqaaas.yml * Point to the current repo * Remove unnecessary checkout step * Rename step --------- Co-authored-by: orviz * Sqaaas code (#89) * Create sqaaas.yml * Update sqaaas.yml * Update sqaaas.yml * Point to the current repo * Remove unnecessary checkout step * Rename step * ADD: adaptive branch discovery for SQAaaS action * Update sqaaas.yml --------- Co-authored-by: orviz * 3dgan use case (#94) * commiting integration of 3dgan scripts * ADD: Download dataset * FIX: DDP distributed training with manual optimization * ADD: log with MLFlow * Sqaaas code (#88) * Create sqaaas.yml * Update sqaaas.yml * Update sqaaas.yml * Point to the current repo * Remove unnecessary checkout step * Rename step --------- Co-authored-by: orviz * Sqaaas code (#89) * Create sqaaas.yml * Update sqaaas.yml * Update sqaaas.yml * Point to the current repo * Remove unnecessary checkout step * Rename step * ADD: adaptive branch discovery for SQAaaS action * Update sqaaas.yml --------- Co-authored-by: orviz * ADD: draft predictor and saver * ADD: stub for inference pipeline * ADD: small docs * UPDATE: inference pipeline components * UPDATE: reorg * ADD: image generation for inference * update tag * ADD: threshold * ADD: draft inference * ADD: draft inference wf * ADD: working inference workflow * ADD: 3D scatter plots * ADD: Dockerfile + refactor * ADD: .dockerignore * Update .dockerignore * REMOVE: keras dependency * ADD: skip download option --------- Co-authored-by: Kalliopi Tsolaki Co-authored-by: orviz * Sqaaas code (#96) * ADD: adaptive branch discovery for SQAaaS action * Update sqaaas.yml * Update sqaaas.yml * ADD: adaptive branch discovery for SQAaaS actin * Trigger only on main and dev branches * ADD: double quote * Trigger pytest only on main and dev PRs * Torch mnist inference (#95) * ADD: draft predictor and saver * ADD: stub for inference pipeline * ADD: small docs * UPDATE: inference pipeline components * UPDATE: reorg * ADD: image generation for inference * update tag * ADD: threshold * Remove keras dependency * 3dgan integration (#97) * commiting integration of 3dgan scripts * ADD: Download dataset * FIX: DDP distributed training with manual optimization * ADD: log with MLFlow * Sqaaas code (#88) * Create sqaaas.yml * Update sqaaas.yml * Update sqaaas.yml * Point to the current repo * Remove unnecessary checkout step * Rename step --------- Co-authored-by: orviz * Sqaaas code (#89) * Create sqaaas.yml * Update sqaaas.yml * Update sqaaas.yml * Point to the current repo * Remove unnecessary checkout step * Rename step * ADD: adaptive branch discovery for SQAaaS action * Update sqaaas.yml --------- Co-authored-by: orviz * ADD: draft predictor and saver * ADD: stub for inference pipeline * ADD: small docs * UPDATE: inference pipeline components * UPDATE: reorg * ADD: image generation for inference * update tag * ADD: threshold * ADD: draft inference * ADD: draft inference wf * ADD: working inference workflow * ADD: 3D scatter plots * ADD: Dockerfile + refactor * ADD: .dockerignore * Update .dockerignore * REMOVE: keras dependency * ADD: skip download option * ADD: cern pipeline.yaml * UPDATE: dataset loading function * UPDATE: dataset loading function * UPDATE conf * UPDATE refactor * UPDATE refactor * UPDATE training docs --------- Co-authored-by: Kalliopi Tsolaki Co-authored-by: orviz * Add SQAaaS dynamic badge for dev branch (#104) * Add SQAaaS dynamic badge * Upgrade to sqaaas-assessment-action@v2 * 3dgan integration (#98) * commiting integration of 3dgan scripts * ADD: Download dataset * FIX: DDP distributed training with manual optimization * ADD: log with MLFlow * Sqaaas code (#88) * Create sqaaas.yml * Update sqaaas.yml * Update sqaaas.yml * Point to the current repo * Remove unnecessary checkout step * Rename step --------- Co-authored-by: orviz * Sqaaas code (#89) * Create sqaaas.yml * Update sqaaas.yml * Update sqaaas.yml * Point to the current repo * Remove unnecessary checkout step * Rename step * ADD: adaptive branch discovery for SQAaaS action * Update sqaaas.yml --------- Co-authored-by: orviz * ADD: draft predictor and saver * ADD: stub for inference pipeline * ADD: small docs * UPDATE: inference pipeline components * UPDATE: reorg * ADD: image generation for inference * update tag * ADD: threshold * ADD: draft inference * ADD: draft inference wf * ADD: working inference workflow * ADD: 3D scatter plots * ADD: Dockerfile + refactor * ADD: .dockerignore * Update .dockerignore * REMOVE: keras dependency * ADD: skip download option * ADD: cern pipeline.yaml * UPDATE: dataset loading function * UPDATE: dataset loading function * UPDATE conf * UPDATE refactor * UPDATE refactor * UPDATE training docs * Update readme * update README * FIX typo * Update README * Update mkdir * UPDATE data paths * UPDATE Dockerfile * UPDATE Dockerfiles * UPDATE for Singularity execution * FIX version mismatch * UPDATE Singularity docs * Named steps pipe (#100) * ADD: dict steps pipe * Relax dependency constraint * UPDATE Singularity exec command * UPDATE: Image version * UPDATE: load components from pipeline * ADD: docs * Simplify 3DGAN model config * ADD: mlflow autologging support for PL trainer * UPDATE container info * Refactor * UPDATE dependencies * FIX linter problem * Simplified workflow configuration (#108) * Add SQAaaS dynamic badge for dev branch (#104) * Add SQAaaS dynamic badge * Upgrade to sqaaas-assessment-action@v2 * Add draft example * UPDATE credits field * ADD docs * REFACTOR components and pipeline code * UPDATE docstring * UPDATE mnist torch uc * ADD config file parser draft * ADD itwinaiCLI and ConfigParser * ADD docs * ADD pipeline parser and serializer plus tests * UPDATE docs * ADD adapter component and tests (incl parser) * ADD splitter component, improve pipeline, tests * UPDATE test * REMOVE todos * ADD component tests * ADD serializer tests * FIX linter * ADD basic workflow tutorial * ADD basic intermediate tutorial * ADD advanced tutorial * UPDATE advanced tutorial * UPDATE use cases * UPDATE save parameters * FIX linter * FIX cyclones use case workflow --------- Co-authored-by: orviz * Simplified workflow configuration (#109) * Add SQAaaS dynamic badge for dev branch (#104) * Add SQAaaS dynamic badge * Upgrade to sqaaas-assessment-action@v2 * Add draft example * UPDATE credits field * ADD docs * REFACTOR components and pipeline code * UPDATE docstring * UPDATE mnist torch uc * ADD config file parser draft * ADD itwinaiCLI and ConfigParser * ADD docs * ADD pipeline parser and serializer plus tests * UPDATE docs * ADD adapter component and tests (incl parser) * ADD splitter component, improve pipeline, tests * UPDATE test * REMOVE todos * ADD component tests * ADD serializer tests * FIX linter * ADD basic workflow tutorial * ADD basic intermediate tutorial * ADD advanced tutorial * UPDATE advanced tutorial * UPDATE use cases * UPDATE save parameters * FIX linter * FIX cyclones use case workflow * ADD slurm jobscript * FIX merge error * FIX components template --------- Co-authored-by: orviz * ADD integration tests * FIX test * FIX 3dgan inference test --------- Co-authored-by: Kalliopi Tsolaki Co-authored-by: orviz * fixed distributed trainer in cyclones use case * 3dgan integration (#118) * fixed distributed trainer in cyclones use case * commiting integration of 3dgan scripts * ADD: Download dataset * FIX: DDP distributed training with manual optimization * ADD: log with MLFlow * Sqaaas code (#88) * Create sqaaas.yml * Update sqaaas.yml * Update sqaaas.yml * Point to the current repo * Remove unnecessary checkout step * Rename step --------- Co-authored-by: orviz * Sqaaas code (#89) * Create sqaaas.yml * Update sqaaas.yml * Update sqaaas.yml * Point to the current repo * Remove unnecessary checkout step * Rename step * ADD: adaptive branch discovery for SQAaaS action * Update sqaaas.yml --------- Co-authored-by: orviz * ADD: draft predictor and saver * ADD: stub for inference pipeline * ADD: small docs * UPDATE: inference pipeline components * UPDATE: reorg * ADD: image generation for inference * update tag * ADD: threshold * ADD: draft inference * ADD: draft inference wf * ADD: working inference workflow * ADD: 3D scatter plots * ADD: Dockerfile + refactor * ADD: .dockerignore * Update .dockerignore * ADD: skip download option * ADD: cern pipeline.yaml * UPDATE: dataset loading function * UPDATE: dataset loading function * UPDATE conf * UPDATE refactor * UPDATE refactor * UPDATE training docs * Update readme * update README * FIX typo * Update README * Update mkdir * UPDATE data paths * UPDATE Dockerfile * UPDATE Dockerfiles * UPDATE for Singularity execution * FIX version mismatch * UPDATE Singularity docs * Named steps pipe (#100) * ADD: dict steps pipe * Relax dependency constraint * UPDATE Singularity exec command * UPDATE: Image version * UPDATE: load components from pipeline * ADD: docs * Simplify 3DGAN model config * ADD: mlflow autologging support for PL trainer * UPDATE container info * Refactor * UPDATE dependencies * FIX linter problem * Simplified workflow configuration (#108) * Add SQAaaS dynamic badge for dev branch (#104) * Add SQAaaS dynamic badge * Upgrade to sqaaas-assessment-action@v2 * Add draft example * UPDATE credits field * ADD docs * REFACTOR components and pipeline code * UPDATE docstring * UPDATE mnist torch uc * ADD config file parser draft * ADD itwinaiCLI and ConfigParser * ADD docs * ADD pipeline parser and serializer plus tests * UPDATE docs * ADD adapter component and tests (incl parser) * ADD splitter component, improve pipeline, tests * UPDATE test * REMOVE todos * ADD component tests * ADD serializer tests * FIX linter * ADD basic workflow tutorial * ADD basic intermediate tutorial * ADD advanced tutorial * UPDATE advanced tutorial * UPDATE use cases * UPDATE save parameters * FIX linter * FIX cyclones use case workflow --------- Co-authored-by: orviz * Simplified workflow configuration (#109) * Add SQAaaS dynamic badge for dev branch (#104) * Add SQAaaS dynamic badge * Upgrade to sqaaas-assessment-action@v2 * Add draft example * UPDATE credits field * ADD docs * REFACTOR components and pipeline code * UPDATE docstring * UPDATE mnist torch uc * ADD config file parser draft * ADD itwinaiCLI and ConfigParser * ADD docs * ADD pipeline parser and serializer plus tests * UPDATE docs * ADD adapter component and tests (incl parser) * ADD splitter component, improve pipeline, tests * UPDATE test * REMOVE todos * ADD component tests * ADD serializer tests * FIX linter * ADD basic workflow tutorial * ADD basic intermediate tutorial * ADD advanced tutorial * UPDATE advanced tutorial * UPDATE use cases * UPDATE save parameters * FIX linter * FIX cyclones use case workflow * ADD slurm jobscript * FIX merge error * FIX components template --------- Co-authored-by: orviz * ADD integration tests * FIX test * FIX 3dgan inference test * ADD GPU support and update tag * FIX linter * ADD override example * UPDATE 3DGAN inference * UPDATE inference execution tutorials * UPDATE README * UPDATE saver saving sparse tensors * ADD interlink pods * UPDATE pod name * UPDATE annotations * FIX README * CLEANUP * Merge * update * ADD tf cpu env * U[date Makefile * FIX 3DGAN tests * FIX data folder path --------- Co-authored-by: zoechbauer1 Co-authored-by: Kalliopi Tsolaki Co-authored-by: orviz * Unit test 4 dev (#113) * Define a step for pytest execution * Fix: use v1 of step action * Print result of step composition * Rename step * Use step previous definition in the assessment * Rename input: workflow -> steps * Avoid caching by using 1.0.0 * Set container image * Bump to v1 * Bump to sqaaas-assessment-action@v2 * Remove 'id' property * Adapt inputs to v2 * Remove current branch * Disable test_cyclones_train_tf * ADD marker * ADD skip memory heavy * Disable for PRs --------- Co-authored-by: Matteo Bunino * Distributed strategy launcher (#117) * ADD: distrib launcher mockup * REFACTOR: cluster env, strategy and launcher * ADD: Torch Elastic Launcher * ADD: info on env vars * ADD: distributed tooling and examples * new folder * UPDATE: distributed strategy setup * generalized for DDP and DS * add config file * UPDATE: kwargs * Update general_trainer.py * Update general_startscript * Update general_trainer.py * UPDATE .gitignore * Update distrib strategy * UPDATE torch distributed strategy classes * Updated docstrings * Small fixes * UPDATE docstrings * ADD deepespeed config loader * ADD first deepspeed tutorial draft * UPDATE DDP Dp distrib strategy * UPDATE horovod strategy * UPDATE tutorial on torch distributed strategies * UPDATE torch strategies tutorial * Update createEnvJSC.sh * Update hvd_slurm.sh * Update README.md * UPDATE distributed tutorial * Delete tutorials/distributed-ml/torch-ddp-deepspeed-horovod/0 * Fixes to deepspeed startscript * Update distributed.py * Update trainer.py * UPDATE tutorial * ADD draft MNIST tutorial * UPDATE DDP tutorial for MNIST * FIX small details * Update distributed.py * Added TF tutorials * Fixes to tutorials * Add files via upload * Update Makefile * Update README.md * UPDATE tutorials * UPDATE documentation and improve explainability * UPDATE SLURM scripts * FIX local rank mismatch * fixed distributed trainer in cyclones use case * UPDATE launcher * UPDATE linter * UPDATE format * FIX linter * FIX linter * Update workflow * UPDATE workflow * update * Update workflow * UPDATE super linter to v6 * UPDATE super linter to v6.3.0 * UPDATE super linter to slim * Cleanup * Update tfmirrored_slurm.sh * Update tfmirrored_slurm.sh * REMOVE workflows legacy * DELETE cyclegan use case * UPDATE dist training tutorials torch * RENAME folders with torch * DRAFT torch imagenet tutorial * UPDATE configuration * UPDATE imagenet tutorial * DRAFT scaling test * ADD scaling analysis report * FIX deepspeed micro batchsize * UPDATE data path * UPDATE checkpoint to avoid race conditions * UPDATE scalability report * UPDATE dataset path * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSCTF.sh * Update README.md * Update README.md * JUBE benchmarks * Update createEnvJSC.sh * Update createEnvJSCTF.sh * ADD logy scale option * Extract JUBE tutorial * CLEANUP baselines * Log epoch time in real-time * FIX deepspeed dataloader for potential performances improvement * UPDATE SC bash severity * FIX deepspeed and horovod trainers * FIX some code checks * Unify redundant SLURM job scripts and configuration files * CLEANUP unused configuration * Reorg configurations * Refactor configurations and add documentation * Update README * ADD report image * Improve plot resolution * UPDATE scaling test * UPDATE launcher scripts * FIX linter * REMOVE jube tutorial --------- Co-authored-by: Mario Rüttgers Co-authored-by: r-sarma <126173968+r-sarma@users.noreply.github.com> Co-authored-by: r-sarma Co-authored-by: zoechbauer1 * Distributed strategy launcher (#127) Update ParseConfig * Distributed strategy launcher (#128) Remove experimental files * Docs dev (#132) * commiting docs functionality for testing deployment * adding documentation deployment relevant files * updating readthedocs.yaml * changing directory of requirements.txt * updating reqs file * commiting changes and adding pages for tutorials * fixed distributed trainer in cyclones use case * adding installation instructions in docs * adding latest changes to docs * adding new pages for itwinai modules and other modifications * modified src/itwinai/torch directory name to solve namespace conflict * fixing tutorial sections * fixes in pages appearance * fixing rendering bugs * fixing pages appearance bugs * adding latest modifications * Deleted duplicate folder after renaming src/itwinai/torch * adding documentation.yml file for automatic updating on github pages * modifying documentation.yml file * updating reqs file to solve bug in deployment * commiting docs functionality for testing deployment * adding documentation deployment relevant files * updating readthedocs.yaml * changing directory of requirements.txt * updating reqs file * commiting changes and adding pages for tutorials * adding installation instructions in docs * adding latest changes to docs * adding new pages for itwinai modules and other modifications * modified src/itwinai/torch directory name to solve namespace conflict * fixing tutorial sections * fixes in pages appearance * fixing rendering bugs * fixing pages appearance bugs * adding latest modifications * Deleted duplicate folder after renaming src/itwinai/torch * adding documentation.yml file for automatic updating on github pages * modifying documentation.yml file * updating reqs file to solve bug in deployment * testing automated docs update * updating getting started page * fixing pages and adding new content * bug fixes * fixing content rendering * latest fixes in rendering * Add version feature to docs * Update .readthedocs.yaml * fixing display structure in getting started page * new fixes similar to previous commit * Update index.rst * Update index.rst Text re-edit index * Update index.rst change 1 word * Update .readthedocs.yaml * Update .readthedocs.yaml * fixing getting started page * Text review getting_started_with_itwinai.rst * Update 3dgan_doc.rst * Update getting_started_with_itwinai.rst punctuation * Fix torch naming problem --------- Co-authored-by: KalliopiTsolaki Co-authored-by: zoechbauer1 Co-authored-by: VerderK <167095399+VerderK@users.noreply.github.com> * Distributed strategy launcher (#131) * ADD: distrib launcher mockup * REFACTOR: cluster env, strategy and launcher * ADD: Torch Elastic Launcher * ADD: info on env vars * ADD: distributed tooling and examples * new folder * UPDATE: distributed strategy setup * generalized for DDP and DS * add config file * UPDATE: kwargs * Update general_trainer.py * Update general_startscript * Update general_trainer.py * UPDATE .gitignore * Update distrib strategy * UPDATE torch distributed strategy classes * Updated docstrings * Small fixes * UPDATE docstrings * ADD deepespeed config loader * ADD first deepspeed tutorial draft * UPDATE DDP Dp distrib strategy * UPDATE horovod strategy * UPDATE tutorial on torch distributed strategies * UPDATE torch strategies tutorial * Update createEnvJSC.sh * Update hvd_slurm.sh * Update README.md * UPDATE distributed tutorial * Delete tutorials/distributed-ml/torch-ddp-deepspeed-horovod/0 * Fixes to deepspeed startscript * Update distributed.py * Update trainer.py * UPDATE tutorial * ADD draft MNIST tutorial * UPDATE DDP tutorial for MNIST * FIX small details * Update distributed.py * Added TF tutorials * Fixes to tutorials * Add files via upload * Update Makefile * Update README.md * UPDATE tutorials * UPDATE documentation and improve explainability * UPDATE SLURM scripts * FIX local rank mismatch * fixed distributed trainer in cyclones use case * UPDATE launcher * UPDATE linter * UPDATE format * FIX linter * FIX linter * Update workflow * UPDATE workflow * update * Update workflow * UPDATE super linter to v6 * UPDATE super linter to v6.3.0 * UPDATE super linter to slim * Cleanup * Update tfmirrored_slurm.sh * Update tfmirrored_slurm.sh * REMOVE workflows legacy * DELETE cyclegan use case * UPDATE dist training tutorials torch * RENAME folders with torch * DRAFT torch imagenet tutorial * UPDATE configuration * UPDATE imagenet tutorial * DRAFT scaling test * ADD scaling analysis report * FIX deepspeed micro batchsize * UPDATE data path * UPDATE checkpoint to avoid race conditions * UPDATE scalability report * UPDATE dataset path * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSCTF.sh * Update README.md * Update README.md * JUBE benchmarks * Update createEnvJSC.sh * Update createEnvJSCTF.sh * ADD logy scale option * Extract JUBE tutorial * CLEANUP baselines * Log epoch time in real-time * FIX deepspeed dataloader for potential performances improvement * UPDATE SC bash severity * FIX deepspeed and horovod trainers * FIX some code checks * Unify redundant SLURM job scripts and configuration files * CLEANUP unused configuration * Reorg configurations * Refactor configurations and add documentation * Update README * ADD report image * Improve plot resolution * UPDATE scaling test * UPDATE launcher scripts * FIX linter * REMOVE jube tutorial * Restore ConfigParser * FIX type hinting * ADD dev dependencies * REMOVE experimental scripts * UPDATE scaling report * Add SLURM logs * Refactor log scale * Update scalability report * Unify SLURM logs per job * Update README.md * Update README.md * Update README.md * ADD itwinai installation * UPDATE torch distributed tutorial 0 * UPDATE torch distributed tutorials * REMOVE imagenet tutorial * ADD NonDistributedStrategy and create_dataloader method * CLEANUP older classes * Rename strategies * Simplify structure * ADD draft new torch trainer class * UPDATED torch trainer draft * UPDATE MNIST use case * INtegrate new trainer into MNIST use case * UPDATE structure: remove unused files and refactor tests * Tmp disable unused tests * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * FIX failing inference * Functiona tests (#133) * UPDATE tests * FIX errors * CLEANUP * Remove unused workflow --------- Co-authored-by: Mario Rüttgers Co-authored-by: r-sarma <126173968+r-sarma@users.noreply.github.com> Co-authored-by: r-sarma Co-authored-by: zoechbauer1 * 3dgan integration (#134) * fixed distributed trainer in cyclones use case * commiting integration of 3dgan scripts * ADD: Download dataset * FIX: DDP distributed training with manual optimization * ADD: log with MLFlow * Sqaaas code (#88) * Create sqaaas.yml * Update sqaaas.yml * Update sqaaas.yml * Point to the current repo * Remove unnecessary checkout step * Rename step --------- Co-authored-by: orviz * Sqaaas code (#89) * Create sqaaas.yml * Update sqaaas.yml * Update sqaaas.yml * Point to the current repo * Remove unnecessary checkout step * Rename step * ADD: adaptive branch discovery for SQAaaS action * Update sqaaas.yml --------- Co-authored-by: orviz * ADD: draft predictor and saver * ADD: stub for inference pipeline * ADD: small docs * UPDATE: inference pipeline components * UPDATE: reorg * ADD: image generation for inference * update tag * ADD: threshold * ADD: draft inference * ADD: draft inference wf * ADD: working inference workflow * ADD: 3D scatter plots * ADD: Dockerfile + refactor * ADD: .dockerignore * Update .dockerignore * ADD: skip download option * ADD: cern pipeline.yaml * UPDATE: dataset loading function * UPDATE: dataset loading function * UPDATE conf * UPDATE refactor * UPDATE refactor * UPDATE training docs * Update readme * update README * FIX typo * Update README * Update mkdir * UPDATE data paths * UPDATE Dockerfile * UPDATE Dockerfiles * UPDATE for Singularity execution * FIX version mismatch * UPDATE Singularity docs * Named steps pipe (#100) * ADD: dict steps pipe * Relax dependency constraint * UPDATE Singularity exec command * UPDATE: Image version * UPDATE: load components from pipeline * ADD: docs * Simplify 3DGAN model config * ADD: mlflow autologging support for PL trainer * UPDATE container info * Refactor * UPDATE dependencies * FIX linter problem * Simplified workflow configuration (#108) * Add SQAaaS dynamic badge for dev branch (#104) * Add SQAaaS dynamic badge * Upgrade to sqaaas-assessment-action@v2 * Add draft example * UPDATE credits field * ADD docs * REFACTOR components and pipeline code * UPDATE docstring * UPDATE mnist torch uc * ADD config file parser draft * ADD itwinaiCLI and ConfigParser * ADD docs * ADD pipeline parser and serializer plus tests * UPDATE docs * ADD adapter component and tests (incl parser) * ADD splitter component, improve pipeline, tests * UPDATE test * REMOVE todos * ADD component tests * ADD serializer tests * FIX linter * ADD basic workflow tutorial * ADD basic intermediate tutorial * ADD advanced tutorial * UPDATE advanced tutorial * UPDATE use cases * UPDATE save parameters * FIX linter * FIX cyclones use case workflow --------- Co-authored-by: orviz * Simplified workflow configuration (#109) * Add SQAaaS dynamic badge for dev branch (#104) * Add SQAaaS dynamic badge * Upgrade to sqaaas-assessment-action@v2 * Add draft example * UPDATE credits field * ADD docs * REFACTOR components and pipeline code * UPDATE docstring * UPDATE mnist torch uc * ADD config file parser draft * ADD itwinaiCLI and ConfigParser * ADD docs * ADD pipeline parser and serializer plus tests * UPDATE docs * ADD adapter component and tests (incl parser) * ADD splitter component, improve pipeline, tests * UPDATE test * REMOVE todos * ADD component tests * ADD serializer tests * FIX linter * ADD basic workflow tutorial * ADD basic intermediate tutorial * ADD advanced tutorial * UPDATE advanced tutorial * UPDATE use cases * UPDATE save parameters * FIX linter * FIX cyclones use case workflow * ADD slurm jobscript * FIX merge error * FIX components template --------- Co-authored-by: orviz * ADD integration tests * FIX test * FIX 3dgan inference test * ADD GPU support and update tag * FIX linter * ADD override example * UPDATE 3DGAN inference * UPDATE inference execution tutorials * UPDATE README * UPDATE saver saving sparse tensors * ADD interlink pods * UPDATE pod name * UPDATE annotations * FIX README * CLEANUP * Merge * update * ADD tf cpu env * U[date Makefile * FIX 3DGAN tests * FIX data folder path * ADD offloading of 3DGAN training * ADAPT 3DGAN training for singularity execution * UPDATE test and fix linter --------- Co-authored-by: zoechbauer1 Co-authored-by: Kalliopi Tsolaki Co-authored-by: orviz * Docs dev (#135) * commiting docs functionality for testing deployment * adding documentation deployment relevant files * updating readthedocs.yaml * changing directory of requirements.txt * updating reqs file * commiting changes and adding pages for tutorials * fixed distributed trainer in cyclones use case * adding installation instructions in docs * adding latest changes to docs * adding new pages for itwinai modules and other modifications * modified src/itwinai/torch directory name to solve namespace conflict * fixing tutorial sections * fixes in pages appearance * fixing rendering bugs * fixing pages appearance bugs * adding latest modifications * Deleted duplicate folder after renaming src/itwinai/torch * adding documentation.yml file for automatic updating on github pages * modifying documentation.yml file * updating reqs file to solve bug in deployment * commiting docs functionality for testing deployment * adding documentation deployment relevant files * updating readthedocs.yaml * changing directory of requirements.txt * updating reqs file * commiting changes and adding pages for tutorials * adding installation instructions in docs * adding latest changes to docs * adding new pages for itwinai modules and other modifications * modified src/itwinai/torch directory name to solve namespace conflict * fixing tutorial sections * fixes in pages appearance * fixing rendering bugs * fixing pages appearance bugs * adding latest modifications * Deleted duplicate folder after renaming src/itwinai/torch * adding documentation.yml file for automatic updating on github pages * modifying documentation.yml file * updating reqs file to solve bug in deployment * testing automated docs update * updating getting started page * fixing pages and adding new content * bug fixes * fixing content rendering * latest fixes in rendering * Add version feature to docs * Update .readthedocs.yaml * fixing display structure in getting started page * new fixes similar to previous commit * Update index.rst * Update index.rst Text re-edit index * Update index.rst change 1 word * Update .readthedocs.yaml * Update .readthedocs.yaml * fixing getting started page * Text review getting_started_with_itwinai.rst * Update 3dgan_doc.rst * Update getting_started_with_itwinai.rst punctuation * Fix torch naming problem * UPDATE requirements --------- Co-authored-by: KalliopiTsolaki Co-authored-by: zoechbauer1 Co-authored-by: VerderK <167095399+VerderK@users.noreply.github.com> * Distributed strategy launcher (#137) * ADD: distrib launcher mockup * REFACTOR: cluster env, strategy and launcher * ADD: Torch Elastic Launcher * ADD: info on env vars * ADD: distributed tooling and examples * new folder * UPDATE: distributed strategy setup * generalized for DDP and DS * add config file * UPDATE: kwargs * Update general_trainer.py * Update general_startscript * Update general_trainer.py * UPDATE .gitignore * Update distrib strategy * UPDATE torch distributed strategy classes * Updated docstrings * Small fixes * UPDATE docstrings * ADD deepespeed config loader * ADD first deepspeed tutorial draft * UPDATE DDP Dp distrib strategy * UPDATE horovod strategy * UPDATE tutorial on torch distributed strategies * UPDATE torch strategies tutorial * Update createEnvJSC.sh * Update hvd_slurm.sh * Update README.md * UPDATE distributed tutorial * Delete tutorials/distributed-ml/torch-ddp-deepspeed-horovod/0 * Fixes to deepspeed startscript * Update distributed.py * Update trainer.py * UPDATE tutorial * ADD draft MNIST tutorial * UPDATE DDP tutorial for MNIST * FIX small details * Update distributed.py * Added TF tutorials * Fixes to tutorials * Add files via upload * Update Makefile * Update README.md * UPDATE tutorials * UPDATE documentation and improve explainability * UPDATE SLURM scripts * FIX local rank mismatch * fixed distributed trainer in cyclones use case * UPDATE launcher * UPDATE linter * UPDATE format * FIX linter * FIX linter * Update workflow * UPDATE workflow * update * Update workflow * UPDATE super linter to v6 * UPDATE super linter to v6.3.0 * UPDATE super linter to slim * Cleanup * Update tfmirrored_slurm.sh * Update tfmirrored_slurm.sh * REMOVE workflows legacy * DELETE cyclegan use case * UPDATE dist training tutorials torch * RENAME folders with torch * DRAFT torch imagenet tutorial * UPDATE configuration * UPDATE imagenet tutorial * DRAFT scaling test * ADD scaling analysis report * FIX deepspeed micro batchsize * UPDATE data path * UPDATE checkpoint to avoid race conditions * UPDATE scalability report * UPDATE dataset path * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSCTF.sh * Update README.md * Update README.md * JUBE benchmarks * Update createEnvJSC.sh * Update createEnvJSCTF.sh * ADD logy scale option * Extract JUBE tutorial * CLEANUP baselines * Log epoch time in real-time * FIX deepspeed dataloader for potential performances improvement * UPDATE SC bash severity * FIX deepspeed and horovod trainers * FIX some code checks * Unify redundant SLURM job scripts and configuration files * CLEANUP unused configuration * Reorg configurations * Refactor configurations and add documentation * Update README * ADD report image * Improve plot resolution * UPDATE scaling test * UPDATE launcher scripts * FIX linter * REMOVE jube tutorial * Restore ConfigParser * FIX type hinting * ADD dev dependencies * REMOVE experimental scripts * UPDATE scaling report * Add SLURM logs * Refactor log scale * Update scalability report * Unify SLURM logs per job * Update README.md * Update README.md * Update README.md * ADD itwinai installation * UPDATE torch distributed tutorial 0 * UPDATE torch distributed tutorials * REMOVE imagenet tutorial * ADD NonDistributedStrategy and create_dataloader method * CLEANUP older classes * Rename strategies * Simplify structure * ADD draft new torch trainer class * UPDATED torch trainer draft * UPDATE MNIST use case * INtegrate new trainer into MNIST use case * UPDATE structure: remove unused files and refactor tests * Tmp disable unused tests * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * FIX failing inference * Functiona tests (#133) * UPDATE tests * FIX errors * CLEANUP * Remove unused workflow * Fixes to TF new version errors * Fixes to TF new version errors * Fixes to TF new version errors * Fixes to TF new version errors * Update distributed.py * Update tfmirrored_slurm.sh * Update train.py * TF updates * Add README * Python venv (#136) * Move to python venv * Update Makefile * Add Horovod installation * Update env * FIX openmpi install * Add TF explicit version * UPDATE env creation * REMOVE constraint on torch 2.0.* * UPDATE installation * FIX test * REMOVE strict dependency on micromamba * FIX docs and debugging states * FIX cpu only installation * FIX deepspeed cpu installation * FIX tf env creation * FIX makefile * ADD pypi deployment * DISABLE push debug * UPDATE pypi * UPDATE classifiers * Update pyproject.toml --------- Co-authored-by: Mario Rüttgers Co-authored-by: r-sarma <126173968+r-sarma@users.noreply.github.com> Co-authored-by: r-sarma Co-authored-by: zoechbauer1 * Update README.md * Distributed strategy launcher (#141) * ADD: distrib launcher mockup * REFACTOR: cluster env, strategy and launcher * ADD: Torch Elastic Launcher * ADD: info on env vars * ADD: distributed tooling and examples * new folder * UPDATE: distributed strategy setup * generalized for DDP and DS * add config file * UPDATE: kwargs * Update general_trainer.py * Update general_startscript * Update general_trainer.py * UPDATE .gitignore * Update distrib strategy * UPDATE torch distributed strategy classes * Updated docstrings * Small fixes * UPDATE docstrings * ADD deepespeed config loader * ADD first deepspeed tutorial draft * UPDATE DDP Dp distrib strategy * UPDATE horovod strategy * UPDATE tutorial on torch distributed strategies * UPDATE torch strategies tutorial * Update createEnvJSC.sh * Update hvd_slurm.sh * Update README.md * UPDATE distributed tutorial * Delete tutorials/distributed-ml/torch-ddp-deepspeed-horovod/0 * Fixes to deepspeed startscript * Update distributed.py * Update trainer.py * UPDATE tutorial * ADD draft MNIST tutorial * UPDATE DDP tutorial for MNIST * FIX small details * Update distributed.py * Added TF tutorials * Fixes to tutorials * Add files via upload * Update Makefile * Update README.md * UPDATE tutorials * UPDATE documentation and improve explainability * UPDATE SLURM scripts * FIX local rank mismatch * fixed distributed trainer in cyclones use case * UPDATE launcher * UPDATE linter * UPDATE format * FIX linter * FIX linter * Update workflow * UPDATE workflow * update * Update workflow * UPDATE super linter to v6 * UPDATE super linter to v6.3.0 * UPDATE super linter to slim * Cleanup * Update tfmirrored_slurm.sh * Update tfmirrored_slurm.sh * REMOVE workflows legacy * DELETE cyclegan use case * UPDATE dist training tutorials torch * RENAME folders with torch * DRAFT torch imagenet tutorial * UPDATE configuration * UPDATE imagenet tutorial * DRAFT scaling test * ADD scaling analysis report * FIX deepspeed micro batchsize * UPDATE data path * UPDATE checkpoint to avoid race conditions * UPDATE scalability report * UPDATE dataset path * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSC.sh * Update createEnvJSCTF.sh * Update README.md * Update README.md * JUBE benchmarks * Update createEnvJSC.sh * Update createEnvJSCTF.sh * ADD logy scale option * Extract JUBE tutorial * CLEANUP baselines * Log epoch time in real-time * FIX deepspeed dataloader for potential performances improvement * UPDATE SC bash severity * FIX deepspeed and horovod trainers * FIX some code checks * Unify redundant SLURM job scripts and configuration files * CLEANUP unused configuration * Reorg configurations * Refactor configurations and add documentation * Update README * ADD report image * Improve plot resolution * UPDATE scaling test * UPDATE launcher scripts * FIX linter * REMOVE jube tutorial * Restore ConfigParser * FIX type hinting * ADD dev dependencies * REMOVE experimental scripts * UPDATE scaling report * Add SLURM logs * Refactor log scale * Update scalability report * Unify SLURM logs per job * Update README.md * Update README.md * Update README.md * ADD itwinai installation * UPDATE torch distributed tutorial 0 * UPDATE torch distributed tutorials * REMOVE imagenet tutorial * ADD NonDistributedStrategy and create_dataloader method * CLEANUP older classes * Rename strategies * Simplify structure * ADD draft new torch trainer class * UPDATED torch trainer draft * UPDATE MNIST use case * INtegrate new trainer into MNIST use case * UPDATE structure: remove unused files and refactor tests * Tmp disable unused tests * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * Update action * FIX failing inference * Functiona tests (#133) * UPDATE tests * FIX errors * CLEANUP * Remove unused workflow * Fixes to TF new version errors * Fixes to TF new version errors * Fixes to TF new version errors * Fixes to TF new version errors * Update distributed.py * Update tfmirrored_slurm.sh * Update train.py * TF updates * Add README * Python venv (#136) * Move to python venv * Update Makefile * Add Horovod installation * Update env * FIX openmpi install * Add TF explicit version * UPDATE env creation * REMOVE constraint on torch 2.0.* * UPDATE installation * FIX test * REMOVE strict dependency on micromamba * FIX docs and debugging states * FIX cpu only installation * FIX deepspeed cpu installation * FIX tf env creation * FIX makefile * ADD pypi deployment * DISABLE push debug * UPDATE pypi * UPDATE classifiers * Update pyproject.toml * Update README.md * Cyclone tf dist (#130) * get_stretegy * UPDATE distributed strategy * change req file * cycline tf dist * small bugs * fix bug in train.py * REFACTOR cyclones use case * Activate pytest * NEW TensorFlow trainer * ADD user information --------- Co-authored-by: ruettgers1 Co-authored-by: Matteo Bunino * Interactive distrib ml (#139) Add examples for distributed ml in interactive mode * Interactive distrib ml (#140) Update tutorial * Disable documentation GH action * Remove action --------- Co-authored-by: Mario Rüttgers Co-authored-by: r-sarma <126173968+r-sarma@users.noreply.github.com> Co-authored-by: r-sarma Co-authored-by: zoechbauer1 Co-authored-by: MarioRuettgers <127950124+MarioRuettgers@users.noreply.github.com> * Merge main (#142) Bring changes on main into dev * Virgo integration (#143) * ADD Virgo data pipeline and some refactoring * FIX typo * UPDATE README * ADD training * ADD TrainingConfiguration * ADD distributed training and refactor * update readme * UPDATE loggers and add tests * Refactor * FIX typo * UPDATE use cases instructions * ADD checkpointing and refactor. * FIX linter * FIX jscpd * FIX jscpd * Disable jscpd * Refactor loggers * ADD loggers to Virgo use case * Update AUTHORS.md * Update AUTHORS.md * Docs dev (#144) * commiting docs functionality for testing deployment * adding documentation deployment relevant files * updating readthedocs.yaml * changing directory of requirements.txt * updating reqs file * commiting changes and adding pages for tutorials * fixed distributed trainer in cyclones use case * adding installation instructions in docs * adding latest changes to docs * adding new pages for itwinai modules and other modifications * modified src/itwinai/torch directory name to solve namespace conflict * fixing tutorial sections * fixes in pages appearance * fixing rendering bugs * fixing pages appearance bugs * adding latest modifications * Deleted duplicate folder after renaming src/itwinai/torch * adding documentation.yml file for automatic updating on github pages * modifying documentation.yml file * updating reqs file to solve bug in deployment * commiting docs functionality for testing deployment * adding documentation deployment relevant files * updating readthedocs.yaml * changing directory of requirements.txt * updating reqs file * commiting changes and adding pages for tutorials * adding installation instructions in docs * adding latest changes to docs * adding new pages for itwinai modules and other modifications * modified src/itwinai/torch directory name to solve namespace conflict * fixing tutorial sections * fixes in pages appearance * fixing rendering bugs * fixing pages appearance bugs * adding latest modifications * Deleted duplicate folder after renaming src/itwinai/torch * adding documentation.yml file for automatic updating on github pages * modifying documentation.yml file * updating reqs file to solve bug in deployment * testing automated docs update * updating getting started page * fixing pages and adding new content * bug fixes * fixing content rendering * latest fixes in rendering * Add version feature to docs * Update .readthedocs.yaml * fixing display structure in getting started page * new fixes similar to previous commit * Update index.rst * Update index.rst Text re-edit index * Update index.rst change 1 word * Update .readthedocs.yaml * Update .readthedocs.yaml * fixing getting started page * Text review getting_started_with_itwinai.rst * Update 3dgan_doc.rst * Update getting_started_with_itwinai.rst punctuation * Fix torch naming problem * UPDATE requirements * Remove unnecessary dependencies * Add docstring * adding latest changes from dev * new content and changes * Update index.rst toctree revise * adding pages for distributed ml tutorials * new shpinx reqs to solve build failing * Docs update: - python code format fixed - added brief explanation on ddp in new section * requirements changed * UPDATE requirements * UPDATE requirements and itwinai.types * ADD CMake and GCC installation * UPDATE CMake and GCC installation * UPDATE CMake and GCC installation * ADD notebooks * Disable notebooks section * FIX TOC * Saving local changes before pulling from remote * saving updates before pull from origin * Update itwinai.torch.modules.rst * Update itwinai.torch.modules.rst * Update itwinai.torch.modules.rst * Update itwinai.torch.modules.rst * adding cyclones and virgo use cases pages * FIX build errors * Update TOC * Update TOC --------- Co-authored-by: KalliopiTsolaki Co-authored-by: zoechbauer1 Co-authored-by: VerderK <167095399+VerderK@users.noreply.github.com> Co-authored-by: Killian Verder --------- Co-authored-by: Roman Machacek <69751521+User3574@users.noreply.github.com> Co-authored-by: linxUser3574 Co-authored-by: orviz Co-authored-by: Kalliopi Tsolaki Co-authored-by: zoechbauer1 Co-authored-by: Mario Rüttgers Co-authored-by: r-sarma <126173968+r-sarma@users.noreply.github.com> Co-authored-by: r-sarma Co-authored-by: KalliopiTsolaki Co-authored-by: VerderK <167095399+VerderK@users.noreply.github.com> Co-authored-by: MarioRuettgers <127950124+MarioRuettgers@users.noreply.github.com> Co-authored-by: Killian Verder --- .dockerignore | 135 + .github/linters/.jscpd.json | 6 + .github/workflows/lint.yml | 8 +- .github/workflows/pypi.yml | 31 + .github/workflows/pytest.yml | 38 + .github/workflows/sqaaas.yml | 33 + .github/workflows/workflows-dt.yml | 51 - .gitignore | 37 +- .python-version | 1 + .readthedocs.yaml | 38 + .vscode/extensions.json | 4 +- .vscode/settings.json | 24 +- AUTHORS.md | 7 +- CODEOWNERS | 1 - Makefile | 169 +- README.md | 177 +- ai/README.md | 6 - ai/env-files/pytorch-env-gpu.yml | 41 - ai/env-files/pytorch-env.yml | 36 - ai/env-files/pytorch-gpu-lock.yml | 4164 ---- ai/env-files/pytorch-lock.yml | 19174 ---------------- ai/setup.py | 20 - ai/src/itwinai/cli.py | 412 - ai/src/itwinai/models/cnn.py | 32 - ai/src/itwinai/plmodels/base.py | 26 - ai/src/itwinai/plmodels/mnist.py | 246 - ai/tests/test_cli.py | 27 - ai/tests/test_utils.py | 24 - ai/training.cwl | 68 - dev-env.yml | 39 - docs/.gitignore | 10 - docs/3dgan_doc.rst | 401 + docs/404.html | 12 - docs/Gemfile | 7 - docs/Gemfile.lock | 79 - docs/LICENSE | 21 - docs/Makefile | 20 + docs/README.md | 181 - docs/_config.yml | 26 - docs/advanced_workflow.rst | 15 + docs/basic_comp.rst | 13 + docs/basic_workflow.rst | 15 + docs/conf.py | 87 + docs/cyclones_doc.rst | 98 + docs/ddp_why.rst | 30 + docs/docs/CLI.md | 53 - docs/docs/Concepts.md | 35 - docs/docs/How-to-use-this-software.md | 540 - docs/docs/img/Workflow DAG concept.png | Bin 224511 -> 0 bytes docs/docs/img/cwl-workflow.png | Bin 375527 -> 0 bytes .../img/user-platform interaction full.png | Bin 211421 -> 0 bytes docs/docs/img/user-platform interaction.png | Bin 30005 -> 0 bytes docs/docs/use-cases/index.md | 12 - docs/docs/use-cases/mnist.md | 185 - docs/favicon.ico | Bin 15406 -> 0 bytes docs/getting_started_with_itwinai.rst | 190 + docs/index.md | 60 - docs/index.rst | 81 + docs/intermediate_workflow.rst | 17 + docs/itwinai.cli.rst | 7 + docs/itwinai.components.rst | 8 + docs/itwinai.distributed.rst | 7 + docs/itwinai.loggers.rst | 8 + docs/itwinai.parser.rst | 8 + docs/itwinai.pipeline.rst | 8 + docs/itwinai.serialization.rst | 8 + docs/itwinai.tf.modules.rst | 37 + docs/itwinai.torch.modules.rst | 63 + docs/itwinai.type.rst | 8 + docs/itwinai.utils.rst | 8 + docs/make.bat | 35 + docs/mnist_doc.rst | 298 + docs/modules.rst | 23 + docs/notebooks/example.ipynb | 92 + docs/requirements.txt | 15 + docs/tf_scaling_test.rst | 78 + docs/tf_tutorial_0_basics.rst | 40 + docs/tf_tutorial_1_imagenet.rst | 39 + docs/torch_scaling_test.rst | 229 + docs/torch_tutorial_0_basics.rst | 101 + docs/torch_tutorial_1_mnist.rst | 119 + docs/tutorials.rst | 52 + docs/use_cases.rst | 97 + docs/virgo_doc.rst | 103 + env-files/tensorflow/createEnvJSCTF.sh | 81 + env-files/tensorflow/generic_tf.sh | 84 + env-files/tensorflow/tensorflow-2.10.yml | 13 + env-files/tensorflow/tensorflow-2.11.yml | 19 + env-files/tensorflow/tensorflow-2.13-cpu.yml | 14 + env-files/tensorflow/tensorflow-2.13.yml | 17 + env-files/torch/createEnvJSC.sh | 63 + env-files/torch/generic_torch.sh | 154 + env-files/torch/pytorch-env-cpu.yml | 15 + env-files/torch/pytorch-env-gpu.yml | 19 + environment-cern.yml | 12 - examples.sh | 72 - other/argparse/argparsee.py | 31 - other/argparse/config.yml | 10 - pyproject.toml | 88 +- run-workflow.py | 154 - {ai/src => src}/itwinai/__init__.py | 0 src/itwinai/cli.py | 295 + src/itwinai/components.py | 460 + src/itwinai/distributed.py | 66 + src/itwinai/loggers.py | 724 + src/itwinai/parser.py | 253 + src/itwinai/pipeline.py | 101 + src/itwinai/serialization.py | 176 + .../itwinai/tensorflow}/__init__.py | 0 .../itwinai/tensorflow/data}/__init__.py | 0 src/itwinai/tensorflow/distributed.py | 62 + src/itwinai/tensorflow/models/__init__.py | 0 src/itwinai/tensorflow/models/mnist.py | 56 + src/itwinai/tensorflow/trainer.py | 213 + src/itwinai/tensorflow/utils.py | 13 + src/itwinai/tests/__init__.py | 11 + src/itwinai/tests/dummy_components.py | 97 + src/itwinai/torch/__init__.py | 0 src/itwinai/torch/config.py | 40 + src/itwinai/torch/data/__init__.py | 0 src/itwinai/torch/distributed.py | 932 + src/itwinai/torch/inference.py | 219 + src/itwinai/torch/mlflow.py | 82 + src/itwinai/torch/models/__init__.py | 0 src/itwinai/torch/models/mnist.py | 75 + src/itwinai/torch/reproducibility.py | 48 + src/itwinai/torch/trainer.py | 712 + src/itwinai/torch/types.py | 74 + src/itwinai/type.py | 15 + src/itwinai/utils.py | 181 + tests/components/conftest.py | 72 + tests/components/test_components.py | 151 + tests/components/test_pipe_parser.py | 216 + tests/components/test_pipeline.py | 83 + tests/conftest.py | 34 + tests/run_on_jsc.sh | 28 + tests/test_loggers.py | 165 + tests/test_utils.py | 99 + tests/torch/distribtued_decorator.py | 111 + tests/torch/test_config.py | 26 + tests/torch/test_distribtued_training.py | 25 + tests/torch/torch_dist_trainer.py | 62 + tests/use-cases/conftest.py | 54 + tests/use-cases/test_3dgan.py | 86 + tests/use-cases/test_cyclones.py | 42 + tests/use-cases/test_mnist.py | 121 +- .../tf-scaling-test-jube/README.md | 44 + .../tf-scaling-test-jube/bench_plot.ipynb | 170 + .../tf-scaling-test-jube/general_jobsys.xml | 140 + .../tf-scaling-test-jube/jube_ddp.sh | 72 + .../tf-scaling-test-jube/train.py | 171 + .../tf-tutorial-0-basics/README.md | 20 + .../tf-tutorial-0-basics/tfmirrored_slurm.sh | 67 + .../tf-tutorial-0-basics/train.py | 109 + .../tf-tutorial-1-imagenet/README.md | 20 + .../tfmirrored_slurm.sh | 70 + .../tf-tutorial-1-imagenet/train.py | 171 + .../torch-scaling-test/README.md | 118 + .../torch-scaling-test/config/base.yaml | 16 + .../torch-scaling-test/config/ddp.yaml | 1 + .../torch-scaling-test/config/deepspeed.yaml | 1 + .../torch-scaling-test/config/horovod.yaml | 3 + .../torch-scaling-test/ddp_trainer.py | 272 + .../torch-scaling-test/deepspeed_trainer.py | 286 + .../torch-scaling-test/horovod_trainer.py | 321 + .../torch-scaling-test/img/report.png | Bin 0 -> 198864 bytes .../torch-scaling-test/itwinai_trainer.py | 342 + .../torch-scaling-test/runall.sh | 89 + .../torch-scaling-test/scaling-test.sh | 10 + .../torch-scaling-test/slurm.sh | 117 + .../torch-scaling-test/utils.py | 21 + .../torch-tutorial-0-basics/README.md | 126 + .../torch-tutorial-0-basics/runall.sh | 39 + .../torch-tutorial-0-basics/slurm.sh | 117 + .../torch-tutorial-0-basics/train.py | 148 + .../torch-tutorial-1-mnist/README.md | 79 + .../torch-tutorial-1-mnist/config.yaml | 28 + .../torch-tutorial-1-mnist/runall.sh | 39 + .../torch-tutorial-1-mnist/slurm.sh | 116 + .../torch-tutorial-1-mnist/train.py | 426 + tutorials/ml-workflows/basic_components.py | 85 + .../ml-workflows/tutorial_0_basic_workflow.py | 71 + .../tutorial_1_intermediate_workflow.py | 98 + .../tutorial_2_advanced_workflow.py | 87 + use-cases/3dgan/Dockerfile | 25 + use-cases/3dgan/README.md | 257 + use-cases/3dgan/__init__.py | 1 + use-cases/3dgan/config.yaml | 208 + use-cases/3dgan/create_inference_sample.py | 23 + use-cases/3dgan/dataloader.py | 212 + .../3dgan/interLink/3dgan-inference-cpu.yaml | 76 + .../3dgan/interLink/3dgan-inference.yaml | 76 + use-cases/3dgan/interLink/3dgan-train.yaml | 88 + use-cases/3dgan/interLink/README.md | 66 + use-cases/3dgan/model.py | 786 + use-cases/3dgan/requirements.txt | 6 + use-cases/3dgan/saver.py | 122 + .../3dgan/startscript | 11 +- use-cases/3dgan/trainer.py | 157 + use-cases/README.md | 33 + use-cases/cyclones/.gitignore | 2 + use-cases/cyclones/README.md | 47 + use-cases/cyclones/cyclones_vgg.py | 729 + use-cases/cyclones/dataloader.py | 223 + use-cases/cyclones/pipeline.yaml | 45 + use-cases/cyclones/requirements.txt | 1 + use-cases/cyclones/src/callbacks.py | 73 + use-cases/cyclones/src/macros.py | 107 + use-cases/cyclones/src/scaling.py | 297 + use-cases/cyclones/src/strategy.py | 16 + use-cases/cyclones/src/tfrecords/dataset.py | 210 + use-cases/cyclones/src/tfrecords/functions.py | 140 + use-cases/cyclones/src/transform.py | 52 + use-cases/cyclones/src/utils.py | 89 + use-cases/cyclones/startscript.sh | 65 + use-cases/cyclones/train.py | 120 + use-cases/cyclones/trainer.py | 175 + use-cases/mnist/README.md | 3 - use-cases/mnist/env-files/preproc-env.yml | 8 - use-cases/mnist/inference-workflow.yml | 49 - use-cases/mnist/meta.yml | 26 - use-cases/mnist/mlflow-server.py | 47 - use-cases/mnist/mnist-ai-inference.yml | 15 - use-cases/mnist/mnist-ai-train.yml | 120 - use-cases/mnist/mnist-preproc.cwl | 69 - use-cases/mnist/mnist-preproc.py | 42 - use-cases/mnist/tensorflow/dataloader.py | 40 + use-cases/mnist/tensorflow/pipeline.yaml | 50 + use-cases/mnist/tensorflow/startscript.sh | 34 + use-cases/mnist/torch-lightning/README.md | 17 + use-cases/mnist/torch-lightning/config.yaml | 96 + use-cases/mnist/torch-lightning/dataloader.py | 95 + use-cases/mnist/torch-lightning/startscript | 34 + .../mnist/torch-lightning}/utils.py | 52 +- use-cases/mnist/torch/Dockerfile | 16 + use-cases/mnist/torch/README.md | 102 + use-cases/mnist/torch/config.yaml | 98 + .../mnist/torch/create_inference_sample.py | 42 + use-cases/mnist/torch/dataloader.py | 120 + use-cases/mnist/torch/model.py | 22 + use-cases/mnist/torch/runall.sh | 39 + use-cases/mnist/torch/saver.py | 59 + use-cases/mnist/torch/slurm.sh | 116 + use-cases/mnist/torch/startscript.sh | 34 + use-cases/mnist/training-workflow.yml | 76 - use-cases/mnist/workflow.cwl | 56 - use-cases/virgo/.gitignore | 1 + use-cases/virgo/README.md | 61 + use-cases/virgo/config.yaml | 39 + use-cases/virgo/data.py | 219 + use-cases/virgo/requirements.txt | 5 + use-cases/virgo/runall.sh | 39 + use-cases/virgo/slurm.sh | 116 + use-cases/virgo/src/dataset.py | 398 + use-cases/virgo/src/model.py | 513 + use-cases/virgo/src/utils.py | 71 + use-cases/virgo/train.py | 278 + use-cases/virgo/trainer.py | 245 + 258 files changed, 21527 insertions(+), 26563 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/linters/.jscpd.json create mode 100644 .github/workflows/pypi.yml create mode 100644 .github/workflows/pytest.yml create mode 100644 .github/workflows/sqaaas.yml delete mode 100644 .github/workflows/workflows-dt.yml create mode 100644 .python-version create mode 100644 .readthedocs.yaml delete mode 100644 ai/README.md delete mode 100644 ai/env-files/pytorch-env-gpu.yml delete mode 100644 ai/env-files/pytorch-env.yml delete mode 100644 ai/env-files/pytorch-gpu-lock.yml delete mode 100644 ai/env-files/pytorch-lock.yml delete mode 100644 ai/setup.py delete mode 100644 ai/src/itwinai/cli.py delete mode 100644 ai/src/itwinai/models/cnn.py delete mode 100644 ai/src/itwinai/plmodels/base.py delete mode 100644 ai/src/itwinai/plmodels/mnist.py delete mode 100644 ai/tests/test_cli.py delete mode 100644 ai/tests/test_utils.py delete mode 100644 ai/training.cwl delete mode 100644 dev-env.yml delete mode 100644 docs/.gitignore create mode 100644 docs/3dgan_doc.rst delete mode 100644 docs/404.html delete mode 100644 docs/Gemfile delete mode 100644 docs/Gemfile.lock delete mode 100644 docs/LICENSE create mode 100644 docs/Makefile delete mode 100644 docs/README.md delete mode 100644 docs/_config.yml create mode 100644 docs/advanced_workflow.rst create mode 100644 docs/basic_comp.rst create mode 100644 docs/basic_workflow.rst create mode 100644 docs/conf.py create mode 100644 docs/cyclones_doc.rst create mode 100644 docs/ddp_why.rst delete mode 100644 docs/docs/CLI.md delete mode 100644 docs/docs/Concepts.md delete mode 100644 docs/docs/How-to-use-this-software.md delete mode 100644 docs/docs/img/Workflow DAG concept.png delete mode 100644 docs/docs/img/cwl-workflow.png delete mode 100644 docs/docs/img/user-platform interaction full.png delete mode 100644 docs/docs/img/user-platform interaction.png delete mode 100644 docs/docs/use-cases/index.md delete mode 100644 docs/docs/use-cases/mnist.md delete mode 100644 docs/favicon.ico create mode 100644 docs/getting_started_with_itwinai.rst delete mode 100644 docs/index.md create mode 100644 docs/index.rst create mode 100644 docs/intermediate_workflow.rst create mode 100644 docs/itwinai.cli.rst create mode 100644 docs/itwinai.components.rst create mode 100644 docs/itwinai.distributed.rst create mode 100644 docs/itwinai.loggers.rst create mode 100644 docs/itwinai.parser.rst create mode 100644 docs/itwinai.pipeline.rst create mode 100644 docs/itwinai.serialization.rst create mode 100644 docs/itwinai.tf.modules.rst create mode 100644 docs/itwinai.torch.modules.rst create mode 100644 docs/itwinai.type.rst create mode 100644 docs/itwinai.utils.rst create mode 100644 docs/make.bat create mode 100644 docs/mnist_doc.rst create mode 100644 docs/modules.rst create mode 100644 docs/notebooks/example.ipynb create mode 100644 docs/requirements.txt create mode 100644 docs/tf_scaling_test.rst create mode 100644 docs/tf_tutorial_0_basics.rst create mode 100644 docs/tf_tutorial_1_imagenet.rst create mode 100644 docs/torch_scaling_test.rst create mode 100644 docs/torch_tutorial_0_basics.rst create mode 100644 docs/torch_tutorial_1_mnist.rst create mode 100644 docs/tutorials.rst create mode 100644 docs/use_cases.rst create mode 100644 docs/virgo_doc.rst create mode 100644 env-files/tensorflow/createEnvJSCTF.sh create mode 100644 env-files/tensorflow/generic_tf.sh create mode 100644 env-files/tensorflow/tensorflow-2.10.yml create mode 100644 env-files/tensorflow/tensorflow-2.11.yml create mode 100644 env-files/tensorflow/tensorflow-2.13-cpu.yml create mode 100644 env-files/tensorflow/tensorflow-2.13.yml create mode 100644 env-files/torch/createEnvJSC.sh create mode 100644 env-files/torch/generic_torch.sh create mode 100644 env-files/torch/pytorch-env-cpu.yml create mode 100644 env-files/torch/pytorch-env-gpu.yml delete mode 100644 environment-cern.yml delete mode 100644 examples.sh delete mode 100644 other/argparse/argparsee.py delete mode 100644 other/argparse/config.yml delete mode 100644 run-workflow.py rename {ai/src => src}/itwinai/__init__.py (100%) create mode 100644 src/itwinai/cli.py create mode 100644 src/itwinai/components.py create mode 100644 src/itwinai/distributed.py create mode 100644 src/itwinai/loggers.py create mode 100644 src/itwinai/parser.py create mode 100644 src/itwinai/pipeline.py create mode 100644 src/itwinai/serialization.py rename {ai/src/itwinai/models => src/itwinai/tensorflow}/__init__.py (100%) rename {ai/src/itwinai/plmodels => src/itwinai/tensorflow/data}/__init__.py (100%) create mode 100644 src/itwinai/tensorflow/distributed.py create mode 100644 src/itwinai/tensorflow/models/__init__.py create mode 100644 src/itwinai/tensorflow/models/mnist.py create mode 100644 src/itwinai/tensorflow/trainer.py create mode 100644 src/itwinai/tensorflow/utils.py create mode 100644 src/itwinai/tests/__init__.py create mode 100644 src/itwinai/tests/dummy_components.py create mode 100644 src/itwinai/torch/__init__.py create mode 100644 src/itwinai/torch/config.py create mode 100644 src/itwinai/torch/data/__init__.py create mode 100644 src/itwinai/torch/distributed.py create mode 100644 src/itwinai/torch/inference.py create mode 100644 src/itwinai/torch/mlflow.py create mode 100644 src/itwinai/torch/models/__init__.py create mode 100644 src/itwinai/torch/models/mnist.py create mode 100644 src/itwinai/torch/reproducibility.py create mode 100644 src/itwinai/torch/trainer.py create mode 100644 src/itwinai/torch/types.py create mode 100644 src/itwinai/type.py create mode 100644 src/itwinai/utils.py create mode 100644 tests/components/conftest.py create mode 100644 tests/components/test_components.py create mode 100644 tests/components/test_pipe_parser.py create mode 100644 tests/components/test_pipeline.py create mode 100644 tests/conftest.py create mode 100644 tests/run_on_jsc.sh create mode 100644 tests/test_loggers.py create mode 100644 tests/test_utils.py create mode 100644 tests/torch/distribtued_decorator.py create mode 100644 tests/torch/test_config.py create mode 100644 tests/torch/test_distribtued_training.py create mode 100644 tests/torch/torch_dist_trainer.py create mode 100644 tests/use-cases/conftest.py create mode 100644 tests/use-cases/test_3dgan.py create mode 100644 tests/use-cases/test_cyclones.py create mode 100644 tutorials/distributed-ml/tf-scaling-test-jube/README.md create mode 100644 tutorials/distributed-ml/tf-scaling-test-jube/bench_plot.ipynb create mode 100644 tutorials/distributed-ml/tf-scaling-test-jube/general_jobsys.xml create mode 100644 tutorials/distributed-ml/tf-scaling-test-jube/jube_ddp.sh create mode 100644 tutorials/distributed-ml/tf-scaling-test-jube/train.py create mode 100644 tutorials/distributed-ml/tf-tutorial-0-basics/README.md create mode 100644 tutorials/distributed-ml/tf-tutorial-0-basics/tfmirrored_slurm.sh create mode 100644 tutorials/distributed-ml/tf-tutorial-0-basics/train.py create mode 100644 tutorials/distributed-ml/tf-tutorial-1-imagenet/README.md create mode 100644 tutorials/distributed-ml/tf-tutorial-1-imagenet/tfmirrored_slurm.sh create mode 100644 tutorials/distributed-ml/tf-tutorial-1-imagenet/train.py create mode 100644 tutorials/distributed-ml/torch-scaling-test/README.md create mode 100644 tutorials/distributed-ml/torch-scaling-test/config/base.yaml create mode 100644 tutorials/distributed-ml/torch-scaling-test/config/ddp.yaml create mode 100644 tutorials/distributed-ml/torch-scaling-test/config/deepspeed.yaml create mode 100644 tutorials/distributed-ml/torch-scaling-test/config/horovod.yaml create mode 100755 tutorials/distributed-ml/torch-scaling-test/ddp_trainer.py create mode 100644 tutorials/distributed-ml/torch-scaling-test/deepspeed_trainer.py create mode 100755 tutorials/distributed-ml/torch-scaling-test/horovod_trainer.py create mode 100644 tutorials/distributed-ml/torch-scaling-test/img/report.png create mode 100644 tutorials/distributed-ml/torch-scaling-test/itwinai_trainer.py create mode 100644 tutorials/distributed-ml/torch-scaling-test/runall.sh create mode 100644 tutorials/distributed-ml/torch-scaling-test/scaling-test.sh create mode 100644 tutorials/distributed-ml/torch-scaling-test/slurm.sh create mode 100644 tutorials/distributed-ml/torch-scaling-test/utils.py create mode 100644 tutorials/distributed-ml/torch-tutorial-0-basics/README.md create mode 100644 tutorials/distributed-ml/torch-tutorial-0-basics/runall.sh create mode 100644 tutorials/distributed-ml/torch-tutorial-0-basics/slurm.sh create mode 100644 tutorials/distributed-ml/torch-tutorial-0-basics/train.py create mode 100644 tutorials/distributed-ml/torch-tutorial-1-mnist/README.md create mode 100644 tutorials/distributed-ml/torch-tutorial-1-mnist/config.yaml create mode 100644 tutorials/distributed-ml/torch-tutorial-1-mnist/runall.sh create mode 100644 tutorials/distributed-ml/torch-tutorial-1-mnist/slurm.sh create mode 100644 tutorials/distributed-ml/torch-tutorial-1-mnist/train.py create mode 100644 tutorials/ml-workflows/basic_components.py create mode 100644 tutorials/ml-workflows/tutorial_0_basic_workflow.py create mode 100644 tutorials/ml-workflows/tutorial_1_intermediate_workflow.py create mode 100644 tutorials/ml-workflows/tutorial_2_advanced_workflow.py create mode 100644 use-cases/3dgan/Dockerfile create mode 100644 use-cases/3dgan/README.md create mode 100644 use-cases/3dgan/__init__.py create mode 100644 use-cases/3dgan/config.yaml create mode 100644 use-cases/3dgan/create_inference_sample.py create mode 100644 use-cases/3dgan/dataloader.py create mode 100644 use-cases/3dgan/interLink/3dgan-inference-cpu.yaml create mode 100644 use-cases/3dgan/interLink/3dgan-inference.yaml create mode 100644 use-cases/3dgan/interLink/3dgan-train.yaml create mode 100644 use-cases/3dgan/interLink/README.md create mode 100644 use-cases/3dgan/model.py create mode 100644 use-cases/3dgan/requirements.txt create mode 100644 use-cases/3dgan/saver.py rename HPC_startscripts/workflow_startscript => use-cases/3dgan/startscript (68%) mode change 100755 => 100644 create mode 100644 use-cases/3dgan/trainer.py create mode 100644 use-cases/README.md create mode 100644 use-cases/cyclones/.gitignore create mode 100644 use-cases/cyclones/README.md create mode 100644 use-cases/cyclones/cyclones_vgg.py create mode 100644 use-cases/cyclones/dataloader.py create mode 100644 use-cases/cyclones/pipeline.yaml create mode 100644 use-cases/cyclones/requirements.txt create mode 100644 use-cases/cyclones/src/callbacks.py create mode 100644 use-cases/cyclones/src/macros.py create mode 100644 use-cases/cyclones/src/scaling.py create mode 100644 use-cases/cyclones/src/strategy.py create mode 100644 use-cases/cyclones/src/tfrecords/dataset.py create mode 100644 use-cases/cyclones/src/tfrecords/functions.py create mode 100644 use-cases/cyclones/src/transform.py create mode 100644 use-cases/cyclones/src/utils.py create mode 100644 use-cases/cyclones/startscript.sh create mode 100644 use-cases/cyclones/train.py create mode 100644 use-cases/cyclones/trainer.py delete mode 100644 use-cases/mnist/README.md delete mode 100644 use-cases/mnist/env-files/preproc-env.yml delete mode 100644 use-cases/mnist/inference-workflow.yml delete mode 100644 use-cases/mnist/meta.yml delete mode 100644 use-cases/mnist/mlflow-server.py delete mode 100644 use-cases/mnist/mnist-ai-inference.yml delete mode 100644 use-cases/mnist/mnist-ai-train.yml delete mode 100644 use-cases/mnist/mnist-preproc.cwl delete mode 100644 use-cases/mnist/mnist-preproc.py create mode 100644 use-cases/mnist/tensorflow/dataloader.py create mode 100644 use-cases/mnist/tensorflow/pipeline.yaml create mode 100644 use-cases/mnist/tensorflow/startscript.sh create mode 100644 use-cases/mnist/torch-lightning/README.md create mode 100644 use-cases/mnist/torch-lightning/config.yaml create mode 100644 use-cases/mnist/torch-lightning/dataloader.py create mode 100644 use-cases/mnist/torch-lightning/startscript rename {ai/src/itwinai => use-cases/mnist/torch-lightning}/utils.py (75%) create mode 100644 use-cases/mnist/torch/Dockerfile create mode 100644 use-cases/mnist/torch/README.md create mode 100644 use-cases/mnist/torch/config.yaml create mode 100644 use-cases/mnist/torch/create_inference_sample.py create mode 100644 use-cases/mnist/torch/dataloader.py create mode 100644 use-cases/mnist/torch/model.py create mode 100644 use-cases/mnist/torch/runall.sh create mode 100644 use-cases/mnist/torch/saver.py create mode 100644 use-cases/mnist/torch/slurm.sh create mode 100644 use-cases/mnist/torch/startscript.sh delete mode 100644 use-cases/mnist/training-workflow.yml delete mode 100644 use-cases/mnist/workflow.cwl create mode 100644 use-cases/virgo/.gitignore create mode 100644 use-cases/virgo/README.md create mode 100644 use-cases/virgo/config.yaml create mode 100644 use-cases/virgo/data.py create mode 100644 use-cases/virgo/requirements.txt create mode 100644 use-cases/virgo/runall.sh create mode 100644 use-cases/virgo/slurm.sh create mode 100644 use-cases/virgo/src/dataset.py create mode 100644 use-cases/virgo/src/model.py create mode 100644 use-cases/virgo/src/utils.py create mode 100644 use-cases/virgo/train.py create mode 100644 use-cases/virgo/trainer.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..d6dc083a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,135 @@ +**/TODO +**/mamba* +pl-training.yml +.vscode + +# Project folders/files +# use-cases +workflows +tests +CHANGELOG + +# Docs +docs + +# interLink pods +**/interLink +**/interlink + +# Data +**/MNIST +**/*-predictions/ +**/*-data/ +**/*.tar.gz +**/exp_data + +# Logs +**/logs +**/lightning_logs +**/mlruns +**/.logs +**/mllogs +**/nohup* +**/*.out +**/*.err +**/checkpoints/ +**/*_logs +**/tmp* +**/.tmp* + +# Markdown +**/*.md + +# Custom envs +**/.venv* + +# Git +.git +.gitignore +.github + +# CI +.codeclimate.yml +.travis.yml +.taskcluster.yml + +# Docker +docker-compose.yml +.docker +.dockerignore +Dockerfile + +# Byte-compiled / optimized / DLL files +**/__pycache__/ +**/*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +**/eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +**/*.egg-info/ +**/.installed.cfg +**/*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Virtual environment +.env/ +.venv/ +venv/ + +# PyCharm +.idea + +# Python mode for VIM +.ropeproject +*/.ropeproject +*/*/.ropeproject +*/*/*/.ropeproject + +# Vim swap files +*.swp +*/*.swp +*/*/*.swp +*/*/*/*.swp \ No newline at end of file diff --git a/.github/linters/.jscpd.json b/.github/linters/.jscpd.json new file mode 100644 index 00000000..8a003c54 --- /dev/null +++ b/.github/linters/.jscpd.json @@ -0,0 +1,6 @@ +{ + "threshold": 2.0, + "ignore": [ + "**/itwinai/loggers.py" + ] +} \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3f8934f4..62218e94 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,16 +36,22 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} MARKDOWN_CONFIG_FILE: .markdownlint.json PYTHON_FLAKE8_CONFIG_FILE: .flake8 + JSCPD_CONFIG_FILE: .jscpd.json VALIDATE_PYTHON_BLACK: false VALIDATE_PYTHON_ISORT: false VALIDATE_PYTHON_MYPY: false VALIDATE_PYTHON_PYLINT: false VALIDATE_HTML: false VALIDATE_GITLEAKS: false + VALIDATE_BASH_EXEC: false + VALIDATE_CHECKOV: false # activate to lint k8s pods + VALIDATE_SHELL_SHFMT: false + VALIDATE_JSCPD: false # Only check new or edited files VALIDATE_ALL_CODEBASE: false # Fail on errors DISABLE_ERRORS: false # Skip linting of docs - FILTER_REGEX_EXCLUDE: .*docs/index.md|.*docs/docs/.*|.*ISSUE_TEMPLATE/.* + FILTER_REGEX_EXCLUDE: .*docs/index.md|.*docs/docs/.*|.*ISSUE_TEMPLATE/.*|use-cases/.*|experimental/.* + BASH_SEVERITY: error diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml new file mode 100644 index 00000000..548caf8e --- /dev/null +++ b/.github/workflows/pypi.yml @@ -0,0 +1,31 @@ +name: Upload Python Package to PyPI when a Release is Created + +on: + release: + types: [created] + +jobs: + pypi-publish: + name: Publish release to PyPI + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/itwinai + permissions: + id-token: write + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -q build + # pip install setuptools wheel + - name: Build package + run: python -m build + # python setup.py sdist bdist_wheel # Could also be python -m build + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 \ No newline at end of file diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 00000000..d5b98751 --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,38 @@ +--- +name: Unit and integration tests + +on: + pull_request: + branches: [main, dev] + +jobs: + test-itwinai: + name: Test itwinai with pytest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install compilers for Horovod + run: | + sudo apt-get update && + sudo apt-get install -y gcc-11 g++-11 && + sudo apt-get install -y cmake && + sudo apt-get install openmpi-bin openmpi-common openmpi-doc libopenmpi-dev && + gcc --version && + cmake --version && + mpirun --version + + - name: Make PyTorch virtualenv + shell: bash -l {0} + run: make torch-env-cpu + + - name: Make Tensorflow virtualenv + shell: bash -l {0} + run: make tensorflow-env-cpu + + # NOTE, to change the name in which tests are run, set custom TORCH_ENV and TF_ENV env variables. + # Default environment names are ".venv-pytorch" and ".venv-tf" + - name: Run pytest for workflows + shell: bash -l {0} + run: .venv-pytorch/bin/pytest -v ./tests/ -m "not slurm" + diff --git a/.github/workflows/sqaaas.yml b/.github/workflows/sqaaas.yml new file mode 100644 index 00000000..bf066c1d --- /dev/null +++ b/.github/workflows/sqaaas.yml @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: Copyright contributors to the Software Quality Assurance as a Service (SQAaaS) project. +# +# SPDX-License-Identifier: GPL-3.0-only +--- +name: SQAaaS + +on: + push: + branches: [main, dev] + # pull_request: + # branches: [main, dev] + +jobs: + sqaaas_job: + runs-on: ubuntu-latest + name: Job that triggers SQAaaS platform + steps: + - name: Step definition for validating the workflow + uses: eosc-synergy/sqaaas-step-action@v1 + with: + name: workflow_validation_step + tool: commands + commands: | + make torch-env-cpu + make tensorflow-env-cpu + .venv-pytorch/bin/pytest -v ./tests/ -m "not slurm and not memory_heavy" + container: eoscsynergy/sqaaas-micromamba:1.5.3-1 + - name: Print out payload + run: cat workflow_validation_step.json + - name: SQAaaS assessment with unit testing (QC.Uni) step + uses: eosc-synergy/sqaaas-assessment-action@v2 + with: + qc_uni_steps: workflow_validation_step diff --git a/.github/workflows/workflows-dt.yml b/.github/workflows/workflows-dt.yml deleted file mode 100644 index 7c6d3195..00000000 --- a/.github/workflows/workflows-dt.yml +++ /dev/null @@ -1,51 +0,0 @@ ---- -name: Test workflows - -on: - # push: - pull_request: - -jobs: - test-dt: - name: Test DT workflows - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Install micromamba - shell: bash -l {0} - run: | - curl micro.mamba.pm/install.sh | bash - micromamba --version - - - name: Make workflow runner - shell: bash -l {0} - run: make workflow-runner-cern - - - name: Run pytest for workflows - shell: bash -l {0} - run: micromamba run -p ./.venv pytest ./tests/ - - test-itwinai: - name: Test itwinai with pytest - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Install micromamba environment with Micromamba for dev env (pytest) - uses: mamba-org/provision-with-micromamba@main - with: - environment-file: dev-env.yml - micromamba-version: "1.4.3" - environment-name: dev-env - cache-downloads: true - cache-env: true - # channels: pytorch,nvidia,conda-forge - - name: Install itwinai - shell: bash -l {0} - run: python -m pip install --no-deps -e ./ai - - name: Run tests - shell: bash -l {0} - run: pytest ai/tests diff --git a/.gitignore b/.gitignore index 43543abc..2624e08a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,48 @@ +s*.png +*.pdf +*_logs +logs_* TODO -data +/data nohup* -lightning_logs -mlruns tmp* .tmp* -*.txt checkpoints/ mamba* +pl-training.yml +*-predictions/ +*-data/ +*.tar.gz +*.pth +*.csv +*tar.gz +0 +*.tar +*.sif + +# Use cases files +MNIST +3dgan-generated-data/ +mnist-sample-data/ +exp_data/ + + +# Kubernetes +secret*.yaml # Custom envs .venv* +envAI* # Logs logs/ +ml_logs/ +mllogs/ +*.out +*.err +.logs/ +lightning_logs/ +mlruns/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..2c073331 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..77183ef7 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,38 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.10" + # You can also specify other tool versions: + # nodejs: "19" + # rust: "1.64" + # golang: "1.19" + apt_packages: + - gcc-11 + - g++-11 + - cmake + - pandoc + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + # - wheel + - requirements: docs/requirements.txt diff --git a/.vscode/extensions.json b/.vscode/extensions.json index c64c501d..d991e64f 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -8,8 +8,8 @@ "ms-python.vscode-pylance", "ms-python.python", "ms-python.autopep8", - "bungcip.better-toml", "bierner.markdown-mermaid", - "github.vscode-github-actions" + "github.vscode-github-actions", + "tamasfe.even-better-toml" ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 6f7a8637..32e0cccb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,4 @@ { - "python.linting.flake8Enabled": true, - "python.linting.enabled": true, "editor.formatOnSave": true, "editor.defaultFormatter": null, "cSpell.ignoreWords": [ @@ -13,12 +11,15 @@ "Convolutional", "cuda", "dataloaders", + "dataloading", "fromlist", "hyperparameters", "hyperparams", + "imagenet", "ipython", "itwinai", "Lockfiles", + "logfiles", "logits", "Mambaforge", "Micromamba", @@ -26,6 +27,7 @@ "mnist", "multiclass", "mypackage", + "NCCL", "omegaconf", "optim", "plmodels", @@ -35,10 +37,14 @@ "pyyaml", "relu", "Roadmap", + "savedir", + "SLURM", "softmax", + "tensorboard", "torchmetrics", "torchvision", - "venv" + "venv", + "wandb" ], "markdownlint.run": "onType", "markdownlint.config": { @@ -49,8 +55,12 @@ "[python]": { "editor.defaultFormatter": "ms-python.autopep8" }, - "python.formatting.provider": "none", - "[markdown]": { - "editor.formatOnSave": false - } + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "python.analysis.extraPaths": [ + "./src/itwinai" + ] } \ No newline at end of file diff --git a/AUTHORS.md b/AUTHORS.md index f5ad1d4a..eafb1a9b 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -2,9 +2,10 @@ ## Maintainers -Matteo Bunino \ -Alexander Zöchbauer \ -Rakesh Sarma \ +Matteo Bunino \<[matteo.bunino@cern.ch](mailto:matteo.bunino@cern.ch)\> +Rakesh Sarma \<[r.sarma@fz-juelich.de](mailto:r.sarma@fz-juelich.de)\> +Mario Ruettgers \<[m.ruettgers@fz-juelich.de](mailto:m.ruettgers@fz-juelich.de)\> +Kalliopi Tsolaki \<[kalliopi.tsolaki@cern.ch](mailto:kalliopi.tsolaki@cern.ch)\> ## Contributors diff --git a/CODEOWNERS b/CODEOWNERS index eec474fb..c328e105 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -7,4 +7,3 @@ # These owners will be the default owners for everything in the repo. # Unless a later match takes precedence, they will be requested for # review when someone opens a pull request. - diff --git a/Makefile b/Makefile index b256a9cd..931eaf15 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,145 @@ -# "Custom" workflow runner environment -workflow-runner-cern: environment-cern.yml - micromamba env create -p ./.venv --file environment-cern.yml -y - -# Development environment -dev-env: dev-env.yml ai/setup.py - micromamba env create -p ./.venv-dev --file dev-env.yml -y - micromamba run -p ./.venv-dev python -m pip install -e ./ai - -# Create pytorch env under ./ai/ folder -ai-env: ai/env-files/pytorch-lock.yml ai/setup.py - micromamba env create -p ./ai/.venv-pytorch --file ai/env-files/pytorch-lock.yml -y - micromamba run -p ./ai/.venv-pytorch python -m pip install -e ./ai - -lock-ai: ai/env-files/pytorch-env.yml ai/env-files/pytorch-env-gpu.yml - @echo "NOTE: Run this command from *whitin* ./.venv where conda-lock is available!" - @echo - @echo "Lock for pytorch CPU" - conda-lock lock --micromamba -f ai/env-files/pytorch-env.yml --lockfile ai/env-files/pytorch-lock.yml - @echo - @echo "Lock for pytorch GPU" - @# Workaround: https://github.com/conda/conda-lock/issues/283#issue-1450199283 - conda-lock lock --micromamba -f ai/env-files/pytorch-env-gpu.yml - mv conda-lock.yml ai/env-files/pytorch-gpu-lock.yml +# Install PyTorch env (GPU support) +torch-env: env-files/torch/generic_torch.sh + env ENV_NAME=.venv-pytorch \ + bash -c 'bash env-files/torch/generic_torch.sh' + .venv-pytorch/bin/horovodrun --check-build + +# Install PyTorch env (without GPU support: Horovod has not NCCL support) +torch-env-cpu: env-files/torch/generic_torch.sh + env ENV_NAME=.venv-pytorch \ + NO_CUDA=1 \ + bash -c 'bash env-files/torch/generic_torch.sh' + .venv-pytorch/bin/horovodrun --check-build + +# Install TensorFlow env (GPU support) +tensorflow-env: env-files/tensorflow/generic_tf.sh + env ENV_NAME=.venv-tf \ + bash -c 'bash env-files/tensorflow/generic_tf.sh' + @#.venv-tf/bin/horovodrun --check-build + +# Install TensorFlow env (without GPU support: Horovod has not NCCL support) +tensorflow-env-cpu: env-files/tensorflow/generic_tf.sh + env ENV_NAME=.venv-tf \ + NO_CUDA=1 \ + bash -c 'bash env-files/tensorflow/generic_tf.sh' + @#.venv-tf/bin/horovodrun --check-build + +test: + .venv-pytorch/bin/pytest -v tests/ -m "not slurm" + +test-jsc: tests/run_on_jsc.sh + bash tests/run_on_jsc.sh + +torch-gpu-mamba: env-files/torch/pytorch-env-gpu.yml + micromamba env create -p ./.venv-pytorch --file env-files/torch/pytorch-env-gpu.yml -y + micromamba run -p ./.venv-pytorch python -m pip install -e .[dev] + +# Install PyTorch env (GPU support) on Juelich Super Computer (tested on HDFML system) +torch-gpu-jsc: env-files/torch/createEnvJSC.sh env-files/torch/generic_torch.sh + sh env-files/torch/createEnvJSC.sh + +# Install Tensorflow env (GPU support) on Juelich Super Computer (tested on HDFML system) +tf-gpu-jsc: env-files/tensorflow/createEnvJSCTF.sh env-files/tensorflow/generic_tf.sh + sh env-files/tensorflow/createEnvJSCTF.sh + +# Install PyTorch env (CPU only) +torch-cpu-mamba: env-files/torch/pytorch-env-cpu.yml + micromamba env create -p ./.venv-pytorch --file env-files/torch/pytorch-env-cpu.yml -y + micromamba run -p ./.venv-pytorch python -m pip install -e .[dev] + +# Install TensorFlow 2.13. Creates ./.venv-tf folder. +# Ref: https://www.tensorflow.org/install/pip#step-by-step_instructions +tf-2.13: env-files/tensorflow/tensorflow-2.13.yml + echo "Installing TensorFlow 2.13 env" + micromamba env create -p ./.venv-tf --file env-files/tensorflow/tensorflow-2.13.yml -y + micromamba run -p ./.venv-tf pip install nvidia-cudnn-cu11==8.6.0.163 + + mkdir -p ./.venv-tf/etc/conda/activate.d + echo 'CUDNN_PATH=$$(dirname $$(python -c "import nvidia.cudnn;print(nvidia.cudnn.__file__)"))' >> ./.venv-tf/etc/conda/activate.d/env_vars.sh + echo 'export LD_LIBRARY_PATH=$$CUDNN_PATH/lib:$$CONDA_PREFIX/lib/:$$LD_LIBRARY_PATH' >> ./.venv-tf/etc/conda/activate.d/env_vars.sh + printf 'export XLA_FLAGS=--xla_gpu_cuda_data_dir=$$CONDA_PREFIX/lib/\n' >> ./.venv-tf/etc/conda/activate.d/env_vars.sh + + micromamba run -p ./.venv-tf pip install --upgrade pip + micromamba run -p ./.venv-tf pip install tensorflow==2.13.* + micromamba run -p ./.venv-tf pip install -e . + + mkdir -p ./.venv-tf/lib/nvvm/libdevice + cp ./.venv-tf/lib/libdevice.10.bc ./.venv-tf/lib/nvvm/libdevice/ + +# Install TensorFlow 2.11. Creates ./.venv-tf folder. +# Ref: https://skeptric.com/tensorflow-conda/ +tf-2.11: env-files/tensorflow/tensorflow-2.11.yml + micromamba env create -p ./.venv-tf --file env-files/tensorflow/tensorflow-2.11.yml -y + @# Env variables + mkdir -p ./.venv-tf/etc/conda/activate.d + micromamba run -p ./.venv-tf echo 'export LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:$$CONDA_PREFIX/lib/' >> ./.venv-tf/etc/conda/activate.d/env_vars.sh + micromamba run -p ./.venv-tf echo 'export XLA_FLAGS=--xla_gpu_cuda_data_dir=$$CONDA_PREFIX/lib/' >> ./.venv-tf/etc/conda/activate.d/env_vars.sh + @# Add library + mkdir -p ./.venv-tf/lib/nvvm/libdevice/ + cp ./.venv-tf/lib/libdevice.10.bc ./.venv-tf/lib/nvvm/libdevice/ + micromamba run -p ./.venv-tf pip install --upgrade pip + micromamba run -p ./.venv-tf pip install tensorflow==2.11.0 + micromamba run -p ./.venv-tf pip install -e . + +# Install TensorFlow 2.10. Creates ./.venv-tf folder. +# Ref: https://phoenixnap.com/kb/how-to-install-tensorflow-ubuntu +tf-2.10: env-files/tensorflow/tensorflow-2.10.yml + micromamba env create -p ./.venv-tf --file env-files/tensorflow/tensorflow-2.10.yml -y + mkdir -p ./.venv-tf/etc/conda/activate.d + micromamba run -p ./.venv-tf echo 'export LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:$$CONDA_PREFIX/lib/' >> ./.venv-tf/etc/conda/activate.d/env_vars.sh + micromamba run -p ./.venv-tf pip install --upgrade pip + micromamba run -p ./.venv-tf pip install tensorflow==2.10 + micromamba run -p ./.venv-tf pip install -e . + +# Install TensorFlow 2.13 for CPU only systems. Creates ./.venv-tf folder. +tf-2.13-cpu: env-files/tensorflow/tensorflow-2.13-cpu.yml + echo "Installing TensorFlow 2.13 env" + micromamba env create -p ./.venv-tf --file env-files/tensorflow/tensorflow-2.13-cpu.yml -y + micromamba run -p ./.venv-tf pip install --upgrade pip + micromamba run -p ./.venv-tf pip install tensorflow==2.13.* + micromamba run -p ./.venv-tf pip install -e . + +# # Install PyTorch env (GPU support) +# torch-env2: +# python3 -m venv .venv-pytorch +# .venv-pytorch/bin/pip install -e .[dev,torch] +# @# Install horovod AFTER torch +# @# https://github.com/horovod/horovod/pull/3998 +# env HOROVOD_CPU_OPERATIONS=MPI \ +# HOROVOD_GPU_ALLREDUCE=NCCL \ +# HOROVOD_NCCL_LINK=SHARED \ +# HOROVOD_NCCL_HOME=$$EBROOTNCCL \ +# HOROVOD_WITH_PYTORCH=1 \ +# HOROVOD_WITHOUT_TENSORFLOW=1 \ +# HOROVOD_WITHOUT_MXNET=1 \ +# bash -c '.venv-pytorch/bin/pip install --no-cache-dir git+https://github.com/thomas-bouvier/horovod.git@compile-cpp17' +# .venv-pytorch/bin/horovodrun --check-build + +# # Install PyTorch env (Horovod has not NCCL support) +# torch-env-cpu: +# python3 -m venv .venv-pytorch +# .venv-pytorch/bin/pip install -e .[dev,torch-cpu] +# @# Install horovod AFTER torch +# @# https://github.com/horovod/horovod/pull/3998 +# env HOROVOD_WITH_PYTORCH=1 \ +# HOROVOD_WITHOUT_TENSORFLOW=1 \ +# HOROVOD_WITHOUT_MXNET=1 \ +# bash -c '.venv-pytorch/bin/pip install --no-cache-dir git+https://github.com/thomas-bouvier/horovod.git@compile-cpp17' +# .venv-pytorch/bin/horovodrun --check-build + + +# # Install PyTorch env (without GPU support: Horovod has not NCCL support) +# torch-env-cpu: +# env ENV_NAME=.venv-pytorch \ +# NO_CUDA=1 \ +# bash -c 'bash env-files/torch/generic_torch.sh' +# .venv-pytorch/bin/horovodrun --check-build + +# # Install TensorFlow env (GPU support) +# tensorflow-env: +# python3 -m venv .venv-tf +# .venv-tf/bin/pip install -e .[dev,tensorflow] +# env HOROVOD_GPU=CUDA \ +# HOROVOD_GPU_OPERATIONS=NCCL \ +# HOROVOD_WITH_TENSORFLOW=1 \ +# bash -c '.venv-tf/bin/pip install --no-cache-dir horovod[tensorflow,keras]' \ No newline at end of file diff --git a/README.md b/README.md index 2ab0fbaa..8440edce 100644 --- a/README.md +++ b/README.md @@ -1,140 +1,159 @@ -# PoC for AI-centric digital twin workflows +# itwinai [![GitHub Super-Linter](https://github.com/interTwin-eu/T6.5-AI-and-ML/actions/workflows/lint.yml/badge.svg)](https://github.com/marketplace/actions/super-linter) [![GitHub Super-Linter](https://github.com/interTwin-eu/T6.5-AI-and-ML/actions/workflows/check-links.yml/badge.svg)](https://github.com/marketplace/actions/markdown-link-check) - [![SQAaaS source code](https://github.com/EOSC-synergy/itwinai.assess.sqaaas/raw/main/.badge/status_shields.svg)](https://sqaaas.eosc-synergy.eu/#/full-assessment/report/https://raw.githubusercontent.com/eosc-synergy/itwinai.assess.sqaaas/main/.report/assessment_output.json) + [![SQAaaS source code](https://github.com/EOSC-synergy/itwinai.assess.sqaaas/raw/dev/.badge/status_shields.svg)](https://sqaaas.eosc-synergy.eu/#/full-assessment/report/https://raw.githubusercontent.com/eosc-synergy/itwinai.assess.sqaaas/dev/.report/assessment_output.json) -See the latest version of our [docs](https://intertwin-eu.github.io/itwinai/) +See the latest version of our [docs](https://itwinai.readthedocs.io/) for a quick overview of this platform for advanced AI/ML workflows in digital twin applications. -If you want to integrate a new use case, you can follow this -[step-by-step guide](https://intertwin-eu.github.io/itwinai/docs/How-to-use-this-software.html). - ## Installation Requirements: -- Linux, macOS environment. Windows was never tested. +- Linux environment. Windows and macOS were never tested. -### Micromamba installation +### Python virtual environment -To manage Conda environments we use micromamba, a light weight version of conda. +Depending on your environment, there are different ways to +select a specific python version. -It is suggested to refer to the -[Manual installation guide](https://mamba.readthedocs.io/en/latest/installation.html#manual-installation). +#### Laptop or GPU node -Consider that Micromamba can eat a lot of space when building environments because packages are cached on -the local filesystem after being downloaded. To clear cache you can use `micromamba clean -a`. -Micromamba data are kept under the `$HOME` location. However, in some systems, `$HOME` has a limited storage -space and it would be cleverer to install Micromamba in another location with more storage space. -Thus by changing the `$MAMBA_ROOT_PREFIX` variable. See a complete installation example for Linux below, where the -default `$MAMBA_ROOT_PREFIX` is overridden: +If you are working on a laptop +or on a simple on-prem setup, you could consider using +[pyenv](https://github.com/pyenv/pyenv). See the +[installation instructions](https://github.com/pyenv/pyenv?tab=readme-ov-file#installation). If you are using pyenv, +make sure to read [this](https://github.com/pyenv/pyenv/wiki#suggested-build-environment). -```bash -cd $HOME +#### Install itwinai environment -# Download micromamba (This command is for Linux Intel (x86_64) systems. Find the right one for your system!) -curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bin/micromamba +Regardless of how you loaded your environment, you can create the +python virtual environments with the following commands. +Once the correct Python version is loaded, create the virtual +environments using our pre-make Makefile: -# Install micromamba in a custom directory -MAMBA_ROOT_PREFIX='my-mamba-root' -./bin/micromamba shell init $MAMBA_ROOT_PREFIX +```bash +make torch-env # or make torch-env-cpu +make tensorflow-env # or make tensorflow-env-cpu -# To invoke micromamba from Makefile, you need to add explicitly to $PATH -echo 'PATH="$(dirname $MAMBA_EXE):$PATH"' >> ~/.bashrc +# Juelich supercomputer +make torch-gpu-jsc +make tf-gpu-jsc ``` -**Reference**: [Micromamba installation guide](https://mamba.readthedocs.io/en/latest/installation.html#micromamba). +## Environment setup -### Workflow orchestrator +Requirements: + +- Linux environment. Windows and macOS were never tested. +- VS Code, for development. -Install the (custom) orchestrator virtual environment. +### TensorFlow + +Installation: ```bash -source ~/.bashrc -# Create local env -make +# Install TensorFlow 2.13 +make tensorflow-env # Activate env -micromamba activate ./.venv +source .venv-tf/bin/activate ``` -To run tests on workflows use: +A CPU-only version is available at the target `tensorflow-env-cpu`. + +### PyTorch (+ Lightning) + +Installation: ```bash -# Activate env -micromamba activate ./.venv +# Install PyTorch + lightning +make torch-env -pytest tests/ +# Activate env +source .venv-pytorch/bin/activate ``` -### Documentation folder +A CPU-only version is available at the target `torch-env-cpu`. -Documentation for this repository is maintained under `./docs` location. -If you are using code from a previous release, you can build the docs webpage -locally using [these instructions](docs/README#building-and-previewing-your-site-locally). +### Development environment -## Development env setup +This is for developers only. To have it, update the installed `itwinai` package +adding the `dev` extra: -Requirements: +```bash +pip install -e .[dev] +``` -- Linux, macOS environment. Windows was never tested. -- Micromamba: see the installation instructions above. -- VS Code, for development. +#### Test with `pytest` -Installation: +Do this only if you are a developer wanting to test your code with pytest. -```bash -make dev-env +First, you need to create virtual environments both for torch and tensorflow. +For instance, you can use: -# Activate env -micromamba activate ./.venv-dev +```bash +make torch-env-cpu +make tensorflow-env-cpu ``` -To run tests on itwinai package: +To select the name of the torch and tf environments you can set the following +environment variables, which allow to run the tests in environments with +custom names which are different from `.venv-pytorch` and `.venv-tf`. ```bash -# Activate env -micromamba activate ./.venv-dev - -pytest tests/ai/ +export TORCH_ENV="my_torch_env" +export TF_ENV="my_tf_env" ``` -## AI environment setup +Functional tests (marked with `pytest.mark.functional`) will be executed under +`/tmp/pytest` location to guarantee they are run in a clean environment. -Requirements: - -- Linux, macOS environment. Windows was never tested. -- Micromamba: see the installation instructions above. -- VS Code, for development. +To run functional tests use: -**NOTE**: this environment gets automatically setup when a workflow is executed! +```bash +pytest -v tests/ -m "functional" +``` -However, you can also set it up explicitly with: +To run all tests on itwinai package: ```bash -make ai-env - -# Activate env -micromamba activate ./ai/.venv-pytorch +make test ``` -### Updating the environment files +Run tests in JSC virtual environments: + +```bash +make test-jsc +``` -The files under `ai/env-files/` are of two categories: +### Micromamba installation (deprecated) -- Simple environment definition, such as `pytorch-env.yml` -and `pytorch-env-gpu.yml` -- Lockfiles, such as `pytorch-lock.yml` and `pytorch-gpu-lock.yml`, -generated by [`conda-lock`](https://conda.github.io/conda-lock/cli/gen/). +To manage Conda environments we use micromamba, a light weight version of conda. -**When you install the ai environment, install it from the lock file!** +It is suggested to refer to the +[Manual installation guide](https://mamba.readthedocs.io/en/latest/installation/micromamba-installation.html#manual-installation). -When the "simple" environment file (e.g., `pytorch-env.yml`) changes, -lock it with [`conda-lock`](https://conda.github.io/conda-lock/cli/gen/): +Consider that Micromamba can eat a lot of space when building environments because packages are cached on +the local filesystem after being downloaded. To clear cache you can use `micromamba clean -a`. +Micromamba data are kept under the `$HOME` location. However, in some systems, `$HOME` has a limited storage +space and it would be cleverer to install Micromamba in another location with more storage space. +Thus by changing the `$MAMBA_ROOT_PREFIX` variable. See a complete installation example for Linux below, where the +default `$MAMBA_ROOT_PREFIX` is overridden: ```bash -micromamba activate ./.venv +cd $HOME -make lock-ai +# Download micromamba (This command is for Linux Intel (x86_64) systems. Find the right one for your system!) +curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bin/micromamba + +# Install micromamba in a custom directory +MAMBA_ROOT_PREFIX='my-mamba-root' +./bin/micromamba shell init $MAMBA_ROOT_PREFIX + +# To invoke micromamba from Makefile, you need to add explicitly to $PATH +echo 'PATH="$(dirname $MAMBA_EXE):$PATH"' >> ~/.bashrc ``` + +**Reference**: [Micromamba installation guide](https://mamba.readthedocs.io/en/latest/installation/micromamba-installation.html). diff --git a/ai/README.md b/ai/README.md deleted file mode 100644 index c2be5813..00000000 --- a/ai/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# AI workflows (T6.5) - -Read all the instructions about the AI environment -[here](https://github.com/interTwin-eu/T6.5-AI-and-ML/#ai-environment-setup). - -At the moment only PyTorch is supported. diff --git a/ai/env-files/pytorch-env-gpu.yml b/ai/env-files/pytorch-env-gpu.yml deleted file mode 100644 index 73661948..00000000 --- a/ai/env-files/pytorch-env-gpu.yml +++ /dev/null @@ -1,41 +0,0 @@ -# Resources: -# - https://pytorch.org/get-started/previous-versions/ -# - https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#creating-an-environment-file-manually - -name: ai-training-gpu -channels: - - pytorch - - nvidia - - conda-forge -dependencies: - - python=3.9.12 - - pytorch::pytorch=1.13.1 - - pytorch::torchvision=0.14.1 - - pytorch::pytorch-cuda=11.7 - - pytorch::torchaudio=0.13.1 - - cudatoolkit=10.1 - # Either use pytorch-cuda or cpuonly, depending on GPU availability - # - pytorch::pytorch-cuda=11.6 - # - pytorch::cpuonly - - lightning=2.0.0 - - torchmetrics - - mlflow>=2 - - typer - - rich - - pyyaml - - omegaconf - - - typing-extensions=4.5.0 - - typing_extensions=4.5.0 - - # Pip dependencies should be given in the setup.py - # If given here, conda-lock breaks - -# Supported platforms (conda-lock) -platforms: - - linux-64 - # - osx-64 - # - win-64 - # - osx-arm64 # For Apple Silicon, e.g. M1/M2 - # - linux-aarch64 # aka arm64, use for Docker on Apple Silicon - # - linux-ppc64le diff --git a/ai/env-files/pytorch-env.yml b/ai/env-files/pytorch-env.yml deleted file mode 100644 index 1266fec1..00000000 --- a/ai/env-files/pytorch-env.yml +++ /dev/null @@ -1,36 +0,0 @@ -# Resources: -# - https://pytorch.org/get-started/previous-versions/ -# - https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#creating-an-environment-file-manually - -name: ai-training -channels: - - pytorch - # - nvidia - - conda-forge -dependencies: - - python=3.9.12 - - pytorch=1.13.1 - - torchvision=0.14.1 - - cpuonly - - lightning=2.0.0 - - torchmetrics - - mlflow>=2 - - typer - - rich - - pyyaml - - omegaconf - - - typing-extensions=4.5.0 - - typing_extensions=4.5.0 - - # Pip dependencies should be given in the setup.py - # If given here, conda-lock breaks - -# Supported platforms (conda-lock) -platforms: - - linux-64 - - osx-64 - - win-64 - - osx-arm64 # For Apple Silicon, e.g. M1/M2 - - linux-aarch64 # aka arm64, use for Docker on Apple Silicon - # - linux-ppc64le \ No newline at end of file diff --git a/ai/env-files/pytorch-gpu-lock.yml b/ai/env-files/pytorch-gpu-lock.yml deleted file mode 100644 index 47898f91..00000000 --- a/ai/env-files/pytorch-gpu-lock.yml +++ /dev/null @@ -1,4164 +0,0 @@ -# This lock file was generated by conda-lock (https://github.com/conda/conda-lock). DO NOT EDIT! -# -# A "lock file" contains a concrete list of package versions (with checksums) to be installed. Unlike -# e.g. `conda env create`, the resulting environment will not change as new package versions become -# available, unless you explicitly update the lock file. -# -# Install this environment as "YOURENV" with: -# conda-lock install -n YOURENV --file conda-lock.yml -# To update a single package to the latest version compatible with the version constraints in the source: -# conda-lock lock --lockfile conda-lock.yml --update PACKAGE -# To re-solve the entire environment, e.g. after changing a version constraint in the source file: -# conda-lock -f ai/env-files/pytorch-env-gpu.yml --lockfile conda-lock.yml -version: 1 -metadata: - content_hash: - linux-64: eb95df0cc7c44276414c17939ab633c653cbe2f972e6e7b465c4cbb6f2a68272 - channels: - - url: pytorch - used_env_vars: [] - - url: nvidia - used_env_vars: [] - - url: conda-forge - used_env_vars: [] - platforms: - - linux-64 - sources: - - ai/env-files/pytorch-env-gpu.yml -package: -- name: _libgcc_mutex - version: '0.1' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - hash: - md5: d7c89558ba9fa0495403155b64376d81 - sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 - category: main - optional: false -- name: ca-certificates - version: 2023.5.7 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2023.5.7-hbcca054_0.conda - hash: - md5: f5c65075fc34438d5b456c7f3f5ab695 - sha256: 0cf1bb3d0bfc5519b60af2c360fa4888fb838e1476b1e0f65b9dbc48b45c7345 - category: main - optional: false -- name: cuda-cudart - version: 11.7.99 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/cuda-cudart-11.7.99-0.tar.bz2 - hash: - md5: 79859ac9109109826d77e5e2bbbe18b5 - sha256: e75a62cf7cebdd7773c8deb148e06030714ed4c5fb6c8dc3f7792bb7b6e8ce6c - category: main - optional: false -- name: cuda-cupti - version: 11.7.101 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/cuda-cupti-11.7.101-0.tar.bz2 - hash: - md5: 099e29408a247a0a50bc976dc5e716b0 - sha256: 11d6d31fec7f54a9390d167741da51c05fa4fa31ce64eac250d069570c06cf2c - category: main - optional: false -- name: cuda-nvrtc - version: 11.7.99 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/cuda-nvrtc-11.7.99-0.tar.bz2 - hash: - md5: f56ebbd9aa130d0fae45079d8bf76617 - sha256: 76eeb17cbdc9767e85fd3e20a8e06f1999aa190fc042265da4eabf550aa12ac9 - category: main - optional: false -- name: cuda-nvtx - version: 11.7.91 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/cuda-nvtx-11.7.91-0.tar.bz2 - hash: - md5: 87381e82875d3b341e2d48a5c4d26e2b - sha256: 9458059b0f5e1d28a09d9cb32e441f01c050e2d92630020011fa009b98e0f445 - category: main - optional: false -- name: ld_impl_linux-64 - version: '2.40' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda - hash: - md5: 7aca3059a1729aa76c597603f10b0dd3 - sha256: f6cc89d887555912d6c61b295d398cff9ec982a3417d38025c45d5dd9b9e79cd - category: main - optional: false -- name: libcublas - version: 11.10.3.66 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libcublas-11.10.3.66-0.tar.bz2 - hash: - md5: 1d3211d2deb549b84f6fdaf96f7cbb88 - sha256: 14c30da20a9c1519ec5358f493222913ede2fa1048b9c65cfdf29e4c1df4b057 - category: main - optional: false -- name: libcufft - version: 10.7.2.124 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libcufft-10.7.2.124-h4fbf590_0.tar.bz2 - hash: - md5: dc69f1daa40372e9b869064ab926ee26 - sha256: cd57e77ca3ec6aee4dc0915fc0db96091a0b7600f130a57ac24a05ff0bfce832 - category: main - optional: false -- name: libcufile - version: 1.6.1.9 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libcufile-1.6.1.9-0.tar.bz2 - hash: - md5: 45b4fa3189d9d326367497cda7ef6ace - sha256: aa131313764309edf16cef60267afff4c9b0a558aa9c56ebe3641cf362f76996 - category: main - optional: false -- name: libcurand - version: 10.3.2.106 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libcurand-10.3.2.106-0.tar.bz2 - hash: - md5: 7a790d8cff49b91c7ca38c87c8c99b15 - sha256: a5aaae9174230d78a9fe6ef8e2c42ffc4f64c82ba105c94d0730e0907c23fc07 - category: main - optional: false -- name: libcusolver - version: 11.4.0.1 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libcusolver-11.4.0.1-0.tar.bz2 - hash: - md5: c4de49e8b2f01065962f2fec81fb7058 - sha256: fbcd211720e29bdb8a42a4360de6d73c2c6cc44040bedefc45c9a8ffb1f906c0 - category: main - optional: false -- name: libcusparse - version: 11.7.4.91 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libcusparse-11.7.4.91-0.tar.bz2 - hash: - md5: 9ca85f813fbc5e8b811a1ad4ca451911 - sha256: c0b0684bc8ad02af7a91506533203eb68467d6ad1fbf3be4aacaa9aa39c89a3b - category: main - optional: false -- name: libgfortran5 - version: 12.2.0 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2 - hash: - md5: 164b4b1acaedc47ee7e658ae6b308ca3 - sha256: 03ea784edd12037dc3a7a0078ff3f9c3383feabb34d5ba910bb2fd7a21a2d961 - category: main - optional: false -- name: libnpp - version: 11.7.4.75 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libnpp-11.7.4.75-0.tar.bz2 - hash: - md5: 0a55ecd0b813fe5774ede297007e5413 - sha256: 34091bd3ca4910ce5f7715637863a76f09739782090586dff9767f1632a631dd - category: main - optional: false -- name: libnvjpeg - version: 11.8.0.2 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libnvjpeg-11.8.0.2-0.tar.bz2 - hash: - md5: 32a0a604688ee485878887ff4c086f77 - sha256: bdd39dcb18ee6fcbd41fc4fa4b45c8e368abb18289fe164151d83080b8128eb5 - category: main - optional: false -- name: libstdcxx-ng - version: 12.2.0 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2 - hash: - md5: 1030b1f38c129f2634eae026f704fe60 - sha256: 0289e6a7b9a5249161a3967909e12dcfb4ab4475cdede984635d3fb65c606f08 - category: main - optional: false -- name: python_abi - version: '3.9' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-3_cp39.conda - hash: - md5: 0dd193187d54e585cac7eab942a8847e - sha256: 89e8c4436dd04d8b4a0c13c508e930be56973a480a9714171969de953bdafd3a - category: main - optional: false -- name: pytorch-mutex - version: '1.0' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/pytorch/noarch/pytorch-mutex-1.0-cuda.tar.bz2 - hash: - md5: a948316e36fb5b11223b3fcfa93f8358 - sha256: c16316183f51b74ca5eee4dcb8631052f328c0bbf244176734a0b7d390b81ee3 - category: main - optional: false -- name: tzdata - version: 2023c - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2023c-h71feb2d_0.conda - hash: - md5: 939e3e74d8be4dac89ce83b20de2492a - sha256: 0449138224adfa125b220154408419ec37c06b0b49f63c5954724325903ecf55 - category: main - optional: false -- name: cuda-libraries - version: 11.7.1 - manager: conda - platform: linux-64 - dependencies: - cuda-cudart: '>=11.7.99' - cuda-nvrtc: '>=11.7.99' - libcublas: '>=11.10.3.66' - libcufft: '>=10.7.2.91' - libcufile: '>=1.3.1.18' - libcurand: '>=10.2.10.91' - libcusolver: '>=11.4.0.1' - libcusparse: '>=11.7.4.91' - libnpp: '>=11.7.4.75' - libnvjpeg: '>=11.8.0.2' - url: https://conda.anaconda.org/nvidia/linux-64/cuda-libraries-11.7.1-0.tar.bz2 - hash: - md5: 6fc99cc9d2e7d6d2da1c10c5e140ab8c - sha256: 0e28c0d2f6462437dfe21c3a8e17351adc76f9882a5f1e8be8dce3d6bf8f19a9 - category: main - optional: false -- name: libgfortran-ng - version: 12.2.0 - manager: conda - platform: linux-64 - dependencies: - libgfortran5: 12.2.0 - url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2 - hash: - md5: cd7a806282c16e1f2d39a7e80d3a3e0d - sha256: c7d061f323e80fbc09564179073d8af303bf69b953b0caddcf79b47e352c746f - category: main - optional: false -- name: cuda-runtime - version: 11.7.1 - manager: conda - platform: linux-64 - dependencies: - cuda-libraries: '>=11.7.1' - url: https://conda.anaconda.org/nvidia/linux-64/cuda-runtime-11.7.1-0.tar.bz2 - hash: - md5: e88cf61af312d2621a0224d431be511b - sha256: 99fccfabca4fda89d71fb92a42b77052f2b43e8ad9207bca1f88c3c518be3f82 - category: main - optional: false -- name: pytorch-cuda - version: '11.7' - manager: conda - platform: linux-64 - dependencies: - cuda-cudart: '>=11.7,<11.8' - cuda-cupti: '>=11.7,<11.8' - cuda-libraries: '>=11.7,<11.8' - cuda-nvrtc: '>=11.7,<11.8' - cuda-nvtx: '>=11.7,<11.8' - cuda-runtime: '>=11.7,<11.8' - libcublas: '>=11.10.1.25,<11.11.3.6' - libcufft: '>=10.7.2.50,<10.9.0.58' - libcusolver: '>=11.3.5.50,<11.4.1.48' - libcusparse: '>=11.7.3.50,<11.7.5.86' - libnpp: '>=11.7.3.21,<11.8.0.86' - libnvjpeg: '>=11.7.2.34,<11.9.0.86' - url: https://conda.anaconda.org/pytorch/linux-64/pytorch-cuda-11.7-h778d358_5.tar.bz2 - hash: - md5: bba2918836f9fe6c1b518256dda67dee - sha256: 526683e5eff9047a0e1a2d26da7712f5ee18fc487555e6b99decca32400890a8 - category: main - optional: false -- name: _openmp_mutex - version: '4.5' - manager: conda - platform: linux-64 - dependencies: - _libgcc_mutex: '0.1' - llvm-openmp: '>=9.0.1' - url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2 - hash: - md5: 562b26ba2e19059551a811e72ab7f793 - sha256: 84a66275da3a66e3f3e70e9d8f10496d807d01a9e4ec16cd2274cc5e28c478fc - category: main - optional: false -- name: libgcc-ng - version: 12.2.0 - manager: conda - platform: linux-64 - dependencies: - _libgcc_mutex: '0.1' - _openmp_mutex: '>=4.5' - url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.2.0-h65d4601_19.tar.bz2 - hash: - md5: e4c94f80aef025c17ab0828cd85ef535 - sha256: f3899c26824cee023f1e360bd0859b0e149e2b3e8b1668bc6dd04bfc70dcd659 - category: main - optional: false -- name: aws-c-common - version: 0.8.19 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.8.19-hd590300_0.conda - hash: - md5: 81bd50906818d08c2f98d6d9f94cbd02 - sha256: cbbec8482596a93cfe14ec8f05d27fd7a1807995b407fa057790db8d41b2d80b - category: main - optional: false -- name: bzip2 - version: 1.0.8 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2 - hash: - md5: a1fd65c7ccbf10880423d82bca54eb54 - sha256: cb521319804640ff2ad6a9f118d972ed76d86bea44e5626c09a13d38f562e1fa - category: main - optional: false -- name: c-ares - version: 1.19.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.19.1-hd590300_0.conda - hash: - md5: e8c18d865be43e2fb3f7a145b6adf1f5 - sha256: c4276b1a0e8f18ab08018b1881666656742b325e0fcf2354f714e924d28683b6 - category: main - optional: false -- name: cudatoolkit - version: 11.7.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=10.3.0' - libstdcxx-ng: '>=10.3.0' - url: https://conda.anaconda.org/nvidia/linux-64/cudatoolkit-11.7.0-hd8887f6_10.tar.bz2 - hash: - md5: fa317a2c53e40808fbeeeb2800519616 - sha256: 31bad17839c35278203afad9c18ed15d7dd7dece11756aecf83368e1cbd9c850 - category: main - optional: false -- name: gettext - version: 0.21.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2 - hash: - md5: 14947d8770185e5153fdd04d4673ed37 - sha256: 4fcfedc44e4c9a053f0416f9fc6ab6ed50644fca3a761126dbd00d09db1f546a - category: main - optional: false -- name: gflags - version: 2.2.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - libstdcxx-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-he1b5a44_1004.tar.bz2 - hash: - md5: cddaf2c63ea4a5901cf09524c490ecdc - sha256: a853c0cacf53cfc59e1bca8d6e5cdfe9f38fce836f08c2a69e35429c2a492e77 - category: main - optional: false -- name: gmp - version: 6.2.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - libstdcxx-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.2.1-h58526e2_0.tar.bz2 - hash: - md5: b94cf2db16066b242ebd26db2facbd56 - sha256: 07a5319e1ac54fe5d38f50c60f7485af7f830b036da56957d0bfb7558a886198 - category: main - optional: false -- name: icu - version: '72.1' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/icu-72.1-hcb278e6_0.conda - hash: - md5: 7c8d20d847bb45f56bd941578fcfa146 - sha256: e44cc00eec068e7f7a6dd117ba17bf5d57658729b7b841945546f82505138292 - category: main - optional: false -- name: jpeg - version: 9e - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h0b41bf4_3.conda - hash: - md5: c7a069243e1fbe9a556ed2ec030e6407 - sha256: 8f73194d09c9ea4a7e2b3562766b8d72125cc147b62c7cf83393e3a3bbfd581b - category: main - optional: false -- name: keyutils - version: 1.6.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 - hash: - md5: 30186d27e2c9fa62b45fb1476b7200e3 - sha256: 150c05a6e538610ca7c43beb3a40d65c90537497a4f6a5f4d15ec0451b6f5ebb - category: main - optional: false -- name: lame - version: '3.100' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2 - hash: - md5: a8832b479f93521a9e7b5b743803be51 - sha256: aad2a703b9d7b038c0f745b853c6bb5f122988fe1a7a096e0e606d9cbec4eaab - category: main - optional: false -- name: lerc - version: 4.0.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2 - hash: - md5: 76bbff344f0134279f225174e9064c8f - sha256: cb55f36dcd898203927133280ae1dc643368af041a48bcf7c026acb7c47b0c12 - category: main - optional: false -- name: libabseil - version: '20230125.2' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20230125.2-cxx17_h59595ed_2.conda - hash: - md5: f67106643beadfc737b94ca0bfd6d8e3 - sha256: 1778dc86603df24aaf6865f7f3e1ffc5c793a0f1fc4570add2a6ccb4c0a62785 - category: main - optional: false -- name: libbrotlicommon - version: 1.0.9 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2 - hash: - md5: 9194c9bf9428035a05352d031462eae4 - sha256: ddc961a36d498aaafd5b71078836ad5dd247cc6ba7924157f3801a2f09b77b14 - category: main - optional: false -- name: libcrc32c - version: 1.1.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - libstdcxx-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 - hash: - md5: c965a5aa0d5c1c37ffc62dff36e28400 - sha256: fd1d153962764433fe6233f34a72cdeed5dcf8a883a85769e8295ce940b5b0c5 - category: main - optional: false -- name: libdeflate - version: '1.17' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.17-h0b41bf4_0.conda - hash: - md5: 5cc781fd91968b11a8a7fdbee0982676 - sha256: f9983a8ea03531f2c14bce76c870ca325c0fddf0c4e872bff1f78bc52624179c - category: main - optional: false -- name: libev - version: '4.33' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2 - hash: - md5: 6f8720dff19e17ce5d48cfe7f3d2f0a3 - sha256: 8c9635aa0ea28922877dc96358f9547f6a55fc7e2eb75a556b05f1725496baf9 - category: main - optional: false -- name: libexpat - version: 2.5.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.5.0-hcb278e6_1.conda - hash: - md5: 6305a3dd2752c76335295da4e581f2fd - sha256: 74c98a563777ae2ad71f1f74d458a8ab043cee4a513467c159ccf159d0e461f3 - category: main - optional: false -- name: libffi - version: 3.4.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - hash: - md5: d645c6d2ac96843a2bfaccd2d62b3ac3 - sha256: ab6e9856c21709b7b517e940ae7028ae0737546122f83c2aa5d692860c3b149e - category: main - optional: false -- name: libiconv - version: '1.17' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2 - hash: - md5: b62b52da46c39ee2bc3c162ac7f1804d - sha256: 6a81ebac9f1aacdf2b4f945c87ad62b972f0f69c8e0981d68e111739e6720fd7 - category: main - optional: false -- name: libnsl - version: 2.0.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2 - hash: - md5: 39b1328babf85c7c3a61636d9cd50206 - sha256: 32f4fb94d99946b0dabfbbfd442b25852baf909637f2eed1ffe3baea15d02aad - category: main - optional: false -- name: libnuma - version: 2.0.16 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libnuma-2.0.16-h0b41bf4_1.conda - hash: - md5: 28bfe2cb11357ccc5be21101a6b7ce86 - sha256: 814a50cba215548ec3ebfb53033ffb9b3b070b2966570ff44910b8d9ba1c359d - category: main - optional: false -- name: libsodium - version: 1.0.18 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.18-h36c2ea0_1.tar.bz2 - hash: - md5: c3788462a6fbddafdb413a9f9053e58d - sha256: 53da0c8b79659df7b53eebdb80783503ce72fb4b10ed6e9e05cc0e9e4207a130 - category: main - optional: false -- name: libutf8proc - version: 2.8.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.8.0-h166bdaf_0.tar.bz2 - hash: - md5: ede4266dc02e875fe1ea77b25dd43747 - sha256: 49082ee8d01339b225f7f8c60f32a2a2c05fe3b16f31b554b4fb2c1dea237d1c - category: main - optional: false -- name: libuuid - version: 2.38.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - hash: - md5: 40b61aab5c7ba9ff276c41cfffe6b80b - sha256: 787eb542f055a2b3de553614b25f09eefb0a0931b0c87dbcce6efdfd92f04f18 - category: main - optional: false -- name: libwebp-base - version: 1.3.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.3.0-h0b41bf4_0.conda - hash: - md5: 0d4a7508d8c6c65314f2b9c1f56ad408 - sha256: ac3e073ea77803da71eb77e7fcef07defb345bda95eee3327c73ddf85b5714da - category: main - optional: false -- name: libzlib - version: 1.2.13 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2 - hash: - md5: f3f9de449d32ca9b9c66a22863c96f41 - sha256: 22f3663bcf294d349327e60e464a51cd59664a71b8ed70c28a9f512d10bc77dd - category: main - optional: false -- name: lz4-c - version: 1.9.4 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda - hash: - md5: 318b08df404f9c9be5712aaa5a6f0bb0 - sha256: 1b4c105a887f9b2041219d57036f72c4739ab9e9fe5a1486f094e58c76b31f5f - category: main - optional: false -- name: ncurses - version: '6.3' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2 - hash: - md5: 4acfc691e64342b9dae57cf2adc63238 - sha256: b801e8cf4b2c9a30bce5616746c6c2a4e36427f045b46d9fc08a4ed40a9f7065 - category: main - optional: false -- name: nettle - version: '3.6' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/nettle-3.6-he412f7d_0.tar.bz2 - hash: - md5: f050099af540c1c960c813b06bca89ad - sha256: d929f0c53f2bb74c8e3d97dc1c53cc76b7cec97837fcf87998fa3dd447f03b36 - category: main - optional: false -- name: openssl - version: 3.1.1 - manager: conda - platform: linux-64 - dependencies: - ca-certificates: '' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.1.1-hd590300_1.conda - hash: - md5: 2e1d7b458ac8f1e3ca4e18b77add6277 - sha256: 407d655643389bdb49266842a816815c981ae98f3513a6a2059b908b3abb380a - category: main - optional: false -- name: pthread-stubs - version: '0.4' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2 - hash: - md5: 22dad4df6e8630e8dff2428f6f6a7036 - sha256: 67c84822f87b641d89df09758da498b2d4558d47b920fd1d3fe6d3a871e000ff - category: main - optional: false -- name: rdma-core - version: '28.9' - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-28.9-h59595ed_1.conda - hash: - md5: aeffb7c06b5f65e55e6c637408dc4100 - sha256: 832f9393ab3144ce6468c6f150db9d398fad4451e96a8879afb3059f0c9902f6 - category: main - optional: false -- name: re2 - version: 2023.03.02 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/re2-2023.03.02-h8c504da_0.conda - hash: - md5: 206f8fa808748f6e90599c3368a1114e - sha256: 1727f893a352ca735fb96b09f9edf6fe18c409d65550fd37e8a192919e8c827b - category: main - optional: false -- name: snappy - version: 1.1.10 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.1.10-h9fff704_0.conda - hash: - md5: e6d228cd0bb74a51dd18f5bfce0b4115 - sha256: 02219f2382b4fe39250627dade087a4412d811936a5a445636b7260477164eac - category: main - optional: false -- name: xorg-libxau - version: 1.0.11 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.11-hd590300_0.conda - hash: - md5: 2c80dc38fface310c9bd81b17037fee5 - sha256: 309751371d525ce50af7c87811b435c176915239fc9e132b99a25d5e1703f2d4 - category: main - optional: false -- name: xorg-libxdmcp - version: 1.1.3 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2 - hash: - md5: be93aabceefa2fac576e971aef407908 - sha256: 4df7c5ee11b8686d3453e7f3f4aa20ceef441262b49860733066c52cfd0e4a77 - category: main - optional: false -- name: xz - version: 5.2.6 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 - hash: - md5: 2161070d867d1b1204ea749c8eec4ef0 - sha256: 03a6d28ded42af8a347345f82f3eebdd6807a08526d47899a42d62d319609162 - category: main - optional: false -- name: yaml - version: 0.2.5 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 - hash: - md5: 4cb3ad778ec2d5a7acbdf254eb1c42ae - sha256: a4e34c710eeb26945bdbdaba82d3d74f60a78f54a874ec10d373811a5d217535 - category: main - optional: false -- name: aws-c-cal - version: 0.5.26 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.5.26-hf677bf3_1.conda - hash: - md5: 7d00f2b22493e28400fdbea8dc110790 - sha256: 74f8fa528ca4096119f6b9de78fb6dd4f7a7345f10b8b1df3634d77be53ad01c - category: main - optional: false -- name: aws-c-compression - version: 0.2.16 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.2.16-hbad4bc6_7.conda - hash: - md5: d58359b64c6d1256c07eeaee753159e3 - sha256: 7a7df29971e9a50231bc0a954fc451052479bc2427df66a82014797418a7ffea - category: main - optional: false -- name: aws-c-sdkutils - version: 0.1.9 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.1.9-hbad4bc6_2.conda - hash: - md5: eee3831810e132b6caf45f03c3428363 - sha256: 4d6713d840f1767cb4989f1b0c5b4f7fea8ce18f68d575ba6bb8e9fac51750e9 - category: main - optional: false -- name: aws-checksums - version: 0.1.14 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.1.14-hbad4bc6_7.conda - hash: - md5: 916c2a86bf786aab811a60990f7538ed - sha256: 5b3c6230ec0f2880ca1738d169d74d7edeef4ad541a6d6460513b70c3a15bf85 - category: main - optional: false -- name: expat - version: 2.5.0 - manager: conda - platform: linux-64 - dependencies: - libexpat: 2.5.0 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-hcb278e6_1.conda - hash: - md5: 8b9b5aca60558d02ddaa09d599e55920 - sha256: 36dfeb4375059b3bba75ce9b38c29c69fd257342a79e6cf20e9f25c1523f785f - category: main - optional: false -- name: glog - version: 0.6.0 - manager: conda - platform: linux-64 - dependencies: - gflags: '>=2.2.2,<2.3.0a0' - libgcc-ng: '>=10.3.0' - libstdcxx-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/glog-0.6.0-h6f12383_0.tar.bz2 - hash: - md5: b31f3565cb84435407594e548a2fb7b2 - sha256: 888cbcfb67f6e3d88a4c4ab9d26c9a406f620c4101a35dc6d2dbadb95f2221d4 - category: main - optional: false -- name: gnutls - version: 3.6.13 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - libstdcxx-ng: '>=7.5.0' - nettle: '>=3.6,<3.7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/gnutls-3.6.13-h85f3911_1.tar.bz2 - hash: - md5: 7d1b6fff16c1431d96cb4934938799fd - sha256: 6c9307f0fedce2c4d060bba9ac888b300bc0912effab423d67b8e1b661a93305 - category: main - optional: false -- name: libbrotlidec - version: 1.0.9 - manager: conda - platform: linux-64 - dependencies: - libbrotlicommon: 1.0.9 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2 - hash: - md5: 4ae4d7795d33e02bd20f6b23d91caf82 - sha256: d88ba07c3be27c89cb4975cc7edf63ee7b1c62d01f70d5c3f7efeb987c82b052 - category: main - optional: false -- name: libbrotlienc - version: 1.0.9 - manager: conda - platform: linux-64 - dependencies: - libbrotlicommon: 1.0.9 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2 - hash: - md5: 04bac51ba35ea023dc48af73c1c88c25 - sha256: a0468858b2f647f51509a32040e93512818a8f9980f20b3554cccac747bcc4be - category: main - optional: false -- name: libedit - version: 3.1.20191231 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - ncurses: '>=6.2,<7.0.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2 - hash: - md5: 4d331e44109e3f0e19b4cb8f9b82f3e1 - sha256: a57d37c236d8f7c886e01656f4949d9dcca131d2a0728609c6f7fa338b65f1cf - category: main - optional: false -- name: libevent - version: 2.1.12 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-h3358134_0.conda - hash: - md5: c164eb2e0df905571d68f40ae957522d - sha256: 4fa9ffd6b627a9cbecc602fa60c52ef6e0a4020d4a45f1a18d8f300e17b18ace - category: main - optional: false -- name: libnghttp2 - version: 1.52.0 - manager: conda - platform: linux-64 - dependencies: - c-ares: '>=1.18.1,<2.0a0' - libev: '>=4.33,<4.34.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.0.8,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.52.0-h61bc06f_0.conda - hash: - md5: 613955a50485812985c059e7b269f42e - sha256: ecd6b08c2b5abe7d1586428c4dd257dcfa00ee53700d79cdc8bca098fdfbd79a - category: main - optional: false -- name: libpng - version: 1.6.39 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda - hash: - md5: e1c890aebdebbfbf87e2c917187b4416 - sha256: a32b36d34e4f2490b99bddbc77d01a674d304f667f0e62c89e02c961addef462 - category: main - optional: false -- name: libprotobuf - version: 3.21.12 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-3.21.12-h3eb15da_0.conda - hash: - md5: 4b36c68184c6c85d88c6e595a32a1ede - sha256: 760118d7879b5524e118db1c75cc2a5dfceb2c4940dcae94751a94786c8cf12b - category: main - optional: false -- name: libsqlite - version: 3.42.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.42.0-h2797004_0.conda - hash: - md5: fdaae20a1cf7cd62130a0973190a31b7 - sha256: 72e958870f49174ebc0ddcd4129e9a9f48de815f20aa3b553f136b514f29bb3a - category: main - optional: false -- name: libssh2 - version: 1.10.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libzlib: '>=1.2.12,<1.3.0a0' - openssl: '>=3.0.5,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-hf14f497_3.tar.bz2 - hash: - md5: d85acad4b47dff4e3def14a769a97906 - sha256: 9a9a01f35d2d50326eb8ca7c0a92d0c45b2d0f77d9ea117680c70094ff480c0c - category: main - optional: false -- name: libxcb - version: '1.13' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - pthread-stubs: '' - xorg-libxau: '' - xorg-libxdmcp: '' - url: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2 - hash: - md5: b3653fdc58d03face9724f602218a904 - sha256: 8d5d24cbeda9282dd707edd3156e5fde2e3f3fe86c802fa7ce08c8f1e803bfd9 - category: main - optional: false -- name: libxml2 - version: 2.11.4 - manager: conda - platform: linux-64 - dependencies: - icu: '>=72.1,<73.0a0' - libgcc-ng: '>=12' - libiconv: '>=1.17,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - xz: '>=5.2.6,<6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.11.4-h0d562d8_0.conda - hash: - md5: e46fad17d5fb57316b956f88dca765e4 - sha256: bc7fa8590c15ffdea5101075ce02de09441c9f378ac1d05e26510d12d25d7099 - category: main - optional: false -- name: pcre2 - version: '10.40' - manager: conda - platform: linux-64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - libgcc-ng: '>=12' - libzlib: '>=1.2.12,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2 - hash: - md5: 69e2c796349cd9b273890bee0febfe1b - sha256: 7a29ec847556eed4faa1646010baae371ced69059a4ade43851367a076d6108a - category: main - optional: false -- name: readline - version: '8.2' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - ncurses: '>=6.3,<7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda - hash: - md5: 47d31b792659ce70f470b5c82fdfb7a4 - sha256: 5435cf39d039387fbdc977b0a762357ea909a7694d9528ab40f005e9208744d7 - category: main - optional: false -- name: s2n - version: 1.3.44 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.3.44-h06160fa_0.conda - hash: - md5: 968cb0fca1249fe9778876201dd2b828 - sha256: 6b719f9fc5a53a34f6ce17ac22aa8bc70b526caf9cef72c4d01964e01cb156b6 - category: main - optional: false -- name: tk - version: 8.6.12 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - libzlib: '>=1.2.11,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2 - hash: - md5: 5b8c42eb62e9fc961af70bdd6a26e168 - sha256: 032fd769aad9d4cad40ba261ab222675acb7ec951a8832455fce18ef33fa8df0 - category: main - optional: false -- name: ucx - version: 1.14.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libnuma: '>=2.0.16,<3.0a0' - libstdcxx-ng: '>=12' - rdma-core: '>=28.9,<29.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/ucx-1.14.1-hf587318_2.conda - hash: - md5: 37b27851c8d5a9a98e61ecd36aa990a7 - sha256: 3cb73a2177457d092cbe1ac5ad1a7ea2c75c1080516776807014a5d991f146e6 - category: main - optional: false -- name: zlib - version: 1.2.13 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libzlib: 1.2.13 - url: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2 - hash: - md5: 4b11e365c0275b808be78b30f904e295 - sha256: 282ce274ebe6da1fbd52efbb61bd5a93dec0365b14d64566e6819d1691b75300 - category: main - optional: false -- name: zstd - version: 1.5.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h3eb15da_6.conda - hash: - md5: 6b63daed8feeca47be78f323e793d555 - sha256: fbe49a8c8df83c2eccb37c5863ad98baeb29796ec96f2c503783d7b89bf80c98 - category: main - optional: false -- name: aws-c-io - version: 0.13.21 - manager: conda - platform: linux-64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - s2n: '>=1.3.44,<1.3.45.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.13.21-h9fef7b8_5.conda - hash: - md5: 0e64949e8f740ceeb9f1d6255f314ab2 - sha256: 74844c9651627bb4274a8976d3ea077df7a6ab48cc2150db330fd0f39d0b85f5 - category: main - optional: false -- name: brotli-bin - version: 1.0.9 - manager: conda - platform: linux-64 - dependencies: - libbrotlidec: 1.0.9 - libbrotlienc: 1.0.9 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2 - hash: - md5: e5613f2bc717e9945840ff474419b8e4 - sha256: ab1994e03bdd88e4b27f9f802ac18e45ed29b92cce25e1fd86da43b89734950f - category: main - optional: false -- name: freetype - version: 2.12.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libpng: '>=1.6.39,<1.7.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda - hash: - md5: e1232042de76d24539a436d37597eb06 - sha256: 1eb913727b54e9aa63c6d9a1177db4e2894cee97c5f26910a2b61899d5ac904f - category: main - optional: false -- name: krb5 - version: 1.20.1 - manager: conda - platform: linux-64 - dependencies: - keyutils: '>=1.6.1,<2.0a0' - libedit: '>=3.1.20191231,<4.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - openssl: '>=3.0.7,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.20.1-h81ceb04_0.conda - hash: - md5: 89a41adce7106749573d883b2f657d78 - sha256: 51a346807ce981e1450eb04c3566415b05eed705bc9e6c98c198ec62367b7c62 - category: main - optional: false -- name: libglib - version: 2.76.3 - manager: conda - platform: linux-64 - dependencies: - gettext: '>=0.21.1,<1.0a0' - libffi: '>=3.4,<4.0a0' - libgcc-ng: '>=12' - libiconv: '>=1.17,<2.0a0' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - pcre2: '>=10.40,<10.41.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.76.3-hebfc3b9_0.conda - hash: - md5: a64f11b244b2c112cd3fa1cbe9493999 - sha256: 6a34c6b123f06fcee7e28e981ec0daad09bce35616ad8e9e61ef84be7fad4d92 - category: main - optional: false -- name: libgrpc - version: 1.54.2 - manager: conda - platform: linux-64 - dependencies: - c-ares: '>=1.18.1,<2.0a0' - libabseil: '>=20230125.2,<20230126.0a0' - libgcc-ng: '>=12' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - re2: '>=2023.3.2,<2023.3.3.0a0' - zlib: '' - url: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.54.2-hb20ce57_2.conda - hash: - md5: 2d6c2c90dd7805816bd78d80977b61d6 - sha256: 8a18c842bbd87bd80f83a3935269650309d8675e9eed3760ee47ea1d6a9b015d - category: main - optional: false -- name: libhwloc - version: 2.9.1 - manager: conda - platform: linux-64 - dependencies: - __cuda: '' - __glibc: '>=2.17' - cudatoolkit: '>=11.2,<12' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libxml2: '>=2.11.4,<2.12.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.9.1-cuda112_haf10fcf_5.conda - hash: - md5: b8996ffa972161676ba6972af4c41384 - sha256: fd4709c6b1960a822899658e9e7bc531dc5ef9b93f9e60c45bcee60d016453f6 - category: main - optional: false -- name: libthrift - version: 0.18.1 - manager: conda - platform: linux-64 - dependencies: - libevent: '>=2.1.12,<2.1.13.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.18.1-h8fd135c_1.conda - hash: - md5: a62fdab22023982131b5f21afdb9a6c8 - sha256: 3c53fc71cd1b582c879f858bb2463439381c26a3e88224fe1414df0ff1328520 - category: main - optional: false -- name: libtiff - version: 4.5.0 - manager: conda - platform: linux-64 - dependencies: - jpeg: '>=9e,<10a' - lerc: '>=4.0.0,<5.0a0' - libdeflate: '>=1.17,<1.18.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libwebp-base: '>=1.2.4,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - xz: '>=5.2.6,<6.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h6adf6a1_2.conda - hash: - md5: 2e648a34072eb39d7c4fc2a9981c5f0c - sha256: e3e18d91fb282b61288d4fd2574dfa31f7ae90ef2737f96722fb6ad3257862ee - category: main - optional: false -- name: llvm-openmp - version: 16.0.4 - manager: conda - platform: linux-64 - dependencies: - libzlib: '>=1.2.13,<1.3.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-16.0.4-h4dfa4b3_0.conda - hash: - md5: 68ffdf82a717033ead1c5edbfeff9f54 - sha256: 48df036610d3ee357b408256f222589ea24909572b20e0bf73f9a1a3c42fe255 - category: main - optional: false -- name: openh264 - version: 2.1.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - libstdcxx-ng: '>=9.3.0' - zlib: '>=1.2.11,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/openh264-2.1.1-h780b84a_0.tar.bz2 - hash: - md5: 034a6f90f1bbc7ba11d04b84ec9d74c8 - sha256: 2ce3df1edb23541595443c7697e5568ae6426fa4d365dede45b16b0310bd6a06 - category: main - optional: false -- name: orc - version: 1.8.3 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - snappy: '>=1.1.10,<2.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/orc-1.8.3-hfdbbad2_0.conda - hash: - md5: 8aafd0a5ba97bf0cc451550b147a4e0a - sha256: aa82f7e63fbc3facec2f291669dba1561b2b8d45d02381abc9d8f66638433f2d - category: main - optional: false -- name: sqlite - version: 3.42.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libsqlite: 3.42.0 - libzlib: '>=1.2.13,<1.3.0a0' - ncurses: '>=6.3,<7.0a0' - readline: '>=8.2,<9.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.42.0-h2c6b66d_0.conda - hash: - md5: 1192f6ec654a5bc4ee1d64bdc4a3e5cc - sha256: 9cf59fa9891248e0e3a86a41041156cec367653d423e5d8a09b4c8ab98441a27 - category: main - optional: false -- name: aws-c-event-stream - version: 0.2.20 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.2.20-hb4b372c_7.conda - hash: - md5: 7eb8e72640ac21ce4e7d26e873a21cbe - sha256: 5882ece0a5fc62a63fc910d2d610e144fd76f83b5dab4036d1632c50faddebbd - category: main - optional: false -- name: aws-c-http - version: 0.7.7 - manager: conda - platform: linux-64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-compression: '>=0.2.16,<0.2.17.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.7.7-h2632f9a_4.conda - hash: - md5: f4cd59b8e2ac740faded0f75aa965a71 - sha256: c4b886452d715ffe223da113e18eb939c15d056304247e37d1e9927cec0d31c7 - category: main - optional: false -- name: brotli - version: 1.0.9 - manager: conda - platform: linux-64 - dependencies: - brotli-bin: 1.0.9 - libbrotlidec: 1.0.9 - libbrotlienc: 1.0.9 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2 - hash: - md5: 2ff08978892a3e8b954397c461f18418 - sha256: 74c0fa22ea7c62d2c8f7a7aea03a3bd4919f7f3940ef5b027ce0dfb5feb38c06 - category: main - optional: false -- name: dbus - version: 1.13.6 - manager: conda - platform: linux-64 - dependencies: - expat: '>=2.4.2,<3.0a0' - libgcc-ng: '>=9.4.0' - libglib: '>=2.70.2,<3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2 - hash: - md5: ecfff944ba3960ecb334b9a2663d708d - sha256: 8f5f995699a2d9dbdd62c61385bfeeb57c82a681a7c8c5313c395aa0ccab68a5 - category: main - optional: false -- name: ffmpeg - version: '4.3' - manager: conda - platform: linux-64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - freetype: '>=2.10.2,<3.0a0' - gmp: '>=6.1.2' - gnutls: '>=3.6.5,<3.7.0a0' - lame: '>=3.100,<3.101.0a0' - libgcc-ng: '>=7.3.0' - libiconv: '' - libstdcxx-ng: '>=7.3.0' - openh264: '>=2.1.0,<2.2.0a0' - zlib: '>=1.2.11,<1.3.0a0' - url: https://conda.anaconda.org/pytorch/linux-64/ffmpeg-4.3-hf484d3e_0.tar.bz2 - hash: - md5: 0b0bf7c3d7e146ef91de5310bbf7a230 - sha256: 60b3e36cb36b706f5850f155bd9d3f33194a522b5ef20be46cb37dbc987a6741 - category: main - optional: false -- name: lcms2 - version: '2.15' - manager: conda - platform: linux-64 - dependencies: - jpeg: '>=9e,<10a' - libgcc-ng: '>=12' - libtiff: '>=4.5.0,<4.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.15-hfd0df8a_0.conda - hash: - md5: aa8840cdf17ef0c6084d1e24abc7a28b - sha256: 443e926b585528112ec6aa4d85bf087722914ed8d85a2f75ae47c023c55c4238 - category: main - optional: false -- name: libcurl - version: 8.1.2 - manager: conda - platform: linux-64 - dependencies: - krb5: '>=1.20.1,<1.21.0a0' - libgcc-ng: '>=12' - libnghttp2: '>=1.52.0,<2.0a0' - libssh2: '>=1.10.0,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.1.2-h409715c_0.conda - hash: - md5: 50c873c9660ed116707ae15b663928d8 - sha256: d572c31ff48d2db6ca5bab476bf325811cfc82577480b3791487c3fe7bff2ffa - category: main - optional: false -- name: openjpeg - version: 2.5.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libpng: '>=1.6.39,<1.7.0a0' - libstdcxx-ng: '>=12' - libtiff: '>=4.5.0,<4.6.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-hfec8fc6_2.conda - hash: - md5: 5ce6a42505c6e9e6151c54c3ec8d68ea - sha256: 3cbfb1fe9bb492dcb672f98f0ddc7b4e029f51f77101d9c301caa3acaea8cba2 - category: main - optional: false -- name: python - version: 3.9.12 - manager: conda - platform: linux-64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - ld_impl_linux-64: '>=2.36.1' - libffi: '>=3.4.2,<3.5.0a0' - libgcc-ng: '>=10.3.0' - libnsl: '>=2.0.0,<2.1.0a0' - libuuid: '>=2.32.1,<3.0a0' - libzlib: '>=1.2.11,<1.3.0a0' - ncurses: '>=6.3,<7.0a0' - openssl: '>=3.0.2,<4.0a0' - readline: '>=8.1,<9.0a0' - sqlite: '>=3.37.1,<4.0a0' - tk: '>=8.6.12,<8.7.0a0' - tzdata: '' - xz: '>=5.2.5,<5.3.0a0' - pip: '' - url: https://conda.anaconda.org/conda-forge/linux-64/python-3.9.12-h2660328_1_cpython.tar.bz2 - hash: - md5: ec06c83477a59cf285544b9a41927305 - sha256: b6a81ac02df0fa274f86b99f6076d9322faa0b3e7d5eb66c735fd9e6b30f2770 - category: main - optional: false -- name: tbb - version: 2021.9.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libhwloc: '>=2.9.1,<2.9.2.0a0' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.9.0-hf52228f_0.conda - hash: - md5: f495e42d3d2020b025705625edf35490 - sha256: 86352f4361e8dc2374a95d9d1dfee742beecaa59dcb0e76ca36ca06a4efe1df2 - category: main - optional: false -- name: antlr-python-runtime - version: 4.9.3 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/antlr-python-runtime-4.9.3-pyhd8ed1ab_1.tar.bz2 - hash: - md5: c88eaec8de9ae1fa161205aa18e7a5b1 - sha256: b91f8ab4ac2b48972fbee1fc8e092cc452fdf59156e4ff2322c94bbf73650f94 - category: main - optional: false -- name: attrs - version: 23.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/attrs-23.1.0-pyh71513ae_1.conda - hash: - md5: 3edfead7cedd1ab4400a6c588f3e75f8 - sha256: 063639cd568f5c7a557b0fb1cc27f098598c0d8ff869088bfeb82934674f8821 - category: main - optional: false -- name: aws-c-auth - version: 0.6.27 - manager: conda - platform: linux-64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-c-sdkutils: '>=0.1.9,<0.1.10.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.6.27-he072965_1.conda - hash: - md5: 2c7406414796748e53bd7d7c6349711d - sha256: 62e7583c3042ebf5cf63c2f631d0338755fe9bc183e9ea835a090ef0faaaa42a - category: main - optional: false -- name: aws-c-mqtt - version: 0.8.11 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.8.11-h2282364_1.conda - hash: - md5: 11a4a996699d883ebda0894faa71bbfc - sha256: cebbf485e3fc744119a94d748c55da45f7ddf3401c20e89a5b5bf15f0c759367 - category: main - optional: false -- name: backports - version: '1.0' - manager: conda - platform: linux-64 - dependencies: - python: '>=2.7' - url: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_3.conda - hash: - md5: 54ca2e08b3220c148a1d8329c2678e02 - sha256: 711602276ae39276cb0faaca6fd0ac851fff0ca17151917569174841ef830bbd - category: main - optional: false -- name: blinker - version: 1.6.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/blinker-1.6.2-pyhd8ed1ab_0.conda - hash: - md5: 2fb79ec81bad9492b6d59a06b3b647a4 - sha256: b6f32491536823e47cf6eb4717dd341385600a2b901235028dedc629a77aeb82 - category: main - optional: false -- name: certifi - version: 2023.5.7 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/certifi-2023.5.7-pyhd8ed1ab_0.conda - hash: - md5: 5d1b71c942b8421285934dad1d891ebc - sha256: f839a6e04d94069f90dd85337ea9108f058dc76771bb469a413f32bb1ba0b256 - category: main - optional: false -- name: charset-normalizer - version: 3.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.1.0-pyhd8ed1ab_0.conda - hash: - md5: 7fcff9f6f123696e940bda77bd4d6551 - sha256: 06cd371fc98f076797d6450f6f337cb679b1060c99680fb7e044591493333194 - category: main - optional: false -- name: click - version: 8.1.3 - manager: conda - platform: linux-64 - dependencies: - __unix: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2 - hash: - md5: 20e4087407c7cb04a40817114b333dbf - sha256: 23676470b591b100393bb0f6c46fe10624dcbefc696a6a9f42932ed8816ef0ea - category: main - optional: false -- name: cloudpickle - version: 2.2.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.1-pyhd8ed1ab_0.conda - hash: - md5: b325bfc4cff7d7f8a868f1f7ecc4ed16 - sha256: f0c2fd0e842899a05ddd7b147fb26424adf58be0e8e54e5bc68b8f7e67d05dcd - category: main - optional: false -- name: colorama - version: 0.4.6 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3faab06a954c2a04039983f2c4a50d99 - sha256: 2c1b2e9755ce3102bca8d69e8f26e4f087ece73f50418186aee7c74bef8e1698 - category: main - optional: false -- name: configparser - version: 5.3.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/configparser-5.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: c99fd5916160900dc5ff64204da99c4d - sha256: ce6ce9ee08437b46c284d52b076fb091cf6f2a9e12860d4a37546adbd5f53b28 - category: main - optional: false -- name: crashtest - version: 0.4.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6,<4.0' - url: https://conda.anaconda.org/conda-forge/noarch/crashtest-0.4.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 709a2295dd907bb34afb57d54320642f - sha256: 2f05954a3faf0700c14c1deddc085385160ee32abe111699c78d9cb277e915cc - category: main - optional: false -- name: cycler - version: 0.11.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: a50559fad0affdbb33729a68669ca1cb - sha256: 3b594bc8aa0b9a51269d54c7a4ef6af777d7fad4bee16b05695e1124de6563f6 - category: main - optional: false -- name: distlib - version: 0.3.6 - manager: conda - platform: linux-64 - dependencies: - python: 2.7|>=3.6 - url: https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2 - hash: - md5: b65b4d50dbd2d50fa0aeac367ec9eed7 - sha256: 06eb7167d4d760b3b437a491e32ab5b3f89e2a18f023c117fe213b038d88538a - category: main - optional: false -- name: entrypoints - version: '0.4' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3cf04868fee0a029769bd41f4b2fbf2d - sha256: 2ec4a0900a4a9f42615fc04d0fb3286b796abe56590e8e042f6ec25e102dd5af - category: main - optional: false -- name: exceptiongroup - version: 1.1.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.1-pyhd8ed1ab_0.conda - hash: - md5: 7312299d7a0ea4993159229b7d2dceb2 - sha256: f073c3ba993912f1c0027bc34a54975642885f0a4cd5f9dc42a17ca945df2c18 - category: main - optional: false -- name: filelock - version: 3.12.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.12.0-pyhd8ed1ab_0.conda - hash: - md5: 650f18a56f366dbf419c15b543592c2d - sha256: 68db3a6280d6786be76f2c7c6cf41dd878c5d1a24f5de10f7f0af82c6fcfade6 - category: main - optional: false -- name: fsspec - version: 2023.5.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/fsspec-2023.5.0-pyh1a96a4e_0.conda - hash: - md5: 20edd290b319aa0eff3e9055375756dc - sha256: cbb5c77c0217cda9bf4f4240158de11822a099a6eaa05ba626e822819a54f46d - category: main - optional: false -- name: greenlet - version: 2.0.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/greenlet-2.0.2-py39h3d6467e_1.conda - hash: - md5: bcc2596fee9dccce15c6ae3d475ea013 - sha256: 90e6375270dcc431ce9b78764a616f222d1cc8f8409ec5eda196279cae88be64 - category: main - optional: false -- name: idna - version: '3.4' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 34272b248891bddccc64479f9a7fffed - sha256: 9887c35c374ec1847f167292d3fde023cb4c994a4ceeec283072b95440131f09 - category: main - optional: false -- name: itsdangerous - version: 2.1.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/itsdangerous-2.1.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3c3de74912f11d2b590184f03c7cd09b - sha256: 31e3492686b4e92b53db9b48bc0eb03873b1caaf28629fee7d2d47627a2c56d3 - category: main - optional: false -- name: jeepney - version: 0.8.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/jeepney-0.8.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 9800ad1699b42612478755a2d26c722d - sha256: 16639759b811866d63315fe1391f6fb45f5478b823972f4d3d9f0392b7dd80b8 - category: main - optional: false -- name: kiwisolver - version: 1.4.4 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_1.tar.bz2 - hash: - md5: 41679a052a8ce841c74df1ebc802e411 - sha256: eb28254cc7029e702d0059536d986b010221de62f9c8588a5a83e95a00b4e74d - category: main - optional: false -- name: libgoogle-cloud - version: 2.10.1 - manager: conda - platform: linux-64 - dependencies: - libabseil: '>=20230125.2,<20230126.0a0' - libcrc32c: '>=1.1.2,<1.2.0a0' - libcurl: '>=8.0.1,<9.0a0' - libgcc-ng: '>=12' - libgrpc: '>=1.54.2,<1.55.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-2.10.1-hac9eb74_1.conda - hash: - md5: 582726eb344fe32d6de102b748067f8d - sha256: 54c6c05e2ffb5bb76164aaa9b039fb928dadd159d03f0c847605a475ad9122dc - category: main - optional: false -- name: lockfile - version: 0.12.2 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/lockfile-0.12.2-py_1.tar.bz2 - hash: - md5: c104d98e09c47519950cffb8dd5b4f10 - sha256: d3a68045ef74a2a7b8c8a55b242fdbc875d362e37adcf793613cf0d8c8e4fbf7 - category: main - optional: false -- name: markupsafe - version: 2.1.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.2-py39h72bdee0_0.conda - hash: - md5: 35514f5320206df9f4661c138c02e1c1 - sha256: da31fe95611393bb7dd3dee309a89328448570fd8a3205c2c55c03eb73688b61 - category: main - optional: false -- name: mdurl - version: 0.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: f8dab71fdc13b1bf29a01248b156d268 - sha256: c678b9194e025b1fb665bec30ee20aab93399203583875b1dcc0a3b52a8f5523 - category: main - optional: false -- name: mkl - version: 2022.2.1 - manager: conda - platform: linux-64 - dependencies: - _openmp_mutex: '>=4.5' - llvm-openmp: '>=15.0.6' - tbb: 2021.* - url: https://conda.anaconda.org/conda-forge/linux-64/mkl-2022.2.1-h84fe81f_16997.conda - hash: - md5: a7ce56d5757f5b57e7daabe703ade5bb - sha256: 5322750d5e96ff5d96b1457db5fb6b10300f2bc4030545e940e17b57c4e96d00 - category: main - optional: false -- name: more-itertools - version: 9.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/more-itertools-9.1.0-pyhd8ed1ab_0.conda - hash: - md5: 1698a717f83cfecf644a877c174c84bd - sha256: 3ee8cbbe4004c56b695a5e734b7dc4d59dacbfefc193ee42c82238b1cf888e08 - category: main - optional: false -- name: msgpack-python - version: 1.0.5 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.0.5-py39h4b4f3f3_0.conda - hash: - md5: 413374bab5022a5199c5dd89aef75df5 - sha256: 9b4b426b97d712c1b631bb775aaa1822b06f63a0ca93343c6eee59ab06f2b46c - category: main - optional: false -- name: munkres - version: 1.1.4 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2 - hash: - md5: 2ba8498c1018c1e9c61eb99b973dfe19 - sha256: f86fb22b58e93d04b6f25e0d811b56797689d598788b59dcb47f59045b568306 - category: main - optional: false -- name: ordered-set - version: 4.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/ordered-set-4.1.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 9a8714decb3967b290263817e876d8a9 - sha256: 78d92f848a6b4a89148dfa1f6e65c0b75e8f3a267b6401be38fb3401853b4afa - category: main - optional: false -- name: orjson - version: 3.9.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/orjson-3.9.0-py39h10b2342_0.conda - hash: - md5: e70f04f1ebea3faa9b4906d83ecf3c0e - sha256: cabaac37aca2d3fd997fde990cf6dc3abb81d1a229fa4b50ae8a3d3195714d4e - category: main - optional: false -- name: packaging - version: '23.1' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/packaging-23.1-pyhd8ed1ab_0.conda - hash: - md5: 91cda59e66e1e4afe9476f8ef98f5c30 - sha256: ded536a96a00d45a693dbc2971bb688248324dadd129eddda2100e177583d768 - category: main - optional: false -- name: pillow - version: 9.4.0 - manager: conda - platform: linux-64 - dependencies: - freetype: '>=2.12.1,<3.0a0' - jpeg: '>=9e,<10a' - lcms2: '>=2.14,<3.0a0' - libgcc-ng: '>=12' - libtiff: '>=4.5.0,<4.6.0a0' - libwebp-base: '>=1.2.4,<2.0a0' - libxcb: '>=1.13,<1.14.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openjpeg: '>=2.5.0,<3.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - tk: '>=8.6.12,<8.7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py39h2320bf1_1.conda - hash: - md5: d2f79132b9c8e416058a4cd84ef27b3d - sha256: 77348588ae7cc8034b63e8a71b6695ba22761e1c531678e724cf06a12be3d1e2 - category: main - optional: false -- name: pkginfo - version: 1.9.6 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.9.6-pyhd8ed1ab_0.conda - hash: - md5: be1e9f1c65a1ed0f2ae9352fec99db64 - sha256: 7ea5a5af62a15376d9f4f9f3c134874d0b0710f39be719e849b7fa9ca8870502 - category: main - optional: false -- name: pkgutil-resolve-name - version: 1.3.10 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pkgutil-resolve-name-1.3.10-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 89e3c7cdde7d3aaa2aee933b604dd07f - sha256: 7d055ffc8a02bf781a89d069db3454b453605cdaff300b82cedcc7133283e47e - category: main - optional: false -- name: prometheus_client - version: 0.17.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.17.0-pyhd8ed1ab_0.conda - hash: - md5: 95c5be3c7cbd872509d16c216617fdab - sha256: eb11fd8b927d9c5ff9482cfbd6cd810a43a1351c44a288e9680542ea698a19a0 - category: main - optional: false -- name: psutil - version: 5.9.5 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.5-py39h72bdee0_0.conda - hash: - md5: 1d54d3a75c3192ab7655d9c3d16809f1 - sha256: 846894b31bf26061a9e83b03b10fe46f49fcf1ffc5fb1c7ed79a61706a57004b - category: main - optional: false -- name: ptyprocess - version: 0.7.0 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd3deb0d_0.tar.bz2 - hash: - md5: 359eeb6536da0e687af562ed265ec263 - sha256: fb31e006a25eb2e18f3440eb8d17be44c8ccfae559499199f73584566d0a444a - category: main - optional: false -- name: pycparser - version: '2.21' - manager: conda - platform: linux-64 - dependencies: - python: 2.7.*|>=3.4 - url: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 076becd9e05608f8dc72757d5f3a91ff - sha256: 74c63fd03f1f1ea2b54e8bc529fd1a600aaafb24027b738d0db87909ee3a33dc - category: main - optional: false -- name: pygments - version: 2.15.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/pygments-2.15.1-pyhd8ed1ab_0.conda - hash: - md5: d316679235612869eba305aa7d41d9bf - sha256: 1bddeb54863c77ed5613b535a3e06a3a16b55786301a5e28c9bf011656bda686 - category: main - optional: false -- name: pyjwt - version: 2.7.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyjwt-2.7.0-pyhd8ed1ab_0.conda - hash: - md5: 99e28be5a278e2319834d7dc99e7bfdd - sha256: f3a64306fa0f405f10f4108d7ff42043d6fd393f940f9e98e395a3756687fc98 - category: main - optional: false -- name: pyparsing - version: 3.0.9 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2 - hash: - md5: e8fbc1b54b25f4b08281467bc13b70cc - sha256: 4acc7151cef5920d130f2e0a7615559cce8bfb037aeecb14d4d359ae3d9bc51b - category: main - optional: false -- name: pyrsistent - version: 0.19.3 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/pyrsistent-0.19.3-py39h72bdee0_0.conda - hash: - md5: 659013ef00dcd1751bfd26d894f73857 - sha256: 8b8719429dc47dd15252fe65fc77a3ad81f25aa5f4db0e6b1d7cdc54722e6ef4 - category: main - optional: false -- name: pysocks - version: 1.7.1 - manager: conda - platform: linux-64 - dependencies: - __unix: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2 - hash: - md5: 2a7de29fb590ca14b5243c4c812c8025 - sha256: a42f826e958a8d22e65b3394f437af7332610e43ee313393d1cf143f0a2d274b - category: main - optional: false -- name: python-editor - version: 1.0.4 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/python-editor-1.0.4-py_0.tar.bz2 - hash: - md5: eaaf29a0644f9407f98a4665f45880c4 - sha256: a6db88da69a27451d2eba675c445bdefd2dbea52ea02a0a214d5fd4f0af31740 - category: main - optional: false -- name: python-installer - version: 0.7.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/python-installer-0.7.0-pyhd8ed1ab_0.conda - hash: - md5: 65dea78f903d686c8b0c2feaf0e15e1f - sha256: 822f95b7786cfa61a6519153117b21d93194890e02a884b9f66ee4275e4f1c0a - category: main - optional: false -- name: python-multipart - version: 0.0.6 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/python-multipart-0.0.6-pyhd8ed1ab_0.conda - hash: - md5: f4f642eeda814c1b65f46fbdf7e89096 - sha256: 2a9b8d02a6ec9862433cfc2741c4cbfe321e4ae3bab066f7ed84bc00effb73d7 - category: main - optional: false -- name: python-tzdata - version: '2023.3' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2023.3-pyhd8ed1ab_0.conda - hash: - md5: 2590495f608a63625e165915fb4e2e34 - sha256: 0108888507014fb24573c31e4deceb61c99e63d37776dddcadd7c89b2ecae0b6 - category: main - optional: false -- name: pytz - version: '2023.3' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pytz-2023.3-pyhd8ed1ab_0.conda - hash: - md5: d3076b483092a435832603243567bc31 - sha256: e4999484f21763ca4b8f92c95b22cb6d1edc1b61d0a2bb073ee2bd11f39401b9 - category: main - optional: false -- name: pywin32-on-windows - version: 0.1.0 - manager: conda - platform: linux-64 - dependencies: - __unix: '' - python: '>=2.7' - url: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - hash: - md5: 2807a0becd1d986fe1ef9b7f8135f215 - sha256: 6502696aaef571913b22a808b15c185bd8ea4aabb952685deb29e6a6765761cb - category: main - optional: false -- name: pyyaml - version: '6.0' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - yaml: '>=0.2.5,<0.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_5.tar.bz2 - hash: - md5: ef9db3c38ae7275f6b14491cfe61a248 - sha256: ae22172640fb16e7df679f55f16353be8ed6de434193a511214cf95eaa202e79 - category: main - optional: false -- name: readchar - version: 4.0.5 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/readchar-4.0.5-pyhd8ed1ab_0.conda - hash: - md5: 513334936060e80697bc21079e4f2829 - sha256: 0426cd7a524c31ab6d52b4d181848daea81d057e200a74200ea6e2896534bc18 - category: main - optional: false -- name: setuptools - version: 67.7.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/setuptools-67.7.2-pyhd8ed1ab_0.conda - hash: - md5: 3b68bc43ec6baa48f7354a446267eefe - sha256: 3ac44771fce01f19218bcdf3992e24984748048db69889a9df65abcc6a10e29b - category: main - optional: false -- name: shellingham - version: 1.5.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/shellingham-1.5.1-pyhd8ed1ab_0.conda - hash: - md5: 1de44299f48f522caa2e0074231614e1 - sha256: 3cb4a4a83b617fdfef9b92751634488db0b8961c80340be8068bf6d4f1d5ac25 - category: main - optional: false -- name: six - version: 1.16.0 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 - hash: - md5: e5f25f8dbc060e9a8d912e432202afc2 - sha256: a85c38227b446f42c5b90d9b642f2c0567880c15d72492d8da074a59c8f91dd6 - category: main - optional: false -- name: smmap - version: 3.0.5 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/smmap-3.0.5-pyh44b312d_0.tar.bz2 - hash: - md5: 3a8dc70789709aa315325d5df06fb7e4 - sha256: 091de70ee6bfe063e0c0f77336975d124fd1e3f49b9c58d97c0c7b3d287c0002 - category: main - optional: false -- name: sniffio - version: 1.3.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: dd6cbc539e74cb1f430efbd4575b9303 - sha256: a3fd30754c20ddb28b777db38345ea00d958f46701f0decd6291a81c0f4eee78 - category: main - optional: false -- name: soupsieve - version: 2.3.2.post1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 146f4541d643d48fc8a75cacf69f03ae - sha256: 72d80dda41c3902c2619e8ab49d4f5b2a894d13375e1f9ed16fc00074ddd2307 - category: main - optional: false -- name: sqlparse - version: 0.4.4 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.5' - url: https://conda.anaconda.org/conda-forge/noarch/sqlparse-0.4.4-pyhd8ed1ab_0.conda - hash: - md5: 2e2f31b3b1c866c29636377e14f8c4c6 - sha256: 7972c9b15dafa1885f3d4cd22dc4edea4cd969d12739fb71f8632f2c3350706a - category: main - optional: false -- name: tabulate - version: 0.9.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.9.0-pyhd8ed1ab_1.tar.bz2 - hash: - md5: 4759805cce2d914c38472f70bf4d8bcb - sha256: f6e4a0dd24ba060a4af69ca79d32361a6678e61d78c73eb5e357909b025b4620 - category: main - optional: false -- name: threadpoolctl - version: 3.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2 - hash: - md5: a2995ee828f65687ac5b1e71a2ab1e0c - sha256: c7a964811ba49c545236f7d6c2486b2ed493931660048a06fe94ecc851dd0b82 - category: main - optional: false -- name: tomli - version: 2.0.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 5844808ffab9ebdb694585b50ba02a96 - sha256: 4cd48aba7cd026d17e86886af48d0d2ebc67ed36f87f6534f4b67138f5a5a58f - category: main - optional: false -- name: tomlkit - version: 0.11.8 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tomlkit-0.11.8-pyha770c72_0.conda - hash: - md5: 75838e8556166263a82038b51d01d5f1 - sha256: 3002e87338a98ba501fbf53981f8267b2def2548265a3622d403d06747872ccd - category: main - optional: false -- name: traitlets - version: 5.9.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.9.0-pyhd8ed1ab_0.conda - hash: - md5: d0b4f5c87cd35ac3fb3d47b223263a64 - sha256: 343610bce6dbe8a5090500dd2e9d1706057960b3f3120ebfe0abb4a8ecbada4d - category: main - optional: false -- name: trove-classifiers - version: 2023.5.24 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2023.5.24-pyhd8ed1ab_0.conda - hash: - md5: 4580a4f27cad1c3b275f6f6ad310abae - sha256: 05e83cd3ac921143c7a25681928727bcc9b01bf8456c9615b72d64f050863503 - category: main - optional: false -- name: typing - version: 3.10.0.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/typing-3.10.0.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: e6573ac68718f17b9d4f5c8eda3190f2 - sha256: ec1cfe0b7dc55a22223562cad799e0b16d122dab611c9923b6068d27a784ba2f - category: main - optional: false -- name: typing_extensions - version: 4.5.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.5.0-pyha770c72_0.conda - hash: - md5: 43e7d9e50261fb11deb76e17d8431aac - sha256: f81eee64fcdfb379e27d01773b34041fbf7f9e86f33b157c9925d19e0a442452 - category: main - optional: false -- name: unicodedata2 - version: 15.0.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py39hb9d737c_0.tar.bz2 - hash: - md5: 230d65004135bf312504a1bbcb0c7a08 - sha256: 03c2cf05d1f4f2b01fc1e3ced22d5f331f2f233e335c4a4cd11a31fea1fccc0c - category: main - optional: false -- name: webencodings - version: 0.5.1 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-py_1.tar.bz2 - hash: - md5: 3563be4c5611a44210d9ba0c16113136 - sha256: 302f4f4bd1ad00c0be1426ecf6bb01db59cfd8aff3de0cf1596526dca1a6b70e - category: main - optional: false -- name: websocket-client - version: 1.5.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.5.2-pyhd8ed1ab_0.conda - hash: - md5: bfe7e7cd1476092f51efbcde15dfb110 - sha256: 85310b382c4220d7846fa8f046216fd722b88db07991f07bd7decdf2e5dc3446 - category: main - optional: false -- name: websockets - version: 11.0.3 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/websockets-11.0.3-py39hd1e30aa_0.conda - hash: - md5: 301d6f20f420d677580ce358f0b0f616 - sha256: e08348212736933af11d52e2098a73b30aec2c1167661d6f2cbfbc3636a0a76d - category: main - optional: false -- name: wheel - version: 0.40.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.40.0-pyhd8ed1ab_0.conda - hash: - md5: 49bb0d9e60ce1db25e151780331bb5f3 - sha256: 79b4d29b0c004014a2abd5fc2c9fcd35cc6256222b960c2a317a27c4b0d8884d - category: main - optional: false -- name: zipp - version: 3.15.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/zipp-3.15.0-pyhd8ed1ab_0.conda - hash: - md5: 13018819ca8f5b7cc675a8faf1f5fedf - sha256: 241de30545299be9bcea3addf8a2c22a3b3d4ba6730890e150ab690ac937a3d2 - category: main - optional: false -- name: anyio - version: 3.7.0 - manager: conda - platform: linux-64 - dependencies: - exceptiongroup: '' - idna: '>=2.8' - python: '>=3.7' - sniffio: '>=1.1' - typing_extensions: '' - url: https://conda.anaconda.org/conda-forge/noarch/anyio-3.7.0-pyhd8ed1ab_1.conda - hash: - md5: 2b35a85d654a47aac8f34c1bb6de7142 - sha256: 863c11a6a0e937977229b405a16f6d43fff543dfe5b1a66da9c42ec0cbdaaf33 - category: main - optional: false -- name: aws-c-s3 - version: 0.3.0 - manager: conda - platform: linux-64 - dependencies: - aws-c-auth: '>=0.6.27,<0.6.28.0a0' - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - libgcc-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.3.0-hcb5a9b2_2.conda - hash: - md5: e32991aa713aafc13ae31869d44e04ad - sha256: 7386d7108cf9796b2c826de0b7770bbe63dca456934bd4dad90686617f24a082 - category: main - optional: false -- name: backports.cached-property - version: 1.0.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - typing: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/backports.cached-property-1.0.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 0e1df3978dd516e20ef88c86d51e5432 - sha256: 1d86eafb5e9ed078f891e12b46692d786723652907dfb01b047c7da31f92b862 - category: main - optional: false -- name: backports.functools_lru_cache - version: 1.6.4 - manager: conda - platform: linux-64 - dependencies: - backports: '' - python: '>=3.6' - setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/backports.functools_lru_cache-1.6.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: c5b3edc62d6309088f4970b3eaaa65a6 - sha256: fdea00d4b79990f3fe938e2716bc32bd895eb5c44b6c75b8261db095a1b33c16 - category: main - optional: false -- name: beautifulsoup4 - version: 4.12.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - soupsieve: '>=1.2' - url: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.2-pyha770c72_0.conda - hash: - md5: a362ff7d976217f8fa78c0f1c4f59717 - sha256: 52d3e6bcd442537e22699cd227d8fdcfd54b708eeb8ee5b4c671a6a9b9cd74da - category: main - optional: false -- name: blas - version: '1.0' - manager: conda - platform: linux-64 - dependencies: - mkl: '' - url: https://conda.anaconda.org/conda-forge/linux-64/blas-1.0-mkl.tar.bz2 - hash: - md5: 349aef876b1d8c9dccae01de20d5b385 - sha256: a9a9125029a66905fc9e932dfd4f595be3a59a30db37fd7bf4a675a5c6151d62 - category: main - optional: false -- name: cffi - version: 1.15.1 - manager: conda - platform: linux-64 - dependencies: - libffi: '>=3.4,<4.0a0' - libgcc-ng: '>=12' - pycparser: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_3.conda - hash: - md5: 20080319ef73fbad74dcd6d62f2a3ffe - sha256: 485a8f65c58c26c7d48bfea20ed1d6f1493f3329dd2c9c0a888a1c2b7c2365c5 - category: main - optional: false -- name: deepdiff - version: 6.3.0 - manager: conda - platform: linux-64 - dependencies: - ordered-set: '>=4.1.0,<4.2.0' - orjson: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/deepdiff-6.3.0-pyhd8ed1ab_0.conda - hash: - md5: 67ce5e3eecbf1e5ff869269640ae6a53 - sha256: f949d860d532a07587bdb8466310394d8c1af4dd89bb65d65219161fcc16db10 - category: main - optional: false -- name: fonttools - version: 4.39.4 - manager: conda - platform: linux-64 - dependencies: - brotli: '' - libgcc-ng: '>=12' - munkres: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - unicodedata2: '>=14.0.0' - url: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.39.4-py39hd1e30aa_0.conda - hash: - md5: 80605b792f58cf5c78a5b7e20cef1e35 - sha256: a7e7256d309fa6561e28aedaaabafceb7b3c04a6261fa3fc9cd7aac4e89df823 - category: main - optional: false -- name: gitdb - version: 4.0.10 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.4' - smmap: '>=3.0.1,<4' - url: https://conda.anaconda.org/conda-forge/noarch/gitdb-4.0.10-pyhd8ed1ab_0.conda - hash: - md5: 3706d2f3d7cb5dae600c833345a76132 - sha256: 0003ab2b971913380633c711bf49a54dcf06e179986c725b0925854b58878377 - category: main - optional: false -- name: gunicorn - version: 20.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - setuptools: '>=3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/gunicorn-20.1.0-py39hf3d152e_3.tar.bz2 - hash: - md5: c60b7e9761ae09c6e8ca32dd4de7a047 - sha256: 04faa3b68c39665baa14b95e44bde6c33a2bc11eef744572213634cfb407e582 - category: main - optional: false -- name: h11 - version: 0.14.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3' - typing_extensions: '' - url: https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: b21ed0883505ba1910994f1df031a428 - sha256: 817d2c77d53afe3f3d9cf7f6eb8745cdd8ea76c7adaa9d7ced75c455a2c2c085 - category: main - optional: false -- name: html5lib - version: '1.1' - manager: conda - platform: linux-64 - dependencies: - python: '' - six: '>=1.9' - webencodings: '' - url: https://conda.anaconda.org/conda-forge/noarch/html5lib-1.1-pyh9f0ad1d_0.tar.bz2 - hash: - md5: b2355343d6315c892543200231d7154a - sha256: 9ad06446fe9847e86cb20d220bf11614afcd2cbe9f58096f08d5d4018877bee4 - category: main - optional: false -- name: importlib-metadata - version: 6.6.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.8' - zipp: '>=0.5' - url: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.6.0-pyha770c72_0.conda - hash: - md5: f91a5d5175fb7ff2a91952ec7da59cb9 - sha256: 33d49065756a73fbb92277c756fa00a41891408528eb90ae05ff3367a401ae6e - category: main - optional: false -- name: importlib_resources - version: 5.12.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - zipp: '>=3.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.12.0-pyhd8ed1ab_0.conda - hash: - md5: e5fd2260a231ee63b6969f4801082f2b - sha256: 091cca3e010f7a7353152f0abda2d68cfd83ddde80a15e974d9e18b2047e7be2 - category: main - optional: false -- name: jaraco.classes - version: 3.2.3 - manager: conda - platform: linux-64 - dependencies: - more-itertools: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.2.3-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 31e4a1506968d017229bdb64695013a1 - sha256: 6a81b67a1de8f761f66a4540bbd07cc27f9fbf2c7d67aa3732ebef379cf62874 - category: main - optional: false -- name: jinja2 - version: 3.1.2 - manager: conda - platform: linux-64 - dependencies: - markupsafe: '>=2.0' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2 - hash: - md5: c8490ed5c70966d232fdd389d0dbed37 - sha256: b045faba7130ab263db6a8fdc96b1a3de5fcf85c4a607c5f11a49e76851500b5 - category: main - optional: false -- name: joblib - version: 1.2.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.2.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 7583652522d71ad78ba536bba06940eb - sha256: 0c21351871df2c0a53168575597dd9c881e2a9fa4c42fe89a9bcd7fab37f462c - category: main - optional: false -- name: libblas - version: 3.9.0 - manager: conda - platform: linux-64 - dependencies: - mkl: '>=2022.1.0,<2023.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_mkl.tar.bz2 - hash: - md5: 85f61af03fd291dae33150ffe89dc09a - sha256: 24e656f13b402b6fceb88df386768445ab9beb657d451a8e5a88d4b3380cf7a4 - category: main - optional: false -- name: lightning-utilities - version: 0.8.0 - manager: conda - platform: linux-64 - dependencies: - packaging: '>=17.1' - python: '>=3.8' - typing_extensions: '' - url: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.8.0-pyhd8ed1ab_0.conda - hash: - md5: ad16f58b64d3b41f4cbb75040b06c9cc - sha256: 8c1fff22ab86c85768e65dc8c4f4664787476a21f4d934c4e0261a1fa7523f9c - category: main - optional: false -- name: markdown-it-py - version: 2.2.0 - manager: conda - platform: linux-64 - dependencies: - mdurl: '>=0.1,<1' - python: '>=3.7' - typing_extensions: '>=3.7.4' - url: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-2.2.0-pyhd8ed1ab_0.conda - hash: - md5: b2928a6c6d52d7e3562b4a59c3214e3a - sha256: 65ed439862c1851463f03a9bc5109992ce3e3e025e9a2d76d13ca19f576eee9f - category: main - optional: false -- name: omegaconf - version: 2.3.0 - manager: conda - platform: linux-64 - dependencies: - antlr-python-runtime: 4.9.* - python: '>=3.7' - pyyaml: '>=5.1.0' - typing_extensions: '' - url: https://conda.anaconda.org/conda-forge/noarch/omegaconf-2.3.0-pyhd8ed1ab_0.conda - hash: - md5: 23cc056834cab53849b91f78d6ee3ea0 - sha256: df806841be847e5287b22b6ae7f380874f81ea51f1b51ae14a570f3385c7b133 - category: main - optional: false -- name: pexpect - version: 4.8.0 - manager: conda - platform: linux-64 - dependencies: - ptyprocess: '>=0.5' - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.8.0-pyh1a96a4e_2.tar.bz2 - hash: - md5: 330448ce4403cc74990ac07c555942a1 - sha256: 07706c0417ead94f359ca7278f65452d3c396448777aba1da6a11fc351bdca9a - category: main - optional: false -- name: pip - version: 23.1.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - setuptools: '' - wheel: '' - url: https://conda.anaconda.org/conda-forge/noarch/pip-23.1.2-pyhd8ed1ab_0.conda - hash: - md5: 7288da0d36821349cf1126e8670292df - sha256: 4fe1f47f6eac5b2635a622b6f985640bf835843c1d8d7ccbbae0f7d27cadec92 - category: main - optional: false -- name: protobuf - version: 4.21.12 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - setuptools: '' - url: https://conda.anaconda.org/conda-forge/linux-64/protobuf-4.21.12-py39h227be39_0.conda - hash: - md5: 984b4ee0c3241d7ce715f8a731421073 - sha256: 22c02a0ae705170d169642056ce2cdf17a9c64b394dc24ed5133e9761bd4486f - category: main - optional: false -- name: pyproject_hooks - version: 1.0.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - tomli: '>=1.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.0.0-pyhd8ed1ab_0.conda - hash: - md5: 21de50391d584eb7f4441b9de1ad773f - sha256: 016340837fcfef57b351febcbe855eedf0c1f0ecfc910ed48c7fbd20535f9847 - category: main - optional: false -- name: python-dateutil - version: 2.8.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - six: '>=1.5' - url: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: dd999d1cc9f79e67dbb855c8924c7984 - sha256: 54d7785c7678166aa45adeaccfc1d2b8c3c799ca2dc05d4a82bb39b1968bd7da - category: main - optional: false -- name: tqdm - version: 4.65.0 - manager: conda - platform: linux-64 - dependencies: - colorama: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.65.0-pyhd8ed1ab_1.conda - hash: - md5: ed792aff3acb977d09c7013358097f83 - sha256: b35f185a678109940d34f68ac5781c3cbda9b118b8d9886b8f68ab5be6afd4fc - category: main - optional: false -- name: typing-extensions - version: 4.5.0 - manager: conda - platform: linux-64 - dependencies: - typing_extensions: 4.5.0 - url: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.5.0-hd8ed1ab_0.conda - hash: - md5: b3c594fde1a80a1fc3eb9cc4a5dfe392 - sha256: 6da5e15fa533620ae2e7aca9a7d16013eed3a73ac64c47d7c3bf3deec39b63b9 - category: main - optional: false -- name: werkzeug - version: 2.3.4 - manager: conda - platform: linux-64 - dependencies: - markupsafe: '>=2.1.1' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.4-pyhd8ed1ab_0.conda - hash: - md5: 23ddbe41ab0115bc0bfb75dcbf5de7cf - sha256: 2df1970270839b36e13a4ba7e4b393cfa95aa1d7438909aa8c3db14170ea207c - category: main - optional: false -- name: arrow - version: 1.2.3 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - python-dateutil: '>=2.7.0' - typing_extensions: '' - url: https://conda.anaconda.org/conda-forge/noarch/arrow-1.2.3-pyhd8ed1ab_0.tar.bz2 - hash: - md5: fd1967c76eda3a3dd9e8e6cb7a15a028 - sha256: a0434c2770cf5b0ab5a33913c0b202b1521bc13f755b762d16a86b110425cdc2 - category: main - optional: false -- name: aws-crt-cpp - version: 0.20.2 - manager: conda - platform: linux-64 - dependencies: - aws-c-auth: '>=0.6.27,<0.6.28.0a0' - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-event-stream: '>=0.2.20,<0.2.21.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-c-mqtt: '>=0.8.11,<0.8.12.0a0' - aws-c-s3: '>=0.3.0,<0.3.1.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.20.2-he0fdcb3_0.conda - hash: - md5: 3d9577a30f0e61331216b381925aa3e3 - sha256: 5e88754191d9e48c06169888cc5f85f3a681face0d8f2d69e1944556a11f8eb9 - category: main - optional: false -- name: bcrypt - version: 3.2.2 - manager: conda - platform: linux-64 - dependencies: - cffi: '>=1.1' - libgcc-ng: '>=12' - pip: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - six: '>=1.4.1' - url: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-3.2.2-py39hb9d737c_1.tar.bz2 - hash: - md5: 93f72f06a4b00ce36d16007c01e6d1aa - sha256: 8afe6676576da6e661ab7c0f2dfa52acc9e6f9bfc5ad2f1d57bf5131ddbdd975 - category: main - optional: false -- name: brotlipy - version: 0.7.0 - manager: conda - platform: linux-64 - dependencies: - cffi: '>=1.0.0' - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1005.tar.bz2 - hash: - md5: a639fdd9428d8b25f8326a3838d54045 - sha256: 293229afcd31e81626e5cfe0478be402b35d29b73aa421a49470645debda5019 - category: main - optional: false -- name: croniter - version: 1.3.15 - manager: conda - platform: linux-64 - dependencies: - python: '>=2.6' - python-dateutil: '' - url: https://conda.anaconda.org/conda-forge/noarch/croniter-1.3.15-pyhd8ed1ab_0.conda - hash: - md5: 50197abb95aa7024eb0eb58fe5a51b07 - sha256: f8f58f6a50a5f63a35ee3bf6805e6dee10fe910f17a339da038967118c12c64f - category: main - optional: false -- name: cryptography - version: 41.0.1 - manager: conda - platform: linux-64 - dependencies: - cffi: '>=1.12' - libgcc-ng: '>=12' - openssl: '>=3.1.1,<4.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/cryptography-41.0.1-py39hd4f0224_0.conda - hash: - md5: 977858e1c3a33c7f3770c50691ce751b - sha256: f6cd66cdcf77a392cf4c009e5d1f4ed75ff4eb72d8d0b337f698f25537720b75 - category: main - optional: false -- name: dateutils - version: 0.6.12 - manager: conda - platform: linux-64 - dependencies: - python: '>=3' - python-dateutil: '' - pytz: '' - url: https://conda.anaconda.org/conda-forge/noarch/dateutils-0.6.12-py_0.tar.bz2 - hash: - md5: acee371a07e9a38a7072e5a5f7054ead - sha256: fb554b32a8f880cafaff4e67c789965d97c41eb1a6cc9ab5a83c6b28b581d809 - category: main - optional: false -- name: flask - version: 2.3.2 - manager: conda - platform: linux-64 - dependencies: - blinker: '>=1.6.2' - click: '>=8.1.3' - importlib-metadata: '>=3.6.0' - itsdangerous: '>=2.1.2' - jinja2: '>=3.1.2' - python: '>=3.8' - werkzeug: '>=2.3.3' - url: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.2-pyhd8ed1ab_0.conda - hash: - md5: 816d75d4c0f2e41b5765d17498c57a2e - sha256: f93246be286f2d0f93e85c4f08f9ce48f3eed875a79225e2ea119e70c0237421 - category: main - optional: false -- name: gitpython - version: 3.1.31 - manager: conda - platform: linux-64 - dependencies: - gitdb: '>=4.0.1,<5' - python: '>=3.7' - typing_extensions: '>=3.7.4.3' - url: https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.31-pyhd8ed1ab_0.conda - hash: - md5: f6e6b482110246a81c3f03e81c68752d - sha256: 77c531def610089bc190508fcf304cf96c085c5fe977ab8f7d7c1641769592ac - category: main - optional: false -- name: importlib-resources - version: 5.12.0 - manager: conda - platform: linux-64 - dependencies: - importlib_resources: '>=5.12.0,<5.12.1.0a0' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-5.12.0-pyhd8ed1ab_0.conda - hash: - md5: 3544c818f0720c89eb16ae6940ab440b - sha256: 0675df2bf18e52d0ea2bc5e1009faac273f059361a0caf36c0e0edc7831098a9 - category: main - optional: false -- name: importlib_metadata - version: 6.6.0 - manager: conda - platform: linux-64 - dependencies: - importlib-metadata: '>=6.6.0,<6.6.1.0a0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-6.6.0-hd8ed1ab_0.conda - hash: - md5: 3cbc9615f10a3d471532b83e4250b971 - sha256: 5de35d3c019d8a36e0a0deeb04a62689837bd68234a0a73a3355b860b442eca4 - category: main - optional: false -- name: jsonschema - version: 4.17.3 - manager: conda - platform: linux-64 - dependencies: - attrs: '>=17.4.0' - importlib-metadata: '' - importlib_resources: '>=1.4.0' - pkgutil-resolve-name: '>=1.3.10' - pyrsistent: '!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0' - python: '>=3.7' - typing_extensions: '' - url: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.17.3-pyhd8ed1ab_0.conda - hash: - md5: 723268a468177cd44568eb8f794e0d80 - sha256: 4f68a23430d1afc5c9b41c46fbac0ade33c0bf57a293c646bfdd6dc65350eada - category: main - optional: false -- name: libcblas - version: 3.9.0 - manager: conda - platform: linux-64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_mkl.tar.bz2 - hash: - md5: 361bf757b95488de76c4f123805742d3 - sha256: 892ba10508f22310ccfe748df1fd3b6c7f20e7b6f6b79e69ed337863551c1bd8 - category: main - optional: false -- name: liblapack - version: 3.9.0 - manager: conda - platform: linux-64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_mkl.tar.bz2 - hash: - md5: a2f166748917d6d6e4707841ca1f519e - sha256: d6201f860b2d76ed59027e69c2bbad6d1cb211a215ec9705cc487cde488fa1fa - category: main - optional: false -- name: mako - version: 1.2.4 - manager: conda - platform: linux-64 - dependencies: - importlib-metadata: '' - markupsafe: '>=0.9.2' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/mako-1.2.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 0d072f0edc017b6318dbab701e053f94 - sha256: 559ed0d4c600d9827c1e9e0f2f3a50724bf2281b28a04e08f60de63f0da309a6 - category: main - optional: false -- name: markdown - version: 3.4.3 - manager: conda - platform: linux-64 - dependencies: - importlib-metadata: '>=4.4' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/markdown-3.4.3-pyhd8ed1ab_0.conda - hash: - md5: 89ed59ad509c05db6f5f2f573d499bfe - sha256: e32ac2c95112caa8cd81f0cbc710f4f4903180a115c7260f03b010d5a0aa771b - category: main - optional: false -- name: platformdirs - version: 3.5.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - typing-extensions: '>=4.5' - url: https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.5.1-pyhd8ed1ab_0.conda - hash: - md5: e2be672aece1f060adf7154f76531a35 - sha256: d7845c01a9ee5a224cc9242782befed7d12dc6aac1103650ec87917b20f3579e - category: main - optional: false -- name: poetry-core - version: 1.6.1 - manager: conda - platform: linux-64 - dependencies: - importlib-metadata: '>=1.7.0' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-core-1.6.1-pyhd8ed1ab_0.conda - hash: - md5: a6d1f61527c27fcc0165a6701a46b9f4 - sha256: 6f6a66476908a1c109e36c852d934eedceb07e0cbc44d3fcd87c6f39521b57be - category: main - optional: false -- name: pydantic - version: 1.10.8 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - typing-extensions: '>=4.2.0' - url: https://conda.anaconda.org/conda-forge/linux-64/pydantic-1.10.8-py39hd1e30aa_0.conda - hash: - md5: 0b010892a3a2515a50f5f166543faa60 - sha256: 0e914ff39c85afa91528900819f5fd9b39804db7518ee6297b59527e820e4090 - category: main - optional: false -- name: pynacl - version: 1.5.0 - manager: conda - platform: linux-64 - dependencies: - cffi: '>=1.4.1' - libgcc-ng: '>=12' - libsodium: '>=1.0.18,<1.0.19.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - six: '' - url: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.5.0-py39hb9d737c_2.tar.bz2 - hash: - md5: 1022b37795420d806b7b36b4e622ee9b - sha256: 937447b9122e4fe2525aba3568bd0635123e6293564b157ccb6753300553d84e - category: main - optional: false -- name: python-build - version: 0.10.0 - manager: conda - platform: linux-64 - dependencies: - colorama: '' - importlib-metadata: '>=0.22' - packaging: '>=19.0' - pyproject_hooks: '' - python: '>=3.7' - tomli: '>=1.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/python-build-0.10.0-pyhd8ed1ab_1.conda - hash: - md5: 0ab47ce574f6a8bcb9f2076436e7fedb - sha256: 4c2cd519c85aa8b8e584723ca5f452aa5941d18374470adebfe73bf30fd27573 - category: main - optional: false -- name: pytorch - version: 1.13.1 - manager: conda - platform: linux-64 - dependencies: - blas: '*' - mkl: '>=2018' - python: '>=3.9,<3.10.0a0' - pytorch-cuda: '>=11.7,<11.8' - pytorch-mutex: '1.0' - typing_extensions: '' - url: https://conda.anaconda.org/pytorch/linux-64/pytorch-1.13.1-py3.9_cuda11.7_cudnn8.5.0_0.tar.bz2 - hash: - md5: 9704aa36a7c4051d3fd3c8eb83e38e97 - sha256: f078ee258a1a8084c72a9737f462e739cfabc1d634b5661e03d6efcb453ac305 - category: main - optional: false -- name: rich - version: 13.4.1 - manager: conda - platform: linux-64 - dependencies: - markdown-it-py: '>=2.2.0,<3.0.0' - pygments: '>=2.13.0,<3.0.0' - python: '>=3.7.0' - typing_extensions: '>=4.0.0,<5.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/rich-13.4.1-pyhd8ed1ab_0.conda - hash: - md5: c3bcbe0d086f15e5918568d3865e4dbf - sha256: 312f2628e06a591096a851bf678833fe670ecb16e9b15517ce8e03d7c9d9e600 - category: main - optional: false -- name: sqlalchemy - version: 2.0.15 - manager: conda - platform: linux-64 - dependencies: - greenlet: '!=0.4.17' - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - typing-extensions: '>=4.2.0' - url: https://conda.anaconda.org/conda-forge/linux-64/sqlalchemy-2.0.15-py39hd1e30aa_0.conda - hash: - md5: 62cb91b2ed1b8c72553b98a1f338ab3e - sha256: 884900438716b6e49f380f3a99f999e23e80b15cc20285469eb3f9d18636ca62 - category: main - optional: false -- name: starlette - version: 0.22.0 - manager: conda - platform: linux-64 - dependencies: - anyio: <5,>=3.4.0 - python: '>=3.7' - typing_extensions: '>=3.10.0' - url: https://conda.anaconda.org/conda-forge/noarch/starlette-0.22.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 49d5cdcc16c691e4ad9355c81f004c3e - sha256: 1441dd55c037184b7d2c1e1dbf60beafb1f92fdc13cabf78a85e12825a55269b - category: main - optional: false -- name: uvicorn - version: 0.22.0 - manager: conda - platform: linux-64 - dependencies: - click: '>=7.0' - h11: '>=0.8' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/uvicorn-0.22.0-py39hf3d152e_0.conda - hash: - md5: ca0c46246d017fe1db8d049fa0de5c54 - sha256: 9b3b1d8765ad84539d1e9ac45e0806d019c9a56a146f4445c753b0a93640a353 - category: main - optional: false -- name: wcwidth - version: 0.2.6 - manager: conda - platform: linux-64 - dependencies: - backports.functools_lru_cache: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.6-pyhd8ed1ab_0.conda - hash: - md5: 078979d33523cb477bd1916ce41aacc9 - sha256: c1bd0ad7d854cae56977b7915ac2b78b652fa5f7ec1e9fc21e7fdb30cf4519b1 - category: main - optional: false -- name: alembic - version: 1.11.1 - manager: conda - platform: linux-64 - dependencies: - importlib-metadata: '' - importlib_resources: '' - mako: '' - python: '>=3.7' - sqlalchemy: '>=1.3.0' - typing-extensions: '>=4' - url: https://conda.anaconda.org/conda-forge/noarch/alembic-1.11.1-pyhd8ed1ab_0.conda - hash: - md5: 6a55e123397b42b79c48b31d1b7a91b8 - sha256: 065dd1b38ebe3a0d14f45549f63cce55125052057db565be153cdd73aa2a7c8d - category: main - optional: false -- name: aws-sdk-cpp - version: 1.10.57 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-event-stream: '>=0.2.20,<0.2.21.0a0' - aws-crt-cpp: '>=0.20.2,<0.20.3.0a0' - libcurl: '>=8.1.1,<9.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.10.57-h059227d_13.conda - hash: - md5: 16eac1f53808f188a44cb0dcb59b109b - sha256: a23ffd9b8920e16faf1a62769aa947933d921e7f56b5b8caeba844b3a2eb2a07 - category: main - optional: false -- name: blessed - version: 1.19.1 - manager: conda - platform: linux-64 - dependencies: - __unix: '' - python: '>=3.8' - six: '>=1.9.0' - wcwidth: '>=0.1.4' - url: https://conda.anaconda.org/conda-forge/noarch/blessed-1.19.1-pyhe4f9e05_2.tar.bz2 - hash: - md5: 65486376a55a80933e5dd95681ddd8b8 - sha256: 9d5b1f751adfe6d77fa8a088417a3aed716a1f727c0fd0230195246362b9d562 - category: main - optional: false -- name: fastapi - version: 0.88.0 - manager: conda - platform: linux-64 - dependencies: - pydantic: '>=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0' - python: '>=3.7' - starlette: 0.22.0.* - url: https://conda.anaconda.org/conda-forge/noarch/fastapi-0.88.0-pyhd8ed1ab_0.conda - hash: - md5: 0bd34a2b460d02e2fbf88ca5d9d3e55d - sha256: 660b356b8d4f3b99b6294c638637699003b0533c0ecab9389c56367b4bfe5c59 - category: main - optional: false -- name: numpy - version: 1.24.3 - manager: conda - platform: linux-64 - dependencies: - libblas: '>=3.9.0,<4.0a0' - libcblas: '>=3.9.0,<4.0a0' - libgcc-ng: '>=12' - liblapack: '>=3.9.0,<4.0a0' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.3-py39h6183b62_0.conda - hash: - md5: 8626d6d5169950ce4b99b082667773f7 - sha256: c8fac78b5292c279449e4ccba03661dd75f9d39b0f5d40b8bf55c3fcd89f64ce - category: main - optional: false -- name: oauthlib - version: 3.2.2 - manager: conda - platform: linux-64 - dependencies: - blinker: '' - cryptography: '' - pyjwt: '>=1.0.0' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/oauthlib-3.2.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 8f882b197fd9c4941a787926baea4868 - sha256: 0cfd5146a91d3974f4abfc2a45de890371d510a77238fe553e036ec8c031dc5b - category: main - optional: false -- name: paramiko - version: 3.2.0 - manager: conda - platform: linux-64 - dependencies: - bcrypt: '>=3.2' - cryptography: '>=3.3' - pynacl: '>=1.5' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/paramiko-3.2.0-pyhd8ed1ab_0.conda - hash: - md5: f212c7eb95e909df4795297f73690993 - sha256: e425a03e5e2ef2ec5a78711686c59cfceeeeec3a98165fbc7d186bd6a5cb78de - category: main - optional: false -- name: poetry-plugin-export - version: 1.4.0 - manager: conda - platform: linux-64 - dependencies: - poetry-core: '>=1.6.0,<2.0.0' - python: '>=3.7,<4.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-plugin-export-1.4.0-pyhd8ed1ab_0.conda - hash: - md5: 00893c7ea4f9f7620706e0aa94c01b6e - sha256: 54478b283b5967a85ee5da717f1512d7ec97eb07c7b52d1f2ad3cb080d56c0ac - category: main - optional: false -- name: prometheus_flask_exporter - version: 0.22.4 - manager: conda - platform: linux-64 - dependencies: - flask: '' - prometheus_client: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/prometheus_flask_exporter-0.22.4-pyhd8ed1ab_0.conda - hash: - md5: 43acea130cafd18740b73fa4c226c9f7 - sha256: be83619ef5964713cd298d0fb86eddd99b159e5fba3d0f91d624ce5d7c3890e0 - category: main - optional: false -- name: pyopenssl - version: 23.2.0 - manager: conda - platform: linux-64 - dependencies: - cryptography: '>=38.0.0,<42,!=40.0.0,!=40.0.1' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.2.0-pyhd8ed1ab_1.conda - hash: - md5: 34f7d568bf59d18e3fef8c405cbece21 - sha256: 4daea3dc896987cc1334956fccfc0ed738663a84ad0c1d3f576a7a7936091534 - category: main - optional: false -- name: secretstorage - version: 3.3.3 - manager: conda - platform: linux-64 - dependencies: - cryptography: '' - dbus: '' - jeepney: '>=0.6' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/secretstorage-3.3.3-py39hf3d152e_1.tar.bz2 - hash: - md5: cfb68a22e2d9108634a08a8a3b19d1b6 - sha256: db76e25d0c1cad3ca6339fd4d09c9cd03dcea7072b302c6eaa4123a358e98a78 - category: main - optional: false -- name: starsessions - version: 1.3.0 - manager: conda - platform: linux-64 - dependencies: - itsdangerous: '>=2.0.1' - python: '>=3.6.2' - starlette: '>=0' - url: https://conda.anaconda.org/conda-forge/noarch/starsessions-1.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 667d08040a85d7ea1c6d4af2290f96c4 - sha256: 4a500ac0a9fe56cee7958d6d0f6530272c43ee4c16c52600001decb39fe3cd59 - category: main - optional: false -- name: torchmetrics - version: 0.11.4 - manager: conda - platform: linux-64 - dependencies: - packaging: '' - python: '>=3.7' - pytorch: '>=1.8.1' - setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-0.11.4-pyhd8ed1ab_0.conda - hash: - md5: 480cb2b6d502003e937d9e4326bc398f - sha256: 3927eae14903c76679d5151af39680e7d42c35e0bdaa5041f577fc40f0619be8 - category: main - optional: false -- name: typer - version: 0.9.0 - manager: conda - platform: linux-64 - dependencies: - click: '>=7.1.1,<9' - colorama: '>=0.4.3,<0.5.0' - python: '>=3.6' - rich: '>=10.11.0,<14.0.0' - shellingham: '>=1.3.0,<2.0.0' - typing-extensions: '>=3.7.4.3' - url: https://conda.anaconda.org/conda-forge/noarch/typer-0.9.0-pyhd8ed1ab_0.conda - hash: - md5: 5030a13b2fe5e143d5956d4943d3018f - sha256: d395e1e92281abb13e043220ecf8ea973ada8d38a1e8c683df14f46541c64bd2 - category: main - optional: false -- name: virtualenv - version: 20.23.0 - manager: conda - platform: linux-64 - dependencies: - distlib: <1,>=0.3.6 - filelock: <4,>=3.11 - platformdirs: <4,>=3.2 - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.23.0-pyhd8ed1ab_0.conda - hash: - md5: a920e114c4c2ced2280e266da65ab5e6 - sha256: 13d667887ea08b6d1fe2eb09d2d737f9af7343735d3bfa5ffaa3f67eec8eaff7 - category: main - optional: false -- name: contourpy - version: 1.0.7 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.16' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.7-py39h4b4f3f3_0.conda - hash: - md5: c5387f3fb1f5b8b71e1c865fc55f4951 - sha256: 74a767b73686caf0bb1d1186cd62a54f01e03ad5432eaaf0a7babad7634c4067 - category: main - optional: false -- name: keyring - version: 23.13.1 - manager: conda - platform: linux-64 - dependencies: - importlib_metadata: '>=4.11.4' - jaraco.classes: '' - jeepney: '>=0.4.2' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - secretstorage: '>=3.2' - url: https://conda.anaconda.org/conda-forge/linux-64/keyring-23.13.1-py39hf3d152e_0.conda - hash: - md5: ae2df7a822f7671da22da24ead24cc0a - sha256: 107e6a5ba122dff162e9d34a87af1ffbb4dca1f7dd1547294646774ca9421524 - category: main - optional: false -- name: libarrow - version: 11.0.0 - manager: conda - platform: linux-64 - dependencies: - aws-crt-cpp: '>=0.20.2,<0.20.3.0a0' - aws-sdk-cpp: '>=1.10.57,<1.10.58.0a0' - bzip2: '>=1.0.8,<2.0a0' - c-ares: '>=1.19.1,<2.0a0' - gflags: '>=2.2.2,<2.3.0a0' - glog: '>=0.6.0,<0.7.0a0' - libabseil: '>=20230125.2,<20230126.0a0' - libbrotlicommon: '>=1.0.9,<1.1.0a0' - libbrotlidec: '>=1.0.9,<1.1.0a0' - libbrotlienc: '>=1.0.9,<1.1.0a0' - libevent: '>=2.1.12,<2.1.13.0a0' - libgcc-ng: '>=12' - libgoogle-cloud: '>=2.10.1,<2.10.2.0a0' - libgrpc: '>=1.54.2,<1.55.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - libthrift: '>=0.18.1,<0.18.2.0a0' - libutf8proc: '>=2.8.0,<3.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - openssl: '>=3.1.0,<4.0a0' - orc: '>=1.8.3,<1.8.4.0a0' - re2: '>=2023.3.2,<2023.3.3.0a0' - snappy: '>=1.1.10,<2.0a0' - ucx: '>=1.14.0,<1.15.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libarrow-11.0.0-h96638e8_21_cpu.conda - hash: - md5: 61af7ea51986d35d77d6d5f0bd5e2b3a - sha256: c1c518ea4715f248d4e00dcad1b3c70b0c6a3071fe6039104aba74377d120892 - category: main - optional: false -- name: pandas - version: 2.0.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.21.6,<2.0a0' - python: '>=3.9,<3.10.0a0' - python-dateutil: '>=2.8.1' - python-tzdata: '>=2022a' - python_abi: 3.9.* - pytz: '>=2020.1' - url: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.0.2-py39h40cae4c_0.conda - hash: - md5: de99b3f807c0b295a7df94623df0fb4c - sha256: 234e0474b55edc7159ddbdf03c99a9d9f53a0b32d9c505dc4be85698bab9bd6b - category: main - optional: false -- name: rapidfuzz - version: 2.15.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/rapidfuzz-2.15.1-py39h227be39_0.conda - hash: - md5: b8474b08c10da5d36a93cbbbdb41c53d - sha256: 2597a34923daaa41f151d3fc97e86b1f811c4c974b6f61c79bd0bff40849b110 - category: main - optional: false -- name: urllib3 - version: 1.26.15 - manager: conda - platform: linux-64 - dependencies: - brotlipy: '>=0.6.0' - certifi: '' - cryptography: '>=1.3.4' - idna: '>=2.0.0' - pyopenssl: '>=0.14' - pysocks: '>=1.5.6,<2.0,!=1.5.7' - python: <4.0 - url: https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.15-pyhd8ed1ab_0.conda - hash: - md5: 27db656619a55d727eaf5a6ece3d2fd6 - sha256: 213bdf6c3a5d721fa83b45d527d3ecd340f9547c0d6bbd0b8d9d746ec9a1fb4b - category: main - optional: false -- name: arrow-cpp - version: 11.0.0 - manager: conda - platform: linux-64 - dependencies: - libarrow: 11.0.0 - url: https://conda.anaconda.org/conda-forge/linux-64/arrow-cpp-11.0.0-ha770c72_21_cpu.conda - hash: - md5: 9b62e81822421b834ad453d3b4687fba - sha256: 06f99c9836045c0a595a050134bc1337eadea5dcb91efcf8cc784d023dda5557 - category: main - optional: false -- name: cleo - version: 2.0.1 - manager: conda - platform: linux-64 - dependencies: - crashtest: '>=0.4.1,<0.5.0' - python: '>=3.7,<4.0' - rapidfuzz: '>=2.2.0,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/cleo-2.0.1-pyhd8ed1ab_0.conda - hash: - md5: f1c5f2af6676cbe9206e191d1e70f661 - sha256: cf9bc4c9356ad8eb68512446eebc076386f2bfb8ca86626e8796621bc5a13082 - category: main - optional: false -- name: dulwich - version: 0.21.5 - manager: conda - platform: linux-64 - dependencies: - certifi: '' - cryptography: '>=1.3.4' - idna: '>=2.0.0' - libgcc-ng: '>=12' - pyopenssl: '>=0.14' - pysocks: '>=1.5.6,<2.0,!=1.5.7' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - urllib3: '' - url: https://conda.anaconda.org/conda-forge/linux-64/dulwich-0.21.5-py39hd1e30aa_0.conda - hash: - md5: 47e35d4a98545edc0d4828e273f6ad92 - sha256: a244714619d29fb09d9315c127fa78c347eaf032d13cb1ac8561b314468c6af8 - category: main - optional: false -- name: matplotlib-base - version: 3.7.1 - manager: conda - platform: linux-64 - dependencies: - certifi: '>=2020.06.20' - contourpy: '>=1.0.1' - cycler: '>=0.10' - fonttools: '>=4.22.0' - freetype: '>=2.12.1,<3.0a0' - importlib-resources: '>=3.2.0' - kiwisolver: '>=1.0.1' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.20.3,<2.0a0' - packaging: '>=20.0' - pillow: '>=6.2.0' - pyparsing: '>=2.3.1' - python: '>=3.9,<3.10.0a0' - python-dateutil: '>=2.7' - python_abi: 3.9.* - tk: '>=8.6.12,<8.7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.7.1-py39he190548_0.conda - hash: - md5: f2a931db797bb58bd335f4a857b4c898 - sha256: 34f8db992c68bee53fb6f0212707503ce197d13fadc231dbc37a99f31f72879a - category: main - optional: false -- name: requests - version: 2.31.0 - manager: conda - platform: linux-64 - dependencies: - certifi: '>=2017.4.17' - charset-normalizer: '>=2,<4' - idna: '>=2.5,<4' - python: '>=3.7' - urllib3: '>=1.21.1,<3' - url: https://conda.anaconda.org/conda-forge/noarch/requests-2.31.0-pyhd8ed1ab_0.conda - hash: - md5: a30144e4156cdbb236f99ebb49828f8b - sha256: 9f629d6fd3c8ac5f2a198639fe7af87c4db2ac9235279164bfe0fcb49d8c4bad - category: main - optional: false -- name: cachecontrol - version: 0.12.11 - manager: conda - platform: linux-64 - dependencies: - msgpack-python: '>=0.5.2' - python: '>=3.6' - requests: '' - url: https://conda.anaconda.org/conda-forge/noarch/cachecontrol-0.12.11-pyhd8ed1ab_1.conda - hash: - md5: e8f0410e0aa03342304357c5cc3bb75d - sha256: 466ce7c155be90a5c903052eba391759ae88eb65f2bb06b0cc1c9d09c4311800 - category: main - optional: false -- name: databricks-cli - version: 0.17.7 - manager: conda - platform: linux-64 - dependencies: - click: '>=7.0' - configparser: '>=0.3.5' - oauthlib: '>=3.1.0' - pyjwt: '>=1.7.0' - python: '>=3.6' - requests: '>=2.17.3' - six: '>=1.10.0' - tabulate: '>=0.7.7' - urllib3: '>=1.26.7,<2.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/databricks-cli-0.17.7-pyhd8ed1ab_1.conda - hash: - md5: 348c2ecbb2d9266936c40506d71bc1d6 - sha256: a5116a1b41ebd90a647f58d677d271cb6a81c4f5bd44f1acb21a47655b327b78 - category: main - optional: false -- name: docker-py - version: 6.1.0 - manager: conda - platform: linux-64 - dependencies: - packaging: '>=14.0' - paramiko: '>=2.4.3' - python: '>=3.7' - pywin32-on-windows: '' - requests: '>=2.26.0' - urllib3: '>=1.26.0' - websocket-client: '>=0.32.0' - url: https://conda.anaconda.org/conda-forge/noarch/docker-py-6.1.0-pyhd8ed1ab_0.conda - hash: - md5: 543336c6aa9516cfb29c51d5c162b177 - sha256: 5e01e15e20ee573c99b530633a0d5c71fd515e4ac6d2f5f5f57baece8b915cc3 - category: main - optional: false -- name: lightning-cloud - version: 0.5.36 - manager: conda - platform: linux-64 - dependencies: - click: '' - fastapi: '' - pyjwt: '' - python: '>=3.7' - python-multipart: '' - requests: '' - rich: '' - six: '' - urllib3: '' - uvicorn: '' - websocket-client: '' - url: https://conda.anaconda.org/conda-forge/noarch/lightning-cloud-0.5.36-pyhd8ed1ab_0.conda - hash: - md5: fd99cc369aa3c6c66493d4d278338af5 - sha256: 2047f4dcd4531f6cd2f87a80fef0d265f663e011931af746b2725f7d567ce016 - category: main - optional: false -- name: parquet-cpp - version: 1.5.1 - manager: conda - platform: linux-64 - dependencies: - arrow-cpp: '>=0.11.0' - url: https://conda.anaconda.org/conda-forge/noarch/parquet-cpp-1.5.1-2.tar.bz2 - hash: - md5: 79a5f78c42817594ae016a7896521a97 - sha256: 15e50657515b791734ba045da5135377404ca37c518b2066b9c6451c65cd732e - category: main - optional: false -- name: pooch - version: 1.7.0 - manager: conda - platform: linux-64 - dependencies: - packaging: '>=20.0' - platformdirs: '>=2.5.0' - python: '>=3.7' - requests: '>=2.19.0' - url: https://conda.anaconda.org/conda-forge/noarch/pooch-1.7.0-pyha770c72_3.conda - hash: - md5: 5936894aade8240c867d292aa0d980c6 - sha256: 64e4d633803df2e36fd141d9bf269568fbe179a313248e1dac4d364c02debdef - category: main - optional: false -- name: pytorch-lightning - version: 2.0.2 - manager: conda - platform: linux-64 - dependencies: - fsspec: '>2021.06.0' - lightning-utilities: '>=0.7.0' - numpy: '>=1.17.2' - packaging: '>=17.1' - python: '>=3.8' - pytorch: '>=1.11.0' - pyyaml: '>=5.4' - requests: '' - torchmetrics: '>=0.7.0' - tqdm: '>=4.57.0' - typing_extensions: '>=4.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.0.2-pyhd8ed1ab_0.conda - hash: - md5: abd4916f586ae33b56d1e2b44a7990aa - sha256: 9332cb927d6c587ed5b23628208ebb4479288244f4388bd4cb9745df115cca25 - category: main - optional: false -- name: querystring_parser - version: 1.2.4 - manager: conda - platform: linux-64 - dependencies: - python: '' - requests: '' - six: '' - url: https://conda.anaconda.org/conda-forge/noarch/querystring_parser-1.2.4-py_0.tar.bz2 - hash: - md5: 0ebdca9b753c2e082e5b5ad06aa76b41 - sha256: 06977a9af6d8605fb6068d8af6bb9c1cb565f8f5e15aa6cf0fb94109d4148b54 - category: main - optional: false -- name: requests-toolbelt - version: 1.0.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - requests: '>=2.0.1,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-1.0.0-pyhd8ed1ab_0.conda - hash: - md5: 99c98318c8646b08cc764f90ce98906e - sha256: 20eaefc5dba74ff6c31e537533dde59b5b20f69e74df49dff19d43be59785fa3 - category: main - optional: false -- name: torchvision - version: 0.14.1 - manager: conda - platform: linux-64 - dependencies: - ffmpeg: '>=4.2' - jpeg: '' - libpng: '' - numpy: '>=1.11' - pillow: '>=5.3.0,!=8.3.*' - python: '>=3.9,<3.10.0a0' - pytorch: 1.13.1 - pytorch-cuda: 11.7.* - pytorch-mutex: '1.0' - requests: '' - url: https://conda.anaconda.org/pytorch/linux-64/torchvision-0.14.1-py39_cu117.tar.bz2 - hash: - md5: ec0dad2404c541586f972e5207727829 - sha256: 3c995765c6ec5909dbf4a35de58b35cb481bfeb97b09b2bd931d3a1d9fced7ae - category: main - optional: false -- name: cachecontrol-with-filecache - version: 0.12.11 - manager: conda - platform: linux-64 - dependencies: - cachecontrol: 0.12.11 - lockfile: '>=0.9' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cachecontrol-with-filecache-0.12.11-pyhd8ed1ab_1.conda - hash: - md5: 9df660456c0076d27b802448f7ede78f - sha256: 81c483fc92656873eb5a7ba657b208c34186556d942a9cebc1f7771e565b95b7 - category: main - optional: false -- name: pyarrow - version: 11.0.0 - manager: conda - platform: linux-64 - dependencies: - gflags: '>=2.2.2,<2.3.0a0' - libarrow: 11.0.0 - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.21.6,<2.0a0' - parquet-cpp: 1.5.1.* - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-11.0.0-py39he4327e9_21_cpu.conda - hash: - md5: fdac93d832c2247e83d6df03febd87b0 - sha256: 71b575fa595852130e997bfffd65a5742fa24cfa8b90e41396029a14058d56f4 - category: main - optional: false -- name: scipy - version: 1.10.1 - manager: conda - platform: linux-64 - dependencies: - libblas: '>=3.9.0,<4.0a0' - libcblas: '>=3.9.0,<4.0a0' - libgcc-ng: '>=12' - libgfortran-ng: '' - libgfortran5: '>=12.2.0' - liblapack: '>=3.9.0,<4.0a0' - libstdcxx-ng: '>=12' - numpy: '>=1.21.6,<2.0a0' - pooch: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.1-py39h6183b62_3.conda - hash: - md5: 84c4007675da392fdb99faeefda69552 - sha256: 68b5690a88e2872012fbe218dfb1f197e70aa83ecc3d049b5df5845d8c603406 - category: main - optional: false -- name: poetry - version: 1.5.1 - manager: conda - platform: linux-64 - dependencies: - __linux: '' - backports.cached-property: '>=1.0.2,<2.0.0' - cachecontrol-with-filecache: '>=0.12.9,<0.13.0' - cleo: '>=2.0.0,<3.0.0' - crashtest: '>=0.4.1,<0.5.0' - dulwich: '>=0.21.2,<0.22.0' - filelock: '>=3.8.0,<4.0.0' - html5lib: '>=1.0.0,<2.0.0' - importlib-metadata: '>=4.4' - jsonschema: '>=4.10.0,<5.0.0' - keyring: '>=23.9.0,<24.0.0' - lockfile: '>=0.12.2,<0.13.0' - packaging: '>=20.4' - pexpect: '>=4.7.0,<5.0.0' - pkginfo: '>=1.9.4,<2.0' - platformdirs: '>=3.0.0,<4.0.0' - poetry-core: 1.6.1.* - poetry-plugin-export: '>=1.4.0,<2.0.0' - pyproject_hooks: '>=1.0.0,<2.0.0' - python: '>=3.7.0,<4.0.0' - python-build: '>=0.10.0,<0.11.0' - python-installer: '>=0.7.0,<0.8.0' - requests: '>=2.18,<3.0' - requests-toolbelt: '>=0.9.1,<2' - shellingham: '>=1.5.0,<2.0.0' - tomli: '>=2.0.1,<3.0.0' - tomlkit: '>=0.11.4,<1.0.0' - trove-classifiers: '>=2022.5.19' - urllib3: '>=1.26.0,<2.0.0' - virtualenv: '>=20.22.0,<21.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-1.5.1-linux_pyhd8ed1ab_0.conda - hash: - md5: 7aae42823198731808dd8e7a9745547f - sha256: 754fb55e83fe06366f4532fbb25344bc9c7d46ac71d2a1c35209e818f7fa9d10 - category: main - optional: false -- name: scikit-learn - version: 1.2.2 - manager: conda - platform: linux-64 - dependencies: - _openmp_mutex: '>=4.5' - joblib: '>=1.1.1' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.21.6,<2.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - scipy: '' - threadpoolctl: '>=2.0.0' - url: https://conda.anaconda.org/conda-forge/linux-64/scikit-learn-1.2.2-py39hc236052_2.conda - hash: - md5: 43894d2ed4e587557e536d3e4ef93f73 - sha256: 857fcc0505405dcbaedeea8e655383f54126102a919f9a32d55cd77582ce15cb - category: main - optional: false -- name: inquirer - version: 3.1.3 - manager: conda - platform: linux-64 - dependencies: - blessed: '>=1.19.0' - poetry: '' - python: '>=3.7' - python-editor: '>=1.0.4' - readchar: '>=2.0.1' - url: https://conda.anaconda.org/conda-forge/noarch/inquirer-3.1.3-pyhd8ed1ab_0.conda - hash: - md5: 0d8bc31361e09dc50555465284e10880 - sha256: da912877ac6e0795490834c96167e93a1eda89290ef8de63502740ef738d4435 - category: main - optional: false -- name: mlflow - version: 2.3.2 - manager: conda - platform: linux-64 - dependencies: - alembic: <2,!=1.10 - click: '>=7.0,<9' - cloudpickle: <3 - databricks-cli: '>=0.8.7,<1' - docker-py: '>=4.0.0,<7' - entrypoints: <1 - flask: <3 - gitpython: '>=2.1.0,<4' - gunicorn: <21 - importlib-metadata: <7,>=3.7.0,!=4.7.0 - jinja2: <4,>=2.11 - markdown: <4,>=3.3 - matplotlib-base: <4 - numpy: <2 - openssl: '' - packaging: <24 - pandas: <3 - prometheus_flask_exporter: <1 - protobuf: '>=3.12.0,<5' - pyarrow: <12,>=4.0.0 - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - pytz: <2024 - pyyaml: '>=5.1,<7' - querystring_parser: <2 - requests: '>=2.17.3,<3' - scikit-learn: <2 - scipy: <2 - sqlalchemy: '>=1.4.0,<3' - sqlparse: '>=0.4.0,<1' - url: https://conda.anaconda.org/conda-forge/linux-64/mlflow-2.3.2-py39ha39b057_1.conda - hash: - md5: 86b04308468de463d9ba8aa203385e09 - sha256: e8a6ab7ac9c91b5303cbe1ea14d124bd0333fc7c50f89f41c272d51636812878 - category: main - optional: false -- name: lightning - version: 2.0.0 - manager: conda - platform: linux-64 - dependencies: - arrow: <3.0,>=1.2.0 - beautifulsoup4: <6.0,>=4.8.0 - click: <10.0 - croniter: <1.4.0,>=1.3.0 - dateutils: <2.0 - deepdiff: <8.0,>=5.7.0 - fastapi: <0.89.0 - fsspec: <2024.0,>=2022.5.0 - inquirer: <5.0,>=2.10.0 - jinja2: <5.0 - lightning-cloud: '>=0.5.31' - lightning-utilities: <2.0,>=0.7.0 - numpy: <3.0,>=1.17.2 - packaging: '' - psutil: <7.0 - pydantic: <3.0 - python: '>=3.8' - python-multipart: '' - pytorch: <4.0,>=1.11.0 - pytorch-lightning: '' - pyyaml: <8.0 - requests: <4.0 - rich: <15.0,>=12.3.0 - starlette: <2.0 - starsessions: <2.0,>=1.2.1 - torchmetrics: <2.0,>=0.7.0 - tqdm: <6.0,>=4.57.0 - traitlets: <7.0,>=5.3.0 - typing-extensions: <6.0,>=4.0.0 - urllib3: <3.0 - uvicorn: <2.0 - websocket-client: <3.0 - websockets: <12.0 - url: https://conda.anaconda.org/conda-forge/noarch/lightning-2.0.0-pyhd8ed1ab_0.conda - hash: - md5: 5c38b552a8b1853c39738d9a9f090ffc - sha256: 4b6bf3a963ea91f81de4947ad8a0686bb8cf868f63a5a125088938fc9551ace1 - category: main - optional: false diff --git a/ai/env-files/pytorch-lock.yml b/ai/env-files/pytorch-lock.yml deleted file mode 100644 index 8f66dc1e..00000000 --- a/ai/env-files/pytorch-lock.yml +++ /dev/null @@ -1,19174 +0,0 @@ -# This lock file was generated by conda-lock (https://github.com/conda/conda-lock). DO NOT EDIT! -# -# A "lock file" contains a concrete list of package versions (with checksums) to be installed. Unlike -# e.g. `conda env create`, the resulting environment will not change as new package versions become -# available, unless you explicitly update the lock file. -# -# Install this environment as "YOURENV" with: -# conda-lock install -n YOURENV --file pytorch-lock.yml -# To update a single package to the latest version compatible with the version constraints in the source: -# conda-lock lock --lockfile pytorch-lock.yml --update PACKAGE -# To re-solve the entire environment, e.g. after changing a version constraint in the source file: -# conda-lock -f /home/mbunino/itwin/T6.5-AI-and-ML/ai/env-files/pytorch-env.yml -f pytorch-env.yml --lockfile pytorch-lock.yml -version: 1 -metadata: - content_hash: - linux-64: e843ae860805bf9aca9656756d8c614bdaa98d0a55e0aacb49a0b69f1e560d85 - linux-aarch64: 7a6e15af267fc90b0237c85983bd4ecc665920e436adad9dce4613c471f2b983 - osx-64: f14f329efd5de8983cce09e5df6470e1ceff2be31e70f89649939395cd1915d1 - osx-arm64: 61a0181909760a0bf113d006015d9d92c72e94cf72fdcdfa5146ffe8d9a5157a - win-64: 3ff158dca28512bc9fc7a16c7d4ade00f4a5bc7232c34503ffb5576c561c146a - channels: - - url: pytorch - used_env_vars: [] - - url: conda-forge - used_env_vars: [] - platforms: - - linux-64 - - linux-aarch64 - - osx-64 - - osx-arm64 - - win-64 - sources: - - /home/mbunino/itwin/T6.5-AI-and-ML/ai/env-files/pytorch-env.yml - - pytorch-env.yml -package: -- name: _libgcc_mutex - version: '0.1' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - hash: - md5: d7c89558ba9fa0495403155b64376d81 - sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 - category: main - optional: false -- name: ca-certificates - version: 2023.5.7 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2023.5.7-hbcca054_0.conda - hash: - md5: f5c65075fc34438d5b456c7f3f5ab695 - sha256: 0cf1bb3d0bfc5519b60af2c360fa4888fb838e1476b1e0f65b9dbc48b45c7345 - category: main - optional: false -- name: ld_impl_linux-64 - version: '2.40' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda - hash: - md5: 7aca3059a1729aa76c597603f10b0dd3 - sha256: f6cc89d887555912d6c61b295d398cff9ec982a3417d38025c45d5dd9b9e79cd - category: main - optional: false -- name: libgfortran5 - version: 12.2.0 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2 - hash: - md5: 164b4b1acaedc47ee7e658ae6b308ca3 - sha256: 03ea784edd12037dc3a7a0078ff3f9c3383feabb34d5ba910bb2fd7a21a2d961 - category: main - optional: false -- name: libstdcxx-ng - version: 12.2.0 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2 - hash: - md5: 1030b1f38c129f2634eae026f704fe60 - sha256: 0289e6a7b9a5249161a3967909e12dcfb4ab4475cdede984635d3fb65c606f08 - category: main - optional: false -- name: python_abi - version: '3.9' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-3_cp39.conda - hash: - md5: 0dd193187d54e585cac7eab942a8847e - sha256: 89e8c4436dd04d8b4a0c13c508e930be56973a480a9714171969de953bdafd3a - category: main - optional: false -- name: pytorch-mutex - version: '1.0' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/pytorch/noarch/pytorch-mutex-1.0-cpu.tar.bz2 - hash: - md5: 49565ed726991fd28d08a39885caa88d - sha256: d48c964188ca49660d750cffd73698d217cf94e694cd51987f9f186425435e76 - category: main - optional: false -- name: tzdata - version: 2023c - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2023c-h71feb2d_0.conda - hash: - md5: 939e3e74d8be4dac89ce83b20de2492a - sha256: 0449138224adfa125b220154408419ec37c06b0b49f63c5954724325903ecf55 - category: main - optional: false -- name: cpuonly - version: '2.0' - manager: conda - platform: linux-64 - dependencies: - pytorch-mutex: '1.0' - url: https://conda.anaconda.org/pytorch/noarch/cpuonly-2.0-0.tar.bz2 - hash: - md5: 1cf3a59ef90a4078c253e3b02c272065 - sha256: f9107aca2a9d23a032634644df5cdb8d0185337891593ce540adc480810ab539 - category: main - optional: false -- name: libgfortran-ng - version: 12.2.0 - manager: conda - platform: linux-64 - dependencies: - libgfortran5: 12.2.0 - url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2 - hash: - md5: cd7a806282c16e1f2d39a7e80d3a3e0d - sha256: c7d061f323e80fbc09564179073d8af303bf69b953b0caddcf79b47e352c746f - category: main - optional: false -- name: _openmp_mutex - version: '4.5' - manager: conda - platform: linux-64 - dependencies: - _libgcc_mutex: '0.1' - llvm-openmp: '>=9.0.1' - url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2 - hash: - md5: 562b26ba2e19059551a811e72ab7f793 - sha256: 84a66275da3a66e3f3e70e9d8f10496d807d01a9e4ec16cd2274cc5e28c478fc - category: main - optional: false -- name: libgcc-ng - version: 12.2.0 - manager: conda - platform: linux-64 - dependencies: - _libgcc_mutex: '0.1' - _openmp_mutex: '>=4.5' - url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.2.0-h65d4601_19.tar.bz2 - hash: - md5: e4c94f80aef025c17ab0828cd85ef535 - sha256: f3899c26824cee023f1e360bd0859b0e149e2b3e8b1668bc6dd04bfc70dcd659 - category: main - optional: false -- name: aws-c-common - version: 0.8.19 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.8.19-hd590300_0.conda - hash: - md5: 81bd50906818d08c2f98d6d9f94cbd02 - sha256: cbbec8482596a93cfe14ec8f05d27fd7a1807995b407fa057790db8d41b2d80b - category: main - optional: false -- name: bzip2 - version: 1.0.8 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2 - hash: - md5: a1fd65c7ccbf10880423d82bca54eb54 - sha256: cb521319804640ff2ad6a9f118d972ed76d86bea44e5626c09a13d38f562e1fa - category: main - optional: false -- name: c-ares - version: 1.19.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.19.1-hd590300_0.conda - hash: - md5: e8c18d865be43e2fb3f7a145b6adf1f5 - sha256: c4276b1a0e8f18ab08018b1881666656742b325e0fcf2354f714e924d28683b6 - category: main - optional: false -- name: cudatoolkit - version: 11.8.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/cudatoolkit-11.8.0-h37601d7_11.conda - hash: - md5: 9d166760c8cfa83e2fc989928312da3d - sha256: 4cf988a4d49fb8c84fce134d05d4ea4e575af1c5219bde10dba1e65e288c3c61 - category: main - optional: false -- name: gettext - version: 0.21.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2 - hash: - md5: 14947d8770185e5153fdd04d4673ed37 - sha256: 4fcfedc44e4c9a053f0416f9fc6ab6ed50644fca3a761126dbd00d09db1f546a - category: main - optional: false -- name: gflags - version: 2.2.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - libstdcxx-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-he1b5a44_1004.tar.bz2 - hash: - md5: cddaf2c63ea4a5901cf09524c490ecdc - sha256: a853c0cacf53cfc59e1bca8d6e5cdfe9f38fce836f08c2a69e35429c2a492e77 - category: main - optional: false -- name: gmp - version: 6.2.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - libstdcxx-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.2.1-h58526e2_0.tar.bz2 - hash: - md5: b94cf2db16066b242ebd26db2facbd56 - sha256: 07a5319e1ac54fe5d38f50c60f7485af7f830b036da56957d0bfb7558a886198 - category: main - optional: false -- name: icu - version: '72.1' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/icu-72.1-hcb278e6_0.conda - hash: - md5: 7c8d20d847bb45f56bd941578fcfa146 - sha256: e44cc00eec068e7f7a6dd117ba17bf5d57658729b7b841945546f82505138292 - category: main - optional: false -- name: jpeg - version: 9e - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h0b41bf4_3.conda - hash: - md5: c7a069243e1fbe9a556ed2ec030e6407 - sha256: 8f73194d09c9ea4a7e2b3562766b8d72125cc147b62c7cf83393e3a3bbfd581b - category: main - optional: false -- name: keyutils - version: 1.6.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 - hash: - md5: 30186d27e2c9fa62b45fb1476b7200e3 - sha256: 150c05a6e538610ca7c43beb3a40d65c90537497a4f6a5f4d15ec0451b6f5ebb - category: main - optional: false -- name: lame - version: '3.100' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2 - hash: - md5: a8832b479f93521a9e7b5b743803be51 - sha256: aad2a703b9d7b038c0f745b853c6bb5f122988fe1a7a096e0e606d9cbec4eaab - category: main - optional: false -- name: lerc - version: 4.0.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2 - hash: - md5: 76bbff344f0134279f225174e9064c8f - sha256: cb55f36dcd898203927133280ae1dc643368af041a48bcf7c026acb7c47b0c12 - category: main - optional: false -- name: libabseil - version: '20230125.2' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20230125.2-cxx17_h59595ed_2.conda - hash: - md5: f67106643beadfc737b94ca0bfd6d8e3 - sha256: 1778dc86603df24aaf6865f7f3e1ffc5c793a0f1fc4570add2a6ccb4c0a62785 - category: main - optional: false -- name: libbrotlicommon - version: 1.0.9 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2 - hash: - md5: 9194c9bf9428035a05352d031462eae4 - sha256: ddc961a36d498aaafd5b71078836ad5dd247cc6ba7924157f3801a2f09b77b14 - category: main - optional: false -- name: libcrc32c - version: 1.1.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - libstdcxx-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 - hash: - md5: c965a5aa0d5c1c37ffc62dff36e28400 - sha256: fd1d153962764433fe6233f34a72cdeed5dcf8a883a85769e8295ce940b5b0c5 - category: main - optional: false -- name: libdeflate - version: '1.17' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.17-h0b41bf4_0.conda - hash: - md5: 5cc781fd91968b11a8a7fdbee0982676 - sha256: f9983a8ea03531f2c14bce76c870ca325c0fddf0c4e872bff1f78bc52624179c - category: main - optional: false -- name: libev - version: '4.33' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2 - hash: - md5: 6f8720dff19e17ce5d48cfe7f3d2f0a3 - sha256: 8c9635aa0ea28922877dc96358f9547f6a55fc7e2eb75a556b05f1725496baf9 - category: main - optional: false -- name: libexpat - version: 2.5.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.5.0-hcb278e6_1.conda - hash: - md5: 6305a3dd2752c76335295da4e581f2fd - sha256: 74c98a563777ae2ad71f1f74d458a8ab043cee4a513467c159ccf159d0e461f3 - category: main - optional: false -- name: libffi - version: 3.4.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - hash: - md5: d645c6d2ac96843a2bfaccd2d62b3ac3 - sha256: ab6e9856c21709b7b517e940ae7028ae0737546122f83c2aa5d692860c3b149e - category: main - optional: false -- name: libiconv - version: '1.17' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2 - hash: - md5: b62b52da46c39ee2bc3c162ac7f1804d - sha256: 6a81ebac9f1aacdf2b4f945c87ad62b972f0f69c8e0981d68e111739e6720fd7 - category: main - optional: false -- name: libnsl - version: 2.0.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2 - hash: - md5: 39b1328babf85c7c3a61636d9cd50206 - sha256: 32f4fb94d99946b0dabfbbfd442b25852baf909637f2eed1ffe3baea15d02aad - category: main - optional: false -- name: libnuma - version: 2.0.16 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libnuma-2.0.16-h0b41bf4_1.conda - hash: - md5: 28bfe2cb11357ccc5be21101a6b7ce86 - sha256: 814a50cba215548ec3ebfb53033ffb9b3b070b2966570ff44910b8d9ba1c359d - category: main - optional: false -- name: libsodium - version: 1.0.18 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.18-h36c2ea0_1.tar.bz2 - hash: - md5: c3788462a6fbddafdb413a9f9053e58d - sha256: 53da0c8b79659df7b53eebdb80783503ce72fb4b10ed6e9e05cc0e9e4207a130 - category: main - optional: false -- name: libutf8proc - version: 2.8.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.8.0-h166bdaf_0.tar.bz2 - hash: - md5: ede4266dc02e875fe1ea77b25dd43747 - sha256: 49082ee8d01339b225f7f8c60f32a2a2c05fe3b16f31b554b4fb2c1dea237d1c - category: main - optional: false -- name: libuuid - version: 2.38.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - hash: - md5: 40b61aab5c7ba9ff276c41cfffe6b80b - sha256: 787eb542f055a2b3de553614b25f09eefb0a0931b0c87dbcce6efdfd92f04f18 - category: main - optional: false -- name: libwebp-base - version: 1.3.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.3.0-h0b41bf4_0.conda - hash: - md5: 0d4a7508d8c6c65314f2b9c1f56ad408 - sha256: ac3e073ea77803da71eb77e7fcef07defb345bda95eee3327c73ddf85b5714da - category: main - optional: false -- name: libzlib - version: 1.2.13 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2 - hash: - md5: f3f9de449d32ca9b9c66a22863c96f41 - sha256: 22f3663bcf294d349327e60e464a51cd59664a71b8ed70c28a9f512d10bc77dd - category: main - optional: false -- name: lz4-c - version: 1.9.4 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda - hash: - md5: 318b08df404f9c9be5712aaa5a6f0bb0 - sha256: 1b4c105a887f9b2041219d57036f72c4739ab9e9fe5a1486f094e58c76b31f5f - category: main - optional: false -- name: ncurses - version: '6.3' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2 - hash: - md5: 4acfc691e64342b9dae57cf2adc63238 - sha256: b801e8cf4b2c9a30bce5616746c6c2a4e36427f045b46d9fc08a4ed40a9f7065 - category: main - optional: false -- name: nettle - version: '3.6' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/nettle-3.6-he412f7d_0.tar.bz2 - hash: - md5: f050099af540c1c960c813b06bca89ad - sha256: d929f0c53f2bb74c8e3d97dc1c53cc76b7cec97837fcf87998fa3dd447f03b36 - category: main - optional: false -- name: openssl - version: 3.1.1 - manager: conda - platform: linux-64 - dependencies: - ca-certificates: '' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.1.1-hd590300_1.conda - hash: - md5: 2e1d7b458ac8f1e3ca4e18b77add6277 - sha256: 407d655643389bdb49266842a816815c981ae98f3513a6a2059b908b3abb380a - category: main - optional: false -- name: pthread-stubs - version: '0.4' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2 - hash: - md5: 22dad4df6e8630e8dff2428f6f6a7036 - sha256: 67c84822f87b641d89df09758da498b2d4558d47b920fd1d3fe6d3a871e000ff - category: main - optional: false -- name: rdma-core - version: '28.9' - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-28.9-h59595ed_1.conda - hash: - md5: aeffb7c06b5f65e55e6c637408dc4100 - sha256: 832f9393ab3144ce6468c6f150db9d398fad4451e96a8879afb3059f0c9902f6 - category: main - optional: false -- name: re2 - version: 2023.03.02 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/re2-2023.03.02-h8c504da_0.conda - hash: - md5: 206f8fa808748f6e90599c3368a1114e - sha256: 1727f893a352ca735fb96b09f9edf6fe18c409d65550fd37e8a192919e8c827b - category: main - optional: false -- name: snappy - version: 1.1.10 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.1.10-h9fff704_0.conda - hash: - md5: e6d228cd0bb74a51dd18f5bfce0b4115 - sha256: 02219f2382b4fe39250627dade087a4412d811936a5a445636b7260477164eac - category: main - optional: false -- name: xorg-libxau - version: 1.0.11 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.11-hd590300_0.conda - hash: - md5: 2c80dc38fface310c9bd81b17037fee5 - sha256: 309751371d525ce50af7c87811b435c176915239fc9e132b99a25d5e1703f2d4 - category: main - optional: false -- name: xorg-libxdmcp - version: 1.1.3 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2 - hash: - md5: be93aabceefa2fac576e971aef407908 - sha256: 4df7c5ee11b8686d3453e7f3f4aa20ceef441262b49860733066c52cfd0e4a77 - category: main - optional: false -- name: xz - version: 5.2.6 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 - hash: - md5: 2161070d867d1b1204ea749c8eec4ef0 - sha256: 03a6d28ded42af8a347345f82f3eebdd6807a08526d47899a42d62d319609162 - category: main - optional: false -- name: yaml - version: 0.2.5 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 - hash: - md5: 4cb3ad778ec2d5a7acbdf254eb1c42ae - sha256: a4e34c710eeb26945bdbdaba82d3d74f60a78f54a874ec10d373811a5d217535 - category: main - optional: false -- name: aws-c-cal - version: 0.5.26 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.5.26-hf677bf3_1.conda - hash: - md5: 7d00f2b22493e28400fdbea8dc110790 - sha256: 74f8fa528ca4096119f6b9de78fb6dd4f7a7345f10b8b1df3634d77be53ad01c - category: main - optional: false -- name: aws-c-compression - version: 0.2.16 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.2.16-hbad4bc6_7.conda - hash: - md5: d58359b64c6d1256c07eeaee753159e3 - sha256: 7a7df29971e9a50231bc0a954fc451052479bc2427df66a82014797418a7ffea - category: main - optional: false -- name: aws-c-sdkutils - version: 0.1.9 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.1.9-hbad4bc6_2.conda - hash: - md5: eee3831810e132b6caf45f03c3428363 - sha256: 4d6713d840f1767cb4989f1b0c5b4f7fea8ce18f68d575ba6bb8e9fac51750e9 - category: main - optional: false -- name: aws-checksums - version: 0.1.14 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.1.14-hbad4bc6_7.conda - hash: - md5: 916c2a86bf786aab811a60990f7538ed - sha256: 5b3c6230ec0f2880ca1738d169d74d7edeef4ad541a6d6460513b70c3a15bf85 - category: main - optional: false -- name: expat - version: 2.5.0 - manager: conda - platform: linux-64 - dependencies: - libexpat: 2.5.0 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-hcb278e6_1.conda - hash: - md5: 8b9b5aca60558d02ddaa09d599e55920 - sha256: 36dfeb4375059b3bba75ce9b38c29c69fd257342a79e6cf20e9f25c1523f785f - category: main - optional: false -- name: glog - version: 0.6.0 - manager: conda - platform: linux-64 - dependencies: - gflags: '>=2.2.2,<2.3.0a0' - libgcc-ng: '>=10.3.0' - libstdcxx-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/glog-0.6.0-h6f12383_0.tar.bz2 - hash: - md5: b31f3565cb84435407594e548a2fb7b2 - sha256: 888cbcfb67f6e3d88a4c4ab9d26c9a406f620c4101a35dc6d2dbadb95f2221d4 - category: main - optional: false -- name: gnutls - version: 3.6.13 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - libstdcxx-ng: '>=7.5.0' - nettle: '>=3.6,<3.7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/gnutls-3.6.13-h85f3911_1.tar.bz2 - hash: - md5: 7d1b6fff16c1431d96cb4934938799fd - sha256: 6c9307f0fedce2c4d060bba9ac888b300bc0912effab423d67b8e1b661a93305 - category: main - optional: false -- name: libbrotlidec - version: 1.0.9 - manager: conda - platform: linux-64 - dependencies: - libbrotlicommon: 1.0.9 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2 - hash: - md5: 4ae4d7795d33e02bd20f6b23d91caf82 - sha256: d88ba07c3be27c89cb4975cc7edf63ee7b1c62d01f70d5c3f7efeb987c82b052 - category: main - optional: false -- name: libbrotlienc - version: 1.0.9 - manager: conda - platform: linux-64 - dependencies: - libbrotlicommon: 1.0.9 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2 - hash: - md5: 04bac51ba35ea023dc48af73c1c88c25 - sha256: a0468858b2f647f51509a32040e93512818a8f9980f20b3554cccac747bcc4be - category: main - optional: false -- name: libedit - version: 3.1.20191231 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - ncurses: '>=6.2,<7.0.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2 - hash: - md5: 4d331e44109e3f0e19b4cb8f9b82f3e1 - sha256: a57d37c236d8f7c886e01656f4949d9dcca131d2a0728609c6f7fa338b65f1cf - category: main - optional: false -- name: libevent - version: 2.1.12 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-h3358134_0.conda - hash: - md5: c164eb2e0df905571d68f40ae957522d - sha256: 4fa9ffd6b627a9cbecc602fa60c52ef6e0a4020d4a45f1a18d8f300e17b18ace - category: main - optional: false -- name: libnghttp2 - version: 1.52.0 - manager: conda - platform: linux-64 - dependencies: - c-ares: '>=1.18.1,<2.0a0' - libev: '>=4.33,<4.34.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.0.8,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.52.0-h61bc06f_0.conda - hash: - md5: 613955a50485812985c059e7b269f42e - sha256: ecd6b08c2b5abe7d1586428c4dd257dcfa00ee53700d79cdc8bca098fdfbd79a - category: main - optional: false -- name: libpng - version: 1.6.39 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda - hash: - md5: e1c890aebdebbfbf87e2c917187b4416 - sha256: a32b36d34e4f2490b99bddbc77d01a674d304f667f0e62c89e02c961addef462 - category: main - optional: false -- name: libprotobuf - version: 3.21.12 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-3.21.12-h3eb15da_0.conda - hash: - md5: 4b36c68184c6c85d88c6e595a32a1ede - sha256: 760118d7879b5524e118db1c75cc2a5dfceb2c4940dcae94751a94786c8cf12b - category: main - optional: false -- name: libsqlite - version: 3.42.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.42.0-h2797004_0.conda - hash: - md5: fdaae20a1cf7cd62130a0973190a31b7 - sha256: 72e958870f49174ebc0ddcd4129e9a9f48de815f20aa3b553f136b514f29bb3a - category: main - optional: false -- name: libssh2 - version: 1.10.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libzlib: '>=1.2.12,<1.3.0a0' - openssl: '>=3.0.5,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-hf14f497_3.tar.bz2 - hash: - md5: d85acad4b47dff4e3def14a769a97906 - sha256: 9a9a01f35d2d50326eb8ca7c0a92d0c45b2d0f77d9ea117680c70094ff480c0c - category: main - optional: false -- name: libxcb - version: '1.13' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - pthread-stubs: '' - xorg-libxau: '' - xorg-libxdmcp: '' - url: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2 - hash: - md5: b3653fdc58d03face9724f602218a904 - sha256: 8d5d24cbeda9282dd707edd3156e5fde2e3f3fe86c802fa7ce08c8f1e803bfd9 - category: main - optional: false -- name: libxml2 - version: 2.11.4 - manager: conda - platform: linux-64 - dependencies: - icu: '>=72.1,<73.0a0' - libgcc-ng: '>=12' - libiconv: '>=1.17,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - xz: '>=5.2.6,<6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.11.4-h0d562d8_0.conda - hash: - md5: e46fad17d5fb57316b956f88dca765e4 - sha256: bc7fa8590c15ffdea5101075ce02de09441c9f378ac1d05e26510d12d25d7099 - category: main - optional: false -- name: pcre2 - version: '10.40' - manager: conda - platform: linux-64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - libgcc-ng: '>=12' - libzlib: '>=1.2.12,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2 - hash: - md5: 69e2c796349cd9b273890bee0febfe1b - sha256: 7a29ec847556eed4faa1646010baae371ced69059a4ade43851367a076d6108a - category: main - optional: false -- name: readline - version: '8.2' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - ncurses: '>=6.3,<7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda - hash: - md5: 47d31b792659ce70f470b5c82fdfb7a4 - sha256: 5435cf39d039387fbdc977b0a762357ea909a7694d9528ab40f005e9208744d7 - category: main - optional: false -- name: s2n - version: 1.3.44 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.3.44-h06160fa_0.conda - hash: - md5: 968cb0fca1249fe9778876201dd2b828 - sha256: 6b719f9fc5a53a34f6ce17ac22aa8bc70b526caf9cef72c4d01964e01cb156b6 - category: main - optional: false -- name: tk - version: 8.6.12 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - libzlib: '>=1.2.11,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2 - hash: - md5: 5b8c42eb62e9fc961af70bdd6a26e168 - sha256: 032fd769aad9d4cad40ba261ab222675acb7ec951a8832455fce18ef33fa8df0 - category: main - optional: false -- name: ucx - version: 1.14.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libnuma: '>=2.0.16,<3.0a0' - libstdcxx-ng: '>=12' - rdma-core: '>=28.9,<29.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/ucx-1.14.1-hf587318_2.conda - hash: - md5: 37b27851c8d5a9a98e61ecd36aa990a7 - sha256: 3cb73a2177457d092cbe1ac5ad1a7ea2c75c1080516776807014a5d991f146e6 - category: main - optional: false -- name: zlib - version: 1.2.13 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libzlib: 1.2.13 - url: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2 - hash: - md5: 4b11e365c0275b808be78b30f904e295 - sha256: 282ce274ebe6da1fbd52efbb61bd5a93dec0365b14d64566e6819d1691b75300 - category: main - optional: false -- name: zstd - version: 1.5.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h3eb15da_6.conda - hash: - md5: 6b63daed8feeca47be78f323e793d555 - sha256: fbe49a8c8df83c2eccb37c5863ad98baeb29796ec96f2c503783d7b89bf80c98 - category: main - optional: false -- name: aws-c-io - version: 0.13.21 - manager: conda - platform: linux-64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - s2n: '>=1.3.44,<1.3.45.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.13.21-h9fef7b8_5.conda - hash: - md5: 0e64949e8f740ceeb9f1d6255f314ab2 - sha256: 74844c9651627bb4274a8976d3ea077df7a6ab48cc2150db330fd0f39d0b85f5 - category: main - optional: false -- name: brotli-bin - version: 1.0.9 - manager: conda - platform: linux-64 - dependencies: - libbrotlidec: 1.0.9 - libbrotlienc: 1.0.9 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2 - hash: - md5: e5613f2bc717e9945840ff474419b8e4 - sha256: ab1994e03bdd88e4b27f9f802ac18e45ed29b92cce25e1fd86da43b89734950f - category: main - optional: false -- name: freetype - version: 2.12.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libpng: '>=1.6.39,<1.7.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda - hash: - md5: e1232042de76d24539a436d37597eb06 - sha256: 1eb913727b54e9aa63c6d9a1177db4e2894cee97c5f26910a2b61899d5ac904f - category: main - optional: false -- name: krb5 - version: 1.20.1 - manager: conda - platform: linux-64 - dependencies: - keyutils: '>=1.6.1,<2.0a0' - libedit: '>=3.1.20191231,<4.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - openssl: '>=3.0.7,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.20.1-h81ceb04_0.conda - hash: - md5: 89a41adce7106749573d883b2f657d78 - sha256: 51a346807ce981e1450eb04c3566415b05eed705bc9e6c98c198ec62367b7c62 - category: main - optional: false -- name: libglib - version: 2.76.3 - manager: conda - platform: linux-64 - dependencies: - gettext: '>=0.21.1,<1.0a0' - libffi: '>=3.4,<4.0a0' - libgcc-ng: '>=12' - libiconv: '>=1.17,<2.0a0' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - pcre2: '>=10.40,<10.41.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.76.3-hebfc3b9_0.conda - hash: - md5: a64f11b244b2c112cd3fa1cbe9493999 - sha256: 6a34c6b123f06fcee7e28e981ec0daad09bce35616ad8e9e61ef84be7fad4d92 - category: main - optional: false -- name: libgrpc - version: 1.54.2 - manager: conda - platform: linux-64 - dependencies: - c-ares: '>=1.18.1,<2.0a0' - libabseil: '>=20230125.2,<20230126.0a0' - libgcc-ng: '>=12' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - re2: '>=2023.3.2,<2023.3.3.0a0' - zlib: '' - url: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.54.2-hb20ce57_2.conda - hash: - md5: 2d6c2c90dd7805816bd78d80977b61d6 - sha256: 8a18c842bbd87bd80f83a3935269650309d8675e9eed3760ee47ea1d6a9b015d - category: main - optional: false -- name: libhwloc - version: 2.9.1 - manager: conda - platform: linux-64 - dependencies: - __cuda: '' - __glibc: '>=2.17' - cudatoolkit: '>=11.2,<12' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libxml2: '>=2.11.4,<2.12.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.9.1-cuda112_haf10fcf_5.conda - hash: - md5: b8996ffa972161676ba6972af4c41384 - sha256: fd4709c6b1960a822899658e9e7bc531dc5ef9b93f9e60c45bcee60d016453f6 - category: main - optional: false -- name: libthrift - version: 0.18.1 - manager: conda - platform: linux-64 - dependencies: - libevent: '>=2.1.12,<2.1.13.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.18.1-h8fd135c_1.conda - hash: - md5: a62fdab22023982131b5f21afdb9a6c8 - sha256: 3c53fc71cd1b582c879f858bb2463439381c26a3e88224fe1414df0ff1328520 - category: main - optional: false -- name: libtiff - version: 4.5.0 - manager: conda - platform: linux-64 - dependencies: - jpeg: '>=9e,<10a' - lerc: '>=4.0.0,<5.0a0' - libdeflate: '>=1.17,<1.18.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libwebp-base: '>=1.2.4,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - xz: '>=5.2.6,<6.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h6adf6a1_2.conda - hash: - md5: 2e648a34072eb39d7c4fc2a9981c5f0c - sha256: e3e18d91fb282b61288d4fd2574dfa31f7ae90ef2737f96722fb6ad3257862ee - category: main - optional: false -- name: llvm-openmp - version: 16.0.4 - manager: conda - platform: linux-64 - dependencies: - libzlib: '>=1.2.13,<1.3.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-16.0.4-h4dfa4b3_0.conda - hash: - md5: 68ffdf82a717033ead1c5edbfeff9f54 - sha256: 48df036610d3ee357b408256f222589ea24909572b20e0bf73f9a1a3c42fe255 - category: main - optional: false -- name: openh264 - version: 2.1.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - libstdcxx-ng: '>=9.3.0' - zlib: '>=1.2.11,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/openh264-2.1.1-h780b84a_0.tar.bz2 - hash: - md5: 034a6f90f1bbc7ba11d04b84ec9d74c8 - sha256: 2ce3df1edb23541595443c7697e5568ae6426fa4d365dede45b16b0310bd6a06 - category: main - optional: false -- name: orc - version: 1.8.3 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - snappy: '>=1.1.10,<2.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/orc-1.8.3-hfdbbad2_0.conda - hash: - md5: 8aafd0a5ba97bf0cc451550b147a4e0a - sha256: aa82f7e63fbc3facec2f291669dba1561b2b8d45d02381abc9d8f66638433f2d - category: main - optional: false -- name: sqlite - version: 3.42.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libsqlite: 3.42.0 - libzlib: '>=1.2.13,<1.3.0a0' - ncurses: '>=6.3,<7.0a0' - readline: '>=8.2,<9.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.42.0-h2c6b66d_0.conda - hash: - md5: 1192f6ec654a5bc4ee1d64bdc4a3e5cc - sha256: 9cf59fa9891248e0e3a86a41041156cec367653d423e5d8a09b4c8ab98441a27 - category: main - optional: false -- name: aws-c-event-stream - version: 0.2.20 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.2.20-hb4b372c_7.conda - hash: - md5: 7eb8e72640ac21ce4e7d26e873a21cbe - sha256: 5882ece0a5fc62a63fc910d2d610e144fd76f83b5dab4036d1632c50faddebbd - category: main - optional: false -- name: aws-c-http - version: 0.7.7 - manager: conda - platform: linux-64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-compression: '>=0.2.16,<0.2.17.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.7.7-h2632f9a_4.conda - hash: - md5: f4cd59b8e2ac740faded0f75aa965a71 - sha256: c4b886452d715ffe223da113e18eb939c15d056304247e37d1e9927cec0d31c7 - category: main - optional: false -- name: brotli - version: 1.0.9 - manager: conda - platform: linux-64 - dependencies: - brotli-bin: 1.0.9 - libbrotlidec: 1.0.9 - libbrotlienc: 1.0.9 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2 - hash: - md5: 2ff08978892a3e8b954397c461f18418 - sha256: 74c0fa22ea7c62d2c8f7a7aea03a3bd4919f7f3940ef5b027ce0dfb5feb38c06 - category: main - optional: false -- name: dbus - version: 1.13.6 - manager: conda - platform: linux-64 - dependencies: - expat: '>=2.4.2,<3.0a0' - libgcc-ng: '>=9.4.0' - libglib: '>=2.70.2,<3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2 - hash: - md5: ecfff944ba3960ecb334b9a2663d708d - sha256: 8f5f995699a2d9dbdd62c61385bfeeb57c82a681a7c8c5313c395aa0ccab68a5 - category: main - optional: false -- name: ffmpeg - version: '4.3' - manager: conda - platform: linux-64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - freetype: '>=2.10.2,<3.0a0' - gmp: '>=6.1.2' - gnutls: '>=3.6.5,<3.7.0a0' - lame: '>=3.100,<3.101.0a0' - libgcc-ng: '>=7.3.0' - libiconv: '' - libstdcxx-ng: '>=7.3.0' - openh264: '>=2.1.0,<2.2.0a0' - zlib: '>=1.2.11,<1.3.0a0' - url: https://conda.anaconda.org/pytorch/linux-64/ffmpeg-4.3-hf484d3e_0.tar.bz2 - hash: - md5: 0b0bf7c3d7e146ef91de5310bbf7a230 - sha256: 60b3e36cb36b706f5850f155bd9d3f33194a522b5ef20be46cb37dbc987a6741 - category: main - optional: false -- name: lcms2 - version: '2.15' - manager: conda - platform: linux-64 - dependencies: - jpeg: '>=9e,<10a' - libgcc-ng: '>=12' - libtiff: '>=4.5.0,<4.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.15-hfd0df8a_0.conda - hash: - md5: aa8840cdf17ef0c6084d1e24abc7a28b - sha256: 443e926b585528112ec6aa4d85bf087722914ed8d85a2f75ae47c023c55c4238 - category: main - optional: false -- name: libcurl - version: 8.1.2 - manager: conda - platform: linux-64 - dependencies: - krb5: '>=1.20.1,<1.21.0a0' - libgcc-ng: '>=12' - libnghttp2: '>=1.52.0,<2.0a0' - libssh2: '>=1.10.0,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.1.2-h409715c_0.conda - hash: - md5: 50c873c9660ed116707ae15b663928d8 - sha256: d572c31ff48d2db6ca5bab476bf325811cfc82577480b3791487c3fe7bff2ffa - category: main - optional: false -- name: openjpeg - version: 2.5.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libpng: '>=1.6.39,<1.7.0a0' - libstdcxx-ng: '>=12' - libtiff: '>=4.5.0,<4.6.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-hfec8fc6_2.conda - hash: - md5: 5ce6a42505c6e9e6151c54c3ec8d68ea - sha256: 3cbfb1fe9bb492dcb672f98f0ddc7b4e029f51f77101d9c301caa3acaea8cba2 - category: main - optional: false -- name: python - version: 3.9.12 - manager: conda - platform: linux-64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - ld_impl_linux-64: '>=2.36.1' - libffi: '>=3.4.2,<3.5.0a0' - libgcc-ng: '>=10.3.0' - libnsl: '>=2.0.0,<2.1.0a0' - libuuid: '>=2.32.1,<3.0a0' - libzlib: '>=1.2.11,<1.3.0a0' - ncurses: '>=6.3,<7.0a0' - openssl: '>=3.0.2,<4.0a0' - readline: '>=8.1,<9.0a0' - sqlite: '>=3.37.1,<4.0a0' - tk: '>=8.6.12,<8.7.0a0' - tzdata: '' - xz: '>=5.2.5,<5.3.0a0' - pip: '' - url: https://conda.anaconda.org/conda-forge/linux-64/python-3.9.12-h2660328_1_cpython.tar.bz2 - hash: - md5: ec06c83477a59cf285544b9a41927305 - sha256: b6a81ac02df0fa274f86b99f6076d9322faa0b3e7d5eb66c735fd9e6b30f2770 - category: main - optional: false -- name: tbb - version: 2021.9.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libhwloc: '>=2.9.1,<2.9.2.0a0' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.9.0-hf52228f_0.conda - hash: - md5: f495e42d3d2020b025705625edf35490 - sha256: 86352f4361e8dc2374a95d9d1dfee742beecaa59dcb0e76ca36ca06a4efe1df2 - category: main - optional: false -- name: antlr-python-runtime - version: 4.9.3 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/antlr-python-runtime-4.9.3-pyhd8ed1ab_1.tar.bz2 - hash: - md5: c88eaec8de9ae1fa161205aa18e7a5b1 - sha256: b91f8ab4ac2b48972fbee1fc8e092cc452fdf59156e4ff2322c94bbf73650f94 - category: main - optional: false -- name: attrs - version: 23.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/attrs-23.1.0-pyh71513ae_1.conda - hash: - md5: 3edfead7cedd1ab4400a6c588f3e75f8 - sha256: 063639cd568f5c7a557b0fb1cc27f098598c0d8ff869088bfeb82934674f8821 - category: main - optional: false -- name: aws-c-auth - version: 0.6.27 - manager: conda - platform: linux-64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-c-sdkutils: '>=0.1.9,<0.1.10.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.6.27-he072965_1.conda - hash: - md5: 2c7406414796748e53bd7d7c6349711d - sha256: 62e7583c3042ebf5cf63c2f631d0338755fe9bc183e9ea835a090ef0faaaa42a - category: main - optional: false -- name: aws-c-mqtt - version: 0.8.11 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.8.11-h2282364_1.conda - hash: - md5: 11a4a996699d883ebda0894faa71bbfc - sha256: cebbf485e3fc744119a94d748c55da45f7ddf3401c20e89a5b5bf15f0c759367 - category: main - optional: false -- name: backports - version: '1.0' - manager: conda - platform: linux-64 - dependencies: - python: '>=2.7' - url: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_3.conda - hash: - md5: 54ca2e08b3220c148a1d8329c2678e02 - sha256: 711602276ae39276cb0faaca6fd0ac851fff0ca17151917569174841ef830bbd - category: main - optional: false -- name: blinker - version: 1.6.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/blinker-1.6.2-pyhd8ed1ab_0.conda - hash: - md5: 2fb79ec81bad9492b6d59a06b3b647a4 - sha256: b6f32491536823e47cf6eb4717dd341385600a2b901235028dedc629a77aeb82 - category: main - optional: false -- name: certifi - version: 2023.5.7 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/certifi-2023.5.7-pyhd8ed1ab_0.conda - hash: - md5: 5d1b71c942b8421285934dad1d891ebc - sha256: f839a6e04d94069f90dd85337ea9108f058dc76771bb469a413f32bb1ba0b256 - category: main - optional: false -- name: charset-normalizer - version: 3.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.1.0-pyhd8ed1ab_0.conda - hash: - md5: 7fcff9f6f123696e940bda77bd4d6551 - sha256: 06cd371fc98f076797d6450f6f337cb679b1060c99680fb7e044591493333194 - category: main - optional: false -- name: click - version: 8.1.3 - manager: conda - platform: linux-64 - dependencies: - __unix: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2 - hash: - md5: 20e4087407c7cb04a40817114b333dbf - sha256: 23676470b591b100393bb0f6c46fe10624dcbefc696a6a9f42932ed8816ef0ea - category: main - optional: false -- name: cloudpickle - version: 2.2.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.1-pyhd8ed1ab_0.conda - hash: - md5: b325bfc4cff7d7f8a868f1f7ecc4ed16 - sha256: f0c2fd0e842899a05ddd7b147fb26424adf58be0e8e54e5bc68b8f7e67d05dcd - category: main - optional: false -- name: colorama - version: 0.4.6 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3faab06a954c2a04039983f2c4a50d99 - sha256: 2c1b2e9755ce3102bca8d69e8f26e4f087ece73f50418186aee7c74bef8e1698 - category: main - optional: false -- name: configparser - version: 5.3.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/configparser-5.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: c99fd5916160900dc5ff64204da99c4d - sha256: ce6ce9ee08437b46c284d52b076fb091cf6f2a9e12860d4a37546adbd5f53b28 - category: main - optional: false -- name: crashtest - version: 0.4.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6,<4.0' - url: https://conda.anaconda.org/conda-forge/noarch/crashtest-0.4.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 709a2295dd907bb34afb57d54320642f - sha256: 2f05954a3faf0700c14c1deddc085385160ee32abe111699c78d9cb277e915cc - category: main - optional: false -- name: cycler - version: 0.11.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: a50559fad0affdbb33729a68669ca1cb - sha256: 3b594bc8aa0b9a51269d54c7a4ef6af777d7fad4bee16b05695e1124de6563f6 - category: main - optional: false -- name: distlib - version: 0.3.6 - manager: conda - platform: linux-64 - dependencies: - python: 2.7|>=3.6 - url: https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2 - hash: - md5: b65b4d50dbd2d50fa0aeac367ec9eed7 - sha256: 06eb7167d4d760b3b437a491e32ab5b3f89e2a18f023c117fe213b038d88538a - category: main - optional: false -- name: entrypoints - version: '0.4' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3cf04868fee0a029769bd41f4b2fbf2d - sha256: 2ec4a0900a4a9f42615fc04d0fb3286b796abe56590e8e042f6ec25e102dd5af - category: main - optional: false -- name: exceptiongroup - version: 1.1.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.1-pyhd8ed1ab_0.conda - hash: - md5: 7312299d7a0ea4993159229b7d2dceb2 - sha256: f073c3ba993912f1c0027bc34a54975642885f0a4cd5f9dc42a17ca945df2c18 - category: main - optional: false -- name: filelock - version: 3.12.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.12.0-pyhd8ed1ab_0.conda - hash: - md5: 650f18a56f366dbf419c15b543592c2d - sha256: 68db3a6280d6786be76f2c7c6cf41dd878c5d1a24f5de10f7f0af82c6fcfade6 - category: main - optional: false -- name: fsspec - version: 2023.5.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/fsspec-2023.5.0-pyh1a96a4e_0.conda - hash: - md5: 20edd290b319aa0eff3e9055375756dc - sha256: cbb5c77c0217cda9bf4f4240158de11822a099a6eaa05ba626e822819a54f46d - category: main - optional: false -- name: greenlet - version: 2.0.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/greenlet-2.0.2-py39h3d6467e_1.conda - hash: - md5: bcc2596fee9dccce15c6ae3d475ea013 - sha256: 90e6375270dcc431ce9b78764a616f222d1cc8f8409ec5eda196279cae88be64 - category: main - optional: false -- name: idna - version: '3.4' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 34272b248891bddccc64479f9a7fffed - sha256: 9887c35c374ec1847f167292d3fde023cb4c994a4ceeec283072b95440131f09 - category: main - optional: false -- name: itsdangerous - version: 2.1.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/itsdangerous-2.1.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3c3de74912f11d2b590184f03c7cd09b - sha256: 31e3492686b4e92b53db9b48bc0eb03873b1caaf28629fee7d2d47627a2c56d3 - category: main - optional: false -- name: jeepney - version: 0.8.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/jeepney-0.8.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 9800ad1699b42612478755a2d26c722d - sha256: 16639759b811866d63315fe1391f6fb45f5478b823972f4d3d9f0392b7dd80b8 - category: main - optional: false -- name: kiwisolver - version: 1.4.4 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_1.tar.bz2 - hash: - md5: 41679a052a8ce841c74df1ebc802e411 - sha256: eb28254cc7029e702d0059536d986b010221de62f9c8588a5a83e95a00b4e74d - category: main - optional: false -- name: libgoogle-cloud - version: 2.10.1 - manager: conda - platform: linux-64 - dependencies: - libabseil: '>=20230125.2,<20230126.0a0' - libcrc32c: '>=1.1.2,<1.2.0a0' - libcurl: '>=8.0.1,<9.0a0' - libgcc-ng: '>=12' - libgrpc: '>=1.54.2,<1.55.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-2.10.1-hac9eb74_1.conda - hash: - md5: 582726eb344fe32d6de102b748067f8d - sha256: 54c6c05e2ffb5bb76164aaa9b039fb928dadd159d03f0c847605a475ad9122dc - category: main - optional: false -- name: lockfile - version: 0.12.2 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/lockfile-0.12.2-py_1.tar.bz2 - hash: - md5: c104d98e09c47519950cffb8dd5b4f10 - sha256: d3a68045ef74a2a7b8c8a55b242fdbc875d362e37adcf793613cf0d8c8e4fbf7 - category: main - optional: false -- name: markupsafe - version: 2.1.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.2-py39h72bdee0_0.conda - hash: - md5: 35514f5320206df9f4661c138c02e1c1 - sha256: da31fe95611393bb7dd3dee309a89328448570fd8a3205c2c55c03eb73688b61 - category: main - optional: false -- name: mdurl - version: 0.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: f8dab71fdc13b1bf29a01248b156d268 - sha256: c678b9194e025b1fb665bec30ee20aab93399203583875b1dcc0a3b52a8f5523 - category: main - optional: false -- name: mkl - version: 2022.2.1 - manager: conda - platform: linux-64 - dependencies: - _openmp_mutex: '>=4.5' - llvm-openmp: '>=15.0.6' - tbb: 2021.* - url: https://conda.anaconda.org/conda-forge/linux-64/mkl-2022.2.1-h84fe81f_16997.conda - hash: - md5: a7ce56d5757f5b57e7daabe703ade5bb - sha256: 5322750d5e96ff5d96b1457db5fb6b10300f2bc4030545e940e17b57c4e96d00 - category: main - optional: false -- name: more-itertools - version: 9.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/more-itertools-9.1.0-pyhd8ed1ab_0.conda - hash: - md5: 1698a717f83cfecf644a877c174c84bd - sha256: 3ee8cbbe4004c56b695a5e734b7dc4d59dacbfefc193ee42c82238b1cf888e08 - category: main - optional: false -- name: msgpack-python - version: 1.0.5 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.0.5-py39h4b4f3f3_0.conda - hash: - md5: 413374bab5022a5199c5dd89aef75df5 - sha256: 9b4b426b97d712c1b631bb775aaa1822b06f63a0ca93343c6eee59ab06f2b46c - category: main - optional: false -- name: munkres - version: 1.1.4 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2 - hash: - md5: 2ba8498c1018c1e9c61eb99b973dfe19 - sha256: f86fb22b58e93d04b6f25e0d811b56797689d598788b59dcb47f59045b568306 - category: main - optional: false -- name: ordered-set - version: 4.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/ordered-set-4.1.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 9a8714decb3967b290263817e876d8a9 - sha256: 78d92f848a6b4a89148dfa1f6e65c0b75e8f3a267b6401be38fb3401853b4afa - category: main - optional: false -- name: orjson - version: 3.8.14 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/orjson-3.8.14-py39h10b2342_0.conda - hash: - md5: d0bd5cc61c364d91cfb9dba8a5820732 - sha256: af4de84a1ae5d710d3050b036d8bdaa81bb0f0191b9136034fa319b463d558f9 - category: main - optional: false -- name: packaging - version: '23.1' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/packaging-23.1-pyhd8ed1ab_0.conda - hash: - md5: 91cda59e66e1e4afe9476f8ef98f5c30 - sha256: ded536a96a00d45a693dbc2971bb688248324dadd129eddda2100e177583d768 - category: main - optional: false -- name: pillow - version: 9.4.0 - manager: conda - platform: linux-64 - dependencies: - freetype: '>=2.12.1,<3.0a0' - jpeg: '>=9e,<10a' - lcms2: '>=2.14,<3.0a0' - libgcc-ng: '>=12' - libtiff: '>=4.5.0,<4.6.0a0' - libwebp-base: '>=1.2.4,<2.0a0' - libxcb: '>=1.13,<1.14.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openjpeg: '>=2.5.0,<3.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - tk: '>=8.6.12,<8.7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py39h2320bf1_1.conda - hash: - md5: d2f79132b9c8e416058a4cd84ef27b3d - sha256: 77348588ae7cc8034b63e8a71b6695ba22761e1c531678e724cf06a12be3d1e2 - category: main - optional: false -- name: pkginfo - version: 1.9.6 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.9.6-pyhd8ed1ab_0.conda - hash: - md5: be1e9f1c65a1ed0f2ae9352fec99db64 - sha256: 7ea5a5af62a15376d9f4f9f3c134874d0b0710f39be719e849b7fa9ca8870502 - category: main - optional: false -- name: pkgutil-resolve-name - version: 1.3.10 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pkgutil-resolve-name-1.3.10-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 89e3c7cdde7d3aaa2aee933b604dd07f - sha256: 7d055ffc8a02bf781a89d069db3454b453605cdaff300b82cedcc7133283e47e - category: main - optional: false -- name: prometheus_client - version: 0.17.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.17.0-pyhd8ed1ab_0.conda - hash: - md5: 95c5be3c7cbd872509d16c216617fdab - sha256: eb11fd8b927d9c5ff9482cfbd6cd810a43a1351c44a288e9680542ea698a19a0 - category: main - optional: false -- name: psutil - version: 5.9.5 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.5-py39h72bdee0_0.conda - hash: - md5: 1d54d3a75c3192ab7655d9c3d16809f1 - sha256: 846894b31bf26061a9e83b03b10fe46f49fcf1ffc5fb1c7ed79a61706a57004b - category: main - optional: false -- name: ptyprocess - version: 0.7.0 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd3deb0d_0.tar.bz2 - hash: - md5: 359eeb6536da0e687af562ed265ec263 - sha256: fb31e006a25eb2e18f3440eb8d17be44c8ccfae559499199f73584566d0a444a - category: main - optional: false -- name: pycparser - version: '2.21' - manager: conda - platform: linux-64 - dependencies: - python: 2.7.*|>=3.4 - url: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 076becd9e05608f8dc72757d5f3a91ff - sha256: 74c63fd03f1f1ea2b54e8bc529fd1a600aaafb24027b738d0db87909ee3a33dc - category: main - optional: false -- name: pygments - version: 2.15.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/pygments-2.15.1-pyhd8ed1ab_0.conda - hash: - md5: d316679235612869eba305aa7d41d9bf - sha256: 1bddeb54863c77ed5613b535a3e06a3a16b55786301a5e28c9bf011656bda686 - category: main - optional: false -- name: pyjwt - version: 2.7.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyjwt-2.7.0-pyhd8ed1ab_0.conda - hash: - md5: 99e28be5a278e2319834d7dc99e7bfdd - sha256: f3a64306fa0f405f10f4108d7ff42043d6fd393f940f9e98e395a3756687fc98 - category: main - optional: false -- name: pyparsing - version: 3.0.9 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2 - hash: - md5: e8fbc1b54b25f4b08281467bc13b70cc - sha256: 4acc7151cef5920d130f2e0a7615559cce8bfb037aeecb14d4d359ae3d9bc51b - category: main - optional: false -- name: pyrsistent - version: 0.19.3 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/pyrsistent-0.19.3-py39h72bdee0_0.conda - hash: - md5: 659013ef00dcd1751bfd26d894f73857 - sha256: 8b8719429dc47dd15252fe65fc77a3ad81f25aa5f4db0e6b1d7cdc54722e6ef4 - category: main - optional: false -- name: pysocks - version: 1.7.1 - manager: conda - platform: linux-64 - dependencies: - __unix: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2 - hash: - md5: 2a7de29fb590ca14b5243c4c812c8025 - sha256: a42f826e958a8d22e65b3394f437af7332610e43ee313393d1cf143f0a2d274b - category: main - optional: false -- name: python-editor - version: 1.0.4 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/python-editor-1.0.4-py_0.tar.bz2 - hash: - md5: eaaf29a0644f9407f98a4665f45880c4 - sha256: a6db88da69a27451d2eba675c445bdefd2dbea52ea02a0a214d5fd4f0af31740 - category: main - optional: false -- name: python-installer - version: 0.7.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/python-installer-0.7.0-pyhd8ed1ab_0.conda - hash: - md5: 65dea78f903d686c8b0c2feaf0e15e1f - sha256: 822f95b7786cfa61a6519153117b21d93194890e02a884b9f66ee4275e4f1c0a - category: main - optional: false -- name: python-multipart - version: 0.0.6 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/python-multipart-0.0.6-pyhd8ed1ab_0.conda - hash: - md5: f4f642eeda814c1b65f46fbdf7e89096 - sha256: 2a9b8d02a6ec9862433cfc2741c4cbfe321e4ae3bab066f7ed84bc00effb73d7 - category: main - optional: false -- name: python-tzdata - version: '2023.3' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2023.3-pyhd8ed1ab_0.conda - hash: - md5: 2590495f608a63625e165915fb4e2e34 - sha256: 0108888507014fb24573c31e4deceb61c99e63d37776dddcadd7c89b2ecae0b6 - category: main - optional: false -- name: pytz - version: '2023.3' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pytz-2023.3-pyhd8ed1ab_0.conda - hash: - md5: d3076b483092a435832603243567bc31 - sha256: e4999484f21763ca4b8f92c95b22cb6d1edc1b61d0a2bb073ee2bd11f39401b9 - category: main - optional: false -- name: pywin32-on-windows - version: 0.1.0 - manager: conda - platform: linux-64 - dependencies: - __unix: '' - python: '>=2.7' - url: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - hash: - md5: 2807a0becd1d986fe1ef9b7f8135f215 - sha256: 6502696aaef571913b22a808b15c185bd8ea4aabb952685deb29e6a6765761cb - category: main - optional: false -- name: pyyaml - version: '6.0' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - yaml: '>=0.2.5,<0.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_5.tar.bz2 - hash: - md5: ef9db3c38ae7275f6b14491cfe61a248 - sha256: ae22172640fb16e7df679f55f16353be8ed6de434193a511214cf95eaa202e79 - category: main - optional: false -- name: readchar - version: 4.0.5 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/readchar-4.0.5-pyhd8ed1ab_0.conda - hash: - md5: 513334936060e80697bc21079e4f2829 - sha256: 0426cd7a524c31ab6d52b4d181848daea81d057e200a74200ea6e2896534bc18 - category: main - optional: false -- name: setuptools - version: 67.7.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/setuptools-67.7.2-pyhd8ed1ab_0.conda - hash: - md5: 3b68bc43ec6baa48f7354a446267eefe - sha256: 3ac44771fce01f19218bcdf3992e24984748048db69889a9df65abcc6a10e29b - category: main - optional: false -- name: shellingham - version: 1.5.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/shellingham-1.5.1-pyhd8ed1ab_0.conda - hash: - md5: 1de44299f48f522caa2e0074231614e1 - sha256: 3cb4a4a83b617fdfef9b92751634488db0b8961c80340be8068bf6d4f1d5ac25 - category: main - optional: false -- name: six - version: 1.16.0 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 - hash: - md5: e5f25f8dbc060e9a8d912e432202afc2 - sha256: a85c38227b446f42c5b90d9b642f2c0567880c15d72492d8da074a59c8f91dd6 - category: main - optional: false -- name: smmap - version: 3.0.5 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/smmap-3.0.5-pyh44b312d_0.tar.bz2 - hash: - md5: 3a8dc70789709aa315325d5df06fb7e4 - sha256: 091de70ee6bfe063e0c0f77336975d124fd1e3f49b9c58d97c0c7b3d287c0002 - category: main - optional: false -- name: sniffio - version: 1.3.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: dd6cbc539e74cb1f430efbd4575b9303 - sha256: a3fd30754c20ddb28b777db38345ea00d958f46701f0decd6291a81c0f4eee78 - category: main - optional: false -- name: soupsieve - version: 2.3.2.post1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 146f4541d643d48fc8a75cacf69f03ae - sha256: 72d80dda41c3902c2619e8ab49d4f5b2a894d13375e1f9ed16fc00074ddd2307 - category: main - optional: false -- name: sqlparse - version: 0.4.4 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.5' - url: https://conda.anaconda.org/conda-forge/noarch/sqlparse-0.4.4-pyhd8ed1ab_0.conda - hash: - md5: 2e2f31b3b1c866c29636377e14f8c4c6 - sha256: 7972c9b15dafa1885f3d4cd22dc4edea4cd969d12739fb71f8632f2c3350706a - category: main - optional: false -- name: tabulate - version: 0.9.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.9.0-pyhd8ed1ab_1.tar.bz2 - hash: - md5: 4759805cce2d914c38472f70bf4d8bcb - sha256: f6e4a0dd24ba060a4af69ca79d32361a6678e61d78c73eb5e357909b025b4620 - category: main - optional: false -- name: threadpoolctl - version: 3.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2 - hash: - md5: a2995ee828f65687ac5b1e71a2ab1e0c - sha256: c7a964811ba49c545236f7d6c2486b2ed493931660048a06fe94ecc851dd0b82 - category: main - optional: false -- name: tomli - version: 2.0.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 5844808ffab9ebdb694585b50ba02a96 - sha256: 4cd48aba7cd026d17e86886af48d0d2ebc67ed36f87f6534f4b67138f5a5a58f - category: main - optional: false -- name: tomlkit - version: 0.11.8 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tomlkit-0.11.8-pyha770c72_0.conda - hash: - md5: 75838e8556166263a82038b51d01d5f1 - sha256: 3002e87338a98ba501fbf53981f8267b2def2548265a3622d403d06747872ccd - category: main - optional: false -- name: traitlets - version: 5.9.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.9.0-pyhd8ed1ab_0.conda - hash: - md5: d0b4f5c87cd35ac3fb3d47b223263a64 - sha256: 343610bce6dbe8a5090500dd2e9d1706057960b3f3120ebfe0abb4a8ecbada4d - category: main - optional: false -- name: trove-classifiers - version: 2023.5.24 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2023.5.24-pyhd8ed1ab_0.conda - hash: - md5: 4580a4f27cad1c3b275f6f6ad310abae - sha256: 05e83cd3ac921143c7a25681928727bcc9b01bf8456c9615b72d64f050863503 - category: main - optional: false -- name: typing - version: 3.10.0.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/typing-3.10.0.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: e6573ac68718f17b9d4f5c8eda3190f2 - sha256: ec1cfe0b7dc55a22223562cad799e0b16d122dab611c9923b6068d27a784ba2f - category: main - optional: false -- name: typing_extensions - version: 4.5.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.5.0-pyha770c72_0.conda - hash: - md5: 43e7d9e50261fb11deb76e17d8431aac - sha256: f81eee64fcdfb379e27d01773b34041fbf7f9e86f33b157c9925d19e0a442452 - category: main - optional: false -- name: unicodedata2 - version: 15.0.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py39hb9d737c_0.tar.bz2 - hash: - md5: 230d65004135bf312504a1bbcb0c7a08 - sha256: 03c2cf05d1f4f2b01fc1e3ced22d5f331f2f233e335c4a4cd11a31fea1fccc0c - category: main - optional: false -- name: webencodings - version: 0.5.1 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-py_1.tar.bz2 - hash: - md5: 3563be4c5611a44210d9ba0c16113136 - sha256: 302f4f4bd1ad00c0be1426ecf6bb01db59cfd8aff3de0cf1596526dca1a6b70e - category: main - optional: false -- name: websocket-client - version: 1.5.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.5.2-pyhd8ed1ab_0.conda - hash: - md5: bfe7e7cd1476092f51efbcde15dfb110 - sha256: 85310b382c4220d7846fa8f046216fd722b88db07991f07bd7decdf2e5dc3446 - category: main - optional: false -- name: websockets - version: 11.0.3 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/websockets-11.0.3-py39hd1e30aa_0.conda - hash: - md5: 301d6f20f420d677580ce358f0b0f616 - sha256: e08348212736933af11d52e2098a73b30aec2c1167661d6f2cbfbc3636a0a76d - category: main - optional: false -- name: wheel - version: 0.40.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.40.0-pyhd8ed1ab_0.conda - hash: - md5: 49bb0d9e60ce1db25e151780331bb5f3 - sha256: 79b4d29b0c004014a2abd5fc2c9fcd35cc6256222b960c2a317a27c4b0d8884d - category: main - optional: false -- name: zipp - version: 3.15.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/zipp-3.15.0-pyhd8ed1ab_0.conda - hash: - md5: 13018819ca8f5b7cc675a8faf1f5fedf - sha256: 241de30545299be9bcea3addf8a2c22a3b3d4ba6730890e150ab690ac937a3d2 - category: main - optional: false -- name: anyio - version: 3.7.0 - manager: conda - platform: linux-64 - dependencies: - exceptiongroup: '' - idna: '>=2.8' - python: '>=3.7' - sniffio: '>=1.1' - typing_extensions: '' - url: https://conda.anaconda.org/conda-forge/noarch/anyio-3.7.0-pyhd8ed1ab_1.conda - hash: - md5: 2b35a85d654a47aac8f34c1bb6de7142 - sha256: 863c11a6a0e937977229b405a16f6d43fff543dfe5b1a66da9c42ec0cbdaaf33 - category: main - optional: false -- name: aws-c-s3 - version: 0.3.0 - manager: conda - platform: linux-64 - dependencies: - aws-c-auth: '>=0.6.27,<0.6.28.0a0' - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - libgcc-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.3.0-hcb5a9b2_2.conda - hash: - md5: e32991aa713aafc13ae31869d44e04ad - sha256: 7386d7108cf9796b2c826de0b7770bbe63dca456934bd4dad90686617f24a082 - category: main - optional: false -- name: backports.cached-property - version: 1.0.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - typing: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/backports.cached-property-1.0.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 0e1df3978dd516e20ef88c86d51e5432 - sha256: 1d86eafb5e9ed078f891e12b46692d786723652907dfb01b047c7da31f92b862 - category: main - optional: false -- name: backports.functools_lru_cache - version: 1.6.4 - manager: conda - platform: linux-64 - dependencies: - backports: '' - python: '>=3.6' - setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/backports.functools_lru_cache-1.6.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: c5b3edc62d6309088f4970b3eaaa65a6 - sha256: fdea00d4b79990f3fe938e2716bc32bd895eb5c44b6c75b8261db095a1b33c16 - category: main - optional: false -- name: beautifulsoup4 - version: 4.12.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - soupsieve: '>=1.2' - url: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.2-pyha770c72_0.conda - hash: - md5: a362ff7d976217f8fa78c0f1c4f59717 - sha256: 52d3e6bcd442537e22699cd227d8fdcfd54b708eeb8ee5b4c671a6a9b9cd74da - category: main - optional: false -- name: blas - version: '1.0' - manager: conda - platform: linux-64 - dependencies: - mkl: '' - url: https://conda.anaconda.org/conda-forge/linux-64/blas-1.0-mkl.tar.bz2 - hash: - md5: 349aef876b1d8c9dccae01de20d5b385 - sha256: a9a9125029a66905fc9e932dfd4f595be3a59a30db37fd7bf4a675a5c6151d62 - category: main - optional: false -- name: cffi - version: 1.15.1 - manager: conda - platform: linux-64 - dependencies: - libffi: '>=3.4,<4.0a0' - libgcc-ng: '>=12' - pycparser: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_3.conda - hash: - md5: 20080319ef73fbad74dcd6d62f2a3ffe - sha256: 485a8f65c58c26c7d48bfea20ed1d6f1493f3329dd2c9c0a888a1c2b7c2365c5 - category: main - optional: false -- name: deepdiff - version: 6.3.0 - manager: conda - platform: linux-64 - dependencies: - ordered-set: '>=4.1.0,<4.2.0' - orjson: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/deepdiff-6.3.0-pyhd8ed1ab_0.conda - hash: - md5: 67ce5e3eecbf1e5ff869269640ae6a53 - sha256: f949d860d532a07587bdb8466310394d8c1af4dd89bb65d65219161fcc16db10 - category: main - optional: false -- name: fonttools - version: 4.39.4 - manager: conda - platform: linux-64 - dependencies: - brotli: '' - libgcc-ng: '>=12' - munkres: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - unicodedata2: '>=14.0.0' - url: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.39.4-py39hd1e30aa_0.conda - hash: - md5: 80605b792f58cf5c78a5b7e20cef1e35 - sha256: a7e7256d309fa6561e28aedaaabafceb7b3c04a6261fa3fc9cd7aac4e89df823 - category: main - optional: false -- name: gitdb - version: 4.0.10 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.4' - smmap: '>=3.0.1,<4' - url: https://conda.anaconda.org/conda-forge/noarch/gitdb-4.0.10-pyhd8ed1ab_0.conda - hash: - md5: 3706d2f3d7cb5dae600c833345a76132 - sha256: 0003ab2b971913380633c711bf49a54dcf06e179986c725b0925854b58878377 - category: main - optional: false -- name: gunicorn - version: 20.1.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - setuptools: '>=3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/gunicorn-20.1.0-py39hf3d152e_3.tar.bz2 - hash: - md5: c60b7e9761ae09c6e8ca32dd4de7a047 - sha256: 04faa3b68c39665baa14b95e44bde6c33a2bc11eef744572213634cfb407e582 - category: main - optional: false -- name: h11 - version: 0.14.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3' - typing_extensions: '' - url: https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: b21ed0883505ba1910994f1df031a428 - sha256: 817d2c77d53afe3f3d9cf7f6eb8745cdd8ea76c7adaa9d7ced75c455a2c2c085 - category: main - optional: false -- name: html5lib - version: '1.1' - manager: conda - platform: linux-64 - dependencies: - python: '' - six: '>=1.9' - webencodings: '' - url: https://conda.anaconda.org/conda-forge/noarch/html5lib-1.1-pyh9f0ad1d_0.tar.bz2 - hash: - md5: b2355343d6315c892543200231d7154a - sha256: 9ad06446fe9847e86cb20d220bf11614afcd2cbe9f58096f08d5d4018877bee4 - category: main - optional: false -- name: importlib-metadata - version: 6.6.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.8' - zipp: '>=0.5' - url: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.6.0-pyha770c72_0.conda - hash: - md5: f91a5d5175fb7ff2a91952ec7da59cb9 - sha256: 33d49065756a73fbb92277c756fa00a41891408528eb90ae05ff3367a401ae6e - category: main - optional: false -- name: importlib_resources - version: 5.12.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - zipp: '>=3.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.12.0-pyhd8ed1ab_0.conda - hash: - md5: e5fd2260a231ee63b6969f4801082f2b - sha256: 091cca3e010f7a7353152f0abda2d68cfd83ddde80a15e974d9e18b2047e7be2 - category: main - optional: false -- name: jaraco.classes - version: 3.2.3 - manager: conda - platform: linux-64 - dependencies: - more-itertools: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.2.3-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 31e4a1506968d017229bdb64695013a1 - sha256: 6a81b67a1de8f761f66a4540bbd07cc27f9fbf2c7d67aa3732ebef379cf62874 - category: main - optional: false -- name: jinja2 - version: 3.1.2 - manager: conda - platform: linux-64 - dependencies: - markupsafe: '>=2.0' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2 - hash: - md5: c8490ed5c70966d232fdd389d0dbed37 - sha256: b045faba7130ab263db6a8fdc96b1a3de5fcf85c4a607c5f11a49e76851500b5 - category: main - optional: false -- name: joblib - version: 1.2.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.2.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 7583652522d71ad78ba536bba06940eb - sha256: 0c21351871df2c0a53168575597dd9c881e2a9fa4c42fe89a9bcd7fab37f462c - category: main - optional: false -- name: libblas - version: 3.9.0 - manager: conda - platform: linux-64 - dependencies: - mkl: '>=2022.1.0,<2023.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_mkl.tar.bz2 - hash: - md5: 85f61af03fd291dae33150ffe89dc09a - sha256: 24e656f13b402b6fceb88df386768445ab9beb657d451a8e5a88d4b3380cf7a4 - category: main - optional: false -- name: lightning-utilities - version: 0.8.0 - manager: conda - platform: linux-64 - dependencies: - packaging: '>=17.1' - python: '>=3.8' - typing_extensions: '' - url: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.8.0-pyhd8ed1ab_0.conda - hash: - md5: ad16f58b64d3b41f4cbb75040b06c9cc - sha256: 8c1fff22ab86c85768e65dc8c4f4664787476a21f4d934c4e0261a1fa7523f9c - category: main - optional: false -- name: markdown-it-py - version: 2.2.0 - manager: conda - platform: linux-64 - dependencies: - mdurl: '>=0.1,<1' - python: '>=3.7' - typing_extensions: '>=3.7.4' - url: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-2.2.0-pyhd8ed1ab_0.conda - hash: - md5: b2928a6c6d52d7e3562b4a59c3214e3a - sha256: 65ed439862c1851463f03a9bc5109992ce3e3e025e9a2d76d13ca19f576eee9f - category: main - optional: false -- name: omegaconf - version: 2.3.0 - manager: conda - platform: linux-64 - dependencies: - antlr-python-runtime: 4.9.* - python: '>=3.7' - pyyaml: '>=5.1.0' - typing_extensions: '' - url: https://conda.anaconda.org/conda-forge/noarch/omegaconf-2.3.0-pyhd8ed1ab_0.conda - hash: - md5: 23cc056834cab53849b91f78d6ee3ea0 - sha256: df806841be847e5287b22b6ae7f380874f81ea51f1b51ae14a570f3385c7b133 - category: main - optional: false -- name: pexpect - version: 4.8.0 - manager: conda - platform: linux-64 - dependencies: - ptyprocess: '>=0.5' - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.8.0-pyh1a96a4e_2.tar.bz2 - hash: - md5: 330448ce4403cc74990ac07c555942a1 - sha256: 07706c0417ead94f359ca7278f65452d3c396448777aba1da6a11fc351bdca9a - category: main - optional: false -- name: pip - version: 23.1.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - setuptools: '' - wheel: '' - url: https://conda.anaconda.org/conda-forge/noarch/pip-23.1.2-pyhd8ed1ab_0.conda - hash: - md5: 7288da0d36821349cf1126e8670292df - sha256: 4fe1f47f6eac5b2635a622b6f985640bf835843c1d8d7ccbbae0f7d27cadec92 - category: main - optional: false -- name: protobuf - version: 4.21.12 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - setuptools: '' - url: https://conda.anaconda.org/conda-forge/linux-64/protobuf-4.21.12-py39h227be39_0.conda - hash: - md5: 984b4ee0c3241d7ce715f8a731421073 - sha256: 22c02a0ae705170d169642056ce2cdf17a9c64b394dc24ed5133e9761bd4486f - category: main - optional: false -- name: pyproject_hooks - version: 1.0.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - tomli: '>=1.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.0.0-pyhd8ed1ab_0.conda - hash: - md5: 21de50391d584eb7f4441b9de1ad773f - sha256: 016340837fcfef57b351febcbe855eedf0c1f0ecfc910ed48c7fbd20535f9847 - category: main - optional: false -- name: python-dateutil - version: 2.8.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - six: '>=1.5' - url: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: dd999d1cc9f79e67dbb855c8924c7984 - sha256: 54d7785c7678166aa45adeaccfc1d2b8c3c799ca2dc05d4a82bb39b1968bd7da - category: main - optional: false -- name: tqdm - version: 4.65.0 - manager: conda - platform: linux-64 - dependencies: - colorama: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.65.0-pyhd8ed1ab_1.conda - hash: - md5: ed792aff3acb977d09c7013358097f83 - sha256: b35f185a678109940d34f68ac5781c3cbda9b118b8d9886b8f68ab5be6afd4fc - category: main - optional: false -- name: typing-extensions - version: 4.5.0 - manager: conda - platform: linux-64 - dependencies: - typing_extensions: 4.5.0 - url: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.5.0-hd8ed1ab_0.conda - hash: - md5: b3c594fde1a80a1fc3eb9cc4a5dfe392 - sha256: 6da5e15fa533620ae2e7aca9a7d16013eed3a73ac64c47d7c3bf3deec39b63b9 - category: main - optional: false -- name: werkzeug - version: 2.3.4 - manager: conda - platform: linux-64 - dependencies: - markupsafe: '>=2.1.1' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.4-pyhd8ed1ab_0.conda - hash: - md5: 23ddbe41ab0115bc0bfb75dcbf5de7cf - sha256: 2df1970270839b36e13a4ba7e4b393cfa95aa1d7438909aa8c3db14170ea207c - category: main - optional: false -- name: arrow - version: 1.2.3 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - python-dateutil: '>=2.7.0' - typing_extensions: '' - url: https://conda.anaconda.org/conda-forge/noarch/arrow-1.2.3-pyhd8ed1ab_0.tar.bz2 - hash: - md5: fd1967c76eda3a3dd9e8e6cb7a15a028 - sha256: a0434c2770cf5b0ab5a33913c0b202b1521bc13f755b762d16a86b110425cdc2 - category: main - optional: false -- name: aws-crt-cpp - version: 0.20.2 - manager: conda - platform: linux-64 - dependencies: - aws-c-auth: '>=0.6.27,<0.6.28.0a0' - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-event-stream: '>=0.2.20,<0.2.21.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-c-mqtt: '>=0.8.11,<0.8.12.0a0' - aws-c-s3: '>=0.3.0,<0.3.1.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.20.2-he0fdcb3_0.conda - hash: - md5: 3d9577a30f0e61331216b381925aa3e3 - sha256: 5e88754191d9e48c06169888cc5f85f3a681face0d8f2d69e1944556a11f8eb9 - category: main - optional: false -- name: bcrypt - version: 3.2.2 - manager: conda - platform: linux-64 - dependencies: - cffi: '>=1.1' - libgcc-ng: '>=12' - pip: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - six: '>=1.4.1' - url: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-3.2.2-py39hb9d737c_1.tar.bz2 - hash: - md5: 93f72f06a4b00ce36d16007c01e6d1aa - sha256: 8afe6676576da6e661ab7c0f2dfa52acc9e6f9bfc5ad2f1d57bf5131ddbdd975 - category: main - optional: false -- name: brotlipy - version: 0.7.0 - manager: conda - platform: linux-64 - dependencies: - cffi: '>=1.0.0' - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1005.tar.bz2 - hash: - md5: a639fdd9428d8b25f8326a3838d54045 - sha256: 293229afcd31e81626e5cfe0478be402b35d29b73aa421a49470645debda5019 - category: main - optional: false -- name: croniter - version: 1.3.15 - manager: conda - platform: linux-64 - dependencies: - python: '>=2.6' - python-dateutil: '' - url: https://conda.anaconda.org/conda-forge/noarch/croniter-1.3.15-pyhd8ed1ab_0.conda - hash: - md5: 50197abb95aa7024eb0eb58fe5a51b07 - sha256: f8f58f6a50a5f63a35ee3bf6805e6dee10fe910f17a339da038967118c12c64f - category: main - optional: false -- name: cryptography - version: 41.0.0 - manager: conda - platform: linux-64 - dependencies: - cffi: '>=1.12' - libgcc-ng: '>=12' - openssl: '>=3.1.1,<4.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/cryptography-41.0.0-py39hd4f0224_0.conda - hash: - md5: 705a4718d0f0f2dacf10416f7277f199 - sha256: a7725f6340162efacf5bb5b871e89723847af21dfe375a05122f5f7b05f2c3e4 - category: main - optional: false -- name: dateutils - version: 0.6.12 - manager: conda - platform: linux-64 - dependencies: - python: '>=3' - python-dateutil: '' - pytz: '' - url: https://conda.anaconda.org/conda-forge/noarch/dateutils-0.6.12-py_0.tar.bz2 - hash: - md5: acee371a07e9a38a7072e5a5f7054ead - sha256: fb554b32a8f880cafaff4e67c789965d97c41eb1a6cc9ab5a83c6b28b581d809 - category: main - optional: false -- name: flask - version: 2.3.2 - manager: conda - platform: linux-64 - dependencies: - blinker: '>=1.6.2' - click: '>=8.1.3' - importlib-metadata: '>=3.6.0' - itsdangerous: '>=2.1.2' - jinja2: '>=3.1.2' - python: '>=3.8' - werkzeug: '>=2.3.3' - url: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.2-pyhd8ed1ab_0.conda - hash: - md5: 816d75d4c0f2e41b5765d17498c57a2e - sha256: f93246be286f2d0f93e85c4f08f9ce48f3eed875a79225e2ea119e70c0237421 - category: main - optional: false -- name: gitpython - version: 3.1.31 - manager: conda - platform: linux-64 - dependencies: - gitdb: '>=4.0.1,<5' - python: '>=3.7' - typing_extensions: '>=3.7.4.3' - url: https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.31-pyhd8ed1ab_0.conda - hash: - md5: f6e6b482110246a81c3f03e81c68752d - sha256: 77c531def610089bc190508fcf304cf96c085c5fe977ab8f7d7c1641769592ac - category: main - optional: false -- name: importlib-resources - version: 5.12.0 - manager: conda - platform: linux-64 - dependencies: - importlib_resources: '>=5.12.0,<5.12.1.0a0' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-5.12.0-pyhd8ed1ab_0.conda - hash: - md5: 3544c818f0720c89eb16ae6940ab440b - sha256: 0675df2bf18e52d0ea2bc5e1009faac273f059361a0caf36c0e0edc7831098a9 - category: main - optional: false -- name: importlib_metadata - version: 6.6.0 - manager: conda - platform: linux-64 - dependencies: - importlib-metadata: '>=6.6.0,<6.6.1.0a0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-6.6.0-hd8ed1ab_0.conda - hash: - md5: 3cbc9615f10a3d471532b83e4250b971 - sha256: 5de35d3c019d8a36e0a0deeb04a62689837bd68234a0a73a3355b860b442eca4 - category: main - optional: false -- name: jsonschema - version: 4.17.3 - manager: conda - platform: linux-64 - dependencies: - attrs: '>=17.4.0' - importlib-metadata: '' - importlib_resources: '>=1.4.0' - pkgutil-resolve-name: '>=1.3.10' - pyrsistent: '!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0' - python: '>=3.7' - typing_extensions: '' - url: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.17.3-pyhd8ed1ab_0.conda - hash: - md5: 723268a468177cd44568eb8f794e0d80 - sha256: 4f68a23430d1afc5c9b41c46fbac0ade33c0bf57a293c646bfdd6dc65350eada - category: main - optional: false -- name: libcblas - version: 3.9.0 - manager: conda - platform: linux-64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_mkl.tar.bz2 - hash: - md5: 361bf757b95488de76c4f123805742d3 - sha256: 892ba10508f22310ccfe748df1fd3b6c7f20e7b6f6b79e69ed337863551c1bd8 - category: main - optional: false -- name: liblapack - version: 3.9.0 - manager: conda - platform: linux-64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_mkl.tar.bz2 - hash: - md5: a2f166748917d6d6e4707841ca1f519e - sha256: d6201f860b2d76ed59027e69c2bbad6d1cb211a215ec9705cc487cde488fa1fa - category: main - optional: false -- name: mako - version: 1.2.4 - manager: conda - platform: linux-64 - dependencies: - importlib-metadata: '' - markupsafe: '>=0.9.2' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/mako-1.2.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 0d072f0edc017b6318dbab701e053f94 - sha256: 559ed0d4c600d9827c1e9e0f2f3a50724bf2281b28a04e08f60de63f0da309a6 - category: main - optional: false -- name: markdown - version: 3.4.3 - manager: conda - platform: linux-64 - dependencies: - importlib-metadata: '>=4.4' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/markdown-3.4.3-pyhd8ed1ab_0.conda - hash: - md5: 89ed59ad509c05db6f5f2f573d499bfe - sha256: e32ac2c95112caa8cd81f0cbc710f4f4903180a115c7260f03b010d5a0aa771b - category: main - optional: false -- name: platformdirs - version: 3.5.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - typing-extensions: '>=4.5' - url: https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.5.1-pyhd8ed1ab_0.conda - hash: - md5: e2be672aece1f060adf7154f76531a35 - sha256: d7845c01a9ee5a224cc9242782befed7d12dc6aac1103650ec87917b20f3579e - category: main - optional: false -- name: poetry-core - version: 1.6.1 - manager: conda - platform: linux-64 - dependencies: - importlib-metadata: '>=1.7.0' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-core-1.6.1-pyhd8ed1ab_0.conda - hash: - md5: a6d1f61527c27fcc0165a6701a46b9f4 - sha256: 6f6a66476908a1c109e36c852d934eedceb07e0cbc44d3fcd87c6f39521b57be - category: main - optional: false -- name: pydantic - version: 1.10.8 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - typing-extensions: '>=4.2.0' - url: https://conda.anaconda.org/conda-forge/linux-64/pydantic-1.10.8-py39hd1e30aa_0.conda - hash: - md5: 0b010892a3a2515a50f5f166543faa60 - sha256: 0e914ff39c85afa91528900819f5fd9b39804db7518ee6297b59527e820e4090 - category: main - optional: false -- name: pynacl - version: 1.5.0 - manager: conda - platform: linux-64 - dependencies: - cffi: '>=1.4.1' - libgcc-ng: '>=12' - libsodium: '>=1.0.18,<1.0.19.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - six: '' - url: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.5.0-py39hb9d737c_2.tar.bz2 - hash: - md5: 1022b37795420d806b7b36b4e622ee9b - sha256: 937447b9122e4fe2525aba3568bd0635123e6293564b157ccb6753300553d84e - category: main - optional: false -- name: python-build - version: 0.10.0 - manager: conda - platform: linux-64 - dependencies: - colorama: '' - importlib-metadata: '>=0.22' - packaging: '>=19.0' - pyproject_hooks: '' - python: '>=3.7' - tomli: '>=1.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/python-build-0.10.0-pyhd8ed1ab_1.conda - hash: - md5: 0ab47ce574f6a8bcb9f2076436e7fedb - sha256: 4c2cd519c85aa8b8e584723ca5f452aa5941d18374470adebfe73bf30fd27573 - category: main - optional: false -- name: pytorch - version: 1.13.1 - manager: conda - platform: linux-64 - dependencies: - blas: '*' - mkl: '>=2018' - python: '>=3.9,<3.10.0a0' - pytorch-mutex: '1.0' - typing_extensions: '' - url: https://conda.anaconda.org/pytorch/linux-64/pytorch-1.13.1-py3.9_cpu_0.tar.bz2 - hash: - md5: 11bbe2a9ff1870da22a4318ff09309b9 - sha256: d19bfa100afa7b4ead06a973cbb4a15fae78857bcfd17d110d7b66912a762120 - category: main - optional: false -- name: rich - version: 13.4.1 - manager: conda - platform: linux-64 - dependencies: - markdown-it-py: '>=2.2.0,<3.0.0' - pygments: '>=2.13.0,<3.0.0' - python: '>=3.7.0' - typing_extensions: '>=4.0.0,<5.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/rich-13.4.1-pyhd8ed1ab_0.conda - hash: - md5: c3bcbe0d086f15e5918568d3865e4dbf - sha256: 312f2628e06a591096a851bf678833fe670ecb16e9b15517ce8e03d7c9d9e600 - category: main - optional: false -- name: sqlalchemy - version: 2.0.15 - manager: conda - platform: linux-64 - dependencies: - greenlet: '!=0.4.17' - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - typing-extensions: '>=4.2.0' - url: https://conda.anaconda.org/conda-forge/linux-64/sqlalchemy-2.0.15-py39hd1e30aa_0.conda - hash: - md5: 62cb91b2ed1b8c72553b98a1f338ab3e - sha256: 884900438716b6e49f380f3a99f999e23e80b15cc20285469eb3f9d18636ca62 - category: main - optional: false -- name: starlette - version: 0.22.0 - manager: conda - platform: linux-64 - dependencies: - anyio: <5,>=3.4.0 - python: '>=3.7' - typing_extensions: '>=3.10.0' - url: https://conda.anaconda.org/conda-forge/noarch/starlette-0.22.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 49d5cdcc16c691e4ad9355c81f004c3e - sha256: 1441dd55c037184b7d2c1e1dbf60beafb1f92fdc13cabf78a85e12825a55269b - category: main - optional: false -- name: uvicorn - version: 0.22.0 - manager: conda - platform: linux-64 - dependencies: - click: '>=7.0' - h11: '>=0.8' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/uvicorn-0.22.0-py39hf3d152e_0.conda - hash: - md5: ca0c46246d017fe1db8d049fa0de5c54 - sha256: 9b3b1d8765ad84539d1e9ac45e0806d019c9a56a146f4445c753b0a93640a353 - category: main - optional: false -- name: wcwidth - version: 0.2.6 - manager: conda - platform: linux-64 - dependencies: - backports.functools_lru_cache: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.6-pyhd8ed1ab_0.conda - hash: - md5: 078979d33523cb477bd1916ce41aacc9 - sha256: c1bd0ad7d854cae56977b7915ac2b78b652fa5f7ec1e9fc21e7fdb30cf4519b1 - category: main - optional: false -- name: alembic - version: 1.11.1 - manager: conda - platform: linux-64 - dependencies: - importlib-metadata: '' - importlib_resources: '' - mako: '' - python: '>=3.7' - sqlalchemy: '>=1.3.0' - typing-extensions: '>=4' - url: https://conda.anaconda.org/conda-forge/noarch/alembic-1.11.1-pyhd8ed1ab_0.conda - hash: - md5: 6a55e123397b42b79c48b31d1b7a91b8 - sha256: 065dd1b38ebe3a0d14f45549f63cce55125052057db565be153cdd73aa2a7c8d - category: main - optional: false -- name: aws-sdk-cpp - version: 1.10.57 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-event-stream: '>=0.2.20,<0.2.21.0a0' - aws-crt-cpp: '>=0.20.2,<0.20.3.0a0' - libcurl: '>=8.1.1,<9.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.10.57-h059227d_13.conda - hash: - md5: 16eac1f53808f188a44cb0dcb59b109b - sha256: a23ffd9b8920e16faf1a62769aa947933d921e7f56b5b8caeba844b3a2eb2a07 - category: main - optional: false -- name: blessed - version: 1.19.1 - manager: conda - platform: linux-64 - dependencies: - __unix: '' - python: '>=3.8' - six: '>=1.9.0' - wcwidth: '>=0.1.4' - url: https://conda.anaconda.org/conda-forge/noarch/blessed-1.19.1-pyhe4f9e05_2.tar.bz2 - hash: - md5: 65486376a55a80933e5dd95681ddd8b8 - sha256: 9d5b1f751adfe6d77fa8a088417a3aed716a1f727c0fd0230195246362b9d562 - category: main - optional: false -- name: fastapi - version: 0.88.0 - manager: conda - platform: linux-64 - dependencies: - pydantic: '>=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0' - python: '>=3.7' - starlette: 0.22.0.* - url: https://conda.anaconda.org/conda-forge/noarch/fastapi-0.88.0-pyhd8ed1ab_0.conda - hash: - md5: 0bd34a2b460d02e2fbf88ca5d9d3e55d - sha256: 660b356b8d4f3b99b6294c638637699003b0533c0ecab9389c56367b4bfe5c59 - category: main - optional: false -- name: numpy - version: 1.24.3 - manager: conda - platform: linux-64 - dependencies: - libblas: '>=3.9.0,<4.0a0' - libcblas: '>=3.9.0,<4.0a0' - libgcc-ng: '>=12' - liblapack: '>=3.9.0,<4.0a0' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.3-py39h6183b62_0.conda - hash: - md5: 8626d6d5169950ce4b99b082667773f7 - sha256: c8fac78b5292c279449e4ccba03661dd75f9d39b0f5d40b8bf55c3fcd89f64ce - category: main - optional: false -- name: oauthlib - version: 3.2.2 - manager: conda - platform: linux-64 - dependencies: - blinker: '' - cryptography: '' - pyjwt: '>=1.0.0' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/oauthlib-3.2.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 8f882b197fd9c4941a787926baea4868 - sha256: 0cfd5146a91d3974f4abfc2a45de890371d510a77238fe553e036ec8c031dc5b - category: main - optional: false -- name: paramiko - version: 3.2.0 - manager: conda - platform: linux-64 - dependencies: - bcrypt: '>=3.2' - cryptography: '>=3.3' - pynacl: '>=1.5' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/paramiko-3.2.0-pyhd8ed1ab_0.conda - hash: - md5: f212c7eb95e909df4795297f73690993 - sha256: e425a03e5e2ef2ec5a78711686c59cfceeeeec3a98165fbc7d186bd6a5cb78de - category: main - optional: false -- name: poetry-plugin-export - version: 1.4.0 - manager: conda - platform: linux-64 - dependencies: - poetry-core: '>=1.6.0,<2.0.0' - python: '>=3.7,<4.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-plugin-export-1.4.0-pyhd8ed1ab_0.conda - hash: - md5: 00893c7ea4f9f7620706e0aa94c01b6e - sha256: 54478b283b5967a85ee5da717f1512d7ec97eb07c7b52d1f2ad3cb080d56c0ac - category: main - optional: false -- name: prometheus_flask_exporter - version: 0.22.4 - manager: conda - platform: linux-64 - dependencies: - flask: '' - prometheus_client: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/prometheus_flask_exporter-0.22.4-pyhd8ed1ab_0.conda - hash: - md5: 43acea130cafd18740b73fa4c226c9f7 - sha256: be83619ef5964713cd298d0fb86eddd99b159e5fba3d0f91d624ce5d7c3890e0 - category: main - optional: false -- name: pyopenssl - version: 23.2.0 - manager: conda - platform: linux-64 - dependencies: - cryptography: '>=38.0.0,<42,!=40.0.0,!=40.0.1' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.2.0-pyhd8ed1ab_1.conda - hash: - md5: 34f7d568bf59d18e3fef8c405cbece21 - sha256: 4daea3dc896987cc1334956fccfc0ed738663a84ad0c1d3f576a7a7936091534 - category: main - optional: false -- name: secretstorage - version: 3.3.3 - manager: conda - platform: linux-64 - dependencies: - cryptography: '' - dbus: '' - jeepney: '>=0.6' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/secretstorage-3.3.3-py39hf3d152e_1.tar.bz2 - hash: - md5: cfb68a22e2d9108634a08a8a3b19d1b6 - sha256: db76e25d0c1cad3ca6339fd4d09c9cd03dcea7072b302c6eaa4123a358e98a78 - category: main - optional: false -- name: starsessions - version: 1.3.0 - manager: conda - platform: linux-64 - dependencies: - itsdangerous: '>=2.0.1' - python: '>=3.6.2' - starlette: '>=0' - url: https://conda.anaconda.org/conda-forge/noarch/starsessions-1.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 667d08040a85d7ea1c6d4af2290f96c4 - sha256: 4a500ac0a9fe56cee7958d6d0f6530272c43ee4c16c52600001decb39fe3cd59 - category: main - optional: false -- name: torchmetrics - version: 0.11.4 - manager: conda - platform: linux-64 - dependencies: - packaging: '' - python: '>=3.7' - pytorch: '>=1.8.1' - setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-0.11.4-pyhd8ed1ab_0.conda - hash: - md5: 480cb2b6d502003e937d9e4326bc398f - sha256: 3927eae14903c76679d5151af39680e7d42c35e0bdaa5041f577fc40f0619be8 - category: main - optional: false -- name: typer - version: 0.9.0 - manager: conda - platform: linux-64 - dependencies: - click: '>=7.1.1,<9' - colorama: '>=0.4.3,<0.5.0' - python: '>=3.6' - rich: '>=10.11.0,<14.0.0' - shellingham: '>=1.3.0,<2.0.0' - typing-extensions: '>=3.7.4.3' - url: https://conda.anaconda.org/conda-forge/noarch/typer-0.9.0-pyhd8ed1ab_0.conda - hash: - md5: 5030a13b2fe5e143d5956d4943d3018f - sha256: d395e1e92281abb13e043220ecf8ea973ada8d38a1e8c683df14f46541c64bd2 - category: main - optional: false -- name: virtualenv - version: 20.23.0 - manager: conda - platform: linux-64 - dependencies: - distlib: <1,>=0.3.6 - filelock: <4,>=3.11 - platformdirs: <4,>=3.2 - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.23.0-pyhd8ed1ab_0.conda - hash: - md5: a920e114c4c2ced2280e266da65ab5e6 - sha256: 13d667887ea08b6d1fe2eb09d2d737f9af7343735d3bfa5ffaa3f67eec8eaff7 - category: main - optional: false -- name: contourpy - version: 1.0.7 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.16' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.7-py39h4b4f3f3_0.conda - hash: - md5: c5387f3fb1f5b8b71e1c865fc55f4951 - sha256: 74a767b73686caf0bb1d1186cd62a54f01e03ad5432eaaf0a7babad7634c4067 - category: main - optional: false -- name: keyring - version: 23.13.1 - manager: conda - platform: linux-64 - dependencies: - importlib_metadata: '>=4.11.4' - jaraco.classes: '' - jeepney: '>=0.4.2' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - secretstorage: '>=3.2' - url: https://conda.anaconda.org/conda-forge/linux-64/keyring-23.13.1-py39hf3d152e_0.conda - hash: - md5: ae2df7a822f7671da22da24ead24cc0a - sha256: 107e6a5ba122dff162e9d34a87af1ffbb4dca1f7dd1547294646774ca9421524 - category: main - optional: false -- name: libarrow - version: 11.0.0 - manager: conda - platform: linux-64 - dependencies: - aws-crt-cpp: '>=0.20.2,<0.20.3.0a0' - aws-sdk-cpp: '>=1.10.57,<1.10.58.0a0' - bzip2: '>=1.0.8,<2.0a0' - c-ares: '>=1.19.1,<2.0a0' - gflags: '>=2.2.2,<2.3.0a0' - glog: '>=0.6.0,<0.7.0a0' - libabseil: '>=20230125.2,<20230126.0a0' - libbrotlicommon: '>=1.0.9,<1.1.0a0' - libbrotlidec: '>=1.0.9,<1.1.0a0' - libbrotlienc: '>=1.0.9,<1.1.0a0' - libevent: '>=2.1.12,<2.1.13.0a0' - libgcc-ng: '>=12' - libgoogle-cloud: '>=2.10.1,<2.10.2.0a0' - libgrpc: '>=1.54.2,<1.55.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - libthrift: '>=0.18.1,<0.18.2.0a0' - libutf8proc: '>=2.8.0,<3.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - openssl: '>=3.1.0,<4.0a0' - orc: '>=1.8.3,<1.8.4.0a0' - re2: '>=2023.3.2,<2023.3.3.0a0' - snappy: '>=1.1.10,<2.0a0' - ucx: '>=1.14.0,<1.15.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libarrow-11.0.0-h96638e8_21_cpu.conda - hash: - md5: 61af7ea51986d35d77d6d5f0bd5e2b3a - sha256: c1c518ea4715f248d4e00dcad1b3c70b0c6a3071fe6039104aba74377d120892 - category: main - optional: false -- name: pandas - version: 2.0.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.21.6,<2.0a0' - python: '>=3.9,<3.10.0a0' - python-dateutil: '>=2.8.1' - python-tzdata: '>=2022a' - python_abi: 3.9.* - pytz: '>=2020.1' - url: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.0.2-py39h40cae4c_0.conda - hash: - md5: de99b3f807c0b295a7df94623df0fb4c - sha256: 234e0474b55edc7159ddbdf03c99a9d9f53a0b32d9c505dc4be85698bab9bd6b - category: main - optional: false -- name: rapidfuzz - version: 2.15.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/rapidfuzz-2.15.1-py39h227be39_0.conda - hash: - md5: b8474b08c10da5d36a93cbbbdb41c53d - sha256: 2597a34923daaa41f151d3fc97e86b1f811c4c974b6f61c79bd0bff40849b110 - category: main - optional: false -- name: urllib3 - version: 1.26.15 - manager: conda - platform: linux-64 - dependencies: - brotlipy: '>=0.6.0' - certifi: '' - cryptography: '>=1.3.4' - idna: '>=2.0.0' - pyopenssl: '>=0.14' - pysocks: '>=1.5.6,<2.0,!=1.5.7' - python: <4.0 - url: https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.15-pyhd8ed1ab_0.conda - hash: - md5: 27db656619a55d727eaf5a6ece3d2fd6 - sha256: 213bdf6c3a5d721fa83b45d527d3ecd340f9547c0d6bbd0b8d9d746ec9a1fb4b - category: main - optional: false -- name: arrow-cpp - version: 11.0.0 - manager: conda - platform: linux-64 - dependencies: - libarrow: 11.0.0 - url: https://conda.anaconda.org/conda-forge/linux-64/arrow-cpp-11.0.0-ha770c72_21_cpu.conda - hash: - md5: 9b62e81822421b834ad453d3b4687fba - sha256: 06f99c9836045c0a595a050134bc1337eadea5dcb91efcf8cc784d023dda5557 - category: main - optional: false -- name: cleo - version: 2.0.1 - manager: conda - platform: linux-64 - dependencies: - crashtest: '>=0.4.1,<0.5.0' - python: '>=3.7,<4.0' - rapidfuzz: '>=2.2.0,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/cleo-2.0.1-pyhd8ed1ab_0.conda - hash: - md5: f1c5f2af6676cbe9206e191d1e70f661 - sha256: cf9bc4c9356ad8eb68512446eebc076386f2bfb8ca86626e8796621bc5a13082 - category: main - optional: false -- name: dulwich - version: 0.21.5 - manager: conda - platform: linux-64 - dependencies: - certifi: '' - cryptography: '>=1.3.4' - idna: '>=2.0.0' - libgcc-ng: '>=12' - pyopenssl: '>=0.14' - pysocks: '>=1.5.6,<2.0,!=1.5.7' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - urllib3: '' - url: https://conda.anaconda.org/conda-forge/linux-64/dulwich-0.21.5-py39hd1e30aa_0.conda - hash: - md5: 47e35d4a98545edc0d4828e273f6ad92 - sha256: a244714619d29fb09d9315c127fa78c347eaf032d13cb1ac8561b314468c6af8 - category: main - optional: false -- name: matplotlib-base - version: 3.7.1 - manager: conda - platform: linux-64 - dependencies: - certifi: '>=2020.06.20' - contourpy: '>=1.0.1' - cycler: '>=0.10' - fonttools: '>=4.22.0' - freetype: '>=2.12.1,<3.0a0' - importlib-resources: '>=3.2.0' - kiwisolver: '>=1.0.1' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.20.3,<2.0a0' - packaging: '>=20.0' - pillow: '>=6.2.0' - pyparsing: '>=2.3.1' - python: '>=3.9,<3.10.0a0' - python-dateutil: '>=2.7' - python_abi: 3.9.* - tk: '>=8.6.12,<8.7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.7.1-py39he190548_0.conda - hash: - md5: f2a931db797bb58bd335f4a857b4c898 - sha256: 34f8db992c68bee53fb6f0212707503ce197d13fadc231dbc37a99f31f72879a - category: main - optional: false -- name: requests - version: 2.31.0 - manager: conda - platform: linux-64 - dependencies: - certifi: '>=2017.4.17' - charset-normalizer: '>=2,<4' - idna: '>=2.5,<4' - python: '>=3.7' - urllib3: '>=1.21.1,<3' - url: https://conda.anaconda.org/conda-forge/noarch/requests-2.31.0-pyhd8ed1ab_0.conda - hash: - md5: a30144e4156cdbb236f99ebb49828f8b - sha256: 9f629d6fd3c8ac5f2a198639fe7af87c4db2ac9235279164bfe0fcb49d8c4bad - category: main - optional: false -- name: cachecontrol - version: 0.12.11 - manager: conda - platform: linux-64 - dependencies: - msgpack-python: '>=0.5.2' - python: '>=3.6' - requests: '' - url: https://conda.anaconda.org/conda-forge/noarch/cachecontrol-0.12.11-pyhd8ed1ab_1.conda - hash: - md5: e8f0410e0aa03342304357c5cc3bb75d - sha256: 466ce7c155be90a5c903052eba391759ae88eb65f2bb06b0cc1c9d09c4311800 - category: main - optional: false -- name: databricks-cli - version: 0.17.7 - manager: conda - platform: linux-64 - dependencies: - click: '>=7.0' - configparser: '>=0.3.5' - oauthlib: '>=3.1.0' - pyjwt: '>=1.7.0' - python: '>=3.6' - requests: '>=2.17.3' - six: '>=1.10.0' - tabulate: '>=0.7.7' - url: https://conda.anaconda.org/conda-forge/noarch/databricks-cli-0.17.7-pyhd8ed1ab_0.conda - hash: - md5: cb44b4e93848f13dce352c422285ac45 - sha256: 1c2ec6c6125bc1c1d100b75e1f5dfda09c56cd516157d2d5b01c1aa69fdd0dbd - category: main - optional: false -- name: docker-py - version: 6.1.0 - manager: conda - platform: linux-64 - dependencies: - packaging: '>=14.0' - paramiko: '>=2.4.3' - python: '>=3.7' - pywin32-on-windows: '' - requests: '>=2.26.0' - urllib3: '>=1.26.0' - websocket-client: '>=0.32.0' - url: https://conda.anaconda.org/conda-forge/noarch/docker-py-6.1.0-pyhd8ed1ab_0.conda - hash: - md5: 543336c6aa9516cfb29c51d5c162b177 - sha256: 5e01e15e20ee573c99b530633a0d5c71fd515e4ac6d2f5f5f57baece8b915cc3 - category: main - optional: false -- name: lightning-cloud - version: 0.5.36 - manager: conda - platform: linux-64 - dependencies: - click: '' - fastapi: '' - pyjwt: '' - python: '>=3.7' - python-multipart: '' - requests: '' - rich: '' - six: '' - urllib3: '' - uvicorn: '' - websocket-client: '' - url: https://conda.anaconda.org/conda-forge/noarch/lightning-cloud-0.5.36-pyhd8ed1ab_0.conda - hash: - md5: fd99cc369aa3c6c66493d4d278338af5 - sha256: 2047f4dcd4531f6cd2f87a80fef0d265f663e011931af746b2725f7d567ce016 - category: main - optional: false -- name: parquet-cpp - version: 1.5.1 - manager: conda - platform: linux-64 - dependencies: - arrow-cpp: '>=0.11.0' - url: https://conda.anaconda.org/conda-forge/noarch/parquet-cpp-1.5.1-2.tar.bz2 - hash: - md5: 79a5f78c42817594ae016a7896521a97 - sha256: 15e50657515b791734ba045da5135377404ca37c518b2066b9c6451c65cd732e - category: main - optional: false -- name: pooch - version: 1.7.0 - manager: conda - platform: linux-64 - dependencies: - packaging: '>=20.0' - platformdirs: '>=2.5.0' - python: '>=3.7' - requests: '>=2.19.0' - url: https://conda.anaconda.org/conda-forge/noarch/pooch-1.7.0-pyha770c72_3.conda - hash: - md5: 5936894aade8240c867d292aa0d980c6 - sha256: 64e4d633803df2e36fd141d9bf269568fbe179a313248e1dac4d364c02debdef - category: main - optional: false -- name: pytorch-lightning - version: 2.0.2 - manager: conda - platform: linux-64 - dependencies: - fsspec: '>2021.06.0' - lightning-utilities: '>=0.7.0' - numpy: '>=1.17.2' - packaging: '>=17.1' - python: '>=3.8' - pytorch: '>=1.11.0' - pyyaml: '>=5.4' - requests: '' - torchmetrics: '>=0.7.0' - tqdm: '>=4.57.0' - typing_extensions: '>=4.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.0.2-pyhd8ed1ab_0.conda - hash: - md5: abd4916f586ae33b56d1e2b44a7990aa - sha256: 9332cb927d6c587ed5b23628208ebb4479288244f4388bd4cb9745df115cca25 - category: main - optional: false -- name: querystring_parser - version: 1.2.4 - manager: conda - platform: linux-64 - dependencies: - python: '' - requests: '' - six: '' - url: https://conda.anaconda.org/conda-forge/noarch/querystring_parser-1.2.4-py_0.tar.bz2 - hash: - md5: 0ebdca9b753c2e082e5b5ad06aa76b41 - sha256: 06977a9af6d8605fb6068d8af6bb9c1cb565f8f5e15aa6cf0fb94109d4148b54 - category: main - optional: false -- name: requests-toolbelt - version: 1.0.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - requests: '>=2.0.1,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-1.0.0-pyhd8ed1ab_0.conda - hash: - md5: 99c98318c8646b08cc764f90ce98906e - sha256: 20eaefc5dba74ff6c31e537533dde59b5b20f69e74df49dff19d43be59785fa3 - category: main - optional: false -- name: torchvision - version: 0.14.1 - manager: conda - platform: linux-64 - dependencies: - ffmpeg: '>=4.2' - jpeg: '' - libpng: '' - numpy: '>=1.11' - pillow: '>=5.3.0,!=8.3.*' - python: '>=3.9,<3.10.0a0' - pytorch: 1.13.1 - pytorch-mutex: '1.0' - requests: '' - url: https://conda.anaconda.org/pytorch/linux-64/torchvision-0.14.1-py39_cpu.tar.bz2 - hash: - md5: a62af9b5578b0ecc017ea4831ef7ff68 - sha256: 0865e3887abb293ec780be2114fe4b347a040a2c813f99f44712d5bd78f99959 - category: main - optional: false -- name: cachecontrol-with-filecache - version: 0.12.11 - manager: conda - platform: linux-64 - dependencies: - cachecontrol: 0.12.11 - lockfile: '>=0.9' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cachecontrol-with-filecache-0.12.11-pyhd8ed1ab_1.conda - hash: - md5: 9df660456c0076d27b802448f7ede78f - sha256: 81c483fc92656873eb5a7ba657b208c34186556d942a9cebc1f7771e565b95b7 - category: main - optional: false -- name: pyarrow - version: 11.0.0 - manager: conda - platform: linux-64 - dependencies: - gflags: '>=2.2.2,<2.3.0a0' - libarrow: 11.0.0 - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.21.6,<2.0a0' - parquet-cpp: 1.5.1.* - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-11.0.0-py39he4327e9_21_cpu.conda - hash: - md5: fdac93d832c2247e83d6df03febd87b0 - sha256: 71b575fa595852130e997bfffd65a5742fa24cfa8b90e41396029a14058d56f4 - category: main - optional: false -- name: scipy - version: 1.10.1 - manager: conda - platform: linux-64 - dependencies: - libblas: '>=3.9.0,<4.0a0' - libcblas: '>=3.9.0,<4.0a0' - libgcc-ng: '>=12' - libgfortran-ng: '' - libgfortran5: '>=12.2.0' - liblapack: '>=3.9.0,<4.0a0' - libstdcxx-ng: '>=12' - numpy: '>=1.21.6,<2.0a0' - pooch: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.1-py39h6183b62_3.conda - hash: - md5: 84c4007675da392fdb99faeefda69552 - sha256: 68b5690a88e2872012fbe218dfb1f197e70aa83ecc3d049b5df5845d8c603406 - category: main - optional: false -- name: poetry - version: 1.5.1 - manager: conda - platform: linux-64 - dependencies: - __linux: '' - backports.cached-property: '>=1.0.2,<2.0.0' - cachecontrol-with-filecache: '>=0.12.9,<0.13.0' - cleo: '>=2.0.0,<3.0.0' - crashtest: '>=0.4.1,<0.5.0' - dulwich: '>=0.21.2,<0.22.0' - filelock: '>=3.8.0,<4.0.0' - html5lib: '>=1.0.0,<2.0.0' - importlib-metadata: '>=4.4' - jsonschema: '>=4.10.0,<5.0.0' - keyring: '>=23.9.0,<24.0.0' - lockfile: '>=0.12.2,<0.13.0' - packaging: '>=20.4' - pexpect: '>=4.7.0,<5.0.0' - pkginfo: '>=1.9.4,<2.0' - platformdirs: '>=3.0.0,<4.0.0' - poetry-core: 1.6.1.* - poetry-plugin-export: '>=1.4.0,<2.0.0' - pyproject_hooks: '>=1.0.0,<2.0.0' - python: '>=3.7.0,<4.0.0' - python-build: '>=0.10.0,<0.11.0' - python-installer: '>=0.7.0,<0.8.0' - requests: '>=2.18,<3.0' - requests-toolbelt: '>=0.9.1,<2' - shellingham: '>=1.5.0,<2.0.0' - tomli: '>=2.0.1,<3.0.0' - tomlkit: '>=0.11.4,<1.0.0' - trove-classifiers: '>=2022.5.19' - urllib3: '>=1.26.0,<2.0.0' - virtualenv: '>=20.22.0,<21.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-1.5.1-linux_pyhd8ed1ab_0.conda - hash: - md5: 7aae42823198731808dd8e7a9745547f - sha256: 754fb55e83fe06366f4532fbb25344bc9c7d46ac71d2a1c35209e818f7fa9d10 - category: main - optional: false -- name: scikit-learn - version: 1.2.2 - manager: conda - platform: linux-64 - dependencies: - _openmp_mutex: '>=4.5' - joblib: '>=1.1.1' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.21.6,<2.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - scipy: '' - threadpoolctl: '>=2.0.0' - url: https://conda.anaconda.org/conda-forge/linux-64/scikit-learn-1.2.2-py39hc236052_2.conda - hash: - md5: 43894d2ed4e587557e536d3e4ef93f73 - sha256: 857fcc0505405dcbaedeea8e655383f54126102a919f9a32d55cd77582ce15cb - category: main - optional: false -- name: inquirer - version: 3.1.3 - manager: conda - platform: linux-64 - dependencies: - blessed: '>=1.19.0' - poetry: '' - python: '>=3.7' - python-editor: '>=1.0.4' - readchar: '>=2.0.1' - url: https://conda.anaconda.org/conda-forge/noarch/inquirer-3.1.3-pyhd8ed1ab_0.conda - hash: - md5: 0d8bc31361e09dc50555465284e10880 - sha256: da912877ac6e0795490834c96167e93a1eda89290ef8de63502740ef738d4435 - category: main - optional: false -- name: mlflow - version: 2.3.2 - manager: conda - platform: linux-64 - dependencies: - alembic: <2,!=1.10 - click: '>=7.0,<9' - cloudpickle: <3 - databricks-cli: '>=0.8.7,<1' - docker-py: '>=4.0.0,<7' - entrypoints: <1 - flask: <3 - gitpython: '>=2.1.0,<4' - gunicorn: <21 - importlib-metadata: <7,>=3.7.0,!=4.7.0 - jinja2: <4,>=2.11 - markdown: <4,>=3.3 - matplotlib-base: <4 - numpy: <2 - openssl: '' - packaging: <24 - pandas: <3 - prometheus_flask_exporter: <1 - protobuf: '>=3.12.0,<5' - pyarrow: <12,>=4.0.0 - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - pytz: <2024 - pyyaml: '>=5.1,<7' - querystring_parser: <2 - requests: '>=2.17.3,<3' - scikit-learn: <2 - scipy: <2 - sqlalchemy: '>=1.4.0,<3' - sqlparse: '>=0.4.0,<1' - url: https://conda.anaconda.org/conda-forge/linux-64/mlflow-2.3.2-py39ha39b057_1.conda - hash: - md5: 86b04308468de463d9ba8aa203385e09 - sha256: e8a6ab7ac9c91b5303cbe1ea14d124bd0333fc7c50f89f41c272d51636812878 - category: main - optional: false -- name: lightning - version: 2.0.0 - manager: conda - platform: linux-64 - dependencies: - arrow: <3.0,>=1.2.0 - beautifulsoup4: <6.0,>=4.8.0 - click: <10.0 - croniter: <1.4.0,>=1.3.0 - dateutils: <2.0 - deepdiff: <8.0,>=5.7.0 - fastapi: <0.89.0 - fsspec: <2024.0,>=2022.5.0 - inquirer: <5.0,>=2.10.0 - jinja2: <5.0 - lightning-cloud: '>=0.5.31' - lightning-utilities: <2.0,>=0.7.0 - numpy: <3.0,>=1.17.2 - packaging: '' - psutil: <7.0 - pydantic: <3.0 - python: '>=3.8' - python-multipart: '' - pytorch: <4.0,>=1.11.0 - pytorch-lightning: '' - pyyaml: <8.0 - requests: <4.0 - rich: <15.0,>=12.3.0 - starlette: <2.0 - starsessions: <2.0,>=1.2.1 - torchmetrics: <2.0,>=0.7.0 - tqdm: <6.0,>=4.57.0 - traitlets: <7.0,>=5.3.0 - typing-extensions: <6.0,>=4.0.0 - urllib3: <3.0 - uvicorn: <2.0 - websocket-client: <3.0 - websockets: <12.0 - url: https://conda.anaconda.org/conda-forge/noarch/lightning-2.0.0-pyhd8ed1ab_0.conda - hash: - md5: 5c38b552a8b1853c39738d9a9f090ffc - sha256: 4b6bf3a963ea91f81de4947ad8a0686bb8cf868f63a5a125088938fc9551ace1 - category: main - optional: false -- name: ca-certificates - version: 2023.5.7 - manager: conda - platform: linux-aarch64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-aarch64/ca-certificates-2023.5.7-hcefe29a_0.conda - hash: - md5: 331e624442b88d96bc05a7f2d38c61a4 - sha256: c03bcf46e3f0264723678fc446f9f5fe30e9d9bf6f58166e5dedd19905e3ef08 - category: main - optional: false -- name: ld_impl_linux-aarch64 - version: '2.40' - manager: conda - platform: linux-aarch64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.40-h2d8c526_0.conda - hash: - md5: 16246d69e945d0b1969a6099e7c5d457 - sha256: 1ba06e8645094b340b4aee23603a6abb1b0383788180e65f3de34e655c5f577c - category: main - optional: false -- name: libgfortran5 - version: 12.2.0 - manager: conda - platform: linux-aarch64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran5-12.2.0-hf695500_19.tar.bz2 - hash: - md5: bc890809e1f807b51bf04dfbee70ddf5 - sha256: e0496081c3a26c578abd0e292317c80159ebfbd5bb1ecca446894b9adf39abd7 - category: main - optional: false -- name: libgomp - version: 12.2.0 - manager: conda - platform: linux-aarch64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-12.2.0-h607ecd0_19.tar.bz2 - hash: - md5: 65b9cb876525dcb2e74a90cf02c6762a - sha256: d802eaceaf6f77fb8cb2e990aacf9b3eaa83361b16369a760fc1585841d7885c - category: main - optional: false -- name: libstdcxx-ng - version: 12.2.0 - manager: conda - platform: linux-aarch64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-ng-12.2.0-hc13a102_19.tar.bz2 - hash: - md5: 981741cd4321edd5c504b48f74fe91f2 - sha256: db906f0ad19acc6aefcd5409a7a72fea76302f72013dce7593467ae07dbf54f3 - category: main - optional: false -- name: python_abi - version: '3.9' - manager: conda - platform: linux-aarch64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-aarch64/python_abi-3.9-3_cp39.conda - hash: - md5: b6f330b045cf3425945d536a6b5cd240 - sha256: f9ea2e91bd871899b5c2682e6ef78523b68769a62ea86af86894cfc5d37d1f0a - category: main - optional: false -- name: pytorch-mutex - version: '1.0' - manager: conda - platform: linux-aarch64 - dependencies: {} - url: https://conda.anaconda.org/pytorch/noarch/pytorch-mutex-1.0-cpu.tar.bz2 - hash: - md5: 49565ed726991fd28d08a39885caa88d - sha256: d48c964188ca49660d750cffd73698d217cf94e694cd51987f9f186425435e76 - category: main - optional: false -- name: tzdata - version: 2023c - manager: conda - platform: linux-aarch64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2023c-h71feb2d_0.conda - hash: - md5: 939e3e74d8be4dac89ce83b20de2492a - sha256: 0449138224adfa125b220154408419ec37c06b0b49f63c5954724325903ecf55 - category: main - optional: false -- name: _openmp_mutex - version: '4.5' - manager: conda - platform: linux-aarch64 - dependencies: - libgomp: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-2_gnu.tar.bz2 - hash: - md5: 6168d71addc746e8f2b8d57dfd2edcea - sha256: 3702bef2f0a4d38bd8288bbe54aace623602a1343c2cfbefd3fa188e015bebf0 - category: main - optional: false -- name: cpuonly - version: '2.0' - manager: conda - platform: linux-aarch64 - dependencies: - pytorch-mutex: '1.0' - url: https://conda.anaconda.org/pytorch/noarch/cpuonly-2.0-0.tar.bz2 - hash: - md5: 1cf3a59ef90a4078c253e3b02c272065 - sha256: f9107aca2a9d23a032634644df5cdb8d0185337891593ce540adc480810ab539 - category: main - optional: false -- name: libgfortran-ng - version: 12.2.0 - manager: conda - platform: linux-aarch64 - dependencies: - libgfortran5: 12.2.0 - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran-ng-12.2.0-he9431aa_19.tar.bz2 - hash: - md5: b5b34211bbf681bd3e7a5a4d80cce77b - sha256: 3ac162edf354bfa46076f52f3bff3a8ac10e626ebb9ed5e01aad954ebd386829 - category: main - optional: false -- name: libgcc-ng - version: 12.2.0 - manager: conda - platform: linux-aarch64 - dependencies: - _openmp_mutex: '>=4.5' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-12.2.0-h607ecd0_19.tar.bz2 - hash: - md5: 8456a29b6d9fc3123ccb9a966b6b2c49 - sha256: 0dd30553f6f38b011c9c81471a50f85e98a79e4dd672fdc1fc97904b54b5419b - category: main - optional: false -- name: aws-c-common - version: 0.8.19 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/aws-c-common-0.8.19-h31becfc_0.conda - hash: - md5: a9cad48fddaa38e01c8c7413aa8d4db3 - sha256: ad689a8d577a6e3de53ceb35ddb9ff0df27c405ad2d3fd30f4ad39ada6cac656 - category: main - optional: false -- name: bzip2 - version: 1.0.8 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=9.3.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-hf897c2e_4.tar.bz2 - hash: - md5: 2d787570a729e273a4e75775ddf3348a - sha256: 3aeb6ab92aa0351722497b2d2a735dc20921cf6c60d9196c04b7a2b9ece198d2 - category: main - optional: false -- name: c-ares - version: 1.19.1 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/c-ares-1.19.1-h31becfc_0.conda - hash: - md5: 92eb241dd6b9d9b782a299fdb95bc699 - sha256: 530f73a1facfb202dd3596176293f9e12f41c1194adc1c0fab84b71df4f66772 - category: main - optional: false -- name: gettext - version: 0.21.1 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/gettext-0.21.1-ha18d298_0.tar.bz2 - hash: - md5: b109f1a4d22966793d61fd7f75b744c3 - sha256: b1d8ee80b7577661a8cebdfd21dd1676ba73b676d106c458d4ecdbe4a6d9c2eb - category: main - optional: false -- name: gflags - version: 2.2.2 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=7.5.0' - libstdcxx-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/gflags-2.2.2-h54f1f3f_1004.tar.bz2 - hash: - md5: f286d3464cc8d467c92e4f17990c98c1 - sha256: c72f18b94048df5525d8ae73a9efb8d830048b70328d63738d91d3ea54e55b91 - category: main - optional: false -- name: keyutils - version: 1.6.1 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/keyutils-1.6.1-h4e544f5_0.tar.bz2 - hash: - md5: 1f24853e59c68892452ef94ddd8afd4b - sha256: 6d4233d97a9b38acbb26e1268bcf8c10a8e79c2aed7e5a385ec3769967e3e65b - category: main - optional: false -- name: lerc - version: 4.0.0 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/lerc-4.0.0-h4de3ea5_0.tar.bz2 - hash: - md5: 1a0ffc65e03ce81559dbcb0695ad1476 - sha256: 2d09ef9b7796d83364957e420b41c32d94e628c3f0520b61c332518a7b5cd586 - category: main - optional: false -- name: libabseil - version: '20230125.2' - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libabseil-20230125.2-cxx17_h2f0025b_2.conda - hash: - md5: f8b4ffd9abfc6f0d69232c0d407c6fbd - sha256: 43f182be67662a2505d831779ae9733cba576e7e6b51b2416c58bd20defe5812 - category: main - optional: false -- name: libbrotlicommon - version: 1.0.9 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libbrotlicommon-1.0.9-h4e544f5_8.tar.bz2 - hash: - md5: 3cedc3935cfaa2a5303daa25fb12cb1d - sha256: 8eedfeb9097042f1005d4764bda83de0eda907e55d77408654367760ad46053d - category: main - optional: false -- name: libcrc32c - version: 1.1.2 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=9.4.0' - libstdcxx-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libcrc32c-1.1.2-h01db608_0.tar.bz2 - hash: - md5: 268ee639c17ada0002fb04dd21816cc2 - sha256: b8b8c57a87da86b3ea24280fd6aa8efaf92f4e684b606bf2db5d3cb06ffbe2ea - category: main - optional: false -- name: libdeflate - version: '1.18' - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libdeflate-1.18-hb4cce97_0.conda - hash: - md5: e0d520842c0ae66b560cc65f9b96f658 - sha256: cec51f6445d9e2e5ae11f81ae8d6e13be4cb0d30bb3bca1ce37797e48878245b - category: main - optional: false -- name: libev - version: '4.33' - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libev-4.33-h516909a_1.tar.bz2 - hash: - md5: 9eac5901791494108c9b9ab85ca8aa93 - sha256: b9e8bcd26f0b0ded4c43232d55048bab910a4268197757f2368458759d9f3ef9 - category: main - optional: false -- name: libexpat - version: 2.5.0 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.5.0-hd600fc2_1.conda - hash: - md5: 6cd3d0a28437b3845c260f9d71d434d7 - sha256: b4651d196d5adb0637c678d874160a318078d963caec264bda7ac07ff6a1cbc7 - category: main - optional: false -- name: libffi - version: 3.4.2 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.4.2-h3557bc0_5.tar.bz2 - hash: - md5: dddd85f4d52121fab0a8b099c5e06501 - sha256: 7e9258a102480757fe3faeb225a3ca04dffd10fecd2a958c65cdb4cdf75f2c3c - category: main - optional: false -- name: libiconv - version: '1.17' - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libiconv-1.17-h9cdd2b7_0.tar.bz2 - hash: - md5: efc27cfbc82a027f65c02c661832ecfc - sha256: e3c95d751ea71a638f781e82b1498e914e1d11536ea52fc354fecb2e65d3a7d3 - category: main - optional: false -- name: libjpeg-turbo - version: 2.1.5.1 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libjpeg-turbo-2.1.5.1-hb4cce97_0.conda - hash: - md5: 89a30f83837239a008593afb78d210f2 - sha256: c25e707b70c800f3b9ce2c70fa02cdf3219bc5d775281a4a618208db77d657ea - category: main - optional: false -- name: libnsl - version: 2.0.0 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libnsl-2.0.0-hf897c2e_0.tar.bz2 - hash: - md5: 36fdbc05c9d9145ece86f5a63c3f352e - sha256: 182dbc318b7ab3ab10bc5a9d3ca161b143ae8db6df8aa11b65140009e322e642 - category: main - optional: false -- name: libnuma - version: 2.0.16 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libnuma-2.0.16-hb4cce97_1.conda - hash: - md5: a63d3c8b8384e64056a8c4bfd80edbdd - sha256: 2f26e79ffadcf679033ef13b9dc72292c65e0511aa045059c2a4f4c8c2c74a32 - category: main - optional: false -- name: libopenblas - version: 0.3.21 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libgfortran-ng: '' - libgfortran5: '>=10.4.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libopenblas-0.3.21-pthreads_h6cb6f83_3.tar.bz2 - hash: - md5: bc66302748a788c3bce59999ed6d737d - sha256: 78a93de015d389597d9bdd470ffcfa3901d4b39b85d6516f242ff71d18dc6607 - category: main - optional: false -- name: libsodium - version: 1.0.18 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libsodium-1.0.18-hb9de7d4_1.tar.bz2 - hash: - md5: d09ab3c60eebb6f14eb4d07e172775cc - sha256: 9ee442d889242c633bc3ce3f50ae89e6d8ebf12e04d943c371c0a56913fa069b - category: main - optional: false -- name: libutf8proc - version: 2.8.0 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libutf8proc-2.8.0-h4e544f5_0.tar.bz2 - hash: - md5: bf0defbd8ac06270fb5ec05c85fb3c96 - sha256: c1956b64ad9613c66cf87398f5e2c36d071034a93892da7e8cc22e75cface878 - category: main - optional: false -- name: libuuid - version: 2.38.1 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.38.1-hb4cce97_0.conda - hash: - md5: 000e30b09db0b7c775b21695dff30969 - sha256: 616277b0c5f7616c2cdf36f6c316ea3f9aa5bb35f2d4476a349ab58b9b91675f - category: main - optional: false -- name: libwebp-base - version: 1.3.0 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libwebp-base-1.3.0-hb4cce97_0.conda - hash: - md5: 53670eaee6d77d9fe60a84f7fd226a4c - sha256: 2c80492703b8d8212f7a533ab35daf690a6c2e4ce57bacdfcf15571f5dbcfca4 - category: main - optional: false -- name: libzlib - version: 1.2.13 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.2.13-h4e544f5_4.tar.bz2 - hash: - md5: 88596b6277fe6d39f046983aae6044db - sha256: 9803ac96dbdbc27df9c149e06d4436b778c468bd0e6e023fbcb49a6fe9c404b4 - category: main - optional: false -- name: lz4-c - version: 1.9.4 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/lz4-c-1.9.4-hd600fc2_0.conda - hash: - md5: 500145a83ed07ce79c8cef24252f366b - sha256: 076870eb72411f41c46598c7582a2f3f42ba94c526a2d60a0c8f70a0a7a64429 - category: main - optional: false -- name: ncurses - version: '6.3' - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.3-headf329_1.tar.bz2 - hash: - md5: 486b68148e121bc8bbadc3cefae4c04f - sha256: d410d840cb39175d7edc388690b93b2e3d7613c2e2f5c64b96582dc8b55b2319 - category: main - optional: false -- name: openssl - version: 3.1.1 - manager: conda - platform: linux-aarch64 - dependencies: - ca-certificates: '' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.1.1-h31becfc_1.conda - hash: - md5: a8e811c3390d93e5db0cef68e52f349f - sha256: b4d819b0a662c955499c2f579e19ca5d45187dd11ec0f7717de9b65092c0b831 - category: main - optional: false -- name: pthread-stubs - version: '0.4' - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/pthread-stubs-0.4-hb9de7d4_1001.tar.bz2 - hash: - md5: d0183ec6ce0b5aaa3486df25fa5f0ded - sha256: f1d7ff5e06cc515ec82010537813c796369f8e9dde46ce3f4fa1a9f70bc7db7d - category: main - optional: false -- name: re2 - version: 2023.03.02 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/re2-2023.03.02-hdc0ed08_0.conda - hash: - md5: de7bc1306fbb9e94b30385a30dae955a - sha256: 99934fe2d1c811482f95130de0db21b41ea13757cb9462921baff510b3b64a7c - category: main - optional: false -- name: sleef - version: 3.5.1 - manager: conda - platform: linux-aarch64 - dependencies: - _openmp_mutex: '>=4.5' - libgcc-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/sleef-3.5.1-h4f30969_2.tar.bz2 - hash: - md5: 5e648d917d1affccfddbc0de0f807caa - sha256: c38431df3701927d4abb3bdcd27e21b200af68621b14cfdcf2b9324c31d2dcfa - category: main - optional: false -- name: snappy - version: 1.1.10 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/snappy-1.1.10-he8610fa_0.conda - hash: - md5: 11c25e55894bb8207a81a87e6a32b6e7 - sha256: 5a7d6cf781cbaaea4effce4d8f2677cd6173af5e8b744912e1283a704eb91946 - category: main - optional: false -- name: xorg-libxau - version: 1.0.11 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxau-1.0.11-h31becfc_0.conda - hash: - md5: 13de34f69cb73165dbe08c1e9148bedb - sha256: c00a8909e783ba7f4ada7256f0385ae46fc21322f4090fa396c80b4481abd5f4 - category: main - optional: false -- name: xorg-libxdmcp - version: 1.1.3 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=9.3.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxdmcp-1.1.3-h3557bc0_0.tar.bz2 - hash: - md5: a6c9016ae1ca5c47a3603ed4cd65fedd - sha256: 2aad9a0b57796170b8fb40317598fd79cfc7ae27fa7fb68c417d815e44499d59 - category: main - optional: false -- name: xz - version: 5.2.6 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/xz-5.2.6-h9cdd2b7_0.tar.bz2 - hash: - md5: 83baad393a31d59c20b63ba4da6592df - sha256: 93f58a7b393adf41fa007ac8c55978765e957e90cd31877ece1e5a343cb98220 - category: main - optional: false -- name: yaml - version: 0.2.5 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-hf897c2e_2.tar.bz2 - hash: - md5: b853307650cb226731f653aa623936a4 - sha256: 8bc601d6dbe249eba44b3c456765265cd8f42ef1e778f8df9b0c9c88b8558d7e - category: main - optional: false -- name: aws-c-cal - version: 0.5.26 - manager: conda - platform: linux-aarch64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/aws-c-cal-0.5.26-h617785b_1.conda - hash: - md5: e2072802ff092ae13be300c893797ed5 - sha256: 0f51239ad0163d332fbd3f4fba42b5e4b4540d09c7b011f19eea30aa06b1fa08 - category: main - optional: false -- name: aws-c-compression - version: 0.2.16 - manager: conda - platform: linux-aarch64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/aws-c-compression-0.2.16-h3c91cf8_7.conda - hash: - md5: e21914d653a8d3cd712a44d2a61e3d90 - sha256: 4a2a8dc42ec2210d48ad4ada12b52bf50f7ac18d434208049e0e09f23ebf353d - category: main - optional: false -- name: aws-c-sdkutils - version: 0.1.9 - manager: conda - platform: linux-aarch64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/aws-c-sdkutils-0.1.9-h3c91cf8_2.conda - hash: - md5: 90a9657e27b7917611099a75f0284d5f - sha256: 4a09e05c9b659b72aef6208a2314f8f1318269b42655665441b7d7f8c34244b9 - category: main - optional: false -- name: aws-checksums - version: 0.1.14 - manager: conda - platform: linux-aarch64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/aws-checksums-0.1.14-h3c91cf8_7.conda - hash: - md5: 16e5338f7d38ffe13c8d9782e8ef0d01 - sha256: 88f1fad6a1b83ed1ab2721c3ab3e43925db742f0d494e2e8144dceefe1156f35 - category: main - optional: false -- name: expat - version: 2.5.0 - manager: conda - platform: linux-aarch64 - dependencies: - libexpat: 2.5.0 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/expat-2.5.0-hd600fc2_1.conda - hash: - md5: 6dfca4be3e0080934b1105d009747e98 - sha256: a00bae815836f8fc73e47701c25998be81284dcefab28e002efde68e0bb7eee0 - category: main - optional: false -- name: glog - version: 0.6.0 - manager: conda - platform: linux-aarch64 - dependencies: - gflags: '>=2.2.2,<2.3.0a0' - libgcc-ng: '>=10.3.0' - libstdcxx-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/glog-0.6.0-h8ab10f1_0.tar.bz2 - hash: - md5: 9dc55595db8d7947bb253f63bbcec8ca - sha256: e41461399e2a5d139935caaa95f8264f700b058ea077708f8b0417a79ced215c - category: main - optional: false -- name: libblas - version: 3.9.0 - manager: conda - platform: linux-aarch64 - dependencies: - libopenblas: '>=0.3.21,<1.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libblas-3.9.0-16_linuxaarch64_openblas.tar.bz2 - hash: - md5: 188f02883567d5b7f96c7aa12e7007c9 - sha256: 6fdf73da8b717f207979f77660646ca2d7e17671482435f281b676ac27eb288e - category: main - optional: false -- name: libbrotlidec - version: 1.0.9 - manager: conda - platform: linux-aarch64 - dependencies: - libbrotlicommon: 1.0.9 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libbrotlidec-1.0.9-h4e544f5_8.tar.bz2 - hash: - md5: 319956380b383ec9f6a46d585599c028 - sha256: 5c735e238743bda58f44fcb5bef564dc5262c0ea0219ccdb8cbcb168c98a58e0 - category: main - optional: false -- name: libbrotlienc - version: 1.0.9 - manager: conda - platform: linux-aarch64 - dependencies: - libbrotlicommon: 1.0.9 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libbrotlienc-1.0.9-h4e544f5_8.tar.bz2 - hash: - md5: 56a0a025208af24e2b43b2bbeee79802 - sha256: 2f6617b2ac53ab440d50a062d08e39cb207dc3ac36a5abe61efe0fa11d2205a1 - category: main - optional: false -- name: libedit - version: 3.1.20191231 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=7.5.0' - ncurses: '>=6.2,<7.0.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libedit-3.1.20191231-he28a2e2_2.tar.bz2 - hash: - md5: 29371161d77933a54fccf1bb66b96529 - sha256: debc31fb2f07ba2b0363f90e455873670734082822926ba4a9556431ec0bf36d - category: main - optional: false -- name: libevent - version: 2.1.12 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libevent-2.1.12-hf55b2d5_0.conda - hash: - md5: 132aa483897469a60779bbe9177abaa7 - sha256: 4fee4338b29bb0e0b4d154399a217c75d558916c1f6c5dca24ea50d313c3d151 - category: main - optional: false -- name: libnghttp2 - version: 1.52.0 - manager: conda - platform: linux-aarch64 - dependencies: - c-ares: '>=1.18.1,<2.0a0' - libev: '>=4.33,<4.34.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.0.8,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libnghttp2-1.52.0-h250e5c5_0.conda - hash: - md5: bfacd7a58ed56ff30d9454d32513d8d3 - sha256: aebb40d3ea3540deca76195bdd8023509eec21f456e609a5e71dee91385243e6 - category: main - optional: false -- name: libpng - version: 1.6.39 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libpng-1.6.39-hf9034f9_0.conda - hash: - md5: 5ec9052384a6ac85e9111e9ac7c5ec4c - sha256: a43ab7cb0a66febe26e33b75e4aef6ce4ce532f69e6336e24ce00235ed000fd9 - category: main - optional: false -- name: libprotobuf - version: 3.21.12 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libprotobuf-3.21.12-h7fe2111_0.conda - hash: - md5: 02e295b58b0533b9da10628de3b5c4f6 - sha256: b5deb40c5b3ee4d9e5b0b5ffbf2393a7af9eb4a1d5d9a2bc2349fbadba754e49 - category: main - optional: false -- name: libsqlite - version: 3.42.0 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.42.0-h194ca79_0.conda - hash: - md5: 5fc895d5063af554f24a7eb69faff054 - sha256: c7da559f4c2cf1cb1ca802e8282bc23c66891636e72ba8caf66059e4475173ec - category: main - optional: false -- name: libssh2 - version: 1.10.0 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libzlib: '>=1.2.12,<1.3.0a0' - openssl: '>=3.0.5,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libssh2-1.10.0-he5a64b1_3.tar.bz2 - hash: - md5: 97a05afae73dc8939078f44732534a47 - sha256: 5aaf9273f7e254dd640b5f2607fa590b6a69a961866044ee8d44128519edee66 - category: main - optional: false -- name: libxcb - version: '1.15' - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - pthread-stubs: '' - xorg-libxau: '' - xorg-libxdmcp: '' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcb-1.15-h2a766a3_0.conda - hash: - md5: eb3d8c8170e3d03f2564ed2024aa00c8 - sha256: d159fcdb8b74187b0bd32f2d9b3a9191bc8b786a97e413aa66e19c39ba7050a0 - category: main - optional: false -- name: pcre2 - version: '10.40' - manager: conda - platform: linux-aarch64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - libgcc-ng: '>=12' - libzlib: '>=1.2.12,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/pcre2-10.40-he7b27c6_0.tar.bz2 - hash: - md5: 2bb3167087f621daefab01b6a2ddc7f9 - sha256: 1c9967f4a1641801b9f976c264e6d3ad92ceb856d244d89e7b5ff3cd893e8751 - category: main - optional: false -- name: readline - version: '8.2' - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - ncurses: '>=6.3,<7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.2-h8fc344f_1.conda - hash: - md5: 105eb1e16bf83bfb2eb380a48032b655 - sha256: 4c99f7417419734e3797d45bc355e61c26520e111893b0d7087a01a7fbfbe3dd - category: main - optional: false -- name: s2n - version: 1.3.44 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/s2n-1.3.44-h5a25046_0.conda - hash: - md5: 4d9cc31f19e76420eb0803b6cf8f85bb - sha256: 52b70a683cc141b1cd3fb6a457df55db80d1410afae0f7d051e63cbababfb9a1 - category: main - optional: false -- name: tk - version: 8.6.12 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=9.4.0' - libzlib: '>=1.2.11,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.12-hd8af866_0.tar.bz2 - hash: - md5: 7894e82ff743bd96c76585ddebe28e2a - sha256: d659316c9e502fb0e1b9a284fb0f0c00e273bff787e9385ab14be9af13dcd0d2 - category: main - optional: false -- name: ucx - version: 1.14.1 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libnuma: '>=2.0.16,<3.0a0' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/ucx-1.14.1-hcd45e03_2.conda - hash: - md5: 2caef528bd30ea91b1fa5623f8277098 - sha256: f880aacfbd594fa30d4e1c3c41564853655041a3898a52e2539c67beac70bfc2 - category: main - optional: false -- name: zlib - version: 1.2.13 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libzlib: 1.2.13 - url: https://conda.anaconda.org/conda-forge/linux-aarch64/zlib-1.2.13-h4e544f5_4.tar.bz2 - hash: - md5: dc8395f4a79bb0b13ca7d855c72482d4 - sha256: 0e4c848421e361f5738aa743cd9c3955ac168905dffee3a7855fb3531b07ef01 - category: main - optional: false -- name: zstd - version: 1.5.2 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.2-h44f6412_6.conda - hash: - md5: 6d0d1cd6d184129eabb96bb220afb5b2 - sha256: d06afa18c6789d29f1d74990d0b2b68ada43665a419deb617d6440368bd951fc - category: main - optional: false -- name: aws-c-io - version: 0.13.21 - manager: conda - platform: linux-aarch64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - libgcc-ng: '>=12' - s2n: '>=1.3.44,<1.3.45.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/aws-c-io-0.13.21-h33ae702_5.conda - hash: - md5: a9f28ba9f48caa897ab6cd51e2dfb8a7 - sha256: 14973ec2fdac12696bdad58ff913e49cf0c7b0a8d90b46a12a5230936f635b61 - category: main - optional: false -- name: brotli-bin - version: 1.0.9 - manager: conda - platform: linux-aarch64 - dependencies: - libbrotlidec: 1.0.9 - libbrotlienc: 1.0.9 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-bin-1.0.9-h4e544f5_8.tar.bz2 - hash: - md5: 0980429a0148a53edd0f1f207ec28a39 - sha256: 30214484976cc0a6f37c6e2473578d4602d66d01acf3ccfd2f97238cbb91621b - category: main - optional: false -- name: freetype - version: 2.12.1 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libpng: '>=1.6.39,<1.7.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/freetype-2.12.1-hbbbf32d_1.conda - hash: - md5: e0891290982420d67651589c8584eec3 - sha256: f574138dd4fcec3acbd87df049bb9161af95ad194120cf322d884fdf0df477b5 - category: main - optional: false -- name: krb5 - version: 1.20.1 - manager: conda - platform: linux-aarch64 - dependencies: - keyutils: '>=1.6.1,<2.0a0' - libedit: '>=3.1.20191231,<4.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - openssl: '>=3.0.7,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/krb5-1.20.1-h113d92e_0.conda - hash: - md5: c0ac34c76019d6ebfef2d47a5687459c - sha256: 622d6cdcf8152aab7535ee36498ea08b99aa6b20b0e767d2b9f95b0518f7d3ae - category: main - optional: false -- name: libcblas - version: 3.9.0 - manager: conda - platform: linux-aarch64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libcblas-3.9.0-16_linuxaarch64_openblas.tar.bz2 - hash: - md5: 520a3ecbebc63239c27dd6f70c2ababe - sha256: c1d4fa9a99475647c7904009c026fe7f9c0b3159b2f7d2bcecac102751104302 - category: main - optional: false -- name: libglib - version: 2.76.3 - manager: conda - platform: linux-aarch64 - dependencies: - gettext: '>=0.21.1,<1.0a0' - libffi: '>=3.4,<4.0a0' - libgcc-ng: '>=12' - libiconv: '>=1.17,<2.0a0' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - pcre2: '>=10.40,<10.41.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libglib-2.76.3-h0464669_0.conda - hash: - md5: 8af0b99551d0d5cd020b5368b15c458a - sha256: 70ab257be48124ed4f3cc830f07c43443c2b56881be1572b80bea5a39b4e022c - category: main - optional: false -- name: libgrpc - version: 1.54.2 - manager: conda - platform: linux-aarch64 - dependencies: - c-ares: '>=1.18.1,<2.0a0' - libabseil: '>=20230125.2,<20230126.0a0' - libgcc-ng: '>=12' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - re2: '>=2023.3.2,<2023.3.3.0a0' - zlib: '' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libgrpc-1.54.2-h53d6f9f_2.conda - hash: - md5: 6c9743896065d77510c6185cb6a77de2 - sha256: 7c3ae6747cf0c84e7d7175195be9228fa128192535777ea0cc94369c6ed0bed6 - category: main - optional: false -- name: liblapack - version: 3.9.0 - manager: conda - platform: linux-aarch64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/linux-aarch64/liblapack-3.9.0-16_linuxaarch64_openblas.tar.bz2 - hash: - md5: 62990b2d1efc22d0beb394e893d39541 - sha256: 80a809ce2c965b27d8b8b90753ab01d467b9bf2a66467ca98fc363e4a41da5ec - category: main - optional: false -- name: libthrift - version: 0.18.1 - manager: conda - platform: linux-aarch64 - dependencies: - libevent: '>=2.1.12,<2.1.13.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libthrift-0.18.1-h0035360_1.conda - hash: - md5: 2325bfef6f60b91e8d2af4697bf37223 - sha256: 54df1fccce99cc428aad15912e15fcf7d821139a201c31af1299fd52e3680df7 - category: main - optional: false -- name: libtiff - version: 4.5.0 - manager: conda - platform: linux-aarch64 - dependencies: - lerc: '>=4.0.0,<5.0a0' - libdeflate: '>=1.18,<1.19.0a0' - libgcc-ng: '>=12' - libjpeg-turbo: '>=2.1.5.1,<3.0a0' - libstdcxx-ng: '>=12' - libwebp-base: '>=1.3.0,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - xz: '>=5.2.6,<6.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libtiff-4.5.0-h536c0eb_6.conda - hash: - md5: 75a0916176030b99c03ca2ecfe961128 - sha256: 2c4036df26e6f9af0e25de318e633e46f75bdedb361ac29e9210b48c020d9781 - category: main - optional: false -- name: orc - version: 1.8.3 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - snappy: '>=1.1.10,<2.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/orc-1.8.3-h3aebf57_0.conda - hash: - md5: b45ae91aa21d2ed954fad6f2c74f4e88 - sha256: 6ba40b25f7770c7e7fbe9d806b9e55270ea232cb8991b8d0f0659eb427141c3d - category: main - optional: false -- name: sqlite - version: 3.42.0 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libsqlite: 3.42.0 - libzlib: '>=1.2.13,<1.3.0a0' - ncurses: '>=6.3,<7.0a0' - readline: '>=8.2,<9.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/sqlite-3.42.0-h3b3482f_0.conda - hash: - md5: 61af86f0bb846ad0900fa8dadf15045b - sha256: 6dc3f91d9ac19e0861f06ff14067dbfb1dab278899bdfb033e908188cdc7d5b5 - category: main - optional: false -- name: aws-c-event-stream - version: 0.2.20 - manager: conda - platform: linux-aarch64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/aws-c-event-stream-0.2.20-hb189dcd_7.conda - hash: - md5: 894be7ddd336830e5a2b97b6ae4e8ddf - sha256: e64b3e81e3c8a8ee77d9e9b1ab6d6582524c01a0136ac547cf3baada4097b95b - category: main - optional: false -- name: aws-c-http - version: 0.7.7 - manager: conda - platform: linux-aarch64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-compression: '>=0.2.16,<0.2.17.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/aws-c-http-0.7.7-h7e0d9f2_4.conda - hash: - md5: 70ec97d1a92cf3cab59b0dbe4c825a9b - sha256: 85ba57fb1e3ee4917799df5e127ec93a6bfd57d4766d01d14c242a1656f64839 - category: main - optional: false -- name: brotli - version: 1.0.9 - manager: conda - platform: linux-aarch64 - dependencies: - brotli-bin: 1.0.9 - libbrotlidec: 1.0.9 - libbrotlienc: 1.0.9 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-1.0.9-h4e544f5_8.tar.bz2 - hash: - md5: 259d82bd990ba225508389509634b157 - sha256: e775343c34d04c6e27b4967b6edeac4793c9f0bd6c843990497c72798f49808f - category: main - optional: false -- name: dbus - version: 1.13.6 - manager: conda - platform: linux-aarch64 - dependencies: - expat: '>=2.4.2,<3.0a0' - libgcc-ng: '>=9.4.0' - libglib: '>=2.70.2,<3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/dbus-1.13.6-h12b9eeb_3.tar.bz2 - hash: - md5: f3d63805602166bac09386741e00935e - sha256: 5fe76bdf27a142cfb9da0fb3197c562e528d2622b573765bee5c9904cf5e6b6b - category: main - optional: false -- name: lcms2 - version: '2.15' - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libjpeg-turbo: '>=2.1.5.1,<3.0a0' - libtiff: '>=4.5.0,<4.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/lcms2-2.15-h3e0bdec_1.conda - hash: - md5: 5d6c6a9042e2316cec7410dd085814d1 - sha256: abdac39993c7d9603289d52d15370f1738ff5610463862bf3949988cecb5cdb7 - category: main - optional: false -- name: libcurl - version: 8.1.2 - manager: conda - platform: linux-aarch64 - dependencies: - krb5: '>=1.20.1,<1.21.0a0' - libgcc-ng: '>=12' - libnghttp2: '>=1.52.0,<2.0a0' - libssh2: '>=1.10.0,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libcurl-8.1.2-hc34909b_0.conda - hash: - md5: 1967e1a5fc2fc9f5b2e0863bd64a7819 - sha256: 488aca4dc8244f6105fe4d929eb2bd68c16e76472af43b819366919b7397cdfd - category: main - optional: false -- name: openjpeg - version: 2.5.0 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libpng: '>=1.6.39,<1.7.0a0' - libstdcxx-ng: '>=12' - libtiff: '>=4.5.0,<4.6.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/openjpeg-2.5.0-h9508984_2.conda - hash: - md5: 3d56d402a845c243f8c2dd3c8e836029 - sha256: 6cb45c526e9577313081a7d020a278fbdfd91e6df14f42a327276ec1a29a5045 - category: main - optional: false -- name: python - version: 3.9.12 - manager: conda - platform: linux-aarch64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - ld_impl_linux-aarch64: '>=2.36.1' - libffi: '>=3.4.2,<3.5.0a0' - libgcc-ng: '>=10.3.0' - libnsl: '>=2.0.0,<2.1.0a0' - libuuid: '>=2.32.1,<3.0a0' - libzlib: '>=1.2.11,<1.3.0a0' - ncurses: '>=6.3,<7.0a0' - openssl: '>=3.0.2,<4.0a0' - readline: '>=8.1,<9.0a0' - sqlite: '>=3.37.1,<4.0a0' - tk: '>=8.6.12,<8.7.0a0' - tzdata: '' - xz: '>=5.2.5,<5.3.0a0' - pip: '' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.9.12-h5016f1d_1_cpython.tar.bz2 - hash: - md5: 09cceb4a3e723338d0fbed7c7d22e2a4 - sha256: 7cde0c4a1630c060ed94cf950a043b4d454b015cf5a7bc1a6de40ae38ce990cd - category: main - optional: false -- name: antlr-python-runtime - version: 4.9.3 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/antlr-python-runtime-4.9.3-pyhd8ed1ab_1.tar.bz2 - hash: - md5: c88eaec8de9ae1fa161205aa18e7a5b1 - sha256: b91f8ab4ac2b48972fbee1fc8e092cc452fdf59156e4ff2322c94bbf73650f94 - category: main - optional: false -- name: attrs - version: 23.1.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/attrs-23.1.0-pyh71513ae_1.conda - hash: - md5: 3edfead7cedd1ab4400a6c588f3e75f8 - sha256: 063639cd568f5c7a557b0fb1cc27f098598c0d8ff869088bfeb82934674f8821 - category: main - optional: false -- name: aws-c-auth - version: 0.6.27 - manager: conda - platform: linux-aarch64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-c-sdkutils: '>=0.1.9,<0.1.10.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/aws-c-auth-0.6.27-hd4bd39e_1.conda - hash: - md5: 5d24dc6777152909ebee8c65d40e218d - sha256: ebdff6bd21b77a9e4c143707937aca045c727bf116a4f0f0904eba0feb5135cd - category: main - optional: false -- name: aws-c-mqtt - version: 0.8.11 - manager: conda - platform: linux-aarch64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/aws-c-mqtt-0.8.11-h6ea80e6_1.conda - hash: - md5: ba18b802aa60ee53b2d78ad155be470d - sha256: ea8f1cfdb622d3e2005721008cc405587a3eb61689bd0337f3f80b4f09aee4da - category: main - optional: false -- name: backports - version: '1.0' - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=2.7' - url: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_3.conda - hash: - md5: 54ca2e08b3220c148a1d8329c2678e02 - sha256: 711602276ae39276cb0faaca6fd0ac851fff0ca17151917569174841ef830bbd - category: main - optional: false -- name: blinker - version: 1.6.2 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/blinker-1.6.2-pyhd8ed1ab_0.conda - hash: - md5: 2fb79ec81bad9492b6d59a06b3b647a4 - sha256: b6f32491536823e47cf6eb4717dd341385600a2b901235028dedc629a77aeb82 - category: main - optional: false -- name: certifi - version: 2023.5.7 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/certifi-2023.5.7-pyhd8ed1ab_0.conda - hash: - md5: 5d1b71c942b8421285934dad1d891ebc - sha256: f839a6e04d94069f90dd85337ea9108f058dc76771bb469a413f32bb1ba0b256 - category: main - optional: false -- name: charset-normalizer - version: 3.1.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.1.0-pyhd8ed1ab_0.conda - hash: - md5: 7fcff9f6f123696e940bda77bd4d6551 - sha256: 06cd371fc98f076797d6450f6f337cb679b1060c99680fb7e044591493333194 - category: main - optional: false -- name: click - version: 8.1.3 - manager: conda - platform: linux-aarch64 - dependencies: - __unix: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2 - hash: - md5: 20e4087407c7cb04a40817114b333dbf - sha256: 23676470b591b100393bb0f6c46fe10624dcbefc696a6a9f42932ed8816ef0ea - category: main - optional: false -- name: cloudpickle - version: 2.2.1 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.1-pyhd8ed1ab_0.conda - hash: - md5: b325bfc4cff7d7f8a868f1f7ecc4ed16 - sha256: f0c2fd0e842899a05ddd7b147fb26424adf58be0e8e54e5bc68b8f7e67d05dcd - category: main - optional: false -- name: colorama - version: 0.4.6 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3faab06a954c2a04039983f2c4a50d99 - sha256: 2c1b2e9755ce3102bca8d69e8f26e4f087ece73f50418186aee7c74bef8e1698 - category: main - optional: false -- name: configparser - version: 5.3.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/configparser-5.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: c99fd5916160900dc5ff64204da99c4d - sha256: ce6ce9ee08437b46c284d52b076fb091cf6f2a9e12860d4a37546adbd5f53b28 - category: main - optional: false -- name: crashtest - version: 0.4.1 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6,<4.0' - url: https://conda.anaconda.org/conda-forge/noarch/crashtest-0.4.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 709a2295dd907bb34afb57d54320642f - sha256: 2f05954a3faf0700c14c1deddc085385160ee32abe111699c78d9cb277e915cc - category: main - optional: false -- name: cycler - version: 0.11.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: a50559fad0affdbb33729a68669ca1cb - sha256: 3b594bc8aa0b9a51269d54c7a4ef6af777d7fad4bee16b05695e1124de6563f6 - category: main - optional: false -- name: distlib - version: 0.3.6 - manager: conda - platform: linux-aarch64 - dependencies: - python: 2.7|>=3.6 - url: https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2 - hash: - md5: b65b4d50dbd2d50fa0aeac367ec9eed7 - sha256: 06eb7167d4d760b3b437a491e32ab5b3f89e2a18f023c117fe213b038d88538a - category: main - optional: false -- name: entrypoints - version: '0.4' - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3cf04868fee0a029769bd41f4b2fbf2d - sha256: 2ec4a0900a4a9f42615fc04d0fb3286b796abe56590e8e042f6ec25e102dd5af - category: main - optional: false -- name: exceptiongroup - version: 1.1.1 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.1-pyhd8ed1ab_0.conda - hash: - md5: 7312299d7a0ea4993159229b7d2dceb2 - sha256: f073c3ba993912f1c0027bc34a54975642885f0a4cd5f9dc42a17ca945df2c18 - category: main - optional: false -- name: filelock - version: 3.12.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.12.0-pyhd8ed1ab_0.conda - hash: - md5: 650f18a56f366dbf419c15b543592c2d - sha256: 68db3a6280d6786be76f2c7c6cf41dd878c5d1a24f5de10f7f0af82c6fcfade6 - category: main - optional: false -- name: fsspec - version: 2023.5.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/fsspec-2023.5.0-pyh1a96a4e_0.conda - hash: - md5: 20edd290b319aa0eff3e9055375756dc - sha256: cbb5c77c0217cda9bf4f4240158de11822a099a6eaa05ba626e822819a54f46d - category: main - optional: false -- name: greenlet - version: 2.0.2 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/greenlet-2.0.2-py39h387a81e_1.conda - hash: - md5: 2e1247ed9652e0594abebc18fb4a72af - sha256: 7019e7dcd5cbb3b9e9b50aa1f49cec77256dcc579903cbcb2661a3b0c782f93f - category: main - optional: false -- name: idna - version: '3.4' - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 34272b248891bddccc64479f9a7fffed - sha256: 9887c35c374ec1847f167292d3fde023cb4c994a4ceeec283072b95440131f09 - category: main - optional: false -- name: itsdangerous - version: 2.1.2 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/itsdangerous-2.1.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3c3de74912f11d2b590184f03c7cd09b - sha256: 31e3492686b4e92b53db9b48bc0eb03873b1caaf28629fee7d2d47627a2c56d3 - category: main - optional: false -- name: jeepney - version: 0.8.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/jeepney-0.8.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 9800ad1699b42612478755a2d26c722d - sha256: 16639759b811866d63315fe1391f6fb45f5478b823972f4d3d9f0392b7dd80b8 - category: main - optional: false -- name: kiwisolver - version: 1.4.4 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/kiwisolver-1.4.4-py39h110580c_1.tar.bz2 - hash: - md5: 9c045502f6ab8c89bfda6be3c389e503 - sha256: 9bf3781b4f46988b7e97d9fbaeab666340d3818d162d362b11529809349c9741 - category: main - optional: false -- name: libgoogle-cloud - version: 2.10.1 - manager: conda - platform: linux-aarch64 - dependencies: - libabseil: '>=20230125.2,<20230126.0a0' - libcrc32c: '>=1.1.2,<1.2.0a0' - libcurl: '>=8.0.1,<9.0a0' - libgcc-ng: '>=12' - libgrpc: '>=1.54.2,<1.55.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libgoogle-cloud-2.10.1-h48e691c_1.conda - hash: - md5: cab0e23c8b5f473c7a12e304f80fa84d - sha256: bb2f2fd03b908e381b5ee3bc84d29cd75fa653a37572056d7bde548bef66b506 - category: main - optional: false -- name: lockfile - version: 0.12.2 - manager: conda - platform: linux-aarch64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/lockfile-0.12.2-py_1.tar.bz2 - hash: - md5: c104d98e09c47519950cffb8dd5b4f10 - sha256: d3a68045ef74a2a7b8c8a55b242fdbc875d362e37adcf793613cf0d8c8e4fbf7 - category: main - optional: false -- name: markupsafe - version: 2.1.2 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/markupsafe-2.1.2-py39h599bc27_0.conda - hash: - md5: 13af483192015190404fede49f1a306e - sha256: 4eb2683d7391a984b0f32e9f9fb20c2708b6a674b0e6d901cd80ccb61b491052 - category: main - optional: false -- name: mdurl - version: 0.1.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: f8dab71fdc13b1bf29a01248b156d268 - sha256: c678b9194e025b1fb665bec30ee20aab93399203583875b1dcc0a3b52a8f5523 - category: main - optional: false -- name: more-itertools - version: 9.1.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/more-itertools-9.1.0-pyhd8ed1ab_0.conda - hash: - md5: 1698a717f83cfecf644a877c174c84bd - sha256: 3ee8cbbe4004c56b695a5e734b7dc4d59dacbfefc193ee42c82238b1cf888e08 - category: main - optional: false -- name: msgpack-python - version: 1.0.5 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/msgpack-python-1.0.5-py39hc8fb0a1_0.conda - hash: - md5: 8741d1a78d300fe74006b3fb875aa85a - sha256: d75971041e9ea63d7c3a4f8762ebf7d8b56ba15e0d9a4b2d9d7daef2e9f66fd2 - category: main - optional: false -- name: munkres - version: 1.1.4 - manager: conda - platform: linux-aarch64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2 - hash: - md5: 2ba8498c1018c1e9c61eb99b973dfe19 - sha256: f86fb22b58e93d04b6f25e0d811b56797689d598788b59dcb47f59045b568306 - category: main - optional: false -- name: numpy - version: 1.24.3 - manager: conda - platform: linux-aarch64 - dependencies: - libblas: '>=3.9.0,<4.0a0' - libcblas: '>=3.9.0,<4.0a0' - libgcc-ng: '>=12' - liblapack: '>=3.9.0,<4.0a0' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/numpy-1.24.3-py39hf88902c_0.conda - hash: - md5: dc4187f9993e49b36eb9c61ce63ed3c5 - sha256: a1c35564aa58aba624cb4c73345399905ec0daa52313cac14ae9e2c8b9e37f87 - category: main - optional: false -- name: ordered-set - version: 4.1.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/ordered-set-4.1.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 9a8714decb3967b290263817e876d8a9 - sha256: 78d92f848a6b4a89148dfa1f6e65c0b75e8f3a267b6401be38fb3401853b4afa - category: main - optional: false -- name: orjson - version: 3.8.14 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/orjson-3.8.14-py39h96b61db_0.conda - hash: - md5: 11ca45feb87a95c59c5e62f912e7d6d1 - sha256: bb6e00157fa4375898a1986865b89c00577beca1e13ddd42d91e2195b2d94daa - category: main - optional: false -- name: packaging - version: '23.1' - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/packaging-23.1-pyhd8ed1ab_0.conda - hash: - md5: 91cda59e66e1e4afe9476f8ef98f5c30 - sha256: ded536a96a00d45a693dbc2971bb688248324dadd129eddda2100e177583d768 - category: main - optional: false -- name: pillow - version: 9.5.0 - manager: conda - platform: linux-aarch64 - dependencies: - freetype: '>=2.12.1,<3.0a0' - lcms2: '>=2.15,<3.0a0' - libgcc-ng: '>=12' - libjpeg-turbo: '>=2.1.5.1,<3.0a0' - libtiff: '>=4.5.0,<4.6.0a0' - libwebp-base: '>=1.3.0,<2.0a0' - libxcb: '>=1.15,<1.16.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openjpeg: '>=2.5.0,<3.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - tk: '>=8.6.12,<8.7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/pillow-9.5.0-py39hc5b5638_1.conda - hash: - md5: 0560194d0eab633c666299c993869cca - sha256: 0390ed9fb866922e033871fdbbb72b2c45f2598b3b2a871c03ab03aec5893a62 - category: main - optional: false -- name: pkginfo - version: 1.9.6 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.9.6-pyhd8ed1ab_0.conda - hash: - md5: be1e9f1c65a1ed0f2ae9352fec99db64 - sha256: 7ea5a5af62a15376d9f4f9f3c134874d0b0710f39be719e849b7fa9ca8870502 - category: main - optional: false -- name: pkgutil-resolve-name - version: 1.3.10 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pkgutil-resolve-name-1.3.10-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 89e3c7cdde7d3aaa2aee933b604dd07f - sha256: 7d055ffc8a02bf781a89d069db3454b453605cdaff300b82cedcc7133283e47e - category: main - optional: false -- name: prometheus_client - version: 0.17.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.17.0-pyhd8ed1ab_0.conda - hash: - md5: 95c5be3c7cbd872509d16c216617fdab - sha256: eb11fd8b927d9c5ff9482cfbd6cd810a43a1351c44a288e9680542ea698a19a0 - category: main - optional: false -- name: psutil - version: 5.9.5 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/psutil-5.9.5-py39h24fc6b6_0.conda - hash: - md5: 83a7e124676f5ad31e6160a46061e5e4 - sha256: cab930dea29f33ec4bec75576e29b4829729b3db651be423fd00a8065a64852c - category: main - optional: false -- name: ptyprocess - version: 0.7.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd3deb0d_0.tar.bz2 - hash: - md5: 359eeb6536da0e687af562ed265ec263 - sha256: fb31e006a25eb2e18f3440eb8d17be44c8ccfae559499199f73584566d0a444a - category: main - optional: false -- name: pycparser - version: '2.21' - manager: conda - platform: linux-aarch64 - dependencies: - python: 2.7.*|>=3.4 - url: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 076becd9e05608f8dc72757d5f3a91ff - sha256: 74c63fd03f1f1ea2b54e8bc529fd1a600aaafb24027b738d0db87909ee3a33dc - category: main - optional: false -- name: pygments - version: 2.15.1 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/pygments-2.15.1-pyhd8ed1ab_0.conda - hash: - md5: d316679235612869eba305aa7d41d9bf - sha256: 1bddeb54863c77ed5613b535a3e06a3a16b55786301a5e28c9bf011656bda686 - category: main - optional: false -- name: pyjwt - version: 2.7.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyjwt-2.7.0-pyhd8ed1ab_0.conda - hash: - md5: 99e28be5a278e2319834d7dc99e7bfdd - sha256: f3a64306fa0f405f10f4108d7ff42043d6fd393f940f9e98e395a3756687fc98 - category: main - optional: false -- name: pyparsing - version: 3.0.9 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2 - hash: - md5: e8fbc1b54b25f4b08281467bc13b70cc - sha256: 4acc7151cef5920d130f2e0a7615559cce8bfb037aeecb14d4d359ae3d9bc51b - category: main - optional: false -- name: pyrsistent - version: 0.19.3 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/pyrsistent-0.19.3-py39h599bc27_0.conda - hash: - md5: eb78b6cdfa3798686407980ff17bc4e1 - sha256: d88449a2ba5a386dbad1881e7086c12b7678fa15a4d11c6d574c59ef31fd7fa8 - category: main - optional: false -- name: pysocks - version: 1.7.1 - manager: conda - platform: linux-aarch64 - dependencies: - __unix: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2 - hash: - md5: 2a7de29fb590ca14b5243c4c812c8025 - sha256: a42f826e958a8d22e65b3394f437af7332610e43ee313393d1cf143f0a2d274b - category: main - optional: false -- name: python-editor - version: 1.0.4 - manager: conda - platform: linux-aarch64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/python-editor-1.0.4-py_0.tar.bz2 - hash: - md5: eaaf29a0644f9407f98a4665f45880c4 - sha256: a6db88da69a27451d2eba675c445bdefd2dbea52ea02a0a214d5fd4f0af31740 - category: main - optional: false -- name: python-installer - version: 0.7.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/python-installer-0.7.0-pyhd8ed1ab_0.conda - hash: - md5: 65dea78f903d686c8b0c2feaf0e15e1f - sha256: 822f95b7786cfa61a6519153117b21d93194890e02a884b9f66ee4275e4f1c0a - category: main - optional: false -- name: python-multipart - version: 0.0.6 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/python-multipart-0.0.6-pyhd8ed1ab_0.conda - hash: - md5: f4f642eeda814c1b65f46fbdf7e89096 - sha256: 2a9b8d02a6ec9862433cfc2741c4cbfe321e4ae3bab066f7ed84bc00effb73d7 - category: main - optional: false -- name: python-tzdata - version: '2023.3' - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2023.3-pyhd8ed1ab_0.conda - hash: - md5: 2590495f608a63625e165915fb4e2e34 - sha256: 0108888507014fb24573c31e4deceb61c99e63d37776dddcadd7c89b2ecae0b6 - category: main - optional: false -- name: pytz - version: '2023.3' - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pytz-2023.3-pyhd8ed1ab_0.conda - hash: - md5: d3076b483092a435832603243567bc31 - sha256: e4999484f21763ca4b8f92c95b22cb6d1edc1b61d0a2bb073ee2bd11f39401b9 - category: main - optional: false -- name: pywin32-on-windows - version: 0.1.0 - manager: conda - platform: linux-aarch64 - dependencies: - __unix: '' - python: '>=2.7' - url: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - hash: - md5: 2807a0becd1d986fe1ef9b7f8135f215 - sha256: 6502696aaef571913b22a808b15c185bd8ea4aabb952685deb29e6a6765761cb - category: main - optional: false -- name: pyyaml - version: '6.0' - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - yaml: '>=0.2.5,<0.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0-py39h0fd3b05_5.tar.bz2 - hash: - md5: eca87d250ba72da233121a1cc75582cc - sha256: a5661a35d7e96341d3c11b39f20e1ce62404be06aff683453cd6b0a2f9c734bd - category: main - optional: false -- name: readchar - version: 4.0.5 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/readchar-4.0.5-pyhd8ed1ab_0.conda - hash: - md5: 513334936060e80697bc21079e4f2829 - sha256: 0426cd7a524c31ab6d52b4d181848daea81d057e200a74200ea6e2896534bc18 - category: main - optional: false -- name: setuptools - version: 67.7.2 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/setuptools-67.7.2-pyhd8ed1ab_0.conda - hash: - md5: 3b68bc43ec6baa48f7354a446267eefe - sha256: 3ac44771fce01f19218bcdf3992e24984748048db69889a9df65abcc6a10e29b - category: main - optional: false -- name: shellingham - version: 1.5.1 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/shellingham-1.5.1-pyhd8ed1ab_0.conda - hash: - md5: 1de44299f48f522caa2e0074231614e1 - sha256: 3cb4a4a83b617fdfef9b92751634488db0b8961c80340be8068bf6d4f1d5ac25 - category: main - optional: false -- name: six - version: 1.16.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 - hash: - md5: e5f25f8dbc060e9a8d912e432202afc2 - sha256: a85c38227b446f42c5b90d9b642f2c0567880c15d72492d8da074a59c8f91dd6 - category: main - optional: false -- name: smmap - version: 3.0.5 - manager: conda - platform: linux-aarch64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/smmap-3.0.5-pyh44b312d_0.tar.bz2 - hash: - md5: 3a8dc70789709aa315325d5df06fb7e4 - sha256: 091de70ee6bfe063e0c0f77336975d124fd1e3f49b9c58d97c0c7b3d287c0002 - category: main - optional: false -- name: sniffio - version: 1.3.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: dd6cbc539e74cb1f430efbd4575b9303 - sha256: a3fd30754c20ddb28b777db38345ea00d958f46701f0decd6291a81c0f4eee78 - category: main - optional: false -- name: soupsieve - version: 2.3.2.post1 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 146f4541d643d48fc8a75cacf69f03ae - sha256: 72d80dda41c3902c2619e8ab49d4f5b2a894d13375e1f9ed16fc00074ddd2307 - category: main - optional: false -- name: sqlparse - version: 0.4.4 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.5' - url: https://conda.anaconda.org/conda-forge/noarch/sqlparse-0.4.4-pyhd8ed1ab_0.conda - hash: - md5: 2e2f31b3b1c866c29636377e14f8c4c6 - sha256: 7972c9b15dafa1885f3d4cd22dc4edea4cd969d12739fb71f8632f2c3350706a - category: main - optional: false -- name: tabulate - version: 0.9.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.9.0-pyhd8ed1ab_1.tar.bz2 - hash: - md5: 4759805cce2d914c38472f70bf4d8bcb - sha256: f6e4a0dd24ba060a4af69ca79d32361a6678e61d78c73eb5e357909b025b4620 - category: main - optional: false -- name: threadpoolctl - version: 3.1.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2 - hash: - md5: a2995ee828f65687ac5b1e71a2ab1e0c - sha256: c7a964811ba49c545236f7d6c2486b2ed493931660048a06fe94ecc851dd0b82 - category: main - optional: false -- name: tomli - version: 2.0.1 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 5844808ffab9ebdb694585b50ba02a96 - sha256: 4cd48aba7cd026d17e86886af48d0d2ebc67ed36f87f6534f4b67138f5a5a58f - category: main - optional: false -- name: tomlkit - version: 0.11.8 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tomlkit-0.11.8-pyha770c72_0.conda - hash: - md5: 75838e8556166263a82038b51d01d5f1 - sha256: 3002e87338a98ba501fbf53981f8267b2def2548265a3622d403d06747872ccd - category: main - optional: false -- name: traitlets - version: 5.9.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.9.0-pyhd8ed1ab_0.conda - hash: - md5: d0b4f5c87cd35ac3fb3d47b223263a64 - sha256: 343610bce6dbe8a5090500dd2e9d1706057960b3f3120ebfe0abb4a8ecbada4d - category: main - optional: false -- name: trove-classifiers - version: 2023.5.24 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2023.5.24-pyhd8ed1ab_0.conda - hash: - md5: 4580a4f27cad1c3b275f6f6ad310abae - sha256: 05e83cd3ac921143c7a25681928727bcc9b01bf8456c9615b72d64f050863503 - category: main - optional: false -- name: typing - version: 3.10.0.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/typing-3.10.0.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: e6573ac68718f17b9d4f5c8eda3190f2 - sha256: ec1cfe0b7dc55a22223562cad799e0b16d122dab611c9923b6068d27a784ba2f - category: main - optional: false -- name: typing_extensions - version: 4.5.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.5.0-pyha770c72_0.conda - hash: - md5: 43e7d9e50261fb11deb76e17d8431aac - sha256: f81eee64fcdfb379e27d01773b34041fbf7f9e86f33b157c9925d19e0a442452 - category: main - optional: false -- name: unicodedata2 - version: 15.0.0 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/unicodedata2-15.0.0-py39h0fd3b05_0.tar.bz2 - hash: - md5: 835f1a9631e600e0176593e95e85f73f - sha256: 06d0dd905a8b4555b729d8c5568a8339a385476890d3b3fc2134ec08d0cfc484 - category: main - optional: false -- name: webencodings - version: 0.5.1 - manager: conda - platform: linux-aarch64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-py_1.tar.bz2 - hash: - md5: 3563be4c5611a44210d9ba0c16113136 - sha256: 302f4f4bd1ad00c0be1426ecf6bb01db59cfd8aff3de0cf1596526dca1a6b70e - category: main - optional: false -- name: websocket-client - version: 1.5.2 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.5.2-pyhd8ed1ab_0.conda - hash: - md5: bfe7e7cd1476092f51efbcde15dfb110 - sha256: 85310b382c4220d7846fa8f046216fd722b88db07991f07bd7decdf2e5dc3446 - category: main - optional: false -- name: websockets - version: 11.0.3 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/websockets-11.0.3-py39h898b7ef_0.conda - hash: - md5: cea26ae7953b7c921b08b6595836bb08 - sha256: 123a283fcbee4a73698c440e706fe8bc4ed37dc5e5329100d55ac33ac3dc69e5 - category: main - optional: false -- name: wheel - version: 0.40.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.40.0-pyhd8ed1ab_0.conda - hash: - md5: 49bb0d9e60ce1db25e151780331bb5f3 - sha256: 79b4d29b0c004014a2abd5fc2c9fcd35cc6256222b960c2a317a27c4b0d8884d - category: main - optional: false -- name: zipp - version: 3.15.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/zipp-3.15.0-pyhd8ed1ab_0.conda - hash: - md5: 13018819ca8f5b7cc675a8faf1f5fedf - sha256: 241de30545299be9bcea3addf8a2c22a3b3d4ba6730890e150ab690ac937a3d2 - category: main - optional: false -- name: anyio - version: 3.7.0 - manager: conda - platform: linux-aarch64 - dependencies: - typing_extensions: '' - exceptiongroup: '' - python: '>=3.7' - idna: '>=2.8' - sniffio: '>=1.1' - url: https://conda.anaconda.org/conda-forge/noarch/anyio-3.7.0-pyhd8ed1ab_1.conda - hash: - md5: 2b35a85d654a47aac8f34c1bb6de7142 - sha256: 863c11a6a0e937977229b405a16f6d43fff543dfe5b1a66da9c42ec0cbdaaf33 - category: main - optional: false -- name: aws-c-s3 - version: 0.3.0 - manager: conda - platform: linux-aarch64 - dependencies: - aws-c-auth: '>=0.6.27,<0.6.28.0a0' - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - libgcc-ng: '>=12' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/aws-c-s3-0.3.0-h53ca5b1_2.conda - hash: - md5: d9bcd16819fde846d65eb133072eb755 - sha256: fdd5aabbb1ab9c4b561d65249c218e1a327e654e9bfcc84fdc0ce245c50d7e35 - category: main - optional: false -- name: backports.cached-property - version: 1.0.2 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - typing: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/backports.cached-property-1.0.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 0e1df3978dd516e20ef88c86d51e5432 - sha256: 1d86eafb5e9ed078f891e12b46692d786723652907dfb01b047c7da31f92b862 - category: main - optional: false -- name: backports.functools_lru_cache - version: 1.6.4 - manager: conda - platform: linux-aarch64 - dependencies: - setuptools: '' - backports: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/backports.functools_lru_cache-1.6.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: c5b3edc62d6309088f4970b3eaaa65a6 - sha256: fdea00d4b79990f3fe938e2716bc32bd895eb5c44b6c75b8261db095a1b33c16 - category: main - optional: false -- name: beautifulsoup4 - version: 4.12.2 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - soupsieve: '>=1.2' - url: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.2-pyha770c72_0.conda - hash: - md5: a362ff7d976217f8fa78c0f1c4f59717 - sha256: 52d3e6bcd442537e22699cd227d8fdcfd54b708eeb8ee5b4c671a6a9b9cd74da - category: main - optional: false -- name: cffi - version: 1.15.1 - manager: conda - platform: linux-aarch64 - dependencies: - libffi: '>=3.4,<4.0a0' - libgcc-ng: '>=12' - pycparser: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/cffi-1.15.1-py39hb26bf21_3.conda - hash: - md5: dee0362c4fde8edce396183fd6390d6e - sha256: 0a3690929b3a22c4e2db8001293509e38b5d90eb2ff57d5d71456e81c9c0f8eb - category: main - optional: false -- name: contourpy - version: 1.0.7 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.16' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/contourpy-1.0.7-py39hd9a2fea_0.conda - hash: - md5: efa783bf5c2b30aba3cf22599fe0274e - sha256: 0da3e468f4ee6cc3d708e32ab4d1e4d6e8ed899168693e3e33570d1e8ce927d9 - category: main - optional: false -- name: deepdiff - version: 6.3.0 - manager: conda - platform: linux-aarch64 - dependencies: - orjson: '' - python: '>=3.7' - ordered-set: '>=4.1.0,<4.2.0' - url: https://conda.anaconda.org/conda-forge/noarch/deepdiff-6.3.0-pyhd8ed1ab_0.conda - hash: - md5: 67ce5e3eecbf1e5ff869269640ae6a53 - sha256: f949d860d532a07587bdb8466310394d8c1af4dd89bb65d65219161fcc16db10 - category: main - optional: false -- name: fonttools - version: 4.39.4 - manager: conda - platform: linux-aarch64 - dependencies: - brotli: '' - libgcc-ng: '>=12' - munkres: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - unicodedata2: '>=14.0.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/fonttools-4.39.4-py39h898b7ef_0.conda - hash: - md5: c10973b2dc04e82014938c14b919e6e0 - sha256: 0a38a10286459b21cc0c40ce9fb3bc42ef3e36446cf9f575649390a539e01d86 - category: main - optional: false -- name: gitdb - version: 4.0.10 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.4' - smmap: '>=3.0.1,<4' - url: https://conda.anaconda.org/conda-forge/noarch/gitdb-4.0.10-pyhd8ed1ab_0.conda - hash: - md5: 3706d2f3d7cb5dae600c833345a76132 - sha256: 0003ab2b971913380633c711bf49a54dcf06e179986c725b0925854b58878377 - category: main - optional: false -- name: gunicorn - version: 20.1.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - setuptools: '>=3.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/gunicorn-20.1.0-py39h4420490_3.tar.bz2 - hash: - md5: e919f00a65c241317b5e95a0c6536bfc - sha256: 9fb461d9586c2fe0577a9cd2b6f961ddb91c16f422800cbd84700afad02cca20 - category: main - optional: false -- name: h11 - version: 0.14.0 - manager: conda - platform: linux-aarch64 - dependencies: - typing_extensions: '' - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: b21ed0883505ba1910994f1df031a428 - sha256: 817d2c77d53afe3f3d9cf7f6eb8745cdd8ea76c7adaa9d7ced75c455a2c2c085 - category: main - optional: false -- name: html5lib - version: '1.1' - manager: conda - platform: linux-aarch64 - dependencies: - python: '' - webencodings: '' - six: '>=1.9' - url: https://conda.anaconda.org/conda-forge/noarch/html5lib-1.1-pyh9f0ad1d_0.tar.bz2 - hash: - md5: b2355343d6315c892543200231d7154a - sha256: 9ad06446fe9847e86cb20d220bf11614afcd2cbe9f58096f08d5d4018877bee4 - category: main - optional: false -- name: importlib-metadata - version: 6.6.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.8' - zipp: '>=0.5' - url: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.6.0-pyha770c72_0.conda - hash: - md5: f91a5d5175fb7ff2a91952ec7da59cb9 - sha256: 33d49065756a73fbb92277c756fa00a41891408528eb90ae05ff3367a401ae6e - category: main - optional: false -- name: importlib_resources - version: 5.12.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - zipp: '>=3.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.12.0-pyhd8ed1ab_0.conda - hash: - md5: e5fd2260a231ee63b6969f4801082f2b - sha256: 091cca3e010f7a7353152f0abda2d68cfd83ddde80a15e974d9e18b2047e7be2 - category: main - optional: false -- name: jaraco.classes - version: 3.2.3 - manager: conda - platform: linux-aarch64 - dependencies: - more-itertools: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.2.3-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 31e4a1506968d017229bdb64695013a1 - sha256: 6a81b67a1de8f761f66a4540bbd07cc27f9fbf2c7d67aa3732ebef379cf62874 - category: main - optional: false -- name: jinja2 - version: 3.1.2 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - markupsafe: '>=2.0' - url: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2 - hash: - md5: c8490ed5c70966d232fdd389d0dbed37 - sha256: b045faba7130ab263db6a8fdc96b1a3de5fcf85c4a607c5f11a49e76851500b5 - category: main - optional: false -- name: joblib - version: 1.2.0 - manager: conda - platform: linux-aarch64 - dependencies: - setuptools: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.2.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 7583652522d71ad78ba536bba06940eb - sha256: 0c21351871df2c0a53168575597dd9c881e2a9fa4c42fe89a9bcd7fab37f462c - category: main - optional: false -- name: lightning-utilities - version: 0.8.0 - manager: conda - platform: linux-aarch64 - dependencies: - typing_extensions: '' - python: '>=3.8' - packaging: '>=17.1' - url: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.8.0-pyhd8ed1ab_0.conda - hash: - md5: ad16f58b64d3b41f4cbb75040b06c9cc - sha256: 8c1fff22ab86c85768e65dc8c4f4664787476a21f4d934c4e0261a1fa7523f9c - category: main - optional: false -- name: markdown-it-py - version: 2.2.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - typing_extensions: '>=3.7.4' - mdurl: '>=0.1,<1' - url: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-2.2.0-pyhd8ed1ab_0.conda - hash: - md5: b2928a6c6d52d7e3562b4a59c3214e3a - sha256: 65ed439862c1851463f03a9bc5109992ce3e3e025e9a2d76d13ca19f576eee9f - category: main - optional: false -- name: omegaconf - version: 2.3.0 - manager: conda - platform: linux-aarch64 - dependencies: - typing_extensions: '' - python: '>=3.7' - pyyaml: '>=5.1.0' - antlr-python-runtime: 4.9.* - url: https://conda.anaconda.org/conda-forge/noarch/omegaconf-2.3.0-pyhd8ed1ab_0.conda - hash: - md5: 23cc056834cab53849b91f78d6ee3ea0 - sha256: df806841be847e5287b22b6ae7f380874f81ea51f1b51ae14a570f3385c7b133 - category: main - optional: false -- name: pexpect - version: 4.8.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '' - ptyprocess: '>=0.5' - url: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.8.0-pyh1a96a4e_2.tar.bz2 - hash: - md5: 330448ce4403cc74990ac07c555942a1 - sha256: 07706c0417ead94f359ca7278f65452d3c396448777aba1da6a11fc351bdca9a - category: main - optional: false -- name: pip - version: 23.1.2 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - setuptools: '' - wheel: '' - url: https://conda.anaconda.org/conda-forge/noarch/pip-23.1.2-pyhd8ed1ab_0.conda - hash: - md5: 7288da0d36821349cf1126e8670292df - sha256: 4fe1f47f6eac5b2635a622b6f985640bf835843c1d8d7ccbbae0f7d27cadec92 - category: main - optional: false -- name: protobuf - version: 4.21.12 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - setuptools: '' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/protobuf-4.21.12-py39hdcdd789_0.conda - hash: - md5: 8e306d0c333b320262a7201696486a4b - sha256: be91445a8e03567a173e1fe8e566f3f1c5cb3dacf6a27795852e697d580d8b20 - category: main - optional: false -- name: pyproject_hooks - version: 1.0.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - tomli: '>=1.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.0.0-pyhd8ed1ab_0.conda - hash: - md5: 21de50391d584eb7f4441b9de1ad773f - sha256: 016340837fcfef57b351febcbe855eedf0c1f0ecfc910ed48c7fbd20535f9847 - category: main - optional: false -- name: python-dateutil - version: 2.8.2 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - six: '>=1.5' - url: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: dd999d1cc9f79e67dbb855c8924c7984 - sha256: 54d7785c7678166aa45adeaccfc1d2b8c3c799ca2dc05d4a82bb39b1968bd7da - category: main - optional: false -- name: pytorch - version: 1.13.1 - manager: conda - platform: linux-aarch64 - dependencies: - _openmp_mutex: '>=4.5' - libcblas: '>=3.9.0,<4.0a0' - libgcc-ng: '>=12' - liblapack: '>=3.9.0,<4.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - numpy: '>=1.20.3,<2.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - sleef: '>=3.5.1,<4.0a0' - typing_extensions: '' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/pytorch-1.13.1-cpu_py39h29ba9e4_1.conda - hash: - md5: 54300170feb6f983ecf31386e506d53b - sha256: cb2d4e012565e33cfd100803b1539d4131954ea08f5216c5e64ea1c7dbaab6df - category: main - optional: false -- name: rapidfuzz - version: 2.15.1 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/rapidfuzz-2.15.1-py39hdcdd789_0.conda - hash: - md5: eea6a5d0d263db5dbe142c257b4bc9c4 - sha256: 24be6720339673a8f90be9668ef13e6e254cb63f060530c6880556b8dee422e2 - category: main - optional: false -- name: tqdm - version: 4.65.0 - manager: conda - platform: linux-aarch64 - dependencies: - colorama: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.65.0-pyhd8ed1ab_1.conda - hash: - md5: ed792aff3acb977d09c7013358097f83 - sha256: b35f185a678109940d34f68ac5781c3cbda9b118b8d9886b8f68ab5be6afd4fc - category: main - optional: false -- name: typing-extensions - version: 4.5.0 - manager: conda - platform: linux-aarch64 - dependencies: - typing_extensions: 4.5.0 - url: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.5.0-hd8ed1ab_0.conda - hash: - md5: b3c594fde1a80a1fc3eb9cc4a5dfe392 - sha256: 6da5e15fa533620ae2e7aca9a7d16013eed3a73ac64c47d7c3bf3deec39b63b9 - category: main - optional: false -- name: werkzeug - version: 2.3.4 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.8' - markupsafe: '>=2.1.1' - url: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.4-pyhd8ed1ab_0.conda - hash: - md5: 23ddbe41ab0115bc0bfb75dcbf5de7cf - sha256: 2df1970270839b36e13a4ba7e4b393cfa95aa1d7438909aa8c3db14170ea207c - category: main - optional: false -- name: arrow - version: 1.2.3 - manager: conda - platform: linux-aarch64 - dependencies: - typing_extensions: '' - python: '>=3.6' - python-dateutil: '>=2.7.0' - url: https://conda.anaconda.org/conda-forge/noarch/arrow-1.2.3-pyhd8ed1ab_0.tar.bz2 - hash: - md5: fd1967c76eda3a3dd9e8e6cb7a15a028 - sha256: a0434c2770cf5b0ab5a33913c0b202b1521bc13f755b762d16a86b110425cdc2 - category: main - optional: false -- name: aws-crt-cpp - version: 0.20.2 - manager: conda - platform: linux-aarch64 - dependencies: - aws-c-auth: '>=0.6.27,<0.6.28.0a0' - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-event-stream: '>=0.2.20,<0.2.21.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-c-mqtt: '>=0.8.11,<0.8.12.0a0' - aws-c-s3: '>=0.3.0,<0.3.1.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/aws-crt-cpp-0.20.2-hfc1de21_0.conda - hash: - md5: 37bb42bddf892d2d117f6dd39b75883f - sha256: 33e041501f7c6cd4cd6746592675ff555015d97dde91f1564952b7df17e1e510 - category: main - optional: false -- name: bcrypt - version: 3.2.2 - manager: conda - platform: linux-aarch64 - dependencies: - cffi: '>=1.1' - libgcc-ng: '>=12' - pip: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - six: '>=1.4.1' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/bcrypt-3.2.2-py39h0fd3b05_1.tar.bz2 - hash: - md5: 9d8a973cbb292ee3ae5f4ed3d9f9031a - sha256: 7d136aecb38c3cea3773e05a5d38a7d64496c32e7aa8e464f42a4496b22ed1cd - category: main - optional: false -- name: brotlipy - version: 0.7.0 - manager: conda - platform: linux-aarch64 - dependencies: - cffi: '>=1.0.0' - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/brotlipy-0.7.0-py39h0fd3b05_1005.tar.bz2 - hash: - md5: 5d37ef329c084829d3ff5b172a08b8f9 - sha256: b62b8ba3688978d1344a4ea639b4ab28988fac5318a9842af4e7b9f5feb8374d - category: main - optional: false -- name: cleo - version: 2.0.1 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7,<4.0' - crashtest: '>=0.4.1,<0.5.0' - rapidfuzz: '>=2.2.0,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/cleo-2.0.1-pyhd8ed1ab_0.conda - hash: - md5: f1c5f2af6676cbe9206e191d1e70f661 - sha256: cf9bc4c9356ad8eb68512446eebc076386f2bfb8ca86626e8796621bc5a13082 - category: main - optional: false -- name: croniter - version: 1.3.15 - manager: conda - platform: linux-aarch64 - dependencies: - python-dateutil: '' - python: '>=2.6' - url: https://conda.anaconda.org/conda-forge/noarch/croniter-1.3.15-pyhd8ed1ab_0.conda - hash: - md5: 50197abb95aa7024eb0eb58fe5a51b07 - sha256: f8f58f6a50a5f63a35ee3bf6805e6dee10fe910f17a339da038967118c12c64f - category: main - optional: false -- name: cryptography - version: 41.0.0 - manager: conda - platform: linux-aarch64 - dependencies: - cffi: '>=1.12' - libgcc-ng: '>=12' - openssl: '>=3.1.1,<4.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/cryptography-41.0.0-py39hea2e5f7_0.conda - hash: - md5: 46655c48e9d8afaf0572f80036f19bd9 - sha256: 2c6f27d48992efe21beaa178e6313a000707611b397c85264990790330eff603 - category: main - optional: false -- name: dateutils - version: 0.6.12 - manager: conda - platform: linux-aarch64 - dependencies: - python-dateutil: '' - pytz: '' - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/dateutils-0.6.12-py_0.tar.bz2 - hash: - md5: acee371a07e9a38a7072e5a5f7054ead - sha256: fb554b32a8f880cafaff4e67c789965d97c41eb1a6cc9ab5a83c6b28b581d809 - category: main - optional: false -- name: flask - version: 2.3.2 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.8' - jinja2: '>=3.1.2' - click: '>=8.1.3' - importlib-metadata: '>=3.6.0' - itsdangerous: '>=2.1.2' - blinker: '>=1.6.2' - werkzeug: '>=2.3.3' - url: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.2-pyhd8ed1ab_0.conda - hash: - md5: 816d75d4c0f2e41b5765d17498c57a2e - sha256: f93246be286f2d0f93e85c4f08f9ce48f3eed875a79225e2ea119e70c0237421 - category: main - optional: false -- name: gitpython - version: 3.1.31 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - typing_extensions: '>=3.7.4.3' - gitdb: '>=4.0.1,<5' - url: https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.31-pyhd8ed1ab_0.conda - hash: - md5: f6e6b482110246a81c3f03e81c68752d - sha256: 77c531def610089bc190508fcf304cf96c085c5fe977ab8f7d7c1641769592ac - category: main - optional: false -- name: importlib-resources - version: 5.12.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - importlib_resources: '>=5.12.0,<5.12.1.0a0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-5.12.0-pyhd8ed1ab_0.conda - hash: - md5: 3544c818f0720c89eb16ae6940ab440b - sha256: 0675df2bf18e52d0ea2bc5e1009faac273f059361a0caf36c0e0edc7831098a9 - category: main - optional: false -- name: importlib_metadata - version: 6.6.0 - manager: conda - platform: linux-aarch64 - dependencies: - importlib-metadata: '>=6.6.0,<6.6.1.0a0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-6.6.0-hd8ed1ab_0.conda - hash: - md5: 3cbc9615f10a3d471532b83e4250b971 - sha256: 5de35d3c019d8a36e0a0deeb04a62689837bd68234a0a73a3355b860b442eca4 - category: main - optional: false -- name: jsonschema - version: 4.17.3 - manager: conda - platform: linux-aarch64 - dependencies: - typing_extensions: '' - importlib-metadata: '' - python: '>=3.7' - attrs: '>=17.4.0' - pyrsistent: '!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0' - importlib_resources: '>=1.4.0' - pkgutil-resolve-name: '>=1.3.10' - url: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.17.3-pyhd8ed1ab_0.conda - hash: - md5: 723268a468177cd44568eb8f794e0d80 - sha256: 4f68a23430d1afc5c9b41c46fbac0ade33c0bf57a293c646bfdd6dc65350eada - category: main - optional: false -- name: mako - version: 1.2.4 - manager: conda - platform: linux-aarch64 - dependencies: - importlib-metadata: '' - python: '>=3.6' - markupsafe: '>=0.9.2' - url: https://conda.anaconda.org/conda-forge/noarch/mako-1.2.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 0d072f0edc017b6318dbab701e053f94 - sha256: 559ed0d4c600d9827c1e9e0f2f3a50724bf2281b28a04e08f60de63f0da309a6 - category: main - optional: false -- name: markdown - version: 3.4.3 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - importlib-metadata: '>=4.4' - url: https://conda.anaconda.org/conda-forge/noarch/markdown-3.4.3-pyhd8ed1ab_0.conda - hash: - md5: 89ed59ad509c05db6f5f2f573d499bfe - sha256: e32ac2c95112caa8cd81f0cbc710f4f4903180a115c7260f03b010d5a0aa771b - category: main - optional: false -- name: pandas - version: 2.0.2 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.21.6,<2.0a0' - python: '>=3.9,<3.10.0a0' - python-dateutil: '>=2.8.1' - python-tzdata: '>=2022a' - python_abi: 3.9.* - pytz: '>=2020.1' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/pandas-2.0.2-py39h61474ce_0.conda - hash: - md5: e1012ea5cefc2e442460134d23f853ce - sha256: f546d6e305d84092355c4ed879df7220db9ccbe5fdd771f1fd91f51f64357819 - category: main - optional: false -- name: platformdirs - version: 3.5.1 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - typing-extensions: '>=4.5' - url: https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.5.1-pyhd8ed1ab_0.conda - hash: - md5: e2be672aece1f060adf7154f76531a35 - sha256: d7845c01a9ee5a224cc9242782befed7d12dc6aac1103650ec87917b20f3579e - category: main - optional: false -- name: poetry-core - version: 1.6.1 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - importlib-metadata: '>=1.7.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-core-1.6.1-pyhd8ed1ab_0.conda - hash: - md5: a6d1f61527c27fcc0165a6701a46b9f4 - sha256: 6f6a66476908a1c109e36c852d934eedceb07e0cbc44d3fcd87c6f39521b57be - category: main - optional: false -- name: pydantic - version: 1.10.8 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - typing-extensions: '>=4.2.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/pydantic-1.10.8-py39h898b7ef_0.conda - hash: - md5: 3b775a1fb04bb454bd11099a6598b5dd - sha256: 997a28555391cba43c3d45ba3baccb520ed2abc4a24971945c91f72bcc7f2a81 - category: main - optional: false -- name: pynacl - version: 1.5.0 - manager: conda - platform: linux-aarch64 - dependencies: - cffi: '>=1.4.1' - libgcc-ng: '>=12' - libsodium: '>=1.0.18,<1.0.19.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - six: '' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/pynacl-1.5.0-py39h0fd3b05_2.tar.bz2 - hash: - md5: 576f57f685c7de6cb06a55e5f0e76a28 - sha256: 85a17e221427a8169907db4eb3642207d56bfb0be79bd8009fda0d168943b8b5 - category: main - optional: false -- name: python-build - version: 0.10.0 - manager: conda - platform: linux-aarch64 - dependencies: - colorama: '' - pyproject_hooks: '' - python: '>=3.7' - tomli: '>=1.1.0' - packaging: '>=19.0' - importlib-metadata: '>=0.22' - url: https://conda.anaconda.org/conda-forge/noarch/python-build-0.10.0-pyhd8ed1ab_1.conda - hash: - md5: 0ab47ce574f6a8bcb9f2076436e7fedb - sha256: 4c2cd519c85aa8b8e584723ca5f452aa5941d18374470adebfe73bf30fd27573 - category: main - optional: false -- name: rich - version: 13.4.1 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7.0' - typing_extensions: '>=4.0.0,<5.0.0' - markdown-it-py: '>=2.2.0,<3.0.0' - pygments: '>=2.13.0,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/rich-13.4.1-pyhd8ed1ab_0.conda - hash: - md5: c3bcbe0d086f15e5918568d3865e4dbf - sha256: 312f2628e06a591096a851bf678833fe670ecb16e9b15517ce8e03d7c9d9e600 - category: main - optional: false -- name: sqlalchemy - version: 2.0.15 - manager: conda - platform: linux-aarch64 - dependencies: - greenlet: '!=0.4.17' - libgcc-ng: '>=12' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - typing-extensions: '>=4.2.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/sqlalchemy-2.0.15-py39h7cc1d5f_0.conda - hash: - md5: 6ed86d2a2e94c3673efcb3de74c5779e - sha256: adad2928918f339c9f2273c54fe9c3300096d837ce7ef1e834d0c47948eeb79d - category: main - optional: false -- name: starlette - version: 0.22.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - typing_extensions: '>=3.10.0' - anyio: <5,>=3.4.0 - url: https://conda.anaconda.org/conda-forge/noarch/starlette-0.22.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 49d5cdcc16c691e4ad9355c81f004c3e - sha256: 1441dd55c037184b7d2c1e1dbf60beafb1f92fdc13cabf78a85e12825a55269b - category: main - optional: false -- name: torchmetrics - version: 0.11.4 - manager: conda - platform: linux-aarch64 - dependencies: - setuptools: '' - packaging: '' - python: '>=3.7' - pytorch: '>=1.8.1' - url: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-0.11.4-pyhd8ed1ab_0.conda - hash: - md5: 480cb2b6d502003e937d9e4326bc398f - sha256: 3927eae14903c76679d5151af39680e7d42c35e0bdaa5041f577fc40f0619be8 - category: main - optional: false -- name: uvicorn - version: 0.22.0 - manager: conda - platform: linux-aarch64 - dependencies: - click: '>=7.0' - h11: '>=0.8' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/uvicorn-0.22.0-py39ha65689a_0.conda - hash: - md5: a85639f3065b80faa17e686b3b05248c - sha256: fd75f7323aa47e427166cbc82c0559a57bdeef111befb96f50416e5f65c650ce - category: main - optional: false -- name: wcwidth - version: 0.2.6 - manager: conda - platform: linux-aarch64 - dependencies: - backports.functools_lru_cache: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.6-pyhd8ed1ab_0.conda - hash: - md5: 078979d33523cb477bd1916ce41aacc9 - sha256: c1bd0ad7d854cae56977b7915ac2b78b652fa5f7ec1e9fc21e7fdb30cf4519b1 - category: main - optional: false -- name: alembic - version: 1.11.1 - manager: conda - platform: linux-aarch64 - dependencies: - importlib-metadata: '' - importlib_resources: '' - mako: '' - python: '>=3.7' - sqlalchemy: '>=1.3.0' - typing-extensions: '>=4' - url: https://conda.anaconda.org/conda-forge/noarch/alembic-1.11.1-pyhd8ed1ab_0.conda - hash: - md5: 6a55e123397b42b79c48b31d1b7a91b8 - sha256: 065dd1b38ebe3a0d14f45549f63cce55125052057db565be153cdd73aa2a7c8d - category: main - optional: false -- name: aws-sdk-cpp - version: 1.10.57 - manager: conda - platform: linux-aarch64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-event-stream: '>=0.2.20,<0.2.21.0a0' - aws-crt-cpp: '>=0.20.2,<0.20.3.0a0' - libcurl: '>=8.1.1,<9.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/aws-sdk-cpp-1.10.57-h42fcb7c_13.conda - hash: - md5: 1cea04fd4bb39feb6c073a53ab0fc07d - sha256: eac3526e48457f34c2505fc3d0394916f544bd9893682864fcb3b906fc9f139f - category: main - optional: false -- name: blessed - version: 1.19.1 - manager: conda - platform: linux-aarch64 - dependencies: - __unix: '' - python: '>=3.8' - six: '>=1.9.0' - wcwidth: '>=0.1.4' - url: https://conda.anaconda.org/conda-forge/noarch/blessed-1.19.1-pyhe4f9e05_2.tar.bz2 - hash: - md5: 65486376a55a80933e5dd95681ddd8b8 - sha256: 9d5b1f751adfe6d77fa8a088417a3aed716a1f727c0fd0230195246362b9d562 - category: main - optional: false -- name: fastapi - version: 0.88.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - pydantic: '>=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0' - starlette: 0.22.0.* - url: https://conda.anaconda.org/conda-forge/noarch/fastapi-0.88.0-pyhd8ed1ab_0.conda - hash: - md5: 0bd34a2b460d02e2fbf88ca5d9d3e55d - sha256: 660b356b8d4f3b99b6294c638637699003b0533c0ecab9389c56367b4bfe5c59 - category: main - optional: false -- name: matplotlib-base - version: 3.7.1 - manager: conda - platform: linux-aarch64 - dependencies: - certifi: '>=2020.06.20' - contourpy: '>=1.0.1' - cycler: '>=0.10' - fonttools: '>=4.22.0' - freetype: '>=2.12.1,<3.0a0' - importlib-resources: '>=3.2.0' - kiwisolver: '>=1.0.1' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.20.3,<2.0a0' - packaging: '>=20.0' - pillow: '>=6.2.0' - pyparsing: '>=2.3.1' - python: '>=3.9,<3.10.0a0' - python-dateutil: '>=2.7' - python_abi: 3.9.* - tk: '>=8.6.12,<8.7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/matplotlib-base-3.7.1-py39h2983639_0.conda - hash: - md5: 6ca14f00270585ac4ff20b04106817ee - sha256: c17647ef462951b54a68249cf17f20f1fb4ddd1f9445cd63842888153dcd8642 - category: main - optional: false -- name: oauthlib - version: 3.2.2 - manager: conda - platform: linux-aarch64 - dependencies: - cryptography: '' - blinker: '' - python: '>=3.6' - pyjwt: '>=1.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/oauthlib-3.2.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 8f882b197fd9c4941a787926baea4868 - sha256: 0cfd5146a91d3974f4abfc2a45de890371d510a77238fe553e036ec8c031dc5b - category: main - optional: false -- name: paramiko - version: 3.2.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - cryptography: '>=3.3' - bcrypt: '>=3.2' - pynacl: '>=1.5' - url: https://conda.anaconda.org/conda-forge/noarch/paramiko-3.2.0-pyhd8ed1ab_0.conda - hash: - md5: f212c7eb95e909df4795297f73690993 - sha256: e425a03e5e2ef2ec5a78711686c59cfceeeeec3a98165fbc7d186bd6a5cb78de - category: main - optional: false -- name: poetry-plugin-export - version: 1.4.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7,<4.0' - poetry-core: '>=1.6.0,<2.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-plugin-export-1.4.0-pyhd8ed1ab_0.conda - hash: - md5: 00893c7ea4f9f7620706e0aa94c01b6e - sha256: 54478b283b5967a85ee5da717f1512d7ec97eb07c7b52d1f2ad3cb080d56c0ac - category: main - optional: false -- name: prometheus_flask_exporter - version: 0.22.4 - manager: conda - platform: linux-aarch64 - dependencies: - flask: '' - prometheus_client: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/prometheus_flask_exporter-0.22.4-pyhd8ed1ab_0.conda - hash: - md5: 43acea130cafd18740b73fa4c226c9f7 - sha256: be83619ef5964713cd298d0fb86eddd99b159e5fba3d0f91d624ce5d7c3890e0 - category: main - optional: false -- name: pyopenssl - version: 23.2.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - cryptography: '>=38.0.0,<42,!=40.0.0,!=40.0.1' - url: https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.2.0-pyhd8ed1ab_1.conda - hash: - md5: 34f7d568bf59d18e3fef8c405cbece21 - sha256: 4daea3dc896987cc1334956fccfc0ed738663a84ad0c1d3f576a7a7936091534 - category: main - optional: false -- name: secretstorage - version: 3.3.3 - manager: conda - platform: linux-aarch64 - dependencies: - cryptography: '' - dbus: '' - jeepney: '>=0.6' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/secretstorage-3.3.3-py39ha65689a_1.tar.bz2 - hash: - md5: 70dcdaa205b584744c82a872234fdf41 - sha256: 2beeb242b749f213eb9f3acefe0d619b7a6b28a45ebb90ac84985241fc5c5f50 - category: main - optional: false -- name: starsessions - version: 1.3.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6.2' - itsdangerous: '>=2.0.1' - starlette: '>=0' - url: https://conda.anaconda.org/conda-forge/noarch/starsessions-1.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 667d08040a85d7ea1c6d4af2290f96c4 - sha256: 4a500ac0a9fe56cee7958d6d0f6530272c43ee4c16c52600001decb39fe3cd59 - category: main - optional: false -- name: typer - version: 0.9.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - typing-extensions: '>=3.7.4.3' - colorama: '>=0.4.3,<0.5.0' - shellingham: '>=1.3.0,<2.0.0' - click: '>=7.1.1,<9' - rich: '>=10.11.0,<14.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/typer-0.9.0-pyhd8ed1ab_0.conda - hash: - md5: 5030a13b2fe5e143d5956d4943d3018f - sha256: d395e1e92281abb13e043220ecf8ea973ada8d38a1e8c683df14f46541c64bd2 - category: main - optional: false -- name: virtualenv - version: 20.23.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.8' - distlib: <1,>=0.3.6 - filelock: <4,>=3.11 - platformdirs: <4,>=3.2 - url: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.23.0-pyhd8ed1ab_0.conda - hash: - md5: a920e114c4c2ced2280e266da65ab5e6 - sha256: 13d667887ea08b6d1fe2eb09d2d737f9af7343735d3bfa5ffaa3f67eec8eaff7 - category: main - optional: false -- name: keyring - version: 23.13.1 - manager: conda - platform: linux-aarch64 - dependencies: - importlib_metadata: '>=4.11.4' - jaraco.classes: '' - jeepney: '>=0.4.2' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - secretstorage: '>=3.2' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/keyring-23.13.1-py39h4420490_0.conda - hash: - md5: 4d7b2fc7cdda30b633b9e4e777f8f8ab - sha256: 9c1f02a63b4edbda44a23f3b0551240206175264776054736bffd4ba87a4fb24 - category: main - optional: false -- name: libarrow - version: 11.0.0 - manager: conda - platform: linux-aarch64 - dependencies: - aws-crt-cpp: '>=0.20.2,<0.20.3.0a0' - aws-sdk-cpp: '>=1.10.57,<1.10.58.0a0' - bzip2: '>=1.0.8,<2.0a0' - c-ares: '>=1.19.1,<2.0a0' - gflags: '>=2.2.2,<2.3.0a0' - glog: '>=0.6.0,<0.7.0a0' - libabseil: '>=20230125.2,<20230126.0a0' - libbrotlicommon: '>=1.0.9,<1.1.0a0' - libbrotlidec: '>=1.0.9,<1.1.0a0' - libbrotlienc: '>=1.0.9,<1.1.0a0' - libevent: '>=2.1.12,<2.1.13.0a0' - libgcc-ng: '>=12' - libgoogle-cloud: '>=2.10.1,<2.10.2.0a0' - libgrpc: '>=1.54.2,<1.55.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - libstdcxx-ng: '>=12' - libthrift: '>=0.18.1,<0.18.2.0a0' - libutf8proc: '>=2.8.0,<3.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - openssl: '>=3.1.0,<4.0a0' - orc: '>=1.8.3,<1.8.4.0a0' - re2: '>=2023.3.2,<2023.3.3.0a0' - snappy: '>=1.1.10,<2.0a0' - ucx: '>=1.14.0,<1.15.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/libarrow-11.0.0-h901c870_21_cpu.conda - hash: - md5: a02dc2f9a193c29c5a0c7279c6eed912 - sha256: 6be68e489b78df30973ae493876a3057d3977e0d6564fba94a696739a4f75d05 - category: main - optional: false -- name: urllib3 - version: 1.26.15 - manager: conda - platform: linux-aarch64 - dependencies: - certifi: '' - python: <4.0 - idna: '>=2.0.0' - pyopenssl: '>=0.14' - pysocks: '>=1.5.6,<2.0,!=1.5.7' - cryptography: '>=1.3.4' - brotlipy: '>=0.6.0' - url: https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.15-pyhd8ed1ab_0.conda - hash: - md5: 27db656619a55d727eaf5a6ece3d2fd6 - sha256: 213bdf6c3a5d721fa83b45d527d3ecd340f9547c0d6bbd0b8d9d746ec9a1fb4b - category: main - optional: false -- name: arrow-cpp - version: 11.0.0 - manager: conda - platform: linux-aarch64 - dependencies: - libarrow: 11.0.0 - url: https://conda.anaconda.org/conda-forge/linux-aarch64/arrow-cpp-11.0.0-h8af1aa0_21_cpu.conda - hash: - md5: 260fe418bf271e32630b2202f87326f1 - sha256: 69ec8b68807eaa9680386ccbae9ebd2776637c3e4b183fdbbcb22600e0dd2b0d - category: main - optional: false -- name: dulwich - version: 0.21.5 - manager: conda - platform: linux-aarch64 - dependencies: - certifi: '' - cryptography: '>=1.3.4' - idna: '>=2.0.0' - libgcc-ng: '>=12' - pyopenssl: '>=0.14' - pysocks: '>=1.5.6,<2.0,!=1.5.7' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - urllib3: '' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/dulwich-0.21.5-py39h898b7ef_0.conda - hash: - md5: d4f6d9d75b2f118aa644d2de19b7bd36 - sha256: 2237f769437d16b809fbf3ae3d0546a8928416043d427f03faae1e498e648d48 - category: main - optional: false -- name: requests - version: 2.31.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - idna: '>=2.5,<4' - certifi: '>=2017.4.17' - charset-normalizer: '>=2,<4' - urllib3: '>=1.21.1,<3' - url: https://conda.anaconda.org/conda-forge/noarch/requests-2.31.0-pyhd8ed1ab_0.conda - hash: - md5: a30144e4156cdbb236f99ebb49828f8b - sha256: 9f629d6fd3c8ac5f2a198639fe7af87c4db2ac9235279164bfe0fcb49d8c4bad - category: main - optional: false -- name: cachecontrol - version: 0.12.11 - manager: conda - platform: linux-aarch64 - dependencies: - requests: '' - python: '>=3.6' - msgpack-python: '>=0.5.2' - url: https://conda.anaconda.org/conda-forge/noarch/cachecontrol-0.12.11-pyhd8ed1ab_1.conda - hash: - md5: e8f0410e0aa03342304357c5cc3bb75d - sha256: 466ce7c155be90a5c903052eba391759ae88eb65f2bb06b0cc1c9d09c4311800 - category: main - optional: false -- name: databricks-cli - version: 0.17.7 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - click: '>=7.0' - six: '>=1.10.0' - pyjwt: '>=1.7.0' - tabulate: '>=0.7.7' - requests: '>=2.17.3' - configparser: '>=0.3.5' - oauthlib: '>=3.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/databricks-cli-0.17.7-pyhd8ed1ab_0.conda - hash: - md5: cb44b4e93848f13dce352c422285ac45 - sha256: 1c2ec6c6125bc1c1d100b75e1f5dfda09c56cd516157d2d5b01c1aa69fdd0dbd - category: main - optional: false -- name: docker-py - version: 6.1.0 - manager: conda - platform: linux-aarch64 - dependencies: - pywin32-on-windows: '' - python: '>=3.7' - requests: '>=2.26.0' - urllib3: '>=1.26.0' - websocket-client: '>=0.32.0' - packaging: '>=14.0' - paramiko: '>=2.4.3' - url: https://conda.anaconda.org/conda-forge/noarch/docker-py-6.1.0-pyhd8ed1ab_0.conda - hash: - md5: 543336c6aa9516cfb29c51d5c162b177 - sha256: 5e01e15e20ee573c99b530633a0d5c71fd515e4ac6d2f5f5f57baece8b915cc3 - category: main - optional: false -- name: lightning-cloud - version: 0.5.36 - manager: conda - platform: linux-aarch64 - dependencies: - requests: '' - six: '' - click: '' - rich: '' - pyjwt: '' - urllib3: '' - fastapi: '' - websocket-client: '' - python-multipart: '' - uvicorn: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/lightning-cloud-0.5.36-pyhd8ed1ab_0.conda - hash: - md5: fd99cc369aa3c6c66493d4d278338af5 - sha256: 2047f4dcd4531f6cd2f87a80fef0d265f663e011931af746b2725f7d567ce016 - category: main - optional: false -- name: parquet-cpp - version: 1.5.1 - manager: conda - platform: linux-aarch64 - dependencies: - arrow-cpp: '>=0.11.0' - url: https://conda.anaconda.org/conda-forge/noarch/parquet-cpp-1.5.1-2.tar.bz2 - hash: - md5: 79a5f78c42817594ae016a7896521a97 - sha256: 15e50657515b791734ba045da5135377404ca37c518b2066b9c6451c65cd732e - category: main - optional: false -- name: pooch - version: 1.7.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.7' - packaging: '>=20.0' - requests: '>=2.19.0' - platformdirs: '>=2.5.0' - url: https://conda.anaconda.org/conda-forge/noarch/pooch-1.7.0-pyha770c72_3.conda - hash: - md5: 5936894aade8240c867d292aa0d980c6 - sha256: 64e4d633803df2e36fd141d9bf269568fbe179a313248e1dac4d364c02debdef - category: main - optional: false -- name: pytorch-lightning - version: 2.0.2 - manager: conda - platform: linux-aarch64 - dependencies: - requests: '' - python: '>=3.8' - pyyaml: '>=5.4' - numpy: '>=1.17.2' - packaging: '>=17.1' - torchmetrics: '>=0.7.0' - tqdm: '>=4.57.0' - typing_extensions: '>=4.0.0' - pytorch: '>=1.11.0' - fsspec: '>2021.06.0' - lightning-utilities: '>=0.7.0' - url: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.0.2-pyhd8ed1ab_0.conda - hash: - md5: abd4916f586ae33b56d1e2b44a7990aa - sha256: 9332cb927d6c587ed5b23628208ebb4479288244f4388bd4cb9745df115cca25 - category: main - optional: false -- name: querystring_parser - version: 1.2.4 - manager: conda - platform: linux-aarch64 - dependencies: - python: '' - requests: '' - six: '' - url: https://conda.anaconda.org/conda-forge/noarch/querystring_parser-1.2.4-py_0.tar.bz2 - hash: - md5: 0ebdca9b753c2e082e5b5ad06aa76b41 - sha256: 06977a9af6d8605fb6068d8af6bb9c1cb565f8f5e15aa6cf0fb94109d4148b54 - category: main - optional: false -- name: requests-toolbelt - version: 1.0.0 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - requests: '>=2.0.1,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-1.0.0-pyhd8ed1ab_0.conda - hash: - md5: 99c98318c8646b08cc764f90ce98906e - sha256: 20eaefc5dba74ff6c31e537533dde59b5b20f69e74df49dff19d43be59785fa3 - category: main - optional: false -- name: torchvision - version: 0.14.1 - manager: conda - platform: linux-aarch64 - dependencies: - libgcc-ng: '>=12' - libjpeg-turbo: '>=2.1.5.1,<3.0a0' - libpng: '>=1.6.39,<1.7.0a0' - libstdcxx-ng: '>=12' - numpy: '>=1.20.3,<2.0a0' - pillow: '>=5.3.0,!=8.3.0,!=8.3.1' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - pytorch: '>=1.13.1,<1.14.0a0' - requests: '' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/torchvision-0.14.1-cpu_py39h06df7d7_1.conda - hash: - md5: 96c2942c18925a8bf853b4854506b07b - sha256: 89aabc41eb8ef7d3d831f591f1c862def14d8c571d0dd85fea813c2f0ab6ba80 - category: main - optional: false -- name: cachecontrol-with-filecache - version: 0.12.11 - manager: conda - platform: linux-aarch64 - dependencies: - python: '>=3.6' - lockfile: '>=0.9' - cachecontrol: 0.12.11 - url: https://conda.anaconda.org/conda-forge/noarch/cachecontrol-with-filecache-0.12.11-pyhd8ed1ab_1.conda - hash: - md5: 9df660456c0076d27b802448f7ede78f - sha256: 81c483fc92656873eb5a7ba657b208c34186556d942a9cebc1f7771e565b95b7 - category: main - optional: false -- name: pyarrow - version: 11.0.0 - manager: conda - platform: linux-aarch64 - dependencies: - gflags: '>=2.2.2,<2.3.0a0' - libarrow: 11.0.0 - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.21.6,<2.0a0' - parquet-cpp: 1.5.1.* - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/pyarrow-11.0.0-py39he66044c_21_cpu.conda - hash: - md5: 9a210c27467e9bb4c917b38ca002619f - sha256: c61ec76d5f2a5c1f569423eade3c98f84c79d87b43ea153fa7b540fd7447ed8a - category: main - optional: false -- name: scipy - version: 1.10.1 - manager: conda - platform: linux-aarch64 - dependencies: - libblas: '>=3.9.0,<4.0a0' - libcblas: '>=3.9.0,<4.0a0' - libgcc-ng: '>=12' - libgfortran-ng: '' - libgfortran5: '>=12.2.0' - liblapack: '>=3.9.0,<4.0a0' - libstdcxx-ng: '>=12' - numpy: '>=1.21.6,<2.0a0' - pooch: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/linux-aarch64/scipy-1.10.1-py39hf88902c_3.conda - hash: - md5: 032bb28beb0c37c48b6e33dadc18f0ec - sha256: 86fb9e4dce83a3a3fc334322e212c6e0eae7082864d1609ef0bbdd8f85532f73 - category: main - optional: false -- name: poetry - version: 1.5.1 - manager: conda - platform: linux-aarch64 - dependencies: - __linux: '' - tomli: '>=2.0.1,<3.0.0' - packaging: '>=20.4' - importlib-metadata: '>=4.4' - python: '>=3.7.0,<4.0.0' - urllib3: '>=1.26.0,<2.0.0' - crashtest: '>=0.4.1,<0.5.0' - requests: '>=2.18,<3.0' - pexpect: '>=4.7.0,<5.0.0' - cleo: '>=2.0.0,<3.0.0' - filelock: '>=3.8.0,<4.0.0' - jsonschema: '>=4.10.0,<5.0.0' - keyring: '>=23.9.0,<24.0.0' - trove-classifiers: '>=2022.5.19' - lockfile: '>=0.12.2,<0.13.0' - backports.cached-property: '>=1.0.2,<2.0.0' - dulwich: '>=0.21.2,<0.22.0' - pkginfo: '>=1.9.4,<2.0' - pyproject_hooks: '>=1.0.0,<2.0.0' - python-build: '>=0.10.0,<0.11.0' - python-installer: '>=0.7.0,<0.8.0' - platformdirs: '>=3.0.0,<4.0.0' - requests-toolbelt: '>=0.9.1,<2' - tomlkit: '>=0.11.4,<1.0.0' - virtualenv: '>=20.22.0,<21.0.0' - cachecontrol-with-filecache: '>=0.12.9,<0.13.0' - html5lib: '>=1.0.0,<2.0.0' - poetry-core: 1.6.1.* - poetry-plugin-export: '>=1.4.0,<2.0.0' - shellingham: '>=1.5.0,<2.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-1.5.1-linux_pyhd8ed1ab_0.conda - hash: - md5: 7aae42823198731808dd8e7a9745547f - sha256: 754fb55e83fe06366f4532fbb25344bc9c7d46ac71d2a1c35209e818f7fa9d10 - category: main - optional: false -- name: scikit-learn - version: 1.2.2 - manager: conda - platform: linux-aarch64 - dependencies: - _openmp_mutex: '>=4.5' - joblib: '>=1.1.1' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.21.6,<2.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - scipy: '' - threadpoolctl: '>=2.0.0' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/scikit-learn-1.2.2-py39hbd2e3eb_2.conda - hash: - md5: 7b5b93dc12c22b27414429010017b7b4 - sha256: 1044922ee7207cb611ed5aa8668b5391a59100334b432de30415edbc3747b2cf - category: main - optional: false -- name: inquirer - version: 3.1.3 - manager: conda - platform: linux-aarch64 - dependencies: - poetry: '' - python: '>=3.7' - blessed: '>=1.19.0' - python-editor: '>=1.0.4' - readchar: '>=2.0.1' - url: https://conda.anaconda.org/conda-forge/noarch/inquirer-3.1.3-pyhd8ed1ab_0.conda - hash: - md5: 0d8bc31361e09dc50555465284e10880 - sha256: da912877ac6e0795490834c96167e93a1eda89290ef8de63502740ef738d4435 - category: main - optional: false -- name: mlflow - version: 2.3.2 - manager: conda - platform: linux-aarch64 - dependencies: - alembic: <2,!=1.10 - click: '>=7.0,<9' - cloudpickle: <3 - databricks-cli: '>=0.8.7,<1' - docker-py: '>=4.0.0,<7' - entrypoints: <1 - flask: <3 - gitpython: '>=2.1.0,<4' - gunicorn: <21 - importlib-metadata: <7,>=3.7.0,!=4.7.0 - jinja2: <4,>=2.11 - markdown: <4,>=3.3 - matplotlib-base: <4 - numpy: <2 - openssl: '' - packaging: <24 - pandas: <3 - prometheus_flask_exporter: <1 - protobuf: '>=3.12.0,<5' - pyarrow: <12,>=4.0.0 - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - pytz: <2024 - pyyaml: '>=5.1,<7' - querystring_parser: <2 - requests: '>=2.17.3,<3' - scikit-learn: <2 - scipy: <2 - sqlalchemy: '>=1.4.0,<3' - sqlparse: '>=0.4.0,<1' - url: https://conda.anaconda.org/conda-forge/linux-aarch64/mlflow-2.3.2-py39h4b1475f_1.conda - hash: - md5: 604881e91f28793e3d3240d5260abbe0 - sha256: d45b38fec66f6f7e2681d0ba9f7cc3dd4d0a7f90cbd6e136f857a0533a4f022a - category: main - optional: false -- name: lightning - version: 2.0.0 - manager: conda - platform: linux-aarch64 - dependencies: - packaging: '' - pytorch-lightning: '' - python-multipart: '' - python: '>=3.8' - arrow: <3.0,>=1.2.0 - beautifulsoup4: <6.0,>=4.8.0 - click: <10.0 - croniter: <1.4.0,>=1.3.0 - dateutils: <2.0 - deepdiff: <8.0,>=5.7.0 - fsspec: <2024.0,>=2022.5.0 - inquirer: <5.0,>=2.10.0 - jinja2: <5.0 - lightning-utilities: <2.0,>=0.7.0 - numpy: <3.0,>=1.17.2 - psutil: <7.0 - pytorch: <4.0,>=1.11.0 - pyyaml: <8.0 - requests: <4.0 - rich: <15.0,>=12.3.0 - starsessions: <2.0,>=1.2.1 - torchmetrics: <2.0,>=0.7.0 - tqdm: <6.0,>=4.57.0 - traitlets: <7.0,>=5.3.0 - typing-extensions: <6.0,>=4.0.0 - urllib3: <3.0 - uvicorn: <2.0 - websocket-client: <3.0 - websockets: <12.0 - fastapi: <0.89.0 - lightning-cloud: '>=0.5.31' - pydantic: <3.0 - starlette: <2.0 - url: https://conda.anaconda.org/conda-forge/noarch/lightning-2.0.0-pyhd8ed1ab_0.conda - hash: - md5: 5c38b552a8b1853c39738d9a9f090ffc - sha256: 4b6bf3a963ea91f81de4947ad8a0686bb8cf868f63a5a125088938fc9551ace1 - category: main - optional: false -- name: aws-c-common - version: 0.8.19 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/aws-c-common-0.8.19-h0dc2134_0.conda - hash: - md5: 4e75cc03707e88235e7874b750d6c7a2 - sha256: 3fba105fdc9095ff9f2cfe34a1967b01e44ad3d3df323d49ab1f870be98db587 - category: main - optional: false -- name: bzip2 - version: 1.0.8 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h0d85af4_4.tar.bz2 - hash: - md5: 37edc4e6304ca87316e160f5ca0bd1b5 - sha256: 60ba4c64f5d0afca0d283c7addba577d3e2efc0db86002808dadb0498661b2f2 - category: main - optional: false -- name: c-ares - version: 1.19.1 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/c-ares-1.19.1-h0dc2134_0.conda - hash: - md5: b3e62631b4e1b9801477523ce1d6f355 - sha256: 1de09d540facc3833e3f0a280ae987859f310f535726eff66d6f4a66045bd32c - category: main - optional: false -- name: ca-certificates - version: 2023.5.7 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2023.5.7-h8857fd0_0.conda - hash: - md5: b704e4b79ba0d887c4870b7b09d6a4df - sha256: a06c9c788de81da3a3868ac56781680cc1fc50a0b5a545d4453818975c141b2c - category: main - optional: false -- name: jpeg - version: 9e - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/jpeg-9e-hb7f2c08_3.conda - hash: - md5: 6b55131ae9445ef38746dc6b080acda9 - sha256: 1ef5f9b4d9817820224c92b016da210b1356250d7272e16901c547e156b3e615 - category: main - optional: false -- name: lame - version: '3.100' - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/lame-3.100-hb7f2c08_1003.tar.bz2 - hash: - md5: 3342b33c9a0921b22b767ed68ee25861 - sha256: 0f943b08abb4c748d73207594321b53bad47eea3e7d06b6078e0f6c59ce6771e - category: main - optional: false -- name: libbrotlicommon - version: 1.0.9 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.0.9-hb7f2c08_8.tar.bz2 - hash: - md5: 37157d273eaf3bc7d6862104161d9ec9 - sha256: c983101653f5bffea605c4423d84fd5ca28ee36b290cdb6207ec246e293f7d94 - category: main - optional: false -- name: libcxx - version: 16.0.4 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/libcxx-16.0.4-hd57cbcb_0.conda - hash: - md5: 195236e2eeb3b108e3b8eee47ad074fd - sha256: c4c3d6636d1142e81b3952a84f051cea4825aec4451fed551ff867bce5c7ea91 - category: main - optional: false -- name: libdeflate - version: '1.17' - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.17-hac1461d_0.conda - hash: - md5: e3894420cf8b6abbf6c4d3d9742fbb4a - sha256: b322e190fd6fe631e1f4836ef99cbfb8352c03c30b51cb5baa216f7c9124d82e - category: main - optional: false -- name: libev - version: '4.33' - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/libev-4.33-haf1e3a3_1.tar.bz2 - hash: - md5: 79dc2be110b2a3d1e97ec21f691c50ad - sha256: c4154d424431898d84d6afb8b32e3ba749fe5d270d322bb0af74571a3cb09c6b - category: main - optional: false -- name: libffi - version: 3.4.2 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 - hash: - md5: ccb34fb14960ad8b125962d3d79b31a9 - sha256: 7a2d27a936ceee6942ea4d397f9c7d136f12549d86f7617e8b6bad51e01a941f - category: main - optional: false -- name: libiconv - version: '1.17' - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.17-hac89ed1_0.tar.bz2 - hash: - md5: 691d103d11180486154af49c037b7ed9 - sha256: 4a3294037d595754f7da7c11a41f3922f995aaa333f3cb66f02d8afa032a7bc2 - category: main - optional: false -- name: libsodium - version: 1.0.18 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.18-hbcb3906_1.tar.bz2 - hash: - md5: 24632c09ed931af617fe6d5292919cab - sha256: 2da45f14e3d383b4b9e3a8bacc95cd2832aac2dbf9fbc70d255d384a310c5660 - category: main - optional: false -- name: libutf8proc - version: 2.8.0 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/libutf8proc-2.8.0-hb7f2c08_0.tar.bz2 - hash: - md5: db98dc3e58cbc11583180609c429c17d - sha256: 55a7f96b2802e94def207fdfe92bc52c24d705d139bb6cdb3d936cbe85e1c505 - category: main - optional: false -- name: libwebp-base - version: 1.3.0 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/libwebp-base-1.3.0-hb7f2c08_0.conda - hash: - md5: 18981e4c840126d6118d8952485fea51 - sha256: 5ed0b7f127f578ddd28e3af86af278df8d5341416935a09ae772a57579cbb11b - category: main - optional: false -- name: libzlib - version: 1.2.13 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.13-hfd90126_4.tar.bz2 - hash: - md5: 35eb3fce8d51ed3c1fd4122bad48250b - sha256: 0d954350222cc12666a1f4852dbc9bcf4904d8e467d29505f2b04ded6518f890 - category: main - optional: false -- name: llvm-openmp - version: 16.0.4 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-16.0.4-hff08bdf_0.conda - hash: - md5: 2957c70d4309ee9ec5e3afbb1e446986 - sha256: 57ea255d7e49b50cd589fdf89d3181f159cb48d4f88f8936a75a77cb1139e12b - category: main - optional: false -- name: mkl-include - version: 2022.1.0 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/mkl-include-2022.1.0-h6bab518_928.tar.bz2 - hash: - md5: 67f8511a5eaf693a202486f74035b3f7 - sha256: 7deb982c0ce30fdec9b89e4f86ef97bec8058e4cc31f5e1deee67a40f634c40b - category: main - optional: false -- name: ncurses - version: '6.3' - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.3-h96cf925_1.tar.bz2 - hash: - md5: 76217ebfbb163ff2770a261f955a5861 - sha256: 9794a23d03586c99cac49d4ae3d5337faaa6bfc256b31d2662ff4ad5972be143 - category: main - optional: false -- name: nettle - version: '3.6' - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/nettle-3.6-hedd7734_0.tar.bz2 - hash: - md5: 9c9d5d69c39596b22a7495aef3310a3d - sha256: 9f128e3b2463442965c38b48029008828e18d5fd7c0e9c6df49d8d94db24a524 - category: main - optional: false -- name: pthread-stubs - version: '0.4' - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/pthread-stubs-0.4-hc929b4f_1001.tar.bz2 - hash: - md5: addd19059de62181cd11ae8f4ef26084 - sha256: 6e3900bb241bcdec513d4e7180fe9a19186c1a38f0b4080ed619d26014222c53 - category: main - optional: false -- name: python_abi - version: '3.9' - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/python_abi-3.9-3_cp39.conda - hash: - md5: 021e2768e8eaf24ee8e25aec64d305a1 - sha256: b02e179f015b042510da8ba256c86f5cfb34058a96ec1c548f33f9f8bcdbb78c - category: main - optional: false -- name: pytorch-mutex - version: '1.0' - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/pytorch/noarch/pytorch-mutex-1.0-cpu.tar.bz2 - hash: - md5: 49565ed726991fd28d08a39885caa88d - sha256: d48c964188ca49660d750cffd73698d217cf94e694cd51987f9f186425435e76 - category: main - optional: false -- name: tzdata - version: 2023c - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2023c-h71feb2d_0.conda - hash: - md5: 939e3e74d8be4dac89ce83b20de2492a - sha256: 0449138224adfa125b220154408419ec37c06b0b49f63c5954724325903ecf55 - category: main - optional: false -- name: xorg-libxau - version: 1.0.11 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxau-1.0.11-h0dc2134_0.conda - hash: - md5: 9566b4c29274125b0266d0177b5eb97b - sha256: 8a2e398c4f06f10c64e69f56bcf3ddfa30b432201446a0893505e735b346619a - category: main - optional: false -- name: xorg-libxdmcp - version: 1.1.3 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxdmcp-1.1.3-h35c211d_0.tar.bz2 - hash: - md5: 86ac76d6bf1cbb9621943eb3bd9ae36e - sha256: 485421c16f03a01b8ed09984e0b2ababdbb3527e1abf354ff7646f8329be905f - category: main - optional: false -- name: xz - version: 5.2.6 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 - hash: - md5: a72f9d4ea13d55d745ff1ed594747f10 - sha256: eb09823f34cc2dd663c0ec4ab13f246f45dcd52e5b8c47b9864361de5204a1c8 - category: main - optional: false -- name: yaml - version: 0.2.5 - manager: conda - platform: osx-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h0d85af4_2.tar.bz2 - hash: - md5: d7e08fcf8259d742156188e8762b4d20 - sha256: 5301417e2c8dea45b401ffee8df3957d2447d4ce80c83c5ff151fc6bfe1c4148 - category: main - optional: false -- name: aws-c-compression - version: 0.2.16 - manager: conda - platform: osx-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/aws-c-compression-0.2.16-h3039e2f_7.conda - hash: - md5: 2a905faf1486dac75155b285f2a7df49 - sha256: 2b8b6b29d132efe1d07998f7d768ba684aef0f0918f3558ce5f4dad3bb6468d3 - category: main - optional: false -- name: aws-c-sdkutils - version: 0.1.9 - manager: conda - platform: osx-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/aws-c-sdkutils-0.1.9-h3039e2f_2.conda - hash: - md5: 9b5faffe74b102e78fe8523d99c440e2 - sha256: dc10c1ecc14e2f2381b177275d66bd5b113c3a213e0cc3c6f2eebae36d1aa15e - category: main - optional: false -- name: aws-checksums - version: 0.1.14 - manager: conda - platform: osx-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/aws-checksums-0.1.14-h3039e2f_7.conda - hash: - md5: 59aa11e5551ffc49436a696288b9de08 - sha256: 4a97d33ff4023ec83a960372caf627852f421226343766352c078416014ed15c - category: main - optional: false -- name: cpuonly - version: '2.0' - manager: conda - platform: osx-64 - dependencies: - pytorch-mutex: '1.0' - url: https://conda.anaconda.org/pytorch/noarch/cpuonly-2.0-0.tar.bz2 - hash: - md5: 1cf3a59ef90a4078c253e3b02c272065 - sha256: f9107aca2a9d23a032634644df5cdb8d0185337891593ce540adc480810ab539 - category: main - optional: false -- name: gettext - version: 0.21.1 - manager: conda - platform: osx-64 - dependencies: - libiconv: '>=1.17,<2.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/gettext-0.21.1-h8a4c099_0.tar.bz2 - hash: - md5: 1e3aff29ce703d421c43f371ad676cc5 - sha256: 915d3cd2d777b9b3fc2e87a25901b8e4a6aa1b2b33cf2ba54e9e9ed4f6b67d94 - category: main - optional: false -- name: gflags - version: 2.2.2 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=10.0.1' - url: https://conda.anaconda.org/conda-forge/osx-64/gflags-2.2.2-hb1e8313_1004.tar.bz2 - hash: - md5: 3f59cc77a929537e42120faf104e0d16 - sha256: 39540f879057ae529cad131644af111a8c3c48b384ec6212de6a5381e0863948 - category: main - optional: false -- name: gmp - version: 6.2.1 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=10.0.1' - url: https://conda.anaconda.org/conda-forge/osx-64/gmp-6.2.1-h2e338ed_0.tar.bz2 - hash: - md5: dedc96914428dae572a39e69ee2a392f - sha256: d6386708f6b7bcf790c57e985a5ca5636ec6ccaed0493b8ddea231aaeb8bfb00 - category: main - optional: false -- name: lerc - version: 4.0.0 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=13.0.1' - url: https://conda.anaconda.org/conda-forge/osx-64/lerc-4.0.0-hb486fe8_0.tar.bz2 - hash: - md5: f9d6a4c82889d5ecedec1d90eb673c55 - sha256: e41790fc0f4089726369b3c7f813117bbc14b533e0ed8b94cf75aba252e82497 - category: main - optional: false -- name: libabseil - version: '20230125.2' - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=15.0.7' - url: https://conda.anaconda.org/conda-forge/osx-64/libabseil-20230125.2-cxx17_h000cb23_2.conda - hash: - md5: 52adda30d725e5afd2b4a25331818bdc - sha256: a245267505766ad336022d9d3bd7a0cea967615ba888bc7e59850e2316be5165 - category: main - optional: false -- name: libbrotlidec - version: 1.0.9 - manager: conda - platform: osx-64 - dependencies: - libbrotlicommon: 1.0.9 - url: https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.0.9-hb7f2c08_8.tar.bz2 - hash: - md5: 7f952a036d9014b4dab96c6ea0f8c2a7 - sha256: 52d8e8929b2476cf13fd397d88cefd911f805de00e77090fdc50b8fb11c372ca - category: main - optional: false -- name: libbrotlienc - version: 1.0.9 - manager: conda - platform: osx-64 - dependencies: - libbrotlicommon: 1.0.9 - url: https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.0.9-hb7f2c08_8.tar.bz2 - hash: - md5: b36a3bfe866d9127f25f286506982166 - sha256: be7e794c6208e7e12982872922df13fbf020ab594d516b7bc306a384ac7d3ac6 - category: main - optional: false -- name: libcrc32c - version: 1.1.2 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=11.1.0' - url: https://conda.anaconda.org/conda-forge/osx-64/libcrc32c-1.1.2-he49afe7_0.tar.bz2 - hash: - md5: 23d6d5a69918a438355d7cbc4c3d54c9 - sha256: 3043869ac1ee84554f177695e92f2f3c2c507b260edad38a0bf3981fce1632ff - category: main - optional: false -- name: libedit - version: 3.1.20191231 - manager: conda - platform: osx-64 - dependencies: - ncurses: '>=6.2,<7.0.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20191231-h0678c8f_2.tar.bz2 - hash: - md5: 6016a8a1d0e63cac3de2c352cd40208b - sha256: dbd3c3f2eca1d21c52e4c03b21930bbce414c4592f8ce805801575b9e9256095 - category: main - optional: false -- name: libgfortran5 - version: 12.2.0 - manager: conda - platform: osx-64 - dependencies: - llvm-openmp: '>=8.0.0' - url: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-12.2.0-he409387_31.conda - hash: - md5: 5a544130e584b1f204ac896ff071d5b3 - sha256: 42ae06bbb3cf7f7c3194482894f4287fad7bc39214d1a0dbf0c43f8efb8d3c1a - category: main - optional: false -- name: libpng - version: 1.6.39 - manager: conda - platform: osx-64 - dependencies: - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.39-ha978bb4_0.conda - hash: - md5: 35e4928794c5391aec14ffdf1deaaee5 - sha256: 5ad9f5e96e6770bfc8b0a826f48835e7f337c2d2e9512d76027a62f9c120b2a3 - category: main - optional: false -- name: libprotobuf - version: 3.21.12 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.6' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/libprotobuf-3.21.12-hbc0c0cd_0.conda - hash: - md5: 7a9b17cfb3e57143e4e9118b5244b691 - sha256: d3fbdc0808c4f433903704f943e4b13c079909f994fa157ec75615658d3bab17 - category: main - optional: false -- name: libsqlite - version: 3.42.0 - manager: conda - platform: osx-64 - dependencies: - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.42.0-h58db7d2_0.conda - hash: - md5: a7d3b44b7b0c9901ac7813b7a0462893 - sha256: 182689f4b1a5ed638cd615c7774e1a9974842bc127c59173f1d25e31a8795eef - category: main - optional: false -- name: libxcb - version: '1.13' - manager: conda - platform: osx-64 - dependencies: - pthread-stubs: '' - xorg-libxau: '' - xorg-libxdmcp: '' - url: https://conda.anaconda.org/conda-forge/osx-64/libxcb-1.13-h0d85af4_1004.tar.bz2 - hash: - md5: eb7860935e14aec936065cbc21a1a962 - sha256: 00e962ea91deae3dbed221c960c3bffab4172d87bc883b615298333fe336a5c6 - category: main - optional: false -- name: lz4-c - version: 1.9.4 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.6' - url: https://conda.anaconda.org/conda-forge/osx-64/lz4-c-1.9.4-hf0c8a7f_0.conda - hash: - md5: aa04f7143228308662696ac24023f991 - sha256: 39aa0c01696e4e202bf5e337413de09dfeec061d89acd5f28e9968b4e93c3f48 - category: main - optional: false -- name: openssl - version: 3.1.1 - manager: conda - platform: osx-64 - dependencies: - ca-certificates: '' - url: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.1.1-h8a1eda9_1.conda - hash: - md5: c7822d6ee74e34af1fd74365cfd18983 - sha256: 67855f92bf50f24cbbc44879864d7a040b1f351e95b599cfcf4cc49b2cc3fd08 - category: main - optional: false -- name: re2 - version: 2023.03.02 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.6' - url: https://conda.anaconda.org/conda-forge/osx-64/re2-2023.03.02-h096449b_0.conda - hash: - md5: 68580e997396899915eef7771ef3a646 - sha256: 6faebc3e5cb65bdf1ca5f1333d83118ec4b92c0d6fc27044cc998dab7e501a11 - category: main - optional: false -- name: readline - version: '8.2' - manager: conda - platform: osx-64 - dependencies: - ncurses: '>=6.3,<7.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda - hash: - md5: f17f77f2acf4d344734bda76829ce14e - sha256: 41e7d30a097d9b060037f0c6a2b1d4c4ae7e942c06c943d23f9d481548478568 - category: main - optional: false -- name: snappy - version: 1.1.10 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.6' - url: https://conda.anaconda.org/conda-forge/osx-64/snappy-1.1.10-h225ccf5_0.conda - hash: - md5: 4320a8781f14cd959689b86e349f3b73 - sha256: 575915dc13152e446a84e2f88de70a14f8b6af1a870e708f9370bd4be105583b - category: main - optional: false -- name: tbb - version: 2021.9.0 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.6' - url: https://conda.anaconda.org/conda-forge/osx-64/tbb-2021.9.0-hb8565cd_0.conda - hash: - md5: 6aedf8fdcdf5f2d7b4db21853a7d42ed - sha256: c778c2e7ba7573dcbb6002fdc6ab357f8e1cd63e1c39329af202233814a26fb8 - category: main - optional: false -- name: tk - version: 8.6.12 - manager: conda - platform: osx-64 - dependencies: - libzlib: '>=1.2.11,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.12-h5dbffcc_0.tar.bz2 - hash: - md5: 8e9480d9c47061db2ed1b4ecce519a7f - sha256: 331aa1137a264fd9cc905f04f09a161c801fe504b93da08b4e6697bd7c9ae6a6 - category: main - optional: false -- name: zlib - version: 1.2.13 - manager: conda - platform: osx-64 - dependencies: - libzlib: 1.2.13 - url: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.2.13-hfd90126_4.tar.bz2 - hash: - md5: be90e6223c74ea253080abae19b3bdb1 - sha256: 9db69bb5fc3e19093b550e25d1158cdf82f4f8eddc1f80f8d7d9de33eb8535a4 - category: main - optional: false -- name: zstd - version: 1.5.2 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.6' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.2-hbc0c0cd_6.conda - hash: - md5: 40a188783d3c425bdccc9ae9104acbb8 - sha256: f845dafb0b488703ce81e25b6f27ed909ee9061b730c172e6b084fcf7156231f - category: main - optional: false -- name: aws-c-cal - version: 0.5.26 - manager: conda - platform: osx-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/aws-c-cal-0.5.26-h9b5d637_1.conda - hash: - md5: ece5e863e7fe3aa3802fde04315b00df - sha256: 0d8e6951dd2177c4377e1c8a3fa65d8442cee7dd262dad006d13223311215662 - category: main - optional: false -- name: brotli-bin - version: 1.0.9 - manager: conda - platform: osx-64 - dependencies: - libbrotlidec: 1.0.9 - libbrotlienc: 1.0.9 - url: https://conda.anaconda.org/conda-forge/osx-64/brotli-bin-1.0.9-hb7f2c08_8.tar.bz2 - hash: - md5: aac5ad0d8f747ef7f871508146df75d9 - sha256: 36f79eb26da032c5d1ddc11e0bcac5526f249bf60d332e4743c8d48bb7334db0 - category: main - optional: false -- name: freetype - version: 2.12.1 - manager: conda - platform: osx-64 - dependencies: - libpng: '>=1.6.39,<1.7.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/freetype-2.12.1-h3f81eb7_1.conda - hash: - md5: 852224ea3e8991a8342228eab274840e - sha256: 0aea2b93d0da8bf022501857de93f2fc0e362fabcd83c4579be8d8f5bc3e17cb - category: main - optional: false -- name: glog - version: 0.6.0 - manager: conda - platform: osx-64 - dependencies: - gflags: '>=2.2.2,<2.3.0a0' - libcxx: '>=12.0.1' - url: https://conda.anaconda.org/conda-forge/osx-64/glog-0.6.0-h8ac2a54_0.tar.bz2 - hash: - md5: 69eb97ca709a136c53fdca1f2fd33ddf - sha256: fdb38560094fb4a952346dc72a79b3cb09e23e4d0cae9ba4f524e6e88203d3c8 - category: main - optional: false -- name: gnutls - version: 3.6.13 - manager: conda - platform: osx-64 - dependencies: - gettext: '>=0.19.8.1,<1.0a0' - libcxx: '>=11.0.0' - nettle: '>=3.6,<3.7.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/gnutls-3.6.13-h756fd2b_1.tar.bz2 - hash: - md5: cf812214a917b4d48ce536d427770ec9 - sha256: ecc18fd15977d02301be0f0c17f3d378f68b3fb49e7856ca299519a69b3b5307 - category: main - optional: false -- name: krb5 - version: 1.20.1 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.6' - libedit: '>=3.1.20191231,<4.0a0' - openssl: '>=3.0.7,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.20.1-h049b76e_0.conda - hash: - md5: db11fa2968ef0837288fe2d7f5b77a50 - sha256: 41cfbf4c5cdb4a32eb5319943113d7ef1edb894ea0a5464233e510b59450c824 - category: main - optional: false -- name: libevent - version: 2.1.12 - manager: conda - platform: osx-64 - dependencies: - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/libevent-2.1.12-h9cee658_0.conda - hash: - md5: f94acee0c90a11785bad4c2510542875 - sha256: b50fc9ec0133d69668498e135084f10c0c85baaeaaabef34ff185c0ed0fa7f18 - category: main - optional: false -- name: libgfortran - version: 5.0.0 - manager: conda - platform: osx-64 - dependencies: - libgfortran5: '' - url: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-5.0.0-11_3_0_h97931a8_31.conda - hash: - md5: 97451338600bd9c5b535eb224ef6c471 - sha256: 55d3c81ce8cd931260c3cb8c85868e36223d2bd0d5e2f35a79503810ee172769 - category: main - optional: false -- name: libgrpc - version: 1.54.2 - manager: conda - platform: osx-64 - dependencies: - c-ares: '>=1.18.1,<2.0a0' - libabseil: '>=20230125.2,<20230126.0a0' - libcxx: '>=15.0.7' - libprotobuf: '>=3.21.12,<3.22.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - re2: '>=2023.3.2,<2023.3.3.0a0' - zlib: '' - url: https://conda.anaconda.org/conda-forge/osx-64/libgrpc-1.54.2-hfaa49da_2.conda - hash: - md5: 5b85fd30432d540f8c727c5c6090862c - sha256: a4d502d78dfd2f87732ff897a11609dca47d0862ee279ac9b5f54581bf49b584 - category: main - optional: false -- name: libnghttp2 - version: 1.52.0 - manager: conda - platform: osx-64 - dependencies: - c-ares: '>=1.18.1,<2.0a0' - libcxx: '>=14.0.6' - libev: '>=4.33,<4.34.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.0.8,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.52.0-he2ab024_0.conda - hash: - md5: 12ac7d100bf260263e30a019517f42a2 - sha256: 093e4f3f62b3b07befa403e84a1f550cffe3b3961e435d42a75284f44be5f68a - category: main - optional: false -- name: libssh2 - version: 1.10.0 - manager: conda - platform: osx-64 - dependencies: - libzlib: '>=1.2.12,<1.3.0a0' - openssl: '>=3.0.5,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.10.0-h47af595_3.tar.bz2 - hash: - md5: 5a28624eeb7812b585b9e2d75f846ba2 - sha256: 3261dc7fa9cb928e8a0da4857b89bdd3e965766a6cd5b6456d4407cba6b25402 - category: main - optional: false -- name: libtiff - version: 4.5.0 - manager: conda - platform: osx-64 - dependencies: - jpeg: '>=9e,<10a' - lerc: '>=4.0.0,<5.0a0' - libcxx: '>=14.0.6' - libdeflate: '>=1.17,<1.18.0a0' - libwebp-base: '>=1.2.4,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - xz: '>=5.2.6,<6.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.5.0-hee9004a_2.conda - hash: - md5: 35f714269a801f7c3cb522aacd3c0e69 - sha256: 03d00d6a3b1e569e9a8da66a9ad75a29c9c676dc7de6c16771abbb961abded2c - category: main - optional: false -- name: mkl - version: 2022.1.0 - manager: conda - platform: osx-64 - dependencies: - llvm-openmp: '>=13.0.1' - tbb: 2021.* - url: https://conda.anaconda.org/conda-forge/osx-64/mkl-2022.1.0-h860c996_928.tar.bz2 - hash: - md5: 98a4d58de0ba6e61ce46620b775c19ce - sha256: 56548424d3c74f2249ef567e5422f83a3ed7c81be1ffebe0034668b44d763854 - category: main - optional: false -- name: openh264 - version: 2.1.1 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=11.0.0' - zlib: '>=1.2.11,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/openh264-2.1.1-hfd3ada9_0.tar.bz2 - hash: - md5: 468e32402dae56c73b29fb74160a9b2a - sha256: d46c0de784614d3c82f7ef7f2e4f21a2e6b30c4aeea09e7c970eafeb8ee4e81f - category: main - optional: false -- name: orc - version: 1.8.3 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.6' - libprotobuf: '>=3.21.12,<3.22.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - snappy: '>=1.1.10,<2.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/orc-1.8.3-ha9d861c_0.conda - hash: - md5: f1fdbf0e6c47818a8ba08b0ac4fbd6b6 - sha256: 2df34205e2f37ac283e2cc2d6b669bce965b171b8809fee228efab124f11d382 - category: main - optional: false -- name: sqlite - version: 3.42.0 - manager: conda - platform: osx-64 - dependencies: - libsqlite: 3.42.0 - libzlib: '>=1.2.13,<1.3.0a0' - ncurses: '>=6.3,<7.0a0' - readline: '>=8.2,<9.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/sqlite-3.42.0-h2b0dec6_0.conda - hash: - md5: 6c22b83608a6e3bd324ab29d3092592f - sha256: ff811793c293cf861746c95fe97ad5384e917bf473337d05283afdadb3b50bc9 - category: main - optional: false -- name: aws-c-io - version: 0.13.21 - manager: conda - platform: osx-64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/aws-c-io-0.13.21-hbe544ae_5.conda - hash: - md5: fd64788b3c6215f7f779e532e48740a8 - sha256: 65317914c901b5a048e4c12b372a4e6fbfd906252642672b5250c854bc4ffda1 - category: main - optional: false -- name: brotli - version: 1.0.9 - manager: conda - platform: osx-64 - dependencies: - brotli-bin: 1.0.9 - libbrotlidec: 1.0.9 - libbrotlienc: 1.0.9 - url: https://conda.anaconda.org/conda-forge/osx-64/brotli-1.0.9-hb7f2c08_8.tar.bz2 - hash: - md5: 55f612fe4a9b5f6ac76348b6de94aaeb - sha256: 1272426370f1e8db1a8b245a7b522afe27413b09eab169990512a7676b802e3b - category: main - optional: false -- name: ffmpeg - version: '4.3' - manager: conda - platform: osx-64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - freetype: '>=2.10.2,<3.0a0' - gmp: '>=6.1.2' - gnutls: '>=3.6.5,<3.7.0a0' - lame: '>=3.100,<3.101.0a0' - libcxx: '>=10.0.0' - libiconv: '>=1.16,<2.0a0' - openh264: '>=2.1.0,<2.2.0a0' - zlib: '>=1.2.11,<1.3.0a0' - url: https://conda.anaconda.org/pytorch/osx-64/ffmpeg-4.3-h0a44026_0.tar.bz2 - hash: - md5: 697e16e77842a96468ec67e779efab6a - sha256: b4da37256ea40beac9198e14c374666e3120da1f152bd36aac6cd11a76ef218e - category: main - optional: false -- name: lcms2 - version: '2.15' - manager: conda - platform: osx-64 - dependencies: - jpeg: '>=9e,<10a' - libtiff: '>=4.5.0,<4.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.15-h29502cd_0.conda - hash: - md5: 4f448677a99bfc41d3bd538c1302f1fb - sha256: 3edfeeb2527904dfa22de1d1559c95a8bfca7638a13fb0d4520f49ae42136959 - category: main - optional: false -- name: libblas - version: 3.9.0 - manager: conda - platform: osx-64 - dependencies: - mkl: '>=2022.1.0,<2023.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.9.0-16_osx64_mkl.tar.bz2 - hash: - md5: 96b23c2ca3208c5cb1ed34270448af5c - sha256: f1fa9b504ad948e70c9ecb834ceddb84ee4fd4a49f773c67e4943b15377f19d0 - category: main - optional: false -- name: libcurl - version: 8.1.2 - manager: conda - platform: osx-64 - dependencies: - krb5: '>=1.20.1,<1.21.0a0' - libnghttp2: '>=1.52.0,<2.0a0' - libssh2: '>=1.10.0,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/libcurl-8.1.2-hbee3ae8_0.conda - hash: - md5: d51e337da844262f9033c9a26452520f - sha256: 2f81bb06377779ea1abe373a2a89289fb440751ad6f68f947ec0f3b1e4399968 - category: main - optional: false -- name: libthrift - version: 0.18.1 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=15.0.7' - libevent: '>=2.1.12,<2.1.13.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/libthrift-0.18.1-h88b220a_1.conda - hash: - md5: 2af78d73075e2797eb25863c43314d10 - sha256: dc649788c3fcb0e1f834ebe290920643c01ef5946654bf849c67b0cf1ceec641 - category: main - optional: false -- name: mkl-devel - version: 2022.1.0 - manager: conda - platform: osx-64 - dependencies: - mkl: 2022.1.0 - mkl-include: 2022.1.0 - url: https://conda.anaconda.org/conda-forge/osx-64/mkl-devel-2022.1.0-h694c41f_929.tar.bz2 - hash: - md5: 041ceef009fe6d29cbd2555907c23ab3 - sha256: 7ef092380db0036e954caf194f6bf7343b3dd5a842cefc6630b2c8e3c23ba0c6 - category: main - optional: false -- name: openjpeg - version: 2.5.0 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.6' - libpng: '>=1.6.39,<1.7.0a0' - libtiff: '>=4.5.0,<4.6.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.5.0-h13ac156_2.conda - hash: - md5: 299a29af9ac9f550ad459d655739280b - sha256: 2375eafbd5241d8249fb467e2a8e190646e8798c33059c72efa60f197cdf4944 - category: main - optional: false -- name: python - version: 3.9.12 - manager: conda - platform: osx-64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - libffi: '>=3.4.2,<3.5.0a0' - libzlib: '>=1.2.11,<1.3.0a0' - ncurses: '>=6.3,<7.0a0' - openssl: '>=3.0.2,<4.0a0' - readline: '>=8.1,<9.0a0' - sqlite: '>=3.37.1,<4.0a0' - tk: '>=8.6.12,<8.7.0a0' - tzdata: '' - xz: '>=5.2.5,<5.3.0a0' - pip: '' - url: https://conda.anaconda.org/conda-forge/osx-64/python-3.9.12-h1cc4136_1_cpython.tar.bz2 - hash: - md5: 9766514b09f88202f8e63b24f1e4df69 - sha256: a273c121e121a39d9fa620cf6c760cd0d969e78c3236892f3e5c7fb9e27a1366 - category: main - optional: false -- name: antlr-python-runtime - version: 4.9.3 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/antlr-python-runtime-4.9.3-pyhd8ed1ab_1.tar.bz2 - hash: - md5: c88eaec8de9ae1fa161205aa18e7a5b1 - sha256: b91f8ab4ac2b48972fbee1fc8e092cc452fdf59156e4ff2322c94bbf73650f94 - category: main - optional: false -- name: attrs - version: 23.1.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/attrs-23.1.0-pyh71513ae_1.conda - hash: - md5: 3edfead7cedd1ab4400a6c588f3e75f8 - sha256: 063639cd568f5c7a557b0fb1cc27f098598c0d8ff869088bfeb82934674f8821 - category: main - optional: false -- name: aws-c-event-stream - version: 0.2.20 - manager: conda - platform: osx-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - libcxx: '>=15.0.7' - url: https://conda.anaconda.org/conda-forge/osx-64/aws-c-event-stream-0.2.20-h58c9f2d_7.conda - hash: - md5: 8ec333819c382649ce6b253a2f9607b4 - sha256: 5199be206752a8e0169e59d5a27e7e6e99e7e6378ec70140fc451ba6434b3e54 - category: main - optional: false -- name: aws-c-http - version: 0.7.7 - manager: conda - platform: osx-64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-compression: '>=0.2.16,<0.2.17.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/aws-c-http-0.7.7-hb536a44_4.conda - hash: - md5: dc9964573958b30064d7ab05bbd3d62d - sha256: 45e80636355e4c571ba9cd7c72877d0754a46d5649f4dd2d8ed94930cc62e1ce - category: main - optional: false -- name: backports - version: '1.0' - manager: conda - platform: osx-64 - dependencies: - python: '>=2.7' - url: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_3.conda - hash: - md5: 54ca2e08b3220c148a1d8329c2678e02 - sha256: 711602276ae39276cb0faaca6fd0ac851fff0ca17151917569174841ef830bbd - category: main - optional: false -- name: blinker - version: 1.6.2 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/blinker-1.6.2-pyhd8ed1ab_0.conda - hash: - md5: 2fb79ec81bad9492b6d59a06b3b647a4 - sha256: b6f32491536823e47cf6eb4717dd341385600a2b901235028dedc629a77aeb82 - category: main - optional: false -- name: certifi - version: 2023.5.7 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/certifi-2023.5.7-pyhd8ed1ab_0.conda - hash: - md5: 5d1b71c942b8421285934dad1d891ebc - sha256: f839a6e04d94069f90dd85337ea9108f058dc76771bb469a413f32bb1ba0b256 - category: main - optional: false -- name: charset-normalizer - version: 3.1.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.1.0-pyhd8ed1ab_0.conda - hash: - md5: 7fcff9f6f123696e940bda77bd4d6551 - sha256: 06cd371fc98f076797d6450f6f337cb679b1060c99680fb7e044591493333194 - category: main - optional: false -- name: click - version: 8.1.3 - manager: conda - platform: osx-64 - dependencies: - __unix: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2 - hash: - md5: 20e4087407c7cb04a40817114b333dbf - sha256: 23676470b591b100393bb0f6c46fe10624dcbefc696a6a9f42932ed8816ef0ea - category: main - optional: false -- name: cloudpickle - version: 2.2.1 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.1-pyhd8ed1ab_0.conda - hash: - md5: b325bfc4cff7d7f8a868f1f7ecc4ed16 - sha256: f0c2fd0e842899a05ddd7b147fb26424adf58be0e8e54e5bc68b8f7e67d05dcd - category: main - optional: false -- name: colorama - version: 0.4.6 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3faab06a954c2a04039983f2c4a50d99 - sha256: 2c1b2e9755ce3102bca8d69e8f26e4f087ece73f50418186aee7c74bef8e1698 - category: main - optional: false -- name: configparser - version: 5.3.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/configparser-5.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: c99fd5916160900dc5ff64204da99c4d - sha256: ce6ce9ee08437b46c284d52b076fb091cf6f2a9e12860d4a37546adbd5f53b28 - category: main - optional: false -- name: crashtest - version: 0.4.1 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6,<4.0' - url: https://conda.anaconda.org/conda-forge/noarch/crashtest-0.4.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 709a2295dd907bb34afb57d54320642f - sha256: 2f05954a3faf0700c14c1deddc085385160ee32abe111699c78d9cb277e915cc - category: main - optional: false -- name: cycler - version: 0.11.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: a50559fad0affdbb33729a68669ca1cb - sha256: 3b594bc8aa0b9a51269d54c7a4ef6af777d7fad4bee16b05695e1124de6563f6 - category: main - optional: false -- name: distlib - version: 0.3.6 - manager: conda - platform: osx-64 - dependencies: - python: 2.7|>=3.6 - url: https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2 - hash: - md5: b65b4d50dbd2d50fa0aeac367ec9eed7 - sha256: 06eb7167d4d760b3b437a491e32ab5b3f89e2a18f023c117fe213b038d88538a - category: main - optional: false -- name: entrypoints - version: '0.4' - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3cf04868fee0a029769bd41f4b2fbf2d - sha256: 2ec4a0900a4a9f42615fc04d0fb3286b796abe56590e8e042f6ec25e102dd5af - category: main - optional: false -- name: exceptiongroup - version: 1.1.1 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.1-pyhd8ed1ab_0.conda - hash: - md5: 7312299d7a0ea4993159229b7d2dceb2 - sha256: f073c3ba993912f1c0027bc34a54975642885f0a4cd5f9dc42a17ca945df2c18 - category: main - optional: false -- name: filelock - version: 3.12.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.12.0-pyhd8ed1ab_0.conda - hash: - md5: 650f18a56f366dbf419c15b543592c2d - sha256: 68db3a6280d6786be76f2c7c6cf41dd878c5d1a24f5de10f7f0af82c6fcfade6 - category: main - optional: false -- name: fsspec - version: 2023.5.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/fsspec-2023.5.0-pyh1a96a4e_0.conda - hash: - md5: 20edd290b319aa0eff3e9055375756dc - sha256: cbb5c77c0217cda9bf4f4240158de11822a099a6eaa05ba626e822819a54f46d - category: main - optional: false -- name: greenlet - version: 2.0.2 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=15.0.7' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/greenlet-2.0.2-py39h840bb9f_1.conda - hash: - md5: 8e17d21e2726aa55522f9ae229450626 - sha256: d1805d1cfd0a4469b553524f0c7638d5bceb9c28c0f062bb1b0b0455a17a7da4 - category: main - optional: false -- name: idna - version: '3.4' - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 34272b248891bddccc64479f9a7fffed - sha256: 9887c35c374ec1847f167292d3fde023cb4c994a4ceeec283072b95440131f09 - category: main - optional: false -- name: itsdangerous - version: 2.1.2 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/itsdangerous-2.1.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3c3de74912f11d2b590184f03c7cd09b - sha256: 31e3492686b4e92b53db9b48bc0eb03873b1caaf28629fee7d2d47627a2c56d3 - category: main - optional: false -- name: kiwisolver - version: 1.4.4 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.4' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.4.4-py39h92daf61_1.tar.bz2 - hash: - md5: 7720e059630e25ab17ab12677e59c615 - sha256: c397173c92ca77678d645bf8ef8064e81b821169db056217963f020acc09d42c - category: main - optional: false -- name: libcblas - version: 3.9.0 - manager: conda - platform: osx-64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.9.0-16_osx64_mkl.tar.bz2 - hash: - md5: 430c4d18fd8bbc987c4367f5d16135cf - sha256: cea791941c65d70883937ceba5ab930a4c97cd2d74451fd0e6b1e06526299a55 - category: main - optional: false -- name: libgoogle-cloud - version: 2.10.1 - manager: conda - platform: osx-64 - dependencies: - libabseil: '>=20230125.2,<20230126.0a0' - libcrc32c: '>=1.1.2,<1.2.0a0' - libcurl: '>=8.0.1,<9.0a0' - libcxx: '>=15.0.7' - libgrpc: '>=1.54.2,<1.55.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/libgoogle-cloud-2.10.1-h20eaa6e_1.conda - hash: - md5: 4e67db61fe76b8a5b49df1c24728d324 - sha256: ea9ba333f6059f658a253b0e76348774d408a9272db2eff716b5e88042eed39c - category: main - optional: false -- name: liblapack - version: 3.9.0 - manager: conda - platform: osx-64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.9.0-16_osx64_mkl.tar.bz2 - hash: - md5: 757f1ae46973ce6542784d99b9984d8d - sha256: 1df7a4e75ccafef1ed768b947cce6817eb98eee235fb61e768046f084cc5f48f - category: main - optional: false -- name: lockfile - version: 0.12.2 - manager: conda - platform: osx-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/lockfile-0.12.2-py_1.tar.bz2 - hash: - md5: c104d98e09c47519950cffb8dd5b4f10 - sha256: d3a68045ef74a2a7b8c8a55b242fdbc875d362e37adcf793613cf0d8c8e4fbf7 - category: main - optional: false -- name: markupsafe - version: 2.1.2 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-2.1.2-py39ha30fb19_0.conda - hash: - md5: 3b7b34916156e45ec52df74efc3db6e4 - sha256: d5aa88cdd75728fe101f83d0c4a7ab36634044f890e9e41aceb7454500e8af2b - category: main - optional: false -- name: mdurl - version: 0.1.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: f8dab71fdc13b1bf29a01248b156d268 - sha256: c678b9194e025b1fb665bec30ee20aab93399203583875b1dcc0a3b52a8f5523 - category: main - optional: false -- name: more-itertools - version: 9.1.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/more-itertools-9.1.0-pyhd8ed1ab_0.conda - hash: - md5: 1698a717f83cfecf644a877c174c84bd - sha256: 3ee8cbbe4004c56b695a5e734b7dc4d59dacbfefc193ee42c82238b1cf888e08 - category: main - optional: false -- name: msgpack-python - version: 1.0.5 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.6' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/msgpack-python-1.0.5-py39h92daf61_0.conda - hash: - md5: a2a730c9085efb9b5890b8786ddc9fe7 - sha256: 67df7c337a6deeb2bb8aa637864d65549652fc9fcc92751251dc8f67b8ab9197 - category: main - optional: false -- name: munkres - version: 1.1.4 - manager: conda - platform: osx-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2 - hash: - md5: 2ba8498c1018c1e9c61eb99b973dfe19 - sha256: f86fb22b58e93d04b6f25e0d811b56797689d598788b59dcb47f59045b568306 - category: main - optional: false -- name: ordered-set - version: 4.1.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/ordered-set-4.1.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 9a8714decb3967b290263817e876d8a9 - sha256: 78d92f848a6b4a89148dfa1f6e65c0b75e8f3a267b6401be38fb3401853b4afa - category: main - optional: false -- name: orjson - version: 3.8.14 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/orjson-3.8.14-py39hdf3f5bf_0.conda - hash: - md5: ee70a4e137d7bea96ddb62af78a94cf3 - sha256: 138a9f42a306ab412cce0d8853fa21f5c6c09bcaee308344db694f504dd1cb73 - category: main - optional: false -- name: packaging - version: '23.1' - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/packaging-23.1-pyhd8ed1ab_0.conda - hash: - md5: 91cda59e66e1e4afe9476f8ef98f5c30 - sha256: ded536a96a00d45a693dbc2971bb688248324dadd129eddda2100e177583d768 - category: main - optional: false -- name: pillow - version: 9.4.0 - manager: conda - platform: osx-64 - dependencies: - freetype: '>=2.12.1,<3.0a0' - jpeg: '>=9e,<10a' - lcms2: '>=2.14,<3.0a0' - libtiff: '>=4.5.0,<4.6.0a0' - libwebp-base: '>=1.2.4,<2.0a0' - libxcb: '>=1.13,<1.14.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openjpeg: '>=2.5.0,<3.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - tk: '>=8.6.12,<8.7.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/pillow-9.4.0-py39h7f5cd59_1.conda - hash: - md5: d2f1bdaa85fd34020259533efeeb40bb - sha256: b7a6d9e50a212215f76666789b0e9c155756d90e27678b4a8720fc6825621648 - category: main - optional: false -- name: pkginfo - version: 1.9.6 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.9.6-pyhd8ed1ab_0.conda - hash: - md5: be1e9f1c65a1ed0f2ae9352fec99db64 - sha256: 7ea5a5af62a15376d9f4f9f3c134874d0b0710f39be719e849b7fa9ca8870502 - category: main - optional: false -- name: pkgutil-resolve-name - version: 1.3.10 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pkgutil-resolve-name-1.3.10-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 89e3c7cdde7d3aaa2aee933b604dd07f - sha256: 7d055ffc8a02bf781a89d069db3454b453605cdaff300b82cedcc7133283e47e - category: main - optional: false -- name: prometheus_client - version: 0.17.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.17.0-pyhd8ed1ab_0.conda - hash: - md5: 95c5be3c7cbd872509d16c216617fdab - sha256: eb11fd8b927d9c5ff9482cfbd6cd810a43a1351c44a288e9680542ea698a19a0 - category: main - optional: false -- name: psutil - version: 5.9.5 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/psutil-5.9.5-py39ha30fb19_0.conda - hash: - md5: 4e8695f9b92a8815e57a219afd00632e - sha256: 54ea6bb14fdb610fe7588caad50e26ccdd4e7bcc5d40899c1bffeb04cc6e116e - category: main - optional: false -- name: ptyprocess - version: 0.7.0 - manager: conda - platform: osx-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd3deb0d_0.tar.bz2 - hash: - md5: 359eeb6536da0e687af562ed265ec263 - sha256: fb31e006a25eb2e18f3440eb8d17be44c8ccfae559499199f73584566d0a444a - category: main - optional: false -- name: pycparser - version: '2.21' - manager: conda - platform: osx-64 - dependencies: - python: 2.7.*|>=3.4 - url: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 076becd9e05608f8dc72757d5f3a91ff - sha256: 74c63fd03f1f1ea2b54e8bc529fd1a600aaafb24027b738d0db87909ee3a33dc - category: main - optional: false -- name: pygments - version: 2.15.1 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/pygments-2.15.1-pyhd8ed1ab_0.conda - hash: - md5: d316679235612869eba305aa7d41d9bf - sha256: 1bddeb54863c77ed5613b535a3e06a3a16b55786301a5e28c9bf011656bda686 - category: main - optional: false -- name: pyjwt - version: 2.7.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyjwt-2.7.0-pyhd8ed1ab_0.conda - hash: - md5: 99e28be5a278e2319834d7dc99e7bfdd - sha256: f3a64306fa0f405f10f4108d7ff42043d6fd393f940f9e98e395a3756687fc98 - category: main - optional: false -- name: pyparsing - version: 3.0.9 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2 - hash: - md5: e8fbc1b54b25f4b08281467bc13b70cc - sha256: 4acc7151cef5920d130f2e0a7615559cce8bfb037aeecb14d4d359ae3d9bc51b - category: main - optional: false -- name: pyrsistent - version: 0.19.3 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/pyrsistent-0.19.3-py39ha30fb19_0.conda - hash: - md5: e4e22a74d0944ff8aa81167aa9ddf9c6 - sha256: 4fd953bf6b4ae465da1e3ad07ea2b27e74642039abe0f6f21f4ee95d4df2b356 - category: main - optional: false -- name: pysocks - version: 1.7.1 - manager: conda - platform: osx-64 - dependencies: - __unix: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2 - hash: - md5: 2a7de29fb590ca14b5243c4c812c8025 - sha256: a42f826e958a8d22e65b3394f437af7332610e43ee313393d1cf143f0a2d274b - category: main - optional: false -- name: python-editor - version: 1.0.4 - manager: conda - platform: osx-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/python-editor-1.0.4-py_0.tar.bz2 - hash: - md5: eaaf29a0644f9407f98a4665f45880c4 - sha256: a6db88da69a27451d2eba675c445bdefd2dbea52ea02a0a214d5fd4f0af31740 - category: main - optional: false -- name: python-installer - version: 0.7.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/python-installer-0.7.0-pyhd8ed1ab_0.conda - hash: - md5: 65dea78f903d686c8b0c2feaf0e15e1f - sha256: 822f95b7786cfa61a6519153117b21d93194890e02a884b9f66ee4275e4f1c0a - category: main - optional: false -- name: python-multipart - version: 0.0.6 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/python-multipart-0.0.6-pyhd8ed1ab_0.conda - hash: - md5: f4f642eeda814c1b65f46fbdf7e89096 - sha256: 2a9b8d02a6ec9862433cfc2741c4cbfe321e4ae3bab066f7ed84bc00effb73d7 - category: main - optional: false -- name: python-tzdata - version: '2023.3' - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2023.3-pyhd8ed1ab_0.conda - hash: - md5: 2590495f608a63625e165915fb4e2e34 - sha256: 0108888507014fb24573c31e4deceb61c99e63d37776dddcadd7c89b2ecae0b6 - category: main - optional: false -- name: pytz - version: '2023.3' - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pytz-2023.3-pyhd8ed1ab_0.conda - hash: - md5: d3076b483092a435832603243567bc31 - sha256: e4999484f21763ca4b8f92c95b22cb6d1edc1b61d0a2bb073ee2bd11f39401b9 - category: main - optional: false -- name: pywin32-on-windows - version: 0.1.0 - manager: conda - platform: osx-64 - dependencies: - __unix: '' - python: '>=2.7' - url: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - hash: - md5: 2807a0becd1d986fe1ef9b7f8135f215 - sha256: 6502696aaef571913b22a808b15c185bd8ea4aabb952685deb29e6a6765761cb - category: main - optional: false -- name: pyyaml - version: '6.0' - manager: conda - platform: osx-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - yaml: '>=0.2.5,<0.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0-py39ha30fb19_5.tar.bz2 - hash: - md5: 45794cac8eadcc11b3f26dda1705bf62 - sha256: 6edf2777c7f6b542d3495d4b2c8f455dfcf5e6351f4ceca9265aac9ace4beac2 - category: main - optional: false -- name: readchar - version: 4.0.5 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/readchar-4.0.5-pyhd8ed1ab_0.conda - hash: - md5: 513334936060e80697bc21079e4f2829 - sha256: 0426cd7a524c31ab6d52b4d181848daea81d057e200a74200ea6e2896534bc18 - category: main - optional: false -- name: setuptools - version: 67.7.2 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/setuptools-67.7.2-pyhd8ed1ab_0.conda - hash: - md5: 3b68bc43ec6baa48f7354a446267eefe - sha256: 3ac44771fce01f19218bcdf3992e24984748048db69889a9df65abcc6a10e29b - category: main - optional: false -- name: shellingham - version: 1.5.1 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/shellingham-1.5.1-pyhd8ed1ab_0.conda - hash: - md5: 1de44299f48f522caa2e0074231614e1 - sha256: 3cb4a4a83b617fdfef9b92751634488db0b8961c80340be8068bf6d4f1d5ac25 - category: main - optional: false -- name: six - version: 1.16.0 - manager: conda - platform: osx-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 - hash: - md5: e5f25f8dbc060e9a8d912e432202afc2 - sha256: a85c38227b446f42c5b90d9b642f2c0567880c15d72492d8da074a59c8f91dd6 - category: main - optional: false -- name: smmap - version: 3.0.5 - manager: conda - platform: osx-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/smmap-3.0.5-pyh44b312d_0.tar.bz2 - hash: - md5: 3a8dc70789709aa315325d5df06fb7e4 - sha256: 091de70ee6bfe063e0c0f77336975d124fd1e3f49b9c58d97c0c7b3d287c0002 - category: main - optional: false -- name: sniffio - version: 1.3.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: dd6cbc539e74cb1f430efbd4575b9303 - sha256: a3fd30754c20ddb28b777db38345ea00d958f46701f0decd6291a81c0f4eee78 - category: main - optional: false -- name: soupsieve - version: 2.3.2.post1 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 146f4541d643d48fc8a75cacf69f03ae - sha256: 72d80dda41c3902c2619e8ab49d4f5b2a894d13375e1f9ed16fc00074ddd2307 - category: main - optional: false -- name: sqlparse - version: 0.4.4 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.5' - url: https://conda.anaconda.org/conda-forge/noarch/sqlparse-0.4.4-pyhd8ed1ab_0.conda - hash: - md5: 2e2f31b3b1c866c29636377e14f8c4c6 - sha256: 7972c9b15dafa1885f3d4cd22dc4edea4cd969d12739fb71f8632f2c3350706a - category: main - optional: false -- name: tabulate - version: 0.9.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.9.0-pyhd8ed1ab_1.tar.bz2 - hash: - md5: 4759805cce2d914c38472f70bf4d8bcb - sha256: f6e4a0dd24ba060a4af69ca79d32361a6678e61d78c73eb5e357909b025b4620 - category: main - optional: false -- name: threadpoolctl - version: 3.1.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2 - hash: - md5: a2995ee828f65687ac5b1e71a2ab1e0c - sha256: c7a964811ba49c545236f7d6c2486b2ed493931660048a06fe94ecc851dd0b82 - category: main - optional: false -- name: tomli - version: 2.0.1 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 5844808ffab9ebdb694585b50ba02a96 - sha256: 4cd48aba7cd026d17e86886af48d0d2ebc67ed36f87f6534f4b67138f5a5a58f - category: main - optional: false -- name: tomlkit - version: 0.11.8 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tomlkit-0.11.8-pyha770c72_0.conda - hash: - md5: 75838e8556166263a82038b51d01d5f1 - sha256: 3002e87338a98ba501fbf53981f8267b2def2548265a3622d403d06747872ccd - category: main - optional: false -- name: traitlets - version: 5.9.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.9.0-pyhd8ed1ab_0.conda - hash: - md5: d0b4f5c87cd35ac3fb3d47b223263a64 - sha256: 343610bce6dbe8a5090500dd2e9d1706057960b3f3120ebfe0abb4a8ecbada4d - category: main - optional: false -- name: trove-classifiers - version: 2023.5.24 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2023.5.24-pyhd8ed1ab_0.conda - hash: - md5: 4580a4f27cad1c3b275f6f6ad310abae - sha256: 05e83cd3ac921143c7a25681928727bcc9b01bf8456c9615b72d64f050863503 - category: main - optional: false -- name: typing - version: 3.10.0.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/typing-3.10.0.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: e6573ac68718f17b9d4f5c8eda3190f2 - sha256: ec1cfe0b7dc55a22223562cad799e0b16d122dab611c9923b6068d27a784ba2f - category: main - optional: false -- name: typing_extensions - version: 4.5.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.5.0-pyha770c72_0.conda - hash: - md5: 43e7d9e50261fb11deb76e17d8431aac - sha256: f81eee64fcdfb379e27d01773b34041fbf7f9e86f33b157c9925d19e0a442452 - category: main - optional: false -- name: unicodedata2 - version: 15.0.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/unicodedata2-15.0.0-py39ha30fb19_0.tar.bz2 - hash: - md5: 17876b4aebf783fb7bba980a79516892 - sha256: 06ff21e0a28f5acee3719fd8c788c4dffbed408f463c933f7f892399039962fc - category: main - optional: false -- name: webencodings - version: 0.5.1 - manager: conda - platform: osx-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-py_1.tar.bz2 - hash: - md5: 3563be4c5611a44210d9ba0c16113136 - sha256: 302f4f4bd1ad00c0be1426ecf6bb01db59cfd8aff3de0cf1596526dca1a6b70e - category: main - optional: false -- name: websocket-client - version: 1.5.2 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.5.2-pyhd8ed1ab_0.conda - hash: - md5: bfe7e7cd1476092f51efbcde15dfb110 - sha256: 85310b382c4220d7846fa8f046216fd722b88db07991f07bd7decdf2e5dc3446 - category: main - optional: false -- name: websockets - version: 11.0.3 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/websockets-11.0.3-py39hdc70f33_0.conda - hash: - md5: 4e0de096dfdbdf2ce5e4b3d4ec43d311 - sha256: 002e148790cb05c74747b724b7e266dc2a2673b21ebf6dea8756bf880807835f - category: main - optional: false -- name: wheel - version: 0.40.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.40.0-pyhd8ed1ab_0.conda - hash: - md5: 49bb0d9e60ce1db25e151780331bb5f3 - sha256: 79b4d29b0c004014a2abd5fc2c9fcd35cc6256222b960c2a317a27c4b0d8884d - category: main - optional: false -- name: zipp - version: 3.15.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/zipp-3.15.0-pyhd8ed1ab_0.conda - hash: - md5: 13018819ca8f5b7cc675a8faf1f5fedf - sha256: 241de30545299be9bcea3addf8a2c22a3b3d4ba6730890e150ab690ac937a3d2 - category: main - optional: false -- name: anyio - version: 3.7.0 - manager: conda - platform: osx-64 - dependencies: - typing_extensions: '' - exceptiongroup: '' - python: '>=3.7' - idna: '>=2.8' - sniffio: '>=1.1' - url: https://conda.anaconda.org/conda-forge/noarch/anyio-3.7.0-pyhd8ed1ab_1.conda - hash: - md5: 2b35a85d654a47aac8f34c1bb6de7142 - sha256: 863c11a6a0e937977229b405a16f6d43fff543dfe5b1a66da9c42ec0cbdaaf33 - category: main - optional: false -- name: aws-c-auth - version: 0.6.27 - manager: conda - platform: osx-64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-c-sdkutils: '>=0.1.9,<0.1.10.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/aws-c-auth-0.6.27-h093a61a_1.conda - hash: - md5: 35be831b3fa15fe73a8276a1da1edc83 - sha256: e73bc16351804457590a75f8dcee6fcad06d38941a9e8f0fc592b0e8742115d6 - category: main - optional: false -- name: aws-c-mqtt - version: 0.8.11 - manager: conda - platform: osx-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/aws-c-mqtt-0.8.11-h3f6fd96_1.conda - hash: - md5: ecd7c6c2576f62444e0237250ad8d8fe - sha256: ff3d43db7af05f76fd979f5a849462dc1bfa10dd4c6280341cd6db040a90af28 - category: main - optional: false -- name: backports.cached-property - version: 1.0.2 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - typing: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/backports.cached-property-1.0.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 0e1df3978dd516e20ef88c86d51e5432 - sha256: 1d86eafb5e9ed078f891e12b46692d786723652907dfb01b047c7da31f92b862 - category: main - optional: false -- name: backports.functools_lru_cache - version: 1.6.4 - manager: conda - platform: osx-64 - dependencies: - setuptools: '' - backports: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/backports.functools_lru_cache-1.6.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: c5b3edc62d6309088f4970b3eaaa65a6 - sha256: fdea00d4b79990f3fe938e2716bc32bd895eb5c44b6c75b8261db095a1b33c16 - category: main - optional: false -- name: beautifulsoup4 - version: 4.12.2 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - soupsieve: '>=1.2' - url: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.2-pyha770c72_0.conda - hash: - md5: a362ff7d976217f8fa78c0f1c4f59717 - sha256: 52d3e6bcd442537e22699cd227d8fdcfd54b708eeb8ee5b4c671a6a9b9cd74da - category: main - optional: false -- name: cffi - version: 1.15.1 - manager: conda - platform: osx-64 - dependencies: - libffi: '>=3.4,<4.0a0' - pycparser: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.15.1-py39h131948b_3.conda - hash: - md5: 35c1b89ab4359002865052df70939c48 - sha256: e099e8ce3f35906071035fef85cbca94bbbb90d18f231ba8cd1a88577c7d84b3 - category: main - optional: false -- name: deepdiff - version: 6.3.0 - manager: conda - platform: osx-64 - dependencies: - orjson: '' - python: '>=3.7' - ordered-set: '>=4.1.0,<4.2.0' - url: https://conda.anaconda.org/conda-forge/noarch/deepdiff-6.3.0-pyhd8ed1ab_0.conda - hash: - md5: 67ce5e3eecbf1e5ff869269640ae6a53 - sha256: f949d860d532a07587bdb8466310394d8c1af4dd89bb65d65219161fcc16db10 - category: main - optional: false -- name: fonttools - version: 4.39.4 - manager: conda - platform: osx-64 - dependencies: - brotli: '' - munkres: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - unicodedata2: '>=14.0.0' - url: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.39.4-py39hdc70f33_0.conda - hash: - md5: 92c230f068dccdac7dc9d8ffd0469d02 - sha256: 83baba88d6d02793a40ba1c4159f5b9df49c2dceffcb895c358d3a93375d63d7 - category: main - optional: false -- name: gitdb - version: 4.0.10 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.4' - smmap: '>=3.0.1,<4' - url: https://conda.anaconda.org/conda-forge/noarch/gitdb-4.0.10-pyhd8ed1ab_0.conda - hash: - md5: 3706d2f3d7cb5dae600c833345a76132 - sha256: 0003ab2b971913380633c711bf49a54dcf06e179986c725b0925854b58878377 - category: main - optional: false -- name: gunicorn - version: 20.1.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - setuptools: '>=3.0' - url: https://conda.anaconda.org/conda-forge/osx-64/gunicorn-20.1.0-py39h6e9494a_3.tar.bz2 - hash: - md5: fda24af3fc6a6693b80f3e32b1fea627 - sha256: e32e0f0502a288a577807433c381cca6271ace08bf0deb286591521c51b5f55d - category: main - optional: false -- name: h11 - version: 0.14.0 - manager: conda - platform: osx-64 - dependencies: - typing_extensions: '' - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: b21ed0883505ba1910994f1df031a428 - sha256: 817d2c77d53afe3f3d9cf7f6eb8745cdd8ea76c7adaa9d7ced75c455a2c2c085 - category: main - optional: false -- name: html5lib - version: '1.1' - manager: conda - platform: osx-64 - dependencies: - python: '' - webencodings: '' - six: '>=1.9' - url: https://conda.anaconda.org/conda-forge/noarch/html5lib-1.1-pyh9f0ad1d_0.tar.bz2 - hash: - md5: b2355343d6315c892543200231d7154a - sha256: 9ad06446fe9847e86cb20d220bf11614afcd2cbe9f58096f08d5d4018877bee4 - category: main - optional: false -- name: importlib-metadata - version: 6.6.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.8' - zipp: '>=0.5' - url: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.6.0-pyha770c72_0.conda - hash: - md5: f91a5d5175fb7ff2a91952ec7da59cb9 - sha256: 33d49065756a73fbb92277c756fa00a41891408528eb90ae05ff3367a401ae6e - category: main - optional: false -- name: importlib_resources - version: 5.12.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - zipp: '>=3.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.12.0-pyhd8ed1ab_0.conda - hash: - md5: e5fd2260a231ee63b6969f4801082f2b - sha256: 091cca3e010f7a7353152f0abda2d68cfd83ddde80a15e974d9e18b2047e7be2 - category: main - optional: false -- name: jaraco.classes - version: 3.2.3 - manager: conda - platform: osx-64 - dependencies: - more-itertools: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.2.3-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 31e4a1506968d017229bdb64695013a1 - sha256: 6a81b67a1de8f761f66a4540bbd07cc27f9fbf2c7d67aa3732ebef379cf62874 - category: main - optional: false -- name: jinja2 - version: 3.1.2 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - markupsafe: '>=2.0' - url: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2 - hash: - md5: c8490ed5c70966d232fdd389d0dbed37 - sha256: b045faba7130ab263db6a8fdc96b1a3de5fcf85c4a607c5f11a49e76851500b5 - category: main - optional: false -- name: joblib - version: 1.2.0 - manager: conda - platform: osx-64 - dependencies: - setuptools: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.2.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 7583652522d71ad78ba536bba06940eb - sha256: 0c21351871df2c0a53168575597dd9c881e2a9fa4c42fe89a9bcd7fab37f462c - category: main - optional: false -- name: liblapacke - version: 3.9.0 - manager: conda - platform: osx-64 - dependencies: - libblas: 3.9.0 - libcblas: 3.9.0 - liblapack: 3.9.0 - url: https://conda.anaconda.org/conda-forge/osx-64/liblapacke-3.9.0-16_osx64_mkl.tar.bz2 - hash: - md5: ba52eebcca282a5abaa3d3ac79cf2b05 - sha256: 976a73ae841c11f9d54ea98dfe7feff60d6afa82646c11042db575ff3fe86cfa - category: main - optional: false -- name: lightning-utilities - version: 0.8.0 - manager: conda - platform: osx-64 - dependencies: - typing_extensions: '' - python: '>=3.8' - packaging: '>=17.1' - url: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.8.0-pyhd8ed1ab_0.conda - hash: - md5: ad16f58b64d3b41f4cbb75040b06c9cc - sha256: 8c1fff22ab86c85768e65dc8c4f4664787476a21f4d934c4e0261a1fa7523f9c - category: main - optional: false -- name: markdown-it-py - version: 2.2.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - typing_extensions: '>=3.7.4' - mdurl: '>=0.1,<1' - url: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-2.2.0-pyhd8ed1ab_0.conda - hash: - md5: b2928a6c6d52d7e3562b4a59c3214e3a - sha256: 65ed439862c1851463f03a9bc5109992ce3e3e025e9a2d76d13ca19f576eee9f - category: main - optional: false -- name: numpy - version: 1.24.3 - manager: conda - platform: osx-64 - dependencies: - libblas: '>=3.9.0,<4.0a0' - libcblas: '>=3.9.0,<4.0a0' - libcxx: '>=15.0.7' - liblapack: '>=3.9.0,<4.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/numpy-1.24.3-py39h892e69a_0.conda - hash: - md5: 5d8b1ee78d2f4f31c9ffdae159db7f27 - sha256: 0754d4f1c2b994b3807e4b05e45cf3d96d40518b8f21f265bebd77eb3e5d9a87 - category: main - optional: false -- name: omegaconf - version: 2.3.0 - manager: conda - platform: osx-64 - dependencies: - typing_extensions: '' - python: '>=3.7' - pyyaml: '>=5.1.0' - antlr-python-runtime: 4.9.* - url: https://conda.anaconda.org/conda-forge/noarch/omegaconf-2.3.0-pyhd8ed1ab_0.conda - hash: - md5: 23cc056834cab53849b91f78d6ee3ea0 - sha256: df806841be847e5287b22b6ae7f380874f81ea51f1b51ae14a570f3385c7b133 - category: main - optional: false -- name: pexpect - version: 4.8.0 - manager: conda - platform: osx-64 - dependencies: - python: '' - ptyprocess: '>=0.5' - url: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.8.0-pyh1a96a4e_2.tar.bz2 - hash: - md5: 330448ce4403cc74990ac07c555942a1 - sha256: 07706c0417ead94f359ca7278f65452d3c396448777aba1da6a11fc351bdca9a - category: main - optional: false -- name: pip - version: 23.1.2 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - setuptools: '' - wheel: '' - url: https://conda.anaconda.org/conda-forge/noarch/pip-23.1.2-pyhd8ed1ab_0.conda - hash: - md5: 7288da0d36821349cf1126e8670292df - sha256: 4fe1f47f6eac5b2635a622b6f985640bf835843c1d8d7ccbbae0f7d27cadec92 - category: main - optional: false -- name: protobuf - version: 4.21.12 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.6' - libprotobuf: '>=3.21.12,<3.22.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - setuptools: '' - url: https://conda.anaconda.org/conda-forge/osx-64/protobuf-4.21.12-py39h7a8716b_0.conda - hash: - md5: f4fae049b4e5bec90ccda2f0660c0a8a - sha256: d3d67664a222153afb5cd7b115e98a2c2be83d70fe2b9a78d1bb686d4daf68ba - category: main - optional: false -- name: pyproject_hooks - version: 1.0.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - tomli: '>=1.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.0.0-pyhd8ed1ab_0.conda - hash: - md5: 21de50391d584eb7f4441b9de1ad773f - sha256: 016340837fcfef57b351febcbe855eedf0c1f0ecfc910ed48c7fbd20535f9847 - category: main - optional: false -- name: python-dateutil - version: 2.8.2 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - six: '>=1.5' - url: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: dd999d1cc9f79e67dbb855c8924c7984 - sha256: 54d7785c7678166aa45adeaccfc1d2b8c3c799ca2dc05d4a82bb39b1968bd7da - category: main - optional: false -- name: tqdm - version: 4.65.0 - manager: conda - platform: osx-64 - dependencies: - colorama: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.65.0-pyhd8ed1ab_1.conda - hash: - md5: ed792aff3acb977d09c7013358097f83 - sha256: b35f185a678109940d34f68ac5781c3cbda9b118b8d9886b8f68ab5be6afd4fc - category: main - optional: false -- name: typing-extensions - version: 4.5.0 - manager: conda - platform: osx-64 - dependencies: - typing_extensions: 4.5.0 - url: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.5.0-hd8ed1ab_0.conda - hash: - md5: b3c594fde1a80a1fc3eb9cc4a5dfe392 - sha256: 6da5e15fa533620ae2e7aca9a7d16013eed3a73ac64c47d7c3bf3deec39b63b9 - category: main - optional: false -- name: werkzeug - version: 2.3.4 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.8' - markupsafe: '>=2.1.1' - url: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.4-pyhd8ed1ab_0.conda - hash: - md5: 23ddbe41ab0115bc0bfb75dcbf5de7cf - sha256: 2df1970270839b36e13a4ba7e4b393cfa95aa1d7438909aa8c3db14170ea207c - category: main - optional: false -- name: arrow - version: 1.2.3 - manager: conda - platform: osx-64 - dependencies: - typing_extensions: '' - python: '>=3.6' - python-dateutil: '>=2.7.0' - url: https://conda.anaconda.org/conda-forge/noarch/arrow-1.2.3-pyhd8ed1ab_0.tar.bz2 - hash: - md5: fd1967c76eda3a3dd9e8e6cb7a15a028 - sha256: a0434c2770cf5b0ab5a33913c0b202b1521bc13f755b762d16a86b110425cdc2 - category: main - optional: false -- name: aws-c-s3 - version: 0.3.0 - manager: conda - platform: osx-64 - dependencies: - aws-c-auth: '>=0.6.27,<0.6.28.0a0' - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/aws-c-s3-0.3.0-hfaded3d_2.conda - hash: - md5: 660f31db8fd908e23a92efb044caf4cf - sha256: 91cd36b8d550b4617d87ce668b8b912334895aba3a7c9cf2ecba5420e23e0332 - category: main - optional: false -- name: bcrypt - version: 3.2.2 - manager: conda - platform: osx-64 - dependencies: - cffi: '>=1.1' - pip: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - six: '>=1.4.1' - url: https://conda.anaconda.org/conda-forge/osx-64/bcrypt-3.2.2-py39ha30fb19_1.tar.bz2 - hash: - md5: a9a8dcc3e08b0fd0bd02cadeb9e94a17 - sha256: 34936cabbcdf1783b91ef7d2d46509394e6f91905009b184c9aec9cfeb30c736 - category: main - optional: false -- name: blas-devel - version: 3.9.0 - manager: conda - platform: osx-64 - dependencies: - libblas: 3.9.0 - libcblas: 3.9.0 - liblapack: 3.9.0 - liblapacke: 3.9.0 - mkl: '>=2022.1.0,<2023.0a0' - mkl-devel: 2022.1.* - url: https://conda.anaconda.org/conda-forge/osx-64/blas-devel-3.9.0-16_osx64_mkl.tar.bz2 - hash: - md5: 2fb6331f94446754c896d1f11d3afa1c - sha256: 8569f8a7175233a655e5dc0b2e5544f92b8f68210583f28fac94d5dce8ef4f47 - category: main - optional: false -- name: brotlipy - version: 0.7.0 - manager: conda - platform: osx-64 - dependencies: - cffi: '>=1.0.0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/brotlipy-0.7.0-py39ha30fb19_1005.tar.bz2 - hash: - md5: 201d86c1f0b0132954fc72251b09df8a - sha256: 0204c1d5ab773e956233c0a6941f87faf7e9dc3fe30dec0d34f04091309859d8 - category: main - optional: false -- name: contourpy - version: 1.0.7 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.6' - numpy: '>=1.16' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/contourpy-1.0.7-py39h92daf61_0.conda - hash: - md5: 3b50cfd6ea07613741693ba535fcefda - sha256: e62b248506d690eaea2de499555288665ca0508d54efe63690638f1b39e6e775 - category: main - optional: false -- name: croniter - version: 1.3.15 - manager: conda - platform: osx-64 - dependencies: - python-dateutil: '' - python: '>=2.6' - url: https://conda.anaconda.org/conda-forge/noarch/croniter-1.3.15-pyhd8ed1ab_0.conda - hash: - md5: 50197abb95aa7024eb0eb58fe5a51b07 - sha256: f8f58f6a50a5f63a35ee3bf6805e6dee10fe910f17a339da038967118c12c64f - category: main - optional: false -- name: cryptography - version: 41.0.0 - manager: conda - platform: osx-64 - dependencies: - cffi: '>=1.12' - openssl: '>=3.1.1,<4.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/cryptography-41.0.0-py39he8b0a07_0.conda - hash: - md5: a7ff5b4784e3d796270070987896210b - sha256: d4241ce6dd84f3a7a093ed67d690e37636a9c6ef23029e590b7fc851bcaa9279 - category: main - optional: false -- name: dateutils - version: 0.6.12 - manager: conda - platform: osx-64 - dependencies: - python-dateutil: '' - pytz: '' - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/dateutils-0.6.12-py_0.tar.bz2 - hash: - md5: acee371a07e9a38a7072e5a5f7054ead - sha256: fb554b32a8f880cafaff4e67c789965d97c41eb1a6cc9ab5a83c6b28b581d809 - category: main - optional: false -- name: flask - version: 2.3.2 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.8' - jinja2: '>=3.1.2' - click: '>=8.1.3' - importlib-metadata: '>=3.6.0' - itsdangerous: '>=2.1.2' - blinker: '>=1.6.2' - werkzeug: '>=2.3.3' - url: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.2-pyhd8ed1ab_0.conda - hash: - md5: 816d75d4c0f2e41b5765d17498c57a2e - sha256: f93246be286f2d0f93e85c4f08f9ce48f3eed875a79225e2ea119e70c0237421 - category: main - optional: false -- name: gitpython - version: 3.1.31 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - typing_extensions: '>=3.7.4.3' - gitdb: '>=4.0.1,<5' - url: https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.31-pyhd8ed1ab_0.conda - hash: - md5: f6e6b482110246a81c3f03e81c68752d - sha256: 77c531def610089bc190508fcf304cf96c085c5fe977ab8f7d7c1641769592ac - category: main - optional: false -- name: importlib-resources - version: 5.12.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - importlib_resources: '>=5.12.0,<5.12.1.0a0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-5.12.0-pyhd8ed1ab_0.conda - hash: - md5: 3544c818f0720c89eb16ae6940ab440b - sha256: 0675df2bf18e52d0ea2bc5e1009faac273f059361a0caf36c0e0edc7831098a9 - category: main - optional: false -- name: importlib_metadata - version: 6.6.0 - manager: conda - platform: osx-64 - dependencies: - importlib-metadata: '>=6.6.0,<6.6.1.0a0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-6.6.0-hd8ed1ab_0.conda - hash: - md5: 3cbc9615f10a3d471532b83e4250b971 - sha256: 5de35d3c019d8a36e0a0deeb04a62689837bd68234a0a73a3355b860b442eca4 - category: main - optional: false -- name: jsonschema - version: 4.17.3 - manager: conda - platform: osx-64 - dependencies: - typing_extensions: '' - importlib-metadata: '' - python: '>=3.7' - attrs: '>=17.4.0' - pyrsistent: '!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0' - importlib_resources: '>=1.4.0' - pkgutil-resolve-name: '>=1.3.10' - url: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.17.3-pyhd8ed1ab_0.conda - hash: - md5: 723268a468177cd44568eb8f794e0d80 - sha256: 4f68a23430d1afc5c9b41c46fbac0ade33c0bf57a293c646bfdd6dc65350eada - category: main - optional: false -- name: mako - version: 1.2.4 - manager: conda - platform: osx-64 - dependencies: - importlib-metadata: '' - python: '>=3.6' - markupsafe: '>=0.9.2' - url: https://conda.anaconda.org/conda-forge/noarch/mako-1.2.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 0d072f0edc017b6318dbab701e053f94 - sha256: 559ed0d4c600d9827c1e9e0f2f3a50724bf2281b28a04e08f60de63f0da309a6 - category: main - optional: false -- name: markdown - version: 3.4.3 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - importlib-metadata: '>=4.4' - url: https://conda.anaconda.org/conda-forge/noarch/markdown-3.4.3-pyhd8ed1ab_0.conda - hash: - md5: 89ed59ad509c05db6f5f2f573d499bfe - sha256: e32ac2c95112caa8cd81f0cbc710f4f4903180a115c7260f03b010d5a0aa771b - category: main - optional: false -- name: pandas - version: 2.0.2 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=15.0.7' - numpy: '>=1.21.6,<2.0a0' - python: '>=3.9,<3.10.0a0' - python-dateutil: '>=2.8.1' - python-tzdata: '>=2022a' - python_abi: 3.9.* - pytz: '>=2020.1' - url: https://conda.anaconda.org/conda-forge/osx-64/pandas-2.0.2-py39h11b3245_0.conda - hash: - md5: d3c14b5babbb9df7fdfad1384434610f - sha256: 38650b2bf74e14aee08ec9c4f85e2a7599b2944c74bac018a25369302cf81b9d - category: main - optional: false -- name: platformdirs - version: 3.5.1 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - typing-extensions: '>=4.5' - url: https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.5.1-pyhd8ed1ab_0.conda - hash: - md5: e2be672aece1f060adf7154f76531a35 - sha256: d7845c01a9ee5a224cc9242782befed7d12dc6aac1103650ec87917b20f3579e - category: main - optional: false -- name: poetry-core - version: 1.6.1 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - importlib-metadata: '>=1.7.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-core-1.6.1-pyhd8ed1ab_0.conda - hash: - md5: a6d1f61527c27fcc0165a6701a46b9f4 - sha256: 6f6a66476908a1c109e36c852d934eedceb07e0cbc44d3fcd87c6f39521b57be - category: main - optional: false -- name: pydantic - version: 1.10.8 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - typing-extensions: '>=4.2.0' - url: https://conda.anaconda.org/conda-forge/osx-64/pydantic-1.10.8-py39hdc70f33_0.conda - hash: - md5: 414abfdc8cef81fb1b5d5b5a1e9e975f - sha256: 65dd7fadf7ed3874c7b272b48c8d062456b509899c1a04cdae4f9855455bd35d - category: main - optional: false -- name: pynacl - version: 1.5.0 - manager: conda - platform: osx-64 - dependencies: - cffi: '>=1.4.1' - libsodium: '>=1.0.18,<1.0.19.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - six: '' - url: https://conda.anaconda.org/conda-forge/osx-64/pynacl-1.5.0-py39ha30fb19_2.tar.bz2 - hash: - md5: 42e5a55dd3b34daf949304bb3df4d86a - sha256: 62375e110fb3a9453cb8aa78c8886ba819ba38a72a6204233d77573723dc2b33 - category: main - optional: false -- name: python-build - version: 0.10.0 - manager: conda - platform: osx-64 - dependencies: - colorama: '' - pyproject_hooks: '' - python: '>=3.7' - tomli: '>=1.1.0' - packaging: '>=19.0' - importlib-metadata: '>=0.22' - url: https://conda.anaconda.org/conda-forge/noarch/python-build-0.10.0-pyhd8ed1ab_1.conda - hash: - md5: 0ab47ce574f6a8bcb9f2076436e7fedb - sha256: 4c2cd519c85aa8b8e584723ca5f452aa5941d18374470adebfe73bf30fd27573 - category: main - optional: false -- name: rapidfuzz - version: 2.15.1 - manager: conda - platform: osx-64 - dependencies: - libcxx: '>=14.0.6' - numpy: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/rapidfuzz-2.15.1-py39h7a8716b_0.conda - hash: - md5: 2d8a2543ef9b983ef7c77cb494954776 - sha256: 269b23cf7520e254ba11be36bfebfc29f146a3da7ddcbaf2781781281514321c - category: main - optional: false -- name: rich - version: 13.4.1 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7.0' - typing_extensions: '>=4.0.0,<5.0.0' - markdown-it-py: '>=2.2.0,<3.0.0' - pygments: '>=2.13.0,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/rich-13.4.1-pyhd8ed1ab_0.conda - hash: - md5: c3bcbe0d086f15e5918568d3865e4dbf - sha256: 312f2628e06a591096a851bf678833fe670ecb16e9b15517ce8e03d7c9d9e600 - category: main - optional: false -- name: sqlalchemy - version: 2.0.15 - manager: conda - platform: osx-64 - dependencies: - greenlet: '!=0.4.17' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - typing-extensions: '>=4.2.0' - url: https://conda.anaconda.org/conda-forge/osx-64/sqlalchemy-2.0.15-py39hdc70f33_0.conda - hash: - md5: be78a99849c2121958f4732df2ff93a0 - sha256: 99ea87e877a5fabd2f463463332e6bd8291bda2acf9c3514a03357fbb2ef1d86 - category: main - optional: false -- name: starlette - version: 0.22.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - typing_extensions: '>=3.10.0' - anyio: <5,>=3.4.0 - url: https://conda.anaconda.org/conda-forge/noarch/starlette-0.22.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 49d5cdcc16c691e4ad9355c81f004c3e - sha256: 1441dd55c037184b7d2c1e1dbf60beafb1f92fdc13cabf78a85e12825a55269b - category: main - optional: false -- name: uvicorn - version: 0.22.0 - manager: conda - platform: osx-64 - dependencies: - click: '>=7.0' - h11: '>=0.8' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/uvicorn-0.22.0-py39h6e9494a_0.conda - hash: - md5: 361bba5a1f6b485d149971a88bcadd55 - sha256: 827ca932c42f0342b9d8f4c0a60c9371fe90dc9d8666dcf5c37f96461e1f8b1d - category: main - optional: false -- name: wcwidth - version: 0.2.6 - manager: conda - platform: osx-64 - dependencies: - backports.functools_lru_cache: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.6-pyhd8ed1ab_0.conda - hash: - md5: 078979d33523cb477bd1916ce41aacc9 - sha256: c1bd0ad7d854cae56977b7915ac2b78b652fa5f7ec1e9fc21e7fdb30cf4519b1 - category: main - optional: false -- name: xattr - version: 0.10.1 - manager: conda - platform: osx-64 - dependencies: - cffi: '>=1.0.0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/xattr-0.10.1-py39ha30fb19_0.conda - hash: - md5: b5fedc04198cdc326228aea3ce8f39c8 - sha256: 271e08cad7fad6995d6a8943a8162cfb3e0ad9b27bf26d355c7ed1a2c5d7d2e3 - category: main - optional: false -- name: alembic - version: 1.11.1 - manager: conda - platform: osx-64 - dependencies: - importlib-metadata: '' - importlib_resources: '' - mako: '' - python: '>=3.7' - sqlalchemy: '>=1.3.0' - typing-extensions: '>=4' - url: https://conda.anaconda.org/conda-forge/noarch/alembic-1.11.1-pyhd8ed1ab_0.conda - hash: - md5: 6a55e123397b42b79c48b31d1b7a91b8 - sha256: 065dd1b38ebe3a0d14f45549f63cce55125052057db565be153cdd73aa2a7c8d - category: main - optional: false -- name: aws-crt-cpp - version: 0.20.2 - manager: conda - platform: osx-64 - dependencies: - aws-c-auth: '>=0.6.27,<0.6.28.0a0' - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-event-stream: '>=0.2.20,<0.2.21.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-c-mqtt: '>=0.8.11,<0.8.12.0a0' - aws-c-s3: '>=0.3.0,<0.3.1.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - libcxx: '>=15.0.7' - url: https://conda.anaconda.org/conda-forge/osx-64/aws-crt-cpp-0.20.2-hb50731f_0.conda - hash: - md5: 35fa6202a57e1ad87c5b18288aa999d0 - sha256: d3f23e28eb365165688ef683b01000e6451468b19c0b56614348990ba5713297 - category: main - optional: false -- name: blas - version: '2.116' - manager: conda - platform: osx-64 - dependencies: - blas-devel: 3.9.0 - libblas: 3.9.0 - libcblas: 3.9.0 - libgfortran: 5.* - libgfortran5: '>=9.3.0' - liblapack: 3.9.0 - liblapacke: 3.9.0 - url: https://conda.anaconda.org/conda-forge/osx-64/blas-2.116-mkl.tar.bz2 - hash: - md5: bcaf774ad76aa575f4b60c585c2a8dab - sha256: dc3d5aa06da3a4b70f42f03c98147159737a261139ac88168a7c64197714e561 - category: main - optional: false -- name: blessed - version: 1.19.1 - manager: conda - platform: osx-64 - dependencies: - __unix: '' - python: '>=3.8' - six: '>=1.9.0' - wcwidth: '>=0.1.4' - url: https://conda.anaconda.org/conda-forge/noarch/blessed-1.19.1-pyhe4f9e05_2.tar.bz2 - hash: - md5: 65486376a55a80933e5dd95681ddd8b8 - sha256: 9d5b1f751adfe6d77fa8a088417a3aed716a1f727c0fd0230195246362b9d562 - category: main - optional: false -- name: cleo - version: 2.0.1 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7,<4.0' - crashtest: '>=0.4.1,<0.5.0' - rapidfuzz: '>=2.2.0,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/cleo-2.0.1-pyhd8ed1ab_0.conda - hash: - md5: f1c5f2af6676cbe9206e191d1e70f661 - sha256: cf9bc4c9356ad8eb68512446eebc076386f2bfb8ca86626e8796621bc5a13082 - category: main - optional: false -- name: fastapi - version: 0.88.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - pydantic: '>=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0' - starlette: 0.22.0.* - url: https://conda.anaconda.org/conda-forge/noarch/fastapi-0.88.0-pyhd8ed1ab_0.conda - hash: - md5: 0bd34a2b460d02e2fbf88ca5d9d3e55d - sha256: 660b356b8d4f3b99b6294c638637699003b0533c0ecab9389c56367b4bfe5c59 - category: main - optional: false -- name: keyring - version: 23.13.1 - manager: conda - platform: osx-64 - dependencies: - importlib_metadata: '>=4.11.4' - jaraco.classes: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/keyring-23.13.1-py39h6e9494a_0.conda - hash: - md5: b62026973caa4606de85fe7078ec477f - sha256: f3ebf0fa77a1e97edcba2744dc74a97944adf4a7d98013e328a79f64caba8b31 - category: main - optional: false -- name: matplotlib-base - version: 3.7.1 - manager: conda - platform: osx-64 - dependencies: - __osx: '>=10.12' - certifi: '>=2020.06.20' - contourpy: '>=1.0.1' - cycler: '>=0.10' - fonttools: '>=4.22.0' - freetype: '>=2.12.1,<3.0a0' - importlib-resources: '>=3.2.0' - kiwisolver: '>=1.0.1' - libcxx: '>=14.0.6' - numpy: '>=1.20.3,<2.0a0' - packaging: '>=20.0' - pillow: '>=6.2.0' - pyparsing: '>=2.3.1' - python: '>=3.9,<3.10.0a0' - python-dateutil: '>=2.7' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.7.1-py39hb2f573b_0.conda - hash: - md5: e6b1b5ed86e30d21bf00a050c1a47af5 - sha256: 722e58b570a63cf199f7c4334233b12086a6caeca392969fef239ed790c45565 - category: main - optional: false -- name: oauthlib - version: 3.2.2 - manager: conda - platform: osx-64 - dependencies: - cryptography: '' - blinker: '' - python: '>=3.6' - pyjwt: '>=1.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/oauthlib-3.2.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 8f882b197fd9c4941a787926baea4868 - sha256: 0cfd5146a91d3974f4abfc2a45de890371d510a77238fe553e036ec8c031dc5b - category: main - optional: false -- name: paramiko - version: 3.2.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - cryptography: '>=3.3' - bcrypt: '>=3.2' - pynacl: '>=1.5' - url: https://conda.anaconda.org/conda-forge/noarch/paramiko-3.2.0-pyhd8ed1ab_0.conda - hash: - md5: f212c7eb95e909df4795297f73690993 - sha256: e425a03e5e2ef2ec5a78711686c59cfceeeeec3a98165fbc7d186bd6a5cb78de - category: main - optional: false -- name: poetry-plugin-export - version: 1.4.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7,<4.0' - poetry-core: '>=1.6.0,<2.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-plugin-export-1.4.0-pyhd8ed1ab_0.conda - hash: - md5: 00893c7ea4f9f7620706e0aa94c01b6e - sha256: 54478b283b5967a85ee5da717f1512d7ec97eb07c7b52d1f2ad3cb080d56c0ac - category: main - optional: false -- name: prometheus_flask_exporter - version: 0.22.4 - manager: conda - platform: osx-64 - dependencies: - flask: '' - prometheus_client: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/prometheus_flask_exporter-0.22.4-pyhd8ed1ab_0.conda - hash: - md5: 43acea130cafd18740b73fa4c226c9f7 - sha256: be83619ef5964713cd298d0fb86eddd99b159e5fba3d0f91d624ce5d7c3890e0 - category: main - optional: false -- name: pyopenssl - version: 23.2.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - cryptography: '>=38.0.0,<42,!=40.0.0,!=40.0.1' - url: https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.2.0-pyhd8ed1ab_1.conda - hash: - md5: 34f7d568bf59d18e3fef8c405cbece21 - sha256: 4daea3dc896987cc1334956fccfc0ed738663a84ad0c1d3f576a7a7936091534 - category: main - optional: false -- name: starsessions - version: 1.3.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6.2' - itsdangerous: '>=2.0.1' - starlette: '>=0' - url: https://conda.anaconda.org/conda-forge/noarch/starsessions-1.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 667d08040a85d7ea1c6d4af2290f96c4 - sha256: 4a500ac0a9fe56cee7958d6d0f6530272c43ee4c16c52600001decb39fe3cd59 - category: main - optional: false -- name: typer - version: 0.9.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - typing-extensions: '>=3.7.4.3' - colorama: '>=0.4.3,<0.5.0' - shellingham: '>=1.3.0,<2.0.0' - click: '>=7.1.1,<9' - rich: '>=10.11.0,<14.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/typer-0.9.0-pyhd8ed1ab_0.conda - hash: - md5: 5030a13b2fe5e143d5956d4943d3018f - sha256: d395e1e92281abb13e043220ecf8ea973ada8d38a1e8c683df14f46541c64bd2 - category: main - optional: false -- name: virtualenv - version: 20.23.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.8' - distlib: <1,>=0.3.6 - filelock: <4,>=3.11 - platformdirs: <4,>=3.2 - url: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.23.0-pyhd8ed1ab_0.conda - hash: - md5: a920e114c4c2ced2280e266da65ab5e6 - sha256: 13d667887ea08b6d1fe2eb09d2d737f9af7343735d3bfa5ffaa3f67eec8eaff7 - category: main - optional: false -- name: aws-sdk-cpp - version: 1.10.57 - manager: conda - platform: osx-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-event-stream: '>=0.2.20,<0.2.21.0a0' - aws-crt-cpp: '>=0.20.2,<0.20.3.0a0' - libcurl: '>=8.1.1,<9.0a0' - libcxx: '>=15.0.7' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/aws-sdk-cpp-1.10.57-hf90da8b_13.conda - hash: - md5: 53cc1f9dce566b35f7f6127d8b8a74e7 - sha256: 9c86565d21eb28f7cb988d68c8d365b55989e9ed21b0476562dce07a98530173 - category: main - optional: false -- name: pytorch - version: 1.13.1 - manager: conda - platform: osx-64 - dependencies: - blas: '*' - mkl: '>=2018' - python: '>=3.9,<3.10.0a0' - typing_extensions: '' - url: https://conda.anaconda.org/pytorch/osx-64/pytorch-1.13.1-py3.9_0.tar.bz2 - hash: - md5: f06f796ab69638eb81c2f4c8077cfd73 - sha256: ad7c46dc150c3493eb580f33bf6bbc91ac38886f0d9e8d774303cb6d2ff4e7e9 - category: main - optional: false -- name: urllib3 - version: 1.26.15 - manager: conda - platform: osx-64 - dependencies: - certifi: '' - python: <4.0 - idna: '>=2.0.0' - pyopenssl: '>=0.14' - pysocks: '>=1.5.6,<2.0,!=1.5.7' - cryptography: '>=1.3.4' - brotlipy: '>=0.6.0' - url: https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.15-pyhd8ed1ab_0.conda - hash: - md5: 27db656619a55d727eaf5a6ece3d2fd6 - sha256: 213bdf6c3a5d721fa83b45d527d3ecd340f9547c0d6bbd0b8d9d746ec9a1fb4b - category: main - optional: false -- name: dulwich - version: 0.21.5 - manager: conda - platform: osx-64 - dependencies: - certifi: '' - cryptography: '>=1.3.4' - idna: '>=2.0.0' - pyopenssl: '>=0.14' - pysocks: '>=1.5.6,<2.0,!=1.5.7' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - urllib3: '' - url: https://conda.anaconda.org/conda-forge/osx-64/dulwich-0.21.5-py39hdc70f33_0.conda - hash: - md5: f1b9eb520e70fa8c2e947fe76478ad09 - sha256: 274e2d750642ba905c44709316f08b27027e8fdd4c44edbaa39347203ca00414 - category: main - optional: false -- name: libarrow - version: 11.0.0 - manager: conda - platform: osx-64 - dependencies: - aws-crt-cpp: '>=0.20.2,<0.20.3.0a0' - aws-sdk-cpp: '>=1.10.57,<1.10.58.0a0' - bzip2: '>=1.0.8,<2.0a0' - c-ares: '>=1.19.1,<2.0a0' - gflags: '>=2.2.2,<2.3.0a0' - glog: '>=0.6.0,<0.7.0a0' - libabseil: '>=20230125.2,<20230126.0a0' - libbrotlicommon: '>=1.0.9,<1.1.0a0' - libbrotlidec: '>=1.0.9,<1.1.0a0' - libbrotlienc: '>=1.0.9,<1.1.0a0' - libcxx: '>=15.0.7' - libevent: '>=2.1.12,<2.1.13.0a0' - libgoogle-cloud: '>=2.10.1,<2.10.2.0a0' - libgrpc: '>=1.54.2,<1.55.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - libthrift: '>=0.18.1,<0.18.2.0a0' - libutf8proc: '>=2.8.0,<3.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - openssl: '>=3.1.0,<4.0a0' - orc: '>=1.8.3,<1.8.4.0a0' - re2: '>=2023.3.2,<2023.3.3.0a0' - snappy: '>=1.1.10,<2.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/libarrow-11.0.0-h8fc5235_21_cpu.conda - hash: - md5: 309a5cf89c7aa4beba02681cd807900f - sha256: 9573562856b61f50f8364c2ff755d0c83f2d41aeb0872616c820a87e4654d677 - category: main - optional: false -- name: requests - version: 2.31.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - idna: '>=2.5,<4' - certifi: '>=2017.4.17' - charset-normalizer: '>=2,<4' - urllib3: '>=1.21.1,<3' - url: https://conda.anaconda.org/conda-forge/noarch/requests-2.31.0-pyhd8ed1ab_0.conda - hash: - md5: a30144e4156cdbb236f99ebb49828f8b - sha256: 9f629d6fd3c8ac5f2a198639fe7af87c4db2ac9235279164bfe0fcb49d8c4bad - category: main - optional: false -- name: torchmetrics - version: 0.11.4 - manager: conda - platform: osx-64 - dependencies: - setuptools: '' - packaging: '' - python: '>=3.7' - pytorch: '>=1.8.1' - url: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-0.11.4-pyhd8ed1ab_0.conda - hash: - md5: 480cb2b6d502003e937d9e4326bc398f - sha256: 3927eae14903c76679d5151af39680e7d42c35e0bdaa5041f577fc40f0619be8 - category: main - optional: false -- name: arrow-cpp - version: 11.0.0 - manager: conda - platform: osx-64 - dependencies: - libarrow: 11.0.0 - url: https://conda.anaconda.org/conda-forge/osx-64/arrow-cpp-11.0.0-h694c41f_21_cpu.conda - hash: - md5: 62d078d3396913ae4166d9e3a2c13132 - sha256: fc79acbff145c8153887d7a0972c0b3d9359f7f407a56e49633e6d2707bd45b2 - category: main - optional: false -- name: cachecontrol - version: 0.12.11 - manager: conda - platform: osx-64 - dependencies: - requests: '' - python: '>=3.6' - msgpack-python: '>=0.5.2' - url: https://conda.anaconda.org/conda-forge/noarch/cachecontrol-0.12.11-pyhd8ed1ab_1.conda - hash: - md5: e8f0410e0aa03342304357c5cc3bb75d - sha256: 466ce7c155be90a5c903052eba391759ae88eb65f2bb06b0cc1c9d09c4311800 - category: main - optional: false -- name: databricks-cli - version: 0.17.7 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - click: '>=7.0' - six: '>=1.10.0' - pyjwt: '>=1.7.0' - tabulate: '>=0.7.7' - requests: '>=2.17.3' - configparser: '>=0.3.5' - oauthlib: '>=3.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/databricks-cli-0.17.7-pyhd8ed1ab_0.conda - hash: - md5: cb44b4e93848f13dce352c422285ac45 - sha256: 1c2ec6c6125bc1c1d100b75e1f5dfda09c56cd516157d2d5b01c1aa69fdd0dbd - category: main - optional: false -- name: docker-py - version: 6.1.0 - manager: conda - platform: osx-64 - dependencies: - pywin32-on-windows: '' - python: '>=3.7' - requests: '>=2.26.0' - urllib3: '>=1.26.0' - websocket-client: '>=0.32.0' - packaging: '>=14.0' - paramiko: '>=2.4.3' - url: https://conda.anaconda.org/conda-forge/noarch/docker-py-6.1.0-pyhd8ed1ab_0.conda - hash: - md5: 543336c6aa9516cfb29c51d5c162b177 - sha256: 5e01e15e20ee573c99b530633a0d5c71fd515e4ac6d2f5f5f57baece8b915cc3 - category: main - optional: false -- name: lightning-cloud - version: 0.5.36 - manager: conda - platform: osx-64 - dependencies: - requests: '' - six: '' - click: '' - rich: '' - pyjwt: '' - urllib3: '' - fastapi: '' - websocket-client: '' - python-multipart: '' - uvicorn: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/lightning-cloud-0.5.36-pyhd8ed1ab_0.conda - hash: - md5: fd99cc369aa3c6c66493d4d278338af5 - sha256: 2047f4dcd4531f6cd2f87a80fef0d265f663e011931af746b2725f7d567ce016 - category: main - optional: false -- name: pooch - version: 1.7.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.7' - packaging: '>=20.0' - requests: '>=2.19.0' - platformdirs: '>=2.5.0' - url: https://conda.anaconda.org/conda-forge/noarch/pooch-1.7.0-pyha770c72_3.conda - hash: - md5: 5936894aade8240c867d292aa0d980c6 - sha256: 64e4d633803df2e36fd141d9bf269568fbe179a313248e1dac4d364c02debdef - category: main - optional: false -- name: pytorch-lightning - version: 2.0.2 - manager: conda - platform: osx-64 - dependencies: - requests: '' - python: '>=3.8' - pyyaml: '>=5.4' - numpy: '>=1.17.2' - packaging: '>=17.1' - torchmetrics: '>=0.7.0' - tqdm: '>=4.57.0' - typing_extensions: '>=4.0.0' - pytorch: '>=1.11.0' - fsspec: '>2021.06.0' - lightning-utilities: '>=0.7.0' - url: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.0.2-pyhd8ed1ab_0.conda - hash: - md5: abd4916f586ae33b56d1e2b44a7990aa - sha256: 9332cb927d6c587ed5b23628208ebb4479288244f4388bd4cb9745df115cca25 - category: main - optional: false -- name: querystring_parser - version: 1.2.4 - manager: conda - platform: osx-64 - dependencies: - python: '' - requests: '' - six: '' - url: https://conda.anaconda.org/conda-forge/noarch/querystring_parser-1.2.4-py_0.tar.bz2 - hash: - md5: 0ebdca9b753c2e082e5b5ad06aa76b41 - sha256: 06977a9af6d8605fb6068d8af6bb9c1cb565f8f5e15aa6cf0fb94109d4148b54 - category: main - optional: false -- name: requests-toolbelt - version: 1.0.0 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - requests: '>=2.0.1,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-1.0.0-pyhd8ed1ab_0.conda - hash: - md5: 99c98318c8646b08cc764f90ce98906e - sha256: 20eaefc5dba74ff6c31e537533dde59b5b20f69e74df49dff19d43be59785fa3 - category: main - optional: false -- name: torchvision - version: 0.14.1 - manager: conda - platform: osx-64 - dependencies: - ffmpeg: '>=4.2' - jpeg: '' - libpng: '' - numpy: '>=1.11' - pillow: '>=5.3.0,!=8.3.*' - python: '>=3.9,<3.10.0a0' - pytorch: 1.13.1 - requests: '' - url: https://conda.anaconda.org/pytorch/osx-64/torchvision-0.14.1-py39_cpu.tar.bz2 - hash: - md5: 5aada860b5745d6976e0d937d234e0f6 - sha256: 4accd5fd017b51fbc9fde6e43b431182d2356b443f12bb5281f9cd2c711deeb0 - category: main - optional: false -- name: cachecontrol-with-filecache - version: 0.12.11 - manager: conda - platform: osx-64 - dependencies: - python: '>=3.6' - lockfile: '>=0.9' - cachecontrol: 0.12.11 - url: https://conda.anaconda.org/conda-forge/noarch/cachecontrol-with-filecache-0.12.11-pyhd8ed1ab_1.conda - hash: - md5: 9df660456c0076d27b802448f7ede78f - sha256: 81c483fc92656873eb5a7ba657b208c34186556d942a9cebc1f7771e565b95b7 - category: main - optional: false -- name: parquet-cpp - version: 1.5.1 - manager: conda - platform: osx-64 - dependencies: - arrow-cpp: '>=0.11.0' - url: https://conda.anaconda.org/conda-forge/noarch/parquet-cpp-1.5.1-2.tar.bz2 - hash: - md5: 79a5f78c42817594ae016a7896521a97 - sha256: 15e50657515b791734ba045da5135377404ca37c518b2066b9c6451c65cd732e - category: main - optional: false -- name: scipy - version: 1.10.1 - manager: conda - platform: osx-64 - dependencies: - libblas: '>=3.9.0,<4.0a0' - libcblas: '>=3.9.0,<4.0a0' - libcxx: '>=15.0.7' - libgfortran: 5.* - libgfortran5: '>=12.2.0' - liblapack: '>=3.9.0,<4.0a0' - numpy: '>=1.21.6,<2.0a0' - pooch: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.10.1-py39hded996c_3.conda - hash: - md5: 6e2d428f46a2724b2e46ff47299974bf - sha256: 78c77c4bfd2bfec1bb65e85a9f7529e49151e70ced68dcbed58ebd906c0b5c23 - category: main - optional: false -- name: poetry - version: 1.5.1 - manager: conda - platform: osx-64 - dependencies: - __osx: '' - tomli: '>=2.0.1,<3.0.0' - packaging: '>=20.4' - importlib-metadata: '>=4.4' - python: '>=3.7.0,<4.0.0' - urllib3: '>=1.26.0,<2.0.0' - crashtest: '>=0.4.1,<0.5.0' - requests: '>=2.18,<3.0' - pexpect: '>=4.7.0,<5.0.0' - cleo: '>=2.0.0,<3.0.0' - filelock: '>=3.8.0,<4.0.0' - jsonschema: '>=4.10.0,<5.0.0' - keyring: '>=23.9.0,<24.0.0' - trove-classifiers: '>=2022.5.19' - lockfile: '>=0.12.2,<0.13.0' - backports.cached-property: '>=1.0.2,<2.0.0' - dulwich: '>=0.21.2,<0.22.0' - pkginfo: '>=1.9.4,<2.0' - pyproject_hooks: '>=1.0.0,<2.0.0' - python-build: '>=0.10.0,<0.11.0' - python-installer: '>=0.7.0,<0.8.0' - xattr: '>=0.10.0,<0.11.0' - platformdirs: '>=3.0.0,<4.0.0' - requests-toolbelt: '>=0.9.1,<2' - tomlkit: '>=0.11.4,<1.0.0' - virtualenv: '>=20.22.0,<21.0.0' - cachecontrol-with-filecache: '>=0.12.9,<0.13.0' - html5lib: '>=1.0.0,<2.0.0' - poetry-core: 1.6.1.* - poetry-plugin-export: '>=1.4.0,<2.0.0' - shellingham: '>=1.5.0,<2.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-1.5.1-osx_pyhd8ed1ab_0.conda - hash: - md5: cb22f4398986b6cde646b8ec6efe141f - sha256: 9a24f0249abad2b3afbe6ff13f71abc35699e8f4e04bed3d9739c7d93689aade - category: main - optional: false -- name: pyarrow - version: 11.0.0 - manager: conda - platform: osx-64 - dependencies: - gflags: '>=2.2.2,<2.3.0a0' - libarrow: 11.0.0 - libcxx: '>=15.0.7' - numpy: '>=1.21.6,<2.0a0' - parquet-cpp: 1.5.1.* - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-64/pyarrow-11.0.0-py39h105b94d_21_cpu.conda - hash: - md5: 6acb4f72c72904529804e376fb6ea699 - sha256: 22cd9b887cd0e48919ca232d0a3ff595a33f928129d1d299a82c673c88d4e287 - category: main - optional: false -- name: scikit-learn - version: 1.2.2 - manager: conda - platform: osx-64 - dependencies: - joblib: '>=1.1.1' - libcxx: '>=15.0.7' - llvm-openmp: '>=15.0.7' - numpy: '>=1.21.6,<2.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - scipy: '' - threadpoolctl: '>=2.0.0' - url: https://conda.anaconda.org/conda-forge/osx-64/scikit-learn-1.2.2-py39hcb35850_2.conda - hash: - md5: 64e1b9f33c5696c06fad72b4e0e93426 - sha256: 3bc669381a064538ab78f6724b2b9cc9250ecbb6d19c23934b3280046896ce4e - category: main - optional: false -- name: inquirer - version: 3.1.3 - manager: conda - platform: osx-64 - dependencies: - poetry: '' - python: '>=3.7' - blessed: '>=1.19.0' - python-editor: '>=1.0.4' - readchar: '>=2.0.1' - url: https://conda.anaconda.org/conda-forge/noarch/inquirer-3.1.3-pyhd8ed1ab_0.conda - hash: - md5: 0d8bc31361e09dc50555465284e10880 - sha256: da912877ac6e0795490834c96167e93a1eda89290ef8de63502740ef738d4435 - category: main - optional: false -- name: mlflow - version: 2.3.2 - manager: conda - platform: osx-64 - dependencies: - alembic: <2,!=1.10 - click: '>=7.0,<9' - cloudpickle: <3 - databricks-cli: '>=0.8.7,<1' - docker-py: '>=4.0.0,<7' - entrypoints: <1 - flask: <3 - gitpython: '>=2.1.0,<4' - gunicorn: <21 - importlib-metadata: <7,>=3.7.0,!=4.7.0 - jinja2: <4,>=2.11 - markdown: <4,>=3.3 - matplotlib-base: <4 - numpy: <2 - openssl: '' - packaging: <24 - pandas: <3 - prometheus_flask_exporter: <1 - protobuf: '>=3.12.0,<5' - pyarrow: <12,>=4.0.0 - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - pytz: <2024 - pyyaml: '>=5.1,<7' - querystring_parser: <2 - requests: '>=2.17.3,<3' - scikit-learn: <2 - scipy: <2 - sqlalchemy: '>=1.4.0,<3' - sqlparse: '>=0.4.0,<1' - url: https://conda.anaconda.org/conda-forge/osx-64/mlflow-2.3.2-py39h8ac9d56_1.conda - hash: - md5: 36d4132d4b3dd058fa658dbcd5ce250a - sha256: 955290b46a0916af41105c2fe6a641d61da2ee56015e9a9b21ce5ee5da56c370 - category: main - optional: false -- name: lightning - version: 2.0.0 - manager: conda - platform: osx-64 - dependencies: - packaging: '' - pytorch-lightning: '' - python-multipart: '' - python: '>=3.8' - arrow: <3.0,>=1.2.0 - beautifulsoup4: <6.0,>=4.8.0 - click: <10.0 - croniter: <1.4.0,>=1.3.0 - dateutils: <2.0 - deepdiff: <8.0,>=5.7.0 - fsspec: <2024.0,>=2022.5.0 - inquirer: <5.0,>=2.10.0 - jinja2: <5.0 - lightning-utilities: <2.0,>=0.7.0 - numpy: <3.0,>=1.17.2 - psutil: <7.0 - pytorch: <4.0,>=1.11.0 - pyyaml: <8.0 - requests: <4.0 - rich: <15.0,>=12.3.0 - starsessions: <2.0,>=1.2.1 - torchmetrics: <2.0,>=0.7.0 - tqdm: <6.0,>=4.57.0 - traitlets: <7.0,>=5.3.0 - typing-extensions: <6.0,>=4.0.0 - urllib3: <3.0 - uvicorn: <2.0 - websocket-client: <3.0 - websockets: <12.0 - fastapi: <0.89.0 - lightning-cloud: '>=0.5.31' - pydantic: <3.0 - starlette: <2.0 - url: https://conda.anaconda.org/conda-forge/noarch/lightning-2.0.0-pyhd8ed1ab_0.conda - hash: - md5: 5c38b552a8b1853c39738d9a9f090ffc - sha256: 4b6bf3a963ea91f81de4947ad8a0686bb8cf868f63a5a125088938fc9551ace1 - category: main - optional: false -- name: aws-c-common - version: 0.8.19 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-common-0.8.19-hb547adb_0.conda - hash: - md5: 3ac2d183c00052168db4f1d72dd42af3 - sha256: e9d44b19552e42c9173e6bb7ce1cd82db285b126a26e53e92254ef3861b950a3 - category: main - optional: false -- name: bzip2 - version: 1.0.8 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h3422bc3_4.tar.bz2 - hash: - md5: fc76ace7b94fb1f694988ab1b14dd248 - sha256: a3efbd06ad1432edb0163c48225421f34c2660f5cc002283a8d27e791320b549 - category: main - optional: false -- name: c-ares - version: 1.19.1 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.19.1-hb547adb_0.conda - hash: - md5: e7fc7430440d255e3a9c7e5a52f7b294 - sha256: fc9d0fcfb30c20c0032b294120b6ba9c01078ddb372c34dd491214c598aecc06 - category: main - optional: false -- name: ca-certificates - version: 2023.5.7 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2023.5.7-hf0a4a13_0.conda - hash: - md5: a8387be82224743cf849fb907790b91a - sha256: 27214b54d1cb9a92455689e20d0007a0ff9ace99b853867d53a05a04c24bdae5 - category: main - optional: false -- name: dav1d - version: 1.2.0 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/dav1d-1.2.0-hb547adb_0.conda - hash: - md5: 85667b31a6624f817f2fde4edce9c347 - sha256: cd64b364261811cc4df3d2735ad9db5ede20132d2ed9eb493d8f1a2a903c8268 - category: main - optional: false -- name: font-ttf-dejavu-sans-mono - version: '2.37' - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - hash: - md5: 0c96522c6bdaed4b1566d11387caaf45 - sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b - category: main - optional: false -- name: font-ttf-inconsolata - version: '3.000' - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - hash: - md5: 34893075a5c9e55cdafac56607368fc6 - sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c - category: main - optional: false -- name: font-ttf-source-code-pro - version: '2.038' - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - hash: - md5: 4d59c254e01d9cde7957100457e2d5fb - sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 - category: main - optional: false -- name: font-ttf-ubuntu - version: '0.83' - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2 - hash: - md5: 19410c3df09dfb12d1206132a1d357c5 - sha256: 470d5db54102bd51dbb0c5990324a2f4a0bc976faa493b22193338adb9882e2e - category: main - optional: false -- name: fribidi - version: 1.0.10 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/fribidi-1.0.10-h27ca646_0.tar.bz2 - hash: - md5: c64443234ff91d70cb9c7dc926c58834 - sha256: 4b37ea851a2cf85edf0a63d2a63266847ec3dcbba4a31156d430cdd6aa811303 - category: main - optional: false -- name: icu - version: '72.1' - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/icu-72.1-he12128b_0.conda - hash: - md5: d1a11dfc54168a07856dbf87f393ca82 - sha256: 997835c56e899f4717b6707ab0734c27e7cdd8c735c952334314a7c9d59808e1 - category: main - optional: false -- name: jpeg - version: 9e - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/jpeg-9e-h1a8c8d9_3.conda - hash: - md5: ef1cce2ab799e0c2f32c3344125ff218 - sha256: 7e21d03917fb535b39c3af0cc7b7115617556a4ca2fe13018c09407987883b34 - category: main - optional: false -- name: lame - version: '3.100' - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/lame-3.100-h1a8c8d9_1003.tar.bz2 - hash: - md5: bff0e851d66725f78dc2fd8b032ddb7e - sha256: f40ce7324b2cf5338b766d4cdb8e0453e4156a4f83c2f31bbfff750785de304c - category: main - optional: false -- name: libbrotlicommon - version: 1.0.9 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.0.9-h1a8c8d9_8.tar.bz2 - hash: - md5: 84eb0c3c995a865079080d092e4a3c06 - sha256: 1bd70570aee08fe0274dd46879d0b4c36c662c18d3afc03c41c375c84658af88 - category: main - optional: false -- name: libcxx - version: 16.0.4 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-16.0.4-h4653b0c_0.conda - hash: - md5: b57808944874f97dbd858b02c8596817 - sha256: faf63d13c318904494cfa2a84f21ce17bddc6d77356037f55fc8d9746bf5df2e - category: main - optional: false -- name: libdeflate - version: '1.17' - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.17-h1a8c8d9_0.conda - hash: - md5: cae34d3f6ab02e0abf92ec3caaf0bd39 - sha256: 9a1979b3f6dc155b8c48987cfae6b13ba19b3e176e4470b87f60011e806218f5 - category: main - optional: false -- name: libev - version: '4.33' - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h642e427_1.tar.bz2 - hash: - md5: 566dbf70fe79eacdb3c3d3d195a27f55 - sha256: eb7325eb2e6bd4c291cb9682781b35b8c0f68cb72651c35a5b9dd22707ebd25c - category: main - optional: false -- name: libexpat - version: 2.5.0 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.5.0-hb7217d7_1.conda - hash: - md5: 5a097ad3d17e42c148c9566280481317 - sha256: 7d143a9c991579ad4207f84c632650a571c66329090daa32b3c87cf7311c3381 - category: main - optional: false -- name: libffi - version: 3.4.2 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 - hash: - md5: 086914b672be056eb70fd4285b6783b6 - sha256: 41b3d13efb775e340e4dba549ab5c029611ea6918703096b2eaa9c015c0750ca - category: main - optional: false -- name: libiconv - version: '1.17' - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.17-he4db4b2_0.tar.bz2 - hash: - md5: 686f9c755574aa221f29fbcf36a67265 - sha256: 2eb33065783b802f71d52bef6f15ce0fafea0adc8506f10ebd0d490244087bec - category: main - optional: false -- name: libopus - version: 1.3.1 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libopus-1.3.1-h27ca646_1.tar.bz2 - hash: - md5: 3d0dbee0ccd2f6d6781d270313627b62 - sha256: e9912101a58cbc609a1917c5289f3bd1f600c82ed3a1c90a6dd4ca02df77958a - category: main - optional: false -- name: libsodium - version: 1.0.18 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.18-h27ca646_1.tar.bz2 - hash: - md5: 90859688dbca4735b74c02af14c4c793 - sha256: 1d95fe5e5e6a0700669aab454b2a32f97289c9ed8d1f7667c2ba98327a6f05bc - category: main - optional: false -- name: libtasn1 - version: 4.19.0 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libtasn1-4.19.0-h1a8c8d9_0.tar.bz2 - hash: - md5: c35bc17c31579789c76739486fc6d27a - sha256: 912e96644ea22b49921c71c9c94bcdd2b6463e9313da895c2fcee298a8c0e44c - category: main - optional: false -- name: libunistring - version: 0.9.10 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libunistring-0.9.10-h3422bc3_0.tar.bz2 - hash: - md5: d88e77a4861e20bd96bde6628ee7a5ae - sha256: a1afe12ab199f82f339eae83405d293d197f2485d45346a709703bc7e8299949 - category: main - optional: false -- name: libutf8proc - version: 2.8.0 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libutf8proc-2.8.0-h1a8c8d9_0.tar.bz2 - hash: - md5: f8c9c41a122ab3abdf8943b13f4957ee - sha256: a3faddac08efd930fa3a1cc254b5053b4ed9428c49a888d437bf084d403c931a - category: main - optional: false -- name: libwebp-base - version: 1.3.0 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.3.0-h1a8c8d9_0.conda - hash: - md5: c316f1e4a833d49672f1df3bc015a115 - sha256: f863700d96c722414d2406cfa6108f6d363068112b88d6b0a4b92e5724439070 - category: main - optional: false -- name: libzlib - version: 1.2.13 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.2.13-h03a7124_4.tar.bz2 - hash: - md5: 780852dc54c4c07e64b276a97f89c162 - sha256: a1bf4a1c107838fea4570a7f1750306d65d84fcf2913d4e0d30b4db785e8f223 - category: main - optional: false -- name: llvm-openmp - version: 16.0.4 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-16.0.4-h1c12783_0.conda - hash: - md5: 1abf75d51caea7969ddf29b21c12b541 - sha256: 9092f37f379de2ac19f04d86e11146853a2dc6ce477eb0f0e1af0d4465e4dccb - category: main - optional: false -- name: ncurses - version: '6.3' - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.3-h07bb92c_1.tar.bz2 - hash: - md5: db86e5a978380a13f5559f97afdfe99d - sha256: 50ba7c13dd7d05569e7caa98a13a3684450f8547b4965a1e86b54e2f1240debe - category: main - optional: false -- name: nettle - version: 3.8.1 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/nettle-3.8.1-h63371fa_1.tar.bz2 - hash: - md5: 0da3266889a3febbb9840a7a89d29da9 - sha256: 712b4e836060ab26772c343a05d243e7486bb44a39bb5b35f3371e72d7b38a24 - category: main - optional: false -- name: pixman - version: 0.40.0 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.40.0-h27ca646_0.tar.bz2 - hash: - md5: 0cedfe37c9aee28f5e926a870965466a - sha256: a3bde72b3f9344ede1a189612d997f775b503a8eec61fb9720d18551f3c71080 - category: main - optional: false -- name: pthread-stubs - version: '0.4' - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-h27ca646_1001.tar.bz2 - hash: - md5: d3f26c6494d4105d4ecb85203d687102 - sha256: 9da9e6f5d51dff6ad2e4ee0874791437ba952e0a6249942273f0fedfd07ea826 - category: main - optional: false -- name: python_abi - version: '3.9' - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/python_abi-3.9-3_cp39.conda - hash: - md5: f8fb5fb65327a2429b084833c8ff1dbc - sha256: 9434a23c734685db9a5017206dae58f141e2edddec2ee9e1ec10a3fdefa55c0f - category: main - optional: false -- name: pytorch-mutex - version: '1.0' - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/pytorch/noarch/pytorch-mutex-1.0-cpu.tar.bz2 - hash: - md5: 49565ed726991fd28d08a39885caa88d - sha256: d48c964188ca49660d750cffd73698d217cf94e694cd51987f9f186425435e76 - category: main - optional: false -- name: tzdata - version: 2023c - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2023c-h71feb2d_0.conda - hash: - md5: 939e3e74d8be4dac89ce83b20de2492a - sha256: 0449138224adfa125b220154408419ec37c06b0b49f63c5954724325903ecf55 - category: main - optional: false -- name: x264 - version: 1!164.3095 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/x264-1!164.3095-h57fd34a_2.tar.bz2 - hash: - md5: b1f6dccde5d3a1f911960b6e567113ff - sha256: debdf60bbcfa6a60201b12a1d53f36736821db281a28223a09e0685edcce105a - category: main - optional: false -- name: xorg-libxau - version: 1.0.11 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.11-hb547adb_0.conda - hash: - md5: ca73dc4f01ea91e44e3ed76602c5ea61 - sha256: 02c313a1cada46912e5b9bdb355cfb4534bfe22143b4ea4ecc419690e793023b - category: main - optional: false -- name: xorg-libxdmcp - version: 1.1.3 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.3-h27ca646_0.tar.bz2 - hash: - md5: 6738b13f7fadc18725965abdd4129c36 - sha256: d9a2fb4762779994718832f05a7d62ab2dcf6103a312235267628b5187ce88f7 - category: main - optional: false -- name: xz - version: 5.2.6 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 - hash: - md5: 39c6b54e94014701dd157f4f576ed211 - sha256: 59d78af0c3e071021cfe82dc40134c19dab8cdf804324b62940f5c8cd71803ec - category: main - optional: false -- name: yaml - version: 0.2.5 - manager: conda - platform: osx-arm64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h3422bc3_2.tar.bz2 - hash: - md5: 4bb3f014845110883a3c5ee811fd84b4 - sha256: 93181a04ba8cfecfdfb162fc958436d868cc37db504c58078eab4c1a3e57fbb7 - category: main - optional: false -- name: aom - version: 3.5.0 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.4' - url: https://conda.anaconda.org/conda-forge/osx-arm64/aom-3.5.0-h7ea286d_0.tar.bz2 - hash: - md5: afb32d2a714ef2c3268508fdc85fc7c4 - sha256: 3a238c39da0bb29da396ae9f88655a1a6b05926055539ecc29cef9533671d71c - category: main - optional: false -- name: aws-c-compression - version: 0.2.16 - manager: conda - platform: osx-arm64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-compression-0.2.16-hf5f6f59_7.conda - hash: - md5: 6f1a43f0afb4d3cc7c863120e7597fca - sha256: 141b253ea1a7e65b6e800845c29ad4713a7714fa7d88a73a7fb1056daf298690 - category: main - optional: false -- name: aws-c-sdkutils - version: 0.1.9 - manager: conda - platform: osx-arm64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-sdkutils-0.1.9-hf5f6f59_2.conda - hash: - md5: fc40b995d86cdc578978507c11b8513e - sha256: 8e344cb79262b62fba6ef251e00fa6a58e047059d275c7225501b46870aee6ac - category: main - optional: false -- name: aws-checksums - version: 0.1.14 - manager: conda - platform: osx-arm64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/aws-checksums-0.1.14-hf5f6f59_7.conda - hash: - md5: 518150dd9aa2b58ae8cf023b6d39a962 - sha256: 9b8063ba55abd518c1b8b87711753779c9a79ef7c54e7e2f08fe859361651c65 - category: main - optional: false -- name: cpuonly - version: '2.0' - manager: conda - platform: osx-arm64 - dependencies: - pytorch-mutex: '1.0' - url: https://conda.anaconda.org/pytorch/noarch/cpuonly-2.0-0.tar.bz2 - hash: - md5: 1cf3a59ef90a4078c253e3b02c272065 - sha256: f9107aca2a9d23a032634644df5cdb8d0185337891593ce540adc480810ab539 - category: main - optional: false -- name: expat - version: 2.5.0 - manager: conda - platform: osx-arm64 - dependencies: - libexpat: 2.5.0 - url: https://conda.anaconda.org/conda-forge/osx-arm64/expat-2.5.0-hb7217d7_1.conda - hash: - md5: 624fa0dd6fdeaa650b71a62296fdfedf - sha256: 9f06afbe4604decf6a2e8e7e87f5ca218a3e9049d57d5b3fcd538ca6240d21a0 - category: main - optional: false -- name: fonts-conda-forge - version: '1' - manager: conda - platform: osx-arm64 - dependencies: - font-ttf-inconsolata: '' - font-ttf-source-code-pro: '' - font-ttf-dejavu-sans-mono: '' - font-ttf-ubuntu: '' - url: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - hash: - md5: f766549260d6815b0c52253f1fb1bb29 - sha256: 53f23a3319466053818540bcdf2091f253cbdbab1e0e9ae7b9e509dcaa2a5e38 - category: main - optional: false -- name: gettext - version: 0.21.1 - manager: conda - platform: osx-arm64 - dependencies: - libiconv: '>=1.17,<2.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/gettext-0.21.1-h0186832_0.tar.bz2 - hash: - md5: 63d2ff6fddfa74e5458488fd311bf635 - sha256: 093b2f96dc4b48e4952ab8946facec98b34b708a056251fc19c23c3aad30039e - category: main - optional: false -- name: gflags - version: 2.2.2 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=11.0.0.rc1' - url: https://conda.anaconda.org/conda-forge/osx-arm64/gflags-2.2.2-hc88da5d_1004.tar.bz2 - hash: - md5: aab9ddfad863e9ef81229a1f8852211b - sha256: 25d4a20af2e5ace95fdec88970f6d190e77e20074d2f6d3cef766198b76a4289 - category: main - optional: false -- name: gmp - version: 6.2.1 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=11.0.0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/gmp-6.2.1-h9f76cd9_0.tar.bz2 - hash: - md5: f8140773b6ca51bf32feec9b4290a8c5 - sha256: 2fd12c3e78b6c632f7f34883b942b973bdd24302c74f2b9b78e776b654baf591 - category: main - optional: false -- name: graphite2 - version: 1.3.13 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=11.0.0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/graphite2-1.3.13-h9f76cd9_1001.tar.bz2 - hash: - md5: 288b591645cb9cb9c0af7309ac1114f5 - sha256: 57db1e563cdfe469cd453a2988039118e96ce4b77c9219e2f1022be0e1c2b03f - category: main - optional: false -- name: lerc - version: 4.0.0 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=13.0.1' - url: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.0.0-h9a09cb3_0.tar.bz2 - hash: - md5: de462d5aacda3b30721b512c5da4e742 - sha256: 6f068bb53dfb6147d3147d981bb851bb5477e769407ad4e6a68edf482fdcb958 - category: main - optional: false -- name: libabseil - version: '20230125.2' - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=15.0.7' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20230125.2-cxx17_h13dd4ca_2.conda - hash: - md5: 2f0c4eed189bb5d3d527fba05a90417a - sha256: b8aaccf5605073c65e5de40befd166f93754ece31be6e4dc55624fc9820c58fa - category: main - optional: false -- name: libbrotlidec - version: 1.0.9 - manager: conda - platform: osx-arm64 - dependencies: - libbrotlicommon: 1.0.9 - url: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.0.9-h1a8c8d9_8.tar.bz2 - hash: - md5: 640ea7b788cdd0420409bd8479f023f9 - sha256: a0a52941eb59369a8b33b01b41bcf56efd313850c583f4814e2db59448439880 - category: main - optional: false -- name: libbrotlienc - version: 1.0.9 - manager: conda - platform: osx-arm64 - dependencies: - libbrotlicommon: 1.0.9 - url: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.0.9-h1a8c8d9_8.tar.bz2 - hash: - md5: 572907b78be867937c258421bc0807a8 - sha256: c5f65062cd41d5f5fd93eadd276885efbe7ce7c9346155852d4f5b619f8a166f - category: main - optional: false -- name: libcrc32c - version: 1.1.2 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=11.1.0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libcrc32c-1.1.2-hbdafb3b_0.tar.bz2 - hash: - md5: 32bd82a6a625ea6ce090a81c3d34edeb - sha256: 58477b67cc719060b5b069ba57161e20ba69b8695d154a719cb4b60caf577929 - category: main - optional: false -- name: libedit - version: 3.1.20191231 - manager: conda - platform: osx-arm64 - dependencies: - ncurses: '>=6.2,<7.0.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20191231-hc8eb9b7_2.tar.bz2 - hash: - md5: 30e4362988a2623e9eb34337b83e01f9 - sha256: 3912636197933ecfe4692634119e8644904b41a58f30cad9d1fc02f6ba4d9fca - category: main - optional: false -- name: libgfortran5 - version: 12.2.0 - manager: conda - platform: osx-arm64 - dependencies: - llvm-openmp: '>=8.0.0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-12.2.0-h0eea778_31.conda - hash: - md5: 244a7665228221cc951d5126b8bc1465 - sha256: 375b6ebafffcc1b0e377559b5ba7cb723634a88b77513ad158982d98ff98c32b - category: main - optional: false -- name: libpng - version: 1.6.39 - manager: conda - platform: osx-arm64 - dependencies: - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.39-h76d750c_0.conda - hash: - md5: 0078e6327c13cfdeae6ff7601e360383 - sha256: 21ab8409a8e66f9408b96428c0a36a9768faee9fe623c56614576f9e12962981 - category: main - optional: false -- name: libprotobuf - version: 3.21.12 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-3.21.12-hb5ab8b9_0.conda - hash: - md5: 7adb342474af442e3842c422f07fbf68 - sha256: 1ba3f141e7554b0d0998808b2ba270760e3d4b882839bb24a566ce046d58bbc8 - category: main - optional: false -- name: libsqlite - version: 3.42.0 - manager: conda - platform: osx-arm64 - dependencies: - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.42.0-hb31c410_0.conda - hash: - md5: 6ae1bbf3ae393a45a75685072fffbe8d - sha256: 120913cf0fb694546fbaf95dff211ac5c1e3e91bc69c73350891a05dc106355f - category: main - optional: false -- name: libvpx - version: 1.13.0 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libvpx-1.13.0-h7ea286d_0.conda - hash: - md5: 8030618261e9c2b8670066ecb9701113 - sha256: 371a109830e0140e0f346ff540b12c8ecef7cc9a19767adc648b1eba3c4bd7e5 - category: main - optional: false -- name: libxcb - version: '1.13' - manager: conda - platform: osx-arm64 - dependencies: - pthread-stubs: '' - xorg-libxau: '' - xorg-libxdmcp: '' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.13-h9b22ae9_1004.tar.bz2 - hash: - md5: 6b3457a192f8091cb413962f65740ac4 - sha256: a89b1e46650c01a8791c201c108d6d49a0a5604dd24ddb18902057bbd90f7dbb - category: main - optional: false -- name: libxml2 - version: 2.11.4 - manager: conda - platform: osx-arm64 - dependencies: - icu: '>=72.1,<73.0a0' - libiconv: '>=1.17,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - xz: '>=5.2.6,<6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.11.4-he3bdae6_0.conda - hash: - md5: 3741cfda603726c135ffe37bb83e1ca0 - sha256: 8f833df2746b013af73c41621c4b4bf4492de6f4c38352a9b6df21aa0db56ed6 - category: main - optional: false -- name: lz4-c - version: 1.9.4 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - url: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.9.4-hb7217d7_0.conda - hash: - md5: 45505bec548634f7d05e02fb25262cb9 - sha256: fc343b8c82efe40819b986e29ba748366514e5ab94a1e1138df195af5f45fa24 - category: main - optional: false -- name: openh264 - version: 2.3.1 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - url: https://conda.anaconda.org/conda-forge/osx-arm64/openh264-2.3.1-hb7217d7_2.conda - hash: - md5: 6ce0517e73640933cf5916deb21d4f23 - sha256: 36c6dc71bb10245ed93f3cb13280948cc8c6ca525f1639aac9d541726e4c80af - category: main - optional: false -- name: openssl - version: 3.1.1 - manager: conda - platform: osx-arm64 - dependencies: - ca-certificates: '' - url: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.1.1-h53f4e23_1.conda - hash: - md5: 7451b96ed28b5fd02f0df32689327755 - sha256: 898aac8f8753385e9cd378d539364647d1deb9396032b7c1fd8f0f08107e020b - category: main - optional: false -- name: p11-kit - version: 0.24.1 - manager: conda - platform: osx-arm64 - dependencies: - libffi: '>=3.4.2,<3.5.0a0' - libtasn1: '>=4.18.0,<5.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/p11-kit-0.24.1-h29577a5_0.tar.bz2 - hash: - md5: 8f111d56c8c7c1895bde91a942c43d93 - sha256: 3e124859307956f9f390f39c74b9700be4843eaaf56891c4b09da75b1bd5b57f - category: main - optional: false -- name: pcre2 - version: '10.40' - manager: conda - platform: osx-arm64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - libzlib: '>=1.2.12,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.40-hb34f9b4_0.tar.bz2 - hash: - md5: 721b7288270bafc83586b0f01c2a67f2 - sha256: 93503b5e05470ccc87f696c0fdf0d47938e0305b5047eacb85c15d78dcf641fe - category: main - optional: false -- name: re2 - version: 2023.03.02 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - url: https://conda.anaconda.org/conda-forge/osx-arm64/re2-2023.03.02-hc5e2d97_0.conda - hash: - md5: 7a851c0ab05247e3246eca2c3b243b9a - sha256: 39bc32dcef3b699e6f748cc51d5e6b05ab788334d5787c64f069f0122e74c0c5 - category: main - optional: false -- name: readline - version: '8.2' - manager: conda - platform: osx-arm64 - dependencies: - ncurses: '>=6.3,<7.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda - hash: - md5: 8cbb776a2f641b943d413b3e19df71f4 - sha256: a1dfa679ac3f6007362386576a704ad2d0d7a02e98f5d0b115f207a2da63e884 - category: main - optional: false -- name: snappy - version: 1.1.10 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - url: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.1.10-h17c5cce_0.conda - hash: - md5: ac82a611d1a67a598096ebaa857198e3 - sha256: dfae03cd2339587871e53b42833657faa4c9e42e3e2c56ee9e32bc60797c7f62 - category: main - optional: false -- name: svt-av1 - version: 1.5.0 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=15.0.7' - url: https://conda.anaconda.org/conda-forge/osx-arm64/svt-av1-1.5.0-hb765f3a_0.conda - hash: - md5: 8320006f208776a59bc9fe578b682fa3 - sha256: da9a4bf8ce253cb29902ed24eed9aa05fdfba18d05355f3b5502d352b53578f3 - category: main - optional: false -- name: tk - version: 8.6.12 - manager: conda - platform: osx-arm64 - dependencies: - libzlib: '>=1.2.11,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.12-he1e0b03_0.tar.bz2 - hash: - md5: 2cb3d18eac154109107f093860bd545f - sha256: 9e43ec80045892e28233e4ca4d974e09d5837392127702fb952f3935b5e985a4 - category: main - optional: false -- name: x265 - version: '3.5' - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=12.0.1' - url: https://conda.anaconda.org/conda-forge/osx-arm64/x265-3.5-hbc6ce65_3.tar.bz2 - hash: - md5: b1f7f2780feffe310b068c021e8ff9b2 - sha256: 2fed6987dba7dee07bd9adc1a6f8e6c699efb851431bcb6ebad7de196e87841d - category: main - optional: false -- name: zlib - version: 1.2.13 - manager: conda - platform: osx-arm64 - dependencies: - libzlib: 1.2.13 - url: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.2.13-h03a7124_4.tar.bz2 - hash: - md5: 34161cff4e29cc45e536abf2f13fd6b4 - sha256: 48844c5c911e2ef69571d6ef7181dcfae68df296c546662cb54057baed008949 - category: main - optional: false -- name: zstd - version: 1.5.2 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.2-hf913c23_6.conda - hash: - md5: 8f346953ef63bf5fb482488a659adcf3 - sha256: 018989ba028e76abc332c246002e8f5975ff123c68f6116a30da8009b14ea88d - category: main - optional: false -- name: aws-c-cal - version: 0.5.26 - manager: conda - platform: osx-arm64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-cal-0.5.26-h5f09868_1.conda - hash: - md5: bf82f58f7a0caff5e38a47bda58c7ff6 - sha256: 57833f6ca36b3ac134b0bef646ab24e4c2381adc3994be1d44188006fd4667a8 - category: main - optional: false -- name: brotli-bin - version: 1.0.9 - manager: conda - platform: osx-arm64 - dependencies: - libbrotlidec: 1.0.9 - libbrotlienc: 1.0.9 - url: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.0.9-h1a8c8d9_8.tar.bz2 - hash: - md5: f212620a4f3606ff8f800b8b1077415a - sha256: d171637710bffc322b35198c03bcfd3d04f454433e845138e5120729f8941996 - category: main - optional: false -- name: fonts-conda-ecosystem - version: '1' - manager: conda - platform: osx-arm64 - dependencies: - fonts-conda-forge: '' - url: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - hash: - md5: fee5683a3f04bd15cbd8318b096a27ab - sha256: a997f2f1921bb9c9d76e6fa2f6b408b7fa549edd349a77639c9fe7a23ea93e61 - category: main - optional: false -- name: freetype - version: 2.12.1 - manager: conda - platform: osx-arm64 - dependencies: - libpng: '>=1.6.39,<1.7.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.12.1-hd633e50_1.conda - hash: - md5: 33ea6326e26d1da25eb8dfa768195b82 - sha256: 9f20ac782386cca6295cf02a07bbc6aedc4739330dc9caba242630602a9ab7f4 - category: main - optional: false -- name: glog - version: 0.6.0 - manager: conda - platform: osx-arm64 - dependencies: - gflags: '>=2.2.2,<2.3.0a0' - libcxx: '>=12.0.1' - url: https://conda.anaconda.org/conda-forge/osx-arm64/glog-0.6.0-h6da1cb0_0.tar.bz2 - hash: - md5: 5a570729c7709399cf8511aeeda6f989 - sha256: 4d772c42477f64be708594ac45870feba3e838977871118eb25e00deb0e9a73c - category: main - optional: false -- name: krb5 - version: 1.20.1 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - libedit: '>=3.1.20191231,<4.0a0' - openssl: '>=3.0.7,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.20.1-h69eda48_0.conda - hash: - md5: a85db53e45b1173f270fc998dd40ec03 - sha256: 80094682db47468befef8e14a8a2ccc82cf71d6cf23bfa5d25c4de1df56e3067 - category: main - optional: false -- name: libevent - version: 2.1.12 - manager: conda - platform: osx-arm64 - dependencies: - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libevent-2.1.12-h5643135_0.conda - hash: - md5: 125372df3bbf157a0dff5dd60d73d7b1 - sha256: 6dede3978f7ed10040459e3afa545f452381fbfdfb9c0e3d62b8c4c8c4dc6aba - category: main - optional: false -- name: libgfortran - version: 5.0.0 - manager: conda - platform: osx-arm64 - dependencies: - libgfortran5: '' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-5.0.0-12_2_0_hd922786_31.conda - hash: - md5: dfc3dff1ce831c8e821f19d5d218825a - sha256: 1abde945c2c7377aec9f2f648d9cc1fcb5f818a1e0caf53f8188b1182cbc1332 - category: main - optional: false -- name: libglib - version: 2.76.3 - manager: conda - platform: osx-arm64 - dependencies: - gettext: '>=0.21.1,<1.0a0' - libcxx: '>=15.0.7' - libffi: '>=3.4,<4.0a0' - libiconv: '>=1.17,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - pcre2: '>=10.40,<10.41.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.76.3-h24e9cb9_0.conda - hash: - md5: 30b160e9e8ca46b28491ca85eab61dce - sha256: 5b31f80d89224fbfbd0c46b313f6a8d3c43f20f2ae57613cd25d46524a3ca23d - category: main - optional: false -- name: libgrpc - version: 1.54.2 - manager: conda - platform: osx-arm64 - dependencies: - c-ares: '>=1.18.1,<2.0a0' - libabseil: '>=20230125.2,<20230126.0a0' - libcxx: '>=15.0.7' - libprotobuf: '>=3.21.12,<3.22.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - re2: '>=2023.3.2,<2023.3.3.0a0' - zlib: '' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libgrpc-1.54.2-h0a338ca_2.conda - hash: - md5: 3787e5fc4d25d9a347c2ac7c60a9957b - sha256: 37b8280181ed5f645b07e30b0ea3394800facb85af5b2c1024d98ad64dc1d417 - category: main - optional: false -- name: libidn2 - version: 2.3.4 - manager: conda - platform: osx-arm64 - dependencies: - gettext: '>=0.21.1,<1.0a0' - libunistring: '>=0,<1.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libidn2-2.3.4-h1a8c8d9_0.tar.bz2 - hash: - md5: 7fdc11b6fd3ea4ce92886df855fc7085 - sha256: 3f2990c33c57559fbf03c5e5b3f3c8e95886548ab4c7fc10314e4514d6632703 - category: main - optional: false -- name: libnghttp2 - version: 1.52.0 - manager: conda - platform: osx-arm64 - dependencies: - c-ares: '>=1.18.1,<2.0a0' - libcxx: '>=14.0.6' - libev: '>=4.33,<4.34.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.0.8,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.52.0-hae82a92_0.conda - hash: - md5: 1d319e95a0216f801293626a00337712 - sha256: 1a3944d6295dcbecdf6489ce8a05fe416ad401727c901ec390e9200a351bdb10 - category: main - optional: false -- name: libssh2 - version: 1.10.0 - manager: conda - platform: osx-arm64 - dependencies: - libzlib: '>=1.2.12,<1.3.0a0' - openssl: '>=3.0.5,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.10.0-h7a5bd25_3.tar.bz2 - hash: - md5: 1ffa4340e73809db5a7516cb1a7425a7 - sha256: fbaa668831c56d88824c9ea42aba7b9c9ffd26cdd9bec850120bf2aff0d9e282 - category: main - optional: false -- name: libtiff - version: 4.5.0 - manager: conda - platform: osx-arm64 - dependencies: - jpeg: '>=9e,<10a' - lerc: '>=4.0.0,<5.0a0' - libcxx: '>=14.0.6' - libdeflate: '>=1.17,<1.18.0a0' - libwebp-base: '>=1.2.4,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - xz: '>=5.2.6,<6.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.5.0-h5dffbdd_2.conda - hash: - md5: 8e08eae60de32c940096ee9b4da35685 - sha256: 0207f4234571d393d2f790aedaa1e127dfcd9d7fe3fe886ebdf31c9e7b9f7ce2 - category: main - optional: false -- name: orc - version: 1.8.3 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - libprotobuf: '>=3.21.12,<3.22.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - snappy: '>=1.1.10,<2.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/orc-1.8.3-hef0d403_0.conda - hash: - md5: 5fe39445f76fa6f2a02b8033f2e5872b - sha256: 59d28ac52c078894362ced8394f5be3c87f3c831516b871fccf366dccf6360bf - category: main - optional: false -- name: sqlite - version: 3.42.0 - manager: conda - platform: osx-arm64 - dependencies: - libsqlite: 3.42.0 - libzlib: '>=1.2.13,<1.3.0a0' - ncurses: '>=6.3,<7.0a0' - readline: '>=8.2,<9.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/sqlite-3.42.0-h203b68d_0.conda - hash: - md5: 5f135a9d6d44e1a94ac6977dd3c9403b - sha256: b6262bbe4ac38aa067e502870de005bea86aa0f4830a8d3c3a8bc026ba04a590 - category: main - optional: false -- name: aws-c-io - version: 0.13.21 - manager: conda - platform: osx-arm64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.13.21-h6297769_5.conda - hash: - md5: 2672882eb54155485e24adbceb6b68cc - sha256: 25902745deb52f9b17b7425f27f6f842f18efc537b34fb00d04f34dc95a3586b - category: main - optional: false -- name: brotli - version: 1.0.9 - manager: conda - platform: osx-arm64 - dependencies: - brotli-bin: 1.0.9 - libbrotlidec: 1.0.9 - libbrotlienc: 1.0.9 - url: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.0.9-h1a8c8d9_8.tar.bz2 - hash: - md5: e2a5e381ddd6529eb62e7710270b2ec5 - sha256: f97debd05c2caeeefba22e0b71173f1fff99c1e5e66e6e9caa91c1c66eb59741 - category: main - optional: false -- name: fontconfig - version: 2.14.2 - manager: conda - platform: osx-arm64 - dependencies: - expat: '>=2.5.0,<3.0a0' - freetype: '>=2.12.1,<3.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.14.2-h82840c6_0.conda - hash: - md5: f77d47ddb6d3cc5b39b9bdf65635afbb - sha256: 7094917fc6758186e17c61d8ee8fd2bbbe9f303b4addac61d918fa415c497e2b - category: main - optional: false -- name: gnutls - version: 3.7.8 - manager: conda - platform: osx-arm64 - dependencies: - gettext: '>=0.19.8.1,<1.0a0' - libcxx: '>=14.0.4' - libidn2: '>=2,<3.0a0' - libtasn1: '>=4.19.0,<5.0a0' - nettle: '>=3.8.1,<3.9.0a0' - p11-kit: '>=0.24.1,<0.25.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/gnutls-3.7.8-h9f1a10d_0.tar.bz2 - hash: - md5: 2367cca5a0451a70d01cff2dd2ce7d3e - sha256: 7b9b69cb2b3134e064b37948a4cd54dee2184a851c0cda5fe52efcfd90ae032d - category: main - optional: false -- name: lcms2 - version: '2.15' - manager: conda - platform: osx-arm64 - dependencies: - jpeg: '>=9e,<10a' - libtiff: '>=4.5.0,<4.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.15-h481adae_0.conda - hash: - md5: 806395a21421da524d72aa4e0b69d54c - sha256: 225028d2cea4e2974415245e4521c85f4baca08bc1103de44b5f8d6994bf2b9f - category: main - optional: false -- name: libcurl - version: 8.1.2 - manager: conda - platform: osx-arm64 - dependencies: - krb5: '>=1.20.1,<1.21.0a0' - libnghttp2: '>=1.52.0,<2.0a0' - libssh2: '>=1.10.0,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.1.2-h912dcd9_0.conda - hash: - md5: af01aa21cd4bb0cf519cda6bcec83fc5 - sha256: 5d5bbc7a6eb363b2df85c5df32d34295346fc8b4d9e3754bbaf2af3e80422fab - category: main - optional: false -- name: libopenblas - version: 0.3.21 - manager: conda - platform: osx-arm64 - dependencies: - libgfortran: 5.* - libgfortran5: '>=11.3.0' - llvm-openmp: '>=14.0.4' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.21-openmp_hc731615_3.tar.bz2 - hash: - md5: 2a980a5d8cc34ce70d339b983f9920de - sha256: 92e341be106c00adf1f1757ec9f9586a3848af94b434554c75dd7c5023f84ea2 - category: main - optional: false -- name: libthrift - version: 0.18.1 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=15.0.7' - libevent: '>=2.1.12,<2.1.13.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.18.1-ha061701_1.conda - hash: - md5: 7faeae04727bbae5023ec95334a4be84 - sha256: fe00b4f6d00b3ef81826c64f8e4711b991e5c9877011f137a35ad9119511d3a8 - category: main - optional: false -- name: openjpeg - version: 2.5.0 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - libpng: '>=1.6.39,<1.7.0a0' - libtiff: '>=4.5.0,<4.6.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.0-hbc2ba62_2.conda - hash: - md5: c3e184f0810a4614863569488b1ac709 - sha256: 2bb159e385e633a08cc164f50b4e39fa465b85f54c376a5c20aa15f57ef407b3 - category: main - optional: false -- name: python - version: 3.9.12 - manager: conda - platform: osx-arm64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - libffi: '>=3.4.2,<3.5.0a0' - libzlib: '>=1.2.11,<1.3.0a0' - ncurses: '>=6.3,<7.0a0' - openssl: '>=3.0.2,<4.0a0' - readline: '>=8.1,<9.0a0' - sqlite: '>=3.37.1,<4.0a0' - tk: '>=8.6.12,<8.7.0a0' - tzdata: '' - xz: '>=5.2.5,<5.3.0a0' - pip: '' - url: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.9.12-h14b404e_1_cpython.tar.bz2 - hash: - md5: 855bbfbe9fdb6cf4ea4a0c9cee97bbd4 - sha256: 3dc31bc2e9785082cf4300be4355127e7097d9fb9e6939e89dd85c1175e030bc - category: main - optional: false -- name: antlr-python-runtime - version: 4.9.3 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/antlr-python-runtime-4.9.3-pyhd8ed1ab_1.tar.bz2 - hash: - md5: c88eaec8de9ae1fa161205aa18e7a5b1 - sha256: b91f8ab4ac2b48972fbee1fc8e092cc452fdf59156e4ff2322c94bbf73650f94 - category: main - optional: false -- name: attrs - version: 23.1.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/attrs-23.1.0-pyh71513ae_1.conda - hash: - md5: 3edfead7cedd1ab4400a6c588f3e75f8 - sha256: 063639cd568f5c7a557b0fb1cc27f098598c0d8ff869088bfeb82934674f8821 - category: main - optional: false -- name: aws-c-event-stream - version: 0.2.20 - manager: conda - platform: osx-arm64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - libcxx: '>=15.0.7' - url: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.2.20-h589842e_7.conda - hash: - md5: d8b9170626bfef91e3182c6ecdad259b - sha256: 58b0191a278a6de399c5d79718044e48f5c782bed604a9a4e0da423201ed1fdc - category: main - optional: false -- name: aws-c-http - version: 0.7.7 - manager: conda - platform: osx-arm64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-compression: '>=0.2.16,<0.2.17.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.7.7-h262875d_4.conda - hash: - md5: 44d5faaeaecf9c11f5f63f7aecb5dea6 - sha256: 5fd15514858c40571505190131ffd89198d1b8417d934c72e28ce862961a40ef - category: main - optional: false -- name: backports - version: '1.0' - manager: conda - platform: osx-arm64 - dependencies: - python: '>=2.7' - url: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_3.conda - hash: - md5: 54ca2e08b3220c148a1d8329c2678e02 - sha256: 711602276ae39276cb0faaca6fd0ac851fff0ca17151917569174841ef830bbd - category: main - optional: false -- name: blinker - version: 1.6.2 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/blinker-1.6.2-pyhd8ed1ab_0.conda - hash: - md5: 2fb79ec81bad9492b6d59a06b3b647a4 - sha256: b6f32491536823e47cf6eb4717dd341385600a2b901235028dedc629a77aeb82 - category: main - optional: false -- name: cairo - version: 1.16.0 - manager: conda - platform: osx-arm64 - dependencies: - fontconfig: '>=2.14.2,<3.0a0' - fonts-conda-ecosystem: '' - freetype: '>=2.12.1,<3.0a0' - icu: '>=72.1,<73.0a0' - libglib: '>=2.76.2,<3.0a0' - libpng: '>=1.6.39,<1.7.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - pixman: '>=0.40.0,<1.0a0' - zlib: '' - url: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.16.0-h1e71087_1016.conda - hash: - md5: be77f5630cabe6a116a033f6fd241aa2 - sha256: 318cad8770aa5aa14a5808e1d6b39458e11d2c2411254e795269986467f864ea - category: main - optional: false -- name: certifi - version: 2023.5.7 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/certifi-2023.5.7-pyhd8ed1ab_0.conda - hash: - md5: 5d1b71c942b8421285934dad1d891ebc - sha256: f839a6e04d94069f90dd85337ea9108f058dc76771bb469a413f32bb1ba0b256 - category: main - optional: false -- name: charset-normalizer - version: 3.1.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.1.0-pyhd8ed1ab_0.conda - hash: - md5: 7fcff9f6f123696e940bda77bd4d6551 - sha256: 06cd371fc98f076797d6450f6f337cb679b1060c99680fb7e044591493333194 - category: main - optional: false -- name: click - version: 8.1.3 - manager: conda - platform: osx-arm64 - dependencies: - __unix: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2 - hash: - md5: 20e4087407c7cb04a40817114b333dbf - sha256: 23676470b591b100393bb0f6c46fe10624dcbefc696a6a9f42932ed8816ef0ea - category: main - optional: false -- name: cloudpickle - version: 2.2.1 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.1-pyhd8ed1ab_0.conda - hash: - md5: b325bfc4cff7d7f8a868f1f7ecc4ed16 - sha256: f0c2fd0e842899a05ddd7b147fb26424adf58be0e8e54e5bc68b8f7e67d05dcd - category: main - optional: false -- name: colorama - version: 0.4.6 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3faab06a954c2a04039983f2c4a50d99 - sha256: 2c1b2e9755ce3102bca8d69e8f26e4f087ece73f50418186aee7c74bef8e1698 - category: main - optional: false -- name: configparser - version: 5.3.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/configparser-5.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: c99fd5916160900dc5ff64204da99c4d - sha256: ce6ce9ee08437b46c284d52b076fb091cf6f2a9e12860d4a37546adbd5f53b28 - category: main - optional: false -- name: crashtest - version: 0.4.1 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6,<4.0' - url: https://conda.anaconda.org/conda-forge/noarch/crashtest-0.4.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 709a2295dd907bb34afb57d54320642f - sha256: 2f05954a3faf0700c14c1deddc085385160ee32abe111699c78d9cb277e915cc - category: main - optional: false -- name: cycler - version: 0.11.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: a50559fad0affdbb33729a68669ca1cb - sha256: 3b594bc8aa0b9a51269d54c7a4ef6af777d7fad4bee16b05695e1124de6563f6 - category: main - optional: false -- name: distlib - version: 0.3.6 - manager: conda - platform: osx-arm64 - dependencies: - python: 2.7|>=3.6 - url: https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2 - hash: - md5: b65b4d50dbd2d50fa0aeac367ec9eed7 - sha256: 06eb7167d4d760b3b437a491e32ab5b3f89e2a18f023c117fe213b038d88538a - category: main - optional: false -- name: entrypoints - version: '0.4' - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3cf04868fee0a029769bd41f4b2fbf2d - sha256: 2ec4a0900a4a9f42615fc04d0fb3286b796abe56590e8e042f6ec25e102dd5af - category: main - optional: false -- name: exceptiongroup - version: 1.1.1 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.1-pyhd8ed1ab_0.conda - hash: - md5: 7312299d7a0ea4993159229b7d2dceb2 - sha256: f073c3ba993912f1c0027bc34a54975642885f0a4cd5f9dc42a17ca945df2c18 - category: main - optional: false -- name: filelock - version: 3.12.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.12.0-pyhd8ed1ab_0.conda - hash: - md5: 650f18a56f366dbf419c15b543592c2d - sha256: 68db3a6280d6786be76f2c7c6cf41dd878c5d1a24f5de10f7f0af82c6fcfade6 - category: main - optional: false -- name: fsspec - version: 2023.5.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/fsspec-2023.5.0-pyh1a96a4e_0.conda - hash: - md5: 20edd290b319aa0eff3e9055375756dc - sha256: cbb5c77c0217cda9bf4f4240158de11822a099a6eaa05ba626e822819a54f46d - category: main - optional: false -- name: greenlet - version: 2.0.2 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=15.0.7' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/greenlet-2.0.2-py39hb198ff7_1.conda - hash: - md5: bf3c909dd78eb257e71d88a2d8ca8e2f - sha256: e42078784f65a851a4c71afad1b9de5b309a04ed4347f79df1ddcb7f8fef5df5 - category: main - optional: false -- name: idna - version: '3.4' - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 34272b248891bddccc64479f9a7fffed - sha256: 9887c35c374ec1847f167292d3fde023cb4c994a4ceeec283072b95440131f09 - category: main - optional: false -- name: itsdangerous - version: 2.1.2 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/itsdangerous-2.1.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3c3de74912f11d2b590184f03c7cd09b - sha256: 31e3492686b4e92b53db9b48bc0eb03873b1caaf28629fee7d2d47627a2c56d3 - category: main - optional: false -- name: kiwisolver - version: 1.4.4 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.4' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.4.4-py39haaf3ac1_1.tar.bz2 - hash: - md5: 5f43e4d5437b93606167c640ea2d06c1 - sha256: afe4759ca7572eb98361cd4c68ae3819a16d368c963d1134b926d2963434b3e6 - category: main - optional: false -- name: libblas - version: 3.9.0 - manager: conda - platform: osx-arm64 - dependencies: - libopenblas: '>=0.3.21,<1.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.9.0-16_osxarm64_openblas.tar.bz2 - hash: - md5: 53d6d5097f0d62e24db8c1979a21102e - sha256: 17dd67806f7e31981a1ac8abb63ed004eac416a1061c7737028f5af269430fa6 - category: main - optional: false -- name: libgoogle-cloud - version: 2.10.1 - manager: conda - platform: osx-arm64 - dependencies: - libabseil: '>=20230125.2,<20230126.0a0' - libcrc32c: '>=1.1.2,<1.2.0a0' - libcurl: '>=8.0.1,<9.0a0' - libcxx: '>=15.0.7' - libgrpc: '>=1.54.2,<1.55.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-2.10.1-he22f4c0_1.conda - hash: - md5: 6f3cac37296e140bd390591b23575f77 - sha256: df77f3a550e62022cde6d77522f11f3af1f0868faf9abbe0adbb4908b2cd1ce9 - category: main - optional: false -- name: lockfile - version: 0.12.2 - manager: conda - platform: osx-arm64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/lockfile-0.12.2-py_1.tar.bz2 - hash: - md5: c104d98e09c47519950cffb8dd5b4f10 - sha256: d3a68045ef74a2a7b8c8a55b242fdbc875d362e37adcf793613cf0d8c8e4fbf7 - category: main - optional: false -- name: markupsafe - version: 2.1.2 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-2.1.2-py39h02fc5c5_0.conda - hash: - md5: 525d6fb3283d4b90cd9f92c9811214af - sha256: 33f4eb17d29fe5983f27ac193e1dd071857447649a6a4197f1bb0310f1928f57 - category: main - optional: false -- name: mdurl - version: 0.1.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: f8dab71fdc13b1bf29a01248b156d268 - sha256: c678b9194e025b1fb665bec30ee20aab93399203583875b1dcc0a3b52a8f5523 - category: main - optional: false -- name: more-itertools - version: 9.1.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/more-itertools-9.1.0-pyhd8ed1ab_0.conda - hash: - md5: 1698a717f83cfecf644a877c174c84bd - sha256: 3ee8cbbe4004c56b695a5e734b7dc4d59dacbfefc193ee42c82238b1cf888e08 - category: main - optional: false -- name: msgpack-python - version: 1.0.5 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.0.5-py39haaf3ac1_0.conda - hash: - md5: 8e142afe3469a80ffd282864a71b793c - sha256: a51c61d4da38bc243189bb1361d6854d3d10b16a29599e739160f629d49627fa - category: main - optional: false -- name: munkres - version: 1.1.4 - manager: conda - platform: osx-arm64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2 - hash: - md5: 2ba8498c1018c1e9c61eb99b973dfe19 - sha256: f86fb22b58e93d04b6f25e0d811b56797689d598788b59dcb47f59045b568306 - category: main - optional: false -- name: ordered-set - version: 4.1.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/ordered-set-4.1.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 9a8714decb3967b290263817e876d8a9 - sha256: 78d92f848a6b4a89148dfa1f6e65c0b75e8f3a267b6401be38fb3401853b4afa - category: main - optional: false -- name: orjson - version: 3.8.14 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/orjson-3.8.14-py39hf934cd7_0.conda - hash: - md5: 838eda1e2926b7cf4b523d173a5f4edb - sha256: 38ccaf645f5dacb1a9bc6b13ec56ead9c4fbf27d63b92eaf81880778bc9706a9 - category: main - optional: false -- name: packaging - version: '23.1' - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/packaging-23.1-pyhd8ed1ab_0.conda - hash: - md5: 91cda59e66e1e4afe9476f8ef98f5c30 - sha256: ded536a96a00d45a693dbc2971bb688248324dadd129eddda2100e177583d768 - category: main - optional: false -- name: pillow - version: 9.4.0 - manager: conda - platform: osx-arm64 - dependencies: - freetype: '>=2.12.1,<3.0a0' - jpeg: '>=9e,<10a' - lcms2: '>=2.14,<3.0a0' - libtiff: '>=4.5.0,<4.6.0a0' - libwebp-base: '>=1.2.4,<2.0a0' - libxcb: '>=1.13,<1.14.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openjpeg: '>=2.5.0,<3.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - tk: '>=8.6.12,<8.7.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-9.4.0-py39h8bd98a6_1.conda - hash: - md5: 90500f863712b55483294662f1f5f5f1 - sha256: 3005f4fc32c370c380abc692c027a1391ab8248798153cb2eca62dfc569912f7 - category: main - optional: false -- name: pkginfo - version: 1.9.6 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.9.6-pyhd8ed1ab_0.conda - hash: - md5: be1e9f1c65a1ed0f2ae9352fec99db64 - sha256: 7ea5a5af62a15376d9f4f9f3c134874d0b0710f39be719e849b7fa9ca8870502 - category: main - optional: false -- name: pkgutil-resolve-name - version: 1.3.10 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pkgutil-resolve-name-1.3.10-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 89e3c7cdde7d3aaa2aee933b604dd07f - sha256: 7d055ffc8a02bf781a89d069db3454b453605cdaff300b82cedcc7133283e47e - category: main - optional: false -- name: prometheus_client - version: 0.17.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.17.0-pyhd8ed1ab_0.conda - hash: - md5: 95c5be3c7cbd872509d16c216617fdab - sha256: eb11fd8b927d9c5ff9482cfbd6cd810a43a1351c44a288e9680542ea698a19a0 - category: main - optional: false -- name: psutil - version: 5.9.5 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-5.9.5-py39h02fc5c5_0.conda - hash: - md5: 3870e334846ba9135091e7675f737610 - sha256: 77c19b06792c17c2b0bd9f07754490385ce2a5a8ef0d915a279b80ace5147324 - category: main - optional: false -- name: ptyprocess - version: 0.7.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd3deb0d_0.tar.bz2 - hash: - md5: 359eeb6536da0e687af562ed265ec263 - sha256: fb31e006a25eb2e18f3440eb8d17be44c8ccfae559499199f73584566d0a444a - category: main - optional: false -- name: pycparser - version: '2.21' - manager: conda - platform: osx-arm64 - dependencies: - python: 2.7.*|>=3.4 - url: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 076becd9e05608f8dc72757d5f3a91ff - sha256: 74c63fd03f1f1ea2b54e8bc529fd1a600aaafb24027b738d0db87909ee3a33dc - category: main - optional: false -- name: pygments - version: 2.15.1 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/pygments-2.15.1-pyhd8ed1ab_0.conda - hash: - md5: d316679235612869eba305aa7d41d9bf - sha256: 1bddeb54863c77ed5613b535a3e06a3a16b55786301a5e28c9bf011656bda686 - category: main - optional: false -- name: pyjwt - version: 2.7.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyjwt-2.7.0-pyhd8ed1ab_0.conda - hash: - md5: 99e28be5a278e2319834d7dc99e7bfdd - sha256: f3a64306fa0f405f10f4108d7ff42043d6fd393f940f9e98e395a3756687fc98 - category: main - optional: false -- name: pyparsing - version: 3.0.9 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2 - hash: - md5: e8fbc1b54b25f4b08281467bc13b70cc - sha256: 4acc7151cef5920d130f2e0a7615559cce8bfb037aeecb14d4d359ae3d9bc51b - category: main - optional: false -- name: pyrsistent - version: 0.19.3 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/pyrsistent-0.19.3-py39h02fc5c5_0.conda - hash: - md5: 750d82d39fc4c317590580dbf2e7a943 - sha256: 894bc6aa3ccf590acb58944a4d2bd3100616f438954576321cd4d6ca641e7ebe - category: main - optional: false -- name: pysocks - version: 1.7.1 - manager: conda - platform: osx-arm64 - dependencies: - __unix: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2 - hash: - md5: 2a7de29fb590ca14b5243c4c812c8025 - sha256: a42f826e958a8d22e65b3394f437af7332610e43ee313393d1cf143f0a2d274b - category: main - optional: false -- name: python-editor - version: 1.0.4 - manager: conda - platform: osx-arm64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/python-editor-1.0.4-py_0.tar.bz2 - hash: - md5: eaaf29a0644f9407f98a4665f45880c4 - sha256: a6db88da69a27451d2eba675c445bdefd2dbea52ea02a0a214d5fd4f0af31740 - category: main - optional: false -- name: python-installer - version: 0.7.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/python-installer-0.7.0-pyhd8ed1ab_0.conda - hash: - md5: 65dea78f903d686c8b0c2feaf0e15e1f - sha256: 822f95b7786cfa61a6519153117b21d93194890e02a884b9f66ee4275e4f1c0a - category: main - optional: false -- name: python-multipart - version: 0.0.6 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/python-multipart-0.0.6-pyhd8ed1ab_0.conda - hash: - md5: f4f642eeda814c1b65f46fbdf7e89096 - sha256: 2a9b8d02a6ec9862433cfc2741c4cbfe321e4ae3bab066f7ed84bc00effb73d7 - category: main - optional: false -- name: python-tzdata - version: '2023.3' - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2023.3-pyhd8ed1ab_0.conda - hash: - md5: 2590495f608a63625e165915fb4e2e34 - sha256: 0108888507014fb24573c31e4deceb61c99e63d37776dddcadd7c89b2ecae0b6 - category: main - optional: false -- name: pytz - version: '2023.3' - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pytz-2023.3-pyhd8ed1ab_0.conda - hash: - md5: d3076b483092a435832603243567bc31 - sha256: e4999484f21763ca4b8f92c95b22cb6d1edc1b61d0a2bb073ee2bd11f39401b9 - category: main - optional: false -- name: pywin32-on-windows - version: 0.1.0 - manager: conda - platform: osx-arm64 - dependencies: - __unix: '' - python: '>=2.7' - url: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - hash: - md5: 2807a0becd1d986fe1ef9b7f8135f215 - sha256: 6502696aaef571913b22a808b15c185bd8ea4aabb952685deb29e6a6765761cb - category: main - optional: false -- name: pyyaml - version: '6.0' - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - yaml: '>=0.2.5,<0.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0-py39h02fc5c5_5.tar.bz2 - hash: - md5: 0f0d3b67c91d129e1fd912985880eaa5 - sha256: 8e5611d518ce0cd138f373d51df2e322c91b30d2a4c5041aa340bd04bcb64e97 - category: main - optional: false -- name: readchar - version: 4.0.5 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/readchar-4.0.5-pyhd8ed1ab_0.conda - hash: - md5: 513334936060e80697bc21079e4f2829 - sha256: 0426cd7a524c31ab6d52b4d181848daea81d057e200a74200ea6e2896534bc18 - category: main - optional: false -- name: setuptools - version: 67.7.2 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/setuptools-67.7.2-pyhd8ed1ab_0.conda - hash: - md5: 3b68bc43ec6baa48f7354a446267eefe - sha256: 3ac44771fce01f19218bcdf3992e24984748048db69889a9df65abcc6a10e29b - category: main - optional: false -- name: shellingham - version: 1.5.1 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/shellingham-1.5.1-pyhd8ed1ab_0.conda - hash: - md5: 1de44299f48f522caa2e0074231614e1 - sha256: 3cb4a4a83b617fdfef9b92751634488db0b8961c80340be8068bf6d4f1d5ac25 - category: main - optional: false -- name: six - version: 1.16.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 - hash: - md5: e5f25f8dbc060e9a8d912e432202afc2 - sha256: a85c38227b446f42c5b90d9b642f2c0567880c15d72492d8da074a59c8f91dd6 - category: main - optional: false -- name: smmap - version: 3.0.5 - manager: conda - platform: osx-arm64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/smmap-3.0.5-pyh44b312d_0.tar.bz2 - hash: - md5: 3a8dc70789709aa315325d5df06fb7e4 - sha256: 091de70ee6bfe063e0c0f77336975d124fd1e3f49b9c58d97c0c7b3d287c0002 - category: main - optional: false -- name: sniffio - version: 1.3.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: dd6cbc539e74cb1f430efbd4575b9303 - sha256: a3fd30754c20ddb28b777db38345ea00d958f46701f0decd6291a81c0f4eee78 - category: main - optional: false -- name: soupsieve - version: 2.3.2.post1 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 146f4541d643d48fc8a75cacf69f03ae - sha256: 72d80dda41c3902c2619e8ab49d4f5b2a894d13375e1f9ed16fc00074ddd2307 - category: main - optional: false -- name: sqlparse - version: 0.4.4 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.5' - url: https://conda.anaconda.org/conda-forge/noarch/sqlparse-0.4.4-pyhd8ed1ab_0.conda - hash: - md5: 2e2f31b3b1c866c29636377e14f8c4c6 - sha256: 7972c9b15dafa1885f3d4cd22dc4edea4cd969d12739fb71f8632f2c3350706a - category: main - optional: false -- name: tabulate - version: 0.9.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.9.0-pyhd8ed1ab_1.tar.bz2 - hash: - md5: 4759805cce2d914c38472f70bf4d8bcb - sha256: f6e4a0dd24ba060a4af69ca79d32361a6678e61d78c73eb5e357909b025b4620 - category: main - optional: false -- name: threadpoolctl - version: 3.1.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2 - hash: - md5: a2995ee828f65687ac5b1e71a2ab1e0c - sha256: c7a964811ba49c545236f7d6c2486b2ed493931660048a06fe94ecc851dd0b82 - category: main - optional: false -- name: tomli - version: 2.0.1 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 5844808ffab9ebdb694585b50ba02a96 - sha256: 4cd48aba7cd026d17e86886af48d0d2ebc67ed36f87f6534f4b67138f5a5a58f - category: main - optional: false -- name: tomlkit - version: 0.11.8 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tomlkit-0.11.8-pyha770c72_0.conda - hash: - md5: 75838e8556166263a82038b51d01d5f1 - sha256: 3002e87338a98ba501fbf53981f8267b2def2548265a3622d403d06747872ccd - category: main - optional: false -- name: traitlets - version: 5.9.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.9.0-pyhd8ed1ab_0.conda - hash: - md5: d0b4f5c87cd35ac3fb3d47b223263a64 - sha256: 343610bce6dbe8a5090500dd2e9d1706057960b3f3120ebfe0abb4a8ecbada4d - category: main - optional: false -- name: trove-classifiers - version: 2023.5.24 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2023.5.24-pyhd8ed1ab_0.conda - hash: - md5: 4580a4f27cad1c3b275f6f6ad310abae - sha256: 05e83cd3ac921143c7a25681928727bcc9b01bf8456c9615b72d64f050863503 - category: main - optional: false -- name: typing - version: 3.10.0.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/typing-3.10.0.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: e6573ac68718f17b9d4f5c8eda3190f2 - sha256: ec1cfe0b7dc55a22223562cad799e0b16d122dab611c9923b6068d27a784ba2f - category: main - optional: false -- name: typing_extensions - version: 4.5.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.5.0-pyha770c72_0.conda - hash: - md5: 43e7d9e50261fb11deb76e17d8431aac - sha256: f81eee64fcdfb379e27d01773b34041fbf7f9e86f33b157c9925d19e0a442452 - category: main - optional: false -- name: unicodedata2 - version: 15.0.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-15.0.0-py39h02fc5c5_0.tar.bz2 - hash: - md5: 1371c4d91f9c3edf170200a1374cb3e8 - sha256: 3c0454fd960aca8f465db69beb281bbd8b4174e3df48871b625d43b037aea671 - category: main - optional: false -- name: webencodings - version: 0.5.1 - manager: conda - platform: osx-arm64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-py_1.tar.bz2 - hash: - md5: 3563be4c5611a44210d9ba0c16113136 - sha256: 302f4f4bd1ad00c0be1426ecf6bb01db59cfd8aff3de0cf1596526dca1a6b70e - category: main - optional: false -- name: websocket-client - version: 1.5.2 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.5.2-pyhd8ed1ab_0.conda - hash: - md5: bfe7e7cd1476092f51efbcde15dfb110 - sha256: 85310b382c4220d7846fa8f046216fd722b88db07991f07bd7decdf2e5dc3446 - category: main - optional: false -- name: websockets - version: 11.0.3 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/websockets-11.0.3-py39h0f82c59_0.conda - hash: - md5: ffb09a37b90dc7d1ab40ee0c8ab6b9f4 - sha256: 4e9fc023e126693cf7147541f1e93a371fc2603f76fd13d2788b74d7ac72dec4 - category: main - optional: false -- name: wheel - version: 0.40.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.40.0-pyhd8ed1ab_0.conda - hash: - md5: 49bb0d9e60ce1db25e151780331bb5f3 - sha256: 79b4d29b0c004014a2abd5fc2c9fcd35cc6256222b960c2a317a27c4b0d8884d - category: main - optional: false -- name: zipp - version: 3.15.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/zipp-3.15.0-pyhd8ed1ab_0.conda - hash: - md5: 13018819ca8f5b7cc675a8faf1f5fedf - sha256: 241de30545299be9bcea3addf8a2c22a3b3d4ba6730890e150ab690ac937a3d2 - category: main - optional: false -- name: anyio - version: 3.7.0 - manager: conda - platform: osx-arm64 - dependencies: - typing_extensions: '' - exceptiongroup: '' - python: '>=3.7' - idna: '>=2.8' - sniffio: '>=1.1' - url: https://conda.anaconda.org/conda-forge/noarch/anyio-3.7.0-pyhd8ed1ab_1.conda - hash: - md5: 2b35a85d654a47aac8f34c1bb6de7142 - sha256: 863c11a6a0e937977229b405a16f6d43fff543dfe5b1a66da9c42ec0cbdaaf33 - category: main - optional: false -- name: aws-c-auth - version: 0.6.27 - manager: conda - platform: osx-arm64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-c-sdkutils: '>=0.1.9,<0.1.10.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.6.27-h71e70db_1.conda - hash: - md5: 35eab46802c832ba790bff5fc43d37b0 - sha256: a8aef38cfcc9d207568106e0545cbff154cf8c6c994afafde8f6b8d56c413135 - category: main - optional: false -- name: aws-c-mqtt - version: 0.8.11 - manager: conda - platform: osx-arm64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.8.11-h6d34d79_1.conda - hash: - md5: c0acb15c5547e8e17a32d61f040a683d - sha256: 93a93e00aefd388769ec040330435943f4b60b5b6a9161a60e7c0398c25aec34 - category: main - optional: false -- name: backports.cached-property - version: 1.0.2 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - typing: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/backports.cached-property-1.0.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 0e1df3978dd516e20ef88c86d51e5432 - sha256: 1d86eafb5e9ed078f891e12b46692d786723652907dfb01b047c7da31f92b862 - category: main - optional: false -- name: backports.functools_lru_cache - version: 1.6.4 - manager: conda - platform: osx-arm64 - dependencies: - setuptools: '' - backports: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/backports.functools_lru_cache-1.6.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: c5b3edc62d6309088f4970b3eaaa65a6 - sha256: fdea00d4b79990f3fe938e2716bc32bd895eb5c44b6c75b8261db095a1b33c16 - category: main - optional: false -- name: beautifulsoup4 - version: 4.12.2 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - soupsieve: '>=1.2' - url: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.2-pyha770c72_0.conda - hash: - md5: a362ff7d976217f8fa78c0f1c4f59717 - sha256: 52d3e6bcd442537e22699cd227d8fdcfd54b708eeb8ee5b4c671a6a9b9cd74da - category: main - optional: false -- name: cffi - version: 1.15.1 - manager: conda - platform: osx-arm64 - dependencies: - libffi: '>=3.4,<4.0a0' - pycparser: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.15.1-py39h7e6b969_3.conda - hash: - md5: 259002f955175cc89beb8477de5de291 - sha256: 0fdb684286cb933d398d32f306a2dbbd605acafc4a0f85ebb3c54ff30d604b41 - category: main - optional: false -- name: deepdiff - version: 6.3.0 - manager: conda - platform: osx-arm64 - dependencies: - orjson: '' - python: '>=3.7' - ordered-set: '>=4.1.0,<4.2.0' - url: https://conda.anaconda.org/conda-forge/noarch/deepdiff-6.3.0-pyhd8ed1ab_0.conda - hash: - md5: 67ce5e3eecbf1e5ff869269640ae6a53 - sha256: f949d860d532a07587bdb8466310394d8c1af4dd89bb65d65219161fcc16db10 - category: main - optional: false -- name: fonttools - version: 4.39.4 - manager: conda - platform: osx-arm64 - dependencies: - brotli: '' - munkres: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - unicodedata2: '>=14.0.0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.39.4-py39h0f82c59_0.conda - hash: - md5: 1572405c2c421ebf38df06e84260652a - sha256: a8b534c91ab04107abdf172d4cee7a3874ce371833f5091fc05e7975b90d4482 - category: main - optional: false -- name: gitdb - version: 4.0.10 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.4' - smmap: '>=3.0.1,<4' - url: https://conda.anaconda.org/conda-forge/noarch/gitdb-4.0.10-pyhd8ed1ab_0.conda - hash: - md5: 3706d2f3d7cb5dae600c833345a76132 - sha256: 0003ab2b971913380633c711bf49a54dcf06e179986c725b0925854b58878377 - category: main - optional: false -- name: gunicorn - version: 20.1.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - setuptools: '>=3.0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/gunicorn-20.1.0-py39h2804cbe_3.tar.bz2 - hash: - md5: f922160ceec2220530c29cc0c815c6ea - sha256: 4d36e3af11c914d5f099ddb20f5ff9b68d0e0caba03c200ad6510f4eef3a314f - category: main - optional: false -- name: h11 - version: 0.14.0 - manager: conda - platform: osx-arm64 - dependencies: - typing_extensions: '' - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: b21ed0883505ba1910994f1df031a428 - sha256: 817d2c77d53afe3f3d9cf7f6eb8745cdd8ea76c7adaa9d7ced75c455a2c2c085 - category: main - optional: false -- name: harfbuzz - version: 7.3.0 - manager: conda - platform: osx-arm64 - dependencies: - cairo: '>=1.16.0,<2.0a0' - freetype: '>=2.12.1,<3.0a0' - graphite2: '' - icu: '>=72.1,<73.0a0' - libcxx: '>=15.0.7' - libglib: '>=2.76.2,<3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/harfbuzz-7.3.0-h46e5fef_0.conda - hash: - md5: 5247712cd97eeda510d1436560b13833 - sha256: 29a2eb09dce14b93687660cf0efcdd2fb879a3786bce17ab73e56fbb05b3d26a - category: main - optional: false -- name: html5lib - version: '1.1' - manager: conda - platform: osx-arm64 - dependencies: - python: '' - webencodings: '' - six: '>=1.9' - url: https://conda.anaconda.org/conda-forge/noarch/html5lib-1.1-pyh9f0ad1d_0.tar.bz2 - hash: - md5: b2355343d6315c892543200231d7154a - sha256: 9ad06446fe9847e86cb20d220bf11614afcd2cbe9f58096f08d5d4018877bee4 - category: main - optional: false -- name: importlib-metadata - version: 6.6.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.8' - zipp: '>=0.5' - url: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.6.0-pyha770c72_0.conda - hash: - md5: f91a5d5175fb7ff2a91952ec7da59cb9 - sha256: 33d49065756a73fbb92277c756fa00a41891408528eb90ae05ff3367a401ae6e - category: main - optional: false -- name: importlib_resources - version: 5.12.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - zipp: '>=3.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.12.0-pyhd8ed1ab_0.conda - hash: - md5: e5fd2260a231ee63b6969f4801082f2b - sha256: 091cca3e010f7a7353152f0abda2d68cfd83ddde80a15e974d9e18b2047e7be2 - category: main - optional: false -- name: jaraco.classes - version: 3.2.3 - manager: conda - platform: osx-arm64 - dependencies: - more-itertools: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.2.3-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 31e4a1506968d017229bdb64695013a1 - sha256: 6a81b67a1de8f761f66a4540bbd07cc27f9fbf2c7d67aa3732ebef379cf62874 - category: main - optional: false -- name: jinja2 - version: 3.1.2 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - markupsafe: '>=2.0' - url: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2 - hash: - md5: c8490ed5c70966d232fdd389d0dbed37 - sha256: b045faba7130ab263db6a8fdc96b1a3de5fcf85c4a607c5f11a49e76851500b5 - category: main - optional: false -- name: joblib - version: 1.2.0 - manager: conda - platform: osx-arm64 - dependencies: - setuptools: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.2.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 7583652522d71ad78ba536bba06940eb - sha256: 0c21351871df2c0a53168575597dd9c881e2a9fa4c42fe89a9bcd7fab37f462c - category: main - optional: false -- name: libcblas - version: 3.9.0 - manager: conda - platform: osx-arm64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.9.0-16_osxarm64_openblas.tar.bz2 - hash: - md5: c7cfc18378f00d3faf7f8a9a2553be3c - sha256: 99a04c6a273e76b01ace4f3a8f333b96a76b7351a155aaeba179e283da5c264e - category: main - optional: false -- name: liblapack - version: 3.9.0 - manager: conda - platform: osx-arm64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.9.0-16_osxarm64_openblas.tar.bz2 - hash: - md5: 52d270c579bfca986d6cdd81eb5ed6e7 - sha256: 87204cb0ff22f260b3aa5fc7c938157b471eb2bd287acf1ba7e61a67f86ba959 - category: main - optional: false -- name: lightning-utilities - version: 0.8.0 - manager: conda - platform: osx-arm64 - dependencies: - typing_extensions: '' - python: '>=3.8' - packaging: '>=17.1' - url: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.8.0-pyhd8ed1ab_0.conda - hash: - md5: ad16f58b64d3b41f4cbb75040b06c9cc - sha256: 8c1fff22ab86c85768e65dc8c4f4664787476a21f4d934c4e0261a1fa7523f9c - category: main - optional: false -- name: markdown-it-py - version: 2.2.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - typing_extensions: '>=3.7.4' - mdurl: '>=0.1,<1' - url: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-2.2.0-pyhd8ed1ab_0.conda - hash: - md5: b2928a6c6d52d7e3562b4a59c3214e3a - sha256: 65ed439862c1851463f03a9bc5109992ce3e3e025e9a2d76d13ca19f576eee9f - category: main - optional: false -- name: omegaconf - version: 2.3.0 - manager: conda - platform: osx-arm64 - dependencies: - typing_extensions: '' - python: '>=3.7' - pyyaml: '>=5.1.0' - antlr-python-runtime: 4.9.* - url: https://conda.anaconda.org/conda-forge/noarch/omegaconf-2.3.0-pyhd8ed1ab_0.conda - hash: - md5: 23cc056834cab53849b91f78d6ee3ea0 - sha256: df806841be847e5287b22b6ae7f380874f81ea51f1b51ae14a570f3385c7b133 - category: main - optional: false -- name: pexpect - version: 4.8.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '' - ptyprocess: '>=0.5' - url: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.8.0-pyh1a96a4e_2.tar.bz2 - hash: - md5: 330448ce4403cc74990ac07c555942a1 - sha256: 07706c0417ead94f359ca7278f65452d3c396448777aba1da6a11fc351bdca9a - category: main - optional: false -- name: pip - version: 23.1.2 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - setuptools: '' - wheel: '' - url: https://conda.anaconda.org/conda-forge/noarch/pip-23.1.2-pyhd8ed1ab_0.conda - hash: - md5: 7288da0d36821349cf1126e8670292df - sha256: 4fe1f47f6eac5b2635a622b6f985640bf835843c1d8d7ccbbae0f7d27cadec92 - category: main - optional: false -- name: protobuf - version: 4.21.12 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - libprotobuf: '>=3.21.12,<3.22.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - setuptools: '' - url: https://conda.anaconda.org/conda-forge/osx-arm64/protobuf-4.21.12-py39h23fbdae_0.conda - hash: - md5: 0b221c03b2823acffb6365b8cc31e887 - sha256: 54929bb589a9273e62eeefc1d2940716d1c3465b375222cd00e4b510d2844a3b - category: main - optional: false -- name: pyproject_hooks - version: 1.0.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - tomli: '>=1.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.0.0-pyhd8ed1ab_0.conda - hash: - md5: 21de50391d584eb7f4441b9de1ad773f - sha256: 016340837fcfef57b351febcbe855eedf0c1f0ecfc910ed48c7fbd20535f9847 - category: main - optional: false -- name: python-dateutil - version: 2.8.2 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - six: '>=1.5' - url: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: dd999d1cc9f79e67dbb855c8924c7984 - sha256: 54d7785c7678166aa45adeaccfc1d2b8c3c799ca2dc05d4a82bb39b1968bd7da - category: main - optional: false -- name: pytorch - version: 1.13.1 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.9,<3.10.0a0' - typing_extensions: '' - url: https://conda.anaconda.org/pytorch/osx-arm64/pytorch-1.13.1-py3.9_0.tar.bz2 - hash: - md5: 9af32b4a87810a99bb9c49e92830e44a - sha256: e61fa6327b73f8dd85468e6691a41c0df0cbff42afbed9fe856dbac5ea849d4b - category: main - optional: false -- name: tqdm - version: 4.65.0 - manager: conda - platform: osx-arm64 - dependencies: - colorama: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.65.0-pyhd8ed1ab_1.conda - hash: - md5: ed792aff3acb977d09c7013358097f83 - sha256: b35f185a678109940d34f68ac5781c3cbda9b118b8d9886b8f68ab5be6afd4fc - category: main - optional: false -- name: typing-extensions - version: 4.5.0 - manager: conda - platform: osx-arm64 - dependencies: - typing_extensions: 4.5.0 - url: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.5.0-hd8ed1ab_0.conda - hash: - md5: b3c594fde1a80a1fc3eb9cc4a5dfe392 - sha256: 6da5e15fa533620ae2e7aca9a7d16013eed3a73ac64c47d7c3bf3deec39b63b9 - category: main - optional: false -- name: werkzeug - version: 2.3.4 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.8' - markupsafe: '>=2.1.1' - url: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.4-pyhd8ed1ab_0.conda - hash: - md5: 23ddbe41ab0115bc0bfb75dcbf5de7cf - sha256: 2df1970270839b36e13a4ba7e4b393cfa95aa1d7438909aa8c3db14170ea207c - category: main - optional: false -- name: arrow - version: 1.2.3 - manager: conda - platform: osx-arm64 - dependencies: - typing_extensions: '' - python: '>=3.6' - python-dateutil: '>=2.7.0' - url: https://conda.anaconda.org/conda-forge/noarch/arrow-1.2.3-pyhd8ed1ab_0.tar.bz2 - hash: - md5: fd1967c76eda3a3dd9e8e6cb7a15a028 - sha256: a0434c2770cf5b0ab5a33913c0b202b1521bc13f755b762d16a86b110425cdc2 - category: main - optional: false -- name: aws-c-s3 - version: 0.3.0 - manager: conda - platform: osx-arm64 - dependencies: - aws-c-auth: '>=0.6.27,<0.6.28.0a0' - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.3.0-h2ea4599_2.conda - hash: - md5: 9d477ec4087f5f5fa7a937381f850c08 - sha256: 556e2179cb561d3e68997715c112a6d8cdcdbcf0c6d9956cf0e79610b4f63d21 - category: main - optional: false -- name: bcrypt - version: 3.2.2 - manager: conda - platform: osx-arm64 - dependencies: - cffi: '>=1.1' - pip: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - six: '>=1.4.1' - url: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-3.2.2-py39h02fc5c5_1.tar.bz2 - hash: - md5: 40e49b607b462a8a297d00db57505d7d - sha256: ed0386b26d0f5e386f5f7759ab60a5ce0f355962d8abc6f8decdc5dcaf8f7634 - category: main - optional: false -- name: brotlipy - version: 0.7.0 - manager: conda - platform: osx-arm64 - dependencies: - cffi: '>=1.0.0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/brotlipy-0.7.0-py39h02fc5c5_1005.tar.bz2 - hash: - md5: cf0b1f6f29ee28e7b20d49cb66bae19e - sha256: d56a680b34d84144d396619eee5331493a9a611ee4ee21bd88a73bcac642abf4 - category: main - optional: false -- name: croniter - version: 1.3.15 - manager: conda - platform: osx-arm64 - dependencies: - python-dateutil: '' - python: '>=2.6' - url: https://conda.anaconda.org/conda-forge/noarch/croniter-1.3.15-pyhd8ed1ab_0.conda - hash: - md5: 50197abb95aa7024eb0eb58fe5a51b07 - sha256: f8f58f6a50a5f63a35ee3bf6805e6dee10fe910f17a339da038967118c12c64f - category: main - optional: false -- name: cryptography - version: 41.0.0 - manager: conda - platform: osx-arm64 - dependencies: - cffi: '>=1.12' - openssl: '>=3.1.1,<4.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-41.0.0-py39had97604_0.conda - hash: - md5: 2b4f09dfcce1ae3a4258a72d86ba7638 - sha256: 7c29b2554e7b4f1ab7a63578a7e6694db867cec593f5dd548cf9e81c2abae5ed - category: main - optional: false -- name: dateutils - version: 0.6.12 - manager: conda - platform: osx-arm64 - dependencies: - python-dateutil: '' - pytz: '' - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/dateutils-0.6.12-py_0.tar.bz2 - hash: - md5: acee371a07e9a38a7072e5a5f7054ead - sha256: fb554b32a8f880cafaff4e67c789965d97c41eb1a6cc9ab5a83c6b28b581d809 - category: main - optional: false -- name: flask - version: 2.3.2 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.8' - jinja2: '>=3.1.2' - click: '>=8.1.3' - importlib-metadata: '>=3.6.0' - itsdangerous: '>=2.1.2' - blinker: '>=1.6.2' - werkzeug: '>=2.3.3' - url: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.2-pyhd8ed1ab_0.conda - hash: - md5: 816d75d4c0f2e41b5765d17498c57a2e - sha256: f93246be286f2d0f93e85c4f08f9ce48f3eed875a79225e2ea119e70c0237421 - category: main - optional: false -- name: gitpython - version: 3.1.31 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - typing_extensions: '>=3.7.4.3' - gitdb: '>=4.0.1,<5' - url: https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.31-pyhd8ed1ab_0.conda - hash: - md5: f6e6b482110246a81c3f03e81c68752d - sha256: 77c531def610089bc190508fcf304cf96c085c5fe977ab8f7d7c1641769592ac - category: main - optional: false -- name: importlib-resources - version: 5.12.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - importlib_resources: '>=5.12.0,<5.12.1.0a0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-5.12.0-pyhd8ed1ab_0.conda - hash: - md5: 3544c818f0720c89eb16ae6940ab440b - sha256: 0675df2bf18e52d0ea2bc5e1009faac273f059361a0caf36c0e0edc7831098a9 - category: main - optional: false -- name: importlib_metadata - version: 6.6.0 - manager: conda - platform: osx-arm64 - dependencies: - importlib-metadata: '>=6.6.0,<6.6.1.0a0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-6.6.0-hd8ed1ab_0.conda - hash: - md5: 3cbc9615f10a3d471532b83e4250b971 - sha256: 5de35d3c019d8a36e0a0deeb04a62689837bd68234a0a73a3355b860b442eca4 - category: main - optional: false -- name: jsonschema - version: 4.17.3 - manager: conda - platform: osx-arm64 - dependencies: - typing_extensions: '' - importlib-metadata: '' - python: '>=3.7' - attrs: '>=17.4.0' - pyrsistent: '!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0' - importlib_resources: '>=1.4.0' - pkgutil-resolve-name: '>=1.3.10' - url: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.17.3-pyhd8ed1ab_0.conda - hash: - md5: 723268a468177cd44568eb8f794e0d80 - sha256: 4f68a23430d1afc5c9b41c46fbac0ade33c0bf57a293c646bfdd6dc65350eada - category: main - optional: false -- name: libass - version: 0.17.1 - manager: conda - platform: osx-arm64 - dependencies: - fontconfig: '>=2.14.2,<3.0a0' - fonts-conda-ecosystem: '' - freetype: '>=2.12.1,<3.0a0' - fribidi: '>=1.0.10,<2.0a0' - harfbuzz: '>=7.2.0,<8.0a0' - libexpat: '>=2.5.0,<3.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libass-0.17.1-h4da34ad_0.conda - hash: - md5: 02b4eb2856769fbde130e14d569b18b0 - sha256: b163e49c115ee3ec76083bfbde7162a0e0531fbb64b321d559b4d29d19338c75 - category: main - optional: false -- name: mako - version: 1.2.4 - manager: conda - platform: osx-arm64 - dependencies: - importlib-metadata: '' - python: '>=3.6' - markupsafe: '>=0.9.2' - url: https://conda.anaconda.org/conda-forge/noarch/mako-1.2.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 0d072f0edc017b6318dbab701e053f94 - sha256: 559ed0d4c600d9827c1e9e0f2f3a50724bf2281b28a04e08f60de63f0da309a6 - category: main - optional: false -- name: markdown - version: 3.4.3 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - importlib-metadata: '>=4.4' - url: https://conda.anaconda.org/conda-forge/noarch/markdown-3.4.3-pyhd8ed1ab_0.conda - hash: - md5: 89ed59ad509c05db6f5f2f573d499bfe - sha256: e32ac2c95112caa8cd81f0cbc710f4f4903180a115c7260f03b010d5a0aa771b - category: main - optional: false -- name: numpy - version: 1.24.3 - manager: conda - platform: osx-arm64 - dependencies: - libblas: '>=3.9.0,<4.0a0' - libcblas: '>=3.9.0,<4.0a0' - libcxx: '>=15.0.7' - liblapack: '>=3.9.0,<4.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-1.24.3-py39h485cf63_0.conda - hash: - md5: 85153e84ad6dee82ff538cc82d7d812a - sha256: bb9c15958597e16be367cbed23288d9199d8671d9c13e4176eb0af889643330f - category: main - optional: false -- name: platformdirs - version: 3.5.1 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - typing-extensions: '>=4.5' - url: https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.5.1-pyhd8ed1ab_0.conda - hash: - md5: e2be672aece1f060adf7154f76531a35 - sha256: d7845c01a9ee5a224cc9242782befed7d12dc6aac1103650ec87917b20f3579e - category: main - optional: false -- name: poetry-core - version: 1.6.1 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - importlib-metadata: '>=1.7.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-core-1.6.1-pyhd8ed1ab_0.conda - hash: - md5: a6d1f61527c27fcc0165a6701a46b9f4 - sha256: 6f6a66476908a1c109e36c852d934eedceb07e0cbc44d3fcd87c6f39521b57be - category: main - optional: false -- name: pydantic - version: 1.10.8 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - typing-extensions: '>=4.2.0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-1.10.8-py39h0f82c59_0.conda - hash: - md5: 96435c7d74dab9e8db33facc488ab202 - sha256: 335e2ac9810ebe39f034f8b2b6eb054729bf673febc280f76b1264c0d75d2117 - category: main - optional: false -- name: pynacl - version: 1.5.0 - manager: conda - platform: osx-arm64 - dependencies: - cffi: '>=1.4.1' - libsodium: '>=1.0.18,<1.0.19.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - six: '' - url: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.5.0-py39h02fc5c5_2.tar.bz2 - hash: - md5: 74b98be1102c2e3a3ac80b64948c2030 - sha256: 5ea4fd64cc38eb6824415cf863629db9bde9664e88950b6d1d9f6cee048f94c9 - category: main - optional: false -- name: python-build - version: 0.10.0 - manager: conda - platform: osx-arm64 - dependencies: - colorama: '' - pyproject_hooks: '' - python: '>=3.7' - tomli: '>=1.1.0' - packaging: '>=19.0' - importlib-metadata: '>=0.22' - url: https://conda.anaconda.org/conda-forge/noarch/python-build-0.10.0-pyhd8ed1ab_1.conda - hash: - md5: 0ab47ce574f6a8bcb9f2076436e7fedb - sha256: 4c2cd519c85aa8b8e584723ca5f452aa5941d18374470adebfe73bf30fd27573 - category: main - optional: false -- name: rich - version: 13.4.1 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7.0' - typing_extensions: '>=4.0.0,<5.0.0' - markdown-it-py: '>=2.2.0,<3.0.0' - pygments: '>=2.13.0,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/rich-13.4.1-pyhd8ed1ab_0.conda - hash: - md5: c3bcbe0d086f15e5918568d3865e4dbf - sha256: 312f2628e06a591096a851bf678833fe670ecb16e9b15517ce8e03d7c9d9e600 - category: main - optional: false -- name: sqlalchemy - version: 2.0.15 - manager: conda - platform: osx-arm64 - dependencies: - greenlet: '!=0.4.17' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - typing-extensions: '>=4.2.0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/sqlalchemy-2.0.15-py39h0f82c59_0.conda - hash: - md5: cb15239456af5a35662fd27a3b40df24 - sha256: 9999d7e3b0774a4454773291c2f0d249fb09142ba154b543dad46e57be292434 - category: main - optional: false -- name: starlette - version: 0.22.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - typing_extensions: '>=3.10.0' - anyio: <5,>=3.4.0 - url: https://conda.anaconda.org/conda-forge/noarch/starlette-0.22.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 49d5cdcc16c691e4ad9355c81f004c3e - sha256: 1441dd55c037184b7d2c1e1dbf60beafb1f92fdc13cabf78a85e12825a55269b - category: main - optional: false -- name: torchmetrics - version: 0.11.4 - manager: conda - platform: osx-arm64 - dependencies: - setuptools: '' - packaging: '' - python: '>=3.7' - pytorch: '>=1.8.1' - url: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-0.11.4-pyhd8ed1ab_0.conda - hash: - md5: 480cb2b6d502003e937d9e4326bc398f - sha256: 3927eae14903c76679d5151af39680e7d42c35e0bdaa5041f577fc40f0619be8 - category: main - optional: false -- name: uvicorn - version: 0.22.0 - manager: conda - platform: osx-arm64 - dependencies: - click: '>=7.0' - h11: '>=0.8' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/uvicorn-0.22.0-py39h2804cbe_0.conda - hash: - md5: 959e92a4026c3eff240533c56fd6d857 - sha256: 9e8d65a6eff5a7dbf5316c96f0bfb015e0884cb7fc24570a4607dada29fd95c2 - category: main - optional: false -- name: wcwidth - version: 0.2.6 - manager: conda - platform: osx-arm64 - dependencies: - backports.functools_lru_cache: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.6-pyhd8ed1ab_0.conda - hash: - md5: 078979d33523cb477bd1916ce41aacc9 - sha256: c1bd0ad7d854cae56977b7915ac2b78b652fa5f7ec1e9fc21e7fdb30cf4519b1 - category: main - optional: false -- name: xattr - version: 0.10.1 - manager: conda - platform: osx-arm64 - dependencies: - cffi: '>=1.0.0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/xattr-0.10.1-py39h02fc5c5_0.conda - hash: - md5: 0adfba7300c9afd90d27c61829f3cb97 - sha256: 3d93b9702b64d02c08223928480f0a294cd06b8aaa67b877280ab9b64be0cd34 - category: main - optional: false -- name: alembic - version: 1.11.1 - manager: conda - platform: osx-arm64 - dependencies: - importlib-metadata: '' - importlib_resources: '' - mako: '' - python: '>=3.7' - sqlalchemy: '>=1.3.0' - typing-extensions: '>=4' - url: https://conda.anaconda.org/conda-forge/noarch/alembic-1.11.1-pyhd8ed1ab_0.conda - hash: - md5: 6a55e123397b42b79c48b31d1b7a91b8 - sha256: 065dd1b38ebe3a0d14f45549f63cce55125052057db565be153cdd73aa2a7c8d - category: main - optional: false -- name: aws-crt-cpp - version: 0.20.2 - manager: conda - platform: osx-arm64 - dependencies: - aws-c-auth: '>=0.6.27,<0.6.28.0a0' - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-event-stream: '>=0.2.20,<0.2.21.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-c-mqtt: '>=0.8.11,<0.8.12.0a0' - aws-c-s3: '>=0.3.0,<0.3.1.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - libcxx: '>=15.0.7' - url: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.20.2-hcf593e6_0.conda - hash: - md5: e676cb08b5cc6f5c797098aaa15f16bb - sha256: 6f1f90606b991d4d66088327a3bcd0d939de63cf9f19347178dac682a71e0c41 - category: main - optional: false -- name: blessed - version: 1.19.1 - manager: conda - platform: osx-arm64 - dependencies: - __unix: '' - python: '>=3.8' - six: '>=1.9.0' - wcwidth: '>=0.1.4' - url: https://conda.anaconda.org/conda-forge/noarch/blessed-1.19.1-pyhe4f9e05_2.tar.bz2 - hash: - md5: 65486376a55a80933e5dd95681ddd8b8 - sha256: 9d5b1f751adfe6d77fa8a088417a3aed716a1f727c0fd0230195246362b9d562 - category: main - optional: false -- name: contourpy - version: 1.0.7 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - numpy: '>=1.16' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.0.7-py39haaf3ac1_0.conda - hash: - md5: 221d648082c1ebdd89e6968441b5a9c5 - sha256: 141e4de214f13537aee7acfa3ed49e43346af017d66030794cd0a4f62ceda9e6 - category: main - optional: false -- name: fastapi - version: 0.88.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - pydantic: '>=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0' - starlette: 0.22.0.* - url: https://conda.anaconda.org/conda-forge/noarch/fastapi-0.88.0-pyhd8ed1ab_0.conda - hash: - md5: 0bd34a2b460d02e2fbf88ca5d9d3e55d - sha256: 660b356b8d4f3b99b6294c638637699003b0533c0ecab9389c56367b4bfe5c59 - category: main - optional: false -- name: ffmpeg - version: 6.0.0 - manager: conda - platform: osx-arm64 - dependencies: - aom: '>=3.5.0,<3.6.0a0' - bzip2: '>=1.0.8,<2.0a0' - dav1d: '>=1.2.0,<1.2.1.0a0' - fontconfig: '>=2.14.2,<3.0a0' - fonts-conda-ecosystem: '' - freetype: '>=2.12.1,<3.0a0' - gmp: '>=6.2.1,<7.0a0' - gnutls: '>=3.7.8,<3.8.0a0' - lame: '>=3.100,<3.101.0a0' - libass: '>=0.17.1,<0.17.2.0a0' - libcxx: '>=15.0.7' - libiconv: '>=1.17,<2.0a0' - libopus: '>=1.3.1,<2.0a0' - libvpx: '>=1.13.0,<1.14.0a0' - libxml2: '>=2.11.3,<2.12.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openh264: '>=2.3.1,<2.3.2.0a0' - svt-av1: '>=1.5.0,<1.5.1.0a0' - x264: '>=1!164.3095,<1!165' - x265: '>=3.5,<3.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/ffmpeg-6.0.0-gpl_hc7e3b81_101.conda - hash: - md5: bcf2a4c3bb34b7a14a81e308cab96538 - sha256: 6121cc71a6800672a6d36167e38208a71aaa6ed057c1c31acf8217ba75dc6b3c - category: main - optional: false -- name: keyring - version: 23.13.1 - manager: conda - platform: osx-arm64 - dependencies: - importlib_metadata: '>=4.11.4' - jaraco.classes: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/keyring-23.13.1-py39h2804cbe_0.conda - hash: - md5: 95531229976116d08045ae3dbe09a949 - sha256: b03823d197caa12c0cdfa9cd41cffe83ffd06b8c26df648e3318b39c913cc589 - category: main - optional: false -- name: oauthlib - version: 3.2.2 - manager: conda - platform: osx-arm64 - dependencies: - cryptography: '' - blinker: '' - python: '>=3.6' - pyjwt: '>=1.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/oauthlib-3.2.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 8f882b197fd9c4941a787926baea4868 - sha256: 0cfd5146a91d3974f4abfc2a45de890371d510a77238fe553e036ec8c031dc5b - category: main - optional: false -- name: pandas - version: 2.0.2 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=15.0.7' - numpy: '>=1.21.6,<2.0a0' - python: '>=3.9,<3.10.0a0' - python-dateutil: '>=2.8.1' - python-tzdata: '>=2022a' - python_abi: 3.9.* - pytz: '>=2020.1' - url: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.0.2-py39h6b13a34_0.conda - hash: - md5: c36838d320d388418daccad623c4ca62 - sha256: 34cb73cd7003015b832e4c647f894d83fdd637fb68cea34379cc9d71e46b80ea - category: main - optional: false -- name: paramiko - version: 3.2.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - cryptography: '>=3.3' - bcrypt: '>=3.2' - pynacl: '>=1.5' - url: https://conda.anaconda.org/conda-forge/noarch/paramiko-3.2.0-pyhd8ed1ab_0.conda - hash: - md5: f212c7eb95e909df4795297f73690993 - sha256: e425a03e5e2ef2ec5a78711686c59cfceeeeec3a98165fbc7d186bd6a5cb78de - category: main - optional: false -- name: poetry-plugin-export - version: 1.4.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7,<4.0' - poetry-core: '>=1.6.0,<2.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-plugin-export-1.4.0-pyhd8ed1ab_0.conda - hash: - md5: 00893c7ea4f9f7620706e0aa94c01b6e - sha256: 54478b283b5967a85ee5da717f1512d7ec97eb07c7b52d1f2ad3cb080d56c0ac - category: main - optional: false -- name: prometheus_flask_exporter - version: 0.22.4 - manager: conda - platform: osx-arm64 - dependencies: - flask: '' - prometheus_client: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/prometheus_flask_exporter-0.22.4-pyhd8ed1ab_0.conda - hash: - md5: 43acea130cafd18740b73fa4c226c9f7 - sha256: be83619ef5964713cd298d0fb86eddd99b159e5fba3d0f91d624ce5d7c3890e0 - category: main - optional: false -- name: pyopenssl - version: 23.2.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - cryptography: '>=38.0.0,<42,!=40.0.0,!=40.0.1' - url: https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.2.0-pyhd8ed1ab_1.conda - hash: - md5: 34f7d568bf59d18e3fef8c405cbece21 - sha256: 4daea3dc896987cc1334956fccfc0ed738663a84ad0c1d3f576a7a7936091534 - category: main - optional: false -- name: rapidfuzz - version: 2.15.1 - manager: conda - platform: osx-arm64 - dependencies: - libcxx: '>=14.0.6' - numpy: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/rapidfuzz-2.15.1-py39h23fbdae_0.conda - hash: - md5: 1d41e6246e4bb9222dc1bcebf81c6abb - sha256: 6d835329569f56e7de67ba56977d9f98751803c4e9c6d38ecc669ec130ef36c7 - category: main - optional: false -- name: starsessions - version: 1.3.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6.2' - itsdangerous: '>=2.0.1' - starlette: '>=0' - url: https://conda.anaconda.org/conda-forge/noarch/starsessions-1.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 667d08040a85d7ea1c6d4af2290f96c4 - sha256: 4a500ac0a9fe56cee7958d6d0f6530272c43ee4c16c52600001decb39fe3cd59 - category: main - optional: false -- name: typer - version: 0.9.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - typing-extensions: '>=3.7.4.3' - colorama: '>=0.4.3,<0.5.0' - shellingham: '>=1.3.0,<2.0.0' - click: '>=7.1.1,<9' - rich: '>=10.11.0,<14.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/typer-0.9.0-pyhd8ed1ab_0.conda - hash: - md5: 5030a13b2fe5e143d5956d4943d3018f - sha256: d395e1e92281abb13e043220ecf8ea973ada8d38a1e8c683df14f46541c64bd2 - category: main - optional: false -- name: virtualenv - version: 20.23.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.8' - distlib: <1,>=0.3.6 - filelock: <4,>=3.11 - platformdirs: <4,>=3.2 - url: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.23.0-pyhd8ed1ab_0.conda - hash: - md5: a920e114c4c2ced2280e266da65ab5e6 - sha256: 13d667887ea08b6d1fe2eb09d2d737f9af7343735d3bfa5ffaa3f67eec8eaff7 - category: main - optional: false -- name: aws-sdk-cpp - version: 1.10.57 - manager: conda - platform: osx-arm64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-event-stream: '>=0.2.20,<0.2.21.0a0' - aws-crt-cpp: '>=0.20.2,<0.20.3.0a0' - libcurl: '>=8.1.1,<9.0a0' - libcxx: '>=15.0.7' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.10.57-h015ea4b_13.conda - hash: - md5: a5b527e9f928c752614226d91533630e - sha256: ea0a3fff497c5ef709f6ee60659cf168b80312123fda8cf2c9e6204c27d71338 - category: main - optional: false -- name: cleo - version: 2.0.1 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7,<4.0' - crashtest: '>=0.4.1,<0.5.0' - rapidfuzz: '>=2.2.0,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/cleo-2.0.1-pyhd8ed1ab_0.conda - hash: - md5: f1c5f2af6676cbe9206e191d1e70f661 - sha256: cf9bc4c9356ad8eb68512446eebc076386f2bfb8ca86626e8796621bc5a13082 - category: main - optional: false -- name: matplotlib-base - version: 3.7.1 - manager: conda - platform: osx-arm64 - dependencies: - certifi: '>=2020.06.20' - contourpy: '>=1.0.1' - cycler: '>=0.10' - fonttools: '>=4.22.0' - freetype: '>=2.12.1,<3.0a0' - importlib-resources: '>=3.2.0' - kiwisolver: '>=1.0.1' - libcxx: '>=14.0.6' - numpy: '>=1.20.3,<2.0a0' - packaging: '>=20.0' - pillow: '>=6.2.0' - pyparsing: '>=2.3.1' - python: '>=3.9,<3.10.0a0' - python-dateutil: '>=2.7' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.7.1-py39h35e9e80_0.conda - hash: - md5: d988ccc3ef2c093353ad1bb8ed9805ea - sha256: 177e8e057ba8f22153bcfb0ddfc3a03cb37e9f185a36411f05bfcf68f9084323 - category: main - optional: false -- name: urllib3 - version: 1.26.15 - manager: conda - platform: osx-arm64 - dependencies: - certifi: '' - python: <4.0 - idna: '>=2.0.0' - pyopenssl: '>=0.14' - pysocks: '>=1.5.6,<2.0,!=1.5.7' - cryptography: '>=1.3.4' - brotlipy: '>=0.6.0' - url: https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.15-pyhd8ed1ab_0.conda - hash: - md5: 27db656619a55d727eaf5a6ece3d2fd6 - sha256: 213bdf6c3a5d721fa83b45d527d3ecd340f9547c0d6bbd0b8d9d746ec9a1fb4b - category: main - optional: false -- name: dulwich - version: 0.21.5 - manager: conda - platform: osx-arm64 - dependencies: - certifi: '' - cryptography: '>=1.3.4' - idna: '>=2.0.0' - pyopenssl: '>=0.14' - pysocks: '>=1.5.6,<2.0,!=1.5.7' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - urllib3: '' - url: https://conda.anaconda.org/conda-forge/osx-arm64/dulwich-0.21.5-py39h0f82c59_0.conda - hash: - md5: 6f596d05e58fb2bedb89712280de73c3 - sha256: 73311042c72878905029aefb1f3f8f55ef656532c6652df26c0072642ed582d6 - category: main - optional: false -- name: libarrow - version: 11.0.0 - manager: conda - platform: osx-arm64 - dependencies: - aws-crt-cpp: '>=0.20.2,<0.20.3.0a0' - aws-sdk-cpp: '>=1.10.57,<1.10.58.0a0' - bzip2: '>=1.0.8,<2.0a0' - c-ares: '>=1.19.1,<2.0a0' - gflags: '>=2.2.2,<2.3.0a0' - glog: '>=0.6.0,<0.7.0a0' - libabseil: '>=20230125.2,<20230126.0a0' - libbrotlicommon: '>=1.0.9,<1.1.0a0' - libbrotlidec: '>=1.0.9,<1.1.0a0' - libbrotlienc: '>=1.0.9,<1.1.0a0' - libcxx: '>=15.0.7' - libevent: '>=2.1.12,<2.1.13.0a0' - libgoogle-cloud: '>=2.10.1,<2.10.2.0a0' - libgrpc: '>=1.54.2,<1.55.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - libthrift: '>=0.18.1,<0.18.2.0a0' - libutf8proc: '>=2.8.0,<3.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - openssl: '>=3.1.0,<4.0a0' - orc: '>=1.8.3,<1.8.4.0a0' - re2: '>=2023.3.2,<2023.3.3.0a0' - snappy: '>=1.1.10,<2.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-11.0.0-h2f988b0_21_cpu.conda - hash: - md5: c4379ec9e53f6467dc93e07be01f67b9 - sha256: dddf9c086bee5e1d727b1d1536f353ed0d505d5bf4f1ac7092987d7ea619da56 - category: main - optional: false -- name: requests - version: 2.31.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - idna: '>=2.5,<4' - certifi: '>=2017.4.17' - charset-normalizer: '>=2,<4' - urllib3: '>=1.21.1,<3' - url: https://conda.anaconda.org/conda-forge/noarch/requests-2.31.0-pyhd8ed1ab_0.conda - hash: - md5: a30144e4156cdbb236f99ebb49828f8b - sha256: 9f629d6fd3c8ac5f2a198639fe7af87c4db2ac9235279164bfe0fcb49d8c4bad - category: main - optional: false -- name: arrow-cpp - version: 11.0.0 - manager: conda - platform: osx-arm64 - dependencies: - libarrow: 11.0.0 - url: https://conda.anaconda.org/conda-forge/osx-arm64/arrow-cpp-11.0.0-hce30654_21_cpu.conda - hash: - md5: c323c6b3b1bd41137705ffa7a19a06bc - sha256: 475fceec05a139b23c94891c0a301054863cf0e7074dc952ad47a960073d6091 - category: main - optional: false -- name: cachecontrol - version: 0.12.11 - manager: conda - platform: osx-arm64 - dependencies: - requests: '' - python: '>=3.6' - msgpack-python: '>=0.5.2' - url: https://conda.anaconda.org/conda-forge/noarch/cachecontrol-0.12.11-pyhd8ed1ab_1.conda - hash: - md5: e8f0410e0aa03342304357c5cc3bb75d - sha256: 466ce7c155be90a5c903052eba391759ae88eb65f2bb06b0cc1c9d09c4311800 - category: main - optional: false -- name: databricks-cli - version: 0.17.7 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - click: '>=7.0' - six: '>=1.10.0' - pyjwt: '>=1.7.0' - tabulate: '>=0.7.7' - requests: '>=2.17.3' - configparser: '>=0.3.5' - oauthlib: '>=3.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/databricks-cli-0.17.7-pyhd8ed1ab_0.conda - hash: - md5: cb44b4e93848f13dce352c422285ac45 - sha256: 1c2ec6c6125bc1c1d100b75e1f5dfda09c56cd516157d2d5b01c1aa69fdd0dbd - category: main - optional: false -- name: docker-py - version: 6.1.0 - manager: conda - platform: osx-arm64 - dependencies: - pywin32-on-windows: '' - python: '>=3.7' - requests: '>=2.26.0' - urllib3: '>=1.26.0' - websocket-client: '>=0.32.0' - packaging: '>=14.0' - paramiko: '>=2.4.3' - url: https://conda.anaconda.org/conda-forge/noarch/docker-py-6.1.0-pyhd8ed1ab_0.conda - hash: - md5: 543336c6aa9516cfb29c51d5c162b177 - sha256: 5e01e15e20ee573c99b530633a0d5c71fd515e4ac6d2f5f5f57baece8b915cc3 - category: main - optional: false -- name: lightning-cloud - version: 0.5.36 - manager: conda - platform: osx-arm64 - dependencies: - requests: '' - six: '' - click: '' - rich: '' - pyjwt: '' - urllib3: '' - fastapi: '' - websocket-client: '' - python-multipart: '' - uvicorn: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/lightning-cloud-0.5.36-pyhd8ed1ab_0.conda - hash: - md5: fd99cc369aa3c6c66493d4d278338af5 - sha256: 2047f4dcd4531f6cd2f87a80fef0d265f663e011931af746b2725f7d567ce016 - category: main - optional: false -- name: pooch - version: 1.7.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.7' - packaging: '>=20.0' - requests: '>=2.19.0' - platformdirs: '>=2.5.0' - url: https://conda.anaconda.org/conda-forge/noarch/pooch-1.7.0-pyha770c72_3.conda - hash: - md5: 5936894aade8240c867d292aa0d980c6 - sha256: 64e4d633803df2e36fd141d9bf269568fbe179a313248e1dac4d364c02debdef - category: main - optional: false -- name: pytorch-lightning - version: 2.0.2 - manager: conda - platform: osx-arm64 - dependencies: - requests: '' - python: '>=3.8' - pyyaml: '>=5.4' - numpy: '>=1.17.2' - packaging: '>=17.1' - torchmetrics: '>=0.7.0' - tqdm: '>=4.57.0' - typing_extensions: '>=4.0.0' - pytorch: '>=1.11.0' - fsspec: '>2021.06.0' - lightning-utilities: '>=0.7.0' - url: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.0.2-pyhd8ed1ab_0.conda - hash: - md5: abd4916f586ae33b56d1e2b44a7990aa - sha256: 9332cb927d6c587ed5b23628208ebb4479288244f4388bd4cb9745df115cca25 - category: main - optional: false -- name: querystring_parser - version: 1.2.4 - manager: conda - platform: osx-arm64 - dependencies: - python: '' - requests: '' - six: '' - url: https://conda.anaconda.org/conda-forge/noarch/querystring_parser-1.2.4-py_0.tar.bz2 - hash: - md5: 0ebdca9b753c2e082e5b5ad06aa76b41 - sha256: 06977a9af6d8605fb6068d8af6bb9c1cb565f8f5e15aa6cf0fb94109d4148b54 - category: main - optional: false -- name: requests-toolbelt - version: 1.0.0 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - requests: '>=2.0.1,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-1.0.0-pyhd8ed1ab_0.conda - hash: - md5: 99c98318c8646b08cc764f90ce98906e - sha256: 20eaefc5dba74ff6c31e537533dde59b5b20f69e74df49dff19d43be59785fa3 - category: main - optional: false -- name: torchvision - version: 0.14.1 - manager: conda - platform: osx-arm64 - dependencies: - ffmpeg: '>=4.2' - jpeg: '' - libpng: '' - numpy: '>=1.11' - pillow: '>=5.3.0,!=8.3.*' - python: '>=3.9,<3.10.0a0' - pytorch: 1.13.1 - requests: '' - url: https://conda.anaconda.org/pytorch/osx-arm64/torchvision-0.14.1-py39_cpu.tar.bz2 - hash: - md5: 6ad912a9eeb6c714e4d5f984892cf99e - sha256: 864a306443cb6da9ac6dffbde6839e60b42ecbd284c17eee00a793ebce4f6f94 - category: main - optional: false -- name: cachecontrol-with-filecache - version: 0.12.11 - manager: conda - platform: osx-arm64 - dependencies: - python: '>=3.6' - lockfile: '>=0.9' - cachecontrol: 0.12.11 - url: https://conda.anaconda.org/conda-forge/noarch/cachecontrol-with-filecache-0.12.11-pyhd8ed1ab_1.conda - hash: - md5: 9df660456c0076d27b802448f7ede78f - sha256: 81c483fc92656873eb5a7ba657b208c34186556d942a9cebc1f7771e565b95b7 - category: main - optional: false -- name: parquet-cpp - version: 1.5.1 - manager: conda - platform: osx-arm64 - dependencies: - arrow-cpp: '>=0.11.0' - url: https://conda.anaconda.org/conda-forge/noarch/parquet-cpp-1.5.1-2.tar.bz2 - hash: - md5: 79a5f78c42817594ae016a7896521a97 - sha256: 15e50657515b791734ba045da5135377404ca37c518b2066b9c6451c65cd732e - category: main - optional: false -- name: scipy - version: 1.10.1 - manager: conda - platform: osx-arm64 - dependencies: - libblas: '>=3.9.0,<4.0a0' - libcblas: '>=3.9.0,<4.0a0' - libcxx: '>=15.0.7' - libgfortran: 5.* - libgfortran5: '>=12.2.0' - liblapack: '>=3.9.0,<4.0a0' - numpy: '>=1.21.6,<2.0a0' - pooch: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.10.1-py39ha6b2cbd_3.conda - hash: - md5: 62a3aefc19e44647f6401f6a89e029b2 - sha256: 4c445be1911ce185e14e09fd566a411642367968e4df854ad46dbb00bcbe5aae - category: main - optional: false -- name: poetry - version: 1.5.1 - manager: conda - platform: osx-arm64 - dependencies: - __osx: '' - tomli: '>=2.0.1,<3.0.0' - packaging: '>=20.4' - importlib-metadata: '>=4.4' - python: '>=3.7.0,<4.0.0' - urllib3: '>=1.26.0,<2.0.0' - crashtest: '>=0.4.1,<0.5.0' - requests: '>=2.18,<3.0' - pexpect: '>=4.7.0,<5.0.0' - cleo: '>=2.0.0,<3.0.0' - filelock: '>=3.8.0,<4.0.0' - jsonschema: '>=4.10.0,<5.0.0' - keyring: '>=23.9.0,<24.0.0' - trove-classifiers: '>=2022.5.19' - lockfile: '>=0.12.2,<0.13.0' - backports.cached-property: '>=1.0.2,<2.0.0' - dulwich: '>=0.21.2,<0.22.0' - pkginfo: '>=1.9.4,<2.0' - pyproject_hooks: '>=1.0.0,<2.0.0' - python-build: '>=0.10.0,<0.11.0' - python-installer: '>=0.7.0,<0.8.0' - xattr: '>=0.10.0,<0.11.0' - platformdirs: '>=3.0.0,<4.0.0' - requests-toolbelt: '>=0.9.1,<2' - tomlkit: '>=0.11.4,<1.0.0' - virtualenv: '>=20.22.0,<21.0.0' - cachecontrol-with-filecache: '>=0.12.9,<0.13.0' - html5lib: '>=1.0.0,<2.0.0' - poetry-core: 1.6.1.* - poetry-plugin-export: '>=1.4.0,<2.0.0' - shellingham: '>=1.5.0,<2.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-1.5.1-osx_pyhd8ed1ab_0.conda - hash: - md5: cb22f4398986b6cde646b8ec6efe141f - sha256: 9a24f0249abad2b3afbe6ff13f71abc35699e8f4e04bed3d9739c7d93689aade - category: main - optional: false -- name: pyarrow - version: 11.0.0 - manager: conda - platform: osx-arm64 - dependencies: - gflags: '>=2.2.2,<2.3.0a0' - libarrow: 11.0.0 - libcxx: '>=15.0.7' - numpy: '>=1.21.6,<2.0a0' - parquet-cpp: 1.5.1.* - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/pyarrow-11.0.0-py39ha0e5d75_21_cpu.conda - hash: - md5: ab671588be3d9a877a2a4fea46c0cf6a - sha256: af3f051d1ac89b837fb1e1e430761bd85fe5792f59fe1e3a9b5e00ca3b8e2bf8 - category: main - optional: false -- name: scikit-learn - version: 1.2.2 - manager: conda - platform: osx-arm64 - dependencies: - joblib: '>=1.1.1' - libcxx: '>=15.0.7' - llvm-openmp: '>=15.0.7' - numpy: '>=1.21.6,<2.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - scipy: '' - threadpoolctl: '>=2.0.0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/scikit-learn-1.2.2-py39hd5c4a62_2.conda - hash: - md5: d2641e60fc2e6cf8d4ffb67db563245a - sha256: 79c802696fc77af5dd57cc7aa7b2321e37a795ac8c3c9676e427b3e2ce730d01 - category: main - optional: false -- name: inquirer - version: 3.1.3 - manager: conda - platform: osx-arm64 - dependencies: - poetry: '' - python: '>=3.7' - blessed: '>=1.19.0' - python-editor: '>=1.0.4' - readchar: '>=2.0.1' - url: https://conda.anaconda.org/conda-forge/noarch/inquirer-3.1.3-pyhd8ed1ab_0.conda - hash: - md5: 0d8bc31361e09dc50555465284e10880 - sha256: da912877ac6e0795490834c96167e93a1eda89290ef8de63502740ef738d4435 - category: main - optional: false -- name: mlflow - version: 2.3.2 - manager: conda - platform: osx-arm64 - dependencies: - alembic: <2,!=1.10 - click: '>=7.0,<9' - cloudpickle: <3 - databricks-cli: '>=0.8.7,<1' - docker-py: '>=4.0.0,<7' - entrypoints: <1 - flask: <3 - gitpython: '>=2.1.0,<4' - gunicorn: <21 - importlib-metadata: <7,>=3.7.0,!=4.7.0 - jinja2: <4,>=2.11 - markdown: <4,>=3.3 - matplotlib-base: <4 - numpy: <2 - openssl: '' - packaging: <24 - pandas: <3 - prometheus_flask_exporter: <1 - protobuf: '>=3.12.0,<5' - pyarrow: <12,>=4.0.0 - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - pytz: <2024 - pyyaml: '>=5.1,<7' - querystring_parser: <2 - requests: '>=2.17.3,<3' - scikit-learn: <2 - scipy: <2 - sqlalchemy: '>=1.4.0,<3' - sqlparse: '>=0.4.0,<1' - url: https://conda.anaconda.org/conda-forge/osx-arm64/mlflow-2.3.2-py39h3c3ad29_1.conda - hash: - md5: 6d5407f5b18abaf3491b5ed82f04e282 - sha256: 633fe56e4d8573d9fab6c16e51997d350e4cf61d4c2b5cce80c67f70ff38bb90 - category: main - optional: false -- name: lightning - version: 2.0.0 - manager: conda - platform: osx-arm64 - dependencies: - packaging: '' - pytorch-lightning: '' - python-multipart: '' - python: '>=3.8' - arrow: <3.0,>=1.2.0 - beautifulsoup4: <6.0,>=4.8.0 - click: <10.0 - croniter: <1.4.0,>=1.3.0 - dateutils: <2.0 - deepdiff: <8.0,>=5.7.0 - fsspec: <2024.0,>=2022.5.0 - inquirer: <5.0,>=2.10.0 - jinja2: <5.0 - lightning-utilities: <2.0,>=0.7.0 - numpy: <3.0,>=1.17.2 - psutil: <7.0 - pytorch: <4.0,>=1.11.0 - pyyaml: <8.0 - requests: <4.0 - rich: <15.0,>=12.3.0 - starsessions: <2.0,>=1.2.1 - torchmetrics: <2.0,>=0.7.0 - tqdm: <6.0,>=4.57.0 - traitlets: <7.0,>=5.3.0 - typing-extensions: <6.0,>=4.0.0 - urllib3: <3.0 - uvicorn: <2.0 - websocket-client: <3.0 - websockets: <12.0 - fastapi: <0.89.0 - lightning-cloud: '>=0.5.31' - pydantic: <3.0 - starlette: <2.0 - url: https://conda.anaconda.org/conda-forge/noarch/lightning-2.0.0-pyhd8ed1ab_0.conda - hash: - md5: 5c38b552a8b1853c39738d9a9f090ffc - sha256: 4b6bf3a963ea91f81de4947ad8a0686bb8cf868f63a5a125088938fc9551ace1 - category: main - optional: false -- name: ca-certificates - version: 2023.5.7 - manager: conda - platform: win-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2023.5.7-h56e8100_0.conda - hash: - md5: 604212634bd8c4d6f20d44b946e8eedb - sha256: d0488a3e7a86cc11f8c847a7c12a5f1fb8567f05646faae78944807862f9d167 - category: main - optional: false -- name: intel-openmp - version: 2023.1.0 - manager: conda - platform: win-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/win-64/intel-openmp-2023.1.0-h57928b3_46319.conda - hash: - md5: dbc4636f419722fbf3ab6501377228ba - sha256: b3006690844eedbb51030e71778767acc9967f4148b6f335ba3fddf8aa42aa5b - category: main - optional: false -- name: mkl-include - version: 2022.1.0 - manager: conda - platform: win-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/win-64/mkl-include-2022.1.0-h6a75c08_874.tar.bz2 - hash: - md5: 414f6ab96ad71e7a95bd00d990fa3473 - sha256: cb871781fe28aa87c7bd494c554715ad04615a5f381f383554ce8ecaf8706610 - category: main - optional: false -- name: msys2-conda-epoch - version: '20160418' - manager: conda - platform: win-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/win-64/msys2-conda-epoch-20160418-1.tar.bz2 - hash: - md5: b0309b72560df66f71a9d5e34a5efdfa - sha256: 99358d58d778abee4dca82ad29fb58058571f19b0f86138363c260049d4ac7f1 - category: main - optional: false -- name: python_abi - version: '3.9' - manager: conda - platform: win-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/win-64/python_abi-3.9-3_cp39.conda - hash: - md5: c7f654abbee61f9a03f54e9064e79e89 - sha256: 86dcf63e17364e1f7e06aac05310954f374a1bfae8ff3cfb721b3cca87010f1e - category: main - optional: false -- name: pytorch-mutex - version: '1.0' - manager: conda - platform: win-64 - dependencies: {} - url: https://conda.anaconda.org/pytorch/noarch/pytorch-mutex-1.0-cpu.tar.bz2 - hash: - md5: 49565ed726991fd28d08a39885caa88d - sha256: d48c964188ca49660d750cffd73698d217cf94e694cd51987f9f186425435e76 - category: main - optional: false -- name: tzdata - version: 2023c - manager: conda - platform: win-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2023c-h71feb2d_0.conda - hash: - md5: 939e3e74d8be4dac89ce83b20de2492a - sha256: 0449138224adfa125b220154408419ec37c06b0b49f63c5954724325903ecf55 - category: main - optional: false -- name: ucrt - version: 10.0.22621.0 - manager: conda - platform: win-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.bz2 - hash: - md5: 72608f6cd3e5898229c3ea16deb1ac43 - sha256: f29cdaf8712008f6b419b8b1a403923b00ab2504bfe0fb2ba8eb60e72d4f14c6 - category: main - optional: false -- name: cpuonly - version: '2.0' - manager: conda - platform: win-64 - dependencies: - pytorch-mutex: '1.0' - url: https://conda.anaconda.org/pytorch/noarch/cpuonly-2.0-0.tar.bz2 - hash: - md5: 1cf3a59ef90a4078c253e3b02c272065 - sha256: f9107aca2a9d23a032634644df5cdb8d0185337891593ce540adc480810ab539 - category: main - optional: false -- name: m2w64-gmp - version: 6.1.0 - manager: conda - platform: win-64 - dependencies: - msys2-conda-epoch: '>=20160418' - url: https://conda.anaconda.org/conda-forge/win-64/m2w64-gmp-6.1.0-2.tar.bz2 - hash: - md5: 53a1c73e1e3d185516d7e3af177596d9 - sha256: 7e3cd95f554660de45f8323fca359e904e8d203efaf07a4d311e46d611481ed1 - category: main - optional: false -- name: m2w64-libwinpthread-git - version: 5.0.0.4634.697f757 - manager: conda - platform: win-64 - dependencies: - msys2-conda-epoch: '>=20160418' - url: https://conda.anaconda.org/conda-forge/win-64/m2w64-libwinpthread-git-5.0.0.4634.697f757-2.tar.bz2 - hash: - md5: 774130a326dee16f1ceb05cc687ee4f0 - sha256: f63a09b2cae7defae0480f1740015d6235f1861afa6fe2e2d3e10bd0d1314ee0 - category: main - optional: false -- name: vc14_runtime - version: 14.34.31931 - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - url: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.34.31931-h5081d32_16.conda - hash: - md5: 22125178654c6a8a393f9743d585704b - sha256: 1161f4848e1c0663897a6324fbc4ff13dafd11650b42c9864428da73593dda95 - category: main - optional: false -- name: m2w64-gcc-libs-core - version: 5.3.0 - manager: conda - platform: win-64 - dependencies: - m2w64-gmp: '' - m2w64-libwinpthread-git: '' - msys2-conda-epoch: '>=20160418' - url: https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-core-5.3.0-7.tar.bz2 - hash: - md5: 4289d80fb4d272f1f3b56cfe87ac90bd - sha256: 58afdfe859ed2e9a9b1cc06bc408720cb2c3a6a132e59d4805b090d7574f4ee0 - category: main - optional: false -- name: vc - version: '14.3' - manager: conda - platform: win-64 - dependencies: - vc14_runtime: '>=14.32.31332' - url: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-hb25d44b_16.conda - hash: - md5: ea326b37e3bd6d2616988e09f3a9396c - sha256: 29bc108d66150ca75cab937f844f4ac4a836beb6ea3ee167d03c611444bb2a82 - category: main - optional: false -- name: vs2015_runtime - version: 14.34.31931 - manager: conda - platform: win-64 - dependencies: - vc14_runtime: '>=14.34.31931' - url: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.34.31931-hed1258a_16.conda - hash: - md5: 0374eae69b6dbfb27c3dc27167109eb4 - sha256: 25d852887a501ca8cb6753a4f3dae1549fa49592d51aec1a230b1f0c85fe4297 - category: main - optional: false -- name: aws-c-common - version: 0.8.19 - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/aws-c-common-0.8.19-hcfcfb64_0.conda - hash: - md5: e7f5362ad02b72bc01eb21b2b645c7c4 - sha256: 67553bd66630e19c83b6a1bc99110e85e5f3869dba3fd4cc7896bc5575084dab - category: main - optional: false -- name: bzip2 - version: 1.0.8 - manager: conda - platform: win-64 - dependencies: - vc: '>=14.1,<15.0a0' - vs2015_runtime: '>=14.16.27012' - url: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h8ffe710_4.tar.bz2 - hash: - md5: 7c03c66026944073040cb19a4f3ec3c9 - sha256: 5389dad4e73e4865bb485f460fc60b120bae74404003d457ecb1a62eb7abf0c1 - category: main - optional: false -- name: c-ares - version: 1.19.1 - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/c-ares-1.19.1-hcfcfb64_0.conda - hash: - md5: 8aa74d9a74ed3a31d9ed38a387a8ca50 - sha256: 4dc79f3b5784ea9bffcbd27f2c23a52f0507e877af59d002aa2202c07d0d4951 - category: main - optional: false -- name: gflags - version: 2.2.2 - manager: conda - platform: win-64 - dependencies: - vc: '>=14.1,<15.0a0' - vs2015_runtime: '>=14.16.27012' - url: https://conda.anaconda.org/conda-forge/win-64/gflags-2.2.2-ha925a31_1004.tar.bz2 - hash: - md5: e9442160f56fa442d4ff3eb2e4cf0f22 - sha256: d2dbb918efa9c89ead501347cce753bdbac3f5426d42ae5f48eee73790757f54 - category: main - optional: false -- name: jpeg - version: 9e - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/jpeg-9e-hcfcfb64_3.conda - hash: - md5: 824f1e030d224e9e376a4655032fdbc7 - sha256: 7ee2ecbb5fe11566601e612fa463fc2060e55083d9cb1eb05c58e30a9e110b41 - category: main - optional: false -- name: lerc - version: 4.0.0 - manager: conda - platform: win-64 - dependencies: - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30037' - url: https://conda.anaconda.org/conda-forge/win-64/lerc-4.0.0-h63175ca_0.tar.bz2 - hash: - md5: 1900cb3cab5055833cfddb0ba233b074 - sha256: f4f39d7f6a2f9b407f8fb567a6c25755270421731d70f0ff331f5de4fa367488 - category: main - optional: false -- name: libabseil - version: '20230125.2' - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libabseil-20230125.2-cxx17_h63175ca_2.conda - hash: - md5: 828f6bb057da235a4703ed33192f81c5 - sha256: 8c4e1cde02f7937b4b1f089a75e785f86241f55d1e5d0101a80e3fae0704c173 - category: main - optional: false -- name: libbrotlicommon - version: 1.0.9 - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.0.9-hcfcfb64_8.tar.bz2 - hash: - md5: e8078e37208cd7d3e1eb5053f370ded8 - sha256: 0e771d447108219f43de770f7ca9428b2e3b5a4fd08475a27ac442ad310cb684 - category: main - optional: false -- name: libcrc32c - version: 1.1.2 - manager: conda - platform: win-64 - dependencies: - vc: '>=14.1,<15.0a0' - vs2015_runtime: '>=14.16.27012' - url: https://conda.anaconda.org/conda-forge/win-64/libcrc32c-1.1.2-h0e60522_0.tar.bz2 - hash: - md5: cd4cc2d0c610c8cb5419ccc979f2d6ce - sha256: 75e60fbe436ba8a11c170c89af5213e8bec0418f88b7771ab7e3d9710b70c54e - category: main - optional: false -- name: libdeflate - version: '1.17' - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.17-hcfcfb64_0.conda - hash: - md5: ae9dfb57bcb42093a2417aceabb530f7 - sha256: 76e642ca8a11da1b537506447f8089353b6607956c069c938a4bec4de36e1194 - category: main - optional: false -- name: libffi - version: 3.4.2 - manager: conda - platform: win-64 - dependencies: - vc: '>=14.1,<15.0a0' - vs2015_runtime: '>=14.16.27012' - url: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2 - hash: - md5: 2c96d1b6915b408893f9472569dee135 - sha256: 1951ab740f80660e9bc07d2ed3aefb874d78c107264fd810f24a1a6211d4b1a5 - category: main - optional: false -- name: libiconv - version: '1.17' - manager: conda - platform: win-64 - dependencies: - vc: '>=14.1,<15' - vs2015_runtime: '>=14.16.27033' - url: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.17-h8ffe710_0.tar.bz2 - hash: - md5: 050119977a86e4856f0416e2edcf81bb - sha256: 657c2a992c896475021a25faebd9ccfaa149c5d70c7dc824d4069784b686cea1 - category: main - optional: false -- name: libsqlite - version: 3.42.0 - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.42.0-hcfcfb64_0.conda - hash: - md5: 9a71d93deb99cc09d8939d5235b5909a - sha256: 70bc1fdb72de847807355c13144666d4f151894f9b141ee559f5d243bdf577e2 - category: main - optional: false -- name: libutf8proc - version: 2.8.0 - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libutf8proc-2.8.0-h82a8f57_0.tar.bz2 - hash: - md5: 076894846fe9f068f91c57d158c90cba - sha256: 6efa83e3f2fb9acaf096a18d21d0f679d110934798348c5defc780d4b759a76c - category: main - optional: false -- name: libuv - version: 1.44.2 - manager: conda - platform: win-64 - dependencies: - vc: '>=14.1,<15' - vs2015_runtime: '>=14.16.27033' - url: https://conda.anaconda.org/conda-forge/win-64/libuv-1.44.2-h8ffe710_0.tar.bz2 - hash: - md5: 0d45ae978c33ff0b5f95ea24c717d5cf - sha256: 446090f3995a76fad652f505cb373775e0c1cd8b0a62ab8f624541bf6622589f - category: main - optional: false -- name: libwebp-base - version: 1.3.0 - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libwebp-base-1.3.0-hcfcfb64_0.conda - hash: - md5: 381a3645c51cbf478872899b16490318 - sha256: 9355940270db76592a1cdbcb840740afb5f6b81d167ac4f2cb0fbb2c37397566 - category: main - optional: false -- name: libzlib - version: 1.2.13 - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.13-hcfcfb64_4.tar.bz2 - hash: - md5: 0cc5c5cc64ee1637f37f8540a175854c - sha256: 184da12b4296088a47086f4e69e65eb5f8537a824ee3131d8076775e1d1ea767 - category: main - optional: false -- name: lz4-c - version: 1.9.4 - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.9.4-hcfcfb64_0.conda - hash: - md5: e34720eb20a33fc3bfb8451dd837ab7a - sha256: a0954b4b1590735ea5f3d0f4579c3883f8ac837387afd5b398b241fda85124ab - category: main - optional: false -- name: m2w64-gcc-libgfortran - version: 5.3.0 - manager: conda - platform: win-64 - dependencies: - m2w64-gcc-libs-core: '' - msys2-conda-epoch: '>=20160418' - url: https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libgfortran-5.3.0-6.tar.bz2 - hash: - md5: 066552ac6b907ec6d72c0ddab29050dc - sha256: 9de95a7996d5366ae0808eef2acbc63f9b11b874aa42375f55379e6715845dc6 - category: main - optional: false -- name: openssl - version: 3.1.1 - manager: conda - platform: win-64 - dependencies: - ca-certificates: '' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/openssl-3.1.1-hcfcfb64_1.conda - hash: - md5: 1d913a5de46c6b2f7e4cfbd26b106b8b - sha256: 4424486fb9a2aeaba912a8dd8a5b5cdb6fcd65d7708fd854e3ea27449bb352a3 - category: main - optional: false -- name: pthreads-win32 - version: 2.9.1 - manager: conda - platform: win-64 - dependencies: - vc: 14.* - url: https://conda.anaconda.org/conda-forge/win-64/pthreads-win32-2.9.1-hfa6e2cd_3.tar.bz2 - hash: - md5: e2da8758d7d51ff6aa78a14dfb9dbed4 - sha256: 576a228630a72f25d255a5e345e5f10878e153221a96560f2498040cd6f54005 - category: main - optional: false -- name: re2 - version: 2023.03.02 - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/re2-2023.03.02-hd4eee63_0.conda - hash: - md5: a59c371d7364446cf1d0b8299e05c1ea - sha256: 8e1bccfe360351251b6a7140bebe66e9f678d940926bb7a92b1b2b06325fdd34 - category: main - optional: false -- name: snappy - version: 1.1.10 - manager: conda - platform: win-64 - dependencies: - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/snappy-1.1.10-hfb803bf_0.conda - hash: - md5: cff1df79c9cff719460eb2dd172568de - sha256: 2a195b38cb63f03ad9f73a82db52434ebefe216fb70f7ea3defe4ddf263d408a - category: main - optional: false -- name: tk - version: 8.6.12 - manager: conda - platform: win-64 - dependencies: - vc: '>=14.1,<15' - vs2015_runtime: '>=14.16.27033' - url: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.12-h8ffe710_0.tar.bz2 - hash: - md5: c69a5047cc9291ae40afd4a1ad6f0c0f - sha256: 087795090a99a1d397ef1ed80b4a01fabfb0122efb141562c168e3c0a76edba6 - category: main - optional: false -- name: xz - version: 5.2.6 - manager: conda - platform: win-64 - dependencies: - vc: '>=14.1,<15' - vs2015_runtime: '>=14.16.27033' - url: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 - hash: - md5: 515d77642eaa3639413c6b1bc3f94219 - sha256: 54d9778f75a02723784dc63aff4126ff6e6749ba21d11a6d03c1f4775f269fe0 - category: main - optional: false -- name: yaml - version: 0.2.5 - manager: conda - platform: win-64 - dependencies: - vc: '>=14.1,<15.0a0' - vs2015_runtime: '>=14.16.27012' - url: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h8ffe710_2.tar.bz2 - hash: - md5: adbfb9f45d1004a26763652246a33764 - sha256: 4e2246383003acbad9682c7c63178e2e715ad0eb84f03a8df1fbfba455dfedc5 - category: main - optional: false -- name: aws-c-cal - version: 0.5.26 - manager: conda - platform: win-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - openssl: '>=3.1.0,<4.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/aws-c-cal-0.5.26-h1b710bc_1.conda - hash: - md5: 2e549466ab4c1993ec5e5db51d2efc20 - sha256: db42b65ce6ba8dcae777da8695b635b65a6542d1450a5d1109658f956554fa12 - category: main - optional: false -- name: aws-c-compression - version: 0.2.16 - manager: conda - platform: win-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/aws-c-compression-0.2.16-h91ceee4_7.conda - hash: - md5: a2421742024ee1eb8face127f005c25b - sha256: 4992ff0e9f36471bdd85a383d1d6cb4857fcc583c51d5aab292e7e5ea68ff0a7 - category: main - optional: false -- name: aws-c-sdkutils - version: 0.1.9 - manager: conda - platform: win-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/aws-c-sdkutils-0.1.9-h91ceee4_2.conda - hash: - md5: d8f8ea968a2c1916914e9b32a7eda645 - sha256: 73558ed260419e8219884e329087f6917864b5785d361177d4a16bba1bb95169 - category: main - optional: false -- name: aws-checksums - version: 0.1.14 - manager: conda - platform: win-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/aws-checksums-0.1.14-h91ceee4_7.conda - hash: - md5: b1edab9da6a0d773b38247e5621432a7 - sha256: 99fc01984f332e6bc4a68db24404b634aae05bce5e9ca841c8ac74a8884a2387 - category: main - optional: false -- name: glog - version: 0.6.0 - manager: conda - platform: win-64 - dependencies: - gflags: '>=2.2.2,<2.3.0a0' - vc: '>=14.1,<15' - vs2015_runtime: '>=14.16.27033' - url: https://conda.anaconda.org/conda-forge/win-64/glog-0.6.0-h4797de2_0.tar.bz2 - hash: - md5: fdc11ab9a621f009995e89f52bea3004 - sha256: 482167f378c66ecad9debf13e642013617931fc80971fb6e7d225493dbbfb90b - category: main - optional: false -- name: krb5 - version: 1.20.1 - manager: conda - platform: win-64 - dependencies: - openssl: '>=3.0.7,<4.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/krb5-1.20.1-heb0366b_0.conda - hash: - md5: a07b05ee8f451ab15698397185efe989 - sha256: 429edc4fa9e2420c55cdbd9febb154d2358bf662735efda4372f62142ff310cd - category: main - optional: false -- name: libbrotlidec - version: 1.0.9 - manager: conda - platform: win-64 - dependencies: - libbrotlicommon: 1.0.9 - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.0.9-hcfcfb64_8.tar.bz2 - hash: - md5: 99839d9d81f33afa173c0fa82a702038 - sha256: 66814b8c0235bcc9124d32cb4b99845bcb2af0eba1d2ba609a501a6d8194e9a0 - category: main - optional: false -- name: libbrotlienc - version: 1.0.9 - manager: conda - platform: win-64 - dependencies: - libbrotlicommon: 1.0.9 - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.0.9-hcfcfb64_8.tar.bz2 - hash: - md5: 88e62627120c20289bf8982b15e0a6a1 - sha256: 3abf0f0b124d54ad892bd74fe77089a712d6dad81a04583331a58728c58a69e0 - category: main - optional: false -- name: libevent - version: 2.1.12 - manager: conda - platform: win-64 - dependencies: - openssl: '>=3.1.0,<4.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libevent-2.1.12-hf43717d_0.conda - hash: - md5: 4d06233c7e2229aa03aba23d943f6515 - sha256: 301124b20e9525bed00a3143a236406de380a40fe8278117552f4fa2cc8fa2d0 - category: main - optional: false -- name: libpng - version: 1.6.39 - manager: conda - platform: win-64 - dependencies: - libzlib: '>=1.2.13,<1.3.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.39-h19919ed_0.conda - hash: - md5: ab6febdb2dbd9c00803609079db4de71 - sha256: 1f139a72109366ba1da69f5bdc569b0e6783f887615807c02d7bfcc2c7575067 - category: main - optional: false -- name: libprotobuf - version: 3.21.12 - manager: conda - platform: win-64 - dependencies: - libzlib: '>=1.2.13,<1.3.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libprotobuf-3.21.12-h12be248_0.conda - hash: - md5: 065644957b64030c520c732a0b7eb43d - sha256: 78340c88bcf39d0968a47509e8d15b30805dd8e3dda77eb67b30c10d3a552a32 - category: main - optional: false -- name: libssh2 - version: 1.10.0 - manager: conda - platform: win-64 - dependencies: - libzlib: '>=1.2.12,<1.3.0a0' - openssl: '>=3.0.5,<4.0a0' - vc: '>=14.1,<15' - vs2015_runtime: '>=14.16.27033' - url: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.10.0-h9a1e1f7_3.tar.bz2 - hash: - md5: c2b344e960a173c777bb3ed172c38cd8 - sha256: 93cc00b7ec3766d4313ff0795997a10745ce68f708811b2213a3655ca2c639b6 - category: main - optional: false -- name: libthrift - version: 0.18.1 - manager: conda - platform: win-64 - dependencies: - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.18.1-h9ce19ad_1.conda - hash: - md5: 2d11df705700cbf70373826037f8c05a - sha256: 9705d4b86851ceec1f6dbcf66d67b269eb37ffe659aed18547fcaf9019535f2d - category: main - optional: false -- name: libxml2 - version: 2.11.4 - manager: conda - platform: win-64 - dependencies: - libiconv: '>=1.17,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.11.4-hc3477c8_0.conda - hash: - md5: 586627982a63815637f871a6360fe3f9 - sha256: d9d2210436ac28d1a15de08468202a944135d17da3b37fd82c0e90a36ce8ef72 - category: main - optional: false -- name: m2w64-gcc-libs - version: 5.3.0 - manager: conda - platform: win-64 - dependencies: - m2w64-gcc-libgfortran: '' - m2w64-gcc-libs-core: '' - m2w64-gmp: '' - m2w64-libwinpthread-git: '' - msys2-conda-epoch: '>=20160418' - url: https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-5.3.0-7.tar.bz2 - hash: - md5: fe759119b8b3bfa720b8762c6fdc35de - sha256: 3bd1ab02b7c89a5b153a17be03b36d833f1517ff2a6a77ead7c4a808b88196aa - category: main - optional: false -- name: sqlite - version: 3.42.0 - manager: conda - platform: win-64 - dependencies: - libsqlite: 3.42.0 - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/sqlite-3.42.0-hcfcfb64_0.conda - hash: - md5: c505cc64dba674d4c419c0de772c8579 - sha256: 8f24c8c96716a57a570d9b31d69307223a3b8592ade0283e3e7a1b5c2f0fa513 - category: main - optional: false -- name: zlib - version: 1.2.13 - manager: conda - platform: win-64 - dependencies: - libzlib: 1.2.13 - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/zlib-1.2.13-hcfcfb64_4.tar.bz2 - hash: - md5: eed9fec3e6d2e8865135b09d24ca040c - sha256: 28e9fe3ca91ccc50080d777cb1642c64e385dbb8843162d7cadad418a12ef35d - category: main - optional: false -- name: zstd - version: 1.5.2 - manager: conda - platform: win-64 - dependencies: - libzlib: '>=1.2.13,<1.3.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.2-h12be248_6.conda - hash: - md5: 62826565682d013b3e2346aaf7bded0e - sha256: ef23b2eb748b0b2139755e5a20d49a642340af1313017918dc91b4a4ce8f3bd9 - category: main - optional: false -- name: aws-c-io - version: 0.13.21 - manager: conda - platform: win-64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.13.21-hcbbf384_5.conda - hash: - md5: cb80a787a2988630f8fc03fcedc525b1 - sha256: 65e8b6d7048ec1ece069ca38332c06152723c92b3fa4cf2cc6579c0a617ff35c - category: main - optional: false -- name: brotli-bin - version: 1.0.9 - manager: conda - platform: win-64 - dependencies: - libbrotlidec: 1.0.9 - libbrotlienc: 1.0.9 - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/brotli-bin-1.0.9-hcfcfb64_8.tar.bz2 - hash: - md5: e18b70ed349d96086fd60a9c642b1b58 - sha256: e8b55a51cb907f336c6f308d5d052483b0cad6a8b09040b811a7f12f4f199d67 - category: main - optional: false -- name: freetype - version: 2.12.1 - manager: conda - platform: win-64 - dependencies: - libpng: '>=1.6.39,<1.7.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - vc: '>=14.1,<15' - vs2015_runtime: '>=14.16.27033' - url: https://conda.anaconda.org/conda-forge/win-64/freetype-2.12.1-h546665d_1.conda - hash: - md5: 1b513009cd012591f3fdc9e03a74ec0a - sha256: fe027235660d9dfe7889c350a51e96bc0134c3f408827a4c58c4b0557409984c - category: main - optional: false -- name: libcurl - version: 8.1.2 - manager: conda - platform: win-64 - dependencies: - krb5: '>=1.20.1,<1.21.0a0' - libssh2: '>=1.10.0,<2.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.1.2-h68f0423_0.conda - hash: - md5: 94b9b7d0e882461fdb72d8d4e7441746 - sha256: a9d21b363c6d3c81ec85903f86989157e7d93d8f247e023ff3b3f69789115a72 - category: main - optional: false -- name: libgrpc - version: 1.54.2 - manager: conda - platform: win-64 - dependencies: - c-ares: '>=1.18.1,<2.0a0' - libabseil: '>=20230125.2,<20230126.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - re2: '>=2023.3.2,<2023.3.3.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - zlib: '' - url: https://conda.anaconda.org/conda-forge/win-64/libgrpc-1.54.2-ha177ca7_2.conda - hash: - md5: 815abf62c5038cc0b80bbd1b57ae4e5e - sha256: 51cf7cd3cb79dab0c84fb61f5d5752bf76b9be5c3431d9d3f31de494bb3ce0e7 - category: main - optional: false -- name: libhwloc - version: 2.9.1 - manager: conda - platform: win-64 - dependencies: - libxml2: '>=2.11.4,<2.12.0a0' - pthreads-win32: '' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.9.1-cpu_hadd60ae_5.conda - hash: - md5: 26867ad630a49c49fc123abfde634c7e - sha256: 2c6b783a68912c2e3267754c1b0373cd87fde6b8518267702a7abd22eaec5c13 - category: main - optional: false -- name: libtiff - version: 4.5.0 - manager: conda - platform: win-64 - dependencies: - jpeg: '>=9e,<10a' - lerc: '>=4.0.0,<5.0a0' - libdeflate: '>=1.17,<1.18.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - xz: '>=5.2.6,<6.0a0' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/win-64/libtiff-4.5.0-hf8721a0_2.conda - hash: - md5: 2e003e276cc1375192569c96afd3d984 - sha256: 86cf8066db11f84b506ba246944901584ab199dfe7490586f5e9b6c299e3b8e0 - category: main - optional: false -- name: orc - version: 1.8.3 - manager: conda - platform: win-64 - dependencies: - libprotobuf: '>=3.21.12,<3.22.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - snappy: '>=1.1.10,<2.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/win-64/orc-1.8.3-hada7b9e_0.conda - hash: - md5: d2c4ea49865e0838df46e74545fb8414 - sha256: 3a938dee0adc27313ad9f1edecbaaaf24d8375cba32987ff16fdca4e4c125684 - category: main - optional: false -- name: pthread-stubs - version: '0.4' - manager: conda - platform: win-64 - dependencies: - m2w64-gcc-libs: '' - url: https://conda.anaconda.org/conda-forge/win-64/pthread-stubs-0.4-hcd874cb_1001.tar.bz2 - hash: - md5: a1f820480193ea83582b13249a7e7bd9 - sha256: bb5a6ddf1a609a63addd6d7b488b0f58d05092ea84e9203283409bff539e202a - category: main - optional: false -- name: python - version: 3.9.12 - manager: conda - platform: win-64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - libffi: '>=3.4.2,<3.5.0a0' - libzlib: '>=1.2.11,<1.3.0a0' - openssl: '>=3.0.2,<4.0a0' - sqlite: '>=3.37.1,<4.0a0' - tk: '>=8.6.12,<8.7.0a0' - tzdata: '' - vc: '>=14.1,<15' - vs2015_runtime: '>=14.16.27033' - xz: '>=5.2.5,<5.3.0a0' - pip: '' - url: https://conda.anaconda.org/conda-forge/win-64/python-3.9.12-hcf16a7b_1_cpython.tar.bz2 - hash: - md5: 7d9f56f3e13f5ffb80a86e834033434c - sha256: 67ae1125c2db3a5a0193e175f0946f8c8d24adc7429f962e3ef2f771b7ab9601 - category: main - optional: false -- name: xorg-libxau - version: 1.0.11 - manager: conda - platform: win-64 - dependencies: - m2w64-gcc-libs: '' - m2w64-gcc-libs-core: '' - url: https://conda.anaconda.org/conda-forge/win-64/xorg-libxau-1.0.11-hcd874cb_0.conda - hash: - md5: c46ba8712093cb0114404ae8a7582e1a - sha256: 8c5b976e3b36001bdefdb41fb70415f9c07eff631f1f0155f3225a7649320e77 - category: main - optional: false -- name: xorg-libxdmcp - version: 1.1.3 - manager: conda - platform: win-64 - dependencies: - m2w64-gcc-libs: '' - url: https://conda.anaconda.org/conda-forge/win-64/xorg-libxdmcp-1.1.3-hcd874cb_0.tar.bz2 - hash: - md5: 46878ebb6b9cbd8afcf8088d7ef00ece - sha256: f51205d33c07d744ec177243e5d9b874002910c731954f2c8da82459be462b93 - category: main - optional: false -- name: ansicon - version: 1.89.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/win-64/ansicon-1.89.0-py39hcbf5309_6.tar.bz2 - hash: - md5: a50c48bd4af1debc96237f676184193a - sha256: 11897c369db543e4b2189dd312d2b4904cae300f5e25249fd47893e6a122f599 - category: main - optional: false -- name: antlr-python-runtime - version: 4.9.3 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/antlr-python-runtime-4.9.3-pyhd8ed1ab_1.tar.bz2 - hash: - md5: c88eaec8de9ae1fa161205aa18e7a5b1 - sha256: b91f8ab4ac2b48972fbee1fc8e092cc452fdf59156e4ff2322c94bbf73650f94 - category: main - optional: false -- name: attrs - version: 23.1.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/attrs-23.1.0-pyh71513ae_1.conda - hash: - md5: 3edfead7cedd1ab4400a6c588f3e75f8 - sha256: 063639cd568f5c7a557b0fb1cc27f098598c0d8ff869088bfeb82934674f8821 - category: main - optional: false -- name: aws-c-event-stream - version: 0.2.20 - manager: conda - platform: win-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/aws-c-event-stream-0.2.20-h3025ae5_7.conda - hash: - md5: 41dc0be8dc332745bbf9bb55c77c109b - sha256: 26374d4f0598ba5d1e29ed5e88256b15b3a6fe229994e39738debf0556b4c6d5 - category: main - optional: false -- name: aws-c-http - version: 0.7.7 - manager: conda - platform: win-64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-compression: '>=0.2.16,<0.2.17.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.7.7-haa0fb2f_4.conda - hash: - md5: adda8280b900c5ee119c10e92ef0cfcb - sha256: 0e7a4e340387f656d5dd35370fbb1f9881a7837358c52f5b9cf9ebf4a9cd82d7 - category: main - optional: false -- name: backports - version: '1.0' - manager: conda - platform: win-64 - dependencies: - python: '>=2.7' - url: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_3.conda - hash: - md5: 54ca2e08b3220c148a1d8329c2678e02 - sha256: 711602276ae39276cb0faaca6fd0ac851fff0ca17151917569174841ef830bbd - category: main - optional: false -- name: blinker - version: 1.6.2 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/blinker-1.6.2-pyhd8ed1ab_0.conda - hash: - md5: 2fb79ec81bad9492b6d59a06b3b647a4 - sha256: b6f32491536823e47cf6eb4717dd341385600a2b901235028dedc629a77aeb82 - category: main - optional: false -- name: brotli - version: 1.0.9 - manager: conda - platform: win-64 - dependencies: - brotli-bin: 1.0.9 - libbrotlidec: 1.0.9 - libbrotlienc: 1.0.9 - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/brotli-1.0.9-hcfcfb64_8.tar.bz2 - hash: - md5: 2e661f21e1741c11506bdc7226e6b0bc - sha256: 6d2fc2f147c9fc6685124d984089683988729463193578ba1d62f80263bce3c5 - category: main - optional: false -- name: certifi - version: 2023.5.7 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/certifi-2023.5.7-pyhd8ed1ab_0.conda - hash: - md5: 5d1b71c942b8421285934dad1d891ebc - sha256: f839a6e04d94069f90dd85337ea9108f058dc76771bb469a413f32bb1ba0b256 - category: main - optional: false -- name: charset-normalizer - version: 3.1.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.1.0-pyhd8ed1ab_0.conda - hash: - md5: 7fcff9f6f123696e940bda77bd4d6551 - sha256: 06cd371fc98f076797d6450f6f337cb679b1060c99680fb7e044591493333194 - category: main - optional: false -- name: cloudpickle - version: 2.2.1 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.1-pyhd8ed1ab_0.conda - hash: - md5: b325bfc4cff7d7f8a868f1f7ecc4ed16 - sha256: f0c2fd0e842899a05ddd7b147fb26424adf58be0e8e54e5bc68b8f7e67d05dcd - category: main - optional: false -- name: colorama - version: 0.4.6 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3faab06a954c2a04039983f2c4a50d99 - sha256: 2c1b2e9755ce3102bca8d69e8f26e4f087ece73f50418186aee7c74bef8e1698 - category: main - optional: false -- name: configparser - version: 5.3.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/configparser-5.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: c99fd5916160900dc5ff64204da99c4d - sha256: ce6ce9ee08437b46c284d52b076fb091cf6f2a9e12860d4a37546adbd5f53b28 - category: main - optional: false -- name: crashtest - version: 0.4.1 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6,<4.0' - url: https://conda.anaconda.org/conda-forge/noarch/crashtest-0.4.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 709a2295dd907bb34afb57d54320642f - sha256: 2f05954a3faf0700c14c1deddc085385160ee32abe111699c78d9cb277e915cc - category: main - optional: false -- name: cycler - version: 0.11.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: a50559fad0affdbb33729a68669ca1cb - sha256: 3b594bc8aa0b9a51269d54c7a4ef6af777d7fad4bee16b05695e1124de6563f6 - category: main - optional: false -- name: distlib - version: 0.3.6 - manager: conda - platform: win-64 - dependencies: - python: 2.7|>=3.6 - url: https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2 - hash: - md5: b65b4d50dbd2d50fa0aeac367ec9eed7 - sha256: 06eb7167d4d760b3b437a491e32ab5b3f89e2a18f023c117fe213b038d88538a - category: main - optional: false -- name: entrypoints - version: '0.4' - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3cf04868fee0a029769bd41f4b2fbf2d - sha256: 2ec4a0900a4a9f42615fc04d0fb3286b796abe56590e8e042f6ec25e102dd5af - category: main - optional: false -- name: exceptiongroup - version: 1.1.1 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.1-pyhd8ed1ab_0.conda - hash: - md5: 7312299d7a0ea4993159229b7d2dceb2 - sha256: f073c3ba993912f1c0027bc34a54975642885f0a4cd5f9dc42a17ca945df2c18 - category: main - optional: false -- name: filelock - version: 3.12.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.12.0-pyhd8ed1ab_0.conda - hash: - md5: 650f18a56f366dbf419c15b543592c2d - sha256: 68db3a6280d6786be76f2c7c6cf41dd878c5d1a24f5de10f7f0af82c6fcfade6 - category: main - optional: false -- name: fsspec - version: 2023.5.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/fsspec-2023.5.0-pyh1a96a4e_0.conda - hash: - md5: 20edd290b319aa0eff3e9055375756dc - sha256: cbb5c77c0217cda9bf4f4240158de11822a099a6eaa05ba626e822819a54f46d - category: main - optional: false -- name: greenlet - version: 2.0.2 - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/greenlet-2.0.2-py39h99910a6_1.conda - hash: - md5: 1db2b9af0b24ffb963687117fc1e876f - sha256: 6433e9dbc799bf0aeae4e03c10ce7992242f724e2166312a53c1a2099718ed0d - category: main - optional: false -- name: idna - version: '3.4' - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 34272b248891bddccc64479f9a7fffed - sha256: 9887c35c374ec1847f167292d3fde023cb4c994a4ceeec283072b95440131f09 - category: main - optional: false -- name: itsdangerous - version: 2.1.2 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/itsdangerous-2.1.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3c3de74912f11d2b590184f03c7cd09b - sha256: 31e3492686b4e92b53db9b48bc0eb03873b1caaf28629fee7d2d47627a2c56d3 - category: main - optional: false -- name: kiwisolver - version: 1.4.4 - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.4.4-py39h1f6ef14_1.tar.bz2 - hash: - md5: de04861ceb137d2952d3f2d0a13e9d46 - sha256: e73843fbff7ae10d7a75629c1e67f71b52b166123c0a65583fb1744ca2c66e39 - category: main - optional: false -- name: lcms2 - version: '2.15' - manager: conda - platform: win-64 - dependencies: - jpeg: '>=9e,<10a' - libtiff: '>=4.5.0,<4.6.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/lcms2-2.15-ha5c8aab_0.conda - hash: - md5: 5ebe92728f44a8b2461410ffb3628c18 - sha256: 77a232675ac1199b8eb8789fc3f541dea4de97c7bf4c4861f3857e770a9a8915 - category: main - optional: false -- name: libgoogle-cloud - version: 2.10.1 - manager: conda - platform: win-64 - dependencies: - libabseil: '>=20230125.2,<20230126.0a0' - libcrc32c: '>=1.1.2,<1.2.0a0' - libcurl: '>=8.0.1,<9.0a0' - libgrpc: '>=1.54.2,<1.55.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - openssl: '>=3.1.0,<4.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-2.10.1-h00b2bdc_1.conda - hash: - md5: 4cb0a20079e997eadbfa517a2a8e0171 - sha256: 95fcf09f9418bea872d3799e5b7375cbf48e4853c2645be3de61e679d050cb21 - category: main - optional: false -- name: libxcb - version: '1.13' - manager: conda - platform: win-64 - dependencies: - m2w64-gcc-libs: '' - pthread-stubs: '' - xorg-libxau: '' - xorg-libxdmcp: '' - url: https://conda.anaconda.org/conda-forge/win-64/libxcb-1.13-hcd874cb_1004.tar.bz2 - hash: - md5: a6d7fd030532378ecb6ba435cd9f8234 - sha256: a6fe7468ed3b9898f7beaa75f7e3adff9c7b96b39a36a3f8399c37223ec6a9e8 - category: main - optional: false -- name: lockfile - version: 0.12.2 - manager: conda - platform: win-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/lockfile-0.12.2-py_1.tar.bz2 - hash: - md5: c104d98e09c47519950cffb8dd5b4f10 - sha256: d3a68045ef74a2a7b8c8a55b242fdbc875d362e37adcf793613cf0d8c8e4fbf7 - category: main - optional: false -- name: markupsafe - version: 2.1.2 - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/markupsafe-2.1.2-py39ha55989b_0.conda - hash: - md5: 31cc450a9c33c89efce33693e3977b01 - sha256: ac03f7958b95eee99b27110569c474e08a6cb2f0115ccb161bc57dd6be575610 - category: main - optional: false -- name: mdurl - version: 0.1.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: f8dab71fdc13b1bf29a01248b156d268 - sha256: c678b9194e025b1fb665bec30ee20aab93399203583875b1dcc0a3b52a8f5523 - category: main - optional: false -- name: more-itertools - version: 9.1.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/more-itertools-9.1.0-pyhd8ed1ab_0.conda - hash: - md5: 1698a717f83cfecf644a877c174c84bd - sha256: 3ee8cbbe4004c56b695a5e734b7dc4d59dacbfefc193ee42c82238b1cf888e08 - category: main - optional: false -- name: msgpack-python - version: 1.0.5 - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.0.5-py39h1f6ef14_0.conda - hash: - md5: b5e111aee8fac27f002b1d71b75da941 - sha256: 45171a6ce90d26e299f0367c21d50610bdb652a382d087fde0956c61226ba0a7 - category: main - optional: false -- name: munkres - version: 1.1.4 - manager: conda - platform: win-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2 - hash: - md5: 2ba8498c1018c1e9c61eb99b973dfe19 - sha256: f86fb22b58e93d04b6f25e0d811b56797689d598788b59dcb47f59045b568306 - category: main - optional: false -- name: openjpeg - version: 2.5.0 - manager: conda - platform: win-64 - dependencies: - libpng: '>=1.6.39,<1.7.0a0' - libtiff: '>=4.5.0,<4.6.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.0-ha2aaf27_2.conda - hash: - md5: db0490689232e8e38c312281df6f31a2 - sha256: 1fb72db47e9b1cdb4980a1fd031e31fad2c6a4a632fc602e7d6fa74f4f491608 - category: main - optional: false -- name: ordered-set - version: 4.1.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/ordered-set-4.1.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 9a8714decb3967b290263817e876d8a9 - sha256: 78d92f848a6b4a89148dfa1f6e65c0b75e8f3a267b6401be38fb3401853b4afa - category: main - optional: false -- name: orjson - version: 3.8.14 - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/win-64/orjson-3.8.14-py39h6c6a30e_0.conda - hash: - md5: f77747362803782ee9874a2563f3a180 - sha256: 742fdba996c6481045b6b1679f2f22aaf623384a3a483bc538f7498f29e69491 - category: main - optional: false -- name: packaging - version: '23.1' - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/packaging-23.1-pyhd8ed1ab_0.conda - hash: - md5: 91cda59e66e1e4afe9476f8ef98f5c30 - sha256: ded536a96a00d45a693dbc2971bb688248324dadd129eddda2100e177583d768 - category: main - optional: false -- name: pkginfo - version: 1.9.6 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.9.6-pyhd8ed1ab_0.conda - hash: - md5: be1e9f1c65a1ed0f2ae9352fec99db64 - sha256: 7ea5a5af62a15376d9f4f9f3c134874d0b0710f39be719e849b7fa9ca8870502 - category: main - optional: false -- name: pkgutil-resolve-name - version: 1.3.10 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pkgutil-resolve-name-1.3.10-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 89e3c7cdde7d3aaa2aee933b604dd07f - sha256: 7d055ffc8a02bf781a89d069db3454b453605cdaff300b82cedcc7133283e47e - category: main - optional: false -- name: prometheus_client - version: 0.17.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.17.0-pyhd8ed1ab_0.conda - hash: - md5: 95c5be3c7cbd872509d16c216617fdab - sha256: eb11fd8b927d9c5ff9482cfbd6cd810a43a1351c44a288e9680542ea698a19a0 - category: main - optional: false -- name: psutil - version: 5.9.5 - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/psutil-5.9.5-py39ha55989b_0.conda - hash: - md5: e38e52d2f2725c282f90b054c037947c - sha256: 464382a78123604a150cf7edc76ee2305252db85910f123f9cf941d2e02f0a2f - category: main - optional: false -- name: ptyprocess - version: 0.7.0 - manager: conda - platform: win-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd3deb0d_0.tar.bz2 - hash: - md5: 359eeb6536da0e687af562ed265ec263 - sha256: fb31e006a25eb2e18f3440eb8d17be44c8ccfae559499199f73584566d0a444a - category: main - optional: false -- name: pycparser - version: '2.21' - manager: conda - platform: win-64 - dependencies: - python: 2.7.*|>=3.4 - url: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 076becd9e05608f8dc72757d5f3a91ff - sha256: 74c63fd03f1f1ea2b54e8bc529fd1a600aaafb24027b738d0db87909ee3a33dc - category: main - optional: false -- name: pygments - version: 2.15.1 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/pygments-2.15.1-pyhd8ed1ab_0.conda - hash: - md5: d316679235612869eba305aa7d41d9bf - sha256: 1bddeb54863c77ed5613b535a3e06a3a16b55786301a5e28c9bf011656bda686 - category: main - optional: false -- name: pyjwt - version: 2.7.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyjwt-2.7.0-pyhd8ed1ab_0.conda - hash: - md5: 99e28be5a278e2319834d7dc99e7bfdd - sha256: f3a64306fa0f405f10f4108d7ff42043d6fd393f940f9e98e395a3756687fc98 - category: main - optional: false -- name: pyparsing - version: 3.0.9 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2 - hash: - md5: e8fbc1b54b25f4b08281467bc13b70cc - sha256: 4acc7151cef5920d130f2e0a7615559cce8bfb037aeecb14d4d359ae3d9bc51b - category: main - optional: false -- name: pyrsistent - version: 0.19.3 - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/pyrsistent-0.19.3-py39ha55989b_0.conda - hash: - md5: 3c40136690d8023e73c5dcfe38e03f95 - sha256: 6ffb909cc5839e569662772c2eef592bdde0e58964b490756d78b49c9d6d31c0 - category: main - optional: false -- name: python-editor - version: 1.0.4 - manager: conda - platform: win-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/python-editor-1.0.4-py_0.tar.bz2 - hash: - md5: eaaf29a0644f9407f98a4665f45880c4 - sha256: a6db88da69a27451d2eba675c445bdefd2dbea52ea02a0a214d5fd4f0af31740 - category: main - optional: false -- name: python-installer - version: 0.7.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/python-installer-0.7.0-pyhd8ed1ab_0.conda - hash: - md5: 65dea78f903d686c8b0c2feaf0e15e1f - sha256: 822f95b7786cfa61a6519153117b21d93194890e02a884b9f66ee4275e4f1c0a - category: main - optional: false -- name: python-multipart - version: 0.0.6 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/python-multipart-0.0.6-pyhd8ed1ab_0.conda - hash: - md5: f4f642eeda814c1b65f46fbdf7e89096 - sha256: 2a9b8d02a6ec9862433cfc2741c4cbfe321e4ae3bab066f7ed84bc00effb73d7 - category: main - optional: false -- name: python-tzdata - version: '2023.3' - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2023.3-pyhd8ed1ab_0.conda - hash: - md5: 2590495f608a63625e165915fb4e2e34 - sha256: 0108888507014fb24573c31e4deceb61c99e63d37776dddcadd7c89b2ecae0b6 - category: main - optional: false -- name: pytz - version: '2023.3' - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/pytz-2023.3-pyhd8ed1ab_0.conda - hash: - md5: d3076b483092a435832603243567bc31 - sha256: e4999484f21763ca4b8f92c95b22cb6d1edc1b61d0a2bb073ee2bd11f39401b9 - category: main - optional: false -- name: pywin32 - version: '304' - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/pywin32-304-py39h99910a6_2.tar.bz2 - hash: - md5: 4cb6e4e7a9a0c2889967a4f436e589bc - sha256: 6852c2708300ea836b68a91b9ebe837b0568d6db0ea8430791b03f32ea6f6406 - category: main - optional: false -- name: pywin32-ctypes - version: 0.2.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/win-64/pywin32-ctypes-0.2.0-py39hcbf5309_1006.tar.bz2 - hash: - md5: 4f5484721b826d0aee0a5bbca509324b - sha256: b3e9770968e76eaa48d6ecd79f556fc54f0f2fcbd5c2427f804f2be53bcc8372 - category: main - optional: false -- name: pyyaml - version: '6.0' - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - yaml: '>=0.2.5,<0.3.0a0' - url: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0-py39ha55989b_5.tar.bz2 - hash: - md5: 03968ff66723b72b793e94033d0fbb2f - sha256: c58efc019e69505e017c6a1d5f7bf8c02bb60b32cfc7dba1743547d64ed6e217 - category: main - optional: false -- name: readchar - version: 4.0.5 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/readchar-4.0.5-pyhd8ed1ab_0.conda - hash: - md5: 513334936060e80697bc21079e4f2829 - sha256: 0426cd7a524c31ab6d52b4d181848daea81d057e200a74200ea6e2896534bc18 - category: main - optional: false -- name: setuptools - version: 67.7.2 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/setuptools-67.7.2-pyhd8ed1ab_0.conda - hash: - md5: 3b68bc43ec6baa48f7354a446267eefe - sha256: 3ac44771fce01f19218bcdf3992e24984748048db69889a9df65abcc6a10e29b - category: main - optional: false -- name: shellingham - version: 1.5.1 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/shellingham-1.5.1-pyhd8ed1ab_0.conda - hash: - md5: 1de44299f48f522caa2e0074231614e1 - sha256: 3cb4a4a83b617fdfef9b92751634488db0b8961c80340be8068bf6d4f1d5ac25 - category: main - optional: false -- name: six - version: 1.16.0 - manager: conda - platform: win-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 - hash: - md5: e5f25f8dbc060e9a8d912e432202afc2 - sha256: a85c38227b446f42c5b90d9b642f2c0567880c15d72492d8da074a59c8f91dd6 - category: main - optional: false -- name: smmap - version: 3.0.5 - manager: conda - platform: win-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/smmap-3.0.5-pyh44b312d_0.tar.bz2 - hash: - md5: 3a8dc70789709aa315325d5df06fb7e4 - sha256: 091de70ee6bfe063e0c0f77336975d124fd1e3f49b9c58d97c0c7b3d287c0002 - category: main - optional: false -- name: sniffio - version: 1.3.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: dd6cbc539e74cb1f430efbd4575b9303 - sha256: a3fd30754c20ddb28b777db38345ea00d958f46701f0decd6291a81c0f4eee78 - category: main - optional: false -- name: soupsieve - version: 2.3.2.post1 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 146f4541d643d48fc8a75cacf69f03ae - sha256: 72d80dda41c3902c2619e8ab49d4f5b2a894d13375e1f9ed16fc00074ddd2307 - category: main - optional: false -- name: sqlparse - version: 0.4.4 - manager: conda - platform: win-64 - dependencies: - python: '>=3.5' - url: https://conda.anaconda.org/conda-forge/noarch/sqlparse-0.4.4-pyhd8ed1ab_0.conda - hash: - md5: 2e2f31b3b1c866c29636377e14f8c4c6 - sha256: 7972c9b15dafa1885f3d4cd22dc4edea4cd969d12739fb71f8632f2c3350706a - category: main - optional: false -- name: tabulate - version: 0.9.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.9.0-pyhd8ed1ab_1.tar.bz2 - hash: - md5: 4759805cce2d914c38472f70bf4d8bcb - sha256: f6e4a0dd24ba060a4af69ca79d32361a6678e61d78c73eb5e357909b025b4620 - category: main - optional: false -- name: tbb - version: 2021.9.0 - manager: conda - platform: win-64 - dependencies: - libhwloc: '>=2.9.1,<2.9.2.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/tbb-2021.9.0-h91493d7_0.conda - hash: - md5: 6aa3f1becefeaa00a4d2a79b2a478aee - sha256: bf9a0f71aa9234776fc5d940464f47b760e5b4907c6c4f7eb0273221fe1b834c - category: main - optional: false -- name: threadpoolctl - version: 3.1.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.1.0-pyh8a188c0_0.tar.bz2 - hash: - md5: a2995ee828f65687ac5b1e71a2ab1e0c - sha256: c7a964811ba49c545236f7d6c2486b2ed493931660048a06fe94ecc851dd0b82 - category: main - optional: false -- name: tomli - version: 2.0.1 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 5844808ffab9ebdb694585b50ba02a96 - sha256: 4cd48aba7cd026d17e86886af48d0d2ebc67ed36f87f6534f4b67138f5a5a58f - category: main - optional: false -- name: tomlkit - version: 0.11.8 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tomlkit-0.11.8-pyha770c72_0.conda - hash: - md5: 75838e8556166263a82038b51d01d5f1 - sha256: 3002e87338a98ba501fbf53981f8267b2def2548265a3622d403d06747872ccd - category: main - optional: false -- name: traitlets - version: 5.9.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.9.0-pyhd8ed1ab_0.conda - hash: - md5: d0b4f5c87cd35ac3fb3d47b223263a64 - sha256: 343610bce6dbe8a5090500dd2e9d1706057960b3f3120ebfe0abb4a8ecbada4d - category: main - optional: false -- name: trove-classifiers - version: 2023.5.24 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2023.5.24-pyhd8ed1ab_0.conda - hash: - md5: 4580a4f27cad1c3b275f6f6ad310abae - sha256: 05e83cd3ac921143c7a25681928727bcc9b01bf8456c9615b72d64f050863503 - category: main - optional: false -- name: typing - version: 3.10.0.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/typing-3.10.0.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: e6573ac68718f17b9d4f5c8eda3190f2 - sha256: ec1cfe0b7dc55a22223562cad799e0b16d122dab611c9923b6068d27a784ba2f - category: main - optional: false -- name: typing_extensions - version: 4.5.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.5.0-pyha770c72_0.conda - hash: - md5: 43e7d9e50261fb11deb76e17d8431aac - sha256: f81eee64fcdfb379e27d01773b34041fbf7f9e86f33b157c9925d19e0a442452 - category: main - optional: false -- name: unicodedata2 - version: 15.0.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/unicodedata2-15.0.0-py39ha55989b_0.tar.bz2 - hash: - md5: 3be582ed58b3e289836a1fdc4e41d0b8 - sha256: d29dbdab1c5bc9e39c26c9cb91bf5745746c99c5fbbacddab855e3ad11b2be67 - category: main - optional: false -- name: waitress - version: 2.1.2 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/waitress-2.1.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 6a44302a7502c1c9c7becdc40d465466 - sha256: bf474e94d59d5e79c6dda9b4b6f4aa14615ab80517ad2b0b95ae30cbecfbee6e - category: main - optional: false -- name: webencodings - version: 0.5.1 - manager: conda - platform: win-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-py_1.tar.bz2 - hash: - md5: 3563be4c5611a44210d9ba0c16113136 - sha256: 302f4f4bd1ad00c0be1426ecf6bb01db59cfd8aff3de0cf1596526dca1a6b70e - category: main - optional: false -- name: websocket-client - version: 1.5.2 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.5.2-pyhd8ed1ab_0.conda - hash: - md5: bfe7e7cd1476092f51efbcde15dfb110 - sha256: 85310b382c4220d7846fa8f046216fd722b88db07991f07bd7decdf2e5dc3446 - category: main - optional: false -- name: websockets - version: 11.0.3 - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/websockets-11.0.3-py39ha55989b_0.conda - hash: - md5: 98246c061ab3f737a5396da11fc5e24e - sha256: 1d08bf37d7ebde22a7cb354bab2a9d0531202ac1a1324e0c7f7f20d72c82971a - category: main - optional: false -- name: wheel - version: 0.40.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.40.0-pyhd8ed1ab_0.conda - hash: - md5: 49bb0d9e60ce1db25e151780331bb5f3 - sha256: 79b4d29b0c004014a2abd5fc2c9fcd35cc6256222b960c2a317a27c4b0d8884d - category: main - optional: false -- name: win_inet_pton - version: 1.1.0 - manager: conda - platform: win-64 - dependencies: - __win: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyhd8ed1ab_6.tar.bz2 - hash: - md5: 30878ecc4bd36e8deeea1e3c151b2e0b - sha256: a11ae693a0645bf6c7b8a47bac030be9c0967d0b1924537b9ff7458e832c0511 - category: main - optional: false -- name: zipp - version: 3.15.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/zipp-3.15.0-pyhd8ed1ab_0.conda - hash: - md5: 13018819ca8f5b7cc675a8faf1f5fedf - sha256: 241de30545299be9bcea3addf8a2c22a3b3d4ba6730890e150ab690ac937a3d2 - category: main - optional: false -- name: anyio - version: 3.7.0 - manager: conda - platform: win-64 - dependencies: - typing_extensions: '' - exceptiongroup: '' - python: '>=3.7' - idna: '>=2.8' - sniffio: '>=1.1' - url: https://conda.anaconda.org/conda-forge/noarch/anyio-3.7.0-pyhd8ed1ab_1.conda - hash: - md5: 2b35a85d654a47aac8f34c1bb6de7142 - sha256: 863c11a6a0e937977229b405a16f6d43fff543dfe5b1a66da9c42ec0cbdaaf33 - category: main - optional: false -- name: aws-c-auth - version: 0.6.27 - manager: conda - platform: win-64 - dependencies: - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-c-sdkutils: '>=0.1.9,<0.1.10.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.6.27-hdef5456_1.conda - hash: - md5: a78df32dfab8801dec4276463b359859 - sha256: 5974d133d932def2808f9700874480750e57d302cc3c4ac4d4148982e79c4fe8 - category: main - optional: false -- name: aws-c-mqtt - version: 0.8.11 - manager: conda - platform: win-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/aws-c-mqtt-0.8.11-ha0c62a6_1.conda - hash: - md5: e06130953df68d64f9e0a3d1a5a1a0ac - sha256: 1bbb6c84526ec4a6fe41883a3d850a2de799fa53d53b2ead42d2f798c4eaa166 - category: main - optional: false -- name: backports.cached-property - version: 1.0.2 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - typing: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/backports.cached-property-1.0.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 0e1df3978dd516e20ef88c86d51e5432 - sha256: 1d86eafb5e9ed078f891e12b46692d786723652907dfb01b047c7da31f92b862 - category: main - optional: false -- name: backports.functools_lru_cache - version: 1.6.4 - manager: conda - platform: win-64 - dependencies: - setuptools: '' - backports: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/backports.functools_lru_cache-1.6.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: c5b3edc62d6309088f4970b3eaaa65a6 - sha256: fdea00d4b79990f3fe938e2716bc32bd895eb5c44b6c75b8261db095a1b33c16 - category: main - optional: false -- name: beautifulsoup4 - version: 4.12.2 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - soupsieve: '>=1.2' - url: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.2-pyha770c72_0.conda - hash: - md5: a362ff7d976217f8fa78c0f1c4f59717 - sha256: 52d3e6bcd442537e22699cd227d8fdcfd54b708eeb8ee5b4c671a6a9b9cd74da - category: main - optional: false -- name: cffi - version: 1.15.1 - manager: conda - platform: win-64 - dependencies: - pycparser: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/cffi-1.15.1-py39h68f70e3_3.conda - hash: - md5: 5e7cb1054cd6f3036a4bdbfe6e23fdfd - sha256: fe9ffe5fa574f7282ebd971c13039a250f411b0403d2be4485d71853ff3f2d4a - category: main - optional: false -- name: click - version: 8.1.3 - manager: conda - platform: win-64 - dependencies: - colorama: '' - __win: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-win_pyhd8ed1ab_2.tar.bz2 - hash: - md5: 6b58680207b526c42dcff68b543803dd - sha256: 84e80a33e9a8e5398d3e97209366b57f635462a5b894f8076ec8c95e56672c44 - category: main - optional: false -- name: deepdiff - version: 6.3.0 - manager: conda - platform: win-64 - dependencies: - orjson: '' - python: '>=3.7' - ordered-set: '>=4.1.0,<4.2.0' - url: https://conda.anaconda.org/conda-forge/noarch/deepdiff-6.3.0-pyhd8ed1ab_0.conda - hash: - md5: 67ce5e3eecbf1e5ff869269640ae6a53 - sha256: f949d860d532a07587bdb8466310394d8c1af4dd89bb65d65219161fcc16db10 - category: main - optional: false -- name: fonttools - version: 4.39.4 - manager: conda - platform: win-64 - dependencies: - brotli: '' - munkres: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - unicodedata2: '>=14.0.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/fonttools-4.39.4-py39ha55989b_0.conda - hash: - md5: 9335c2f4bd810fbd705baa1eb7b3b7e1 - sha256: 1b9c50199b65936804e2c09d41bcbf4c2852785b98df4002e94caa31b0567b8c - category: main - optional: false -- name: gitdb - version: 4.0.10 - manager: conda - platform: win-64 - dependencies: - python: '>=3.4' - smmap: '>=3.0.1,<4' - url: https://conda.anaconda.org/conda-forge/noarch/gitdb-4.0.10-pyhd8ed1ab_0.conda - hash: - md5: 3706d2f3d7cb5dae600c833345a76132 - sha256: 0003ab2b971913380633c711bf49a54dcf06e179986c725b0925854b58878377 - category: main - optional: false -- name: h11 - version: 0.14.0 - manager: conda - platform: win-64 - dependencies: - typing_extensions: '' - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: b21ed0883505ba1910994f1df031a428 - sha256: 817d2c77d53afe3f3d9cf7f6eb8745cdd8ea76c7adaa9d7ced75c455a2c2c085 - category: main - optional: false -- name: html5lib - version: '1.1' - manager: conda - platform: win-64 - dependencies: - python: '' - webencodings: '' - six: '>=1.9' - url: https://conda.anaconda.org/conda-forge/noarch/html5lib-1.1-pyh9f0ad1d_0.tar.bz2 - hash: - md5: b2355343d6315c892543200231d7154a - sha256: 9ad06446fe9847e86cb20d220bf11614afcd2cbe9f58096f08d5d4018877bee4 - category: main - optional: false -- name: importlib-metadata - version: 6.6.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.8' - zipp: '>=0.5' - url: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.6.0-pyha770c72_0.conda - hash: - md5: f91a5d5175fb7ff2a91952ec7da59cb9 - sha256: 33d49065756a73fbb92277c756fa00a41891408528eb90ae05ff3367a401ae6e - category: main - optional: false -- name: importlib_resources - version: 5.12.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - zipp: '>=3.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.12.0-pyhd8ed1ab_0.conda - hash: - md5: e5fd2260a231ee63b6969f4801082f2b - sha256: 091cca3e010f7a7353152f0abda2d68cfd83ddde80a15e974d9e18b2047e7be2 - category: main - optional: false -- name: jaraco.classes - version: 3.2.3 - manager: conda - platform: win-64 - dependencies: - more-itertools: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.2.3-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 31e4a1506968d017229bdb64695013a1 - sha256: 6a81b67a1de8f761f66a4540bbd07cc27f9fbf2c7d67aa3732ebef379cf62874 - category: main - optional: false -- name: jinja2 - version: 3.1.2 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - markupsafe: '>=2.0' - url: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2 - hash: - md5: c8490ed5c70966d232fdd389d0dbed37 - sha256: b045faba7130ab263db6a8fdc96b1a3de5fcf85c4a607c5f11a49e76851500b5 - category: main - optional: false -- name: jinxed - version: 1.2.0 - manager: conda - platform: win-64 - dependencies: - __win: '' - ansicon: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/jinxed-1.2.0-pyh95a074a_0.tar.bz2 - hash: - md5: c6436269626513a7b95af8448b912a9e - sha256: 14db9748aef65d1d0a85067917ccd7e3181821c50ed696c8fb9509d9444851b4 - category: main - optional: false -- name: joblib - version: 1.2.0 - manager: conda - platform: win-64 - dependencies: - setuptools: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.2.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 7583652522d71ad78ba536bba06940eb - sha256: 0c21351871df2c0a53168575597dd9c881e2a9fa4c42fe89a9bcd7fab37f462c - category: main - optional: false -- name: lightning-utilities - version: 0.8.0 - manager: conda - platform: win-64 - dependencies: - typing_extensions: '' - python: '>=3.8' - packaging: '>=17.1' - url: https://conda.anaconda.org/conda-forge/noarch/lightning-utilities-0.8.0-pyhd8ed1ab_0.conda - hash: - md5: ad16f58b64d3b41f4cbb75040b06c9cc - sha256: 8c1fff22ab86c85768e65dc8c4f4664787476a21f4d934c4e0261a1fa7523f9c - category: main - optional: false -- name: markdown-it-py - version: 2.2.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - typing_extensions: '>=3.7.4' - mdurl: '>=0.1,<1' - url: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-2.2.0-pyhd8ed1ab_0.conda - hash: - md5: b2928a6c6d52d7e3562b4a59c3214e3a - sha256: 65ed439862c1851463f03a9bc5109992ce3e3e025e9a2d76d13ca19f576eee9f - category: main - optional: false -- name: mkl - version: 2022.1.0 - manager: conda - platform: win-64 - dependencies: - intel-openmp: '' - tbb: 2021.* - url: https://conda.anaconda.org/conda-forge/win-64/mkl-2022.1.0-h6a75c08_874.tar.bz2 - hash: - md5: 2ff89a7337a9636029b4db9466e9f8e3 - sha256: b130d13dba6a798cbcce8f19c52e9765b75b8668d2f8f95ba8210c63b6fa84eb - category: main - optional: false -- name: omegaconf - version: 2.3.0 - manager: conda - platform: win-64 - dependencies: - typing_extensions: '' - python: '>=3.7' - pyyaml: '>=5.1.0' - antlr-python-runtime: 4.9.* - url: https://conda.anaconda.org/conda-forge/noarch/omegaconf-2.3.0-pyhd8ed1ab_0.conda - hash: - md5: 23cc056834cab53849b91f78d6ee3ea0 - sha256: df806841be847e5287b22b6ae7f380874f81ea51f1b51ae14a570f3385c7b133 - category: main - optional: false -- name: pexpect - version: 4.8.0 - manager: conda - platform: win-64 - dependencies: - python: '' - ptyprocess: '>=0.5' - url: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.8.0-pyh1a96a4e_2.tar.bz2 - hash: - md5: 330448ce4403cc74990ac07c555942a1 - sha256: 07706c0417ead94f359ca7278f65452d3c396448777aba1da6a11fc351bdca9a - category: main - optional: false -- name: pillow - version: 9.4.0 - manager: conda - platform: win-64 - dependencies: - freetype: '>=2.12.1,<3.0a0' - jpeg: '>=9e,<10a' - lcms2: '>=2.14,<3.0a0' - libtiff: '>=4.5.0,<4.6.0a0' - libwebp-base: '>=1.2.4,<2.0a0' - libxcb: '>=1.13,<1.14.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openjpeg: '>=2.5.0,<3.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - tk: '>=8.6.12,<8.7.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/pillow-9.4.0-py39hcebd2be_1.conda - hash: - md5: 7cf5bb13bc3823aa5159e00c7d1cb18f - sha256: 518c9cd2e83494858f96cc1a522d97e9d01d0465393f4e75ee0f3eae9c5bb936 - category: main - optional: false -- name: pip - version: 23.1.2 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - setuptools: '' - wheel: '' - url: https://conda.anaconda.org/conda-forge/noarch/pip-23.1.2-pyhd8ed1ab_0.conda - hash: - md5: 7288da0d36821349cf1126e8670292df - sha256: 4fe1f47f6eac5b2635a622b6f985640bf835843c1d8d7ccbbae0f7d27cadec92 - category: main - optional: false -- name: protobuf - version: 4.21.12 - manager: conda - platform: win-64 - dependencies: - libprotobuf: 3.21.12.* - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - setuptools: '' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/protobuf-4.21.12-py39h99910a6_0.conda - hash: - md5: 5336f718b1bd02058a244f59f4e85dd5 - sha256: 911add44a5d05e1423502a8a8d76168cd3cfb7d2f44ee273a0730807132f70ea - category: main - optional: false -- name: pyproject_hooks - version: 1.0.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - tomli: '>=1.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.0.0-pyhd8ed1ab_0.conda - hash: - md5: 21de50391d584eb7f4441b9de1ad773f - sha256: 016340837fcfef57b351febcbe855eedf0c1f0ecfc910ed48c7fbd20535f9847 - category: main - optional: false -- name: pysocks - version: 1.7.1 - manager: conda - platform: win-64 - dependencies: - __win: '' - win_inet_pton: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh0701188_6.tar.bz2 - hash: - md5: 56cd9fe388baac0e90c7149cfac95b60 - sha256: b3a612bc887f3dd0fb7c4199ad8e342bd148cf69a9b74fd9468a18cf2bef07b7 - category: main - optional: false -- name: python-dateutil - version: 2.8.2 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - six: '>=1.5' - url: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: dd999d1cc9f79e67dbb855c8924c7984 - sha256: 54d7785c7678166aa45adeaccfc1d2b8c3c799ca2dc05d4a82bb39b1968bd7da - category: main - optional: false -- name: pywin32-on-windows - version: 0.1.0 - manager: conda - platform: win-64 - dependencies: - pywin32: '' - python: '>=2.7' - url: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh07e9846_2.tar.bz2 - hash: - md5: 91733394059b880d9cc0d010c20abda0 - sha256: 09803b75cccc16d8586d2f41ea890658d165f4afc359973fa1c7904a2c140eae - category: main - optional: false -- name: tqdm - version: 4.65.0 - manager: conda - platform: win-64 - dependencies: - colorama: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.65.0-pyhd8ed1ab_1.conda - hash: - md5: ed792aff3acb977d09c7013358097f83 - sha256: b35f185a678109940d34f68ac5781c3cbda9b118b8d9886b8f68ab5be6afd4fc - category: main - optional: false -- name: typing-extensions - version: 4.5.0 - manager: conda - platform: win-64 - dependencies: - typing_extensions: 4.5.0 - url: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.5.0-hd8ed1ab_0.conda - hash: - md5: b3c594fde1a80a1fc3eb9cc4a5dfe392 - sha256: 6da5e15fa533620ae2e7aca9a7d16013eed3a73ac64c47d7c3bf3deec39b63b9 - category: main - optional: false -- name: werkzeug - version: 2.3.4 - manager: conda - platform: win-64 - dependencies: - python: '>=3.8' - markupsafe: '>=2.1.1' - url: https://conda.anaconda.org/conda-forge/noarch/werkzeug-2.3.4-pyhd8ed1ab_0.conda - hash: - md5: 23ddbe41ab0115bc0bfb75dcbf5de7cf - sha256: 2df1970270839b36e13a4ba7e4b393cfa95aa1d7438909aa8c3db14170ea207c - category: main - optional: false -- name: arrow - version: 1.2.3 - manager: conda - platform: win-64 - dependencies: - typing_extensions: '' - python: '>=3.6' - python-dateutil: '>=2.7.0' - url: https://conda.anaconda.org/conda-forge/noarch/arrow-1.2.3-pyhd8ed1ab_0.tar.bz2 - hash: - md5: fd1967c76eda3a3dd9e8e6cb7a15a028 - sha256: a0434c2770cf5b0ab5a33913c0b202b1521bc13f755b762d16a86b110425cdc2 - category: main - optional: false -- name: aws-c-s3 - version: 0.3.0 - manager: conda - platform: win-64 - dependencies: - aws-c-auth: '>=0.6.27,<0.6.28.0a0' - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.3.0-h9503c08_2.conda - hash: - md5: 90d9297f1e5fccd72f5b02ef800594ec - sha256: 019c6406432cf086744ce87057785ca2ff0d933461c5ebeb88e464b795634738 - category: main - optional: false -- name: bcrypt - version: 3.2.2 - manager: conda - platform: win-64 - dependencies: - cffi: '>=1.1' - pip: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - six: '>=1.4.1' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/bcrypt-3.2.2-py39ha55989b_1.tar.bz2 - hash: - md5: d2d5f13883ea6a933c78af12cd439347 - sha256: b26122f8970c94e02050d75814c54a3087408709c72224fb50744757d218a194 - category: main - optional: false -- name: brotlipy - version: 0.7.0 - manager: conda - platform: win-64 - dependencies: - cffi: '>=1.0.0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/brotlipy-0.7.0-py39ha55989b_1005.tar.bz2 - hash: - md5: 5ffea1498e831c783af65e274d6c58f5 - sha256: c46aa1a2711519653b1ef74e8e6c3cb9fe0bec11b8e905498b9b2ecfd57dbc17 - category: main - optional: false -- name: croniter - version: 1.3.15 - manager: conda - platform: win-64 - dependencies: - python-dateutil: '' - python: '>=2.6' - url: https://conda.anaconda.org/conda-forge/noarch/croniter-1.3.15-pyhd8ed1ab_0.conda - hash: - md5: 50197abb95aa7024eb0eb58fe5a51b07 - sha256: f8f58f6a50a5f63a35ee3bf6805e6dee10fe910f17a339da038967118c12c64f - category: main - optional: false -- name: cryptography - version: 41.0.0 - manager: conda - platform: win-64 - dependencies: - cffi: '>=1.12' - openssl: '>=3.1.1,<4.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/cryptography-41.0.0-py39hb6bd5e6_0.conda - hash: - md5: ebefa3c816d75ca38fdc34a1627510b7 - sha256: f4312650de11ef8591bb98343fb6bba8f043414722b8467f8249a448a74e0aae - category: main - optional: false -- name: dateutils - version: 0.6.12 - manager: conda - platform: win-64 - dependencies: - python-dateutil: '' - pytz: '' - python: '>=3' - url: https://conda.anaconda.org/conda-forge/noarch/dateutils-0.6.12-py_0.tar.bz2 - hash: - md5: acee371a07e9a38a7072e5a5f7054ead - sha256: fb554b32a8f880cafaff4e67c789965d97c41eb1a6cc9ab5a83c6b28b581d809 - category: main - optional: false -- name: flask - version: 2.3.2 - manager: conda - platform: win-64 - dependencies: - python: '>=3.8' - jinja2: '>=3.1.2' - click: '>=8.1.3' - importlib-metadata: '>=3.6.0' - itsdangerous: '>=2.1.2' - blinker: '>=1.6.2' - werkzeug: '>=2.3.3' - url: https://conda.anaconda.org/conda-forge/noarch/flask-2.3.2-pyhd8ed1ab_0.conda - hash: - md5: 816d75d4c0f2e41b5765d17498c57a2e - sha256: f93246be286f2d0f93e85c4f08f9ce48f3eed875a79225e2ea119e70c0237421 - category: main - optional: false -- name: gitpython - version: 3.1.31 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - typing_extensions: '>=3.7.4.3' - gitdb: '>=4.0.1,<5' - url: https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.31-pyhd8ed1ab_0.conda - hash: - md5: f6e6b482110246a81c3f03e81c68752d - sha256: 77c531def610089bc190508fcf304cf96c085c5fe977ab8f7d7c1641769592ac - category: main - optional: false -- name: importlib-resources - version: 5.12.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - importlib_resources: '>=5.12.0,<5.12.1.0a0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib-resources-5.12.0-pyhd8ed1ab_0.conda - hash: - md5: 3544c818f0720c89eb16ae6940ab440b - sha256: 0675df2bf18e52d0ea2bc5e1009faac273f059361a0caf36c0e0edc7831098a9 - category: main - optional: false -- name: importlib_metadata - version: 6.6.0 - manager: conda - platform: win-64 - dependencies: - importlib-metadata: '>=6.6.0,<6.6.1.0a0' - url: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-6.6.0-hd8ed1ab_0.conda - hash: - md5: 3cbc9615f10a3d471532b83e4250b971 - sha256: 5de35d3c019d8a36e0a0deeb04a62689837bd68234a0a73a3355b860b442eca4 - category: main - optional: false -- name: jsonschema - version: 4.17.3 - manager: conda - platform: win-64 - dependencies: - typing_extensions: '' - importlib-metadata: '' - python: '>=3.7' - attrs: '>=17.4.0' - pyrsistent: '!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0' - importlib_resources: '>=1.4.0' - pkgutil-resolve-name: '>=1.3.10' - url: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.17.3-pyhd8ed1ab_0.conda - hash: - md5: 723268a468177cd44568eb8f794e0d80 - sha256: 4f68a23430d1afc5c9b41c46fbac0ade33c0bf57a293c646bfdd6dc65350eada - category: main - optional: false -- name: libblas - version: 3.9.0 - manager: conda - platform: win-64 - dependencies: - mkl: 2022.1.0 - url: https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-16_win64_mkl.tar.bz2 - hash: - md5: d2e6f4e86cee2b4e8c27ff6884ccdc61 - sha256: 0825e98108590b83f91177a6f31e4815441b2f70c67d29df36f11039c23b947a - category: main - optional: false -- name: mako - version: 1.2.4 - manager: conda - platform: win-64 - dependencies: - importlib-metadata: '' - python: '>=3.6' - markupsafe: '>=0.9.2' - url: https://conda.anaconda.org/conda-forge/noarch/mako-1.2.4-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 0d072f0edc017b6318dbab701e053f94 - sha256: 559ed0d4c600d9827c1e9e0f2f3a50724bf2281b28a04e08f60de63f0da309a6 - category: main - optional: false -- name: markdown - version: 3.4.3 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - importlib-metadata: '>=4.4' - url: https://conda.anaconda.org/conda-forge/noarch/markdown-3.4.3-pyhd8ed1ab_0.conda - hash: - md5: 89ed59ad509c05db6f5f2f573d499bfe - sha256: e32ac2c95112caa8cd81f0cbc710f4f4903180a115c7260f03b010d5a0aa771b - category: main - optional: false -- name: mkl-devel - version: 2022.1.0 - manager: conda - platform: win-64 - dependencies: - mkl: 2022.1.0 - mkl-include: 2022.1.0 - url: https://conda.anaconda.org/conda-forge/win-64/mkl-devel-2022.1.0-h57928b3_875.tar.bz2 - hash: - md5: 6319a06307af296c1dfae93687c283b2 - sha256: 53743958ce4fbc2e17b84c0a97392bd9fdefa0b66dab5ad61dd3128129cae376 - category: main - optional: false -- name: platformdirs - version: 3.5.1 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - typing-extensions: '>=4.5' - url: https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.5.1-pyhd8ed1ab_0.conda - hash: - md5: e2be672aece1f060adf7154f76531a35 - sha256: d7845c01a9ee5a224cc9242782befed7d12dc6aac1103650ec87917b20f3579e - category: main - optional: false -- name: poetry-core - version: 1.6.1 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - importlib-metadata: '>=1.7.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-core-1.6.1-pyhd8ed1ab_0.conda - hash: - md5: a6d1f61527c27fcc0165a6701a46b9f4 - sha256: 6f6a66476908a1c109e36c852d934eedceb07e0cbc44d3fcd87c6f39521b57be - category: main - optional: false -- name: pydantic - version: 1.10.8 - manager: conda - platform: win-64 - dependencies: - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - typing-extensions: '>=4.2.0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/pydantic-1.10.8-py39ha55989b_0.conda - hash: - md5: 022494423703646dd0a723ca4b4b90eb - sha256: e6513f3c20c8f60dd68362215511d04840c6beb861dabc0e021b73086f9bcd8b - category: main - optional: false -- name: pynacl - version: 1.5.0 - manager: conda - platform: win-64 - dependencies: - cffi: '>=1.4.1' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - six: '' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.5.0-py39h09fa780_2.tar.bz2 - hash: - md5: 23134a4532918f402a00bfbdca1292b2 - sha256: 8d72d82fe9787952b28226a0ec83584a563ee8399bd0b20d8b8f291410052de1 - category: main - optional: false -- name: python-build - version: 0.10.0 - manager: conda - platform: win-64 - dependencies: - colorama: '' - pyproject_hooks: '' - python: '>=3.7' - tomli: '>=1.1.0' - packaging: '>=19.0' - importlib-metadata: '>=0.22' - url: https://conda.anaconda.org/conda-forge/noarch/python-build-0.10.0-pyhd8ed1ab_1.conda - hash: - md5: 0ab47ce574f6a8bcb9f2076436e7fedb - sha256: 4c2cd519c85aa8b8e584723ca5f452aa5941d18374470adebfe73bf30fd27573 - category: main - optional: false -- name: rich - version: 13.4.1 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7.0' - typing_extensions: '>=4.0.0,<5.0.0' - markdown-it-py: '>=2.2.0,<3.0.0' - pygments: '>=2.13.0,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/rich-13.4.1-pyhd8ed1ab_0.conda - hash: - md5: c3bcbe0d086f15e5918568d3865e4dbf - sha256: 312f2628e06a591096a851bf678833fe670ecb16e9b15517ce8e03d7c9d9e600 - category: main - optional: false -- name: sqlalchemy - version: 2.0.15 - manager: conda - platform: win-64 - dependencies: - greenlet: '!=0.4.17' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - typing-extensions: '>=4.2.0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/sqlalchemy-2.0.15-py39ha55989b_0.conda - hash: - md5: ca1135dd756371fea21b0eac651f358b - sha256: 69902c45b5ad01e5156dd5a2e8f108b7158b36baa33f14c3b5c8b0281f10e632 - category: main - optional: false -- name: starlette - version: 0.22.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - typing_extensions: '>=3.10.0' - anyio: <5,>=3.4.0 - url: https://conda.anaconda.org/conda-forge/noarch/starlette-0.22.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 49d5cdcc16c691e4ad9355c81f004c3e - sha256: 1441dd55c037184b7d2c1e1dbf60beafb1f92fdc13cabf78a85e12825a55269b - category: main - optional: false -- name: uvicorn - version: 0.22.0 - manager: conda - platform: win-64 - dependencies: - click: '>=7.0' - h11: '>=0.8' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - url: https://conda.anaconda.org/conda-forge/win-64/uvicorn-0.22.0-py39hcbf5309_0.conda - hash: - md5: 3ec81ca1e40c0737b5abb9373cdb9b7d - sha256: 44ef4adbe03d3caeb74e9002786175338665f66d27c2e3a06151e5c215d1f28d - category: main - optional: false -- name: wcwidth - version: 0.2.6 - manager: conda - platform: win-64 - dependencies: - backports.functools_lru_cache: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.6-pyhd8ed1ab_0.conda - hash: - md5: 078979d33523cb477bd1916ce41aacc9 - sha256: c1bd0ad7d854cae56977b7915ac2b78b652fa5f7ec1e9fc21e7fdb30cf4519b1 - category: main - optional: false -- name: alembic - version: 1.11.1 - manager: conda - platform: win-64 - dependencies: - importlib-metadata: '' - importlib_resources: '' - mako: '' - python: '>=3.7' - sqlalchemy: '>=1.3.0' - typing-extensions: '>=4' - url: https://conda.anaconda.org/conda-forge/noarch/alembic-1.11.1-pyhd8ed1ab_0.conda - hash: - md5: 6a55e123397b42b79c48b31d1b7a91b8 - sha256: 065dd1b38ebe3a0d14f45549f63cce55125052057db565be153cdd73aa2a7c8d - category: main - optional: false -- name: aws-crt-cpp - version: 0.20.2 - manager: conda - platform: win-64 - dependencies: - aws-c-auth: '>=0.6.27,<0.6.28.0a0' - aws-c-cal: '>=0.5.26,<0.5.27.0a0' - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-event-stream: '>=0.2.20,<0.2.21.0a0' - aws-c-http: '>=0.7.7,<0.7.8.0a0' - aws-c-io: '>=0.13.21,<0.13.22.0a0' - aws-c-mqtt: '>=0.8.11,<0.8.12.0a0' - aws-c-s3: '>=0.3.0,<0.3.1.0a0' - aws-checksums: '>=0.1.14,<0.1.15.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/aws-crt-cpp-0.20.2-he8b9daf_0.conda - hash: - md5: d4767837eb46f07d02365fba40f6bf06 - sha256: 4e9e6e3dbf528e66dcadeeaa091ee3e05db152b222873e1f0a1732e859c7c239 - category: main - optional: false -- name: blessed - version: 1.19.1 - manager: conda - platform: win-64 - dependencies: - __win: '' - python: '>=3.8' - six: '>=1.9.0' - wcwidth: '>=0.1.4' - jinxed: '>=0.5.4' - url: https://conda.anaconda.org/conda-forge/noarch/blessed-1.19.1-pyh95a074a_2.tar.bz2 - hash: - md5: cbfae8732ee6ecb78d871a7179cc15cf - sha256: 49144739a6a820b1a6e6e69bb1546101dd7b0418562598498686cf314a2c66a8 - category: main - optional: false -- name: fastapi - version: 0.88.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - pydantic: '>=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0' - starlette: 0.22.0.* - url: https://conda.anaconda.org/conda-forge/noarch/fastapi-0.88.0-pyhd8ed1ab_0.conda - hash: - md5: 0bd34a2b460d02e2fbf88ca5d9d3e55d - sha256: 660b356b8d4f3b99b6294c638637699003b0533c0ecab9389c56367b4bfe5c59 - category: main - optional: false -- name: keyring - version: 23.13.1 - manager: conda - platform: win-64 - dependencies: - importlib_metadata: '>=4.11.4' - jaraco.classes: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - pywin32-ctypes: '' - url: https://conda.anaconda.org/conda-forge/win-64/keyring-23.13.1-py39hcbf5309_0.conda - hash: - md5: a18cc6e0aff1726685341f384141ecb5 - sha256: 20b81a86861b6b45fb66401e4b3b26e29c917e13846b0b37cb0f21913ac6aff7 - category: main - optional: false -- name: libcblas - version: 3.9.0 - manager: conda - platform: win-64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-16_win64_mkl.tar.bz2 - hash: - md5: 14c2fb03b2bb14dfa3806186ca91d557 - sha256: 4ca91d4ff2ef409d2426c5aa5f451410bd817c0ad7410f3a95d62ddc13e2d1f1 - category: main - optional: false -- name: liblapack - version: 3.9.0 - manager: conda - platform: win-64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.9.0-16_win64_mkl.tar.bz2 - hash: - md5: be2f9d5712a5bb05cd900005ee752a05 - sha256: cb10f543120e277e44c342f65bcec2bd27f4bd206f5ea9332efd91e4551e5bac - category: main - optional: false -- name: oauthlib - version: 3.2.2 - manager: conda - platform: win-64 - dependencies: - cryptography: '' - blinker: '' - python: '>=3.6' - pyjwt: '>=1.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/oauthlib-3.2.2-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 8f882b197fd9c4941a787926baea4868 - sha256: 0cfd5146a91d3974f4abfc2a45de890371d510a77238fe553e036ec8c031dc5b - category: main - optional: false -- name: paramiko - version: 3.2.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - cryptography: '>=3.3' - bcrypt: '>=3.2' - pynacl: '>=1.5' - url: https://conda.anaconda.org/conda-forge/noarch/paramiko-3.2.0-pyhd8ed1ab_0.conda - hash: - md5: f212c7eb95e909df4795297f73690993 - sha256: e425a03e5e2ef2ec5a78711686c59cfceeeeec3a98165fbc7d186bd6a5cb78de - category: main - optional: false -- name: poetry-plugin-export - version: 1.4.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7,<4.0' - poetry-core: '>=1.6.0,<2.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-plugin-export-1.4.0-pyhd8ed1ab_0.conda - hash: - md5: 00893c7ea4f9f7620706e0aa94c01b6e - sha256: 54478b283b5967a85ee5da717f1512d7ec97eb07c7b52d1f2ad3cb080d56c0ac - category: main - optional: false -- name: prometheus_flask_exporter - version: 0.22.4 - manager: conda - platform: win-64 - dependencies: - flask: '' - prometheus_client: '' - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/prometheus_flask_exporter-0.22.4-pyhd8ed1ab_0.conda - hash: - md5: 43acea130cafd18740b73fa4c226c9f7 - sha256: be83619ef5964713cd298d0fb86eddd99b159e5fba3d0f91d624ce5d7c3890e0 - category: main - optional: false -- name: pyopenssl - version: 23.2.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - cryptography: '>=38.0.0,<42,!=40.0.0,!=40.0.1' - url: https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.2.0-pyhd8ed1ab_1.conda - hash: - md5: 34f7d568bf59d18e3fef8c405cbece21 - sha256: 4daea3dc896987cc1334956fccfc0ed738663a84ad0c1d3f576a7a7936091534 - category: main - optional: false -- name: starsessions - version: 1.3.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6.2' - itsdangerous: '>=2.0.1' - starlette: '>=0' - url: https://conda.anaconda.org/conda-forge/noarch/starsessions-1.3.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 667d08040a85d7ea1c6d4af2290f96c4 - sha256: 4a500ac0a9fe56cee7958d6d0f6530272c43ee4c16c52600001decb39fe3cd59 - category: main - optional: false -- name: typer - version: 0.9.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - typing-extensions: '>=3.7.4.3' - colorama: '>=0.4.3,<0.5.0' - shellingham: '>=1.3.0,<2.0.0' - click: '>=7.1.1,<9' - rich: '>=10.11.0,<14.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/typer-0.9.0-pyhd8ed1ab_0.conda - hash: - md5: 5030a13b2fe5e143d5956d4943d3018f - sha256: d395e1e92281abb13e043220ecf8ea973ada8d38a1e8c683df14f46541c64bd2 - category: main - optional: false -- name: virtualenv - version: 20.23.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.8' - distlib: <1,>=0.3.6 - filelock: <4,>=3.11 - platformdirs: <4,>=3.2 - url: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.23.0-pyhd8ed1ab_0.conda - hash: - md5: a920e114c4c2ced2280e266da65ab5e6 - sha256: 13d667887ea08b6d1fe2eb09d2d737f9af7343735d3bfa5ffaa3f67eec8eaff7 - category: main - optional: false -- name: aws-sdk-cpp - version: 1.10.57 - manager: conda - platform: win-64 - dependencies: - aws-c-common: '>=0.8.19,<0.8.20.0a0' - aws-c-event-stream: '>=0.2.20,<0.2.21.0a0' - aws-crt-cpp: '>=0.20.2,<0.20.3.0a0' - libcurl: '>=8.1.1,<9.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - openssl: '>=3.1.0,<4.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/aws-sdk-cpp-1.10.57-ha156882_13.conda - hash: - md5: 4c3b2413b3ad740a00a078da1b7bb629 - sha256: bd21c4da0c535e2ba43a944bd675d19a23f2a8a76d3d79d66e145ce7d5814f36 - category: main - optional: false -- name: liblapacke - version: 3.9.0 - manager: conda - platform: win-64 - dependencies: - libblas: 3.9.0 - libcblas: 3.9.0 - liblapack: 3.9.0 - url: https://conda.anaconda.org/conda-forge/win-64/liblapacke-3.9.0-16_win64_mkl.tar.bz2 - hash: - md5: 983e827b7c9562075c2e74d596d056c1 - sha256: 63a59c4affb5d1d6a0f55154cb82cc26e34506e60addf6c9110457d9a95d2ba1 - category: main - optional: false -- name: numpy - version: 1.24.3 - manager: conda - platform: win-64 - dependencies: - libblas: '>=3.9.0,<4.0a0' - libcblas: '>=3.9.0,<4.0a0' - liblapack: '>=3.9.0,<4.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/numpy-1.24.3-py39h816b6a6_0.conda - hash: - md5: 9b48d066a664d421b1db4e0bfeaf8e3b - sha256: 1ec141f79322b9d798fca36dd7544be9ecbf03b821a5df8b840fbfd5ff3d2349 - category: main - optional: false -- name: urllib3 - version: 1.26.15 - manager: conda - platform: win-64 - dependencies: - certifi: '' - python: <4.0 - idna: '>=2.0.0' - pyopenssl: '>=0.14' - pysocks: '>=1.5.6,<2.0,!=1.5.7' - cryptography: '>=1.3.4' - brotlipy: '>=0.6.0' - url: https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.15-pyhd8ed1ab_0.conda - hash: - md5: 27db656619a55d727eaf5a6ece3d2fd6 - sha256: 213bdf6c3a5d721fa83b45d527d3ecd340f9547c0d6bbd0b8d9d746ec9a1fb4b - category: main - optional: false -- name: blas-devel - version: 3.9.0 - manager: conda - platform: win-64 - dependencies: - libblas: 3.9.0 - libcblas: 3.9.0 - liblapack: 3.9.0 - liblapacke: 3.9.0 - mkl: '>=2022.1.0,<2023.0a0' - mkl-devel: 2022.1.* - url: https://conda.anaconda.org/conda-forge/win-64/blas-devel-3.9.0-16_win64_mkl.tar.bz2 - hash: - md5: dc89c75a7dd26c88ac77d64bf313973e - sha256: c983ce54e11af04c279fb19b25150347720e50957075c8d7d827232413e1588e - category: main - optional: false -- name: contourpy - version: 1.0.7 - manager: conda - platform: win-64 - dependencies: - numpy: '>=1.16' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/contourpy-1.0.7-py39h1f6ef14_0.conda - hash: - md5: a9349020a5d31dc6eed3580e35f5f45a - sha256: 6578411eb7e8113870075cfd7f078132a459a1c2a2aa820a82734a4a23628c04 - category: main - optional: false -- name: dulwich - version: 0.21.5 - manager: conda - platform: win-64 - dependencies: - certifi: '' - cryptography: '>=1.3.4' - idna: '>=2.0.0' - pyopenssl: '>=0.14' - pysocks: '>=1.5.6,<2.0,!=1.5.7' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - urllib3: '' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/dulwich-0.21.5-py39ha55989b_0.conda - hash: - md5: e0359f929c9a4753f380aea2f5c80e76 - sha256: 507b6264e421378088e0158073b80089b7331679d81cc0245910fce0f54e7fba - category: main - optional: false -- name: libarrow - version: 11.0.0 - manager: conda - platform: win-64 - dependencies: - aws-sdk-cpp: '>=1.10.57,<1.10.58.0a0' - bzip2: '>=1.0.8,<2.0a0' - c-ares: '>=1.19.1,<2.0a0' - gflags: '>=2.2.2,<2.3.0a0' - glog: '>=0.6.0,<0.7.0a0' - libabseil: '>=20230125.2,<20230126.0a0' - libbrotlicommon: '>=1.0.9,<1.1.0a0' - libbrotlidec: '>=1.0.9,<1.1.0a0' - libbrotlienc: '>=1.0.9,<1.1.0a0' - libcrc32c: '>=1.1.2,<1.2.0a0' - libcurl: '>=8.1.1,<9.0a0' - libevent: '>=2.1.12,<2.1.13.0a0' - libgoogle-cloud: '>=2.10.1,<2.10.2.0a0' - libgrpc: '>=1.54.2,<1.55.0a0' - libprotobuf: '>=3.21.12,<3.22.0a0' - libthrift: '>=0.18.1,<0.18.2.0a0' - libutf8proc: '>=2.8.0,<3.0a0' - libzlib: '>=1.2.13,<1.3.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - openssl: '>=3.1.0,<4.0a0' - orc: '>=1.8.3,<1.8.4.0a0' - re2: '>=2023.3.2,<2023.3.3.0a0' - snappy: '>=1.1.10,<2.0a0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - zstd: '>=1.5.2,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/win-64/libarrow-11.0.0-ha096e86_21_cpu.conda - hash: - md5: f00741dcd1fa62d24b39c472e2f52004 - sha256: e0882f88d31682912adc035b84d17387f82e078835d4f7e143880226828c6bcf - category: main - optional: false -- name: pandas - version: 2.0.2 - manager: conda - platform: win-64 - dependencies: - numpy: '>=1.21.6,<2.0a0' - python: '>=3.9,<3.10.0a0' - python-dateutil: '>=2.8.1' - python-tzdata: '>=2022a' - python_abi: 3.9.* - pytz: '>=2020.1' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/pandas-2.0.2-py39h1679cfb_0.conda - hash: - md5: c5f0804ec7dba566e0e1ee043fea5bbc - sha256: a4faefc205bc18f702936c59483ab13bd054978c5fdbc78b73cc42bbb2768872 - category: main - optional: false -- name: rapidfuzz - version: 2.15.1 - manager: conda - platform: win-64 - dependencies: - numpy: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/rapidfuzz-2.15.1-py39h99910a6_0.conda - hash: - md5: 77d7b609361d7bca9be3604a076d2512 - sha256: 32c1d7d3e54c20dd6b15ad045da34e9cf7281964c610b600504064358bf3c438 - category: main - optional: false -- name: requests - version: 2.31.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - idna: '>=2.5,<4' - certifi: '>=2017.4.17' - charset-normalizer: '>=2,<4' - urllib3: '>=1.21.1,<3' - url: https://conda.anaconda.org/conda-forge/noarch/requests-2.31.0-pyhd8ed1ab_0.conda - hash: - md5: a30144e4156cdbb236f99ebb49828f8b - sha256: 9f629d6fd3c8ac5f2a198639fe7af87c4db2ac9235279164bfe0fcb49d8c4bad - category: main - optional: false -- name: arrow-cpp - version: 11.0.0 - manager: conda - platform: win-64 - dependencies: - libarrow: 11.0.0 - url: https://conda.anaconda.org/conda-forge/win-64/arrow-cpp-11.0.0-h57928b3_21_cpu.conda - hash: - md5: 1f98391e60e18b1b7e3af093b1ab549e - sha256: 86959f55160469a8abc8139c77ca396fdf6d97c5b9e5568f36651b053964b1d9 - category: main - optional: false -- name: blas - version: '2.116' - manager: conda - platform: win-64 - dependencies: - blas-devel: 3.9.0 - libblas: 3.9.0 - libcblas: 3.9.0 - liblapack: 3.9.0 - liblapacke: 3.9.0 - m2w64-gcc-libs: '' - url: https://conda.anaconda.org/conda-forge/win-64/blas-2.116-mkl.tar.bz2 - hash: - md5: 7529860b43278247a278c6f56a191d2e - sha256: a51d1e9b1f693460d0af3b3240b9cac581cba5b2bfb54b3fd3430c14ed626c87 - category: main - optional: false -- name: cachecontrol - version: 0.12.11 - manager: conda - platform: win-64 - dependencies: - requests: '' - python: '>=3.6' - msgpack-python: '>=0.5.2' - url: https://conda.anaconda.org/conda-forge/noarch/cachecontrol-0.12.11-pyhd8ed1ab_1.conda - hash: - md5: e8f0410e0aa03342304357c5cc3bb75d - sha256: 466ce7c155be90a5c903052eba391759ae88eb65f2bb06b0cc1c9d09c4311800 - category: main - optional: false -- name: cleo - version: 2.0.1 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7,<4.0' - crashtest: '>=0.4.1,<0.5.0' - rapidfuzz: '>=2.2.0,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/cleo-2.0.1-pyhd8ed1ab_0.conda - hash: - md5: f1c5f2af6676cbe9206e191d1e70f661 - sha256: cf9bc4c9356ad8eb68512446eebc076386f2bfb8ca86626e8796621bc5a13082 - category: main - optional: false -- name: databricks-cli - version: 0.17.7 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - click: '>=7.0' - six: '>=1.10.0' - pyjwt: '>=1.7.0' - tabulate: '>=0.7.7' - requests: '>=2.17.3' - configparser: '>=0.3.5' - oauthlib: '>=3.1.0' - url: https://conda.anaconda.org/conda-forge/noarch/databricks-cli-0.17.7-pyhd8ed1ab_0.conda - hash: - md5: cb44b4e93848f13dce352c422285ac45 - sha256: 1c2ec6c6125bc1c1d100b75e1f5dfda09c56cd516157d2d5b01c1aa69fdd0dbd - category: main - optional: false -- name: docker-py - version: 6.1.0 - manager: conda - platform: win-64 - dependencies: - pywin32-on-windows: '' - python: '>=3.7' - requests: '>=2.26.0' - urllib3: '>=1.26.0' - websocket-client: '>=0.32.0' - packaging: '>=14.0' - paramiko: '>=2.4.3' - url: https://conda.anaconda.org/conda-forge/noarch/docker-py-6.1.0-pyhd8ed1ab_0.conda - hash: - md5: 543336c6aa9516cfb29c51d5c162b177 - sha256: 5e01e15e20ee573c99b530633a0d5c71fd515e4ac6d2f5f5f57baece8b915cc3 - category: main - optional: false -- name: lightning-cloud - version: 0.5.36 - manager: conda - platform: win-64 - dependencies: - requests: '' - six: '' - click: '' - rich: '' - pyjwt: '' - urllib3: '' - fastapi: '' - websocket-client: '' - python-multipart: '' - uvicorn: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/lightning-cloud-0.5.36-pyhd8ed1ab_0.conda - hash: - md5: fd99cc369aa3c6c66493d4d278338af5 - sha256: 2047f4dcd4531f6cd2f87a80fef0d265f663e011931af746b2725f7d567ce016 - category: main - optional: false -- name: matplotlib-base - version: 3.7.1 - manager: conda - platform: win-64 - dependencies: - certifi: '>=2020.06.20' - contourpy: '>=1.0.1' - cycler: '>=0.10' - fonttools: '>=4.22.0' - freetype: '>=2.12.1,<3.0a0' - importlib-resources: '>=3.2.0' - kiwisolver: '>=1.0.1' - numpy: '>=1.20.3,<2.0a0' - packaging: '>=20.0' - pillow: '>=6.2.0' - pyparsing: '>=2.3.1' - python: '>=3.9,<3.10.0a0' - python-dateutil: '>=2.7' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vs2015_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.7.1-py39haf65ace_0.conda - hash: - md5: 67eab77ac976416272dd5c588815a878 - sha256: 965e27bc70a05b0011b6155829dde57f90642969c409da1b0883ee70610aa367 - category: main - optional: false -- name: pooch - version: 1.7.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.7' - packaging: '>=20.0' - requests: '>=2.19.0' - platformdirs: '>=2.5.0' - url: https://conda.anaconda.org/conda-forge/noarch/pooch-1.7.0-pyha770c72_3.conda - hash: - md5: 5936894aade8240c867d292aa0d980c6 - sha256: 64e4d633803df2e36fd141d9bf269568fbe179a313248e1dac4d364c02debdef - category: main - optional: false -- name: querystring_parser - version: 1.2.4 - manager: conda - platform: win-64 - dependencies: - python: '' - requests: '' - six: '' - url: https://conda.anaconda.org/conda-forge/noarch/querystring_parser-1.2.4-py_0.tar.bz2 - hash: - md5: 0ebdca9b753c2e082e5b5ad06aa76b41 - sha256: 06977a9af6d8605fb6068d8af6bb9c1cb565f8f5e15aa6cf0fb94109d4148b54 - category: main - optional: false -- name: requests-toolbelt - version: 1.0.0 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - requests: '>=2.0.1,<3.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-1.0.0-pyhd8ed1ab_0.conda - hash: - md5: 99c98318c8646b08cc764f90ce98906e - sha256: 20eaefc5dba74ff6c31e537533dde59b5b20f69e74df49dff19d43be59785fa3 - category: main - optional: false -- name: cachecontrol-with-filecache - version: 0.12.11 - manager: conda - platform: win-64 - dependencies: - python: '>=3.6' - lockfile: '>=0.9' - cachecontrol: 0.12.11 - url: https://conda.anaconda.org/conda-forge/noarch/cachecontrol-with-filecache-0.12.11-pyhd8ed1ab_1.conda - hash: - md5: 9df660456c0076d27b802448f7ede78f - sha256: 81c483fc92656873eb5a7ba657b208c34186556d942a9cebc1f7771e565b95b7 - category: main - optional: false -- name: parquet-cpp - version: 1.5.1 - manager: conda - platform: win-64 - dependencies: - arrow-cpp: '>=0.11.0' - url: https://conda.anaconda.org/conda-forge/noarch/parquet-cpp-1.5.1-2.tar.bz2 - hash: - md5: 79a5f78c42817594ae016a7896521a97 - sha256: 15e50657515b791734ba045da5135377404ca37c518b2066b9c6451c65cd732e - category: main - optional: false -- name: pytorch - version: 1.13.1 - manager: conda - platform: win-64 - dependencies: - blas: '*' - intel-openmp: '' - libuv: '>=1.40.0,<2.0a0' - mkl: '>=2018' - python: '>=3.9,<3.10.0a0' - pytorch-mutex: '1.0' - typing_extensions: '' - url: https://conda.anaconda.org/pytorch/win-64/pytorch-1.13.1-py3.9_cpu_0.tar.bz2 - hash: - md5: ca9e16014c020a6dba084bf0c210d6e5 - sha256: 84d3a97604480d9a4f48090555e9b618fad4fe68d029617186404227f4d696d1 - category: main - optional: false -- name: scipy - version: 1.10.1 - manager: conda - platform: win-64 - dependencies: - libblas: '>=3.9.0,<4.0a0' - libcblas: '>=3.9.0,<4.0a0' - liblapack: '>=3.9.0,<4.0a0' - m2w64-gcc-libs: '' - m2w64-gcc-libs-core: '' - numpy: '>=1.21.6,<2.0a0' - pooch: '' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/scipy-1.10.1-py39hde5eda1_3.conda - hash: - md5: 8abf202e8e71ea7233aa6c9f02fcdec6 - sha256: 50eac2767015db950e1b8c40bfd3bc00923bde3e04b8add5680abed35953b3aa - category: main - optional: false -- name: poetry - version: 1.5.1 - manager: conda - platform: win-64 - dependencies: - __win: '' - tomli: '>=2.0.1,<3.0.0' - packaging: '>=20.4' - importlib-metadata: '>=4.4' - python: '>=3.7.0,<4.0.0' - urllib3: '>=1.26.0,<2.0.0' - crashtest: '>=0.4.1,<0.5.0' - requests: '>=2.18,<3.0' - pexpect: '>=4.7.0,<5.0.0' - cleo: '>=2.0.0,<3.0.0' - filelock: '>=3.8.0,<4.0.0' - jsonschema: '>=4.10.0,<5.0.0' - keyring: '>=23.9.0,<24.0.0' - trove-classifiers: '>=2022.5.19' - lockfile: '>=0.12.2,<0.13.0' - backports.cached-property: '>=1.0.2,<2.0.0' - dulwich: '>=0.21.2,<0.22.0' - pkginfo: '>=1.9.4,<2.0' - pyproject_hooks: '>=1.0.0,<2.0.0' - python-build: '>=0.10.0,<0.11.0' - python-installer: '>=0.7.0,<0.8.0' - platformdirs: '>=3.0.0,<4.0.0' - requests-toolbelt: '>=0.9.1,<2' - tomlkit: '>=0.11.4,<1.0.0' - virtualenv: '>=20.22.0,<21.0.0' - cachecontrol-with-filecache: '>=0.12.9,<0.13.0' - html5lib: '>=1.0.0,<2.0.0' - poetry-core: 1.6.1.* - poetry-plugin-export: '>=1.4.0,<2.0.0' - shellingham: '>=1.5.0,<2.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/poetry-1.5.1-win_pyhd8ed1ab_0.conda - hash: - md5: 278a253d341cdb93af47a14c794f6c02 - sha256: 03eee33329dceb0b2a5d88eac3bff5a316bf75046f4634b53d1e6816588325a8 - category: main - optional: false -- name: pyarrow - version: 11.0.0 - manager: conda - platform: win-64 - dependencies: - libarrow: 11.0.0 - numpy: '>=1.21.6,<2.0a0' - parquet-cpp: 1.5.1.* - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/pyarrow-11.0.0-py39hca4e8af_21_cpu.conda - hash: - md5: 2c5f2cbbd5bf9018832ace73bbaf8b63 - sha256: f25b0b32378dd31f34345ab667ab53d0ec88e90d562536271c2ffbff1fbdf231 - category: main - optional: false -- name: scikit-learn - version: 1.2.2 - manager: conda - platform: win-64 - dependencies: - joblib: '>=1.1.1' - numpy: '>=1.21.6,<2.0a0' - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - scipy: '' - threadpoolctl: '>=2.0.0' - ucrt: '>=10.0.20348.0' - vc: '>=14.2,<15' - vc14_runtime: '>=14.29.30139' - url: https://conda.anaconda.org/conda-forge/win-64/scikit-learn-1.2.2-py39hfa9d973_2.conda - hash: - md5: 907305bd8b6a384f14d41081efd9eb7e - sha256: 506e62c9813cb398b5831b18e5c50937625607cd1b432987962c94d27508e298 - category: main - optional: false -- name: torchmetrics - version: 0.11.4 - manager: conda - platform: win-64 - dependencies: - setuptools: '' - packaging: '' - python: '>=3.7' - pytorch: '>=1.8.1' - url: https://conda.anaconda.org/conda-forge/noarch/torchmetrics-0.11.4-pyhd8ed1ab_0.conda - hash: - md5: 480cb2b6d502003e937d9e4326bc398f - sha256: 3927eae14903c76679d5151af39680e7d42c35e0bdaa5041f577fc40f0619be8 - category: main - optional: false -- name: torchvision - version: 0.14.1 - manager: conda - platform: win-64 - dependencies: - jpeg: '' - libpng: '' - numpy: '>=1.11' - pillow: '>=5.3.0,!=8.3.*' - python: '>=3.9,<3.10.0a0' - pytorch: 1.13.1 - pytorch-mutex: '1.0' - requests: '' - url: https://conda.anaconda.org/pytorch/win-64/torchvision-0.14.1-py39_cpu.tar.bz2 - hash: - md5: c0b3b48bed026b0698aac1fefb9db720 - sha256: 9deaae802dfcb7454f738c7aa8aa5aeb13e4a17e6c30001bc9ab57f0dbf3154d - category: main - optional: false -- name: inquirer - version: 3.1.3 - manager: conda - platform: win-64 - dependencies: - poetry: '' - python: '>=3.7' - blessed: '>=1.19.0' - python-editor: '>=1.0.4' - readchar: '>=2.0.1' - url: https://conda.anaconda.org/conda-forge/noarch/inquirer-3.1.3-pyhd8ed1ab_0.conda - hash: - md5: 0d8bc31361e09dc50555465284e10880 - sha256: da912877ac6e0795490834c96167e93a1eda89290ef8de63502740ef738d4435 - category: main - optional: false -- name: mlflow - version: 2.3.2 - manager: conda - platform: win-64 - dependencies: - alembic: <2,!=1.10 - click: '>=7.0,<9' - cloudpickle: <3 - databricks-cli: '>=0.8.7,<1' - docker-py: '>=4.0.0,<7' - entrypoints: <1 - flask: <3 - gitpython: '>=2.1.0,<4' - importlib-metadata: <7,>=3.7.0,!=4.7.0 - jinja2: <4,>=3.0 - markdown: <4,>=3.3 - matplotlib-base: <4 - numpy: <2 - openssl: '' - packaging: <24 - pandas: <3 - prometheus_flask_exporter: <1 - protobuf: '>=3.12.0,<5' - pyarrow: <12,>=4.0.0 - python: '>=3.9,<3.10.0a0' - python_abi: 3.9.* - pytz: <2024 - pyyaml: '>=5.1,<7' - querystring_parser: <2 - requests: '>=2.17.3,<3' - scikit-learn: <2 - scipy: <2 - sqlalchemy: '>=1.4.0,<3' - sqlparse: '>=0.4.0,<1' - waitress: <3 - url: https://conda.anaconda.org/conda-forge/win-64/mlflow-2.3.2-py39h54a35c1_1.conda - hash: - md5: 3e0f3023d4b6743ed3d05b846e486793 - sha256: a31e6d20ca26ba0c5675e5657109af3b342fa70a0acd8fe14a7620e89892e289 - category: main - optional: false -- name: pytorch-lightning - version: 2.0.2 - manager: conda - platform: win-64 - dependencies: - requests: '' - python: '>=3.8' - pyyaml: '>=5.4' - numpy: '>=1.17.2' - packaging: '>=17.1' - torchmetrics: '>=0.7.0' - tqdm: '>=4.57.0' - typing_extensions: '>=4.0.0' - pytorch: '>=1.11.0' - fsspec: '>2021.06.0' - lightning-utilities: '>=0.7.0' - url: https://conda.anaconda.org/conda-forge/noarch/pytorch-lightning-2.0.2-pyhd8ed1ab_0.conda - hash: - md5: abd4916f586ae33b56d1e2b44a7990aa - sha256: 9332cb927d6c587ed5b23628208ebb4479288244f4388bd4cb9745df115cca25 - category: main - optional: false -- name: lightning - version: 2.0.0 - manager: conda - platform: win-64 - dependencies: - packaging: '' - pytorch-lightning: '' - python-multipart: '' - python: '>=3.8' - arrow: <3.0,>=1.2.0 - beautifulsoup4: <6.0,>=4.8.0 - click: <10.0 - croniter: <1.4.0,>=1.3.0 - dateutils: <2.0 - deepdiff: <8.0,>=5.7.0 - fsspec: <2024.0,>=2022.5.0 - inquirer: <5.0,>=2.10.0 - jinja2: <5.0 - lightning-utilities: <2.0,>=0.7.0 - numpy: <3.0,>=1.17.2 - psutil: <7.0 - pytorch: <4.0,>=1.11.0 - pyyaml: <8.0 - requests: <4.0 - rich: <15.0,>=12.3.0 - starsessions: <2.0,>=1.2.1 - torchmetrics: <2.0,>=0.7.0 - tqdm: <6.0,>=4.57.0 - traitlets: <7.0,>=5.3.0 - typing-extensions: <6.0,>=4.0.0 - urllib3: <3.0 - uvicorn: <2.0 - websocket-client: <3.0 - websockets: <12.0 - fastapi: <0.89.0 - lightning-cloud: '>=0.5.31' - pydantic: <3.0 - starlette: <2.0 - url: https://conda.anaconda.org/conda-forge/noarch/lightning-2.0.0-pyhd8ed1ab_0.conda - hash: - md5: 5c38b552a8b1853c39738d9a9f090ffc - sha256: 4b6bf3a963ea91f81de4947ad8a0686bb8cf868f63a5a125088938fc9551ace1 - category: main - optional: false diff --git a/ai/setup.py b/ai/setup.py deleted file mode 100644 index 9b73f924..00000000 --- a/ai/setup.py +++ /dev/null @@ -1,20 +0,0 @@ -from setuptools import setup, find_packages - -setup( - name="itwinai", - description="AI and ML workflows module for interTwin", - author="Matteo Bunino and Alexander Zoechbauer", - author_email="matteo.bunino@cern.ch and alexander.zoechbauer@cern.ch", - version="0.1", - packages=find_packages("src"), - package_dir={"": "src"}, - entry_points={ - "console_scripts": [ - "itwinai=itwinai.cli:app" - ] - }, - # Pip dependencies - install_requires=[ - 'jsonargparse[signatures]>=4.17.0' - ] -) diff --git a/ai/src/itwinai/cli.py b/ai/src/itwinai/cli.py deleted file mode 100644 index 051377fb..00000000 --- a/ai/src/itwinai/cli.py +++ /dev/null @@ -1,412 +0,0 @@ -""" -Command line interface for out Python application. -You can call commands from the command line. -Example - ->>> $ itwinai --help - -""" - -# NOTE: import libs in the command"s function, not here. -# Otherwise this will slow the whole CLI. - -# from typing import Optional -import os -import sys -import typer - - -app = typer.Typer() - - -@app.command() -def train( - train_dataset: str = typer.Option( - "unk", - help="Path to training dataset." - ), - config: str = typer.Option( - "unk", - help="Path to training configuration file." - ), - ml_logs: str = typer.Option( - "ml-logs/", - help="Path to logs storage." - ) -): - """ - Train a neural network defined as a Pytorch Lightning model. - """ - import copy - import shutil - import mlflow - from lightning.pytorch.cli import LightningCLI - from omegaconf import DictConfig, OmegaConf - - from itwinai.utils import ( - load_yaml_with_deps, - check_server, - flatten_dict - ) - from itwinai.plmodels.base import ( - ItwinaiBasePlModule, - ItwinaiBasePlDataModule - ) - - # Replicate args under cli field, to be used in imported config files - cli_conf = dict(cli=dict( - train_dataset=train_dataset, - config=config, - ml_logs=ml_logs - )) - cli_conf = OmegaConf.create(cli_conf) - - # os.makedirs(ml_logs, exist_ok=True) - train_config: DictConfig = load_yaml_with_deps(config) - train_config = OmegaConf.merge(train_config, cli_conf) - # print(OmegaConf.to_yaml(train_config)) - train_config = OmegaConf.to_container(train_config, resolve=True) - - # Setup logger - if os.path.exists('checkpoints'): - # Remove old checkpoints - shutil.rmtree('checkpoints') - - # Check if MLflow server is reachable - if not check_server(ml_logs): - raise RuntimeError("MLFlow server not reachable!") - - log_conf = train_config['logger'] - # mlflow.set_tracking_uri('file:' + ml_logs) - mlflow.set_tracking_uri(ml_logs) - mlflow.set_experiment(log_conf['experiment_name']) - mlflow.pytorch.autolog( - log_every_n_epoch=log_conf['log_every_n_epoch'], - log_every_n_step=log_conf['log_every_n_steps'], - registered_model_name=log_conf['registered_model_name'] - ) - # Note: we use autolog and MlFlowLogger combined: - # - MlFlow logger provides better flexibility - # - autolog takes care of repetitive operations - # Ref: https://github.com/Lightning-AI/lightning/discussions/11197 - - # Load training configuration - lightning_conf = train_config['train']['conf'] - # lightning_conf = OmegaConf.to_container(lightning_conf, resolve=True) - - # Start Mlflow run - with mlflow.start_run(description=log_conf['description']): - # Log hyperparameters - config_params = copy.copy(train_config) - config_params['cli.train_dataset'] = train_dataset - config_params['cli.ml_logs'] = ml_logs - config_params['cli.config'] = config - mlflow.log_params(flatten_dict(config_params)) - - # Save config file used for this specific training run - # for reproducibility - mlflow.log_artifact(config) - - # Update lightning MLFlow logger constructor args - # Infer MlFlow conf from pre-configured mlflow client - lightning_conf['trainer']['logger']['init_args'].update(dict( - experiment_name=mlflow.get_experiment( - mlflow.active_run().info.experiment_id - ).name, - tracking_uri=mlflow.get_tracking_uri(), - log_model='all', - run_id=mlflow.active_run().info.run_id, - save_dir=None - )) - # Append CSVLogger in front: - # https://github.com/Lightning-AI/lightning/issues/16310#issuecomment-1404177131 - csv_log_conf = dict( - class_path='lightning.pytorch.loggers.CSVLogger', - init_args=dict(save_dir='./.tmp') - ) - lightning_conf['trainer']['logger'] = [ - csv_log_conf, - lightning_conf['trainer']['logger'] - ] - - # Reset argv before using Lightning CLI - old_argv = sys.argv - sys.argv = ['some_script_placeholder.py'] - cli = LightningCLI( - args=lightning_conf, - model_class=ItwinaiBasePlModule, - datamodule_class=ItwinaiBasePlDataModule, - run=False, - save_config_kwargs={"overwrite": True, - "config_filename": "pl-training.yml"}, - subclass_mode_model=True, - subclass_mode_data=True - ) - print(cli.trainer.log_dir) - sys.argv = old_argv - # Train + validation, and test - cli.trainer.fit(cli.model, datamodule=cli.datamodule) - cli.trainer.test( - dataloaders=cli.datamodule, - datamodule=cli.datamodule, - ckpt_path='best' - ) - - # Save updated lightning conf as an mlflow artifact - mlflow.log_artifact( - os.path.join(cli.trainer.log_dir, "pl-training.yml") - ) - - -@app.command() -def predict( - input_dataset: str = typer.Option( - "unk", - help="Path to dataset of unseen data to make predictions." - ), - config: str = typer.Option( - "unk", - help="Path to inference configuration file." - ), - predictions_location: str = typer.Option( - "preds/", - help="Where to save predictions." - ), - ml_logs: str = typer.Option( - "ml-logs/", - help="Path to MLFLow logs." - ), -): - """ - Apply a pre-trained neural network to a set of unseen data. - """ - import logging - import mlflow - from mlflow.exceptions import MlflowException - from lightning.pytorch.cli import LightningCLI - from lightning.pytorch.trainer.trainer import Trainer - import torch - from omegaconf import DictConfig, OmegaConf - - from itwinai.utils import load_yaml_with_deps, load_yaml - from itwinai.plmodels.base import ( - ItwinaiBasePlModule, - ItwinaiBasePlDataModule - ) - - # Replicate args under cli field, to be used in imported config files - cli_conf = dict(cli=dict( - input_dataset=input_dataset, - config=config, - predictions_location=predictions_location, - ml_logs=ml_logs - )) - cli_conf = OmegaConf.create(cli_conf) - - os.makedirs(predictions_location, exist_ok=True) - ml_conf: DictConfig = load_yaml_with_deps(config) - ml_conf = OmegaConf.merge(ml_conf, cli_conf) - # print(OmegaConf.to_yaml(ml_conf)) - ml_conf = OmegaConf.to_container(ml_conf, resolve=True) - ml_conf = ml_conf['inference'] - - os.makedirs(predictions_location, exist_ok=True) - - # mlflow.set_tracking_uri('file:' + ml_logs) - mlflow.set_tracking_uri(ml_logs) - - # Check if run ID exists - try: - mlflow.get_run(ml_conf['run_id']) - # mlflow_client.get_run(ml_conf['run_id']) - except MlflowException: - logging.warning( - f"Run ID '{ml_conf['run_id']}' not found! " - "Using latest run available for experiment " - f"'{ml_conf['experiment_name']}' instead." - ) - runs = mlflow.search_runs( - experiment_names=[ml_conf['experiment_name']], - - ) - new_run_id = runs[runs.status == 'FINISHED'].iloc[0]['run_id'] - ml_conf['run_id'] = new_run_id - logging.warning(f"Using Run ID: '{new_run_id}'") - - # Download training configuration - train_conf_path = mlflow.artifacts.download_artifacts( - run_id=ml_conf['run_id'], - artifact_path=ml_conf['train_config_artifact_path'], - dst_path='tmp/', - tracking_uri=mlflow.get_tracking_uri() - ) - - # Download last ckpt - ckpt_path = mlflow.artifacts.download_artifacts( - run_id=ml_conf['run_id'], - artifact_path=ml_conf['ckpt_path'], - dst_path='tmp/', - tracking_uri=mlflow.get_tracking_uri() - ) - - # Instantiate PL model - lightning_conf = load_yaml(train_conf_path) - if ml_conf['conf'] is not None: - # Override training configuration with the one - # provided during inference. - # Example: predictions dataset is different - # from training dataset - lightning_conf.update(ml_conf['conf']) - - # Reset argv before using Lightning CLI - old_argv = sys.argv - sys.argv = ['some_script_placeholder.py'] - cli = LightningCLI( - args=lightning_conf, - model_class=ItwinaiBasePlModule, - run=False, - subclass_mode_model=True, - subclass_mode_data=True, - save_config_callback=None - ) - sys.argv = old_argv - - # Load best model - loaded_model = cli.model.load_from_checkpoint( - ckpt_path, - lightning_conf['model']['init_args'] - ) - - # Load Data module - loaded_data_module: ItwinaiBasePlDataModule = cli.datamodule - - trainer = Trainer(logger=cli.trainer.logger) - - # Predict - predictions = trainer.predict( - loaded_model, - datamodule=loaded_data_module - ) # , ckpt_path='best') - pred_class_names = loaded_data_module.preds_to_names( - torch.cat(predictions) - ) - - # Save list of predictions as class names - preds_file = os.path.join(predictions_location, 'predictions.txt') - with open(preds_file, 'w') as preds_file: - preds_file.write('\n'.join(pred_class_names)) - - -@app.command() -def mlflow_ui( - path: str = typer.Option( - "ml-logs/", - help="Path to logs storage." - ), -): - """ - Visualize Mlflow logs. - """ - import subprocess - subprocess.run(f"mlflow ui --backend-store-uri {path}".split()) - - -@app.command() -def datasets( - use_case: str = typer.Option( - "./use-cases/mnist", - help="Path to use case files." - ), -): - """List datasets of an use case.""" - from rich.console import Console - from rich.table import Table - from itwinai.utils import load_yaml - from pathlib import Path - from omegaconf import OmegaConf - - datasets_reg = load_yaml( - os.path.join(use_case, 'meta.yml') - ) - # datasets_reg = OmegaConf.create(datasets_reg) - datasets_reg = OmegaConf.to_container( - OmegaConf.create(datasets_reg), - resolve=True - ) - - rows = [] - columns = [ - "Name", - "Description", - "Location" - ] - for ds_name, ds_info in datasets_reg['datasets'].items(): - rows.append( - [ - ds_name, - ds_info['doc'], - ds_info['location'] - ] - ) - - use_case_dir = os.path.basename(Path(use_case)) - table = Table(title=f"Datasets registry for '{use_case_dir}' use case") - for column in columns: - table.add_column(column) - for row in rows: - table.add_row(*row, style='bright_green') - console = Console() - console.print(table) - - -@app.command() -def workflows( - use_case: str = typer.Option( - "./use-cases/mnist", - help="Path to use case files." - ), -): - """List workflows of an use case.""" - from omegaconf import OmegaConf - from rich.console import Console - from rich.table import Table - from pathlib import Path - from itwinai.utils import load_yaml_with_deps - use_case_dir = os.path.basename(Path(use_case)) - wf_files = filter(lambda itm: itm.endswith( - "-workflow.yml"), os.listdir(use_case)) - columns = [ - "Step", - "Description", - "Command", - "Env location", - "Env file" - ] - for workflow_file in wf_files: - wf = load_yaml_with_deps( - os.path.join(use_case, workflow_file) - ) - wf_name = workflow_file.split('.')[0] - rows = [] - for step in wf.steps: - step = OmegaConf.to_container(step, resolve=True) - step_name, step_data = list(step.items())[0] - rows.append([ - step_name, - step_data['doc'], - step_data['command'], - step_data['env']['prefix'], - step_data['env']['file'], - ]) - - table = Table(title=f"'{wf_name}' for '{use_case_dir}' use case") - for column in columns: - table.add_column(column) - for row in rows: - table.add_row(*row, style='bright_green') - console = Console() - console.print(table) - - -if __name__ == "__main__": - app() diff --git a/ai/src/itwinai/models/cnn.py b/ai/src/itwinai/models/cnn.py deleted file mode 100644 index 67e9da20..00000000 --- a/ai/src/itwinai/models/cnn.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Convolutional Neural Networks. -""" - -from torch import nn -import torch.nn.functional as F - - -class LeNetv5(nn.Module): - - def __init__(self): - super(LeNetv5, self).__init__() - self.conv1 = nn.Conv2d(1, 6, 5, padding=2) - self.conv2 = nn.Conv2d(6, 16, 5) - self.fc1 = nn.Linear(16 * 5 * 5, 120) - self.fc2 = nn.Linear(120, 84) - self.fc3 = nn.Linear(84, 10) - - def forward(self, x): - """ - One forward pass through the network. - - Args: - x: input - """ - x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) - x = F.max_pool2d(F.relu(self.conv2(x)), (2, 2)) - x = x.view(-1, self.num_flat_features(x)) - x = F.relu(self.fc1(x)) - x = F.relu(self.fc2(x)) - x = self.fc3(x) - return x diff --git a/ai/src/itwinai/plmodels/base.py b/ai/src/itwinai/plmodels/base.py deleted file mode 100644 index fe88963b..00000000 --- a/ai/src/itwinai/plmodels/base.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Base classes for pytorch lightning for itwinai. -""" - -from typing import Any, List -import lightning as L - - -class ItwinaiBasePlModule(L.LightningModule): - """ - Base class for pytorch lightning models for itwinai. - """ - pass - - -class ItwinaiBasePlDataModule(L.LightningDataModule): - """ - Base class for pytorch lightning models for itwinai. - """ - - def preds_to_names( - self, - preds: Any - ) -> List[str]: - """Convert predictions to class names.""" - raise NotImplementedError diff --git a/ai/src/itwinai/plmodels/mnist.py b/ai/src/itwinai/plmodels/mnist.py deleted file mode 100644 index ebd4c379..00000000 --- a/ai/src/itwinai/plmodels/mnist.py +++ /dev/null @@ -1,246 +0,0 @@ -""" -Pytorch Lightning models for MNIST dataset. -""" - -from typing import Callable, List, Optional, Union -from lightning.pytorch.utilities.types import EVAL_DATALOADERS -import torch -from torch import nn -from torch.nn import functional as F -from torch.utils.data import DataLoader, random_split -from torchmetrics import Accuracy -from torchvision import transforms -from torchvision.datasets import MNIST - -from .base import ItwinaiBasePlModule, ItwinaiBasePlDataModule - - -class MNISTDataModule(ItwinaiBasePlDataModule): - """Pytorch Lightning data module for MNIST dataset - - Args: - data_dir (str): path to dataset directory - batch_size (int, optional): batch size. Defaults to 32. - train_prop (float, optional): proportion of examples in the train - split, after dataset is split into train and validation. - Defaults to 0.7. - transform (Optional[Callable], optional): transformations to apply - to the loaded images. Defaults to None. - """ - - def __init__( - self, - data_dir: str, - batch_size: int = 32, - train_prop: float = 0.7, - transform: Optional[Callable] = None, - ) -> None: - super().__init__() - self.data_dir = data_dir - self.batch_size = batch_size - self.train_prop = train_prop - if transform is None: - self.transform = transforms.Compose( - [ - transforms.ToTensor(), - transforms.Normalize((0.1307,), (0.3081,)), - ] - ) - else: - self.transform = transform - - def setup(self, stage=None): - # Assign train/val datasets for use in dataloaders - if stage == "fit" or stage is None: - mnist_full = MNIST( - self.data_dir, train=True, - transform=self.transform, - download=False - ) - n_train_samples = int(self.train_prop * len(mnist_full)) - n_val_samples = len(mnist_full) - n_train_samples - self.mnist_train, self.mnist_val = random_split( - mnist_full, [n_train_samples, n_val_samples] - ) - - # Assign test dataset for use in dataloader(s) - if stage == "test" or stage is None: - self.mnist_test = MNIST( - self.data_dir, - train=False, - download=False, - transform=self.transform - ) - - if stage == "predict": - self.mnist_predict = MNIST( - self.data_dir, - train=False, - download=False, - transform=self.transform - ) - - def train_dataloader(self): - return DataLoader( - self.mnist_train, - batch_size=self.batch_size, - num_workers=4, - ) - - def val_dataloader(self): - return DataLoader( - self.mnist_val, - batch_size=self.batch_size, - num_workers=4, - ) - - def test_dataloader(self): - return DataLoader( - self.mnist_test, - batch_size=self.batch_size, - num_workers=4, - ) - - def predict_dataloader(self) -> EVAL_DATALOADERS: - return DataLoader( - self.mnist_predict, - batch_size=self.batch_size, - num_workers=4, - ) - - def preds_to_names( - self, - preds: Union[torch.Tensor, List[torch.Tensor]] - ) -> List[str]: - """Convert predictions to class names.""" - # Convert prediction to label: in this case is easy, as the label - # is an integer. - if not isinstance(preds, list): - preds = [preds] - - names = [] - for p in preds: - p += 1 - names.extend([str(el) for el in p.tolist()]) - return names - - -class LitMNIST(ItwinaiBasePlModule): - """ - Simple PL model for MNIST. - Adapted from - https://lightning.ai/docs/pytorch/stable/notebooks/lightning_examples/mnist-hello-world.html - """ - - def __init__( - self, - hidden_size: int = 64, - ): - super().__init__() - - # Automatically save constructor args as hyperparameters - self.save_hyperparameters() - - # Set our init args as class attributes - self.hidden_size = hidden_size - - # Hardcode some dataset specific attributes - self.num_classes = 10 - self.dims = (1, 28, 28) - channels, width, height = self.dims - self.transform = transforms.Compose( - [ - transforms.ToTensor(), - transforms.Normalize((0.1307,), (0.3081,)), - ] - ) - - # Define PyTorch model - self.model = nn.Sequential( - nn.Flatten(), - nn.Linear(channels * width * height, hidden_size), - nn.ReLU(), - nn.Dropout(0.1), - nn.Linear(hidden_size, hidden_size), - nn.ReLU(), - nn.Dropout(0.1), - nn.Linear(hidden_size, self.num_classes), - ) - - self.val_accuracy = Accuracy(task="multiclass", num_classes=10) - self.test_accuracy = Accuracy(task="multiclass", num_classes=10) - - def forward(self, x): - x = self.model(x) - return F.log_softmax(x, dim=1) - - def training_step(self, batch, batch_idx): - x, y = batch - logits = self(x) - loss = F.nll_loss(logits, y) - - # Log metrics with autolog - self.log( - "train_loss", - loss, - on_step=True, - on_epoch=True - ) - - # Good alternative - # # Log with generic logger - # self.logger.log_metrics( - # metrics=dict(train_loss=loss.item()), - # step=self.global_step - # ) - - return loss - - def validation_step(self, batch, batch_idx): - x, y = batch - logits = self(x) - loss = F.nll_loss(logits, y) - preds = torch.argmax(logits, dim=1) - self.val_accuracy.update(preds, y) - - self.log( - "val_loss", - loss, - prog_bar=True, - on_step=True, - on_epoch=True - ) - self.log( - "val_acc", - self.val_accuracy, - prog_bar=True, - on_step=True, - on_epoch=True - ) - - # good alternative - # # Log with generic logger - # self.logger.log_metrics( - # metrics=dict(val_loss=loss.item()), - # step=self.global_step - # ) - # self.logger.log_metrics( - # metrics=dict(val_acc=acc.item()), - # step=self.global_step - # ) - - def test_step(self, batch, batch_idx): - x, y = batch - logits = self(x) - loss = F.nll_loss(logits, y) - preds = torch.argmax(logits, dim=1) - self.test_accuracy.update(preds, y) - - self.log("test_loss", loss) - self.log("test_acc", self.test_accuracy) - - def predict_step(self, batch, batch_idx, dataloader_idx=0): - x, _ = batch - logits = self(x) - preds = torch.argmax(logits, dim=1) - return preds diff --git a/ai/tests/test_cli.py b/ai/tests/test_cli.py deleted file mode 100644 index de668466..00000000 --- a/ai/tests/test_cli.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -Test itwinai CLI. -""" - -import subprocess - - -def test_datasets_viz(): - """ - Test visualization of use case's dataset registry. - """ - USE_CASE = 'use-cases/mnist/' - subprocess.run( - f'itwinai datasets --use-case {USE_CASE}'.split(), - check=True - ) - - -def test_workflows_viz(): - """ - Test visualization of use case's workflows. - """ - USE_CASE = './use-cases/mnist/' - subprocess.run( - f'itwinai workflows --use-case {USE_CASE}'.split(), - check=True - ) diff --git a/ai/tests/test_utils.py b/ai/tests/test_utils.py deleted file mode 100644 index 3ce7681d..00000000 --- a/ai/tests/test_utils.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Tests for itwinai.utils module. -""" - -from itwinai.utils import flatten_dict - - -def test_flatten_dict(): - """ - Test flatten dict function. - """ - dict1 = dict( - a=1, - b=dict( - b1=2, - b2=3 - ) - ) - - flattened = flatten_dict(dict1) - assert flattened.get('a') == 1 - assert flattened.get('b.b1') == 2 - assert flattened.get('b.b2') == 3 - assert len(flattened) == 3 diff --git a/ai/training.cwl b/ai/training.cwl deleted file mode 100644 index f6954b80..00000000 --- a/ai/training.cwl +++ /dev/null @@ -1,68 +0,0 @@ -cwlVersion: v1.2 # Specifies the version of the Common Workflow Language (CWL) being used -class: CommandLineTool - -baseCommand: [conda, run] -# The command to be executed by the tool. It runs the 'conda' command with 'run' subcommand. - -requirements: - EnvVarRequirement: - envDef: - FILE_READ_BUFFER_SIZE: "10" -# The following requirement sets the environment variable 'FILE_READ_BUFFER_SIZE' to a value of 10. -# This requirement defines an environment variable requirement, specifying the value of the environment -# variable to be set. - -inputs: - trainingEnvironment: - type: Directory - inputBinding: - position: 1 - prefix: -p - # Specifies that the 'trainingEnvironment' input is a directory. - # The 'inputBinding' section provides additional information on how this input should be passed - # to the command line tool. 'position' specifies the position of the argument in the command line, - # and 'prefix' specifies the prefix to be used for the argument. - - trainingCommand: - type: string - inputBinding: - position: 2 - prefix: itwinai - # Specifies that the 'trainingCommand' input is a string. - # 'position' and 'prefix' are used to pass this input to the command line tool. - - trainingConfig: - type: File - inputBinding: - position: 3 - prefix: --config - # Specifies that the 'trainingConfig' input is a file. - # 'position' and 'prefix' are used to pass this input to the command line tool. - - preprocessedDatasetPath: - type: Directory - inputBinding: - position: 4 - prefix: --train-dataset - # Specifies that the 'preprocessedDatasetPath' input is a directory. - # 'position' and 'prefix' are used to pass this input to the command line tool. - - preprocessingFlag: - type: File - # Specifies that the 'preprocessingFlag' input is a file. - -outputs: - outputCheckpoint: - type: Directory - outputBinding: - glob: checkpoints - # Specifies that the 'outputCheckpoint' output is a directory. - # 'glob' specifies the glob pattern to find the output directory. - - mlLogs: - type: Directory - outputBinding: - glob: "ml-logs" - # Specifies that the 'mlLogs' output is a directory. - # 'glob' specifies the glob pattern to find the output directory. - diff --git a/dev-env.yml b/dev-env.yml deleted file mode 100644 index fbf12159..00000000 --- a/dev-env.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Resources: -# - https://pytorch.org/get-started/previous-versions/ -# - https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#creating-an-environment-file-manually - -name: ai-dev -channels: - # Order = priority! Don't mess this up. - - pytorch - - nvidia - - conda-forge -dependencies: - - python=3.9.12 - - pytorch::pytorch=1.13.1 - - pytorch::torchvision=0.14.1 - - pytorch-cuda=11.7 - # - pytorch::torchaudio=0.13.1 - # Either use pytorch-cuda or cpuonly, depending on GPU availability - # - pytorch::pytorch-cuda=11.6 - # - pytorch::cpuonly - - lightning=2.0.0 - - torchmetrics - - mlflow>=2 - - typer - - rich - - pyyaml - - omegaconf - - - typing-extensions=4.5.0 - - typing_extensions=4.5.0 - - # Pip dependencies should be given in the setup.py - - -# Dev dependencies - - ipython - - pytest - - flake8 - - conda-lock - - ipykernel diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index f55f6395..00000000 --- a/docs/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Copied from https://github.com/github/gitignore/blob/main/Jekyll.gitignore -# Ignore metadata generated by Jekyll -_site/ -.sass-cache/ -.jekyll-cache/ -.jekyll-metadata - -# Ignore folders generated by Bundler -.bundle/ -vendor/ diff --git a/docs/3dgan_doc.rst b/docs/3dgan_doc.rst new file mode 100644 index 00000000..a2104a86 --- /dev/null +++ b/docs/3dgan_doc.rst @@ -0,0 +1,401 @@ +3DGAN +===== + +This section covers the CERN use case that utilizes the `torch-lightning` framework for training and evaluation. Following you can find instructions to execute CERN use case and its integral scripts: + +itwinai x 3DGAN +--------------- + +First of all, from the repository root, create a torch environment: + +.. code-block:: bash + + make torch-gpu + + +Now, install custom requirements for 3DGAN: + +.. code-block:: bash + + micromamba activate ./.venv-pytorch + cd use-cases/3dgan + pip install -r requirements.txt + + +**NOTE**: Python commands below assumed to be executed from within the +micromamba virtual environment. + +Training +++++++++ + +Launch training using ``itwinai`` and the training configuration: + +.. code-block:: bash + + cd use-cases/3dgan + itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline + + # Or better: + micromamba run -p ../../.venv-pytorch/ torchrun --nproc_per_node gpu \ + itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline + + +To visualize the logs with MLFLow, if you set a local path as tracking URI, +run the following in the terminal: + +.. code-block:: bash + + micromamba run -p ../../.venv-pytorch mlflow ui --backend-store-uri LOCAL_TRACKING_URI + + +And select the "3DGAN" experiment. + +Inference ++++++++++ + +Disclaimer: the following is preliminary and not 100% ML/scientifically sound. + +1. As inference dataset we can reuse training/validation dataset, +for instance the one downloaded from Google Drive folder: if the +dataset root folder is not present, the dataset will be downloaded. +The inference dataset is a set of H5 files stored inside ``exp_data`` +sub-folders: + +:: + + ├── exp_data + │ ├── data + | │ ├── file_0.h5 + | │ ├── file_1.h5 + ... + | │ ├── file_N.h5 + + +2. As model, if a pre-trained checkpoint is not available, +we can create a dummy version of it with: + +.. code-block:: bash + + python create_inference_sample.py + + +3. Run inference command. This will generate a ``3dgan-generated-data`` +folder containing generated particle traces in form of torch tensors +(.pth files) and 3D scatter plots (.jpg images). + +.. code-block:: bash + + itwinai exec-pipeline --config config.yaml --pipe-key inference_pipeline + + +The inference execution will produce a folder called +``3dgan-generated-data`` containing +generated 3D particle trajectories (overwritten if already +there). Each generated 3D image is stored both as a +torch tensor (.pth) and 3D scatter plot (.jpg): + +:: + + ├── 3dgan-generated-data + | ├── energy=1.296749234199524&angle=1.272539496421814.pth + | ├── energy=1.296749234199524&angle=1.272539496421814.jpg + ... + | ├── energy=1.664689540863037&angle=1.4906378984451294.pth + | ├── energy=1.664689540863037&angle=1.4906378984451294.jpg + + +However, if ``aggregate_predictions`` in the ``ParticleImagesSaver`` step is set to ``True``, +only one pickled file will be generated inside ``3dgan-generated-data`` folder. +Notice that multiple inference calls will create new files under ``3dgan-generated-data`` folder. + +With fields overriding: + +.. code-block:: bash + + # Override variables + export CERN_DATA_ROOT="../.." # data root + export TMP_DATA_ROOT=$CERN_DATA_ROOT + export CERN_CODE_ROOT="." # where code and configuration are stored + export MAX_DATA_SAMPLES=20000 # max dataset size + export BATCH_SIZE=1024 # increase to fill up GPU memory + export NUM_WORKERS_DL=4 # num worker processes used by the dataloader to pre-fetch data + export AGGREGATE_PREDS="true" # write predictions in a single file + export ACCELERATOR="gpu" # choose "cpu" or "gpu" + export STRATEGY="auto" # distributed strategy + export DEVICES="0," # GPU devices list + + + itwinai exec-pipeline --print-config --config $CERN_CODE_ROOT/config.yaml \ + --pipe-key inference_pipeline \ + -o dataset_location=$CERN_DATA_ROOT/exp_data \ + -o logs_dir=$TMP_DATA_ROOT/ml_logs/mlflow_logs \ + -o distributed_strategy=$STRATEGY \ + -o devices=$DEVICES \ + -o hw_accelerators=$ACCELERATOR \ + -o checkpoints_path=\\$TMP_DATA_ROOT/checkpoints \ + -o inference_model_uri=$CERN_CODE_ROOT/3dgan-inference.pth \ + -o max_dataset_size=$MAX_DATA_SAMPLES \ + -o batch_size=$BATCH_SIZE \ + -o num_workers_dataloader=$NUM_WORKERS_DL \ + -o inference_results_location=$TMP_DATA_ROOT/3dgan-generated-data \ + -o aggregate_predictions=$AGGREGATE_PREDS + + +**Docker image** + +Build from project root with + +.. code-block:: bash + + # Local + docker buildx build -t itwinai:0.0.1-3dgan-0.1 -f use-cases/3dgan/Dockerfile . + + # Ghcr.io + docker buildx build -t ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.1 -f use-cases/3dgan/Dockerfile . + docker push ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.1 + + +You can run inference from wherever a sample of H5 files is available +(folder called ``exp_data/``'): + +:: + + ├── $PWD + | ├── exp_data + | │ ├── data + | | │ ├── file_0.h5 + | | │ ├── file_1.h5 + ... + | | │ ├── file_N.h5 + + +.. code-block:: bash + + docker run -it --rm --name running-inference -v "$PWD":/tmp/data ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.1 + + +This command will store the results in a folder called ``3dgan-generated-data``: + +:: + + ├── $PWD + | ├── 3dgan-generated-data + | │ ├── energy=1.296749234199524&angle=1.272539496421814.pth + | │ ├── energy=1.296749234199524&angle=1.272539496421814.jpg + ... + | │ ├── energy=1.664689540863037&angle=1.4906378984451294.pth + | │ ├── energy=1.664689540863037&angle=1.4906378984451294.jpg + + +To override fields in the configuration file at runtime, you can use the ``-o`` +flag. Example: ``-o path.to.config.element=NEW_VALUE``. + +Please find a complete exampled below, showing how to override default configurations +by setting some env variables: + +.. code-block:: bash + + # Override variables + export CERN_DATA_ROOT="/usr/data" + export CERN_CODE_ROOT="/usr/src/app" + export MAX_DATA_SAMPLES=10 # max dataset size + export BATCH_SIZE=64 # increase to fill up GPU memory + export NUM_WORKERS_DL=4 # num worker processes used by the dataloader to pre-fetch data + export AGGREGATE_PREDS="true" # write predictions in a single file + export ACCELERATOR="gpu" # choose "cpu" or "gpu" + + docker run -it --rm --name running-inference \ + -v "$PWD":/usr/data ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.1 \ + /bin/bash -c "itwinai exec-pipeline \ + --print-config --config $CERN_CODE_ROOT/config.yaml \ + --pipe-key inference_pipeline \ + -o dataset_location=$CERN_DATA_ROOT/exp_data \ + -o logs_dir=$TMP_DATA_ROOT/ml_logs/mlflow_logs \ + -o distributed_strategy=$STRATEGY \ + -o devices=$DEVICES \ + -o hw_accelerators=$ACCELERATOR \ + -o checkpoints_path=\\$TMP_DATA_ROOT/checkpoints \ + -o inference_model_uri=$CERN_CODE_ROOT/3dgan-inference.pth \ + -o max_dataset_size=$MAX_DATA_SAMPLES \ + -o batch_size=$BATCH_SIZE \ + -o num_workers_dataloader=$NUM_WORKERS_DL \ + -o inference_results_location=$TMP_DATA_ROOT/3dgan-generated-data \ + -o aggregate_predictions=$AGGREGATE_PREDS " + + +**How to fully exploit GPU resources** + +Keeping the example above as reference, increase the value of ``BATCH_SIZE`` as much as possible +(just below "out of memory" errors). Also, make sure that ``ACCELERATOR="gpu"``. Also, make sure +to use a dataset large enough by changing the value of ``MAX_DATA_SAMPLES`` to collect meaningful +performance data. Consider that each H5 file contains roughly 5k items, thus setting +``MAX_DATA_SAMPLES=10000`` should be enough to use all items in each input H5 file. + +You can try: + +.. code-block:: bash + + export MAX_DATA_SAMPLES=10000 # max dataset size + export BATCH_SIZE=1024 # increase to fill up GPU memory + export ACCELERATOR="gpu + + +**Singularity** + +Run Docker container with Singularity: + +.. code-block:: bash + + singularity run --nv -B "$PWD":/usr/data docker://ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.1 /bin/bash -c \ + "cd /usr/src/app && itwinai exec-pipeline --config config.yaml --pipe-key inference_pipeline" + + +Example with overrides (as above for Docker): + +.. code-block:: bash + + # Override variables + export CERN_DATA_ROOT="/usr/data" + export CERN_CODE_ROOT="/usr/src/app" + export MAX_DATA_SAMPLES=10 # max dataset size + export BATCH_SIZE=64 # increase to fill up GPU memory + export NUM_WORKERS_DL=4 # num worker processes used by the dataloader to pre-fetch data + export AGGREGATE_PREDS="true" # write predictions in a single file + export ACCELERATOR="gpu" # choose "cpu" or "gpu" + + singularity run --nv -B "$PWD":/usr/data docker://ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.1 /bin/bash -c \ + "cd /usr/src/app && itwinai exec-pipeline \ + --print-config --config $CERN_CODE_ROOT/config.yaml \ + --pipe-key inference_pipeline \ + -o dataset_location=$CERN_DATA_ROOT/exp_data \ + -o logs_dir=$TMP_DATA_ROOT/ml_logs/mlflow_logs \ + -o distributed_strategy=$STRATEGY \ + -o devices=$DEVICES \ + -o hw_accelerators=$ACCELERATOR \ + -o checkpoints_path=\\$TMP_DATA_ROOT/checkpoints \ + -o inference_model_uri=$CERN_CODE_ROOT/3dgan-inference.pth \ + -o max_dataset_size=$MAX_DATA_SAMPLES \ + -o batch_size=$BATCH_SIZE \ + -o num_workers_dataloader=$NUM_WORKERS_DL \ + -o inference_results_location=$TMP_DATA_ROOT/3dgan-generated-data \ + -o aggregate_predictions=$AGGREGATE_PREDS " + + +.. toctree:: + :maxdepth: 5 + +model.py +++++++++ + +.. literalinclude:: ../use-cases/3dgan/model.py + :language: python + + +trainer.py +++++++++++ +.. literalinclude:: ../use-cases/3dgan/trainer.py + :language: python + + +saver.py +++++++++ + +.. literalinclude:: ../use-cases/3dgan/saver.py + :language: python + + +dataloader.py ++++++++++++++ + +.. literalinclude:: ../use-cases/3dgan/dataloader.py + :language: python + + +config.yaml ++++++++++++ + +This YAML file defines the pipeline configuration for the CERN use case. + +.. literalinclude:: ../use-cases/3dgan/config.yaml + :language: yaml + + +create_inference_sample.py +++++++++++++++++++++++++++ + +This file defines a pipeline configuration for the CERN use case inference. + +.. literalinclude:: ../use-cases/3dgan/create_inference_sample.py + :language: python + + +Dockerfile +++++++++++ + +.. literalinclude:: ../use-cases/3dgan/Dockerfile + :language: bash + + +startscript ++++++++++++ + +.. literalinclude:: ../use-cases/3dgan/startscript + :language: bash + + + +This section covers the CERN use case integration with `interLink `_ using ``itwinai``. The following files are integral to this use case: + +interLink x 3DGAN +----------------- + +.. toctree:: + :maxdepth: 5 + + +3dgan-inference-cpu.yaml +++++++++++++++++++++++++ + +.. literalinclude:: ../use-cases/3dgan/interLink/3dgan-inference-cpu.yaml + :language: yaml + + +3dgan-inference.yaml +++++++++++++++++++++ + +.. literalinclude:: ../use-cases/3dgan/interLink/3dgan-inference.yaml + :language: yaml + + +3dgan-train.yaml +++++++++++++++++ + +.. literalinclude:: ../use-cases/3dgan/interLink/3dgan-train.yaml + :language: yaml + + + +.. .. automodule:: 3dgan.model +.. :members: +.. :undoc-members: +.. :show-inheritance: + +.. .. automodule:: 3dgan.train +.. :members: +.. :undoc-members: +.. :show-inheritance: + +.. .. automodule:: 3dgan.trainer +.. :members: +.. :undoc-members: +.. :show-inheritance: + +.. .. automodule:: 3dgan.saver +.. :members: +.. :undoc-members: +.. :show-inheritance: + +.. .. automodule:: 3dgan.dataloader +.. :members: +.. :undoc-members: +.. :show-inheritance: diff --git a/docs/404.html b/docs/404.html deleted file mode 100644 index b8547546..00000000 --- a/docs/404.html +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: default -title: 404 -permalink: /404 -nav_exclude: true -search_exclude: true ---- - -

Page not found

- -

The page you requested could not be found. Try using the navigation {% if site.search_enabled != false %}or search {% - endif %}to find what you're looking for or go to this site's home page.

\ No newline at end of file diff --git a/docs/Gemfile b/docs/Gemfile deleted file mode 100644 index 387154f8..00000000 --- a/docs/Gemfile +++ /dev/null @@ -1,7 +0,0 @@ -source 'https://rubygems.org' - -gem "jekyll", "~> 4.3" # installed by `gem jekyll` -# gem "webrick" # required when using Ruby >= 3 and Jekyll <= 4.2.2 - -gem "just-the-docs", "0.5.0" # pinned to the current release -# gem "just-the-docs" # always download the latest release diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock deleted file mode 100644 index 81efc419..00000000 --- a/docs/Gemfile.lock +++ /dev/null @@ -1,79 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - colorator (1.1.0) - concurrent-ruby (1.1.10) - em-websocket (0.5.3) - eventmachine (>= 0.12.9) - http_parser.rb (~> 0) - eventmachine (1.2.7) - ffi (1.15.5) - forwardable-extended (2.6.0) - http_parser.rb (0.8.0) - i18n (1.12.0) - concurrent-ruby (~> 1.0) - jekyll (4.3.0) - addressable (~> 2.4) - colorator (~> 1.0) - em-websocket (~> 0.5) - i18n (~> 1.0) - jekyll-sass-converter (>= 2.0, < 4.0) - jekyll-watch (~> 2.0) - kramdown (~> 2.3, >= 2.3.1) - kramdown-parser-gfm (~> 1.0) - liquid (~> 4.0) - mercenary (>= 0.3.6, < 0.5) - pathutil (~> 0.9) - rouge (>= 3.0, < 5.0) - safe_yaml (~> 1.0) - terminal-table (>= 1.8, < 4.0) - webrick (~> 1.7) - jekyll-sass-converter (2.2.0) - sassc (> 2.0.1, < 3.0) - jekyll-seo-tag (2.8.0) - jekyll (>= 3.8, < 5.0) - jekyll-watch (2.2.1) - listen (~> 3.0) - just-the-docs (0.5.0) - jekyll (>= 3.8.5) - jekyll-seo-tag (>= 2.0) - rake (>= 12.3.1) - kramdown (2.4.0) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - liquid (4.0.3) - listen (3.7.1) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - mercenary (0.4.0) - pathutil (0.16.2) - forwardable-extended (~> 2.6) - public_suffix (5.0.0) - rake (13.0.6) - rb-fsevent (0.11.2) - rb-inotify (0.10.1) - ffi (~> 1.0) - rexml (3.2.5) - rouge (4.0.0) - safe_yaml (1.0.5) - sassc (2.4.0) - ffi (~> 1.9) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - unicode-display_width (2.3.0) - webrick (1.7.0) - -PLATFORMS - arm64-darwin-21 - x86_64-darwin-19 - x86_64-linux - -DEPENDENCIES - jekyll (~> 4.3) - just-the-docs (= 0.5.0) - -BUNDLED WITH - 2.3.9 diff --git a/docs/LICENSE b/docs/LICENSE deleted file mode 100644 index 7d510d02..00000000 --- a/docs/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 just-the-docs - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 4a405acd..00000000 --- a/docs/README.md +++ /dev/null @@ -1,181 +0,0 @@ -# just-the-docs-template - -This is a *bare-minimum* template to create a [Jekyll] site that: - -- uses the [Just the Docs] theme; -- can be built and published on [GitHub Pages]; -- can be built and previewed locally, and published on other platforms. - -More specifically, the created site: - -- uses a gem-based approach, i.e. uses a `Gemfile` and loads the `just-the-docs` gem; -- uses the [GitHub Pages / Actions workflow] to build and publish the site on GitHub Pages. - -To get started with creating a site, just click "[use this template]"! - -If you want to maintain your docs in the `docs` directory of an existing project repository, see -[Hosting your docs from an existing project repository](#hosting-your-docs-from-an-existing-project-repository). - -After completing the creation of your new site on GitHub, update it as needed: - -## Replace the content of the template pages - -Update the following files to your own content: - -- `index.md` (your new home page) -- `README.md` (information for those who access your site repository on GitHub) - -## Changing the version of the theme and/or Jekyll - -Simply edit the relevant line(s) in the `Gemfile`. - -## Adding a plugin - -The Just the Docs theme automatically includes the [`jekyll-seo-tag`] plugin. - -To add an extra plugin, you need to add it in the `Gemfile` *and* in `_config.yml`. For example, to add [`jekyll-default-layout`]: - -- Add the following to your site's `Gemfile`: - - ```ruby - gem "jekyll-default-layout" - ``` - -- And add the following to your site's `_config.yml`: - - ```yaml - plugins: - - jekyll-default-layout - ``` - -Note: If you are using a Jekyll version less than 3.5.0, use the `gems` key instead of `plugins`. - -## Publishing your site on GitHub Pages - -1. If your created site is `YOUR-USERNAME/YOUR-SITE-NAME`, update `_config.yml` to: - - ```yaml - title: YOUR TITLE - description: YOUR DESCRIPTION - theme: just-the-docs - - url: https://YOUR-USERNAME.github.io/YOUR-SITE-NAME - - aux_links: # remove if you don't want this link to appear on your pages - Template Repository: https://github.com/YOUR-USERNAME/YOUR-SITE-NAME - ``` - -2. Push your updated `_config.yml` to your site on GitHub. - -3. In your newly created repository on GitHub: - - go to the `Settings` tab -> `Pages` -> `Build and deployment`, then select `Source`: `GitHub Actions`. - - if there were any failed Actions, go to the `Actions` tab and click on `Re-run jobs`. - -## Building and previewing your site locally - -Assuming [Jekyll] and [Bundler] are installed on your computer: - -1. Change your working directory to the root directory of your site. - -2. Run `bundle install`. - -3. Run `bundle exec jekyll serve` to build your site and preview it at `localhost:4000`. - - The built site is stored in the directory `_site`. - -## Publishing your built site on a different platform - -Just upload all the files in the directory `_site`. - -## Customization - -You're free to customize sites that you create with this template, however you like! - -[Browse our documentation][Just the Docs] to learn more about how to use this theme. - -## Hosting your docs from an existing project repository - -You might want to maintain your docs in an existing project repository. Instead of creating a new repository using -the [just-the-docs template](https://github.com/just-the-docs/just-the-docs-template), you can copy the template -files into your existing repository and configure the template's GitHub Actions workflow to -build from a `docs` directory. You can clone the template to your local machine or download the `.zip` file -to access the files. - -### Copy the template files - -1. Create a `.github/workflows` directory at your project root if your repository doesn't already have one. -Copy the `pages.yml` file into this directory. GitHub Actions searches this directory for workflow files. - -2. Create a `docs` directory at your project root and copy all remaining template files into this directory. - -### Modify the GitHub Actions workflow - -The GitHub Actions workflow that builds and deploys your site to GitHub Pages is defined by the `pages.yml` file. -You'll need to edit this file to that so that your build and deploy steps look to your `docs` directory, -rather than the project root. - -1. Set the default `working-directory` param for the build job. - - ```yaml - build: - runs-on: ubuntu-latest - defaults: - run: - working-directory: docs - ``` - -2. Set the `working-directory` param for the Setup Ruby step. - - ```yaml - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.1' - bundler-cache: true - cache-version: 0 - working-directory: '${{ github.workspace }}/docs' - ``` - -3. Set the path param for the Upload artifact step: - - ```yaml - - name: Upload artifact - uses: actions/upload-pages-artifact@v1 - with: - path: "docs/_site/" - ``` - -4. Modify the trigger so that only changes within the `docs` directory start the workflow. -Otherwise, every change to your project (even those that don't affect the docs) would trigger a new site build and deploy. - - ```yaml - on: - push: - branches: - - "main" - paths: - - "docs/**" - ``` - -## Licensing and Attribution - -This repository is licensed under the [MIT License]. You are generally free to reuse or extend upon this code as you -see fit; just include the original copy of the license (which is preserved when you "make a template"). -While it's not necessary, we'd love to hear from you if you do use this template, and how we can improve it for future use! - -The deployment GitHub Actions workflow is heavily based on GitHub's mixed-party [starter workflows]. -A copy of their MIT License is available in [actions/starter-workflows]. - ----- - -[Jekyll]: https://jekyllrb.com -[Just the Docs]: https://just-the-docs.github.io/just-the-docs/ -[GitHub Pages]: https://docs.github.com/en/pages -[GitHub Pages / Actions workflow]: https://github.blog/changelog/2022-07-27-github-pages-custom-github-actions-workflows-beta/ -[Bundler]: https://bundler.io -[use this template]: https://github.com/just-the-docs/just-the-docs-template -[`jekyll-default-layout`]: https://github.com/benbalter/jekyll-default-layout -[`jekyll-seo-tag`]: https://jekyll.github.io/jekyll-seo-tag -[MIT License]: https://en.wikipedia.org/wiki/MIT_License -[starter workflows]: https://github.com/actions/starter-workflows/blob/main/pages/jekyll.yml -[actions/starter-workflows]: https://github.com/actions/starter-workflows/blob/main/LICENSE diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index f5182469..00000000 --- a/docs/_config.yml +++ /dev/null @@ -1,26 +0,0 @@ -title: itwinai -description: Docs for task T6.5 prototype of interTwin project -theme: just-the-docs - -url: https://interTwin-eu.github.io/itwinai - -aux_links: - Template Repository: https://github.com/interTwin-eu/itwinai - -favicon_ico: "favicon.ico" - -nav_external_links: - - title: itwinai on GitHub - url: https://github.com/interTwin-eu/itwinai - hide_icon: false # set to true to hide the external link icon - defaults to false - - title: interTwin on GitHub - url: https://github.com/interTwin-eu/ - hide_icon: false # set to true to hide the external link icon - defaults to false - - title: interTwin project - url: https://www.intertwin.eu/ - hide_icon: false # set to true to hide the external link icon - defaults to false - -mermaid: - # Version of mermaid library - # Pick an available version from https://cdn.jsdelivr.net/npm/mermaid/ - version: "10.1.0" diff --git a/docs/advanced_workflow.rst b/docs/advanced_workflow.rst new file mode 100644 index 00000000..add65297 --- /dev/null +++ b/docs/advanced_workflow.rst @@ -0,0 +1,15 @@ +Advanced workflow +================= + +.. tutorial_2_advanced_workflow.py +.. +++++++++++++++++++++++++++++++ + + +.. .. automodule:: tutorial_2_advanced_workflow +.. :members: +.. :undoc-members: +.. :show-inheritance: + + +.. literalinclude:: ../tutorials/ml-workflows/tutorial_2_advanced_workflow.py + :language: python diff --git a/docs/basic_comp.rst b/docs/basic_comp.rst new file mode 100644 index 00000000..54475269 --- /dev/null +++ b/docs/basic_comp.rst @@ -0,0 +1,13 @@ +Example of pipeline components +============================== + +.. basic_components.py +.. +++++++++++++++++++ +.. literalinclude:: ../tutorials/ml-workflows/basic_components.py + :language: python + +.. .. automodule:: basic_components +.. :members: +.. :undoc-members: +.. :show-inheritance: + diff --git a/docs/basic_workflow.rst b/docs/basic_workflow.rst new file mode 100644 index 00000000..f52217ae --- /dev/null +++ b/docs/basic_workflow.rst @@ -0,0 +1,15 @@ +Basic workflow +============== + +.. tutorial_0_basic_workflow.py +.. ++++++++++++++++++++++++++++ +.. .. literalinclude:: ../tutorials/ml-workflows/tutorial_0_basic_workflow.py + :language: python + +.. literalinclude:: ../tutorials/ml-workflows/tutorial_0_basic_workflow.py + :language: python + +.. .. automodule:: tutorial_0_basic_workflow +.. :members: +.. :undoc-members: +.. :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..7db77a35 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,87 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +import os +import sys +import subprocess + +exclude_patterns = 'requirements.txt' + +# sys.path.insert(0, os.path.abspath('../use-cases/')) +# sys.path.insert(0, os.path.abspath('../use-cases/3dgan/')) +# sys.path.insert(0, os.path.abspath('../use-cases/mnist/torch-lightning/')) +# sys.path.insert(0, os.path.abspath('../use-cases/mnist/torch/')) +sys.path.insert(0, os.path.abspath('../tutorials/ml-workflows/')) +# sys.path.insert(0, os.path.abspath('../tutorials/distributed-ml/')) +# sys.path.insert(0, os.path.abspath('../src/itwinai')) +# sys.path.insert(0, os.path.abspath('../src/itwinai/tensorflow')) +# sys.path.insert(0, os.path.abspath('../src/itwinai/torch')) +sys.path.insert(0, os.path.abspath('../src')) +# sys.path.insert(0, os.path.abspath('../...')) + +project = 'itwinai' +copyright = ('2024, Matteo Bunino, Kalliopi Tsolaki,' + 'Rakesh Sarma, Mario Ruettgers on behalf of CERN & JSC') +author = 'Matteo Bunino, Kalliopi Tsolaki, Rakesh Sarma, Mario Ruettgers' +version = '0.0' # short version +release = '0.0.2' # full version + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', + 'sphinx.ext.viewcode', 'myst_parser', 'nbsphinx'] + +source_suffix = { + '.rst': 'restructuredtext', + '.txt': 'markdown', + '.md': 'markdown' # , + # '.ipynb': 'nbsphinx' +} + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +autodoc_mock_imports = ["mlflow"] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + + +def get_git_tag(): + try: + return subprocess.check_output( + ['git', 'describe', '--tags', '--abbrev=0'] + ).decode('utf-8').strip() + except subprocess.CalledProcessError: + return 'unknown' + + +# Set the version to the latest tag +version = get_git_tag() +release = version + +html_theme = 'sphinx_rtd_theme' # 'alabaster' +html_static_path = ['_static'] + +html_context = { + 'display_version': True, + 'release': release +} + +html_footer = """ + +""" + +html_sidebars = { + '**': [ + html_footer # Adds the custom footer with version information + ] +} diff --git a/docs/cyclones_doc.rst b/docs/cyclones_doc.rst new file mode 100644 index 00000000..913a9531 --- /dev/null +++ b/docs/cyclones_doc.rst @@ -0,0 +1,98 @@ +Tropical Cyclones Detection +=========================== + +The code is adapted from the CMCC use case's +`repository `_. + +Setup env ++++++++++ + +.. code-block:: bash + + # After activating the environment + pip install -r requirements.txt + + +Dataset ++++++++ + +If the automatic download from python does not work, try from the command line from +within the virtual environment: + +.. code-block:: bash + + gdown https://drive.google.com/drive/folders/1TnmujO4T-8_j4bCxqNe5HEw9njJIIBQD -O data/tmp_data/trainval --folder + + +For more info visit the `gdown `_ repository. + +Training +++++++++ + +Launch training: + +.. code-block:: bash + + # # ONLY IF tensorflow>=2.16 + # export TF_USE_LEGACY_KERAS=1 + + source ../../.venv-tf/bin/activate + python train.py -p pipeline.yaml + + +On JSC, the dataset is pre-downloaded and you can use the following command: + +.. code-block:: bash + + # # ONLY IF tensorflow>=2.16 + # export TF_USE_LEGACY_KERAS=1 + + source ../../envAItf_hdfml/bin/activate + python train.py -p pipeline.yaml --data_path /p/project/intertwin/smalldata/cmcc + + # Launch a job with SLURM + sbatch startscript.sh + + + +cyclones_vgg.py ++++++++++++++++ + +.. literalinclude:: ../use-cases/cyclones/dataloader.py + :language: python + + +dataloader.py ++++++++++++++ + +.. literalinclude:: ../use-cases/cyclones/dataloader.py + :language: python + + +pipeline.yaml ++++++++++++++ + +This YAML file defines the pipeline configuration for the CMCC use case. + +.. literalinclude:: ../use-cases/cyclones/pipeline.yaml + :language: yaml + + +startscript ++++++++++++ + +.. literalinclude:: ../use-cases/cyclones/startscript.sh + :language: bash + + +trainer.py +++++++++++ +.. literalinclude:: ../use-cases/cyclones/trainer.py + :language: python + + +train.py +++++++++++ +.. literalinclude:: ../use-cases/cyclones/train.py + :language: python + diff --git a/docs/ddp_why.rst b/docs/ddp_why.rst new file mode 100644 index 00000000..c82b2d5a --- /dev/null +++ b/docs/ddp_why.rst @@ -0,0 +1,30 @@ +Distributed Data Parallelism +================================= +Deep neural networks (DNN) are often extremely large and are trained on massive amounts of data, more than most computers have memory for. +Even smaller DNNs can take days to train. +Distributed Data Parallelisation (DDP) adresses these two issues, long training times and limited memory, by using multiple machines to host and train both model and data. + +Data parallelisation is an easy way for a developer to vastly reduce training times. +Rather than using single-node parallelism, Distributed Data Parallelism (DDP) scales to multiple machnies. +This scaling maximises parallelisability of your model and drastically reduces training times. + +Another benefit of DDP is removal of single-machine memory constraints. Since a dataset or model can be stored across several machines, it becomes possible to analyse much larger datasets or models. + +Below is a list of resources expanding on theoretical aspects and practical implementations of DDP: + +* Introduction to DP: https://siboehm.com/articles/22/data-parallel-training + +* https://pytorch.org/tutorials/beginner/ddp_series_theory.html + +* https://pytorch.org/tutorials/intermediate/ddp_tutorial.html + +* https://huggingface.co/blog/pytorch-ddp-accelerate-transformers + + +Investigation of expected performance improvement: + +* https://www.mdpi.com/2079-9292/11/10/1525 + + + + diff --git a/docs/docs/CLI.md b/docs/docs/CLI.md deleted file mode 100644 index a2383b46..00000000 --- a/docs/docs/CLI.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -layout: default -title: CLI -nav_order: 3 ---- - -# Command-line interface (CLI) - - -The `itwinai` package provides a custom CLI, which can be accessed, for instance -from the development environment: - -```bash -# Activate development environment -micromamba activate ./.venv-dev - -# Access itwinai CLI -itwinai --help -``` - -## Visualization - -Some visualization functionalities offered by `itwinai` CLI. - -```bash -# Datasets registry -itwinai datasets --help - -# Workflows (any file '*-workflow.yml') -itwinai workflows --help -``` - -## Machine learning - -```bash -# Training -itwinai train --help - -# Launch MLFlow UI to visualize ML logs -itwinai mlflow-ui --help - -# Inference -itwinai predict --help -``` diff --git a/docs/docs/Concepts.md b/docs/docs/Concepts.md deleted file mode 100644 index 5830fcfe..00000000 --- a/docs/docs/Concepts.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: default -title: Concepts -nav_order: 4 ---- - -# Concepts - - -Here we presents the key concepts on which `itwinai` is based. - -## Workflow - -We define a workflow as a directed acyclic graph (DAG) of data processing -operations, in which each step can have multiple inputs and outputs, and -each input or output is a dataset. - -![image](img/Workflow%20DAG%20concept.png) - -In the picture above, the yellow boxes with numbers represent the steps -in the example workflow, whereas the blue cylinders represent data -(e.g., dataset, configuration file). - -Each step runs in *isolation* from the others, and data is the *interface*. -Isolation can be guaranteed by executing each step in a Docker container or -in a separate Python virtual environment. diff --git a/docs/docs/How-to-use-this-software.md b/docs/docs/How-to-use-this-software.md deleted file mode 100644 index 15a36b44..00000000 --- a/docs/docs/How-to-use-this-software.md +++ /dev/null @@ -1,540 +0,0 @@ ---- -layout: default -title: How to use this software -nav_order: 2 ---- - -# How to use this software -{: .no_toc } - -## Table of contents -{: .no_toc .text-delta } - -1. TOC -{:toc} - ---- - -This guide provides a detailed explanation on how to use the AI/ML workflow -tool, developed in the context of [interTwin](https://github.com/interTwin-eu/). - -**Target audience**: anyone aiming to simplify MLOps for their digital twin (DT) -use case/project. Use cases from interTwin project. - -## Clone this repo - -```bash -git clone git@github.com:interTwin-eu/T6.5-AI-and-ML.git -``` - -A new use case/project can be added under `./use-cases` folder. - -Build the workflow runner environment and development environment -following the instructions on the README file. - -## Define a DT workflow - -Before delving into workflow definition rules, make sure to have -understood *what is* a [workflow](./Concepts#workflow) in this context. - -You can define one or more workflows for your DT use case (e.g., ML training, -ML inference, other). A workflow is -defined through configuration files in the use case subfolder. -For the same use case, a DT developer can define multiple workflows, -in which multiple datasets are involved. - -Currently, each step is executed in an isolated Python virtual environment, -built according to [conda](https://docs.conda.io/en/latest/) standards. -In practice, it is built with -[Micromamba](https://mamba.readthedocs.io/en/latest/user_guide/micromamba.html), -which is faster. - -To begin with, you can start by looking at an example of the -[MNIST toy use case](use-cases/mnist), located at `use-cases/mnist` -in the code repository. - -### Use case metadata - -The main configuration file of an use case is `meta.yml`, which stores -the metadata of it. When creating a new use case, you need to update the -`root` field with the path to the use case folder, with respect to the -repo root. - -The datasets registry is a field in this configuration file, -which stores the metadata -for all datasets involved in a use case. This configuration provides a -unified place where datasets can be maintained, making it easy to access -them from other configuration files. - -The dataset registry has the format: - -```yaml -datasets: - some-dataset-name: - doc: Documentation string for this dataset - location: path/to/dataset/disk/location -``` - -Example of `meta.yml` from [MNIST use case](use-cases/mnist): - -```yaml -# Use case root location. End without path '/' char! -root: ./use-cases/mnist - -# AI folder location. End without path '/' char! -ai-root: ./ai - -# Datasets registry -datasets: - preproc-images: - doc: Preprocessed MNIST images - location: ${root}/data/preproc-images - ml-logs: - doc: MLflow tracking URI for local logging - location: ${root}/data/ml-logs - ml-predictions: - doc: predictions on unseen data - location: ${root}/data/ml-predictions -``` - -Datasets are imported from the datasets registry to other files by means -of [OmegaConf](https://omegaconf.readthedocs.io/)'s -[variable interpolation](https://omegaconf.readthedocs.io/en/2.3_branch/usage.html#variable-interpolation). -This way, you can easily import datasets metadata (e.g., location on -file system) from datasets registry. - -Dataset registry of an use case can be visualized using [itwinai CLI](./CLI#visualization): - -```bash -USE_CASE_ROOT='use-cases/mnist/' -micromamba activate ./.venv-dev && \ - itwinai datasets --use-case $USE_CASE_ROOT -``` - -### Workflow configuration - -Use case workflows are defined with `*-workflow.yml` files in the use case root, -and there are two ways to define a workflow: - -- "Custom" format for workflow definition is an intuitive standard we created -for this prototype, for easy prototyping. -- [Common Workflow Language](https://www.commonwl.org/) (CWL), which is -currently under development, and not ready to be used. - -Which of the two is used is defined by setting the `--cwl` flag (explained -[below](#run-the-workflow)). - -#### Custom workflow definition - -To define a workflow with the custom format, the DT developer must follow -the structure provided below. - -The `steps` section defines the steps of the workflow, in the order in which -they have to be executed: - -```yaml -steps: - - some-step-name: - doc: Documentation string for this step - env: # micromamba environment metadata - file: some-conda-env.yml - prefix: path/to/conda/env/ - command: Command to execute inside micromamba env - args: # Command arguments. - # Note interpolation with datasets registry here! - some-arg: ${datasets.my-dataset.location} - some-other-arg: 42 - - next-step-name: - ... -``` - -Example workflow from [MNIST use case](use-cases/mnist), defined in -`training-workflow.yml`: - -```yaml -steps: - - preprocessing: - doc: Download and split MNIST dataset into train and test sets - command: python ${root}/mnist-preproc.py - env: - file: ${root}/env-files/preproc-env.yml - prefix: ${root}/.venv-preproc - args: - output: ${datasets.preproc-images.location} - stage: train - - ml-training: - doc: Train a neural network to classify MNIST images - command: itwinai train - env: - file: ${ai-root}/env-files/pytorch-lock.yml - prefix: ${ai-root}/.venv-pytorch - source: ${ai-root} - args: - train-dataset: ${datasets.preproc-images.location} - ml-logs: ${datasets.ml-logs.location} - config: ${root}/mnist-ai-train.yml -``` - -Step 1 is named `preprocessing` and uses the `mnist-preproc.py` script to pre-process the MNIST dataset. It takes no -input, generates an output dataset named `preproc-images`, and uses an environment defined in a YAML file named -`preproc-env.yml` located in the `./use-cases/mnist` directory. -Step 2 is named `ml-training` and trains a machine learning model using the preprocessed image data generated in -the first step. The training is performed using the train command from the `itwinai` tool. The input dataset is -`preproc-images`, and the output is `ml-logs`. The step uses an environment defined in a YAML file named -`pytorch-env.yml` located in the `./ai` directory. The machine learning model is configured using the `mnist-ai-train.yml` -file located in the `./use-cases/mnist` directory. - -#### CWL: under development and not ready to be used yet - -**NOTE**. At the moment, support for CWL is under development, -and is not available. - - - -## Implement workflow steps - -Implement use case-specific steps. -Note that the implementation of steps involving AI/ML are addressed in the next -step, and they can be implemented a bit more easily. - -Each step of a workflow is characterized by its python virtual environment -and a command to be executed in that -environment. A command can be implemented by providing, for instance, a python script to be executed in some environment. - -To execute a step, the workflow engine will run something like: - -```bash -micromamba run -p PATH_TO_STEP_ENV CMD --arg1 ARG_1_VAL ... --argN ARG_N_VAL -``` - -Where: - -- `PATH_TO_STEP_ENV` is the path to the micromamba environment for this step. -- `CMD` is the command to execute in that environment. -- The developer can use additional parameters which are automatically appended -to the command. - -*Example*: in the [MNIST use case](use-cases/mnist), -the preprocessing step is implemented by a python script, which downloads and -splits the MNIST dataset in a specific location. Using a command similar to: - -```bash -micromamba run -p ./use-cases/mnist/.venv-preproc \ - python ./use-cases/mnist/mnist-preproc.py \ - --output ./data/mnist/preproc-images-inference \ - --stage train -``` - -## Define AI/ML workflow - -AI/ML workflows are implemented by the `itwinai` module. -The DT developer, who wants to include a new use case, needs to provide -only a reduced amount of code to describe a neural network, plus some -configuration files. - -The developer must implement the neural network to train and include it inside -`itwinai` python package, under `ai/src/itwinai`. For instance, under -`ai/src/itwinai/plmodels` when using PyTorch Lightning. - -For instance, `LitMNIST` neural network used in [MNIST use case](use-cases/mnist) -has been added under `ai/src/itwinai/plmodels/mnist.py` - -Once a model has been included inside the `itwinai` python module, it can be imported during training. -In the future, `itwinai` will support also neural networks not provided out-of-the-box by `itwinai`. - -The developer must define two configuration files to access `itwinai` -functionalities. -First, ML training configuration, associated with `$ itwinai train` [CLI](./CLI) command. -Second, ML inference configuration, associated with `$ itwinai predict` [CLI](./CLI) command. - -MLOps heavily relies on commands provided by [itwinai CLI](./CLI). -Therefore, before continuing, make sure to have understood how -[itwinai CLI](./CLI) works. - -### ML training configuration - -ML training configuration is provided in a with naming convention `*-ai-train.yml` -under the use case root directory. - -An example configuration file is provided below, where the fields have been replaced with their respective description: - -```yaml -# Configuration file of AI workflows for X use case - -# Training configuration -train: - type: > - can be 'lightning' or 'tf', depending whether the neural network is defined - using PyTorch Lightning or TensorFlow. - At the moment, only 'lightning' is supported. - - # Configuration format defined by PyTorch Lightning CLI - # https://pytorch-lightning.readthedocs.io/en/1.6.5/common/lightning_cli.html - conf: - # See discussion below - ... - -# MLFlow logger configuration -logger: - experiment_name: > - Unique name for an experiment, to group all similar - runs under the same experiment - description: Description for this specific run. - log_every_n_epoch: how often to log (epochs) - log_every_n_steps: how often to log (steps, i.e., batches) - registered_model_name: > - Unique name used in Models Registry to identify an ML model. - If given, it is automatically registered in the Models Registry. -``` - -When using PyTorch Lightning (PL) ML framework, the training configuration is easy to define, as it follows the schema -pre-defined by lightning authors for the PL CLI. See its documentation -[here](https://pytorch-lightning.readthedocs.io/en/1.6.5/common/lightning_cli.html#trainer-callbacks-and-arguments-with-class-type), -[here](https://pytorch-lightning.readthedocs.io/en/1.6.5/common/lightning_cli.html#trainer-callbacks-and-arguments-with-class-type), -[here](https://pytorch-lightning.readthedocs.io/en/1.6.5/common/lightning_cli.html#multiple-models-and-or-datasets), and -[here](https://pytorch-lightning.readthedocs.io/en/1.6.5/common/lightning_cli.html#optimizers-and-learning-rate-schedulers). - -An example taken from -[MNIST use case](use-cases/mnist) located at `use-cases/mnist/mnist-ai-training.yml`: - -```yaml -# Pytorch lightning config for training -train: - type: lightning - # Follows lightning config file format: - # https://pytorch-lightning.readthedocs.io/en/1.6.5/common/lightning_cli.html#multiple-models-and-or-datasets - conf: - seed_everything: 4231162351 - - # Lightning Trainer configuration - trainer: - accelerator: auto - strategy: auto - devices: auto - num_nodes: 1 - precision: 32-true - - # MLFlow logger (initial) configuration. - # To be completed with run details, later on - logger: - class_path: lightning.pytorch.loggers.MLFlowLogger - init_args: - experiment_name: ${logger.experiment_name} - save_dir: ./mlruns - - # Callbacks - callbacks: - - class_path: lightning.pytorch.callbacks.early_stopping.EarlyStopping - init_args: - monitor: val_loss - patience: 2 - - class_path: lightning.pytorch.callbacks.lr_monitor.LearningRateMonitor - init_args: - logging_interval: step - - class_path: lightning.pytorch.callbacks.ModelCheckpoint - init_args: - dirpath: checkpoints - filename: best-checkpoint - save_top_k: 1 - verbose: true - monitor: val_loss - mode: min - - max_epochs: 1 - - # Lightning Model configuration - model: - class_path: itwinai.plmodels.mnist.LitMNIST - init_args: - hidden_size: 64 - - # Lightning data module configuration - data: - class_path: itwinai.plmodels.mnist.MNISTDataModule - init_args: - data_dir: ${cli.train_dataset} - batch_size: 32 - - # Torch Optimizer configuration - optimizer: - class_path: torch.optim.AdamW - init_args: - lr: 0.001 - - # Torch LR scheduler configuration - lr_scheduler: - class_path: torch.optim.lr_scheduler.ExponentialLR - init_args: - gamma: 0.1 - -# Mlflow -logger: - experiment_name: MNIST classification lite - description: A MLP classifier for MNIST dataset. - log_every_n_epoch: 1 - log_every_n_steps: 1 - # Name used in Models Registry. If given, it is automatically - # registered in the Models Registry. - registered_model_name: MNIST-clf-lite -``` - -Note the field `data_dir: ${cli.train_dataset}` in the above configuration. -More on this later. - -### ML inference configuration - -ML training configuration is provided in a with naming convention -`*-ai-inference.yml` under the use case root directory. - -An example configuration file is provided below, where the fields have been replaced with their respective description: - -```yaml -inference: - experiment_name: > - Unique name for an experiment, to group all similar - runs under the same experiment - run_id: Run ID in MLFlow server of pre-trained model - ckpt_path: model/checkpoints/best-checkpoint/best-checkpoint.ckpt - train_config_artifact_path: name of training config saved to MLFlow artifacts folder - type: > - can be 'lightning' or 'tf', depending whether the neural network is defined - using PyTorch Lightning or TensorFlow. - At the moment, only 'lightning' is supported. - - # Configuration format defined by PyTorch Lightning CLI - # https://pytorch-lightning.readthedocs.io/en/1.6.5/common/lightning_cli.html - conf: - # See discussion below - ... -``` - -Regarding the `inference.conf` field, same considerations hold as for `train.conf` field of ML training configuration. - -An example taken from -[MNIST use case](use-cases/mnist) located at `use-cases/mnist/mnist-ai-training.yml`: - -```yaml -inference: - type: lightning - experiment_name: MNIST classification lite - # Run ID in MLFlow server: pre-trained model - run_id: 54f790100be646e0a7ccbb1235729d00 - ckpt_path: model/checkpoints/best-checkpoint/best-checkpoint.ckpt - train_config_artifact_path: pl-training.yml - conf: - # Lightning data module configuration - data: - class_path: itwinai.plmodels.mnist.MNISTDataModule - init_args: - data_dir: ${cli.input_dataset} - batch_size: 32 -``` - -### Accessing CLI args from config file - -As explained above, train and predict commands in itwinai CLI receive as input -specific configuration files: - -- The `train` command receives `*-ai-train.yml` as configuration. -- The `predict` command receives `*-ai-inference.yml` as configuration. - -With [OmegaConf](https://omegaconf.readthedocs.io/)'s -[variable interpolation](https://omegaconf.readthedocs.io/en/2.3_branch/usage.html#variable-interpolation) -you can access the args from the itwinai CLI command from the configuration file -associated with this command. - -Example: the field `data_dir: ${cli.input_dataset}` in the above configuration -accesses the value of `--input-dataset` argument of `itwinai predict` command. - -### ML framework: PyTorch vs. TensorFlow - -At the moment, only PyTorch are supported. TensorFlow support is planned for -future releases. - -## Run the workflow - -Once a workflow has been configured, it can be run by executing `run-workflow.py` in the root of this repo: - -```bash -micromamba run -p ./.venv python run-workflow.py -f WORKFLOW_DEFINITION_FILE -``` - -This script performs two main actions: - -1. Deploy ste steps of a workflow as python environments, managed with Conda. -2. Run a workflow step-by-step, following the directives given in the config file. - -See some examples of workflow executions in `examples.sh`, for instance: - -```bash -# Run workflow for MNIST toy use case -micromamba run -p ./.venv python run-workflow.py -f ./use-cases/mnist/training-workflow.yml -``` - - - -## Write tests cases - -Integrating an new use case means defining new workflows for it. -It is strongly suggested to define "integration" test cases for -those workflows. This way, every time `itwinai` -framework is updated, integration tests automatically verify that -the use case integrates well with the new changes introduced in the -main framework. -Moreover, integration tests verify that an use case case is stable, -and is not hiding some "bug". - -Add test for your use case under the `test/` folder. You can take -inspiration from other use cases' tests. diff --git a/docs/docs/img/Workflow DAG concept.png b/docs/docs/img/Workflow DAG concept.png deleted file mode 100644 index f09d8146dfe9d5a1c53588430d214b1a0b779b21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224511 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

Eg&*2Z{> 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

i@NV+o>U;V6}xe^^j4-P-(iDe!6g2X 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 diff --git a/docs/docs/img/cwl-workflow.png b/docs/docs/img/cwl-workflow.png deleted file mode 100644 index 9c434e3fe5a68bb38f5243021afad62e682fb1d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 375527 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

  • eMMd>z=z&BZIqdC@O|1rGr~r7j`%& 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 <}Sa%Ascn?j^Re)@k{yE%vZsbL& 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⪙1#T)U9)N%*qYfh6bOdQA_l3XGNxO8 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 z�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%0ARH(2W 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 diff --git a/docs/docs/img/user-platform interaction.png b/docs/docs/img/user-platform interaction.png deleted file mode 100644 index 19dc3eeb9d67edf86b6a3bdb011651764be1e550..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30005 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 diff --git a/docs/docs/use-cases/index.md b/docs/docs/use-cases/index.md deleted file mode 100644 index 681caeb6..00000000 --- a/docs/docs/use-cases/index.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: default -title: Use cases -nav_order: 4 -has_children: true ---- - -# Use cases - -Digital twin use cases employing machine learning workflows, currently integrated -in this prototype. -To integrate a new use case, please refer to [this page](../How-to-use-this-software). diff --git a/docs/docs/use-cases/mnist.md b/docs/docs/use-cases/mnist.md deleted file mode 100644 index e22f7ab1..00000000 --- a/docs/docs/use-cases/mnist.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -layout: default -title: MNIST -parent: Use cases -nav_order: 1 ---- - -# MNIST: toy example for DT workflows -{: .no_toc } - -## Table of contents -{: .no_toc .text-delta } - -1. TOC -{:toc} - ---- - -Of course MNIST images classification is not a digital twin. Still, it is useful to -provide an example on how to define an end-to-end digital twin workflow with the -software provided in this repository. - -The MNIST use case implements two workflows: - -1. Training workflow: train a neural network to classify MNIST images, and save the trained -neural network to the Models Registry. - - ```mermaid - flowchart LR - %% Nodes - preproc(Pre-processing) - ai(ML training) - reg[(Models Registry:\npre-trained ML models)] - - %% Workflow - preproc --> ai - - %% Connections - ai -.-> |Saves to| reg - ``` - - This workflow is executed by running the command: - - ```bash - micromamba run -p ./.venv python run-workflow.py -f ./use-cases/mnist/training-workflow.yml - ``` - -1. Inference workflow: use the pre-trained neural network to classify unseen images (the test set, in this case). - - ```mermaid - flowchart LR - %% Nodes - preproc(Pre-processing) - ai_depl(ML inference) - pred[(Predictions)] - - %% Workflow - preproc --> ai_depl - - %% Connections - ai_depl -.-> |Saves to| pred - ``` - - This workflow is executed by running the command: - - ```bash - micromamba run -p ./.venv python run-workflow.py -f ./use-cases/mnist/inference-workflow.yml - ``` - -The interactions among workflows and their steps can be described in more details as the following, where conceptual ordering -among different workflow steps is represented by solid arrows: - -```mermaid -graph TD - %% Nodes - remote_repo[(Remote repo)] - preproc(Pre-processing) - ai(ML training) - ai_depl(ML inference) - train_set[(Train dataset)] - test_set[(Test dataset)] - ml_logs[(ML logs)] - reg[(Models Registry:\npre-trained ML models)] - pred[(Predictions)] - - %% Workflow - preproc --> ai ---> ai_depl - - %% Connections - preproc -.-> |Fetches| remote_repo - preproc -.-> |Stores| train_set - preproc -.-> |Stores| test_set - ai -.-> |Trains/validates model| train_set - ai -.-> |Tests model| test_set - ai -.-> |Stores model to| reg - ai -.-> |Logs| ml_logs - ai_depl -.-> |Loads from| reg - ai_depl -.-> |Predict from| test_set - ai_depl -.-> |Stores| pred -``` - -## Workflow steps - -Here we explain in more details how the workflow steps have been configured. -Configuration files and Python scripts are organized under `use-cases/mnist/` -folder, in the core repository. - -### Pre-processing - -This step is implemented by executing `mnist-preproc.py` script in its dedicated micromamba environment, defined by -`preproc-env.yml`. This solution gives full freedom to the DT developer to implement any preprocessing logic, adaptable -to any custom dataset format. - -### ML training - -Is the step in which a neural network is trained on the training dataset, and -validated on the validation dataset. -The mentioned datasets are a result from a split of the pre-processed training -dataset, produced by the pre-processing step. -This step completes the **training workflow**, and results into ML logs and a -trained neural network, which is saved to -the Models Registry. The training workflow can be re-run multiple times with different (hyper)parameters, with the goal -of optimizing some ML validation metric. The neural network with the best validation performances is used to make -predictions on unseen data, in the inference step. - -ML training logic is implemented by the `itwinai` library, requiring the DT developer to produce only a set of YAML -configuration files. For the moment, we assume that the neural network and the training code is already present in -`itwinai` library. - -Both ML training and inference are implemented by commands executed in the same virtual environment. At the moment, -only PyTorch is supported. The corresponding virtual environment definition, used by the `itwinai` library, -is located at `ai/pytorch-env.yml`. - -The ML training logic provided by `itwinai` library is accessed via the -[itwinai CLI](../CLI). - -The DT developer must provide a training configuration file, following some -rules explained in [this section](../How-to-use-this-software#ml-training-configuration). For MNIST use case, the -training configuration is provided by `mnist-ai-train.yml` file. - -Training command is automatically managed by the workflow runner, but it can also -be triggered from withing the ai environment running the following command: - -```bash -micromamba activate ./ai/.venv-pytorch && \ - itwinai train --train-dataset $TRAINING_DATASET_PATH \ - --ml-logs $MLFLOW_TRACKING_URI \ - --config ./use-cases/mnist/mnist-ai-train.yml -``` - -While training is running, the produced ML logs can be inspected in real-time from MLFlow UI by running the command in -the training virtual environment (Conda): - -```bash -micromamba activate ./ai/.venv-pytorch && \ - itwinai mlflow-ui --path $PATH_TO_ML_LOGS -``` - -### ML inference - -A pre-trained neural network is applied to a set of data which was not used to train it. In fact, this is defined as -"unseen" data, from the neural network perspective. An example of ML inference is the application of a trained neural -network to make predictions on new data, to support decision making. *Example*: forecast fire risk maps in Sicily in -August 2023, starting from newly-collected satellite images, to alert local authorities in case of elevated fire risk. - -To select a pre-trained ML model, the DT developer must retrieve the `RUN ID` of -the training run created by MLFLow for some specific training. - -The, the DT developer can update the inference configuration file -`mnist-ai-inference.yml` and run inference workflow. - -Inference/prediction command is automatically managed by the workflow runner, but it can also be triggered from within -the ai environment running the following command: - -```bash -micromamba activate ./ai/.venv-pytorch && \ - itwinai predict --input-dataset $UNSEEN_EXAMPLES_DATASET_PATH \ - --predictions-location $PREDICTIONS_LOCATION \ - --ml-logs $PATH_TO_ML_LOGS \ - --config ./use-cases/mnist/mnist-ai-inference.yml -``` - -## References - -To learn more on how to use this software, e.g., to deploy a new use case, please refer to [this guide](../How-to-use-this-software). diff --git a/docs/favicon.ico b/docs/favicon.ico deleted file mode 100644 index 1665919207a78e8012a58e4425fd3fb90f51c3f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15406 zcmeHNU1(fI6rR~MB$33l52nTjS+R)~MN*bZkd`ipL`oD96-sF-rKz?rqDAaO!TL}e z4Q*Al6#q6=@Q)&GL{P+neUQFbL8`GTt)eqv`yT1xmezvd+#-eTPzCU8mF%w1EgD z(&b2k(o!_s5B?E6nPEP&jCEerfQr|_(_ryn*0BxST8a6iU@tjmIon!wP^YtvU!F1~ z_Gt#Q)}rR$AgZ<@E8o!2AZ=}JqFFa%hcafY(&gP>zk6U$^aTe9oBI!!N{BWEk>Rgiowq@C}#YMoj;gQ`J@?>psDd6$`|XuMhA7y_@Wt?nbhn3 zp}ZmElg2kV$9(pCe~o-zDc6}OI1c9eR03*-jy2#nz{kNOVD_8LXO^*!ZP>O{bPE(E zz!Tu0KESq=p=_aa4Fvaqr@%q&I@?i}I;blj!fv5XfSvjZ%~Kb32B8=lod*|c%S(T z?csXIccPbX6a|}+NJKVo-Yln2pU&>%d>9=amHz&IX=-Zn!a z_#ySY@m=}g;72yQ&dkuzkStoXC|7@a4AuYx*KB)zq(=1CBeUDi#6eFUz3McXlRtLs znAFtN*lnb3n!UtUr(+KXvduA=_sW?3a^>$Fo6dsOt5@4>Wcqv@Dfcd0o$h@vuzh0K zef&?Sxt#o=Lx*fOGB(W~YOmY=O?BDjf%G$5?_|pe${jae9 zRr~$p8er>xDqp$vr`qc;x|@m>bH~7pOL_Mn+OPKhOAOVFKWM+&@fR^vGybG~QZ?i6 zG=lUvlrk31=ljE8pg+(ji-}7qOC4_UHi`og-^~QrJw`NZggq_A_oow_i4{%EWMYSM ze}6l1{HyXjC&3Y|S`RMKISDl7Py{6lz&C??zF)(#jf}$`o06qieSzA!NUhSxZx(2{ea5+drDQ*eW zQ{W`n3rn%()J+?-MVqCfI23&bF2#8J^^rDcD-JG}I0lb`&w;s~`q?g3J#En@Z5KKZ z^^ko6Tuy)S>o08+1F_^o>@(#qg8giiyPg<`C7*fVyj64Dont~Q#NnX8X<0ZyVj6nk9*7etlzV&X=cdZw_qE{lf-M)~Y&V0ESc#d$_?0oBzu(WR6Mf@S(Q9ABef=M~XQ6vWB(mv4(YOy#b&Xj} zjg5`6a^*@H930Hv?OP9G$H&Lzz<~o2i^Zg_t}ZJFOkU5#O3cK*0L(bnt8Kcx`Fhd! z?-1QLoQpeiW^T^$b^kfhkL*qTuYvz#*tc(=jE#+1A-g`0jEu;xUAv^UwRLtZX{^Ld z?EH>6J1hPM_{y67qV`_&#cukbUc4mg@y}%N^Dkv;YRVO_TjAv7q$CoFAU;LR=DW$e zzd9oNo~@!@x)L0_Wj-GIWI~emzjV04wv|m!Ps_G#+fsKxvySV`H=if|$W6;c_n$2Y zJLjTle^TE0VM+igFQUJ7b#-~-%Ji!bDZfeG+ul+rx(s z`^|y*)^^38x>J1i`=ar;zL}JtNB!@joD(!WJS-g@9j@3iW9~dBu(j2J+3aUSSP=fIV`6DY;~owdJlWVkN*pOF^7&(Q{L#lfW_ z&I8w0?hobkC+emR+M>;JP!viBz>JqlF(1@HT?61KxDv$JBL)uR|2M4TW6s~rIWWIP Y&6%F{T!YVZ>Hjm`_, build the environment containing pytorch, horovod, and deepspeed. You can try with: + +.. code-block:: bash + + # Creates a Python environment called envAI_hdfml + make torch-gpu-jsc + + +Distributed training +++++++++++++++++++++ + + Each distributed strategy is described with a SLURM job script used to run that strategy. + +So if you want to distribute the code in `train.py` with, for example, **torch DDP**, run from terminal: + +.. code-block:: bash + + sbatch ddp_slurm.sh + +Similarly, if you want to distribute the code in `train.py` with **DeepSpeed**, run from terminal: + +.. code-block:: bash + + sbatch deepspeed_slurm.sh + +To distribute the code in `train.py` with **Horovod**, run from terminal: + +.. code-block:: bash + + sbatch hvd_slurm.sh + +Finally, you can run all of them with: + +.. code-block:: bash + + bash runall.sh + + + + + +💻 Local systems +----------------- + +**Requirements** + +* Linux environment. + +Windows and macOS were never tested. + + +Micromamba installation ++++++++++++++++++++++++ + +To manage Conda environments we use micromamba, a lightweight version of Conda. + +In order to install micromamba, please refer to the `Manual installation guide `_. + +Consider that Micromamba can eat a lot of space when building environments because packages are cached on the local filesystem after being downloaded. To clear cache, you can use `micromamba clean -a`. +Micromamba data are kept under the `$HOME` location. However, in some systems, `$HOME` has a limited storage space so it is recommended to install Micromamba in another location with more storage space by changing the `$MAMBA_ROOT_PREFIX` variable. +Below is a complete installation example where the default `$MAMBA_ROOT_PREFIX` is overridden for Linux: + + +.. code-block:: bash + + cd $HOME + + # Download micromamba (This command is for Linux Intel (x86_64) systems. Find the right one for your system!) + curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bin/micromamba + + # Install micromamba in a custom directory + MAMBA_ROOT_PREFIX='my-mamba-root' + ./bin/micromamba shell init $MAMBA_ROOT_PREFIX + + # To invoke micromamba from Makefile, you need to add explicitly to $PATH + echo 'PATH="$(dirname $MAMBA_EXE):$PATH"' >> ~/.bashrc + +**Reference**: `Micromamba installation guide `_. + + +Environment setup ++++++++++++++++++ + +**Requirements:** + +* Linux environment. Windows and macOS were never tested. +* Micromamba: see the installation instructions above. +* VS Code, for development. + +Tensorflow +++++++++++ + +Installation: + +.. code-block:: bash + + # Install TensorFlow 2.13 + make tf-2.13 + + # Activate env + micromamba activate ./.venv-tf + +Other TensorFlow versions are available, using the following targets `tf-2.10`, and `tf-2.11`. + + +PyTorch (+ Lightning) ++++++++++++++++++++++ + +Installation: + +.. code-block:: bash + + # Install PyTorch + lightning + make torch-gpu + + # Activate env + micromamba activate ./.venv-pytorch + +Other similarly CPU-only version is available at the target `torch-cpu`. + + +Development environment ++++++++++++++++++++++++ + +This is for developers only. To have it, update the installed `itwinai` package adding the `dev` extra: + +.. code-block:: bash + + pip install -e .[dev] + + +**Test with `pytest`** +To run tests on itwinai package: + +.. code-block:: bash + + # Activate env + micromamba activate ./.venv-pytorch # or ./.venv-tf + + pytest -v -m "not slurm" tests/ + + +However, some tests are intended to be executed only on HPC systems, where SLURM is available. They are marked with "slurm" tags. To run these tests, use the dedicated job script: + +.. code-block:: bash + + sbatch tests/slurm_tests_startscript + + # Upon completion, check the output: + cat job.err + cat job.out + + + + diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 4ea5cd09..00000000 --- a/docs/index.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: Overview -layout: home -nav_order: 1 ---- - -# Overview - -Welcome to the `itwinai` docs! A framework for advanced AI/ML workflows in digital twins (DTs). - -Below we you are going to find an overview of interTwin's AI/ML workflows component. This platform -is intended to support general-purpose MLOps for digital twin use cases in [interTwin](https://www.intertwin.eu/) project. - -Additional resources include: - -- Detailed instructions on [How to use this software](docs/How-to-use-this-software). -- Roadmap towards a prototype for T6.5 AI workflows for -digital twins here: [Prototype for T6.5](https://github.com/interTwin-eu/T6.5-AI-and-ML/wiki/Prototype-for-T6.5). - -## Platform for machine learning workflows in digital twins - -The goal of this platform is to provide ML researchers with an easy-to-use endpoint -to manage general-purpose machine learning (ML) workflows, with limited engineering overhead, -while providing state-of-the-art MLOps best practices. - -We call this platform `itwinai`. - -The user is going to provide as input a set of configuration files, to fully -describe ML workflows, in the context of digital twin (DT) applications. -`itwinai` platform instantiates ML workflows with the configurations -provided by the DT developer. The execution of ML workflows produces as output a -set of ML metrics, which are visualized by `itwinai` via -[MLFlow](https://mlflow.org/). -As a result of ML training, the best model (on validation dataset) -is saved to the *Models Registry* for future predictions. - -![image](docs/img/user-platform%20interaction%20full.png) - -### Simulating a whole DT workflow - -A DT workflow is more than ML. Generally speaking, MLOps -(i.e., ML model lifecycle management), -can be considered just as a step of a larger DT workflow. - -![image](docs/img/cwl-workflow.png) - -In `itwinai` platform, we focus mainly on the MLOps step, simulating or oversimplifying all the rest -(e.g., pre-processing, authentication, workflow execution). - -For further details on how to define a DT workflow in `itwinai`, follow [this guide](docs/How-to-use-this-software#2-define-a-dt-workflow). - -### How to integrate a new use case - -To integrate an existing use case in this platform, the ML engineer rewrites -the ML experiments according to a format supported by `itwinai`. -Some examples can be found by looking at the use cases -already integrated [here](docs/use-cases/index), located under `use-cases/` -in the code repository. - -A detailed guide on how to integrate a new use case in `itwinai` can be found [here](docs/How-to-use-this-software). diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..eb1b3ba8 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,81 @@ +.. itwinai documentation master file, created by + sphinx-quickstart on Fri Feb 9 13:58:30 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to itwinai's documentation! +=================================== + +``itwinai`` is a framework for advanced AI/ML workflows in Digital Twins (DTs). + +This platform is intended to support general-purpose MLOps for Digital Twin use cases in the `interTwin `_ project. + +Platform for machine learning workflows in digital twins +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +The goal of this platform is to provide ML researchers with an easy-to-use endpoint to manage general-purpose ML workflows, +with limited engineering overhead, while providing state-of-the-art MLOps best practices. + +The user can fully describe ML workflows for DT applications by providing a set of configuration files as input. +The ``itwinai`` platform instantiates ML workflows with the configurations provided by the DT developer. +The execution of ML workflows outputs a set of ML metrix, which are visualised by ``itwinai`` via `MLFlow `_. +The trained ML model that performed best on the validation dataset is saved to the Models Registry for future predictions. + +In ``itwinai`` platform, we focus mainly on the MLOps step, simulating or oversimplifying the rest (e.g., pre-processing, authentication, workflow execution). + + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: 💡 Installation + + getting_started_with_itwinai + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: 🪄 Itwinai how it works + + ddp_why + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: 📚 Integrated Use Cases + + use_cases + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: 🚀 Tutorials + + tutorials + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: ⚡ Python API reference + + modules + +.. .. toctree:: +.. :maxdepth: 2 +.. :hidden: +.. :caption: Additional resources + +.. notebooks/example + + +`interTwin Demo: itwinai integration with other DTE modules `_ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +Indices and tables +++++++++++++++++++ + +* :ref:`genindex` +* :ref:`modindex` + +.. * :ref:`search` + diff --git a/docs/intermediate_workflow.rst b/docs/intermediate_workflow.rst new file mode 100644 index 00000000..8b7016cb --- /dev/null +++ b/docs/intermediate_workflow.rst @@ -0,0 +1,17 @@ +Intermediate workflow +===================== + +.. tutorial_1_intermediate_workflow.py +.. +++++++++++++++++++++++++++++++++++ + + + +.. .. automodule:: tutorial_1_intermediate_workflow +.. :members: +.. :undoc-members: +.. :show-inheritance: + +.. literalinclude:: ../tutorials/ml-workflows/tutorial_1_intermediate_workflow.py + :language: python + + diff --git a/docs/itwinai.cli.rst b/docs/itwinai.cli.rst new file mode 100644 index 00000000..18e0d0ef --- /dev/null +++ b/docs/itwinai.cli.rst @@ -0,0 +1,7 @@ +itwinai.cli +=========== + +.. automodule:: itwinai.cli + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/itwinai.components.rst b/docs/itwinai.components.rst new file mode 100644 index 00000000..db3b0956 --- /dev/null +++ b/docs/itwinai.components.rst @@ -0,0 +1,8 @@ +itwinai.components +================== + +.. automodule:: itwinai.components + :members: + :undoc-members: + :show-inheritance: + diff --git a/docs/itwinai.distributed.rst b/docs/itwinai.distributed.rst new file mode 100644 index 00000000..26810535 --- /dev/null +++ b/docs/itwinai.distributed.rst @@ -0,0 +1,7 @@ +itwinai.distributed +=================== + +.. automodule:: itwinai.distributed + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/itwinai.loggers.rst b/docs/itwinai.loggers.rst new file mode 100644 index 00000000..513e3942 --- /dev/null +++ b/docs/itwinai.loggers.rst @@ -0,0 +1,8 @@ +itwinai.loggers +=============== + +.. automodule:: itwinai.loggers + :members: + :undoc-members: + :show-inheritance: + diff --git a/docs/itwinai.parser.rst b/docs/itwinai.parser.rst new file mode 100644 index 00000000..f9c7d930 --- /dev/null +++ b/docs/itwinai.parser.rst @@ -0,0 +1,8 @@ +itwinai.parser +============== + +.. automodule:: itwinai.parser + :members: + :undoc-members: + :show-inheritance: + diff --git a/docs/itwinai.pipeline.rst b/docs/itwinai.pipeline.rst new file mode 100644 index 00000000..b849240e --- /dev/null +++ b/docs/itwinai.pipeline.rst @@ -0,0 +1,8 @@ +itwinai.pipeline +================ + +.. automodule:: itwinai.pipeline + :members: + :undoc-members: + :show-inheritance: + diff --git a/docs/itwinai.serialization.rst b/docs/itwinai.serialization.rst new file mode 100644 index 00000000..691a3721 --- /dev/null +++ b/docs/itwinai.serialization.rst @@ -0,0 +1,8 @@ +itwinai.serialization +===================== + +.. automodule:: itwinai.serialization + :members: + :undoc-members: + :show-inheritance: + diff --git a/docs/itwinai.tf.modules.rst b/docs/itwinai.tf.modules.rst new file mode 100644 index 00000000..e380e275 --- /dev/null +++ b/docs/itwinai.tf.modules.rst @@ -0,0 +1,37 @@ +itwinai Tensorflow Modules +========================== + + +trainer.py ++++++++++++ + +.. .. literalinclude:: ../src/itwinai/tensorflow/trainer.py + :language: python + +.. automodule:: itwinai.tensorflow.trainer + :members: + :undoc-members: + :show-inheritance: + +utils.py +++++++++ + +.. automodule:: itwinai.tensorflow.utils + :members: + :undoc-members: + :show-inheritance: + +.. .. literalinclude:: ../src/itwinai/tensorflow/utils.py + :language: python + + +distributed.py +++++++++++++++ + +.. automodule:: itwinai.tensorflow.distributed + :members: + :undoc-members: + :show-inheritance: + +.. .. literalinclude:: ../src/itwinai/tensorflow/distributed.py + :language: python diff --git a/docs/itwinai.torch.modules.rst b/docs/itwinai.torch.modules.rst new file mode 100644 index 00000000..95f31277 --- /dev/null +++ b/docs/itwinai.torch.modules.rst @@ -0,0 +1,63 @@ +itwinai PyTorch Modules +======================= + +distributed.py +++++++++++++++ +.. automodule:: itwinai.torch.distributed + :members: + :undoc-members: + :show-inheritance: + + +inference.py +++++++++++++ +.. automodule:: itwinai.torch.inference + :members: + :undoc-members: + :show-inheritance: + + +mlflow.py ++++++++++ +.. automodule:: itwinai.torch.mlflow + :members: + :undoc-members: + :show-inheritance: + + +trainer.py +++++++++++ +.. automodule:: itwinai.torch.trainer + :members: + :undoc-members: + :show-inheritance: + + +types.py +++++++++ +.. automodule:: itwinai.torch.types + :members: + :undoc-members: + :show-inheritance: + + +reproducibility.py +++++++++++++++++++ +.. automodule:: itwinai.torch.reproducibility + :members: + :undoc-members: + :show-inheritance: + + +config.py +++++++++++++++++++ +.. automodule:: itwinai.torch.config + :members: + :undoc-members: + :show-inheritance: + + + + + + diff --git a/docs/itwinai.type.rst b/docs/itwinai.type.rst new file mode 100644 index 00000000..dd3a22a7 --- /dev/null +++ b/docs/itwinai.type.rst @@ -0,0 +1,8 @@ +itwinai.type +============= + +.. automodule:: itwinai.type + :members: + :undoc-members: + :show-inheritance: + diff --git a/docs/itwinai.utils.rst b/docs/itwinai.utils.rst new file mode 100644 index 00000000..b487da7f --- /dev/null +++ b/docs/itwinai.utils.rst @@ -0,0 +1,8 @@ +itwinai.utils +============= + +.. automodule:: itwinai.utils + :members: + :undoc-members: + :show-inheritance: + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..32bb2452 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/mnist_doc.rst b/docs/mnist_doc.rst new file mode 100644 index 00000000..708995a9 --- /dev/null +++ b/docs/mnist_doc.rst @@ -0,0 +1,298 @@ +MNIST +===== + +This section covers the MNIST use case, which utilizes the `torch-lightning` framework for training and evaluation. The following files are integral to this use case: + +Torch Lightning +--------------- + +**Training** + +.. code-block:: bash + + # Download dataset and exit: only run first step in the pipeline (index=0) + itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline --steps 0 + + # Run the whole training pipeline + itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline + + +View training logs on MLFLow server (if activated from the configuration): + +.. code-block:: bash + + mlflow ui --backend-store-uri mllogs/mlflow/ + + +.. toctree:: + :maxdepth: 5 + +dataloader.py ++++++++++++++ + +The `dataloader.py` script is responsible for loading the MNIST dataset and preparing it for training. + +.. literalinclude:: ../use-cases/mnist/torch-lightning/dataloader.py + :language: python + +.. .. automodule:: torch-lightning.dataloader +.. :members: +.. :undoc-members: +.. :show-inheritance: + +config.yaml ++++++++++++ + +This YAML file defines the pipeline configuration for the MNIST use case. It includes settings for the model, training, and evaluation. + +.. literalinclude:: ../use-cases/mnist/torch-lightning/config.yaml + :language: yaml + +startscript ++++++++++++ + +The `startscript` is a shell script to initiate the training process. It sets up the environment and starts the training using the `train.py` script. + +.. literalinclude:: ../use-cases/mnist/torch-lightning/startscript + :language: bash + + +utils.py +++++++++ + +The `utils.py` script includes utility functions and classes that are used across the MNIST use case. + +.. literalinclude:: ../use-cases/mnist/torch-lightning/utils.py + :language: python + + + +This section covers the MNIST use case, which utilizes the `torch` framework for training and evaluation. The following files are integral to this use case: + +PyTorch +------- + +**Training** + +.. code-block:: bash + + # Download dataset and exit + itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline --steps dataloading_step + + # Run the whole training pipeline + itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline + + +View training logs on MLFLow server (if activated from the configuration): + +.. code-block:: bash + + mlflow ui --backend-store-uri mllogs/mlflow/ + + +**Inference** + +1. Create sample dataset + + .. code-block:: python + + from dataloader import InferenceMNIST + InferenceMNIST.generate_jpg_sample('mnist-sample-data/', 10) + + +2. Generate a dummy pre-trained neural network + + .. code-block:: python + + import torch + from model import Net + dummy_nn = Net() + torch.save(dummy_nn, 'mnist-pre-trained.pth') + + +3. Run inference command. This will generate a "mnist-predictions" folder containing a CSV file with the predictions as rows. + + .. code-block:: bash + + itwinai exec-pipeline --config config.yaml --pipe-key inference_pipeline + + +Note the same entry point as for training. + +Docker image +++++++++++++ + +Build from project root with + +.. code-block:: bash + + # Local + docker buildx build -t itwinai:0.0.1-mnist-torch-0.1 -f use-cases/mnist/torch/Dockerfile . + + # Ghcr.io + docker buildx build -t ghcr.io/intertwin-eu/itwinai:0.0.1-mnist-torch-0.1 -f use-cases/mnist/torch/Dockerfile . + docker push ghcr.io/intertwin-eu/itwinai:0.0.1-mnist-torch-0.1 + + +**Training with Docker container** + +.. code-block:: bash + + docker run -it --rm --name running-inference \ + -v "$PWD":/usr/data ghcr.io/intertwin-eu/itwinai:0.01-mnist-torch-0.1 \ + /bin/bash -c "itwinai exec-pipeline --print-config \ + --config /usr/src/app/config.yaml \ + --pipe-key training_pipeline \ + -o dataset_root=/usr/data/mnist-dataset " + + +**Inference with Docker container** + +From wherever a sample of MNIST jpg images is available +(folder called 'mnist-sample-data/'): + +:: + + ├── $PWD + │ ├── mnist-sample-data + │ │ ├── digit_0.jpg + │ │ ├── digit_1.jpg + │ │ ├── digit_2.jpg + ... + │ │ ├── digit_N.jpg + + +.. code-block:: bash + + docker run -it --rm --name running-inference \ + -v "$PWD":/usr/data ghcr.io/intertwin-eu/itwinai:0.01-mnist-torch-0.1 \ + /bin/bash -c "itwinai exec-pipeline --print-config \ + --config /usr/src/app/config.yaml \ + --pipe-key inference_pipeline \ + -o test_data_path=/usr/data/mnist-sample-data \ + -o inference_model_mlflow_uri=/usr/src/app/mnist-pre-trained.pth \ + -o predictions_dir=/usr/data/mnist-predictions " + + +This command will store the results in a folder called "mnist-predictions": + +:: + + ├── $PWD + │ ├── mnist-predictions + | │ ├── predictions.csv + + + +.. toctree:: + :maxdepth: 5 + +dataloader.py ++++++++++++++ + +The `dataloader.py` script is responsible for loading the MNIST dataset and preparing it for training. + +.. literalinclude:: ../use-cases/mnist/torch/dataloader.py + :language: python + + +Dockerfile +++++++++++ + +.. literalinclude:: ../use-cases/mnist/torch/Dockerfile + :language: bash + + +create_inference_sample.py +++++++++++++++++++++++++++ + +This file defines a pipeline configuration for the MNIST use case inference. + +.. literalinclude:: ../use-cases/mnist/torch/create_inference_sample.py + :language: python + +model.py +++++++++ + +The `model.py` script is responsible for loading a simple model. + +.. literalinclude:: ../use-cases/mnist/torch/model.py + :language: python + +config.yaml ++++++++++++ + +This YAML file defines the pipeline configuration for the MNIST use case. It includes settings for the model, training, and evaluation. + +.. literalinclude:: ../use-cases/mnist/torch/config.yaml + :language: yaml + +startscript.sh +++++++++++++++ + +The `startscript` is a shell script to initiate the training process. It sets up the environment and starts the training using the `train.py` script. + +.. literalinclude:: ../use-cases/mnist/torch/startscript.sh + :language: bash + + +saver.py +++++++++ +... + +.. literalinclude:: ../use-cases/mnist/torch/saver.py + :language: python + + +runall.sh ++++++++++ + +.. literalinclude:: ../use-cases/mnist/torch/runall.sh + :language: bash + + +slurm.sh +++++++++ + +.. literalinclude:: ../use-cases/mnist/torch/slurm.sh + :language: bash + + + +This section covers the MNIST use case, which utilizes the `tensorflow` framework for training and evaluation. The following files are integral to this use case: + +Tensorflow +---------- + +.. toctree:: + :maxdepth: 5 + +dataloader.py ++++++++++++++ + +The `dataloader.py` script is responsible for loading the MNIST dataset and preparing it for training. + +.. literalinclude:: ../use-cases/mnist/tensorflow/dataloader.py + :language: python + + +pipeline.yaml ++++++++++++++ + +This YAML file defines the pipeline configuration for the MNIST use case. It includes settings for the model, training, and evaluation. + +.. literalinclude:: ../use-cases/mnist/tensorflow/pipeline.yaml + :language: yaml + + +startscript.sh +++++++++++++++ + +The `startscript` is a shell script to initiate the training pipeline. + +.. literalinclude:: ../use-cases/mnist/tensorflow/startscript.sh + :language: bash + + + + \ No newline at end of file diff --git a/docs/modules.rst b/docs/modules.rst new file mode 100644 index 00000000..11a5a28f --- /dev/null +++ b/docs/modules.rst @@ -0,0 +1,23 @@ +`itwinai `_ +============================================== + +.. toctree:: + :maxdepth: 4 + + itwinai.cli + itwinai.components + itwinai.distributed + itwinai.loggers + itwinai.parser + itwinai.pipeline + itwinai.serialization + itwinai.type + itwinai.utils + + +.. toctree:: + :maxdepth: 4 + + itwinai.tf.modules + itwinai.torch.modules + diff --git a/docs/notebooks/example.ipynb b/docs/notebooks/example.ipynb new file mode 100644 index 00000000..432ef610 --- /dev/null +++ b/docs/notebooks/example.ipynb @@ -0,0 +1,92 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dece1c67", + "metadata": {}, + "source": [ + "# Example" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4d002f7c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3.8.10 (default, May 19 2021, 13:12:57) [MSC v.1916 64 bit (AMD64)]\n" + ] + } + ], + "source": [ + "import sys\n", + "print (sys.version)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "77e147e9", + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import Image" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ac52e888", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Image(\"http://sipi.usc.edu/database/preview/misc/4.2.03.png\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b059f9ad", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..3c57b131 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,15 @@ +Sphinx==7.2.6 +sphinx-rtd-theme==2.0.0 +nbsphinx==0.9.4 +myst-parser==2.0.0 +wheel +tensorflow==2.15 +torch==2.1.* +torchvision +torchaudio +git+https://github.com/thomas-bouvier/horovod.git@compile-cpp17 +deepspeed + +# local path to itwinai module, assuming that pip install -r docs/requirements.txt is run form the repository root +# If needed, you can add optional dependencies, like: ".[dev]" +.[torch] diff --git a/docs/tf_scaling_test.rst b/docs/tf_scaling_test.rst new file mode 100644 index 00000000..b61e720f --- /dev/null +++ b/docs/tf_scaling_test.rst @@ -0,0 +1,78 @@ +Tensorflow scaling test +======================= + +Benchmarking tutorial using JUBE +-------------------------------- + +Benchmarking of itwinai can also be performed with the JUBE Benchmarking Environment from JSC. +The JUBE benchmarking tool is already setup in the environment files provided under ``env-files``. + +Source the environment +++++++++++++++++++++++ + +Find the location of your environment file along with the module load commands, such as: + +.. code-block:: bash + + ml Stages/2024 GCC/12.3.0 OpenMPI CUDA/12 MPI-settings/CUDA Python HDF5 PnetCDF libaio mpi4py CMake cuDNN/8.9.5.29-CUDA-12 + source envAI_hdfml/bin/activate + + +Run benchmark ++++++++++++++ + +The benchmarks are defined in the ``general_jobsys.xml`` file. +One can specify the configurations in terms of parameters such as the number of nodes. +The benchmark can be simply launched with the command: + +.. code-block:: bash + + jube run general_jobsys.xml + + +Monitor status of benchmark run ++++++++++++++++++++++++++++++++ + +The status of the run can be monitored with: + +.. code-block:: bash + + jube continue bench_run --id last + + +Check results of the benchmark run +++++++++++++++++++++++++++++++++++ + +The results can be viewed with: + +.. code-block:: bash + + jube result -a bench_run --id last + + +This will create ``result-csv.dat`` file in the ``results`` folder. + +The scaling and efficiency plots can be generated with the ``bench_plot.ipynb`` file +which takes the ``result-csv.dat`` file as input. + + + +bench_plot.ipynb +++++++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/tf-scaling-test-jube/bench_plot.ipynb + :language: python + + +train.py +++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/tf-scaling-test-jube/train.py + :language: python + + +jube_ddp.sh ++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/tf-scaling-test-jube/jube_ddp.sh + :language: bash \ No newline at end of file diff --git a/docs/tf_tutorial_0_basics.rst b/docs/tf_tutorial_0_basics.rst new file mode 100644 index 00000000..6634565f --- /dev/null +++ b/docs/tf_tutorial_0_basics.rst @@ -0,0 +1,40 @@ +Tensorflow basics example +========================= + +Tutorial: distributed strategies for Tensorflow +----------------------------------------------- + +In this tutorial we show how to use Tensorflow ``MultiWorkerMirroredStrategy``. +Note that the environment is tested on the HDFML system at JSC. +For other systems, the module versions might need change accordingly. +Other strategies will be updated here. + +First, from the root of this repository, build the environment containing +Tensorflow. You can *try* with: + +.. code-block:: bash + + # Creates a Python venv called envAItf_hdfml + make tf-gpu-jsc + + +If you want to distribute the code in ``train.py``, run from terminal: + +.. code-block:: bash + + sbatch tfmirrored_slurm.sh + + +train.py +++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/tf-tutorial-0-basics/train.py + :language: python + + +tfmirrored_slurm.sh ++++++++++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/tf-tutorial-0-basics/tfmirrored_slurm.sh + :language: bash + diff --git a/docs/tf_tutorial_1_imagenet.rst b/docs/tf_tutorial_1_imagenet.rst new file mode 100644 index 00000000..c8e08f6f --- /dev/null +++ b/docs/tf_tutorial_1_imagenet.rst @@ -0,0 +1,39 @@ +Tensorflow ImageNet example +=========================== + +Tutorial: distributed strategies for Tensorflow +----------------------------------------------- + +In this tutorial we show how to use Tensorflow ``MultiWorkerMirroredStrategy``. +Note that the environment is tested on the HDFML system at JSC. +For other systems, the module versions might need change accordingly. +Other strategies will be updated here. + +First, from the root of this repository, build the environment containing +Tensorflow. You can *try* with: + +.. code-block:: bash + + # Creates a Python venv called envAItf_hdfml + make tf-gpu-jsc + +If you want to distribute the code in ``train.py``, run from terminal: + +.. code-block:: bash + + sbatch tfmirrored_slurm.sh + + +train.py +++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/tf-tutorial-1-imagenet/train.py + :language: python + + +tfmirrored_slurm.sh ++++++++++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/tf-tutorial-1-imagenet/tfmirrored_slurm.sh + :language: bash + diff --git a/docs/torch_scaling_test.rst b/docs/torch_scaling_test.rst new file mode 100644 index 00000000..3ddf8ac5 --- /dev/null +++ b/docs/torch_scaling_test.rst @@ -0,0 +1,229 @@ +PyTorch scaling test +==================== + +Scaling tests for PyTorch of ResNet152 on Imagenet +-------------------------------------------------- + +Introduction +++++++++++++ + +This tutorial contains six training configurations: three baselines plus the itwinai +trainer, which allows to switch from DDP, Horovod, and DeepSpeed in a simplified way. + +The training scripts are: + +- ``ddp_trainer.py``: baseline of distributed training with vanilla torch DDP +- ``deepspeed_trainer.py``: baseline of distributed training with vanilla Microsoft DeepSpeed +- ``horovod_trainer.py``: baseline of distributed training with vanilla Horovod +- ``itwinai_trainer.py``: provides the same functionalities as all the above, using the unified itwinai's distributed training interface. + + +Configuration files are stored into ``config/`` folder. ``base.yaml`` provides the +configuration common to all training experiments, whereas ``ddp.yaml``, ``deepspeed.yaml``, +and ``horovod.yaml`` provide framework-specific configuration. +Thanks to ``itwinai.parser.ArgumentParser``, the CLI arguments can be parsed from a list of +configuration files, while also allowing for online override. +Example: + +.. code-block:: bash + + # Rather than requiring a LONG list of inline configuration params... + python ddp_trainer.py --data-dir some/dir --log-int 10 --verbose --nworker 4 ... + + # ...itwinai's ArgumentParser allows to load them from a set of configuration files + # with inline override, if needed + python ddp_trainer.py -c config/base.yaml -c config/ddp.yaml --log-int 42 + + +Run a single training ++++++++++++++++++++++ + +Training runs are meant to be submitted via SLURM, from a unified job script file: +``slurm.sh``. +You can select the distributed training algorithm and provide the command to execute +setting SLURM environment variables using the ``--export`` option: + +.. code-block:: bash + + # Launch a distributed training setup with Torch DDP + export DIST_MODE="ddp" + export RUN_NAME="ddp-bl-imagenent" + export TRAINING_CMD="ddp_trainer.py -c config/base.yaml -c config/ddp.yaml" + export PYTHON_VENV="../../../envAI_hdfml" + export N=2 # Number of nodes + sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + --nodes=$N slurm.sh + + +Run all training configurations ++++++++++++++++++++++++++++++++ + +To run all training configurations you can use the ``runall.sh`` script, which provides +further insight how different training configurations can be launched using the same +SLURM job script. + +.. code-block:: bash + + bash runall.sh + + +And check the newly created jobs in the SLURM queue: + +.. code-block:: bash + + squeue -u YOUR_USERNAME + + +Each execution will generate a ``.csv`` file recording the time that each training epoch +took to complete. Below you can learn more on how to analyze these files to produce report. + +Launch scaling test ++++++++++++++++++++ + +Similarly to ``runall.sh``, there is another script which is meant to launch a scalability +analysis experiment. This will launch all the training configuration for different number +of node allocations. By default it will run the same distributed trainings on 1, 2, 4, and +8 nodes. Each independent execution will generate a separate ``.csv`` file which can be +analyzed later to produce a scalability report. + +Launch the scaling test: + +.. code-block:: bash + + bash scaling-test.sh + + +And check the newly created jobs in the SLURM queue: + +.. code-block:: bash + + squeue -u YOUR_USERNAME + + +Analyze results ++++++++++++++++ + +Once all jobs have completed, you can automatically generate scalability report +using itwinai's CLI: + +.. code-block:: bash + + # First, activate you Python virtual environment + + # For more info run + itwinai scalability-report --help + + # Generate a scalability report + itwinai scalability-report --pattern="^epoch.+\.csv$" \ + --plot-title "ResNet152 on Imagenet" --archive imagenet_results + + +The last command prints to terminal the average epoch time per training +configuration and per number of nodes, and it generated scaling test +analysis plot, which is saved as ``.png`` file. This command will also +create a ``.tar.gz`` archive of all the analyzed ``.csv`` files and +the generated plots, allowing you to easily organize different experiments +and reducing the risk of overwriting the logs generated during the scaling +test. + +Example of scalability plot generated by ``itwinai scalability-report``: + + +.. image:: ../tutorials/distributed-ml/torch-scaling-test/img/report.png + + +Configuration files +------------------- + + +base.yaml ++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-scaling-test/config/base.yaml + :language: yaml + + +ddp.yaml +++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-scaling-test/config/ddp.yaml + :language: yaml + + +deepspeed.yaml +++++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-scaling-test/config/deepspeed.yaml + :language: yaml + + +horovod.yaml +++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-scaling-test/config/horovod.yaml + :language: yaml + + + +Training scripts and utils +-------------------------- + + +ddp_trainer.py +++++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-scaling-test/ddp_trainer.py + :language: python + + +deepspeed_trainer.py +++++++++++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-scaling-test/deepspeed_trainer.py + :language: python + + +horovod_trainer.py +++++++++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-scaling-test/horovod_trainer.py + :language: python + + +itwinai_trainer.py +++++++++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-scaling-test/itwinai_trainer.py + :language: python + + +utils.py +++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-scaling-test/utils.py + :language: python + + +runall.sh ++++++++++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-scaling-test//runall.sh + :language: bash + + +scaling-test.sh ++++++++++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-scaling-test/scaling-test.sh + :language: bash + + +slurm.sh ++++++++++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-scaling-test/slurm.sh + :language: bash + diff --git a/docs/torch_tutorial_0_basics.rst b/docs/torch_tutorial_0_basics.rst new file mode 100644 index 00000000..8599b51d --- /dev/null +++ b/docs/torch_tutorial_0_basics.rst @@ -0,0 +1,101 @@ +PyTorch basics example +====================== + +Tutorial: distributed strategies for PyTorch +-------------------------------------------- + +In this tutorial we show how to use torch ``DistributedDataParallel`` (DDP), Horovod and +DeepSpeed from the same client code. +Note that the environment is tested on the HDFML system at JSC. For other systems, +the module versions might need change accordingly. + +Setup ++++++ + +First, from the root of this repository, build the environment containing +pytorch, horovod and deepspeed. You can *try* with: + +.. code-block:: bash + + # Creates a Python venv called envAI_hdfml + make torch-gpu-jsc + + +Distributed training +++++++++++++++++++++ + +Each distributed strategy has its own SLURM job script, which +should be used to run it: + +If you want to distribute the code in ``train.py`` with **torch DDP**, run from terminal: + +.. code-block:: bash + + export DIST_MODE="ddp" + export RUN_NAME="ddp-itwinai" + export TRAINING_CMD="train.py -s ddp" + export PYTHON_VENV="../../../envAI_hdfml" + sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + + +If you want to distribute the code in ``train.py`` with **DeepSpeed**, run from terminal: + +.. code-block:: bash + + export DIST_MODE="deepspeed" + export RUN_NAME="deepspeed-itwinai" + export TRAINING_CMD="train.py -s deepspeed" + export PYTHON_VENV="../../../envAI_hdfml" + sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + + +If you want to distribute the code in ``train.py`` with **Horovod**, run from terminal: + +.. code-block:: bash + + export DIST_MODE="deepspeed" + export RUN_NAME="deepspeed-itwinai" + export TRAINING_CMD="train.py -s deepspeed" + export PYTHON_VENV="../../../envAI_hdfml" + sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + + +**You can run all of them with:** + +.. code-block:: bash + + bash runall.sh + + +train.py +++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-tutorial-0-basics/train.py + :language: python + + +runall.sh ++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-tutorial-0-basics/runall.sh + :language: bash + + +slurm.sh +++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-tutorial-0-basics/slurm.sh + :language: bash + diff --git a/docs/torch_tutorial_1_mnist.rst b/docs/torch_tutorial_1_mnist.rst new file mode 100644 index 00000000..a5226f91 --- /dev/null +++ b/docs/torch_tutorial_1_mnist.rst @@ -0,0 +1,119 @@ +PyTorch MNIST example +===================== + +Tutorial: distributed strategies for PyTorch model trained on MNIST dataset +--------------------------------------------------------------------------- + +In this tutorial we show how to use torch ``DistributedDataParallel`` (DDP), Horovod and +DeepSpeed from the same client code. +Note that the environment is tested on the HDFML system at JSC. For other systems, +the module versions might need change accordingly. + +Setup ++++++ + +First, from the root of this repository, build the environment containing +pytorch, horovod and deepspeed. You can *try* with: + +.. code-block:: bash + + # Creates a Python venv called envAI_hdfml + make torch-gpu-jsc + + +Before launching training, since on JSC's compute nodes there is not internet connection, +you need to download the dataset before while on the login lode: + +.. code-block:: bash + + source ../../../envAI_hdfml/bin/activate + python train.py --download-only + + +This command creates a local folder called "MNIST" with the dataset. + +Distributed training +++++++++++++++++++++ + +Each distributed strategy has its own SLURM job script, which +should be used to run it: + +If you want to distribute the code in ``train.py`` with **torch DDP**, run from terminal: + +.. code-block:: bash + + export DIST_MODE="ddp" + export RUN_NAME="ddp-itwinai" + export TRAINING_CMD="train.py -s ddp -c config.yaml" + export PYTHON_VENV="../../../envAI_hdfml" + sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + + +If you want to distribute the code in ``train.py`` with **DeepSpeed**, run from terminal: + +.. code-block:: bash + + export DIST_MODE="deepspeed" + export RUN_NAME="deepspeed-itwinai" + export TRAINING_CMD="train.py -s deepspeed -c config.yaml" + export PYTHON_VENV="../../../envAI_hdfml" + sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + + +If you want to distribute the code in ``train.py`` with **Horovod**, run from terminal: + +.. code-block:: bash + + export DIST_MODE="horovod" + export RUN_NAME="horovod-itwinai" + export TRAINING_CMD="train.py -s horovod -c config.yaml" + export PYTHON_VENV="../../../envAI_hdfml" + sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + + +**You can run all of them with:** + +.. code-block:: bash + + bash runall.sh + + +config.yaml ++++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-tutorial-1-mnist/config.yaml + :language: yaml + + +runall.sh ++++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-tutorial-1-mnist/runall.sh + :language: bash + + +slurm.sh +++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-tutorial-1-mnist/slurm.sh + :language: bash + + +train.py +++++++++ + +.. literalinclude:: ../tutorials/distributed-ml/torch-tutorial-1-mnist/train.py + :language: python + diff --git a/docs/tutorials.rst b/docs/tutorials.rst new file mode 100644 index 00000000..5685f8bd --- /dev/null +++ b/docs/tutorials.rst @@ -0,0 +1,52 @@ +.. _tutorials: + + + +Distributed ML training tutorials +================================= + +Here you can find a collection of tutorials for distributing Tensorflow and PyTorch based workflows. + +Distributed ML with TensorFlow +------------------------------ + +.. toctree:: + :maxdepth: 1 + :numbered: + + tf_tutorial_0_basics + tf_tutorial_1_imagenet + tf_scaling_test + + + +Distributed ML with PyTorch +--------------------------- + +.. toctree:: + :maxdepth: 1 + :numbered: + + torch_tutorial_0_basics + torch_tutorial_1_mnist + torch_scaling_test + + + +ML workflow tutorials +===================== + +Here you can find a collection of tutorials for various complexity ML workflows. + + +.. toctree:: + :maxdepth: 1 + :numbered: + + basic_comp + basic_workflow + intermediate_workflow + advanced_workflow + + + \ No newline at end of file diff --git a/docs/use_cases.rst b/docs/use_cases.rst new file mode 100644 index 00000000..b2132e94 --- /dev/null +++ b/docs/use_cases.rst @@ -0,0 +1,97 @@ +Integrated Use Cases +==================== + +Here you can find a collection of use cases showing how ``itwinai`` can be used. Each use case folder contains: + +- ``pipeline.yaml``: textual description of the ML workflow for that use case +- ``train.py``: entry point of training workflow. +- ``startscript``: file to execute the training workflow on a SLURM-based cluster. +- ``requirements.txt``: (optional) use case-specific requirements. can be installed with: + +.. code-block:: bash + + cd use/case/folder + # After activating the correct environment... + pip install -r requirements.txt + + +How to run a use case +--------------------- + +First, create the use case's Python environment (i.e., PyTorch or TensorFlow) +as described `here `_, and activate it. +Then, install use case-specific dependencies, if any: + +.. code-block:: bash + + pip install -r /use/case/path/requirements.txt + + +Alternatively, you can use the use case Docker image, if available. + +Then, go to the use case's directory: + +.. code-block:: bash + + cd /use/case/path + + +From here you can run the use case (having activated the correct Python env): + +.. code-block:: bash + + # Locally + python train.py [OPTIONS...] + + # With SLURM: stdout and stderr will be saved to job.out and job.err files + sbatch startscript + + + +Fast particle detector simulation | CERN use case +------------------------------------------------- + +The first ``interTwin`` use case integrated with ``itwinai`` framework is the DT for fast particle detector simulation. +3D Generative Adversarial Network (3DGAN) for generation of images of calorimeter depositions. +This project is based on the prototype `3DGAN `_ model developed at CERN and is implemented on PyTorch Lightning framework. + +.. toctree:: + :maxdepth: 2 + + 3dgan_doc + + +MNIST dataset use case +---------------------- + +MNIST image classification is used to provide an example on +how to define an end-to-end digital twin workflow with the ``itwinai`` software. + +.. toctree:: + :maxdepth: 2 + + mnist_doc + + +Tropical Cyclones Detection | CMCC use case +------------------------------------------- + +Below you can find the training and validation of a Tropical Cyclones (TCs) Detection model, developed by CMCC, integrated with ``itwinai`` framework. + +.. toctree:: + :maxdepth: 1 + + cyclones_doc + + + +Noise Simulation for Gravitational Waves Detector (Virgo) | INFN use case +------------------------------------------------------------------------- + +Below you can find the integration of the Virgo use case with ``itwinai`` framework, developed by INFN. + +.. toctree:: + :maxdepth: 1 + + virgo_doc + diff --git a/docs/virgo_doc.rst b/docs/virgo_doc.rst new file mode 100644 index 00000000..35cc6378 --- /dev/null +++ b/docs/virgo_doc.rst @@ -0,0 +1,103 @@ +Virgo +===== + +The code is adapted from +`this notebook `_ +available on the Virgo use case's `repository `_. + +Installation +++++++++++++ + +Before continuing, install the required libraries in the pre-existing itwinai environment. + +.. code-block:: bash + + pip install -r requirements.txt + + +Training +++++++++ + +You can run the whole pipeline in one shot, including dataset generation, or you can +execute it from the second step (after the synthetic dataset have been generated). + +.. code-block:: bash + + itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline + + # Run from the second step (use python-like slicing syntax). + # In this case, the dataset is loaded from "data/Image_dataset_synthetic_64x64.pkl" + itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline --steps 1: + + +Launch distributed training with SLURM using the dedicated ``slurm.sh`` job script: + +.. code-block:: bash + + # Distributed training with torch DistributedDataParallel + PYTHON_VENV="../../envAI_hdfml" + DIST_MODE="ddp" + RUN_NAME="ddp-virgo" + TRAINING_CMD="$PYTHON_VENV/bin/itwinai exec-pipeline --config config.yaml --steps 1: --pipe-key training_pipeline -o strategy=ddp" + sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + slurm.sh + + +...and check the results in ``job.out`` and ``job.err`` log files. + +To understand how to use all the distributed strategies supported by ``itwinai``, +check the content of ``runall.sh``: + +.. code-block:: bash + + bash runall.sh + + +> [!WARNING] +> The file ``train.py`` is not to be considered the suggested way to launch training, +> as it is deprecated and is there to testify an intermediate integration step +> of the use case into ``itwinai``. + +When using MLFLow logger, you can visualize the logs in from the MLFlow UI: + +.. code-block:: bash + + mlflow ui --backend-store-uri mllogs/mlflow + + # In background + mlflow ui --backend-store-uri mllogs/mlflow > /dev/null 2>&1 & + + +config.yaml ++++++++++++ +.. literalinclude:: ../use-cases/virgo/config.yaml + :language: yaml + + +data.py ++++++++ + +.. literalinclude:: ../use-cases/virgo/data.py + :language: python + + +runall.sh ++++++++++ + +.. literalinclude:: ../use-cases/virgo/runall.sh + :language: bash + + +slurm.sh +++++++++ + +.. literalinclude:: ../use-cases/virgo/slurm.sh + :language: bash + + +trainer.py +++++++++++ +.. literalinclude:: ../use-cases/virgo/trainer.py + :language: python + + diff --git a/env-files/tensorflow/createEnvJSCTF.sh b/env-files/tensorflow/createEnvJSCTF.sh new file mode 100644 index 00000000..df11eec5 --- /dev/null +++ b/env-files/tensorflow/createEnvJSCTF.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# -*- coding: utf-8 -*- + +if [ ! -f "env-files/tensorflow/generic_tf.sh" ]; then + echo "ERROR: env-tensorflow/torch/generic_tf.sh not found!" + exit 1 +fi + +# set modules +ml --force purge + +# get sys info +cDir=$PWD +sysN="$(uname -n | cut -f2- -d.)" +echo "system:${sysN}" +echo + +cont1=false +if [ "$sysN" = 'hdfml' ] ; then + ml Stages/2024 GCC/12.3.0 OpenMPI CUDA/12 MPI-settings/CUDA Python/3.11 HDF5 PnetCDF libaio mpi4py CMake cuDNN/8.9.5.29-CUDA-12 + cont1=true +else + echo + echo 'unknown system detected' + echo 'canceling' + echo +fi +echo "modules loaded" +echo + +# get python version +pver="$(python --version 2>&1 | awk '{print $2}' | cut -f1-2 -d.)" +echo "python version is ${pver}" +echo + +if [ "$cont1" = true ] ; then + if [ -d "${cDir}/envAItf_${sysN}" ];then + echo 'env already exist' + echo + + source envAItf_${sysN}/bin/activate + else + # create env + python3 -m venv envAItf_${sysN} + + # get headers for pip + if [ -f "${cDir}/envAItf_${sysN}/bin/pip3" ]; then + echo 'pip already exist' + else + cp "$(which pip3)" $cDir/envAItf_${sysN}/bin/ + ln -s $cDir/envAItf_${sysN}/bin/pip3 $cDir/envAItf_${sysN}/bin/pip${pver} + var="#!$cDir/envAItf_${sysN}/bin/python${pver}" + sed -i "1s|.*|$var|" $cDir/envAItf_${sysN}/bin/pip3 + fi + + # activate env + source envAItf_${sysN}/bin/activate + + echo "a new env is created in ${cDir}" + echo "activation is done via:" + echo "source ${cDir}/envAItf_${sysN}/bin/activate" + fi +fi + +# Install TF dependencies in env +export ENV_NAME="envAItf_$sysN" +bash env-files/tensorflow/generic_tf.sh +source $ENV_NAME/bin/activate + +# JUBE benchmarking environment +if [ -f "${cDir}/envAI_${sysN}/bin/jube" ]; then + echo 'JUBE already installed' +else + pip3 install --no-cache-dir http://apps.fz-juelich.de/jsc/jube/jube2/download.php?version=latest +fi + +# # get rest of the libraries$ +# if [ "$cont1" = true ] ; then +# pip3 install -r reqs_TF.txt #--ignore-installed +# fi + diff --git a/env-files/tensorflow/generic_tf.sh b/env-files/tensorflow/generic_tf.sh new file mode 100644 index 00000000..b38d795b --- /dev/null +++ b/env-files/tensorflow/generic_tf.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +# ENV VARIABLES: +# - ENV_NAME: set custom name for virtual env. Default: ".venv-tf" +# - NO_CUDA: if set, install without cuda support + +# Detect custom env name from env +if [ -z "$ENV_NAME" ]; then + ENV_NAME=".venv-tf" +fi + +# get python version +pver="$(python --version 2>&1 | awk '{print $2}' | cut -f1-2 -d.)" + +# use pyenv if exist +if [ -d "$HOME/.pyenv" ];then + export PYENV_ROOT="$HOME/.pyenv" + export PATH="$PYENV_ROOT/bin:$PATH" +fi + +# set dir +cDir=$PWD + +# create environment +if [ -d "${cDir}/$ENV_NAME" ];then + echo "env $ENV_NAME already exists" + + source $ENV_NAME/bin/activate +else + python3 -m venv $ENV_NAME + + # activate env + source $ENV_NAME/bin/activate + + echo "$ENV_NAME environment is created in ${cDir}" +fi + +pip3 install --upgrade pip + +# get wheel -- setuptools extension +pip3 install --no-cache-dir wheel + +# install TF +if [ -f "${cDir}/$ENV_NAME/bin/tensorboard" ]; then + echo 'TF already installed' + echo +else + if [ -z "$NO_CUDA" ]; then + pip3 install tensorflow[and-cuda]==2.15 --no-cache-dir + else + # CPU only installation + pip3 install tensorflow==2.15 --no-cache-dir + fi +fi + +# CURRENTLY, horovod is not used with TF. Skipped. +# # install horovod +# if [ -f "${cDir}/$ENV_NAME/bin/horovodrun" ]; then +# echo 'Horovod already installed' +# echo +# else +# if [ -z "$NO_CUDA" ]; then +# export HOROVOD_GPU=CUDA +# export HOROVOD_GPU_OPERATIONS=NCCL +# export HOROVOD_WITH_TENSORFLOW=1 +# # export TMPDIR=${cDir} +# else +# # CPU only installation +# export HOROVOD_WITH_TENSORFLOW=1 +# # export TMPDIR=${cDir} +# fi + +# pip3 install --no-cache-dir horovod[tensorflow,keras] # --ignore-installed +# fi + +# WHEN USING TF >= 2.16: +# # install legacy version of keras (2.16) +# # Since TF 2.16, keras updated to 3.3, +# # which leads to an error when more than 1 node is used +# # https://keras.io/getting_started/ +# pip3 install tf_keras + +# itwinai +pip3 install -e .[dev] diff --git a/env-files/tensorflow/tensorflow-2.10.yml b/env-files/tensorflow/tensorflow-2.10.yml new file mode 100644 index 00000000..492b0486 --- /dev/null +++ b/env-files/tensorflow/tensorflow-2.10.yml @@ -0,0 +1,13 @@ +# https://phoenixnap.com/kb/how-to-install-tensorflow-ubuntu +name: tensorflow-env +channels: + - conda-forge +dependencies: + - python=3.9.12 + - cudatoolkit=11.2 + - cudnn=8.1.0 + - pip + - pip: + - tensorflow-addons + - tensorflow-datasets + diff --git a/env-files/tensorflow/tensorflow-2.11.yml b/env-files/tensorflow/tensorflow-2.11.yml new file mode 100644 index 00000000..c7d86d60 --- /dev/null +++ b/env-files/tensorflow/tensorflow-2.11.yml @@ -0,0 +1,19 @@ +# Ref: https://skeptric.com/tensorflow-conda/ +name: tensorflow +channels: + - defaults + - conda-forge + - nvidia/label/cuda-11.7.1 +dependencies: + - python=3.9 + - cudatoolkit=11.7 + - cudnn=8.1.0 + - cuda-nvcc + - pip + - pip: + - tensorflow-addons + - tensorflow-datasets + +# variables: +# LD_LIBRARY_PATH: "'$LD_LIBRARY_PATH:$CONDA_PREFIX/lib/'" +# XLA_FLAGS: "'--xla_gpu_cuda_data_dir=$CONDA_PREFIX/lib/'" \ No newline at end of file diff --git a/env-files/tensorflow/tensorflow-2.13-cpu.yml b/env-files/tensorflow/tensorflow-2.13-cpu.yml new file mode 100644 index 00000000..2d4740a6 --- /dev/null +++ b/env-files/tensorflow/tensorflow-2.13-cpu.yml @@ -0,0 +1,14 @@ +# https://www.tensorflow.org/install/pip#step-by-step_instructions +# https://phoenixnap.com/kb/how-to-install-tensorflow-ubuntu +# https://skeptric.com/tensorflow-conda/ +name: tensorflow-env +channels: + - conda-forge +dependencies: + - python=3.9.12 + - pip + - pip: + - tensorflow-addons + - tensorflow-datasets + # - git+https://github.com/interTwin-eu/T6.5-AI-and-ML.git@pipelines_backend#egg=itwinai&subdirectory=ai + diff --git a/env-files/tensorflow/tensorflow-2.13.yml b/env-files/tensorflow/tensorflow-2.13.yml new file mode 100644 index 00000000..cf7b245b --- /dev/null +++ b/env-files/tensorflow/tensorflow-2.13.yml @@ -0,0 +1,17 @@ +# https://www.tensorflow.org/install/pip#step-by-step_instructions +# https://phoenixnap.com/kb/how-to-install-tensorflow-ubuntu +# https://skeptric.com/tensorflow-conda/ +name: tensorflow-env +channels: + - conda-forge + - nvidia +dependencies: + - python=3.9.12 + - cudatoolkit=11.8.0 + - cuda-nvcc=11.3.58 + - pip + - pip: + - tensorflow-addons + - tensorflow-datasets + # - git+https://github.com/interTwin-eu/T6.5-AI-and-ML.git@pipelines_backend#egg=itwinai&subdirectory=ai + diff --git a/env-files/torch/createEnvJSC.sh b/env-files/torch/createEnvJSC.sh new file mode 100644 index 00000000..2203c708 --- /dev/null +++ b/env-files/torch/createEnvJSC.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# -*- coding: utf-8 -*- + +if [ ! -f "env-files/torch/generic_torch.sh" ]; then + echo "ERROR: env-files/torch/generic_torch.sh not found!" + exit 1 +fi + +# set dir +cDir=$PWD + +# get sys info +sysN="$(uname -n | cut -f2- -d.)" +sysN="${sysN%%[0-9]*}" + +# load modules +ml --force purge +ml Stages/2024 GCC OpenMPI CUDA/12 cuDNN MPI-settings/CUDA +ml Python CMake HDF5 PnetCDF libaio mpi4py +# echo "these modules are loaded:" +# ml + +# Create and install torch env +export ENV_NAME="envAI_$sysN" +bash env-files/torch/generic_torch.sh +source $ENV_NAME/bin/activate + +# fix IB IP config - FZJ specific +if [ -f "${cDir}/envAI_${sysN}/bin/torchrun" ]; then + sed -i -e '5,100s/^/#/' ${cDir}/envAI_${sysN}/bin/torchrun + echo """ +import re +import sys +from torch.distributed.run import main +from torch.distributed.elastic.agent.server import api as sapi + +def new_get_fq_hostname(): + return _orig_get_fq_hostname().replace('.', 'i.', 1) + +if __name__ == '__main__': + _orig_get_fq_hostname = sapi._get_fq_hostname + sapi._get_fq_hostname = new_get_fq_hostname + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) +""" >> ${cDir}/envAI_${sysN}/bin/torchrun +fi + +# JUBE benchmarking environment +if [ -f "${cDir}/envAI_${sysN}/bin/jube" ]; then + echo 'JUBE already installed' +else + pip3 install --no-cache-dir http://apps.fz-juelich.de/jsc/jube/jube2/download.php?version=latest +fi + +# some tests +echo "unit tests:" +for item in 'torch' 'deepspeed' 'horovod';do + python3 -c "import $item; print('$item version:',$item.__version__)" +done + + + + diff --git a/env-files/torch/generic_torch.sh b/env-files/torch/generic_torch.sh new file mode 100644 index 00000000..2bc27dff --- /dev/null +++ b/env-files/torch/generic_torch.sh @@ -0,0 +1,154 @@ +#!/bin/bash + +# ENV VARIABLES: +# - ENV_NAME: set custom name for virtual env. Default: ".venv-pytorch" +# - NO_CUDA: if set, install without cuda support + +# Detect custom env name from env +if [ -z "$ENV_NAME" ]; then + ENV_NAME=".venv-pytorch" +fi + +# get python version +pver="$(python --version 2>&1 | awk '{print $2}' | cut -f1-2 -d.)" + +# use pyenv if exist +if [ -d "$HOME/.pyenv" ];then + export PYENV_ROOT="$HOME/.pyenv" + export PATH="$PYENV_ROOT/bin:$PATH" +fi + +# set dir +cDir=$PWD + +# create environment +if [ -d "${cDir}/$ENV_NAME" ];then + echo "env $ENV_NAME already exists" + + source $ENV_NAME/bin/activate +else + python3 -m venv $ENV_NAME + + # activate env + source $ENV_NAME/bin/activate + + echo "$ENV_NAME environment is created in ${cDir}" +fi + +pip3 install --upgrade pip + +# get wheel -- setuptools extension +pip3 install --no-cache-dir wheel + +# install Torch +if [ -f "${cDir}/$ENV_NAME/bin/torchrun" ]; then + echo 'Torch already installed' +else + if [ -z "$NO_CUDA" ]; then + pip3 install --no-cache-dir \ + torch==2.1.* torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 + else + # CPU only installation + pip3 install --no-cache-dir \ + torch==2.1.* torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu + fi +fi + +# HPO - RayTune +if [ -f "${cDir}/$ENV_NAME/bin/ray" ]; then + echo 'Ray already installed' +else + pip3 install --no-cache-dir ray ray[tune] +fi + +# install deepspeed +if [ -f "${cDir}/$ENV_NAME/bin/deepspeed" ]; then + echo 'DeepSpeed already installed' +else + if [ -z "$NO_CUDA" ]; then + export DS_BUILD_CCL_COMM=1 + export DS_BUILD_UTILS=1 + export DS_BUILD_AIO=1 + export DS_BUILD_FUSED_ADAM=1 + export DS_BUILD_FUSED_LAMB=1 + export DS_BUILD_TRANSFORMER=1 + export DS_BUILD_STOCHASTIC_TRANSFORMER=1 + export DS_BUILD_TRANSFORMER_INFERENCE=1 + pip3 install --no-cache-dir DeepSpeed + else + # CPU only installation + pip3 install deepspeed + fi + + # fix .triton/autotune/Fp16Matmul_2d_kernel.pickle bug + line=$(cat -n $ENV_NAME/lib/python${pver}/site-packages/deepspeed/ops/transformer/inference/triton/matmul_ext.py | grep os.rename | awk '{print $1}' | head -n 1) + sed -i "${line}s|^|#|" $ENV_NAME/lib/python${pver}/site-packages/deepspeed/ops/transformer/inference/triton/matmul_ext.py +fi + +# install horovod +if [ -f "${cDir}/$ENV_NAME/bin/horovodrun" ]; then + echo 'Horovod already installed' +else + + if [ -z "$NO_CUDA" ]; then + # compiler vars + export LDSHARED="$CC -shared" && + export CMAKE_CXX_STANDARD=17 + + # CPU vars + export HOROVOD_MPI_THREADS_DISABLE=1 + export HOROVOD_CPU_OPERATIONS=MPI + + # GPU vars + export HOROVOD_GPU_ALLREDUCE=NCCL + export HOROVOD_NCCL_LINK=SHARED + export HOROVOD_NCCL_HOME=$EBROOTNCCL + + # Host language vars + export HOROVOD_WITH_PYTORCH=1 + export HOROVOD_WITHOUT_TENSORFLOW=1 + export HOROVOD_WITHOUT_MXNET=1 + else + # CPU only installation + export HOROVOD_WITH_PYTORCH=1 + export HOROVOD_WITHOUT_TENSORFLOW=1 + export HOROVOD_WITHOUT_MXNET=1 + fi + +# # need to modify for torch 2.1.0 +# git clone --recurse-submodules https://github.com/horovod/horovod.git +# line=$(cat -n horovod/CMakeLists.txt | grep CMAKE_CXX_STANDARD | awk '{print $1}' | head -n 1) +# var='set(CMAKE_CXX_STANDARD 17)' +# sed -i "${line}s|.*|$var|" horovod/CMakeLists.txt +# line=$(cat -n horovod/horovod/torch/CMakeLists.txt | grep CMAKE_CXX_STANDARD | awk '{print $1}' | head -n 1) +# var=' set(CMAKE_CXX_STANDARD 17)' +# sed -i "${line}s|.*|$var|" horovod/horovod/torch/CMakeLists.txt + +# # create tar! +# rm -rf horovod.tar.gz +# tar czf horovod.tar.gz horovod + +# # install +# pip3 install --no-cache-dir horovod.tar.gz +# rm -rf horovod horovod.tar.gz + + # Cleaner Horovod installation + # https://github.com/horovod/horovod/pull/3998 + # Assume that Horovod env vars are already in the current env! + pip3 install --no-cache-dir git+https://github.com/thomas-bouvier/horovod.git@compile-cpp17 +fi + +# get required libraries in reqs.txt +if [ -f "${cDir}/$ENV_NAME/lib/python${pver}/site-packages/torchnlp/_third_party/weighted_random_sampler.py" ]; then + echo 'required libs already exist' +else +# pip3 install -r Scripts/reqs.txt --no-cache-dir + + # fix int bug: modify l.4 of /torchnlp/_third_party/weighted_random_sampler.py + var='int_classes = int' + sed -i "4s|.*|$var|" \ + ${cDir}/$ENV_NAME/lib/python${pver}/site-packages/torchnlp/_third_party/weighted_random_sampler.py +fi + +# Install itwinai +pip3 install -e .[dev,torch] diff --git a/env-files/torch/pytorch-env-cpu.yml b/env-files/torch/pytorch-env-cpu.yml new file mode 100644 index 00000000..13825157 --- /dev/null +++ b/env-files/torch/pytorch-env-cpu.yml @@ -0,0 +1,15 @@ +# Resources: +# - https://pytorch.org/get-started/previous-versions/ +# - https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#creating-an-environment-file-manually + +name: ai-training +channels: + - pytorch + - conda-forge +dependencies: + - python=3.11 + - pytorch=1.13.1 + - torchvision=0.14.1 + - cpuonly + - lightning=2.0.0 + - torchmetrics diff --git a/env-files/torch/pytorch-env-gpu.yml b/env-files/torch/pytorch-env-gpu.yml new file mode 100644 index 00000000..2b87ffde --- /dev/null +++ b/env-files/torch/pytorch-env-gpu.yml @@ -0,0 +1,19 @@ +# Resources: +# - https://pytorch.org/get-started/previous-versions/ +# - https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#creating-an-environment-file-manually + +name: ai-training-gpu +channels: + - pytorch + - nvidia + - conda-forge +dependencies: + - python=3.11 + - pytorch::pytorch=1.13.1 + - pytorch::torchvision=0.14.1 + - pytorch::pytorch-cuda=11.7 + - pytorch::torchaudio=0.13.1 + - cudatoolkit=10.1 + - lightning=2.0.0 + - torchmetrics + - cuda-compiler diff --git a/environment-cern.yml b/environment-cern.yml deleted file mode 100644 index 2c2944b0..00000000 --- a/environment-cern.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: workflow-orchestrator -channels: - - conda-forge -dependencies: - - cwltool - - python=3.9.12 - - pyyaml - - omegaconf - - conda-lock - - # Testing - - pytest \ No newline at end of file diff --git a/examples.sh b/examples.sh deleted file mode 100644 index e5729e08..00000000 --- a/examples.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash - -# Setup workflow runner -make - -# Run mlflow server -mlflow server --backend-store-uri file:./use-cases/mnist/data/ml-logs/ - -# Launch some workflows - -################################ -### Training examples ### -################################ - -# MNIST training -micromamba run -p ./.venv \ - python run-workflow.py -f ./use-cases/mnist/training-workflow.yml - -# MNIST training (with CWL workflow definition) -micromamba run -p ./.venv \ - python run-workflow.py -f ./use-cases/mnist/training-workflow.yml --cwl - -# AI commnd: from within AI venv -itwinai train --train-dataset ./use-cases/mnist/data/preproc-images \ - --ml-logs ./use-cases/mnist/data/ml-logs \ - --config ./use-cases/mnist/mnist-ai-train.yml - -itwinai train --train-dataset ./use-cases/mnist/data/preproc-images \ - --ml-logs http://127.0.0.1:5000 \ - --config ./use-cases/mnist/mnist-ai-train.yml - -################################ -### Inference examples ### -################################ - -# MNIST inference -micromamba run -p ./.venv \ - python run-workflow.py -f ./use-cases/mnist/inference-workflow.yml - -# MNIST inference -itwinai predict --input-dataset ./use-cases/mnist/data/preproc-images \ - --predictions-location ./use-cases/mnist/data/ml-predictions \ - --config ./use-cases/mnist/mnist-ai-inference.yml \ - --ml-logs ./use-cases/mnist/data/ml-logs - -itwinai predict --input-dataset ./use-cases/mnist/data/preproc-images \ - --predictions-location ./use-cases/mnist/data/ml-predictions \ - --config ./use-cases/mnist/mnist-ai-inference.yml \ - --ml-logs http://127.0.0.1:5000 - -################################ -### Visualization examples ### -################################ - -# Visualize logs -micromamba activate ./ai/.venv-pytorch && \ - itwinai mlflow-ui --path ./use-cases/mnist/data/ml-logs - -# Datasets registry -micromamba activate ./ai/.venv-pytorch && \ - itwinai datasets --use-case use-cases/mnist/ - -# Workflows (any file '*-workflow.yml') -micromamba activate ./ai/.venv-pytorch && \ - itwinai workflows --use-case use-cases/mnist/ - -################################ -### Build docs ### -################################ - -# According to docs/README.md -cd docs && bundle exec jekyll serve \ No newline at end of file diff --git a/other/argparse/argparsee.py b/other/argparse/argparsee.py deleted file mode 100644 index f1e780a3..00000000 --- a/other/argparse/argparsee.py +++ /dev/null @@ -1,31 +0,0 @@ -from jsonargparse import ArgumentParser -import yaml - -from itwinai.plmodels.base import ItwinaiBasePlModule - -# Ref: -# https://jsonargparse.readthedocs.io/en/stable/#class-type-and-sub-classes - -parser = ArgumentParser() - -parser.add_argument('--car', type=str) -parser.add_argument('--number', type=int) -parser.add_subclass_arguments(ItwinaiBasePlModule, 'model') - - -# Parse (part of) YAML loaded in memory -with open('config.yml', "r", encoding="utf-8") as yaml_file: - try: - train_config = yaml.safe_load(yaml_file) - except yaml.YAMLError as exc: - print(exc) - raise exc -cfg = parser.parse_object(train_config) - -# # Parse whole YAML from file -# cfg = parser.parse_path('config.yml') - -print(cfg.model.as_dict()) - -cfg = parser.instantiate_classes(cfg) -print(cfg.model) diff --git a/other/argparse/config.yml b/other/argparse/config.yml deleted file mode 100644 index 468a59a4..00000000 --- a/other/argparse/config.yml +++ /dev/null @@ -1,10 +0,0 @@ -model: - class_path: itwinai.plmodels.mnist.LitMNIST - init_args: - data_dir: ../data/mnist/preproc-images-train - learning_rate: 0.001 - batch_size: 32 - hidden_size: 64 - -car: my car -number: 3 diff --git a/pyproject.toml b/pyproject.toml index f9efd3d5..c2dab9a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,88 @@ +# Refs: +# https://packaging.python.org/en/latest/specifications/declaring-project-metadata/#example +# https://setuptools.pypa.io/en/latest/userguide/dependency_management.html#dependencies-management-in-setuptools + +[build-system] +requires = ["setuptools", "setuptools-scm", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "itwinai" +version = "0.0.2" +description = "AI and ML workflows module for scientific digital twins." +readme = "README.md" +requires-python = ">=3.10" +license = { file = "LICENSE" } +keywords = ["ml", "ai", "hpc"] +authors = [ + { name = "Matteo Bunino", email = "matteo.bunino@cern.ch" }, + { name = "Rakesh Sarma", email = "r.sarma@fz-juelich.de" }, + { name = "Mario Ruettgers", email = "m.ruettgers@fz-juelich.de" }, + { name = "Kalliopi Tsolaki", email = "kalliopi.tsolaki@cern.ch" }, +] +maintainers = [ + { name = "Matteo Bunino", email = "matteo.bunino@cern.ch" }, + { name = "Rakesh Sarma", email = "r.sarma@fz-juelich.de" }, + { name = "Mario Ruettgers", email = "m.ruettgers@fz-juelich.de" }, + { name = "Kalliopi Tsolaki", email = "kalliopi.tsolaki@cern.ch" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python", +] + +dependencies = [ + "wandb", + "mlflow", + "jsonargparse[signatures]", + "pyyaml", + "omegaconf", + "rich>=13.5.3", + "typer>=0.9.0", + "wheel", + "pydantic", +] + +# dynamic = ["version", "description"] + +[project.optional-dependencies] +torch = ["lightning", "torchmetrics"] +# torch-cpu = [ +# "torch==2.1.*", +# "torchvision", +# "torchaudio", +# "lightning", +# "torchmetrics", +# "deepspeed", +# ] +dev = [ + "pytest>=7.4.2", + "pytest-mock>=3.11.1", + "pytest-cov>=4.1.0", + "ipykernel", + "ipython", + "tensorflow==2.15", # needed by tests on tensorboard +] + +[project.urls] +Homepage = "https://www.intertwin.eu/" +Documentation = "https://itwinai.readthedocs.io/" +Repository = "https://github.com/interTwin-eu/itwinai" +# Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md" + +[project.scripts] +itwinai = "itwinai.cli:app" + +# [project.gui-scripts] +# spam-gui = "spam:main_gui" + +# [project.entry-points."spam.magical"] +# tomatoes = "spam:main_tomatoes" + [tool.pytest.ini_options] markers = [ - "integration: integration tests (deselect with '-m \"not slow\"')", -] \ No newline at end of file + "integration: integration tests (deselect with '-m \"not integration\"')", + "slurm: needs SLURM and HPC resources (multiple GPUs/nodes). (deselect with '-m \"not slurm\"')", + "functional: functional tests. (deselect with '-m \"not functional\"')", + "memory_heavy: high memory footprint. (deselect with '-m \"not heavy_memory\"')", +] diff --git a/run-workflow.py b/run-workflow.py deleted file mode 100644 index 5b6aa48b..00000000 --- a/run-workflow.py +++ /dev/null @@ -1,154 +0,0 @@ -""" -Entry point of any workflow. -This script gathers the info from the workflow definition file -and runs a workflow step by step, passing the info loaded from -the workflow definition file. - -Example: - ->>> python run-workflow.py -f ./use-cases/mnist/training-workflow.yml - -""" - -import os -import subprocess -import argparse -from typing import Dict -import yaml -from omegaconf import OmegaConf -from omegaconf.dictconfig import DictConfig - - -def load_yaml(path: str) -> Dict: - """Load YAML file as dict. - - Args: - path (str): path to YAML file. - - Raises: - exc: yaml.YAMLError for loading/parsing errors. - - Returns: - Dict: nested dict representation of parsed YAML file. - """ - with open(path, "r", encoding="utf-8") as yaml_file: - try: - loaded_config = yaml.safe_load(yaml_file) - except yaml.YAMLError as exc: - print(exc) - raise exc - return loaded_config - - -def load_yaml_with_deps(path: str) -> DictConfig: - """ - Load YAML file with OmegaConf and merge it with its dependencies - specified in the `conf-dependencies` field. - Assume that the dependencies live in the same folder of the - YAML file which is importing them. - - Args: - path (str): path to YAML file. - - Raises: - exc: yaml.YAMLError for loading/parsing errors. - - Returns: - DictConfig: nested representation of parsed YAML file. - """ - yaml_conf = load_yaml(path) - use_case_dir = os.path.dirname(path) - deps = [] - if yaml_conf.get('conf-dependencies'): - for dependency in yaml_conf['conf-dependencies']: - deps.append(load_yaml( - os.path.join( - use_case_dir, - dependency - )) - ) - - return OmegaConf.merge(yaml_conf, *deps) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Run a simple DT workflow.') - parser.add_argument( - '-f', '--workflow-file', - type=str, - help='Path to file describing DT a workflow.', - required=True - ) - parser.add_argument( - '--cwl', - action=argparse.BooleanOptionalAction - ) - args = parser.parse_args() - - # 1. Parse workflow file - - # workflow definition file - workflow = load_yaml_with_deps(args.workflow_file) - - # 2. Deploy steps (i.e., create micromamba envs), if not already there - - for step in workflow.get('steps'): - step_name, step_data = next(iter(step.items())) - if not os.path.exists(step_data['env']['prefix']): - print(f'Deploying "{step_name}" step...') - # Install python env from micromamba env definition file - subprocess.run( - (f"micromamba env create -p {step_data['env']['prefix']} " - f"--file {step_data['env']['file']} -y").split(), - check=True - ) - # Install local python project from source, if present - if step_data['env'].get('source') is not None: - subprocess.run( - (f"micromamba run -p {step_data['env']['prefix']} " - "python -m pip install " # --no-deps - f"-e {step_data['env']['source']}").split(), - check=True - ) - - # 3. Run the by invoking the CWL execution tool' - # invoke workflow with CWL - if args.cwl: - print('Invoked workflow with CWL.') - # raise NotImplementedError('CWL workflow definition need to be updated') - print( - (f"cwltool --leave-tmpdir " - f"--outdir={workflow['root'] + '/data'} " - f"{workflow.get('workflowFileCWL')} " - f"{args.workflow_file}") - ) - subprocess.run( - (f"cwltool --leave-tmpdir " - f"--outdir={workflow['root'] + '/data'} " - f"{workflow.get('workflowFileCWL')} " - f"{args.workflow_file}"), - shell=True, - check=True, - ) - - # invoke workflow step-by-step with 'micromamba run ...' - else: - for step in workflow.get('steps'): - step_name, step_data = next(iter(step.items())) - # Args - args_str = '' - if step_data['args'] is not None: - args_str = " ".join([ - f"--{k} {v}" for k, v in step_data['args'].items() - ]) - - print(f'Running "{step_name}" step...') - print( - f"micromamba run -p {step_data['env']['prefix']} " - f"{step_data['command']} {args_str} \n\n" - ) - subprocess.run( - (f"micromamba run -p {step_data['env']['prefix']} " - f"{step_data['command']} {args_str} ").split(), - check=True, - ) diff --git a/ai/src/itwinai/__init__.py b/src/itwinai/__init__.py similarity index 100% rename from ai/src/itwinai/__init__.py rename to src/itwinai/__init__.py diff --git a/src/itwinai/cli.py b/src/itwinai/cli.py new file mode 100644 index 00000000..6c27d069 --- /dev/null +++ b/src/itwinai/cli.py @@ -0,0 +1,295 @@ +""" +Command line interface for out Python application. +You can call commands from the command line. +Example + +>>> $ itwinai --help + +""" + +# NOTE: import libs in the command"s function, not here. +# Otherwise this will slow the whole CLI. + +from typing import Optional, List +from typing_extensions import Annotated +from pathlib import Path +import typer + + +app = typer.Typer(pretty_exceptions_enable=False) + + +@app.command() +def scalability_report( + pattern: Annotated[str, typer.Option( + help="Python pattern matching names of CSVs in sub-folders." + )], + plot_title: Annotated[Optional[str], typer.Option( + help=("Plot name.") + )] = None, + skip_id: Annotated[Optional[int], typer.Option( + help=("Skip epoch ID.") + )] = None, + archive: Annotated[Optional[str], typer.Option( + help=("Archive name to backup the data, without extension.") + )] = None, +): + """ + Generate scalability report merging all CSVs containing epoch time + records in sub-folders. + + Example: + >>> itwinai scalability-report --pattern="^epoch.+\\.csv$" --skip-id 0 \\ + >>> --plot-title "Some title" --archive archive_name + """ + # TODO: add max depth and path different from CWD + import os + import re + import glob + import shutil + import pandas as pd + import matplotlib + import matplotlib.pyplot as plt + import numpy as np + + regex = re.compile(r'{}'.format(pattern)) + combined_df = pd.DataFrame() + csv_files = [] + for root, _, files in os.walk(os.getcwd()): + for file in files: + if regex.match(file): + fpath = os.path.join(root, file) + csv_files.append(fpath) + df = pd.read_csv(fpath) + if skip_id is not None: + df = df.drop(df[df.epoch_id == skip_id].index) + combined_df = pd.concat([combined_df, df]) + print("Merged CSV:") + print(combined_df) + + avg_times = ( + combined_df + .drop(columns='epoch_id') + .groupby(['name', 'nodes']) + .mean() + .reset_index() + ) + print("\nAvg over name and nodes:") + print(avg_times.rename(columns=dict(time='avg(time)'))) + + # fig, (sp_up_ax, eff_ax) = plt.subplots(1, 2, figsize=(12, 4)) + fig, sp_up_ax = plt.subplots(1, 1, figsize=(6, 4)) + if plot_title is not None: + fig.suptitle(plot_title) + + sp_up_ax.set_yscale("log") + sp_up_ax.set_xscale("log") + + markers = iter("ov^s*dXpD.+12348") + + series_names = sorted(set(avg_times.name.values)) + for name in series_names: + df = avg_times[avg_times.name == name].drop(columns='name') + + # Debug + # compute_time = [3791., 1884., 1011., 598.] + # nodes = [1, 2, 4, 8] + # d = {'nodes': nodes, 'time': compute_time} + # df = pd.DataFrame(data=d) + + df["NGPUs"] = df["nodes"]*4 + # speedup + df["Speedup - ideal"] = df["nodes"].astype(float) + df["Speedup"] = df["time"].iloc[0] / df["time"] + df["Nworkers"] = 1 + + # efficiency + df["Threadscaled Sim. Time / s"] = df["time"] * \ + df["nodes"] * df["Nworkers"] + df["Efficiency"] = df["Threadscaled Sim. Time / s"].iloc[0] / \ + df["Threadscaled Sim. Time / s"] + + sp_up_ax.plot( + df["NGPUs"].values, df["Speedup"].values, + marker=next(markers), lw=1.0, label=name, alpha=0.7) + + sp_up_ax.plot(df["NGPUs"].values, df["Speedup - ideal"].values, + ls='dashed', lw=1.0, c='k', label="ideal") + sp_up_ax.legend(ncol=1) + + sp_up_ax.set_xticks(df["NGPUs"].values) + sp_up_ax.get_xaxis().set_major_formatter( + matplotlib.ticker.ScalarFormatter()) + + sp_up_ax.set_ylabel('Speedup') + sp_up_ax.set_xlabel('NGPUs (4 per node)') + sp_up_ax.grid() + + # Sort legend + handles, labels = sp_up_ax.get_legend_handles_labels() + order = np.argsort(labels) + plt.legend([handles[idx] for idx in order], [labels[idx] for idx in order]) + + plot_png = f"scaling_plot_{plot_title}.png" + plt.tight_layout() + plt.savefig(plot_png, bbox_inches='tight', format='png', dpi=300) + print("Saved scaling plot to: ", plot_png) + + if archive is not None: + if '/' in archive: + raise ValueError("Archive name must NOT contain a path. " + f"Received: '{archive}'") + if '.' in archive: + raise ValueError("Archive name must NOT contain an extension. " + f"Received: '{archive}'") + if os.path.isdir(archive): + raise ValueError(f"Folder '{archive}' already exists. " + "Change archive name.") + os.makedirs(archive) + for csvfile in csv_files: + shutil.copyfile(csvfile, os.path.join(archive, + os.path.basename(csvfile))) + shutil.copyfile(plot_png, os.path.join(archive, plot_png)) + avg_times.to_csv(os.path.join(archive, "avg_times.csv"), index=False) + print("Archived AVG epoch times CSV") + + # Copy SLURM logs: *.err *.out files + if os.path.exists('logs_slurm'): + print("Archived SLURM logs") + shutil.copytree('logs_slurm', os.path.join(archive, 'logs_slurm')) + # Copy other SLURM logs + for ext in ['*.out', '*.err']: + for file in glob.glob(ext): + shutil.copyfile(file, os.path.join(archive, file)) + + # Create archive + archive_name = shutil.make_archive( + base_name=archive, # archive file name + format='gztar', + # root_dir='.', + base_dir=archive # folder path inside archive + ) + shutil.rmtree(archive) + print("Archived logs and plot at: ", archive_name) + + +@app.command() +def exec_pipeline( + config: Annotated[Path, typer.Option( + help="Path to the configuration file of the pipeline to execute." + )], + pipe_key: Annotated[str, typer.Option( + help=("Key in the configuration file identifying " + "the pipeline object to execute.") + )] = "pipeline", + steps: Annotated[Optional[str], typer.Option( + help=("Run only some steps of the pipeline. Accepted values are " + "indices, python slices (e.g., 0:3 or 2:10:100), and " + "string names of steps.") + )] = None, + print_config: Annotated[bool, typer.Option( + help=("Print config to be executed after overrides.") + )] = False, + overrides_list: Annotated[ + Optional[List[str]], typer.Option( + "--override", "-o", + help=( + "Nested key to dynamically override elements in the " + "configuration file with the " + "corresponding new value, joined by '='. It is also possible " + "to index elements in lists using their list index. " + "Example: [...] " + "-o pipeline.init_args.trainer.init_args.lr=0.001 " + "-o pipeline.my_list.2.batch_size=64 " + ) + ) + ] = None +): + """Execute a pipeline from configuration file. + Allows dynamic override of fields. + """ + # Add working directory to python path so that the interpreter is able + # to find the local python files imported from the pipeline file + import os + import sys + import re + from .utils import str_to_slice + sys.path.append(os.path.dirname(config)) + sys.path.append(os.getcwd()) + + # Parse and execute pipeline + from itwinai.parser import ConfigParser + overrides_list = overrides_list if overrides_list is not None else [] + overrides = { + k: v for k, v + in map(lambda x: (x.split('=')[0], x.split('=')[1]), overrides_list) + } + parser = ConfigParser(config=config, override_keys=overrides) + if print_config: + import json + print() + print("#="*15 + " Used configuration " + "#="*15) + print(json.dumps(parser.config, indent=2)) + print("#="*50) + print() + pipeline = parser.parse_pipeline(pipeline_nested_key=pipe_key) + if steps: + if not re.match(r"\d+(:\d+)?(:\d+)?", steps): + print(f"Looking for step name '{steps}'") + else: + steps = str_to_slice(steps) + pipeline = pipeline[steps] + pipeline.execute() + + # Cleanup PYTHONPATH + sys.path.pop() + sys.path.pop() + + +@app.command() +def mlflow_ui( + path: str = typer.Option("ml-logs/", help="Path to logs storage."), +): + """ + Visualize Mlflow logs. + """ + import subprocess + + subprocess.run(f"mlflow ui --backend-store-uri {path}".split()) + + +@app.command() +def mlflow_server( + path: str = typer.Option("ml-logs/", help="Path to logs storage."), + port: int = typer.Option( + 5000, help="Port on which the server is listening."), +): + """ + Spawn Mlflow server. + """ + import subprocess + + subprocess.run( + f"mlflow server --backend-store-uri {path} --port {port}".split()) + + +@app.command() +def kill_mlflow_server( + port: int = typer.Option( + 5000, help="Port on which the server is listening."), +): + """ + Kill Mlflow server. + """ + import subprocess + + subprocess.run( + f"kill -9 $(lsof -t -i:{port})", + shell=True, + check=True, + stderr=subprocess.DEVNULL + ) + + +if __name__ == "__main__": + app() diff --git a/src/itwinai/components.py b/src/itwinai/components.py new file mode 100644 index 00000000..92ee850e --- /dev/null +++ b/src/itwinai/components.py @@ -0,0 +1,460 @@ +""" +This module provides the base classes to define modular and reproducible ML +workflows. The base component classes provide a template to follow for +extending existing components or creating new ones. + +There are two ways of creating workflows: simple and advanced workflows. + +Simple workflows can be obtained by creating a sequence of components +wrapped in a Pipeline object, which executes them in cascade, passing the +output of a component as the input of the following one. It is responsibility +of the user to prevent mismatches among outputs and inputs of component +sequences. This pipeline can be configured +both in terms of parameters and structure, with a configuration file +representing the whole pipeline. This configuration file can be executed +using itwinai CLI without the need of python files. + +Example: + +>>> from itwinai.components import DataGetter, Saver +>>> from itwinai.pipeline import Pipeline +>>> +>>> my_pipe = Pipeline({"getter": DataGetter(...), "data_saver": Saver(...)}) +>>> my_pipe.execute() +>>> my_pipe.to_yaml("training_pipe.yaml") +>>> +>>> # The pipeline can be parsed back to Python with: +>>> from itwinai.parser import PipeParser +>>> my_pipe = PipeParser("training_pipe.yaml") +>>> my_pipe.execute() +>>> +>>> # Run the pipeline from configuration file with dynamic override +>>> itwinai exec-pipeline --config training_pipe.yaml \ +>>> --override pipeline.init_args.steps.data_saver.some_param 42 + + +Advanced workflows foresee more complicated connections between the +components and it is very difficult to define a structure beforehand +without risking of over-constraining the user. Therefore, advanced +workflows are defined by explicitly connecting component outputs to +to the inputs of other components, without a wrapper Pipeline object. +In this case, the configuration files enable the user to persist the +parameters passed to the argument parser, enabling reuse through +configuration files, with the possibility of dynamic overrides of parameters. + +Example: + +>>> from jsonargparse import ArgumentParser, ActionConfigFile +>>> +>>> parser = ArgumentParser(description='PyTorch MNIST Example') +>>> parser.add_argument('--batch-size', type=int, default=64, +>>> help='input batch size for training (default: 64)') +>>> parser.add_argument('--epochs', type=int, default=10, +>>> help='number of epochs to train (default: 10)') +>>> parser.add_argument('--lr', type=float, default=0.01, +>>> help='learning rate (default: 0.01)') +>>> parser.add_argument( +>>> "-c", "--config", action=ActionConfigFile, +>>> required=True, +>>> help="Path to a configuration file in json or yaml format." +>>> ) +>>> args = parser.parse_args() +>>> +>>> from itwinai.components import ( +>>> DataGetter, Saver, DataSplitter, Trainer +>>> ) +>>> getter = DataGetter(...) +>>> splitter = DataSplitter(...) +>>> data_saver = Saver(...) +>>> model_saver = Saver(...) +>>> trainer = Trainer( +>>> batch_size=args.batch_size, lr=args.lr, epochs=args.epochs +>>> ) +>>> +>>> # Compose workflow +>>> my_dataset = getter.execute() +>>> train_set, valid_set, test_set = splitter.execute(my_dataset) +>>> data_saver.execute("train_dataset.pkl", test_set) +>>> _, _, _, trained_model = trainer(train_set, valid_set) +>>> model_saver.execute(trained_model) +>>> +>>> # Run the script using a previous configuration with dynamic override +>>> python my_train.py --config training_pipe.yaml --lr 0.002 +""" + + +from __future__ import annotations +from typing import Any, Optional, Tuple, Union, Callable, Dict, List +from abc import ABC, abstractmethod +import time +import functools +# import logging +# from logging import Logger as PythonLogger + +from .type import MLModel, MLDataset, MLArtifact +from .serialization import ModelLoader, Serializable +from .distributed import ( + detect_distributed_environment, + distributed_patch_print +) + + +def monitor_exec(method: Callable) -> Callable: + """Decorator for execute method of a component class. + Computes execution time and gives some information about + the execution of the component. + + Args: + func (Callable): class method. + """ + @functools.wraps(method) + def monitored_method(self: BaseComponent, *args, **kwargs) -> Any: + # Disable print in workers different from the main one, + # when in distributed environments. + dist_grank = detect_distributed_environment().global_rank + distributed_patch_print(is_main=dist_grank == 0) + + msg = f"Starting execution of '{self.name}'..." + self._printout(msg) + start_t = time.time() + try: + # print(f'ARGS: {args}') + # print(f'KWARGS: {kwargs}') + result = method(self, *args, **kwargs) + finally: + self.cleanup() + self.exec_t = time.time() - start_t + msg = f"'{self.name}' executed in {self.exec_t:.3f}s" + self._printout(msg) + return result + + return monitored_method + + +class BaseComponent(ABC, Serializable): + """Base component class. Each component provides a simple interface + to foster modularity in machine learning code. Each component class + implements the `execute` method, which received some input ML artifacts + (e.g., datasets), performs some operations and returns new artifacts. + The components are meant to be assembled in complex ML workflows, + represented as pipelines. + + Args: + name (Optional[str], optional): unique identifier for a step. + Defaults to None. + """ + _name: str = None + parameters: Dict[Any, Any] = None + + def __init__( + self, + name: Optional[str] = None, + # logs_dir: Optional[str] = None, + # debug: bool = False, + ) -> None: + self.save_parameters(**self.locals2params(locals())) + self.name = name + + @property + def name(self) -> str: + return ( + self._name if self._name is not None else self.__class__.__name__ + ) + + @name.setter + def name(self, name: str) -> None: + self._name = name + + @abstractmethod + @monitor_exec + def execute(self, *args, **kwargs) -> Any: + """"Execute some operations.""" + + # def setup_console(self): + # """Setup Python logging""" + # self.log_file = os.path.join(self.logs_dir, self.name + ".log") + # f_handler = logging.FileHandler(self.log_file, mode='w') + # stdout_h = logging.StreamHandler(sys.stdout) + + # if self.debug: + # log_format = ("%(asctime)s %(levelname)s " + # "[%(filename)s:%(lineno)s - %(funcName)s()]: " + # "%(message)s") + # else: + # log_format = ("%(levelname)s : %(message)s") + + # logging.basicConfig( + # level=logging.DEBUG if self.debug else logging.INFO, + # handlers=[f_handler, stdout_h], + # format=log_format, + # datefmt="%Y-%m-%d %H:%M:%S" + # ) + # self.console = logging.getLogger(self.name) + + def cleanup(self): + """Cleanup resources allocated by this component.""" + + @staticmethod + def _printout(msg: str): + msg = f"# {msg} #" + print("#"*len(msg)) + print(msg) + print("#"*len(msg)) + + +class Trainer(BaseComponent): + """Trains a machine learning model.""" + + @abstractmethod + @monitor_exec + def execute( + self, + train_dataset: MLDataset, + validation_dataset: MLDataset, + test_dataset: MLDataset + ) -> Tuple[MLDataset, MLDataset, MLDataset, MLModel]: + """Trains a machine learning model. + + Args: + train_dataset (MLDataset): training dataset. + validation_dataset (MLDataset): validation dataset. + test_dataset (MLDataset): test dataset. + + Returns: + Tuple[MLDataset, MLDataset, MLDataset]: training dataset, + validation dataset, test dataset, trained model. + """ + + +class Predictor(BaseComponent): + """Applies a pre-trained machine learning model to unseen data.""" + + model: MLModel + + def __init__( + self, + model: Union[MLModel, ModelLoader], + name: Optional[str] = None, + ) -> None: + super().__init__(name=name) + self.save_parameters(**self.locals2params(locals())) + self.model = model() if isinstance(model, ModelLoader) else model + + @abstractmethod + @monitor_exec + def execute( + self, + predict_dataset: MLDataset, + model: Optional[MLModel] = None + ) -> MLDataset: + """Applies a machine learning model on a dataset of samples. + + Args: + predict_dataset (MLDataset): dataset for inference. + model (Optional[MLModel], optional): overrides the internal model, + if given. Defaults to None. + + Returns: + MLDataset: predictions with the same cardinality of the + input dataset. + """ + + +class DataGetter(BaseComponent): + """Retrieves a dataset.""" + + @abstractmethod + @monitor_exec + def execute(self) -> MLDataset: + """Retrieves a dataset. + + Returns: + MLDataset: retrieved dataset. + """ + + +class DataProcessor(BaseComponent): + """Performs dataset pre-processing.""" + + @abstractmethod + @monitor_exec + def execute( + self, + train_dataset: MLDataset, + validation_dataset: MLDataset, + test_dataset: MLDataset + ) -> Tuple[MLDataset, MLDataset, MLDataset]: + """Trains a machine learning model. + + Args: + train_dataset (MLDataset): training dataset. + validation_dataset (MLDataset): validation dataset. + test_dataset (MLDataset): test dataset. + + Returns: + Tuple[MLDataset, MLDataset, MLDataset]: preprocessed training + dataset, validation dataset, test dataset. + """ + + +class Saver(BaseComponent): + """Saves artifact to disk.""" + + @abstractmethod + @monitor_exec + def execute(self, artifact: MLArtifact) -> MLArtifact: + """Saves an ML artifact to disk. + + Args: + artifact (MLArtifact): artifact to save. + + Returns: + MLArtifact: the same input artifact, after saving it. + """ + + +class Adapter(BaseComponent): + """Connects to components in a sequential pipeline, allowing to + control with greater detail how intermediate results are propagated + among the components. + + Args: + policy (List[Any]): list of the same length of the output of this + component, describing how to map the input args to the output. + name (Optional[str], optional): name of the component. + Defaults to None. + + The adapter allows to define a policy with which inputs are re-arranged + before being propagated to the next component. + Some examples: [policy]: (input) -> (output) + - ["INPUT_ARG#2", "INPUT_ARG#1", "INPUT_ARG#0"]: (11,22,33) -> (33,22,11) + - ["INPUT_ARG#0", "INPUT_ARG#2", None]: (11, 22, 33) -> (11, 33, None) + - []: (11, 22, 33) -> () + - [42, "INPUT_ARG#2", "hello"] -> (11,22,33,44,55) -> (42, 33, "hello") + - [None, 33, 3.14]: () -> (None, 33, 3.14) + - [None, 33, 3.14]: ("double", 44, None, True) -> (None, 33, 3.14) + """ + + policy: List[Any] + INPUT_PREFIX: str = "INPUT_ARG#" + + def __init__(self, policy: List[Any], name: Optional[str] = None) -> None: + super().__init__(name=name) + self.save_parameters(**self.locals2params(locals())) + self.name = name + self.policy = policy + + @monitor_exec + def execute(self, *args) -> Tuple: + """Produces an output tuple by arranging input arguments according + to the policy specified in the constructor. + + Args: + args (Tuple): input arguments. + + Returns: + Tuple: input args arranged according to some policy. + """ + result = [] + for itm in self.policy: + if isinstance(itm, str) and itm.startswith(self.INPUT_PREFIX): + arg_idx = int(itm[len(self.INPUT_PREFIX):]) + if arg_idx >= len(args): + max_idx = max(map( + lambda itm: int(itm[len(self.INPUT_PREFIX):]), + filter( + lambda el: ( + isinstance(el, str) + and el.startswith(self.INPUT_PREFIX) + ), + self.policy + ))) + raise IndexError( + f"The args received as input by '{self.name}' " + "are not consistent with the given adapter policy " + "because input args are too few! " + f"Input args are {len(args)} but the policy foresees " + f"at least {max_idx+1} items." + ) + result.append(args[arg_idx]) + else: + result.append(itm) + return tuple(result) + + +class DataSplitter(BaseComponent): + """Splits a dataset into train, validation, and test splits.""" + _train_proportion: Union[int, float] + _validation_proportion: Union[int, float] + _test_proportion: Union[int, float] + + def __init__( + self, + train_proportion: Union[int, float], + validation_proportion: Union[int, float], + test_proportion: Union[int, float], + name: Optional[str] = None + ) -> None: + super().__init__(name) + self.save_parameters(**self.locals2params(locals())) + self.train_proportion = train_proportion + self.validation_proportion = validation_proportion + self.test_proportion = test_proportion + + @property + def train_proportion(self) -> Union[int, float]: + """Training set proportion.""" + return self._train_proportion + + @train_proportion.setter + def train_proportion(self, prop: Union[int, float]) -> None: + if isinstance(prop, float) and not 0.0 <= prop <= 1.0: + raise ValueError( + "Train proportion should be in the interval [0.0, 1.0] " + f"if given as float. Received {prop}" + ) + self._train_proportion = prop + + @property + def validation_proportion(self) -> Union[int, float]: + """Validation set proportion.""" + return self._validation_proportion + + @validation_proportion.setter + def validation_proportion(self, prop: Union[int, float]) -> None: + if isinstance(prop, float) and not 0.0 <= prop <= 1.0: + raise ValueError( + "Validation proportion should be in the interval [0.0, 1.0] " + f"if given as float. Received {prop}" + ) + self._validation_proportion = prop + + @property + def test_proportion(self) -> Union[int, float]: + """Test set proportion.""" + return self._test_proportion + + @test_proportion.setter + def test_proportion(self, prop: Union[int, float]) -> None: + if isinstance(prop, float) and not 0.0 <= prop <= 1.0: + raise ValueError( + "Test proportion should be in the interval [0.0, 1.0] " + f"if given as float. Received {prop}" + ) + self._test_proportion = prop + + @abstractmethod + @monitor_exec + def execute( + self, + dataset: MLDataset + ) -> Tuple[MLDataset, MLDataset, MLDataset]: + """Splits a dataset into train, validation and test splits. + + Args: + dataset (MLDataset): input dataset. + + Returns: + Tuple[MLDataset, MLDataset, MLDataset]: tuple of + train, validation and test splits. + """ diff --git a/src/itwinai/distributed.py b/src/itwinai/distributed.py new file mode 100644 index 00000000..8a152263 --- /dev/null +++ b/src/itwinai/distributed.py @@ -0,0 +1,66 @@ +import abc +import os +from pydantic import BaseModel + + +class DistributedStrategy(abc.ABC): + """Abstract class to define the distributed backend methods.""" + + +class ClusterEnvironment(BaseModel): + """Stores information about distributed environment.""" + global_rank: int = 0 + local_rank: int = 0 + local_world_size: int = 1 + global_world_size: int = 1 + + +def detect_distributed_environment() -> ClusterEnvironment: + """Detects distributed environment, extracting information like + global ans local ranks, and world size. + """ + if os.getenv('TORCHELASTIC_RUN_ID') is not None: + # Torch elastic environment + # https://pytorch.org/docs/stable/elastic/run.html#environment-variables + return ClusterEnvironment( + global_rank=os.getenv('RANK'), + local_rank=os.getenv('LOCAL_RANK'), + local_world_size=os.getenv('LOCAL_WORLD_SIZE'), + global_world_size=os.getenv('WORLD_SIZE') + ) + elif os.getenv('OMPI_COMM_WORLD_SIZE') is not None: + # Open MPI environment + # https://docs.open-mpi.org/en/v5.0.x/tuning-apps/environment-var.html + return ClusterEnvironment( + global_rank=os.getenv('OMPI_COMM_WORLD_RANK'), + local_rank=os.getenv('OMPI_COMM_WORLD_LOCAL_RANK'), + local_world_size=os.getenv('OMPI_COMM_WORLD_LOCAL_SIZE'), + global_world_size=os.getenv('OMPI_COMM_WORLD_SIZE') + ) + elif os.getenv('SLURM_JOB_ID') is not None: + print("WARNING: detected SLURM environment, but " + "unable to determine ranks and world sizes!") + return ClusterEnvironment() + else: + return ClusterEnvironment() + + +def distributed_patch_print(is_main: bool) -> None: + """Disable ``print()`` when not in main worker. + + Args: + is_main (bool): whether it is called from main worker. + """ + import builtins as __builtin__ + builtin_print = __builtin__.print + + def print(*args, **kwself): + """Print is disables on workers different from + the main one, unless the print is called with + ``force=True`` argument. + """ + force = kwself.pop('force', False) + if is_main or force: + builtin_print(*args, **kwself) + + __builtin__.print = print diff --git a/src/itwinai/loggers.py b/src/itwinai/loggers.py new file mode 100644 index 00000000..fc3fd5dc --- /dev/null +++ b/src/itwinai/loggers.py @@ -0,0 +1,724 @@ +"""Abstraction layer for loggers.""" +import os +import csv +from abc import ABCMeta, abstractmethod +from contextlib import contextmanager +from typing import Any, Dict, List, Optional, Union, Literal +import pickle +import pathlib + +import wandb +import mlflow + +BASE_EXP_NAME: str = 'default_experiment' + + +class LogMixin(metaclass=ABCMeta): + @abstractmethod + def log( + self, + item: Union[Any, List[Any]], + identifier: Union[str, List[str]], + kind: str = 'metric', + step: Optional[int] = None, + batch_idx: Optional[int] = None, + **kwargs + ) -> None: + """Log ``item`` with ``identifier`` name of ``kind`` type at ``step`` + time step. + + Args: + item (Union[Any, List[Any]]): element to be logged (e.g., metric). + identifier (Union[str, List[str]]): unique identifier for the + element to log(e.g., name of a metric). + kind (str, optional): type of the item to be logged. Must be one + among the list of self.supported_types. Defaults to 'metric'. + step (Optional[int], optional): logging step. Defaults to None. + batch_idx (Optional[int], optional): DataLoader batch counter + (i.e., batch idx), if available. Defaults to None. + """ + + +class Logger(LogMixin, metaclass=ABCMeta): + """Base class for logger + + Args: + savedir (str, optional): filesystem location where logs are stored. + Defaults to 'mllogs'. + log_freq (Union[int, Literal['epoch', 'batch']], + optional): how often should the logger fulfill calls to the `log()` + method: + - When set to ``'epoch'``, the logger logs only if + ``batch_idx`` is not passed to the ``log`` method. + - When an integer + is given, the logger logs if ``batch_idx`` is a multiple of + ``log_freq``. + - When set to ``'batch'``, the logger logs always. + + Defaults to ``'epoch'``. + """ + savedir: str = None + supported_types: List[str] # Supported logging 'kinds' + _log_freq: Union[int, Literal['epoch', 'batch']] + + def __init__( + self, + savedir: str = 'mllogs', + log_freq: Union[int, Literal['epoch', 'batch']] = 'epoch' + ) -> None: + self.savedir = savedir + self.log_freq = log_freq + + @property + def log_freq(self) -> Union[int, Literal['epoch', 'batch']]: + """Get ``log_feq``, namely how often should the logger + fulfill or ignore calls to the `log()` method.""" + return self._log_freq + + @log_freq.setter + def log_freq(self, val: Union[int, Literal['epoch', 'batch']]): + """Sanitize log_freq value.""" + if val in ['epoch', 'batch'] or (isinstance(val, int) and val > 0): + self._log_freq = val + else: + raise ValueError( + "Wrong value for 'log_freq'. Supported values are: " + f"['epoch', 'batch'] or int > 0. Received: {val}" + ) + + @contextmanager + def start_logging(self): + """Start logging context. + + Example: + + + >>> with my_logger.start_logging(): + >>> my_logger.log(123, 'value', kind='metric', step=0) + + + """ + try: + self.create_logger_context() + yield + finally: + self.destroy_logger_context() + + @abstractmethod + def create_logger_context(self): + """Initialize logger.""" + + @abstractmethod + def destroy_logger_context(self): + """Destroy logger.""" + + @abstractmethod + def save_hyperparameters(self, params: Dict[str, Any]) -> None: + """Save hyperparameters. + + Args: + params (Dict[str, Any]): hyperparameters dictionary. + """ + + def serialize(self, obj: Any, identifier: str) -> str: + """Serializes object to disk and returns its path. + + Args: + obj (Any): item to save. + identifier (str): identifier of the item to log (expected to be a + path under ``self.savedir``). + + Returns: + str: local path of the serialized object to be logged. + """ + itm_path = os.path.join(self.savedir, identifier) + with open(itm_path, 'wb') as itm_file: + pickle.dump(obj, itm_file) + + def should_log( + self, + batch_idx: Optional[int] = None + ) -> bool: + """Determines whether the logger should fulfill or ignore calls to the + `log()` method, depending on the ``log_freq`` property: + - When ``log_freq`` is set to ``'epoch'``, the logger logs only if + ``batch_idx`` is not passed to the ``log`` method. + - When ``log_freq`` is an integer + is given, the logger logs if ``batch_idx`` is a multiple of + ``log_freq``. + - When ``log_freq`` is set to ``'batch'``, the logger logs always. + + Args: + batch_idx (Optional[int]): the dataloader batch idx, if available. + Defaults to None. + + Returns: + bool: True if the logger should log, False otherwise. + """ + if batch_idx is not None: + if isinstance(self.log_freq, int): + if batch_idx % self.log_freq == 0: + return True + return False + if self.log_freq == 'batch': + return True + return False + return True + + +class ConsoleLogger(Logger): + """Simplified logger. + + Args: + savedir (str, optional): where to store artifacts. + Defaults to 'mllogs'. + log_freq (Union[int, Literal['epoch', 'batch']], + optional): determines whether the logger should fulfill or ignore + calls to the `log()` method. See ``should_log`` method for more + details. Defaults to ``'epoch'``. + """ + + def __init__( + self, + savedir: str = 'mllogs', + log_freq: Union[int, Literal['epoch', 'batch']] = 'epoch' + ) -> None: + savedir = os.path.join(savedir, 'simple-logger') + super().__init__(savedir=savedir, log_freq=log_freq) + self.supported_types = ['torch', 'artifact'] + + def create_logger_context(self): + """Initialize logger.""" + os.makedirs(self.savedir, exist_ok=True) + run_dirs = sorted([int(dir) for dir in os.listdir(self.savedir)]) + if len(run_dirs) == 0: + self.run_id = 0 + else: + self.run_id = int(run_dirs[-1]) + 1 + self.run_path = os.path.join(self.savedir, str(self.run_id)) + os.makedirs(self.run_path) + + def destroy_logger_context(self): + """Destroy logger. Do nothing.""" + + def save_hyperparameters(self, params: Dict[str, Any]) -> None: + """Save hyperparameters. Do nothing. + + Args: + params (Dict[str, Any]): hyperparameters dictionary. + """ + + def log( + self, + item: Union[Any, List[Any]], + identifier: Union[str, List[str]], + kind: str = 'metric', + step: Optional[int] = None, + batch_idx: Optional[int] = None, + **kwargs + ) -> None: + """Print metrics to stdout and save artifacts to the filesystem. + + Args: + item (Union[Any, List[Any]]): element to be logged (e.g., metric). + identifier (Union[str, List[str]]): unique identifier for the + element to log(e.g., name of a metric). + kind (str, optional): type of the item to be logged. Must be one + among the list of self.supported_types. Defaults to 'metric'. + step (Optional[int], optional): logging step. Defaults to None. + batch_idx (Optional[int], optional): DataLoader batch counter + (i.e., batch idx), if available. Defaults to None. + """ + if not self.should_log(batch_idx=batch_idx): + return + + if kind == 'artifact': + if isinstance(item, str) and os.path.isfile(item): + import shutil + identifier = os.path.join( + self.run_path, + identifier + ) + print(f"ConsoleLogger: Serializing to {identifier}...") + shutil.copyfile(item, identifier) + else: + identifier = os.path.join( + os.path.basename(self.run_path), + identifier + ) + print(f"ConsoleLogger: Serializing to {identifier}...") + self.serialize(item, identifier) + elif kind == 'torch': + identifier = os.path.join(self.run_path, identifier) + print(f"ConsoleLogger: Saving to {identifier}...") + import torch + torch.save(item, identifier) + else: + print(f"ConsoleLogger: {identifier} = {item}") + + +class MLFlowLogger(Logger): + """Abstraction around MLFlow logger. + + Args: + savedir (str, optional): path on local filesystem where logs are + stored. Defaults to 'mllogs'. + experiment_name (str, optional): experiment name. Defaults to + ``itwinai.loggers.BASE_EXP_NAME``. + tracking_uri (Optional[str], optional): MLFLow tracking URI. + Overrides ``savedir`` if given. Defaults to None. + run_description (Optional[str], optional): run description. + Defaults to None. + log_freq (Union[int, Literal['epoch', 'batch']], + optional): determines whether the logger should fulfill or ignore + calls to the `log()` method. See ``should_log`` method for more + details. Defaults to ``'epoch'``. + """ + + active_run: mlflow.ActiveRun + + def __init__( + self, + savedir: str = 'mllogs', + experiment_name: str = BASE_EXP_NAME, + tracking_uri: Optional[str] = None, + run_description: Optional[str] = None, + log_freq: Union[int, Literal['epoch', 'batch']] = 'epoch' + ): + savedir = os.path.join(savedir, 'mlflow') + super().__init__(savedir=savedir, log_freq=log_freq) + self.experiment_name = experiment_name + self.tracking_uri = tracking_uri + self.run_description = run_description + + if self.tracking_uri is None: + # Default MLFLow tracking URI + saved_abs_path = os.path.abspath(self.savedir) + self.tracking_uri = pathlib.Path(saved_abs_path).as_uri() + + # TODO: for pytorch lightning: + # mlflow.pytorch.autolog() + + self.supported_types = [ + 'metric', 'figure', 'image', 'artifact', 'torch', 'dict', 'param', + 'text' + ] + + def create_logger_context(self): + """Initialize logger. Start MLFLow run.""" + mlflow.set_tracking_uri(self.tracking_uri) + mlflow.set_experiment(experiment_name=self.experiment_name) + self.active_run: mlflow.ActiveRun = mlflow.start_run( + description=self.run_description + ) + + def destroy_logger_context(self): + """Destroy logger. End current MLFlow run.""" + mlflow.end_run() + + def save_hyperparameters(self, params: Dict[str, Any]) -> None: + """Save hyperparameters as MLFlow parameters. + + Args: + params (Dict[str, Any]): hyperparameters dictionary. + """ + for param_name, val in params.items(): + self.log(item=val, identifier=param_name, step=0, kind='param') + + def log( + self, + item: Union[Any, List[Any]], + identifier: Union[str, List[str]], + kind: str = 'metric', + step: Optional[int] = None, + batch_idx: Optional[int] = None, + **kwargs + ) -> None: + """Log with MLFlow. + + Args: + item (Union[Any, List[Any]]): element to be logged (e.g., metric). + identifier (Union[str, List[str]]): unique identifier for the + element to log(e.g., name of a metric). + kind (str, optional): type of the item to be logged. Must be one + among the list of self.supported_types. Defaults to 'metric'. + step (Optional[int], optional): logging step. Defaults to None. + batch_idx (Optional[int], optional): DataLoader batch counter + (i.e., batch idx), if available. Defaults to None. + """ + if not self.should_log(batch_idx=batch_idx): + return + + if kind == 'metric': + # if isinstance(item, list) and isinstance(identifier, list): + mlflow.log_metric( + key=identifier, + value=item, + step=step + ) + if kind == 'artifact': + if not isinstance(item, str): + # Save the object locally and then log it + name = os.path.basename(identifier) + save_path = os.path.join(self.savedir, '.trash', name) + os.makedirs(os.path.dirname(save_path), exist_ok=True) + item = self.serialize(item, save_path) + mlflow.log_artifact( + local_path=item, + artifact_path=identifier + ) + if kind == 'torch': + import torch + # Save the object locally and then log it + name = os.path.basename(identifier) + save_path = os.path.join(self.savedir, '.trash', name) + os.makedirs(os.path.dirname(save_path), exist_ok=True) + torch.save(item, save_path) + # Log into mlflow + mlflow.log_artifact( + local_path=save_path, + artifact_path=identifier + ) + if kind == 'dict': + mlflow.log_dict( + dictionary=item, + artifact_file=identifier + ) + if kind == 'figure': + mlflow.log_figure( + artifact_file=identifier, + figure=item, + save_kwargs=kwargs.get('save_kwargs') + ) + if kind == 'image': + mlflow.log_image( + artifact_file=identifier, + image=item + ) + if kind == 'param': + mlflow.log_param( + key=identifier, + value=item + ) + if kind == 'text': + mlflow.log_text( + artifact_file=identifier, + text=item + ) + + +class WanDBLogger(Logger): + """Abstraction around WandB logger. + + Args: + savedir (str, optional): location on local filesystem where logs + are stored. Defaults to 'mllogs'. + project_name (str, optional): experiment name. Defaults to + ``itwinai.loggers.BASE_EXP_NAME``. + log_freq (Union[int, Literal['epoch', 'batch']], + optional): determines whether the logger should fulfill or ignore + calls to the `log()` method. See ``should_log`` method for more + details. Defaults to ``'epoch'``. + """ + + def __init__( + self, + savedir: str = 'mllogs', + project_name: str = BASE_EXP_NAME, + log_freq: Union[int, Literal['epoch', 'batch']] = 'epoch' + ) -> None: + savedir = os.path.join(savedir, 'wandb') + super().__init__(savedir=savedir, log_freq=log_freq) + self.project_name = project_name + self.supported_types = [ + 'watch', 'metric', 'figure', 'image', 'artifact', 'torch', 'dict', + 'param', 'text' + ] + + def create_logger_context(self): + """Initialize logger. Init WandB run.""" + self.active_run = wandb.init( + dir=self.savedir, + project=self.project_name + ) + + def destroy_logger_context(self): + """Destroy logger.""" + + def save_hyperparameters(self, params: Dict[str, Any]) -> None: + """Save hyperparameters. + + Args: + params (Dict[str, Any]): hyperparameters dictionary. + """ + wandb.config.update(params) + + def log( + self, + item: Union[Any, List[Any]], + identifier: Union[str, List[str]], + kind: str = 'metric', + step: Optional[int] = None, + batch_idx: Optional[int] = None, + **kwargs + ) -> None: + """Log with WandB. Wrapper of https://docs.wandb.ai/ref/python/log + + Args: + item (Union[Any, List[Any]]): element to be logged (e.g., metric). + identifier (Union[str, List[str]]): unique identifier for the + element to log(e.g., name of a metric). + kind (str, optional): type of the item to be logged. Must be one + among the list of self.supported_types. Defaults to 'metric'. + step (Optional[int], optional): logging step. Defaults to None. + batch_idx (Optional[int], optional): DataLoader batch counter + (i.e., batch idx), if available. Defaults to None. + """ + if not self.should_log(batch_idx=batch_idx): + return + + if kind == 'watch': + wandb.watch(item) + if kind in self.supported_types[1:]: + wandb.log({identifier: item}, step=step, commit=True) + + +class TensorBoardLogger(Logger): + """Abstraction around TensorBoard logger, both for PyTorch and + TensorFlow. + + Args: + savedir (str, optional): location on local filesystem where logs + are stored. Defaults to 'mllogs'. + log_freq (Union[int, Literal['epoch', 'batch']], + optional): determines whether the logger should fulfill or ignore + calls to the `log()` method. See ``should_log`` method for more + details. Defaults to ``'epoch'``. + framework (Literal['tensorflow', 'pytorch'], + optional): whether to log PyTorch or TensorFlow ML data. + Defaults to 'pytorch'. + + Raises: + ValueError: when ``framework`` is not recognized. + """ + + # TODO: decouple the logger into TorchTBLogger and TFTBLogger + # and add the missing logging types supported by each. + + def __init__( + self, + savedir: str = 'mllogs', + log_freq: Union[int, Literal['epoch', 'batch']] = 'epoch', + framework: Literal['tensorflow', 'pytorch'] = 'pytorch' + ) -> None: + savedir = os.path.join(savedir, 'tensorboard') + super().__init__(savedir=savedir, log_freq=log_freq) + self.framework = framework + if framework.lower() == 'tensorflow': + import tensorflow as tf + self.tf = tf + self.writer = tf.summary.create_file_writer(savedir) + elif framework.lower() == 'pytorch': + from torch.utils.tensorboard import SummaryWriter + self.writer = SummaryWriter(savedir) + else: + raise ValueError( + "Framework must be either 'tensorflow' or 'pytorch'") + self.supported_types = ['metric', 'image', + 'text', 'figure', 'torch'] + + def create_logger_context(self): + """Initialize logger.""" + if self.framework == 'tensorflow': + self.writer.set_as_default() + + def destroy_logger_context(self): + """Destroy logger. Close SummaryWriter.""" + self.writer.close() + + def save_hyperparameters(self, params: Dict[str, Any]) -> None: + """Save hyperparameters. + + Args: + params (Dict[str, Any]): hyperparameters dictionary. + """ + if self.framework == 'tensorflow': + from tensorboard.plugins.hparams import api as hp + hparams = {hp.HParam(k): v for k, v in params.items()} + with self.writer.as_default(): + hp.hparams(hparams) + elif self.framework == 'pytorch': + self.writer.add_hparams(params, {}) + + def log( + self, + item: Union[Any, List[Any]], + identifier: Union[str, List[str]], + kind: str = 'metric', + step: Optional[int] = None, + batch_idx: Optional[int] = None, + **kwargs + ) -> None: + """Log with Tensorboard. + + Args: + item (Union[Any, List[Any]]): element to be logged (e.g., metric). + identifier (Union[str, List[str]]): unique identifier for the + element to log(e.g., name of a metric). + kind (str, optional): type of the item to be logged. Must be one + among the list of self.supported_types. Defaults to 'metric'. + step (Optional[int], optional): logging step. Defaults to None. + batch_idx (Optional[int], optional): DataLoader batch counter + (i.e., batch idx), if available. Defaults to None. + """ + if not self.should_log(batch_idx=batch_idx): + return + + if self.framework == 'tensorflow': + with self.writer.as_default(): + if kind == 'metric': + self.tf.summary.scalar(identifier, item, step=step) + elif kind == 'image': + self.tf.summary.image(identifier, item, step=step) + elif kind == 'text': + self.tf.summary.text(identifier, item, step=step) + elif kind == 'figure': + self.tf.summary.figure(identifier, item, step=step) + elif self.framework == 'pytorch': + if kind == 'metric': + self.writer.add_scalar(identifier, item, global_step=step) + elif kind == 'image': + self.writer.add_image(identifier, item, global_step=step) + elif kind == 'text': + self.writer.add_text(identifier, item, global_step=step) + elif kind == 'figure': + self.writer.add_figure(identifier, item, global_step=step) + elif kind == 'torch': + self.writer.add_graph(item) + + +class LoggersCollection(Logger): + """Wrapper of a set of loggers, allowing to use them simultaneously. + + Args: + loggers (List[Logger]): list of itwinai loggers. + """ + + def __init__( + self, + loggers: List[Logger] + ) -> None: + super().__init__(savedir='/tmp/mllogs_LoggersCollection', log_freq=1) + self.loggers = loggers + + def should_log(self, batch_idx: int = None) -> bool: + """Transparent method which delegates the ``should_log`` + to individual loggers. Always returns True. + + Args: + batch_idx (int, optional): dataloader batch index. + Defaults to None. + + Returns: + bool: always True. + """ + return True + + def log( + self, + item: Union[Any, List[Any]], + identifier: Union[str, List[str]], + kind: str = 'metric', + step: Optional[int] = None, + batch_idx: Optional[int] = None, + **kwargs + ) -> None: + """Log on all loggers. + + Args: + item (Union[Any, List[Any]]): element to be logged (e.g., metric). + identifier (Union[str, List[str]]): unique identifier for the + element to log(e.g., name of a metric). + kind (str, optional): type of the item to be logged. Must be one + among the list of self.supported_types. Defaults to 'metric'. + step (Optional[int], optional): logging step. Defaults to None. + batch_idx (Optional[int], optional): DataLoader batch counter + (i.e., batch idx), if available. Defaults to None. + """ + for logger in self.loggers: + logger.log( + item=item, + identifier=identifier, + kind=kind, + step=step, + batch_idx=batch_idx, + **kwargs + ) + + def create_logger_context(self): + """Initialize all loggers.""" + for logger in self.loggers: + logger.create_logger_context() + + def destroy_logger_context(self): + """Destroy all loggers.""" + for logger in self.loggers: + logger.destroy_logger_context() + + def save_hyperparameters(self, params: Dict[str, Any]) -> None: + """Save hyperparameters for all loggers. + + Args: + params (Dict[str, Any]): hyperparameters dictionary. + """ + for logger in self.loggers: + logger.save_hyperparameters(params=params) + + +class EpochTimeTracker: + """Profiler for epoch execution time used to support scaling tests. + It uses CSV files to store, for each epoch, the ``name`` of the + experiment, the number of compute ``nodes`` used, the ``epoch_id``, + and the execution ``time`` in seconds. + + Args: + series_name (str): name of the experiment/job. + csv_file (str): path to CSV file to store experiments times. + """ + + def __init__(self, series_name: str, csv_file: str) -> None: + self.series_name = series_name + self._data = [] + self.csv_file = csv_file + with open(csv_file, 'w') as csvfile: + csvwriter = csv.writer(csvfile) + csvwriter.writerow(['name', 'nodes', 'epoch_id', 'time']) + + def add_epoch_time(self, epoch_idx: int, time: float) -> None: + """Add row to the current experiment's CSV file in append mode. + + Args: + epoch_idx (int): epoch order idx. + time (float): epoch execution time (seconds). + """ + n_nodes = os.environ.get('SLURM_NNODES', -1) + fields = (self.series_name, n_nodes, epoch_idx, time) + self._data.append(fields) + with open(self.csv_file, 'a') as csvfile: + csvwriter = csv.writer(csvfile) + csvwriter.writerow(fields) + + def save(self, csv_file: Optional[str] = None) -> None: + """Save data to a new CSV file. + + Args: + csv_file (Optional[str], optional): path to the CSV file. + If not given, uses the one given in the constructor. + Defaults to None. + """ + if not csv_file: + csv_file = self.csv_file + with open(csv_file, 'w') as csvfile: + csvwriter = csv.writer(csvfile) + csvwriter.writerow(['name', 'nodes', 'epoch_id', 'time']) + csvwriter.writerows(self._data) diff --git a/src/itwinai/parser.py b/src/itwinai/parser.py new file mode 100644 index 00000000..b7aa92ab --- /dev/null +++ b/src/itwinai/parser.py @@ -0,0 +1,253 @@ +""" +Provide functionalities to manage configuration files, including parsing, +execution, and dynamic override of fields. +""" + +import logging +import os +from typing import Dict, Any, List, Type, Union, Optional +from jsonargparse import ArgumentParser as JAPArgumentParser +from jsonargparse import ActionConfigFile +from jsonargparse._formatters import DefaultHelpFormatter + +import json +from omegaconf import OmegaConf +from pathlib import Path + +from .components import BaseComponent +from .pipeline import Pipeline +from .utils import load_yaml + + +def add_replace_field( + config: Dict, + key_chain: str, + value: Any +) -> None: + """Replace or add (if not present) a field in a dictionary, following a + path of dot-separated keys. Adding is not supported for list items. + Inplace operation. + + Args: + config (Dict): dictionary to be modified. + key_chain (str): path of nested (dot-separated) keys to specify the + location + of the new value (e.g., 'foo.bar.line' adds/overwrites the value + located at config['foo']['bar']['line']). + value (Any): the value to insert. + """ + sub_config = config + for idx, k in enumerate(key_chain.split('.')): + if idx >= len(key_chain.split('.')) - 1: + # Last key reached + break + + if isinstance(sub_config, (list, tuple)): + k = int(k) + next_elem = sub_config[k] + else: + next_elem = sub_config.get(k) + + if not isinstance(next_elem, (dict, list, tuple)): + sub_config[k] = dict() + + sub_config = sub_config[k] + if isinstance(sub_config, (list, tuple)): + k = int(k) + sub_config[k] = value + + +class ConfigParser: + """ + Parses a pipeline from a configuration file. + It also provides functionalities for dynamic override + of fields by means of nested key notation. + + Args: + config (Union[str, Dict]): path to YAML configuration file + or dict storing a configuration. + override_keys (Optional[Dict[str, Any]], optional): dict mapping + nested keys to the value to override. Defaults to None. + + Example: + + + >>> # pipeline.yaml file + >>> pipeline: + >>> class_path: itwinai.pipeline.Pipeline + >>> init_args: + >>> steps: + >>> - class_path: dataloader.MNISTDataModuleTorch + >>> init_args: + >>> save_path: .tmp/ + >>> + >>> - class_path: itwinai.torch.trainer.TorchTrainer + >>> init_args: + >>> model: + >>> class_path: model.Net + >>> + >>> from itwinai.parser import ConfigParser + >>> + >>> parser = ConfigParser( + >>> config='pipeline.yaml', + >>> override_keys={ + >>> 'pipeline.init_args.steps.0.init_args.save_path': /save/path + >>> } + >>> ) + >>> pipeline = parser.parse_pipeline() + >>> print(pipeline) + >>> print(pipeline.steps) + >>> + >>> dataloader = parser.parse_step(0) + >>> print(dataloader) + >>> print(dataloader.save_path) + + """ + + config: Dict + pipeline: Pipeline + + def __init__( + self, + config: Union[str, Dict], + override_keys: Optional[Dict[str, Any]] = None + ) -> None: + self.config = config + self.override_keys = override_keys + if isinstance(self.config, (str, Path)): + self.config = load_yaml(self.config) + self._dynamic_override_keys() + self._omegaconf_interpolate() + + def _dynamic_override_keys(self): + if self.override_keys is not None: + for key_chain, value in self.override_keys.items(): + add_replace_field(self.config, key_chain, value) + + def _omegaconf_interpolate(self) -> None: + """Performs variable interpolation with OmegaConf on internal + configuration file. + """ + conf = OmegaConf.create(self.config) + self.config = OmegaConf.to_container(conf, resolve=True) + + def parse_pipeline( + self, + pipeline_nested_key: str = "pipeline", + verbose: bool = False + ) -> Pipeline: + """Merges steps into pipeline and parses it. + + Args: + pipeline_nested_key (str, optional): nested key in the + configuration file identifying the pipeline object. + Defaults to "pipeline". + verbose (bool): if True, prints the assembled pipeline + to console formatted as JSON. + + Returns: + Pipeline: instantiated pipeline. + """ + pipe_parser = JAPArgumentParser() + pipe_parser.add_subclass_arguments(Pipeline, "pipeline") + + pipe_dict = self.config + for key in pipeline_nested_key.split('.'): + pipe_dict = pipe_dict[key] + # pipe_dict = self.config[pipeline_nested_key] + pipe_dict = {"pipeline": pipe_dict} + + if verbose: + print("Assembled pipeline:") + print(json.dumps(pipe_dict, indent=4)) + + # Parse pipeline dict once merged with steps + conf = pipe_parser.parse_object(pipe_dict) + pipe = pipe_parser.instantiate_classes(conf) + self.pipeline = pipe["pipeline"] + return self.pipeline + + def parse_step( + self, + step_idx: Union[str, int], + pipeline_nested_key: str = "pipeline", + verbose: bool = False + ) -> BaseComponent: + pipeline_dict = self.config + for key in pipeline_nested_key.split('.'): + pipeline_dict = pipeline_dict[key] + + step_dict_config = pipeline_dict['init_args']['steps'][step_idx] + + if verbose: + print(f"STEP '{step_idx}' CONFIG:") + print(json.dumps(step_dict_config, indent=4)) + + # Wrap config under "step" field and parse it + step_dict_config = {'step': step_dict_config} + step_parser = JAPArgumentParser() + step_parser.add_subclass_arguments(BaseComponent, "step") + parsed_namespace = step_parser.parse_object(step_dict_config) + return step_parser.instantiate_classes(parsed_namespace)["step"] + + +class ArgumentParser(JAPArgumentParser): + def __init__( + self, + *args, + env_prefix: Union[bool, str] = True, + formatter_class: Type[DefaultHelpFormatter] = DefaultHelpFormatter, + exit_on_error: bool = True, + logger: Union[bool, str, dict, logging.Logger] = False, + version: Optional[str] = None, + print_config: Optional[str] = "--print_config", + parser_mode: str = "yaml", + dump_header: Optional[List[str]] = None, + default_config_files: Optional[List[Union[str, os.PathLike]]] = None, + default_env: bool = False, + default_meta: bool = True, + **kwargs, + ) -> None: + """Initializer for ArgumentParser instance. It can parse arguments from + a series of configuration files. Example: + + >>> python main.py --config base-conf.yaml --config other-conf.yaml \\ + >>> --param OVERRIDE_VAL + + All the arguments from the initializer of `argparse.ArgumentParser + `_ + are supported. Additionally it accepts: + + Args: + env_prefix: Prefix for environment variables. ``True`` to derive + from ``prog``. + formatter_class: Class for printing help messages. + logger: Configures the logger, see :class:`.LoggerProperty`. + version: Program version which will be printed by the --version + argument. + print_config: Add this as argument to print config, set None to + disable. + parser_mode: Mode for parsing config files: ``'yaml'``, + ``'jsonnet'`` or ones added via :func:`.set_loader`. + dump_header: Header to include as comment when dumping a config + object. + default_config_files: Default config file locations, e.g. + :code:`['~/.config/myapp/*.yaml']`. + default_env: Set the default value on whether to parse environment + variables. + default_meta: Set the default value on whether to include metadata + in config objects. + """ + super().__init__( + *args, env_prefix=env_prefix, formatter_class=formatter_class, + exit_on_error=exit_on_error, logger=logger, version=version, + print_config=print_config, parser_mode=parser_mode, + dump_header=dump_header, default_config_files=default_config_files, + default_env=default_env, + default_meta=default_meta, **kwargs) + self.add_argument( + "-c", "--config", action=ActionConfigFile, + help="Path to a configuration file in json or yaml format." + ) + +# type: ignore diff --git a/src/itwinai/pipeline.py b/src/itwinai/pipeline.py new file mode 100644 index 00000000..1391bfef --- /dev/null +++ b/src/itwinai/pipeline.py @@ -0,0 +1,101 @@ +""" +This module provides the functionalities to execute workflows defined in +in form of pipelines. +""" +from __future__ import annotations +from typing import Iterable, Dict, Any, Tuple, Union, Optional + +from .components import BaseComponent, monitor_exec +from .utils import SignatureInspector + + +class Pipeline(BaseComponent): + """Executes a set of components arranged as a pipeline.""" + + steps: Union[Dict[str, BaseComponent], Iterable[BaseComponent]] + + def __init__( + self, + steps: Union[Dict[str, BaseComponent], Iterable[BaseComponent]], + name: Optional[str] = None + ): + super().__init__(name=name) + self.save_parameters(steps=steps, name=name) + self.steps = steps + + def __getitem__(self, subscript: Union[str, int, slice]) -> Pipeline: + if isinstance(subscript, slice): + # First, convert to list if is a dict + if isinstance(self.steps, dict): + steps = list(self.steps.items()) + else: + steps = self.steps + # Second, perform slicing + s = steps[subscript.start:subscript.stop: subscript.step] + # Third, reconstruct dict, if it is a dict + if isinstance(self.steps, dict): + s = dict(s) + # Fourth, return sliced sub-pipeline, preserving its + # initial structure + sliced = self.__class__( + steps=s, + name=self.name + ) + return sliced + else: + return self.steps[subscript] + + def __len__(self) -> int: + return len(self.steps) + + @monitor_exec + def execute(self, *args) -> Any: + """"Execute components sequentially.""" + if isinstance(self.steps, dict): + steps = list(self.steps.values()) + else: + steps = self.steps + + for step in steps: + step: BaseComponent + args = self._pack_args(args) + self.validate_args(args, step) + args = step.execute(*args) + + return args + + @staticmethod + def _pack_args(args) -> Tuple: + """Wraps args in a tuple, if needed.""" + args = () if args is None else args + if not isinstance(args, tuple): + args = (args,) + return args + + @staticmethod + def validate_args(input_args: Tuple, component: BaseComponent): + """Verify that the number of input args provided to some component + match with the number of the non-default args in the component. + + Args: + input_args (Tuple): input args to be fed to the component. + component (BaseComponent): component to be executed. + + Raises: + RuntimeError: in case of args mismatch. + """ + inspector = SignatureInspector(component.execute) + if inspector.min_params_num > len(input_args): + raise TypeError( + f"Component '{component.name}' received too few " + f"input arguments: {input_args}. Expected at least " + f"{inspector.min_params_num}, with names: " + f"{inspector.required_params}." + ) + if (inspector.max_params_num != inspector.INFTY + and len(input_args) > inspector.max_params_num): + raise TypeError( + f"Component '{component.name}' received too many " + f"input arguments: {input_args}. Expected at most " + f"{inspector.max_params_num}." + ) diff --git a/src/itwinai/serialization.py b/src/itwinai/serialization.py new file mode 100644 index 00000000..6d139460 --- /dev/null +++ b/src/itwinai/serialization.py @@ -0,0 +1,176 @@ +from typing import Dict, Any, Union +import abc +import json +import yaml +from pathlib import Path +import inspect + +from .type import MLModel +from .utils import SignatureInspector + + +def is_jsonable(x): + try: + json.dumps(x) + return True + except Exception: + return False + + +def fullname(o): + klass = o.__class__ + module = klass.__module__ + if module == 'builtins': + return klass.__qualname__ # avoid outputs like 'builtins.str' + return module + '.' + klass.__qualname__ + + +class SerializationError(Exception): + ... + + +class Serializable: + parameters: Dict[Any, Any] = None + + def save_parameters(self, **kwargs) -> None: + """Simplified way to store constructor arguments in as class + attributes. Keeps track of the parameters to enable + YAML/JSON serialization. + """ + if self.parameters is None: + self.parameters = {} + self.parameters.update(kwargs) + + # for k, v in kwargs.items(): + # self.__setattr__(k, v) + + @staticmethod + def locals2params(locals: Dict, pop_self: bool = True) -> Dict: + """Remove ``self`` from the output of ``locals()``. + + Args: + locals (Dict): output of ``locals()`` called in the constructor + of a class. + pop_self (bool, optional): whether to remove ``self``. + Defaults to True. + + Returns: + Dict: cleaned ``locals()``. + """ + if pop_self: + locals.pop('self', None) + return locals + + def update_parameters(self, **kwargs) -> None: + """Updates stored parameters.""" + self.save_parameters(**kwargs) + + def to_dict(self) -> Dict: + """Returns a dict serialization of the current object.""" + self._validate_parameters() + class_path = self._get_class_path() + init_args = dict() + for par_name, par in self._saved_constructor_parameters().items(): + init_args[par_name] = self._recursive_serialization(par, par_name) + return dict(class_path=class_path, init_args=init_args) + + def _validate_parameters(self) -> None: + if self.parameters is None: + raise SerializationError( + f"{self.__class__.__name__} cannot be serialized " + "because its constructor arguments were not saved. " + "Please add 'self.save_parameters(param_1=param_1, " + "..., param_n=param_n)' as first instruction of its " + "constructor." + ) + + init_inspector = SignatureInspector(self.__init__) + for par_name in init_inspector.required_params: + if self.parameters.get(par_name) is None: + raise SerializationError( + f"Required parameter '{par_name}' of " + f"{self.__class__.__name__} class not present in " + "saved parameters. " + "Please add 'self.save_parameters(param_1=param_1, " + "..., param_n=param_n)' as first instruction of its " + f"constructor, including also '{par_name}'." + ) + + def _get_class_path(self) -> str: + class_path = fullname(self) + if "" in class_path: + raise SerializationError( + f"{self.__class__.__name__} is " + "defined locally, which is not supported for serialization. " + "Move the class to a separate Python file and import it " + "from there." + ) + return class_path + + def _saved_constructor_parameters(self) -> Dict[str, Any]: + """Extracts the current constructor parameters from all + the saved parameters, as some of them may had been added by + superclasses. + + Returns: + Dict[str, Any]: subset of saved parameters containing only + the constructor parameters for this class. + """ + init_params = inspect.signature(self.__init__).parameters.items() + init_par_nam = map(lambda x: x[0], init_params) + return { + par_name: self.parameters[par_name] for par_name in init_par_nam + if self.parameters.get(par_name, inspect._empty) != inspect._empty + } + + def _recursive_serialization(self, item: Any, item_name: str) -> Any: + if isinstance(item, (tuple, list, set)): + return [self._recursive_serialization(x, item_name) for x in item] + elif isinstance(item, dict): + return { + k: self._recursive_serialization(v, item_name) + for k, v in item.items() + } + elif is_jsonable(item): + return item + elif isinstance(item, Serializable): + return item.to_dict() + else: + raise SerializationError( + f"{self.__class__.__name__} cannot be serialized " + f"because its constructor argument '{item_name}' " + "is not a Python built-in type and it does not " + "extend 'itwinai.serialization.Serializable' class." + ) + + def to_json(self, file_path: Union[str, Path], nested_key: str) -> None: + """Save a component to JSON file. + + Args: + file_path (Union[str, Path]): JSON file path. + nested_key (str): root field containing the serialized object. + """ + with open(file_path, "w") as fp: + json.dump({nested_key: self.to_dict()}, fp) + + def to_yaml(self, file_path: Union[str, Path], nested_key: str) -> None: + """Save a component to YAML file. + + Args: + file_path (Union[str, Path]): YAML file path. + nested_key (str): root field containing the serialized object. + """ + with open(file_path, "w") as fp: + yaml.dump({nested_key: self.to_dict()}, fp) + + +class ModelLoader(abc.ABC, Serializable): + """Loads a machine learning model from somewhere.""" + + def __init__(self, model_uri: str) -> None: + super().__init__() + self.model_uri = model_uri + + @abc.abstractmethod + def __call__(self) -> MLModel: + """Loads model from model URI.""" diff --git a/ai/src/itwinai/models/__init__.py b/src/itwinai/tensorflow/__init__.py similarity index 100% rename from ai/src/itwinai/models/__init__.py rename to src/itwinai/tensorflow/__init__.py diff --git a/ai/src/itwinai/plmodels/__init__.py b/src/itwinai/tensorflow/data/__init__.py similarity index 100% rename from ai/src/itwinai/plmodels/__init__.py rename to src/itwinai/tensorflow/data/__init__.py diff --git a/src/itwinai/tensorflow/distributed.py b/src/itwinai/tensorflow/distributed.py new file mode 100644 index 00000000..417a8c78 --- /dev/null +++ b/src/itwinai/tensorflow/distributed.py @@ -0,0 +1,62 @@ +""" +TensorFlow distributed strategies. +""" + +from typing import Tuple +import os +import tensorflow as tf +import tensorflow.distribute as dist + + +def get_strategy() -> Tuple[tf.distribute.Strategy, int]: + """Strategy for distributed TensorFlow training. It will automatically + detect if you are running in a multi-node environment, returning the + correct TensorFlow distributed strategy for data parallel distributed + training. + + Returns: + Tuple[tf.distribute.Strategy, int]: strategy and number of parallel + workers. See: + https://stackoverflow.com/questions/66005641/why-we-are-using-strategy-num-replicas-in-sync). + """ + + slurm_jobid = os.environ.get('SLURM_JOB_ID') + slurm_nnodes = int(os.environ.get('SLURM_NNODES', 0)) + if not slurm_jobid or slurm_nnodes < 2: + # Single-node environment + print('Not in SLURM env! Assuming that you ' + 'are running on a single node') + mirrored_strategy = dist.MirroredStrategy() + return mirrored_strategy, mirrored_strategy.num_replicas_in_sync + + # Multi-node environment in SLURM + cluster_resolver = dist.cluster_resolver.SlurmClusterResolver( + port_base=12345) + implementation = dist.experimental.CommunicationImplementation.NCCL + communication_options = dist.experimental.CommunicationOptions( + implementation=implementation) + + # declare distribution strategy + tf_dist_strategy = dist.MultiWorkerMirroredStrategy( + cluster_resolver=cluster_resolver, + communication_options=communication_options + ) + + # number of workers + n_workers = int(os.environ['SLURM_NTASKS']) + # list of devices per worker + devices = tf.config.experimental.list_physical_devices('GPU') + # number of devices per worker + n_gpus_per_worker = len(devices) + # total number of GPUs + n_gpus = n_workers * n_gpus_per_worker + + # get total number of detected GPUs + print("Number of detected devices: {}".format( + n_gpus)) + + # get total number of workers + print("Number of devices: {}".format( + tf_dist_strategy.num_replicas_in_sync)) + + return tf_dist_strategy, tf_dist_strategy.num_replicas_in_sync diff --git a/src/itwinai/tensorflow/models/__init__.py b/src/itwinai/tensorflow/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/itwinai/tensorflow/models/mnist.py b/src/itwinai/tensorflow/models/mnist.py new file mode 100644 index 00000000..d0e706c1 --- /dev/null +++ b/src/itwinai/tensorflow/models/mnist.py @@ -0,0 +1,56 @@ +# import tensorflow.keras as keras +import tensorflow as tf + +from typing import List + + +class MNIST_Model(tf.keras.Model): + def __init__( + self, + input_shape: List[int] = (28, 28, 1), + output_shape: int = 10 + ): + super().__init__() + + self.model = tf.keras.Sequential( + [ + # tf.keras.Input(shape=input_shape), + tf.keras.layers.Conv2D( + 32, kernel_size=3, activation="relu", + input_shape=input_shape), + tf.keras.layers.MaxPooling2D(), + # tf.keras.layers.Conv2D( + # 64, kernel_size=3, activation="relu"), + # tf.keras.layers.MaxPooling2D(pool_size=2), + tf.keras.layers.Flatten(), + # tf.keras.layers.Dropout(0.5), + tf.keras.layers.Dense(output_shape) + ] + ) + + self.model = tf.keras.Sequential([ + tf.keras.layers.Conv2D( + 32, 3, activation='relu', input_shape=input_shape), + tf.keras.layers.MaxPooling2D(), + tf.keras.layers.Flatten(), + tf.keras.layers.Dense(64, activation='relu'), + tf.keras.layers.Dense(output_shape) + ]) + + # LeNet5 + self.model = tf.keras.Sequential([ + + tf.keras.layers.Conv2D(filters=6, kernel_size=( + 3, 3), activation='relu', input_shape=(28, 28, 1)), + tf.keras.layers.AveragePooling2D(), + tf.keras.layers.Conv2D( + filters=16, kernel_size=(3, 3), activation='relu'), + tf.keras.layers.AveragePooling2D(), + tf.keras.layers.Flatten(), + tf.keras.layers.Dense(units=120, activation='relu'), + tf.keras.layers.Dense(units=84, activation='relu'), + tf.keras.layers.Dense(units=10) + ]) + + def call(self, inputs): + return self.model(inputs) diff --git a/src/itwinai/tensorflow/trainer.py b/src/itwinai/tensorflow/trainer.py new file mode 100644 index 00000000..f0adbcda --- /dev/null +++ b/src/itwinai/tensorflow/trainer.py @@ -0,0 +1,213 @@ +"""Base TensorFlow trainer module.""" +from typing import Dict, Any, Optional, List, Union + +from jsonargparse import ArgumentParser +import tensorflow as tf +from tensorflow.data import Dataset +from keras.callbacks import Callback + +from ..components import Trainer, monitor_exec +from itwinai.tensorflow.distributed import get_strategy + + +def import_class(name): + components = name.split('.') + mod = __import__(components[0]) + for comp in components[1:]: + mod = getattr(mod, comp) + return mod + + +def instance_from_dict(obj_dict: Dict, fail_untyped: bool = True) -> Any: + if isinstance(obj_dict, dict) and obj_dict.get('class_path') is not None: + # obj_dict is a dictionary with a structure compliant with + # jsonargparse + obj_class = import_class(obj_dict["class_path"]) + parser = ArgumentParser() + parser.add_subclass_arguments( + obj_class, "object", fail_untyped=fail_untyped) + obj_dict = {"object": obj_dict} + return parser.instantiate_classes(obj_dict).object + + raise ValueError( + "Unable to instantiate object with this " + f"dict configuration: {obj_dict}.\nIt should have " + "valid 'class_path' and 'init_args' fields" + ) + + +class TensorflowTrainer(Trainer): + """Trains a Keras model. + + Args: + epochs (int): number of training epochs. + micro_batch_size (int): per-worker batch size. Equals macro batch + size when not running distributed. + shuffle_buffer (Optional[int], optional): if given, shuffles + dataset using a buffer of given size. See + ``tf.data.Dataset.shuffle``. Defaults to None. + callbacks (Optional[List], optional): list fo Keras callbacks. + Can be a list of dictionary configurations. Defaults to None. + model_config (Optional[Dict], optional): model configuration. If + given, a model is instantiated from this configuration. + Defaults to None. + model_compile_config (Optional[Dict], optional): configuration for + ``keras.Model.compile``. Defaults to None. + rnd_seed (Optional[int], optional): random seed. Defaults to None. + verbose (Union[str, int], optional): verbosity level for + ``keras.Model.fit``. Defaults to 'auto'. + """ + + strategy: tf.distribute.Strategy + num_workers: int + callbacks: Optional[List] = None + epochs: int + shuffle_buffer: Optional[int] + micro_batch_size: int + macro_batch_size: int + rnd_seed: Optional[int] + + def __init__( + self, + epochs: int, + micro_batch_size: int, + shuffle_buffer: Optional[int] = None, + callbacks: Optional[List[Union[Dict, Callback]]] = None, + model_config: Optional[Dict] = None, + model_compile_config: Optional[Dict] = None, + rnd_seed: Optional[int] = None, + verbose: Union[str, int] = 'auto' + ): + super().__init__() + self.save_parameters(**self.locals2params(locals())) + self.epochs = epochs + self.micro_batch_size = micro_batch_size + self.shuffle_buffer = shuffle_buffer + self.rnd_seed = rnd_seed + self.verbose = verbose + if callbacks is not None: + self.callbacks = self.instantiate_callbacks(callbacks) + else: + self.callbacks = [] + + # Distributed strategy + self.strategy, self.num_workers = get_strategy() + print( + f"Distributed strategy is working with: {self.num_workers} devices" + ) + self.macro_batch_size = self.micro_batch_size * self.num_workers + + # Compile model from configuration, if given + if model_config is not None and model_compile_config is not None: + with self.strategy.scope(): + self.model: tf.keras.Model = instance_from_dict(model_config) + model_compile_config = self.instantiate_compile_conf( + model_compile_config + ) + self.model.compile(**model_compile_config) + else: + print( + "Either model_config or model_compile_config were not given. " + "Skipping automatic model compilation." + ) + + @staticmethod + def instantiate_compile_conf(conf: Dict) -> Dict: + final_conf = {} + for item_name, item in conf.items(): + if isinstance(item, dict): + item = instance_from_dict(item) + final_conf[item_name] = item + return final_conf + + @staticmethod + def instantiate_callbacks(callbacks: List) -> List: + final_callbacks = [] + for item in callbacks: + if isinstance(item, dict): + # Not all constructor args in keras callbacks + # are typed! + item = instance_from_dict(item, fail_untyped=False) + final_callbacks.append(item) + return final_callbacks + + @monitor_exec + def execute( + self, + train_dataset: Dataset, + validation_dataset: Dataset, + test_dataset: Optional[Dataset] = None + ) -> Any: + + print(f"len(train_dataset): {len(train_dataset)}") + print(f"len(validation_dataset): {len(validation_dataset)}") + print("micro_batch_size: ", self.micro_batch_size, flush=True) + print("macro_batch_size: ", self.macro_batch_size, flush=True) + + # Shuffle dataset + if self.shuffle_buffer: + train_ds = train_dataset.shuffle( + self.shuffle_buffer, seed=self.rnd_seed) + valid_ds = validation_dataset.shuffle( + self.shuffle_buffer, seed=self.rnd_seed) + else: + train_ds = train_dataset + valid_ds = validation_dataset + + # Set batch size to the dataset and repeat + train_ds = train_ds.batch( + self.macro_batch_size, drop_remainder=True, + num_parallel_calls=tf.data.AUTOTUNE + ).repeat(self.epochs) + valid_ds = valid_ds.batch( + self.macro_batch_size, drop_remainder=True, + num_parallel_calls=tf.data.AUTOTUNE + ).repeat(self.epochs) + + print(f"len(train_ds): {len(train_ds)}") + print(f"len(valid_ds): {len(valid_ds)}") + + # Distribute datasets among strategy's replica + dist_train_dataset = self.strategy.experimental_distribute_dataset( + train_ds + ) + dist_valid_dataset = self.strategy.experimental_distribute_dataset( + valid_ds + ) + + print(f"len(dist_train_dataset): {len(train_ds)}") + print(f"len(dist_train_dataset): {len(valid_ds)}") + + # Compute the steps per epoch for train and valid + steps_per_epoch = len(train_dataset) // self.macro_batch_size + validation_steps = len(validation_dataset) // self.macro_batch_size + + print(f"steps_per_epoch: {steps_per_epoch}") + print(f"validation_steps: {validation_steps}") + + ##################################################################### + # Instantiate here model, optimizer, loss under the strategy scope, # + # if not done previously through `model_compile_config` and # + # `model_config` ! # + # Always remember that they should be instantiated under the # + # distributed strategy scope: ``with self.strategy.scope():`` # + # # + # Example: # + # with self.strategy.scope(): # + # model = tf.keras.Sequential(...) # + # optimizer = rf.keras.optimizers.Adam(...) # + # loss = tf.keras.losses.BinaryCrossentropy(...) # + ##################################################################### + + # Train the model + self.model.fit( + dist_train_dataset, + validation_data=dist_valid_dataset, + steps_per_epoch=steps_per_epoch, + validation_steps=validation_steps, + epochs=self.epochs, + callbacks=self.callbacks, + verbose=self.verbose + ) + print("Training completed") + return train_dataset, validation_dataset, test_dataset, self.model diff --git a/src/itwinai/tensorflow/utils.py b/src/itwinai/tensorflow/utils.py new file mode 100644 index 00000000..96cc536d --- /dev/null +++ b/src/itwinai/tensorflow/utils.py @@ -0,0 +1,13 @@ +import keras +import json + + +def model_to_json(model: keras.Model, filepath: str): + with open(filepath, "w") as f: + json.dump(model.to_json(), f) + + +def model_from_json(filepath: str) -> keras.Model: + with open(filepath, "r") as f: + config = json.load(f) + return keras.models.model_from_json(config) diff --git a/src/itwinai/tests/__init__.py b/src/itwinai/tests/__init__.py new file mode 100644 index 00000000..5486fb7a --- /dev/null +++ b/src/itwinai/tests/__init__.py @@ -0,0 +1,11 @@ +from .dummy_components import ( + FakeGetter, FakeGetterExec, FakePreproc, FakePreprocExec, + FakeSaver, FakeSaverExec, FakeSplitter, FakeSplitterExec, + FakeTrainer, FakeTrainerExec +) + +_ = ( + FakeGetter, FakeGetterExec, FakePreproc, FakePreprocExec, + FakeSaver, FakeSaverExec, FakeSplitter, FakeSplitterExec, + FakeTrainer, FakeTrainerExec +) diff --git a/src/itwinai/tests/dummy_components.py b/src/itwinai/tests/dummy_components.py new file mode 100644 index 00000000..b60f1df0 --- /dev/null +++ b/src/itwinai/tests/dummy_components.py @@ -0,0 +1,97 @@ +from typing import Optional +from ..components import BaseComponent, monitor_exec + + +class FakeGetter(BaseComponent): + def __init__(self, data_uri: str, name: Optional[str] = None + ) -> None: + super().__init__(name) + self.save_parameters(data_uri=data_uri, name=name) + self.data_uri = data_uri + + def execute(self): + ... + + +class FakeGetterExec(FakeGetter): + result: str = "dataset" + + @monitor_exec + def execute(self): + return self.result + + +class FakeSplitter(BaseComponent): + def __init__(self, train_prop: float, name: Optional[str] = None + ) -> None: + super().__init__(name) + self.save_parameters(train_prop=train_prop, name=name) + self.train_prop = train_prop + + def execute(self): + ... + + +class FakeSplitterExec(FakeSplitter): + result: tuple = ("train_dataset", "val_dataset", "test_dataset") + + @monitor_exec + def execute(self, dataset): + return self.result + + +class FakePreproc(BaseComponent): + def __init__(self, max_items: int, name: Optional[str] = None + ) -> None: + super().__init__(name) + self.save_parameters(max_items=max_items, name=name) + self.max_items = max_items + + def execute(self): + ... + + +class FakePreprocExec(FakePreproc): + @monitor_exec + def execute(self, train_dataset, val_dataset, test_dataset): + return train_dataset, val_dataset, test_dataset + + +class FakeTrainer(BaseComponent): + def __init__( + self, + lr: float, + batch_size: int, + name: Optional[str] = None + ) -> None: + super().__init__(name) + self.save_parameters(lr=lr, batch_size=batch_size, name=name) + self.lr = lr + self.batch_size = batch_size + + def execute(self): + ... + + +class FakeTrainerExec(FakeTrainer): + model: str = "trained_model" + + @monitor_exec + def execute(self, train_dataset, val_dataset, test_dataset): + return train_dataset, val_dataset, test_dataset, self.model + + +class FakeSaver(BaseComponent): + def __init__(self, save_path: str, name: Optional[str] = None) -> None: + super().__init__(name) + self.save_parameters(save_path=save_path, name=name) + self.save_path = save_path + + def execute(self): + ... + + +class FakeSaverExec(FakeSaver): + @monitor_exec + def execute(self, artifact): + return artifact diff --git a/src/itwinai/torch/__init__.py b/src/itwinai/torch/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/itwinai/torch/config.py b/src/itwinai/torch/config.py new file mode 100644 index 00000000..b31823da --- /dev/null +++ b/src/itwinai/torch/config.py @@ -0,0 +1,40 @@ +"""Default configuration""" + +from pydantic import BaseModel + + +class Configuration(BaseModel, extra='allow'): + """Base configuration class.""" + + def __getitem__(self, idx): + return self.__getattribute__(idx) + + +class TrainingConfiguration(Configuration): + """Default configuration object for training. + Override and/or create new configurations using the constructor. + + Example: + + >>> cfg = TrainingConfiguration(batch_size=2, param_a=42) + >>> print(cfg.batch_size) # returns 17 (overrides default) + >>> print(cfg.param_a) # returns 42 (new value) + >>> print(cfg.pin_memory) # returns the default value + >>> + >>> from rich import print + >>> print(cfg) # pretty-print of configuration + + """ + # DataLoader + batch_size: int = 32 + pin_memory: bool = False + num_workers: int = 4 + + # Optimization + lr: float = 1e-3 + momentum: float = .9 + + # Horovod + fp16_allreduce: bool = False + use_adasum: bool = False + gradient_predivide_factor: float = 1.0 diff --git a/src/itwinai/torch/data/__init__.py b/src/itwinai/torch/data/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/itwinai/torch/distributed.py b/src/itwinai/torch/distributed.py new file mode 100644 index 00000000..7c794f33 --- /dev/null +++ b/src/itwinai/torch/distributed.py @@ -0,0 +1,932 @@ +import abc +from typing import Any, List, Optional, Tuple, Union, Iterable +from pathlib import Path +import json +import os + +import deepspeed +import torch +import torch.distributed as dist +import horovod.torch as hvd +import torch.nn as nn +import torch.optim as optim +from torch.optim.lr_scheduler import _LRScheduler as LRScheduler +from torch.optim.optimizer import Optimizer +from torch.utils.data import Dataset, Sampler, DistributedSampler, DataLoader +from torch.utils.data.dataloader import T_co, _worker_init_fn_t, _collate_fn_t + +from ..distributed import DistributedStrategy +from .types import UninitializedStrategyError, DistributedStrategyError + + +def distributed_resources_available() -> bool: + """Check if the current execution environment + has (enough) GPUs available to allow for distributed ML. + + Returns: + bool: env can support distributed ML. + """ + if torch.cuda.is_available() and torch.cuda.device_count() > 1: + return True + return False + + +class TorchDistributedStrategy(DistributedStrategy): + """Abstract class to define the distributed backend methods for + PyTorch models. + """ + is_distributed: bool = True + is_initialized: bool = False + + @property + def is_main_worker(self) -> bool: + """Checks if local worker has global rank equal to zero. + + Returns: + bool: True if main worker. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return self.global_rank() == 0 + + @abc.abstractmethod + def init(self) -> None: + """Initializes the chosen distributed backend""" + # @abc.abstractmethod + # def distributed_engine( + # self, model: nn.Module, optimizer: Optimizer, + # lr_scheduler: Optional[LRScheduler] = None + # ) -> ModelEngine: + # """Build a distributed model engine.""" + + @abc.abstractmethod + def distributed( + self, model: nn.Module, optimizer: Optimizer, + lr_scheduler: Optional[LRScheduler] = None + ) -> Tuple[nn.Module, Optimizer, Optional[LRScheduler]]: + """Setup model, optimizer and scheduler for distributed.""" + + @abc.abstractmethod + def global_world_size(self) -> int: + """Returns the total number of processes (global world size). + + Returns: + int: global world size. + """ + + @abc.abstractmethod + def local_world_size(self) -> int: + """Returns the number of local workers available on a node + (local world size). + Usually it is equal to the number of available GPUs. + + Returns: + int: local world size. + """ + + @abc.abstractmethod + def global_rank(self) -> int: + """Returns the global rank of the current process. + Rank ranges from 0 to world_size. + + Returns: + int: global rank. + """ + + @abc.abstractmethod + def local_rank(self) -> int: + """Returns the local rank of the current process. + + Returns: + int: local rank. + """ + + def device(self) -> str: + """Device used by local worker. + + Returns: + str: torch device in the form 'cuda:N'. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return f"cuda:{self.local_rank()}" + + def set_device(self): + """Set local device.""" + torch.cuda.device(self.local_rank()) + # Needed by torch.distributed.gather_object + torch.cuda.set_device(self.local_rank()) + + def create_dataloader( + self, dataset: Dataset[T_co], batch_size: Optional[int] = 1, + shuffle: Optional[bool] = None, + sampler: Union[Sampler, Iterable, None] = None, + batch_sampler: Union[Sampler[List], Iterable[List], None] = None, + num_workers: int = 0, collate_fn: Optional[_collate_fn_t] = None, + pin_memory: bool = False, drop_last: bool = False, + timeout: float = 0, + worker_init_fn: Optional[_worker_init_fn_t] = None, + multiprocessing_context=None, generator=None, + *, prefetch_factor: Optional[int] = None, + persistent_workers: bool = False, + pin_memory_device: str = "" + ): + """Create a distributed DataLoader by using ``DistributedSampler`` as + random sampler. + + Args: + dataset (Dataset): dataset from which to load the data. + batch_size (int, optional): how many samples per batch to load + (default: ``1``). + shuffle (bool, optional): set to ``True`` to have the data + reshuffled at every epoch (default: ``False``). + sampler (Sampler or Iterable, optional): defines the strategy to + draw + samples from the dataset. Can be any ``Iterable`` with + ``__len__`` + implemented. If specified, :attr:`shuffle` must not be + specified. + batch_sampler (Sampler or Iterable, optional): like + :attr:`sampler`, but + returns a batch of indices at a time. Mutually exclusive with + :attr:`batch_size`, :attr:`shuffle`, :attr:`sampler`, + and :attr:`drop_last`. + num_workers (int, optional): how many subprocesses to use for data + loading. ``0`` means that the data will be loaded in the main + process. (default: ``0``) + collate_fn (Callable, optional): merges a list of samples to form a + mini-batch of Tensor(s). Used when using batched loading from + a map-style dataset. + pin_memory (bool, optional): If ``True``, the data loader will + copy Tensors + into device/CUDA pinned memory before returning them. If your + data elements + are a custom type, or your :attr:`collate_fn` returns a batch + that is a custom type, + see the example below. + drop_last (bool, optional): set to ``True`` to drop the last + incomplete batch, + if the dataset size is not divisible by the batch size. + If ``False`` and + the size of dataset is not divisible by the batch size, then + the last batch + will be smaller. (default: ``False``) + timeout (numeric, optional): if positive, the timeout value for + collecting a batch + from workers. Should always be non-negative. (default: ``0``) + worker_init_fn (Callable, optional): If not ``None``, + this will be called on each + worker subprocess with the worker id (an int in + ``[0, num_workers - 1]``) as + input, after seeding and before data loading. + (default: ``None``) + multiprocessing_context (str or + multiprocessing.context.BaseContext, optional): If + ``None``, the default `multiprocessing context`_ of + your operating system will + be used. (default: ``None``) + generator (torch.Generator, optional): If not ``None``, + this RNG will be used + by RandomSampler to generate random indexes and + multiprocessing to generate + ``base_seed`` for workers. (default: ``None``) + prefetch_factor (int, optional, keyword-only arg): Number of + batches loaded + in advance by each worker. ``2`` means there will be a total of + 2 * num_workers batches prefetched across all workers. + (default value depends + on the set value for num_workers. If value of num_workers=0 + default is ``None``. + Otherwise, if value of ``num_workers > 0`` default is ``2``). + persistent_workers (bool, optional): If ``True``, the data loader + will not shut down + the worker processes after a dataset has been consumed once. + This allows to + maintain the workers `Dataset` instances alive. + (default: ``False``) + pin_memory_device (str, optional): the device to + :attr:`pin_memory` to if ``pin_memory`` is ``True``. + + + .. warning:: If the ``spawn`` start method is used, + :attr:`worker_init_fn` + cannot be an unpicklable object, e.g., a lambda function. + See `Multiprocessing best practices`_ on more + details related to multiprocessing in PyTorch. + + + .. warning:: ``len(dataloader)`` heuristic is based on the length of + the sampler used. + When :attr:`dataset` is an + :class:`~torch.utils.data.IterableDataset`, + it instead returns an estimate based on + ``len(dataset) / batch_size``, with proper + rounding depending on :attr:`drop_last`, regardless + of multi-process loading + configurations. This represents the best guess PyTorch + can make because PyTorch + trusts user :attr:`dataset` code in correctly handling + multi-process + loading to avoid duplicate data. + + However, if sharding results in multiple workers having + incomplete last batches, + this estimate can still be inaccurate, because (1) an + otherwise complete batch can + be broken into multiple ones and (2) more than one batch + worth of samples can be + dropped when :attr:`drop_last` is set. Unfortunately, + PyTorch can not detect such cases in general. + + See `Dataset Types`_ for more details on these two + types of datasets and how + :class:`~torch.utils.data.IterableDataset` interacts with + `Multi-process data loading`_. + + + .. warning:: See `Reproducibility`_, and + `My data loader workers return identical random numbers`_, + and + `Randomness in multi-process data loading`_ notes for + random seed related questions. + + + .. _multiprocessing context: + https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods + .. _Multiprocessing best practices: + https://pytorch.org/docs/stable/notes/multiprocessing.html#multiprocessing-best-practices + .. _Reproducibility: + https://pytorch.org/docs/stable/notes/randomness.html#reproducibility + .. _My data loader workers return identical random numbers: + https://pytorch.org/docs/stable/notes/faq.html#dataloader-workers-random-seed + .. _Randomness in multi-process data loading: + https://pytorch.org/docs/stable/data.html#data-loading-randomness + .. _Multi-process data loading: + https://pytorch.org/docs/stable/data.html#multi-process-data-loading + .. _Dataset Types: + https://pytorch.org/docs/stable/data.html#dataset-types + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + + if self.is_distributed: + if sampler is not None: + raise RuntimeError( + "User-provided sampler is not supported." + ) + sampler = DistributedSampler( + dataset, num_replicas=self.global_world_size(), + rank=self.global_rank(), + shuffle=shuffle + ) + # shuffle and batch_sampler must be unset + return DataLoader( + dataset=dataset, batch_size=batch_size, sampler=sampler, + num_workers=num_workers, collate_fn=collate_fn, + pin_memory=pin_memory, drop_last=drop_last, timeout=timeout, + worker_init_fn=worker_init_fn, + multiprocessing_context=multiprocessing_context, + generator=generator, prefetch_factor=prefetch_factor, + persistent_workers=persistent_workers, + pin_memory_device=pin_memory_device + ) + + @abc.abstractmethod + def clean_up(self) -> None: + """Cleans up resources allocated by distributed strategy.""" + + @abc.abstractmethod + def allgather_obj(self, obj: Any) -> List[Any]: + """All-gathers any object from the whole group in a list + (to all workers). + + Args: + obj (Any): object to gather from all workers. + + Returns: + List[Any]: list of objects gathered from all workers. + """ + + @abc.abstractmethod + def gather_obj(self, obj: Any, dst_rank: int = 0) -> List[Any]: + """Gathers any object from the whole group in a list + (to all workers). + + Args: + obj (Any): object to gather from all workers. + dst_rank (int): rank of the worker on which the objects list + are gathered. + + Returns: + List[Any]: list of objects gathered from all workers. + """ + + +class TorchDDPStrategy(TorchDistributedStrategy): + """PyTorch ``DistributedDataParallel`` distributed strategy class. + + Args: + backend (str): Name of the communication backend to employ. + """ + + backend: str + + def __init__(self, backend: str) -> None: + super().__init__() + self.backend = backend + + def init(self) -> None: + """Initializes the distributed process group and the distributed + package. + + Raises: + RuntimeError: when there are not (enough) GPUs available. + DistributedStrategyError: when trying to initialize a strategy + already initialized. + """ + if not distributed_resources_available(): + raise RuntimeError( + "Trying to run distributed on insufficient resources.") + if self.is_initialized: + raise DistributedStrategyError("Strategy was already initialized") + dist.init_process_group(backend=self.backend) + self.is_initialized = True + + self.set_device() + + # def distributed_engine( + # self, model: nn.Module, optimizer: Optimizer, + # lr_scheduler: Optional[LRScheduler] = None, + # mixed_precision: bool = False + # ) -> ModelEngine: + # """Build a distributed model engine.""" + # if torch.cuda.is_available(): + # # device = self.dist_lrank() + # model = model.to(self.dist_device()) + # dist_model = torch.nn.parallel.DistributedDataParallel( + # model, + # device_ids=[self.dist_device()], + # output_device=self.dist_device() + # ) + # else: + # dist_model = model + + # model_engine = DDPModelEngine( + # dist_model, optimizer, lr_scheduler, + # mixed_precision=mixed_precision + # ) + + # return model_engine + + def distributed( + self, model: nn.Module, optimizer: Optimizer, + lr_scheduler: Optional[LRScheduler] = None, + **kwargs + ) -> Tuple[nn.Module, Optimizer, Optional[LRScheduler]]: + """Setup model, optimizer and scheduler for distributed.""" + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + if torch.cuda.is_available(): + # device = self.dist_lrank() + model = model.to(self.device()) + dist_model = torch.nn.parallel.DistributedDataParallel( + model, + device_ids=[self.device()], + output_device=self.device() + ) + else: + dist_model = model + + return dist_model, optimizer, lr_scheduler + + def global_world_size(self) -> int: + """Returns the total number of processes (global world size). + + Returns: + int: global world size. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return dist.get_world_size() + + def local_world_size(self) -> int: + """Returns the local number of workers available per node, + which is usually the number of GPUs available. + + Returns: + int: local world size. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return torch.cuda.device_count() + + def global_rank(self) -> int: + """Returns the global rank of the current process, where + rank ranges from 0 to world_size. + + Returns: + int: global rank. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return dist.get_rank() + + def local_rank(self) -> int: + """Returns the local rank of the current process. + + Returns: + int: local rank. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return dist.get_rank() % torch.cuda.device_count() + + def clean_up(self) -> None: + """Destroys the current process group.""" + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + if torch.cuda.is_available(): + dist.barrier() + dist.destroy_process_group() + + def allgather_obj(self, obj: Any) -> List[Any]: + """All-gathers any object from the whole group + in a list (to all workers). + + Args: + obj (Any): Object to gather from all workers. + + Returns: + List[Any]: List of gathered objects. + """ + # https://pytorch.org/docs/stable/distributed.html#collective-functions + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + res = [None] * self.global_world_size() + dist.all_gather_object(res, obj) + return res + + def gather_obj(self, obj: Any, dst_rank: int = 0) -> Optional[List[Any]]: + """Gathers any object from the whole group in a list + (to all workers). + + Args: + obj (Any): object to gather from all workers. + dst_rank (int): rank of the worker on which the objects list + are gathered. + + Returns: + Optional[List[Any]]: list of objects gathered from all workers + or ``None`` on non-destination ranks. + """ + # https://pytorch.org/docs/stable/distributed.html#collective-functions + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + if self.global_rank() == dst_rank: + res = [None] * self.global_world_size() + dist.gather_object(obj, res, dst=dst_rank) + return res + + dist.gather_object(obj, dst=dst_rank) + + +class DeepSpeedStrategy(TorchDistributedStrategy): + """DeepSpeed distributed strategy class. + + Args: + backend (str): Name of the communication backend to employ. + config (Union[dict, Path, str]): DeepSpeed config. Either a + dictionary or a path to a JSON file. + """ + + backend: str + + def __init__( + self, + backend: str + ) -> None: + super().__init__() + self.backend = backend + + def _load_config(self, ds_config) -> None: + if isinstance(ds_config, (str, Path)): + with open(ds_config) as fp: + self.config = json.load(fp) + elif isinstance(ds_config, dict): + self.config = ds_config + else: + raise ValueError("ds_config is neither a dictionary not a path.") + + def init(self) -> None: + """Initializes the distributed process group and the distributed + package. + + Raises: + RuntimeError: when there are not (enough) GPUs available. + DistributedStrategyError: when trying to initialize a strategy + already initialized. + """ + if not distributed_resources_available(): + raise RuntimeError( + "Trying to run distributed on insufficient resources.") + + if self.is_initialized: + raise DistributedStrategyError("Strategy was already initialized") + + # https://github.com/Lightning-AI/pytorch-lightning/issues/13567 + ompi_lrank = os.environ.get('OMPI_COMM_WORLD_LOCAL_RANK') + os.environ['OMPI_COMM_WORLD_LOCAL_RANK'] = os.environ.get( + 'LOCAL_RANK', ompi_lrank) + + # https://deepspeed.readthedocs.io/en/latest/initialize.html#training-initialization + deepspeed.init_distributed(dist_backend=self.backend) + self.is_initialized = True + + self.set_device() + + def distributed( + self, model: nn.Module, optimizer: Optional[Optimizer] = None, + lr_scheduler: Optional[LRScheduler] = None, + model_parameters: Optional[Any] = None, + **init_kwargs + ) -> Tuple[nn.Module, Optimizer, Optional[LRScheduler]]: + """Setup model, optimizer and scheduler for distributed.""" + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + + if init_kwargs.get("config"): + self._load_config(init_kwargs.get("config")) + # https://deepspeed.readthedocs.io/en/latest/initialize.html#training-initialization + # To prioritize optim in the config, you need to pass optim=None + distrib_model, optimizer, _, lr_scheduler = deepspeed.initialize( + model=model, + model_parameters=model_parameters, + optimizer=optimizer, + lr_scheduler=lr_scheduler, + dist_init_required=True, + **init_kwargs + ) + return distrib_model, optimizer, lr_scheduler + + def global_world_size(self) -> int: + """Returns the total number of processes (global world size). + + Returns: + int: global world size. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return dist.get_world_size() + + def local_world_size(self) -> int: + """Returns the local number of workers available per node, + which is usually the number of GPUs available. + + Returns: + int: local world size. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return torch.cuda.device_count() + + def global_rank(self) -> int: + """Returns the global rank of the current process, where + rank ranges from 0 to world_size. + + Returns: + int: global rank. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return dist.get_rank() + + def local_rank(self) -> int: + """Returns the local rank of the current process. + + Returns: + int: local rank. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return dist.get_rank() % torch.cuda.device_count() + + def clean_up(self) -> None: + """Destroys the current process group.""" + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + # deepspeed.sys.exit() # disabled as it kills the execution + + def allgather_obj(self, obj: Any) -> List[Any]: + """All-gathers any object from the whole group + in a list (to all workers). + + Args: + obj (Any): Object to gather from all workers. + + Returns: + List[Any]: List of gathered objects. + """ + # https://pytorch.org/docs/stable/distributed.html#collective-functions + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + res = [None] * self.global_world_size() + dist.all_gather_object(res, obj) + return res + + def gather_obj(self, obj: Any, dst_rank: int = 0) -> Optional[List[Any]]: + """Gathers any object from the whole group in a list + (to all workers). + + Args: + obj (Any): object to gather from all workers. + dst_rank (int): rank of the worker on which the objects list + are gathered. + + Returns: + Optional[List[Any]]: list of objects gathered from all workers + or ``None`` on non-destination ranks. + """ + # https://pytorch.org/docs/stable/distributed.html#collective-functions + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + if self.global_rank() == dst_rank: + res = [None] * self.global_world_size() + dist.gather_object(obj, res, dst=dst_rank) + return res + + dist.gather_object(obj, dst=dst_rank) + + +class HorovodStrategy(TorchDistributedStrategy): + """Horovod distributed strategy class.""" + + def init(self) -> None: + """Initializes the Horovod distributed backend. + + Raises: + RuntimeError: when there are not (enough) GPUs available. + DistributedStrategyError: when trying to initialize a strategy + already initialized. + """ + if not distributed_resources_available(): + raise RuntimeError( + "Trying to run distributed on insufficient resources.") + if self.is_initialized: + raise DistributedStrategyError("Strategy was already initialized") + hvd.init() + self.is_initialized = True + + self.set_device() + + def distributed( + self, model: nn.Module, optimizer: Optional[Optimizer] = None, + lr_scheduler: Optional[LRScheduler] = None, + **optim_kwargs + ) -> Tuple[nn.Module, Optimizer, Optional[LRScheduler]]: + """Setup model, optimizer and scheduler for distributed.""" + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + + model.to(self.device()) + + # Scale learning rate + # https://github.com/horovod/horovod/issues/1653#issuecomment-574764452 + lr_scaler = 1 + if optim_kwargs.get('op') == hvd.Adasum: + lr_scaler = hvd.local_size() + elif optim_kwargs.get('op') == hvd.Average: + lr_scaler = hvd.size() + for g in optimizer.param_groups: + g['lr'] *= lr_scaler + + self._broadcast_params(model, optimizer) + + distOptimizer = hvd.DistributedOptimizer( + optimizer, + named_parameters=model.named_parameters(), + **optim_kwargs + ) + return model, distOptimizer, lr_scheduler + + def _broadcast_params( + self, model: nn.Module, optimizer: optim.Optimizer + ) -> None: + """Broadcasts variables from root rank to all other processes. + + Args: + model (nn.Module): ML model that is to be broadcasted + across processes. + optimizer (optim.Optimizer): Optimizer that is to be broadcasted + across processes. + """ + hvd.broadcast_parameters(model.state_dict(), root_rank=0) + hvd.broadcast_optimizer_state(optimizer, root_rank=-0) + + def global_world_size(self) -> int: + """Returns the total number of processes (global world size). + + Returns: + int: global world size. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return hvd.size() + + def local_world_size(self) -> int: + """Returns the local number of workers available per node, + which is usually the number of GPUs available. + + Returns: + int: local world size. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return hvd.local_size() + + def global_rank(self) -> int: + """Returns the global rank of the current process, where + rank ranges from 0 to world_size. + + Returns: + int: global rank. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return hvd.rank() + + def local_rank(self) -> int: + """Returns the local rank of the current process. + + Returns: + int: local rank. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return hvd.local_rank() + + def clean_up(self) -> None: + """Shuts Horovod down.""" + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + hvd.shutdown() + + def allgather_obj(self, obj: Any) -> list[Any]: + """All-gathers scalar objects across all workers to a + list with size(#worker), uses horovod communicator + + Args: + obj (Any): object in a worker. + + Returns: + list: gathered list with size(#worker). + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return hvd.allgather_object(obj) + + def gather_obj(self, obj: Any, dst_rank: int = 0) -> list[Any]: + """The same as ``allgather_obj``, as gather is not supported + by Horovod. + + Args: + obj (Any): object in a worker. + dst_rank (int): ignored. + + Returns: + list: gathered list with size(#worker). + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + return self.allgather_obj(obj) + + +class NonDistributedStrategy(TorchDistributedStrategy): + """Dummy class for non-distributed environments.""" + + is_distributed: bool = False + + def init(self) -> None: + """If CUDA is available set CUDA device, and do nothing more. + + Raises: + DistributedStrategyError: when trying to initialize a strategy + already initialized. + """ + if self.is_initialized: + raise DistributedStrategyError("Strategy was already initialized") + if torch.cuda.is_available(): + self.set_device() + self.is_initialized = True + + def device(self) -> str: + """Device used by local worker. + + Returns: + str: cpu device if CUDA is not available. + """ + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + if torch.cuda.is_available(): + return super().device() + return "cpu" + + def distributed( + self, model: nn.Module, optimizer: Optional[Optimizer] = None, + lr_scheduler: Optional[LRScheduler] = None, + **kwargs + ) -> Tuple[nn.Module, Optimizer, Optional[LRScheduler]]: + """Do nothing and return model, optimizer and scheduler.""" + if not self.is_initialized: + raise UninitializedStrategyError( + "Strategy has not been initialized. Use the init method.") + if torch.cuda.is_available(): + model = model.cuda() + return model, optimizer, lr_scheduler + + def global_world_size(self) -> int: + """Returns the total number of processes (global world size). + + Returns: + int: global world size. + """ + return 1 + + def local_world_size(self) -> int: + """Returns the local number of workers available per node, + which is usually the number of GPUs available. + + Returns: + int: local world size. + """ + return 1 + + def global_rank(self) -> int: + """Returns the global rank of the current process, where + rank ranges from 0 to world_size. + + Returns: + int: global rank. + """ + return 0 + + def local_rank(self) -> int: + """Returns the local rank of the current process. + + Returns: + int: local rank. + """ + return 0 + + def clean_up(self) -> None: + """Do nothing.""" + + def allgather_obj(self, obj: Any) -> list[Any]: + """Wraps ``obj`` into a List object. + + Args: + obj (Any): object in a worker. + + Returns: + list[Any]: input object wrapped in a list. + """ + return [obj] + + def gather_obj(self, obj: Any, dst_rank: int = 0) -> list[Any]: + """Wraps ``obj`` into a List object. + + Args: + obj (Any): object in a worker. + dst_rank (int): ignored. + + Returns: + list[Any]: input object wrapped in a list. + """ + return [obj] diff --git a/src/itwinai/torch/inference.py b/src/itwinai/torch/inference.py new file mode 100644 index 00000000..9b1e0781 --- /dev/null +++ b/src/itwinai/torch/inference.py @@ -0,0 +1,219 @@ +from typing import Optional, Dict, Any, Union +import os +import abc + +import torch +from torch import nn +from torch.utils.data import DataLoader, Dataset + +from ..utils import dynamically_import_class, clear_key +from ..components import Predictor, monitor_exec +from .types import TorchDistributedStrategy as StrategyT +from .types import Metric, Batch +from ..serialization import ModelLoader + + +class TorchModelLoader(ModelLoader): + """Loads a torch model from somewhere. + + Args: + model_uri (str): Can be a path on local filesystem + or an mlflow 'locator' in the form: + 'mlflow+MLFLOW_TRACKING_URI+RUN_ID+ARTIFACT_PATH' + """ + + def __call__(self) -> nn.Module: + """"Loads model from model URI. + + Raises: + ValueError: if the model URI is not recognized + or the model is not found. + + Returns: + nn.Module: torch neural network. + """ + if os.path.exists(self.model_uri): + # Model is on local filesystem. + model = torch.load(self.model_uri) + return model.eval() + + if self.model_uri.startswith('mlflow+'): + # Model is on an MLFLow server + # Form is 'mlflow+MLFLOW_TRACKING_URI+RUN_ID+ARTIFACT_PATH' + import mlflow + from mlflow import MlflowException + _, tracking_uri, run_id, artifact_path = self.model_uri.split('+') + mlflow.set_tracking_uri(tracking_uri) + + # Check that run exists + try: + mlflow.get_run(run_id) + except MlflowException: + raise ValueError(f"Run ID '{run_id}' was not found!") + + # Download model weights + ckpt_path = mlflow.artifacts.download_artifacts( + run_id=run_id, + artifact_path=artifact_path, + dst_path='tmp/', + tracking_uri=mlflow.get_tracking_uri() + ) + model = torch.load(ckpt_path) + return model.eval() + + raise ValueError( + 'Unrecognized model URI: model may not be there! ' + f'Received model URI: {self.model_uri}' + ) + + +class TorchPredictor(Predictor): + """Applies a pre-trained torch model to unseen data.""" + + model: nn.Module = None + test_dataset: Dataset + test_dataloader: DataLoader = None + _strategy: StrategyT = StrategyT.NONE.value + epoch_idx: int = 0 + train_glob_step: int = 0 + validation_glob_step: int = 0 + train_metrics: Dict[str, Metric] + validation_metrics: Dict[str, Metric] + + def __init__( + self, + model: Union[nn.Module, ModelLoader], + test_dataloader_class: str = 'torch.utils.data.DataLoader', + test_dataloader_kwargs: Optional[Dict] = None, + # strategy: str = StrategyT.NONE.value, + # seed: Optional[int] = None, + # logger: Optional[List[Logger]] = None, + # cluster: Optional[ClusterEnvironment] = None, + # test_metrics: Optional[Dict[str, Metric]] = None, + name: str = None + ) -> None: + super().__init__(model=model, name=name) + self.save_parameters(**self.locals2params(locals())) + self.model = self.model.eval() + # self.seed = seed + # self.strategy = strategy + # self.cluster = cluster + + # Train and validation dataloaders + self.test_dataloader_class = dynamically_import_class( + test_dataloader_class + ) + test_dataloader_kwargs = ( + test_dataloader_kwargs + if test_dataloader_kwargs is not None else {} + ) + self.test_dataloader_kwargs = clear_key( + test_dataloader_kwargs, 'train_dataloader_kwargs', 'dataset' + ) + + # # Loggers + # self.logger = logger if logger is not None else ConsoleLogger() + + # # Metrics + # self.train_metrics = ( + # {} if train_metrics is None else train_metrics + # ) + # self.validation_metrics = ( + # self.train_metrics if validation_metrics is None + # else validation_metrics + # ) + + @monitor_exec + def execute( + self, + test_dataset: Dataset, + model: nn.Module = None, + ) -> Dict[str, Any]: + """Applies a torch model to a dataset for inference. + + Args: + test_dataset (Dataset[str, Any]): each item in this dataset is a + couple (item_unique_id, item) + model (nn.Module, optional): torch model. Overrides the existing + model, if given. Defaults to None. + + Returns: + Dict[str, Any]: maps each item ID to the corresponding predicted + value(s). + """ + if model is not None: + # Overrides existing "internal" model + self.model = model + + test_dataloader = self.test_dataloader_class( + test_dataset, **self.test_dataloader_kwargs + ) + + all_predictions = dict() + for samples_ids, samples in test_dataloader: + with torch.no_grad(): + pred = self.model(samples) + pred = self.transform_predictions(pred) + for idx, pre in zip(samples_ids, pred): + # For each item in the batch + if pre.numel() == 1: + pre = pre.item() + else: + pre = pre.to_dense().tolist() + all_predictions[idx] = pre + return all_predictions + + @abc.abstractmethod + def transform_predictions(self, batch: Batch) -> Batch: + """ + Post-process the predictions of the torch model (e.g., apply + threshold in case of multilabel classifier). + """ + + +class MulticlassTorchPredictor(TorchPredictor): + """ + Applies a pre-trained torch model to unseen data for + multiclass classification. + """ + + def transform_predictions(self, batch: Batch) -> Batch: + batch = batch.argmax(-1) + return batch + + +class MultilabelTorchPredictor(TorchPredictor): + """ + Applies a pre-trained torch model to unseen data for + multilabel classification, applying a threshold on the + output of the neural network. + """ + + threshold: float + + def __init__( + self, + model: Union[nn.Module, ModelLoader], + test_dataloader_class: str = 'torch.utils.data.DataLoader', + test_dataloader_kwargs: Optional[Dict] = None, + threshold: float = 0.5, + name: str = None + ) -> None: + super().__init__( + model, test_dataloader_class, test_dataloader_kwargs, name + ) + self.threshold = threshold + + def transform_predictions(self, batch: Batch) -> Batch: + return (batch > self.threshold).float() + + +class RegressionTorchPredictor(TorchPredictor): + """ + Applies a pre-trained torch model to unseen data for + regression, leaving untouched the output of the neural + network. + """ + + def transform_predictions(self, batch: Batch) -> Batch: + return batch diff --git a/src/itwinai/torch/mlflow.py b/src/itwinai/torch/mlflow.py new file mode 100644 index 00000000..07352ee8 --- /dev/null +++ b/src/itwinai/torch/mlflow.py @@ -0,0 +1,82 @@ +from typing import Dict, Optional +import os + +import mlflow +import yaml + + +def _get_mlflow_logger_conf(pl_config: Dict) -> Optional[Dict]: + """Extract MLFLowLogger configuration from pytorch lightning + configuration file, if present. + + Args: + pl_config (Dict): lightning configuration loaded in memory. + + Returns: + Optional[Dict]: if present, MLFLowLogger constructor arguments + (under 'init_args' key). + """ + if not pl_config['trainer'].get('logger'): + return None + if isinstance(pl_config['trainer']['logger'], list): + # If multiple loggers are provided + for logger_conf in pl_config['trainer']['logger']: + if logger_conf['class_path'].endswith('MLFlowLogger'): + return logger_conf['init_args'] + elif pl_config['trainer']['logger']['class_path'].endswith('MLFlowLogger'): + return pl_config['trainer']['logger']['init_args'] + + +def _mlflow_log_pl_config(pl_config: Dict, local_yaml_path: str) -> None: + os.makedirs(os.path.dirname(local_yaml_path), exist_ok=True) + with open(local_yaml_path, 'w') as outfile: + yaml.dump(pl_config, outfile, default_flow_style=False) + mlflow.log_artifact(local_yaml_path) + + +def init_lightning_mlflow( + pl_config: Dict, + default_experiment_name: str = 'Default', + tmp_dir: str = '.tmp', + **autolog_kwargs +) -> None: + """Initialize mlflow for pytorch lightning, also setting up + auto-logging (mlflow.pytorch.autolog(...)). Creates a new mlflow + run and attaches it to the mlflow auto-logger. + + Args: + pl_config (Dict): pytorch lightning configuration loaded in memory. + default_experiment_name (str, optional): used as experiment name + if it is not given in the lightning conf. Defaults to 'Default'. + tmp_dir (str): where to temporarily store some artifacts. + autolog_kwargs (kwargs): args for mlflow.pytorch.autolog(...). + """ + mlflow_conf: Optional[Dict] = _get_mlflow_logger_conf(pl_config) + if not mlflow_conf: + return + + tracking_uri = mlflow_conf.get('tracking_uri') + if not tracking_uri: + save_path = mlflow_conf.get('save_dir') + tracking_uri = "file://" + os.path.abspath(save_path) + + experiment_name = mlflow_conf.get('experiment_name') + if not experiment_name: + experiment_name = default_experiment_name + + mlflow.set_tracking_uri(tracking_uri) + mlflow.set_experiment(experiment_name) + mlflow.pytorch.autolog(**autolog_kwargs) + run = mlflow.start_run() + print(f"MLFlow's artifacts URI: {run.info.artifact_uri}") + + mlflow_conf['experiment_name'] = experiment_name + mlflow_conf['run_id'] = mlflow.active_run().info.run_id + + _mlflow_log_pl_config(pl_config, os.path.join(tmp_dir, 'pl_config.yml')) + + +def teardown_lightning_mlflow() -> None: + """End active mlflow run, if any.""" + if mlflow.active_run() is not None: + mlflow.end_run() diff --git a/src/itwinai/torch/models/__init__.py b/src/itwinai/torch/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/itwinai/torch/models/mnist.py b/src/itwinai/torch/models/mnist.py new file mode 100644 index 00000000..c222a5a3 --- /dev/null +++ b/src/itwinai/torch/models/mnist.py @@ -0,0 +1,75 @@ +import torch +import torch.nn as nn +import lightning as L + +from torch.nn import functional as F + + +class MNISTModel(L.LightningModule): + """ + Simple PL model for MNIST. + Adapted from + https://lightning.ai/docs/pytorch/stable/notebooks/lightning_examples/mnist-hello-world.html + """ + + def __init__( + self, + hidden_size: int = 64, + ): + super().__init__() + + # Automatically save constructor args as hyperparameters + self.save_hyperparameters() + + # Set our init args as class attributes + self.hidden_size = hidden_size + + # Hardcode some dataset specific attributes + self.num_classes = 10 + self.dims = (1, 28, 28) + channels, width, height = self.dims + + # Define PyTorch model + self.model = nn.Sequential( + nn.Flatten(), + nn.Linear(channels * width * height, hidden_size), + nn.ReLU(), + nn.Dropout(0.1), + nn.Linear(hidden_size, hidden_size), + nn.ReLU(), + nn.Dropout(0.1), + nn.Linear(hidden_size, self.num_classes), + ) + + def forward(self, x): + x = self.model(x) + return F.log_softmax(x, dim=1) + + def training_step(self, batch, batch_idx): + x, y = batch + logits = self(x) + loss = F.nll_loss(logits, y) + + # Log metrics with autolog + self.log("train_loss", loss, on_step=True, on_epoch=True) + + return loss + + def validation_step(self, batch, batch_idx): + x, y = batch + logits = self(x) + loss = F.nll_loss(logits, y) + # preds = torch.argmax(logits, dim=1) + self.log("val_loss", loss, prog_bar=True, on_step=True, on_epoch=True) + + def test_step(self, batch, batch_idx): + x, y = batch + logits = self(x) + loss = F.nll_loss(logits, y) + self.log("test_loss", loss) + + def predict_step(self, batch, batch_idx, dataloader_idx=0): + x, _ = batch + logits = self(x) + preds = torch.argmax(logits, dim=1) + return preds diff --git a/src/itwinai/torch/reproducibility.py b/src/itwinai/torch/reproducibility.py new file mode 100644 index 00000000..1513c82a --- /dev/null +++ b/src/itwinai/torch/reproducibility.py @@ -0,0 +1,48 @@ +""" +This module provides the tools to support reproducible execution of +torch scripts. +""" + +from typing import Optional +import numpy as np +import random + +import torch + + +def seed_worker(worker_id): + """Seed DataLoader worker.""" + worker_seed = torch.initial_seed() % 2**32 + np.random.seed(worker_seed) + random.seed(worker_seed) + + +def set_seed( + rnd_seed: Optional[int], + deterministic_cudnn: bool = True +) -> torch.Generator: + """Set torch random seed and return a PRNG object. + + Args: + rnd_seed (Optional[int]): random seed. If None, the seed is not set. + deterministic_cudnn (bool): if True, sets + ``torch.backends.cudnn.benchmark = False``, which may affect + performances. + + Returns: + torch.Generator: PRNG object. + """ + g = torch.Generator() + if rnd_seed is not None: + # Deterministic execution + np.random.seed(rnd_seed) + random.seed(rnd_seed) + torch.manual_seed(rnd_seed) + g.manual_seed(rnd_seed) + if torch.cuda.is_available(): + torch.cuda.manual_seed(rnd_seed) + torch.cuda.manual_seed_all(rnd_seed) + if deterministic_cudnn: + torch.backends.cudnn.benchmark = False + torch.backends.cudnn.deterministic = True + return g diff --git a/src/itwinai/torch/trainer.py b/src/itwinai/torch/trainer.py new file mode 100644 index 00000000..3f6f6550 --- /dev/null +++ b/src/itwinai/torch/trainer.py @@ -0,0 +1,712 @@ +"""Provides training logic for PyTorch models via Trainer classes.""" + +from typing import ( + Optional, Dict, Union, Tuple, List, Any, Literal +) +import os +import sys + +import torch +from torch.utils.data import DataLoader, Dataset +from torch.utils.data.distributed import DistributedSampler +import torch.distributed as dist +from torch.nn.parallel import DistributedDataParallel as DDP +import torch.nn as nn +from torch.optim.optimizer import Optimizer + +import lightning as L +from lightning.pytorch.cli import LightningCLI + +import horovod.torch as hvd + +from ..components import Trainer, monitor_exec +from .types import ( + Batch, Loss, LrScheduler, Metric +) +from ..loggers import LogMixin, Logger +from .reproducibility import seed_worker, set_seed +from .distributed import ( + TorchDistributedStrategy, + TorchDDPStrategy, + HorovodStrategy, + DeepSpeedStrategy, + NonDistributedStrategy, + distributed_resources_available +) +from ..utils import load_yaml +from .mlflow import ( + init_lightning_mlflow, + teardown_lightning_mlflow +) +from .config import TrainingConfiguration + + +class TorchTrainer(Trainer, LogMixin): + """Trainer class for torch training algorithms. + + Args: + config (Dict): training configuration containing hyperparameters. + epochs (int): number of training epochs. + model (Optional[nn.Module], optional): model to train. + Defaults to None. + strategy (Literal["ddp", "deepspeed", + "horovod"], optional): distributed strategy. + Defaults to 'ddp'. + validation_every (Optional[int], optional): run a validation epoch + every ``validation_every`` epochs. Disabled if None. Defaults to 1. + test_every (Optional[int], optional): run a test epoch + every ``test_every`` epochs. Disabled if None. Defaults to None. + random_seed (Optional[int], optional): set random seed for + reproducibility. If None, the seed is not set. Defaults to None. + logger (Optional[Logger], optional): logger for ML tracking. + Defaults to None. + log_all_workers (bool, optional): if True, the ``log`` method is + called on all workers in the distributed context. Defaults to False. + metrics (Optional[Dict[str, Metric]], optional): map of torchmetrics + metrics. Defaults to None. + checkpoints_location (str): path to checkpoints directory. + Defaults to "checkpoints". + checkpoint_every (Optional[int]): save a checkpoint every + ``checkpoint_every`` epochs. Disabled if None. Defaults to None. + name (Optional[str], optional): trainer custom name. Defaults to None. + """ + # TODO: + # - add checkpointing. + # - extract BaseTorchTrainer and extend it creating a set of trainer + # templates (e.g.. GAN, Classifier, Transformer) allowing scientists + # to reuse ML algos. + # - improve get from configuration object + + _strategy: TorchDistributedStrategy = None + + train_dataloader: DataLoader = None + validation_dataloader: DataLoader = None + test_dataloader: DataLoader = None + + model: nn.Module = None + loss: Loss = None + optimizer: Optimizer = None + lr_scheduler: LrScheduler = None + + torch_rng: torch.Generator = None + logger: Logger = None + train_glob_step: int = 0 + validation_glob_step: int = 0 + test_glob_step: int = 0 + metrics: Dict[str, Metric] + + def __init__( + self, + config: Dict, + epochs: int, + model: Optional[nn.Module] = None, + strategy: Literal["ddp", "deepspeed", "horovod"] = 'ddp', + validation_every: Optional[int] = 1, + test_every: Optional[int] = None, + random_seed: Optional[int] = None, + logger: Optional[Logger] = None, + log_all_workers: bool = False, + metrics: Optional[Dict[str, Metric]] = None, + checkpoints_location: str = "checkpoints", + checkpoint_every: Optional[int] = None, + name: Optional[str] = None + ) -> None: + super().__init__(name) + self.save_parameters(**self.locals2params(locals())) + + # config is mean to store all hyperparameters, which can very from use + # case to use case + # and include learning_rate, batch_size.... + self.config = TrainingConfiguration(**config) + self.epochs = epochs + self.model = model + self.strategy = strategy + self.validation_every = validation_every + self.test_every = test_every + self.random_seed = random_seed + self.logger = logger + self.log_all_workers = log_all_workers + self.metrics = metrics if metrics is not None else {} + self.checkpoints_location = checkpoints_location + os.makedirs(self.checkpoints_location, exist_ok=True) + self.checkpoint_every = checkpoint_every + + @property + def strategy(self) -> TorchDistributedStrategy: + return self._strategy + + @strategy.setter + def strategy(self, strategy: Union[str, TorchDistributedStrategy]) -> None: + if isinstance(strategy, TorchDistributedStrategy): + self._strategy = strategy + else: + self._strategy = self._detect_strategy(strategy) + + @property + def device(self) -> str: + return self.strategy.device() + + def _detect_strategy(self, strategy: str) -> TorchDistributedStrategy: + if not distributed_resources_available(): + print("WARNING: falling back to non-distributed strategy.") + dist_str = NonDistributedStrategy() + elif strategy == 'ddp': + dist_str = TorchDDPStrategy(backend='nccl') + elif strategy == 'horovod': + dist_str = HorovodStrategy() + elif strategy == 'deepspeed': + dist_str = DeepSpeedStrategy(backend='nccl') + else: + raise NotImplementedError( + f"Strategy '{strategy}' is not recognized/implemented.") + return dist_str + + def _init_distributed_strategy(self) -> None: + if not self.strategy.is_initialized: + self.strategy.init() + + def create_model_loss_optimizer(self) -> None: + """ + Instantiate a torch model, loss, optimizer, and LR scheduler using the + configuration provided in the Trainer constructor. + Generally a user-define method. + """ + ################################### + # Dear user, this is a method you # + # may be interested to override! # + ################################### + + if self.model is None: + # Model was not passed to the constructor. + # Create a model here + raise ValueError( + "self.model is None! Either pass it to the constructor or " + "override this method." + ) + + # A simple NLLLoss + self.loss = nn.functional.nll_loss + + # TODO: improve robustness of getting from config + self.optimizer = torch.optim.SGD( + self.model.parameters(), + lr=self.config.lr, + momentum=self.config.momentum + ) + # Create self.lr_scheduler if needed + + # IMPORTANT: model, optimizer, and scheduler need to be distributed + + # First, define strategy-wise optional configurations + # TODO: improve robustness of getting from config + if isinstance(self.strategy, DeepSpeedStrategy): + # Batch size definition is not optional for DeepSpeedStrategy! + distribute_kwargs = dict( + config_params=dict( + train_micro_batch_size_per_gpu=self.config.batch_size + ) + ) + elif isinstance(self.strategy, HorovodStrategy): + distribute_kwargs = dict( + compression=( + hvd.Compression.fp16 if self.config.fp16_allreduce + else hvd.Compression.none + ), + op=hvd.Adasum if self.config.use_adasum else hvd.Average, + gradient_predivide_factor=self.config.gradient_predivide_factor + ) + else: + distribute_kwargs = {} + + # Distributed model, optimizer, and scheduler + ( + self.model, + self.optimizer, + self.lr_scheduler + ) = self.strategy.distributed( + self.model, self.optimizer, self.lr_scheduler, **distribute_kwargs + ) + + def create_dataloaders( + self, + train_dataset: Dataset, + validation_dataset: Optional[Dataset] = None, + test_dataset: Optional[Dataset] = None + ) -> None: + """ + Create train, validation and test dataloaders using the + configuration provided in the Trainer constructor. + Generally a user-define method. + + Args: + train_dataset (Dataset): training dataset object. + validation_dataset (Optional[Dataset]): validation dataset object. + Default None. + test_dataset (Optional[Dataset]): test dataset object. + Default None. + """ + + ################################### + # Dear user, this is a method you # + # may be interested to override! # + ################################### + + # TODO: improve robustness of getting from config + self.train_dataloader = self.strategy.create_dataloader( + dataset=train_dataset, + batch_size=self.config.batch_size, + num_workers=self.config.num_workers, + pin_memory=self.config.pin_memory, + generator=self.torch_rng + ) + if validation_dataset is not None: + self.validation_dataloader = self.strategy.create_dataloader( + dataset=train_dataset, + batch_size=self.config.batch_size, + num_workers=self.config.num_workers, + pin_memory=self.config.pin_memory, + generator=self.torch_rng + ) + if test_dataset is not None: + self.test_dataloader = self.strategy.create_dataloader( + dataset=train_dataset, + batch_size=self.config.batch_size, + num_workers=self.config.num_workers, + pin_memory=self.config.pin_memory, + generator=self.torch_rng + ) + + def _setup_metrics(self): + """Move metrics to current device.""" + for m_name, metric in self.metrics.items(): + self.metrics[m_name] = metric.to(self.device) + + @monitor_exec + def execute( + self, + train_dataset: Dataset, + validation_dataset: Dataset, + test_dataset: Dataset + ) -> Tuple[Dataset, Dataset, Dataset, Any]: + """Prepares distributed environment and data structures + for the actual training. + + Args: + train_dataset (Dataset): training dataset. + validation_dataset (Dataset): validation dataset. + test_dataset (Dataset): test dataset. + + Returns: + Tuple[Dataset, Dataset, Dataset, Any]: training dataset, + validation dataset, test dataset, trained model. + """ + self.torch_rng = set_seed(self.random_seed) + self._init_distributed_strategy() + self._setup_metrics() + + self.create_dataloaders( + train_dataset=train_dataset, + validation_dataset=validation_dataset, + test_dataset=test_dataset + ) + self.create_model_loss_optimizer() + + if self.strategy.is_main_worker and self.logger: + self.logger.create_logger_context() + + self.train() + + if self.strategy.is_main_worker and self.logger: + self.logger.destroy_logger_context() + self.strategy.clean_up() + return train_dataset, validation_dataset, test_dataset, self.model + + def _set_epoch_dataloaders(self, epoch: int): + """ + Sets epoch in the distributed sampler of a dataloader when using it. + """ + if self.strategy.is_distributed: + self.train_dataloader.sampler.set_epoch(epoch) + if self.validation_dataloader is not None: + self.validation_dataloader.sampler.set_epoch(epoch) + if self.test_dataloader is not None: + self.test_dataloader.sampler.set_epoch(epoch) + + def log( + self, + item: Union[Any, List[Any]], + identifier: Union[str, List[str]], + kind: str = 'metric', + step: Optional[int] = None, + batch_idx: Optional[int] = None, + **kwargs + ) -> None: + """Log ``item`` with ``identifier`` name of ``kind`` type at ``step`` + time step. + + Args: + item (Union[Any, List[Any]]): element to be logged (e.g., metric). + identifier (Union[str, List[str]]): unique identifier for the + element to log(e.g., name of a metric). + kind (str, optional): type of the item to be logged. Must be one + among the list of self.supported_types. Defaults to 'metric'. + step (Optional[int], optional): logging step. Defaults to None. + batch_idx (Optional[int], optional): DataLoader batch counter + (i.e., batch idx), if available. Defaults to None. + """ + if self.logger and ( + self.strategy.is_main_worker or self.log_all_workers): + self.logger.log( + item=item, + identifier=identifier, + kind=kind, + step=step, + batch_idx=batch_idx, + **kwargs + ) + + def save_checkpoint( + self, name: str, epoch: int, loss: Optional[torch.Tensor] = None + ) -> None: + """Save training checkpoint. + + Args: + name (str): name of the checkpoint. + epoch (int): current training epoch. + loss (Optional[torch.Tensor]): current loss (if available). + """ + state = dict( + epoch=epoch, + loss=loss, + optimizer=self.optimizer.state_dict(), + model=self.model.state_dict(), + lr_scheduler=self.lr_scheduler + ) + ckpt_path = os.path.join(self.checkpoints_location, name) + torch.save(state, ckpt_path) + print(f"Saved '{name}' checkpoint at {ckpt_path}") + + # Save checkpoint to logger + self.log(ckpt_path, name, kind='artifact') + + def load_checkpoint(self, name: str) -> None: + """Load state from a checkpoint. + + Args: + name (str): name of the checkpoint to load, assuming it + is under ``self.checkpoints_location`` location. + """ + ckpt_path = os.path.join(self.checkpoints_location, name) + state = torch.load(ckpt_path, map_location=self.device) + self.model.load_state_dict(state['model']) + self.optimizer.load_state_dict(state['optimizer']) + self.lr_scheduler = state['lr_scheduler'] + + def train(self): + """Trains a machine learning model. + Main training loop/logic. + + Args: + train_dataset (Dataset): training dataset. + validation_dataset (Dataset): validation dataset. + test_dataset (Dataset): test dataset. + + Returns: + Tuple[Dataset, Dataset, Dataset, Any]: training dataset, + validation dataset, test dataset, trained model. + """ + best_loss = float('inf') + for epoch in range(self.epochs): + epoch_n = epoch + 1 + self._set_epoch_dataloaders(epoch) + self.train_epoch() + if self.validation_every and epoch_n % self.validation_every == 0: + val_loss = self.validation_epoch() + + # Checkpointing current best model + worker_val_losses = self.strategy.gather_obj( + val_loss, dst_rank=0) + if self.strategy.global_rank() == 0: + avg_loss = torch.mean( + torch.stack(worker_val_losses) + ).detach().cpu() + if avg_loss < best_loss: + ckpt_name = "best_model.pth" + self.save_checkpoint( + name=ckpt_name, epoch=epoch, loss=avg_loss) + + if self.test_every and epoch_n % self.test_every == 0: + self.test_epoch() + + # Periodic checkpointing + if (self.strategy.is_main_worker and self.checkpoint_every + and epoch_n % self.checkpoint_every == 0): + ckpt_name = f"epoch_{epoch}.pth" + self.save_checkpoint(name=ckpt_name, epoch=epoch) + + def compute_metrics( + self, + true: Batch, + pred: Batch, + logger_step: int, + batch_idx: Optional[int], + stage: str = 'train' + ) -> Dict[str, Any]: + """Compute and log metrics. + + Args: + metrics (Dict[str, Metric]): metrics dict. Can be + ``self.train_metrics`` or ``self.validation_metrics``. + true (Batch): true values. + pred (Batch): predicted values. + logger_step (int): global step to pass to the logger. + stage (str): 'train', 'validation'... + + Returns: + Dict[str, Any]: metric values. + """ + m_values = {} + for m_name, metric in self.metrics.items(): + # metric = metric.to(self.device) + m_val = metric(pred, true).detach().cpu().numpy() + self.log( + item=m_val, + identifier=f'{m_name}_{stage}', + kind='metric', + step=logger_step, + batch_idx=batch_idx + ) + m_values[m_name] = m_val + return m_values + + def training_step( + self, + batch: Batch, + batch_idx: int + ) -> Tuple[Loss, Dict[str, Any]]: + x, y = batch + x, y = x.to(self.device), y.to(self.device) + pred_y = self.model(x) + loss: Loss = self.loss(pred_y, y) + self.log( + item=loss.item(), + identifier='training_loss', + kind='metric', + step=self.train_glob_step, + batch_idx=batch_idx + ) + metrics: Dict[str, Any] = self.compute_metrics( + true=y, + pred=pred_y, + logger_step=self.train_glob_step, + batch_idx=batch_idx, + stage='training' + ) + return loss, metrics + + def validation_step( + self, + batch: Batch, + batch_idx: int + ) -> Tuple[Loss, Dict[str, Any]]: + x, y = batch + x, y = x.to(self.device), y.to(self.device) + with torch.no_grad(): + pred_y = self.model(x) + loss: Loss = self.loss(pred_y, y) + self.log( + item=loss.item(), + identifier='validation_loss', + kind='metric', + step=self.validation_glob_step, + batch_idx=batch_idx + ) + metrics: Dict[str, Any] = self.compute_metrics( + true=y, + pred=pred_y, + logger_step=self.validation_glob_step, + batch_idx=batch_idx, + stage='validation' + ) + return loss, metrics + + def train_epoch(self) -> Loss: + self.model.train() + train_losses = [] + for batch_idx, train_batch in enumerate(self.train_dataloader): + loss, metrics = self.training_step( + batch=train_batch, + batch_idx=batch_idx + ) + # TODO: merge and log batch metrics and loss into epoch metrics + self.optimizer.zero_grad() + loss.backward() + self.optimizer.step() + train_losses.append(loss) + # Important: update counter + self.train_glob_step += 1 + + # Aggregate and log losses + avg_loss = torch.mean(torch.stack(train_losses)).detach().cpu() + self.log( + item=avg_loss.item(), + identifier='training_loss_epoch', + kind='metric', + step=self.train_glob_step, + ) + return avg_loss + + def validation_epoch(self) -> Loss: + if self.validation_dataloader is not None: + self.model.eval() + validation_losses = [] + for batch_idx, val_batch \ + in enumerate(self.validation_dataloader): + # TODO: merge and log batch metrics and loss into epoch metrics + loss, metrics = self.validation_step( + batch=val_batch, + batch_idx=batch_idx + ) + validation_losses.append(loss) + # Important: update counter + self.validation_glob_step += 1 + + # Aggregate and log losses + avg_loss = torch.mean( + torch.stack(validation_losses) + ).detach().cpu() + self.log( + item=avg_loss.item(), + identifier='validation_loss_epoch', + kind='metric', + step=self.validation_glob_step, + ) + return avg_loss + + def test_epoch(self): + # TODO: implement test epoch + raise NotImplementedError() + + +class TorchLightningTrainer(Trainer): + """Generic trainer for torch Lightning workflows. + + Args: + config (Union[Dict, str]): (path to a) Lightning configuration + https://pytorch-lightning.readthedocs.io/en/1.6.5/common/lightning_cli.html + mlflow_saved_model (str, optional): name of the model created in + MLFlow. Defaults to 'my_model'. + """ + + def __init__( + self, + config: Union[Dict, str], + mlflow_saved_model: str = 'my_model' + ): + self.save_parameters(**self.locals2params(locals())) + super().__init__() + if isinstance(config, str) and os.path.isfile(config): + # Load from YAML + config = load_yaml(config) + self.conf = config + self.mlflow_saved_model = mlflow_saved_model + + @monitor_exec + def execute(self) -> Any: + init_lightning_mlflow( + self.conf, + tmp_dir='/tmp', + registered_model_name=self.mlflow_saved_model + ) + old_argv = sys.argv + sys.argv = ['some_script_placeholder.py'] + cli = LightningCLI( + args=self.conf, + model_class=L.LightningModule, + datamodule_class=L.LightningDataModule, + run=False, + save_config_kwargs={ + "overwrite": True, + "config_filename": "pl-training.yml", + }, + subclass_mode_model=True, + subclass_mode_data=True, + ) + sys.argv = old_argv + cli.trainer.fit(cli.model, datamodule=cli.datamodule) + teardown_lightning_mlflow() + + +def preproc_dataloader(dataloader: DataLoader, gwsize, grank): + """Makes a Dataloader distributed.""" + sampler = DistributedSampler( + dataloader.dataset, + num_replicas=gwsize, + rank=grank, + shuffle=True + ) + # Recreate dataloader, with updated sampler + return DataLoader( + dataloader.dataset, + batch_size=dataloader.batch_size, + sampler=sampler, + num_workers=dataloader.num_workers, + collate_fn=dataloader.collate_fn, + pin_memory=dataloader.pin_memory, + drop_last=dataloader.drop_last, + timeout=dataloader.timeout, + worker_init_fn=seed_worker, # dataloader.worker_init_fn, + multiprocessing_context=dataloader.multiprocessing_context, + generator=dataloader.generator, + prefetch_factor=dataloader.prefetch_factor, + persistent_workers=dataloader.persistent_workers, + pin_memory_device=dataloader.pin_memory_device + ) + + +def distributed(func): + """The decorated function must have a standard signature. + Its first arguments must be: + model, train_dataloader, validation_dataloader, device (in this order). + + Additional args or kwargs are allowed consistently with the signature + of the decorated function. + """ + def dist_train( + model, train_dataloader, validation_dataloader=None, device='cpu', + *args, **kwargs + ): + if torch.cuda.is_available(): + dist.init_process_group(backend='nccl') + + if torch.cuda.is_available(): + lwsize = torch.cuda.device_count() # local world size - per node + gwsize = dist.get_world_size() # global world size - per run + grank = dist.get_rank() # global rank - assign per run + lrank = dist.get_rank() % lwsize # local rank - assign per node + else: + gwsize = 1 + grank = 0 + lrank = 0 + + device = torch.device( + 'cuda' if torch.cuda.is_available() else 'cpu', lrank) + if torch.cuda.is_available(): + torch.cuda.set_device(lrank) + + model = model.to(device) + model = DDP(model, device_ids=[device], output_device=device) + + train_dataloader = preproc_dataloader(train_dataloader, gwsize, grank) + if validation_dataloader is not None: + validation_dataloader = preproc_dataloader( + validation_dataloader, gwsize, grank) + + try: + func(model, train_dataloader, validation_dataloader, device, + *args, **kwargs) + finally: + if torch.cuda.is_available(): + dist.barrier() + dist.destroy_process_group() + return dist_train diff --git a/src/itwinai/torch/types.py b/src/itwinai/torch/types.py new file mode 100644 index 00000000..0b6f88ad --- /dev/null +++ b/src/itwinai/torch/types.py @@ -0,0 +1,74 @@ +"""Custom types definition.""" + +from typing import Callable +from enum import Enum, EnumMeta + +import torch +from torch import nn + +Loss = nn.Module +LrScheduler = nn.Module +Batch = torch.Tensor +Metric = Callable + + +class MetaEnum(EnumMeta): + def __contains__(cls, item): + try: + cls(item) + except ValueError: + return False + return True + + +class BaseEnum(Enum, metaclass=MetaEnum): + @classmethod + def list(cls): + return list(map(lambda c: c.value, cls)) + + +class TorchDistributedBackend(BaseEnum): + """ + Enum for torch distributed backends. + Reference: https://pytorch.org/docs/stable/distributed.html#backends + """ + DEFAULT = 'nccl' + GLOO = 'gloo' + NCCL = 'nccl' + MPI = 'mpi' + + +class TorchDistributedStrategy(BaseEnum): + DEFAULT = None + NONE = None + DDP = 'ddp' + HVD = 'horovod' + DS = 'deepspeed' + + +class TorchLoss(BaseEnum): + """ + Torch loss class names. + TODO: complete from https://pytorch.org/docs/stable/nn.html#loss-functions + """ + L1 = 'L1Loss' + MSE = 'MSELoss' + CROSS_ENTROPY = 'CrossEntropyLoss' + NLLLOSS = 'NLLLoss' + + +class TorchOptimizer(BaseEnum): + """ + Torch optimizer class names. + TODO: complete from https://pytorch.org/docs/stable/optim.html#algorithms + """ + SGD = 'SGD' + ADAM = 'Adam' + + +class UninitializedStrategyError(Exception): + """Error raised when a strategy has not been initialized.""" + + +class DistributedStrategyError(Exception): + """Error raised when a strategy has already been initialized.""" diff --git a/src/itwinai/type.py b/src/itwinai/type.py new file mode 100644 index 00000000..977068b9 --- /dev/null +++ b/src/itwinai/type.py @@ -0,0 +1,15 @@ +""" +Framework-independent types. +""" + + +class MLArtifact: + """A framework-independent machine learning artifact.""" + + +class MLDataset(MLArtifact): + """A framework-independent machine learning dataset.""" + + +class MLModel(MLArtifact): + """A framework-independent machine learning model.""" diff --git a/src/itwinai/utils.py b/src/itwinai/utils.py new file mode 100644 index 00000000..8bf69037 --- /dev/null +++ b/src/itwinai/utils.py @@ -0,0 +1,181 @@ +""" +Utilities for itwinai package. +""" +from typing import Dict, Type, Callable, Tuple, Hashable +import sys +import inspect +from collections.abc import MutableMapping +import yaml + + +def load_yaml(path: str) -> Dict: + """Load YAML file as dict. + + Args: + path (str): path to YAML file. + + Raises: + exc: yaml.YAMLError for loading/parsing errors. + + Returns: + Dict: nested dict representation of parsed YAML file. + """ + with open(path, "r", encoding="utf-8") as yaml_file: + try: + loaded_config = yaml.safe_load(yaml_file) + except yaml.YAMLError as exc: + print(exc) + raise exc + return loaded_config + + +def dynamically_import_class(name: str) -> Type: + """ + Dynamically import class by module path. + Adapted from https://stackoverflow.com/a/547867 + + Args: + name (str): path to the class (e.g., mypackage.mymodule.MyClass) + + Returns: + __class__: class type. + """ + try: + module, class_name = name.rsplit(".", 1) + mod = __import__(module, fromlist=[class_name]) + klass = getattr(mod, class_name) + except ModuleNotFoundError as err: + print( + f"Module not found when trying to dynamically import '{name}'. " + "Make sure that the module's file is reachable from your current " + "directory." + ) + raise err + except Exception as err: + print( + f"Exception occurred when trying to dynamically import '{name}'. " + "Make sure that the module's file is reachable from your current " + "directory and that the class is present in that module." + ) + raise err + + return klass + + +def flatten_dict( + d: MutableMapping, parent_key: str = "", sep: str = "." +) -> MutableMapping: + """Flatten dictionary + + Args: + d (MutableMapping): nested dictionary to flatten + parent_key (str, optional): prefix for all keys. Defaults to ''. + sep (str, optional): separator for nested key concatenation. + Defaults to '.'. + + Returns: + MutableMapping: flattened dictionary with new keys. + """ + items = [] + for k, v in d.items(): + new_key = parent_key + sep + k if parent_key else k + if isinstance(v, MutableMapping): + items.extend(flatten_dict(v, new_key, sep=sep).items()) + else: + items.append((new_key, v)) + return dict(items) + + +class SignatureInspector: + """Provides the functionalities to inspect the signature of a function + or a method. + + Args: + func (Callable): function to be inspected. + """ + + INFTY: int = sys.maxsize + + def __init__(self, func: Callable) -> None: + self.func = func + self.func_params = inspect.signature(func).parameters.items() + + @property + def has_varargs(self) -> bool: + """Checks if the function has ``*args`` parameter.""" + return any(map( + lambda p: p[1].kind == p[1].VAR_POSITIONAL, + self.func_params + )) + + @property + def has_kwargs(self) -> bool: + """Checks if the function has ``**kwargs`` parameter.""" + return any(map( + lambda p: p[1].kind == p[1].VAR_KEYWORD, + self.func_params + )) + + @property + def required_params(self) -> Tuple[str]: + """Names of required parameters. Class method's 'self' is skipped.""" + required_params = list(filter( + lambda p: (p[0] != 'self' and p[1].default == inspect._empty + and p[1].kind != p[1].VAR_POSITIONAL + and p[1].kind != p[1].VAR_KEYWORD), + self.func_params + )) + return tuple(map(lambda p: p[0], required_params)) + + @property + def min_params_num(self) -> int: + """Minimum number of arguments required.""" + return len(self.required_params) + + @property + def max_params_num(self) -> int: + """Max number of supported input arguments. + If no limit, ``SignatureInspector.INFTY`` is returned. + """ + if self.has_kwargs or self.has_varargs: + return self.INFTY + return len(self.func_params) + + +def str_to_slice(interval: str) -> slice: + import re + # TODO: add support for slices starting with empty index + # e.g., :20:3 + if not re.match(r"\d+(:\d+)?(:\d+)?", interval): + raise ValueError( + f"Received invalid interval for slice: '{interval}'" + ) + if ":" in interval: + return slice(*map( + lambda x: int(x.strip()) if x.strip() else None, + interval.split(':') + )) + return int(interval) + + +def clear_key( + my_dict: Dict, + dict_name: str, + key: Hashable, + complain: bool = True +) -> Dict: + """Remove key from dictionary if present and complain. + + Args: + my_dict (Dict): Dictionary. + dict_name (str): name of the dictionary. + key (Hashable): Key to remove. + """ + if key in my_dict: + if complain: + print( + f"Field '{key}' should not be present " + f"in dictionary '{dict_name}'" + ) + del my_dict[key] + return my_dict diff --git a/tests/components/conftest.py b/tests/components/conftest.py new file mode 100644 index 00000000..0ba66af1 --- /dev/null +++ b/tests/components/conftest.py @@ -0,0 +1,72 @@ +import pytest + +pytest.PIPE_LIST_YAML = """ +my-list-pipeline: + class_path: itwinai.pipeline.Pipeline + init_args: + steps: + - class_path: itwinai.tests.dummy_components.FakePreproc + init_args: + max_items: 32 + name: my-preproc + + - class_path: itwinai.tests.dummy_components.FakeTrainer + init_args: + lr: 0.001 + batch_size: 32 + name: my-trainer + + - class_path: itwinai.tests.dummy_components.FakeSaver + init_args: + save_path: ./some/path + name: my-saver +""" + +pytest.PIPE_DICT_YAML = """ +my-dict-pipeline: + class_path: itwinai.pipeline.Pipeline + init_args: + steps: + preproc-step: + class_path: itwinai.tests.dummy_components.FakePreproc + init_args: + max_items: 32 + name: my-preproc + + train-step: + class_path: itwinai.tests.dummy_components.FakeTrainer + init_args: + lr: 0.001 + batch_size: 32 + name: my-trainer + + save-step: + class_path: itwinai.tests.dummy_components.FakeSaver + init_args: + save_path: ./some/path + name: my-saver +""" + +pytest.NESTED_PIPELINE = """ +some: + field: + nst-pipeline: + class_path: itwinai.pipeline.Pipeline + init_args: + steps: + - class_path: itwinai.tests.FakePreproc + init_args: + max_items: 32 + name: my-preproc + + - class_path: itwinai.tests.FakeTrainer + init_args: + lr: 0.001 + batch_size: 32 + name: my-trainer + + - class_path: itwinai.tests.FakeSaver + init_args: + save_path: ./some/path + name: my-saver +""" diff --git a/tests/components/test_components.py b/tests/components/test_components.py new file mode 100644 index 00000000..890188d7 --- /dev/null +++ b/tests/components/test_components.py @@ -0,0 +1,151 @@ +import pytest + +from itwinai.components import Trainer, Adapter +from itwinai.pipeline import Pipeline +from itwinai.tests import ( + FakeGetterExec, FakeSplitterExec, FakeTrainerExec, FakeSaverExec +) +from itwinai.serialization import SerializationError + + +def test_serializable(): + """Test serialization of components.""" + comp = FakeGetterExec(data_uri='http://...') + dict_serializ = comp.to_dict() + assert isinstance(dict_serializ, dict) + assert comp.name == "FakeGetterExec" + assert dict_serializ == dict( + class_path="itwinai.tests.dummy_components.FakeGetterExec", + init_args=dict(data_uri='http://...', name=None) + ) + + # List + comp = FakeGetterExec(data_uri=[1, 2, 3]) + dict_serializ = comp.to_dict() + assert isinstance(dict_serializ, dict) + assert comp.name == "FakeGetterExec" + assert dict_serializ == dict( + class_path="itwinai.tests.dummy_components.FakeGetterExec", + init_args=dict(data_uri=[1, 2, 3], name=None) + ) + + # Tuple + comp = FakeGetterExec(data_uri=(1, 2, 3)) + dict_serializ = comp.to_dict() + assert isinstance(dict_serializ, dict) + assert comp.name == "FakeGetterExec" + assert dict_serializ == dict( + class_path="itwinai.tests.dummy_components.FakeGetterExec", + init_args=dict(data_uri=[1, 2, 3], name=None) + ) + + # Set + comp = FakeGetterExec(data_uri={1, 2, 3}) + dict_serializ = comp.to_dict() + assert isinstance(dict_serializ, dict) + assert comp.name == "FakeGetterExec" + assert dict_serializ == dict( + class_path="itwinai.tests.dummy_components.FakeGetterExec", + init_args=dict(data_uri=[1, 2, 3], name=None) + ) + + # Dict + comp = FakeGetterExec(data_uri=dict(foo=12, bar="123", hl=3.14)) + dict_serializ = comp.to_dict() + assert isinstance(dict_serializ, dict) + assert comp.name == "FakeGetterExec" + assert dict_serializ == dict( + class_path="itwinai.tests.dummy_components.FakeGetterExec", + init_args=dict(data_uri=dict(foo=12, bar="123", hl=3.14), name=None) + ) + + # Non serializable obj + class NonSerializable: + ... + + comp = FakeGetterExec(data_uri=NonSerializable()) + with pytest.raises(SerializationError) as exc_info: + dict_serializ = comp.to_dict() + assert ("is not a Python built-in type and it does not extend" + in str(exc_info.value)) + + # Local component class + class MyTrainer(Trainer): + def execute(self): + ... + + comp = MyTrainer() + with pytest.raises(SerializationError) as exc_info: + dict_serializ = comp.to_dict() + assert ("is defined locally, which is not supported for serialization." + in str(exc_info.value)) + + +def test_adapter(): + """Test Adapter component.""" + prefix = Adapter.INPUT_PREFIX + adapter = Adapter( + policy=[f"{prefix}{3-i}" for i in range(4)] + ) + result = adapter.execute(0, 1, 2, 3) + assert result == (3, 2, 1, 0) + + result = adapter.execute(*tuple(range(100))) + assert result == (3, 2, 1, 0) + + adapter = Adapter( + policy=[f"{prefix}0" for i in range(4)] + ) + result = adapter.execute(0, 1, 2, 3) + assert result == (0, 0, 0, 0) + + adapter = Adapter( + policy=[f"{prefix}{i % 2}" for i in range(4)] + ) + result = adapter.execute(0, 1, 2, 3) + assert result == (0, 1, 0, 1) + + adapter = Adapter( + policy=[f"{prefix}2", "hello", "world", 3.14] + ) + result = adapter.execute(0, 1, 2, 3) + assert result == (2, "hello", "world", 3.14) + + adapter = Adapter( + policy=[1, 3, 5, 7, 11] + ) + result = adapter.execute(0, 1, 2, 3) + assert result == (1, 3, 5, 7, 11) + + adapter = Adapter( + policy=[f"{prefix}{9-i}" for i in range(10)] + ) + with pytest.raises(IndexError) as exc_info: + result = adapter.execute(0, 1) + assert str(exc_info.value) == ( + "The args received as input by 'Adapter' are not consistent with " + "the given adapter policy because input args are too few! Input " + "args are 2 but the policy foresees at least 10 items." + ) + + adapter = Adapter( + policy=[] + ) + result = adapter.execute(*tuple(range(100))) + assert result == () + + +@pytest.mark.integration +def test_adapter_integration_pipeline(): + """Test integration of Adapter component in the pipeline, + connecting other components. + """ + pipeline = Pipeline([ + FakeGetterExec(data_uri='http://...'), + FakeSplitterExec(train_prop=.7), + FakeTrainerExec(lr=1e-3, batch_size=32), + Adapter(policy=[f"{Adapter.INPUT_PREFIX}-1"]), + FakeSaverExec(save_path="my_model.pth") + ]) + saved_model = pipeline.execute() + assert saved_model == FakeTrainerExec.model diff --git a/tests/components/test_pipe_parser.py b/tests/components/test_pipe_parser.py new file mode 100644 index 00000000..f26d105d --- /dev/null +++ b/tests/components/test_pipe_parser.py @@ -0,0 +1,216 @@ +import yaml +import pytest + +from itwinai.components import BaseComponent +from itwinai.parser import ConfigParser, add_replace_field +from itwinai.tests import FakeTrainer, FakePreproc, FakeSaver + + +def test_add_replace_field(): + conf = {} + add_replace_field(conf, "some.key.chain", 123) + target1 = dict(some=dict(key=dict(chain=123))) + assert conf == target1 + + add_replace_field(conf, "some.key.chain", 222) + target2 = dict(some=dict(key=dict(chain=222))) + assert conf == target2 + + add_replace_field(conf, "some.key.field", 333) + target3 = dict(some=dict(key=dict(chain=222, field=333))) + assert conf == target3 + + conf['some']['list'] = [1, 2, 3] + add_replace_field(conf, "some.list.0", 3) + target4 = dict(some=dict( + key=dict(chain=222, field=333), + list=[3, 2, 3] + )) + assert conf == target4 + + add_replace_field(conf, "some.list.0.some.el", 7) + target5 = dict(some=dict( + key=dict(chain=222, field=333), + list=[dict(some=dict(el=7)), 2, 3] + )) + assert conf == target5 + + conf2 = dict(first=dict(list1=[[0, 1], [2, 3]], el=0)) + add_replace_field(conf2, "first.list1.1.0", 77) + target6 = dict(first=dict(list1=[[0, 1], [77, 3]], el=0)) + assert conf2 == target6 + + conf3 = dict(first=dict( + list1=[[0, dict(nst=("el", dict(ciao="ciao")))], [2, 3]], el=0)) + add_replace_field(conf3, "first.list1.0.1.nst.1.ciao", "hello") + target7 = dict(first=dict( + list1=[[0, dict(nst=("el", dict(ciao="hello")))], [2, 3]], el=0)) + assert conf3 == target7 + + add_replace_field(conf3, "first.list1.0.1.nst.1.ciao.I.am.john", True) + target8 = dict(first=dict( + list1=[ + [0, dict(nst=("el", dict(ciao=dict(I=dict(am=dict(john=True))))))], + [2, 3] + ], el=0)) + assert conf3 == target8 + + +def test_parse_list_pipeline(): + """Parse a pipeline from config file, + where the pipeline is define as a list of components. + """ + config = yaml.safe_load(pytest.PIPE_LIST_YAML) + parser = ConfigParser(config=config) + pipe = parser.parse_pipeline( + pipeline_nested_key="my-list-pipeline" + ) + + assert isinstance(pipe.steps, list) + for step in pipe.steps: + assert isinstance(step, BaseComponent) + + +def test_parse_dict_pipeline(): + """Parse a pipeline from config file, + where the pipeline is define as a dict of components. + """ + config = yaml.safe_load(pytest.PIPE_DICT_YAML) + parser = ConfigParser(config=config) + pipe = parser.parse_pipeline( + pipeline_nested_key="my-dict-pipeline" + ) + + assert isinstance(pipe.steps, dict) + for step in pipe.steps.values(): + assert isinstance(step, BaseComponent) + + +def test_parse_non_existing_pipeline(): + """Parse a pipeline from config file, + where the pipeline key is wrong. + """ + config = yaml.safe_load(pytest.PIPE_DICT_YAML) + parser = ConfigParser(config=config) + with pytest.raises(KeyError): + _ = parser.parse_pipeline( + pipeline_nested_key="non-existing-pipeline" + ) + + +def test_parse_nested_pipeline(): + """Parse a pipeline from config file, + where the pipeline key is nested. + """ + config = yaml.safe_load(pytest.NESTED_PIPELINE) + parser = ConfigParser(config=config) + _ = parser.parse_pipeline( + pipeline_nested_key="some.field.nst-pipeline" + ) + + +def test_dynamic_override_parser_pipeline_dict(): + """Parse a pipeline from config file, + and verify that dynamic override works + in a pipeline composed of a dict of components. + """ + config = yaml.safe_load(pytest.PIPE_DICT_YAML) + + override_keys = { + "my-dict-pipeline.init_args.steps.preproc-step.init_args.max_items": 33 + } + parser = ConfigParser(config=config, override_keys=override_keys) + pipe = parser.parse_pipeline( + pipeline_nested_key="my-dict-pipeline" + ) + assert pipe.steps['preproc-step'].max_items == 33 + + +def test_dynamic_override_parser_pipeline_list(): + """Parse a pipeline from config file, + and verify that dynamic override works + in a pipeline composed of a list of components. + """ + config = yaml.safe_load(pytest.PIPE_LIST_YAML) + + override_keys = { + "my-list-pipeline.init_args.steps.0.init_args.max_items": 42 + } + parser = ConfigParser(config=config, override_keys=override_keys) + pipe = parser.parse_pipeline( + pipeline_nested_key="my-list-pipeline" + ) + assert pipe.steps[0].max_items == 42 + + +def test_parse_step_list_pipeline(): + """Parse a pipeline step from config file, + where the pipeline is define as a list of components. + """ + config = yaml.safe_load(pytest.PIPE_LIST_YAML) + parser = ConfigParser(config=config) + step = parser.parse_step( + step_idx=1, + pipeline_nested_key="my-list-pipeline" + ) + + assert isinstance(step, BaseComponent) + assert isinstance(step, FakeTrainer) + + with pytest.raises(IndexError): + _ = parser.parse_step( + step_idx=12, + pipeline_nested_key="my-list-pipeline" + ) + with pytest.raises(TypeError): + _ = parser.parse_step( + step_idx='my-step-name', + pipeline_nested_key="my-list-pipeline" + ) + + +def test_parse_step_dict_pipeline(): + """Parse a pipeline step from config file, + where the pipeline is define as a dict of components. + """ + config = yaml.safe_load(pytest.PIPE_DICT_YAML) + parser = ConfigParser(config=config) + step = parser.parse_step( + step_idx='preproc-step', + pipeline_nested_key="my-dict-pipeline" + ) + + assert isinstance(step, BaseComponent) + assert isinstance(step, FakePreproc) + + with pytest.raises(KeyError): + _ = parser.parse_step( + step_idx='unk-step', + pipeline_nested_key="my-dict-pipeline" + ) + with pytest.raises(KeyError): + _ = parser.parse_step( + step_idx=0, + pipeline_nested_key="my-dict-pipeline" + ) + + +def test_parse_step_nested_pipeline(): + """Parse a pipeline step from config file, + where the pipeline is nested under some field. + """ + config = yaml.safe_load(pytest.NESTED_PIPELINE) + parser = ConfigParser(config=config) + step = parser.parse_step( + step_idx=2, + pipeline_nested_key="some.field.nst-pipeline" + ) + + assert isinstance(step, BaseComponent) + assert isinstance(step, FakeSaver) + + with pytest.raises(KeyError): + _ = parser.parse_step( + step_idx=2, + pipeline_nested_key="my-pipeline" + ) diff --git a/tests/components/test_pipeline.py b/tests/components/test_pipeline.py new file mode 100644 index 00000000..a61198b6 --- /dev/null +++ b/tests/components/test_pipeline.py @@ -0,0 +1,83 @@ +import yaml +import pytest + +from itwinai.pipeline import Pipeline +from itwinai.parser import ConfigParser +from itwinai.tests import ( + FakeGetterExec, FakeSplitterExec, FakeTrainerExec, FakeSaverExec +) + + +def test_slice_into_sub_pipelines(): + """Test slicing the pipeline to obtain + sub-pipelines as Pipeline objects. + """ + p = Pipeline(['step1', 'step2', 'step3']) + sub_pipe1, sub_pipe2 = p[:1], p[1:] + assert isinstance(sub_pipe1, Pipeline) + assert isinstance(sub_pipe2, Pipeline) + assert len(sub_pipe1) == 1 + assert sub_pipe1[0] == "step1" + assert len(sub_pipe2) == 2 + + p = Pipeline(dict(step1="step1", step2="step2", step3="step3")) + sub_pipe1, sub_pipe2 = p[:1], p[1:] + assert isinstance(sub_pipe1, Pipeline) + assert isinstance(sub_pipe2, Pipeline) + assert len(sub_pipe1) == 1 + assert sub_pipe1["step1"] == "step1" + assert len(sub_pipe2) == 2 + + +def test_serialization_pipe_list(): + """Test dict serialization of pipeline + defined as list of BaseComponent objects. + """ + config = yaml.safe_load(pytest.PIPE_LIST_YAML) + parser = ConfigParser(config=config) + pipe = parser.parse_pipeline( + pipeline_nested_key="my-list-pipeline" + ) + + dict_pipe = pipe.to_dict() + del dict_pipe['init_args']['name'] + dict_pipe = {"my-list-pipeline": dict_pipe} + assert dict_pipe == config + + +def test_serialization_pipe_dict(): + """Test dict serialization of pipeline + defined as dict of BaseComponent objects. + """ + config = yaml.safe_load(pytest.PIPE_DICT_YAML) + parser = ConfigParser(config=config) + pipe = parser.parse_pipeline( + pipeline_nested_key="my-dict-pipeline" + ) + + dict_pipe = pipe.to_dict() + del dict_pipe['init_args']['name'] + dict_pipe = {"my-dict-pipeline": dict_pipe} + assert dict_pipe == config + + +def test_arguments_mismatch(): + """Test mismatch of arguments passed among components in a pipeline.""" + pipeline = Pipeline([ + FakeGetterExec(data_uri='http://...'), + FakeSplitterExec(train_prop=.7), + FakeTrainerExec(lr=1e-3, batch_size=32), + # Adapter(policy=[f"{Adapter.INPUT_PREFIX}-1"]), + FakeSaverExec(save_path="my_model.pth") + ]) + with pytest.raises(TypeError) as exc_info: + _ = pipeline.execute() + assert "received too many input arguments" in str(exc_info.value) + + pipeline = Pipeline([ + FakeGetterExec(data_uri='http://...'), + FakeTrainerExec(lr=1e-3, batch_size=32), + ]) + with pytest.raises(TypeError) as exc_info: + _ = pipeline.execute() + assert "received too few input arguments" in str(exc_info.value) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..5082f31b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,34 @@ +import os +import pytest + + +@pytest.fixture +def torch_env() -> str: + """ + If TORCH_ENV env variable is defined, it overrides the default + torch virtual environment name. Otherwise, fall back + to './.venv-pytorch'. + + Returns absolute path to torch virtual environment. + """ + if os.environ.get('TORCH_ENV') is None: + env_p = './.venv-pytorch' + else: + env_p = str(os.environ.get('TORCH_ENV')) + return os.path.abspath(env_p) + + +@pytest.fixture +def tf_env() -> str: + """ + If TF_ENV env variable is defined, it overrides the default + torch virtual environment name. Otherwise, fall back + to './.venv-tf'. + + Returns absolute path to torch virtual environment. + """ + if os.environ.get('TF_ENV') is None: + env_p = './.venv-tf' + else: + env_p = str(os.environ.get('TF_ENV')) + return os.path.abspath(env_p) diff --git a/tests/run_on_jsc.sh b/tests/run_on_jsc.sh new file mode 100644 index 00000000..48006820 --- /dev/null +++ b/tests/run_on_jsc.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Run tests on JSC environment +# Set TORCH_ENV and TF_ENV variables below to use different +# virtual environment names. + +ml --force purge +ml Stages/2024 GCC OpenMPI CUDA/12 cuDNN MPI-settings/CUDA +ml Python CMake HDF5 PnetCDF libaio + +export TORCH_ENV="envAI_hdfml" +export TF_ENV="envAItf_hdfml" + +if [ ! -d "$TORCH_ENV" ]; then + echo "$TORCH_ENV not found!" + exit 1 +fi +if [ ! -d "$TF_ENV" ]; then + echo "$TF_ENV not found!" + exit 1 +fi + +# Avoid downloading datasets from Gdrive +export CERN_DATASET="/p/project/intertwin/smalldata/3dgan-sample" +export CMCCC_DATASET="/p/project/intertwin/smalldata/cmcc" +export MNIST_DATASET="/p/project/intertwin/smalldata/mnist" + +$TORCH_ENV/bin/pytest -v tests/ -m "not slurm" \ No newline at end of file diff --git a/tests/test_loggers.py b/tests/test_loggers.py new file mode 100644 index 00000000..7f13ab45 --- /dev/null +++ b/tests/test_loggers.py @@ -0,0 +1,165 @@ +import pytest +import shutil +import numpy as np +from unittest.mock import patch, MagicMock + +from itwinai.loggers import ( + ConsoleLogger, + MLFlowLogger, + WanDBLogger, + TensorBoardLogger, + LoggersCollection +) + + +@pytest.fixture(scope="module") +def console_logger(): + yield ConsoleLogger(savedir='/tmp/console/test_mllogs', log_freq=1) + shutil.rmtree('/tmp/console/test_mllogs', ignore_errors=True) + + +@pytest.fixture(scope="module") +def mlflow_logger(): + yield MLFlowLogger( + savedir='/tmp/mlflow/test_mllogs', + experiment_name='test_experiment', + tracking_uri='file:///tmp/mlruns' + ) + shutil.rmtree('/tmp/mlflow/test_mllogs', ignore_errors=True) + shutil.rmtree('/tmp/mlruns', ignore_errors=True) + + +@pytest.fixture(scope="module") +def wandb_logger(): + yield WanDBLogger(savedir='/tmp/wandb/test_mllogs', + project_name='test_project') + shutil.rmtree('/tmp/wandb/test_mllogs', ignore_errors=True) + + +@pytest.fixture(scope="module") +def tensorboard_logger_tf(): + yield TensorBoardLogger(savedir='/tmp/tf_tb/test_mllogs', + framework='tensorflow') + shutil.rmtree('/tmp/tf_tb/test_mllogs', ignore_errors=True) + + +@pytest.fixture(scope="module") +def tensorboard_logger_torch(): + yield TensorBoardLogger(savedir='/tmp/torch_tb/test_mllogs', + framework='pytorch') + shutil.rmtree('/tmp/torch_tb/test_mllogs', ignore_errors=True) + + +@pytest.fixture +def loggers_collection(console_logger, mlflow_logger, wandb_logger): + return LoggersCollection([console_logger, mlflow_logger, wandb_logger]) + + +def test_console_logger_log(console_logger): + console_logger.create_logger_context() + with patch('builtins.print') as mocked_print: + console_logger.log('test_value', 'test_identifier') + mocked_print.assert_called_with( + 'ConsoleLogger: test_identifier = test_value') + console_logger.destroy_logger_context() + + +def test_mlflow_logger_log(mlflow_logger): + with patch('mlflow.log_metric') as mock_log_metric: + mlflow_logger.create_logger_context() + mlflow_logger.log(0.5, 'test_metric', kind='metric', step=1) + mock_log_metric.assert_called_once_with( + key='test_metric', value=0.5, step=1) + mlflow_logger.destroy_logger_context() + + +def test_wandb_logger_log(wandb_logger): + with patch('wandb.init') as mock_init, patch('wandb.log') as mock_log: + mock_init.return_value = MagicMock() + wandb_logger.create_logger_context() + wandb_logger.log(0.5, 'test_metric', kind='metric', step=1) + mock_log.assert_called_once_with( + {'test_metric': 0.5}, step=1, commit=True) + + +def test_tensorboard_logger_log_tf(tensorboard_logger_tf): + import tensorflow as tf + + with patch('tensorflow.summary.scalar') as mock_scalar, \ + patch('tensorflow.summary.image') as mock_image, \ + patch('tensorflow.summary.text') as mock_text: + tensorboard_logger_tf.create_logger_context() + + # Log a scalar + tensorboard_logger_tf.log(0.5, 'test_scalar', kind='metric', step=1) + mock_scalar.assert_called_once_with('test_scalar', 0.5, step=1) + + # Log an image + image = tf.zeros([10, 10, 3]) + tensorboard_logger_tf.log(image, 'test_image', kind='image', step=1) + mock_image.assert_called_once() + assert np.allclose(mock_image.call_args[0][1].numpy(), image.numpy()) + + # Log text + tensorboard_logger_tf.log( + 'test text', 'test_text', kind='text', step=1) + mock_text.assert_called_once_with('test_text', 'test text', step=1) + + tensorboard_logger_tf.destroy_logger_context() + + +def test_tensorboard_logger_log_torch(tensorboard_logger_torch): + import torch + + step = 1 + with patch('torch.utils.tensorboard.SummaryWriter.add_scalar') \ + as mock_scalar, \ + patch('torch.utils.tensorboard.SummaryWriter.add_image') \ + as mock_image, \ + patch('torch.utils.tensorboard.SummaryWriter.add_text') \ + as mock_text: + tensorboard_logger_torch.create_logger_context() + + # Log a scalar + tensorboard_logger_torch.log( + 0.5, 'test_scalar', kind='metric', step=step) + mock_scalar.assert_called_once_with( + 'test_scalar', 0.5, global_step=step) + + # Log an image + image = torch.zeros([3, 10, 10]) + tensorboard_logger_torch.log( + image, 'test_image', kind='image', step=step) + mock_image.assert_called_once_with( + 'test_image', image, global_step=step) + + # Log text + tensorboard_logger_torch.log( + 'test text', 'test_text', kind='text', step=step) + mock_text.assert_called_once_with( + 'test_text', 'test text', global_step=step) + + tensorboard_logger_torch.destroy_logger_context() + + +def test_loggers_collection_log(loggers_collection): + with patch('builtins.print') as mocked_print, \ + patch('mlflow.log_metric') as mock_log_metric, \ + patch('wandb.init') as mock_wandb_init, \ + patch('wandb.log') as mock_wandb_log: # , \ + # patch('shutil.copyfile') as mock_shutil_copyfile, \ + # patch('pickle.dump') as mock_pickle_dump, \ + # patch('torch.save') as mock_torch_save: + + mock_wandb_init.return_value = MagicMock() + + loggers_collection.create_logger_context() + loggers_collection.log(0.5, 'test_metric', kind='metric', step=1) + + mocked_print.assert_called_with('ConsoleLogger: test_metric = 0.5') + mock_log_metric.assert_called_once_with( + key='test_metric', value=0.5, step=1) + mock_wandb_log.assert_called_once_with( + {'test_metric': 0.5}, step=1, commit=True) + + loggers_collection.destroy_logger_context() diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 00000000..5fb7b936 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,99 @@ +""" +Tests for itwinai.utils module. +""" + +from itwinai.utils import flatten_dict, SignatureInspector + + +def test_flatten_dict(): + """ + Test flatten dict function. + """ + dict1 = dict(a=1, b=dict(b1=2, b2=3)) + + flattened = flatten_dict(dict1) + assert flattened.get("a") == 1 + assert flattened.get("b.b1") == 2 + assert flattened.get("b.b2") == 3 + assert len(flattened) == 3 + + +def test_signature_inspector(): + """Test SignatureInspector class.""" + def f(): + ... + + inspector = SignatureInspector(f) + assert not inspector.has_varargs + assert not inspector.has_kwargs + assert inspector.required_params == () + assert inspector.min_params_num == 0 + assert inspector.max_params_num == 0 + + def f(*args): + ... + + inspector = SignatureInspector(f) + assert inspector.has_varargs + assert not inspector.has_kwargs + assert inspector.required_params == () + assert inspector.min_params_num == 0 + assert inspector.max_params_num == SignatureInspector.INFTY + + def f(foo, *args): + ... + + inspector = SignatureInspector(f) + assert inspector.has_varargs + assert not inspector.has_kwargs + assert inspector.required_params == ("foo",) + assert inspector.min_params_num == 1 + assert inspector.max_params_num == SignatureInspector.INFTY + + def f(foo, bar=123): + ... + + inspector = SignatureInspector(f) + assert not inspector.has_varargs + assert not inspector.has_kwargs + assert inspector.required_params == ("foo",) + assert inspector.min_params_num == 1 + assert inspector.max_params_num == 2 + + def f(foo, *args, bar=123): + ... + + inspector = SignatureInspector(f) + assert inspector.has_varargs + assert not inspector.has_kwargs + assert inspector.required_params == ("foo",) + assert inspector.min_params_num == 1 + assert inspector.max_params_num == SignatureInspector.INFTY + + def f(*args, **kwargs): + ... + + inspector = SignatureInspector(f) + assert inspector.has_varargs + assert inspector.has_kwargs + assert inspector.required_params == () + assert inspector.min_params_num == 0 + assert inspector.max_params_num == SignatureInspector.INFTY + + def f(foo, /, bar, *arg, **kwargs): + ... + inspector = SignatureInspector(f) + assert inspector.has_varargs + assert inspector.has_kwargs + assert inspector.required_params == ("foo", "bar") + assert inspector.min_params_num == 2 + assert inspector.max_params_num == SignatureInspector.INFTY + + def f(foo, /, bar, *, hello, **kwargs): + ... + inspector = SignatureInspector(f) + assert not inspector.has_varargs + assert inspector.has_kwargs + assert inspector.required_params == ("foo", "bar", "hello") + assert inspector.min_params_num == 3 + assert inspector.max_params_num == SignatureInspector.INFTY diff --git a/tests/torch/distribtued_decorator.py b/tests/torch/distribtued_decorator.py new file mode 100644 index 00000000..fd086154 --- /dev/null +++ b/tests/torch/distribtued_decorator.py @@ -0,0 +1,111 @@ +""" +Test @distributed function decorator. To run this script, use the following +command: + +>>> torchrun --nnodes=1 --nproc_per_node=2 --rdzv_id=100 --rdzv_backend=c10d \ + --rdzv_endpoint=localhost:29400 distribtued_decorator.py + +""" + +import torch +from torch import nn +import torch.nn.functional as F +from torch.utils.data import DataLoader +from torchvision import transforms, datasets +import torch.optim as optim +from torch.optim.lr_scheduler import StepLR + +from itwinai.torch.trainer import distributed + + +class Net(nn.Module): + + def __init__(self): + super(Net, self).__init__() + self.conv1 = nn.Conv2d(1, 10, kernel_size=5) + self.conv2 = nn.Conv2d(10, 20, kernel_size=5) + self.conv2_drop = nn.Dropout2d() + self.fc1 = nn.Linear(320, 50) + self.fc2 = nn.Linear(50, 10) + + def forward(self, x): + x = F.relu(F.max_pool2d(self.conv1(x), 2)) + x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2)) + x = x.view(-1, 320) + x = F.relu(self.fc1(x)) + x = F.dropout(x, training=self.training) + x = self.fc2(x) + return F.log_softmax(x, dim=0) + + +def train(model, device, train_loader, optimizer, epoch): + model.train() + for batch_idx, (data, target) in enumerate(train_loader): + data, target = data.to(device), target.to(device) + optimizer.zero_grad() + output = model(data) + loss = F.nll_loss(output, target) + loss.backward() + optimizer.step() + if batch_idx % 100 == 0: + print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( + epoch, batch_idx * len(data), len(train_loader.dataset), + 100. * batch_idx / len(train_loader), loss.item())) + + +def test(model, device, test_loader): + model.eval() + test_loss = 0 + correct = 0 + with torch.no_grad(): + for data, target in test_loader: + data, target = data.to(device), target.to(device) + output = model(data) + # sum up batch loss + test_loss += F.nll_loss(output, target, reduction='sum').item() + # get the index of the max log-probability + pred = output.argmax(dim=1, keepdim=True) + correct += pred.eq(target.view_as(pred)).sum().item() + + test_loss /= len(test_loader.dataset) + + print( + '\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format( + test_loss, correct, len(test_loader.dataset), + 100. * correct / len(test_loader.dataset))) + + +@distributed +def train_func( + model, train_dataloader, validation_dataloader, device, + optimizer, scheduler, epochs=10 +): + for epoch in range(1, epochs + 1): + train(model, device, train_dataloader, optimizer, epoch) + test(model, device, validation_dataloader) + scheduler.step() + + +if __name__ == '__main__': + + train_set = datasets.MNIST( + '.tmp/', train=True, download=True, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)) + ])) + val_set = datasets.MNIST( + '.tmp/', train=False, download=True, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)) + ])) + model = Net() + train_dataloader = DataLoader(train_set, batch_size=32, pin_memory=True) + validation_dataloader = DataLoader(val_set, batch_size=32, pin_memory=True) + optimizer = optim.Adadelta(model.parameters(), lr=1e-3) + scheduler = StepLR(optimizer, step_size=1, gamma=0.9) + + # Train distributed + train_func(model, train_dataloader, validation_dataloader, 'cuda', + optimizer, scheduler=scheduler, epochs=1) diff --git a/tests/torch/test_config.py b/tests/torch/test_config.py new file mode 100644 index 00000000..968007a0 --- /dev/null +++ b/tests/torch/test_config.py @@ -0,0 +1,26 @@ +import pytest +from itwinai.torch.config import TrainingConfiguration +from pydantic import ValidationError + + +def test_values_parsing(): + """Check dynamic override and creation of new entries.""" + cfg = TrainingConfiguration( + batch_size='11', + param_abc='11', + param_xyz=1.1 + ) + assert cfg.batch_size == 11 + assert cfg.param_abc == '11' + assert cfg.param_xyz == 1.1 + assert isinstance(cfg.pin_memory, bool) + + # Check dict-like getitem + assert cfg['batch_size'] == 11 + + +def test_illegal_override(): + """Test that illegal type override fails.""" + with pytest.raises(ValidationError) as exc_info: + TrainingConfiguration(batch_size='hello') + assert "batch_size" in str(exc_info.value) diff --git a/tests/torch/test_distribtued_training.py b/tests/torch/test_distribtued_training.py new file mode 100644 index 00000000..f09f2720 --- /dev/null +++ b/tests/torch/test_distribtued_training.py @@ -0,0 +1,25 @@ +"""Test distributed training strategies.""" + +import subprocess +import pytest + + +@pytest.mark.slurm +def test_distributed_decorator(torch_env): + """Test function decorator. Needs torchrun cmd.""" + cmd = (f"{torch_env}/bin/torchrun " + " --nnodes=1 --nproc_per_node=2 --rdzv_id=100 " + "--rdzv_backend=c10d --rdzv_endpoint=localhost:29400 " + "tests/torch/distribtued_decorator.py") + subprocess.run(cmd.split(), check=True) + + +@pytest.mark.skip(reason="TorchTrainer not implemented yet") +@pytest.mark.slurm +def test_distributed_trainer(torch_env): + """Test vanilla torch distributed trainer. Needs torchrun cmd.""" + cmd = (f"{torch_env}/bin/torchrun " + "--nnodes=1 --nproc_per_node=2 --rdzv_id=100 " + "--rdzv_backend=c10d --rdzv_endpoint=localhost:29400 " + "tests/torch/torch_dist_trainer.py") + subprocess.run(cmd.split(), check=True) diff --git a/tests/torch/torch_dist_trainer.py b/tests/torch/torch_dist_trainer.py new file mode 100644 index 00000000..63871f4a --- /dev/null +++ b/tests/torch/torch_dist_trainer.py @@ -0,0 +1,62 @@ +""" +Test Trainer class. To run this script, use the following command: + +>>> torchrun --nnodes=1 --nproc_per_node=2 --rdzv_id=100 --rdzv_backend=c10d \ + --rdzv_endpoint=localhost:29400 torch_dist_trainer.py + +""" + +from torch import nn +import torch.nn.functional as F +from torch.utils.data import DataLoader +from torchvision import transforms, datasets + +from itwinai.torch.trainer import TorchTrainer + + +class Net(nn.Module): + + def __init__(self): + super(Net, self).__init__() + self.conv1 = nn.Conv2d(1, 10, kernel_size=5) + self.conv2 = nn.Conv2d(10, 20, kernel_size=5) + self.conv2_drop = nn.Dropout2d() + self.fc1 = nn.Linear(320, 50) + self.fc2 = nn.Linear(50, 10) + + def forward(self, x): + x = F.relu(F.max_pool2d(self.conv1(x), 2)) + x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2)) + x = x.view(-1, 320) + x = F.relu(self.fc1(x)) + x = F.dropout(x, training=self.training) + x = self.fc2(x) + return F.log_softmax(x, dim=0) + + +if __name__ == '__main__': + train_set = datasets.MNIST( + '.tmp/', train=True, download=True, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)) + ])) + val_set = datasets.MNIST( + '.tmp/', train=False, download=True, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)) + ])) + train_dataloder = DataLoader(train_set, batch_size=32, pin_memory=True) + validation_dataloader = DataLoader(val_set, batch_size=32, pin_memory=True) + trainer = TorchTrainer( + model=Net(), + loss=nn.NLLLoss(), + optimizer_class='torch.optim.SGD', + optimizer_kwargs=dict(lr=1e-3), + epochs=2, + strategy='ddp', + backend='nccl', + checkpoint_every=1 + ) + trainer.execute(args=[train_dataloder, validation_dataloader]) diff --git a/tests/use-cases/conftest.py b/tests/use-cases/conftest.py new file mode 100644 index 00000000..6249e1de --- /dev/null +++ b/tests/use-cases/conftest.py @@ -0,0 +1,54 @@ +import os +from typing import Callable +import pytest +import subprocess +import random +import string + + +FNAMES = [ + 'pipeline.yaml', + 'startscript', +] + + +def rnd_string(len: int = 26): + return ''.join(random.sample(string.ascii_lowercase, len)) + + +@pytest.fixture +def tmp_test_dir(): + root = '/tmp/pytest' + os.makedirs(root, exist_ok=True) + test_dir = os.path.join(root, rnd_string()) + while os.path.exists(test_dir): + test_dir = os.path.join(root, rnd_string()) + os.makedirs(test_dir, exist_ok=True) + + yield test_dir + + # Optional: remove dir here... + + +@pytest.fixture +def check_folder_structure() -> Callable: + """ + Verify that the use case folder complies with some predefined + structure. + """ + def _check_structure(root: str): + for fname in FNAMES: + fpath = os.path.join(root, fname) + assert os.path.isfile(fpath), f"'{fname}' is missing in '{fpath}'" + return _check_structure + + +@pytest.fixture +def install_requirements() -> Callable: + """Install requirements.txt, if present in root folder.""" + def _install_reqs(root: str, env_prefix: str): + req_path = os.path.join(root, 'requirements.txt') + if os.path.isfile(req_path): + cmd = f"{env_prefix}/bin/pip install -r {req_path}" + subprocess.run(cmd.split(), check=True) + return _install_reqs diff --git a/tests/use-cases/test_3dgan.py b/tests/use-cases/test_3dgan.py new file mode 100644 index 00000000..29b5ab0e --- /dev/null +++ b/tests/use-cases/test_3dgan.py @@ -0,0 +1,86 @@ +""" +Tests for CERN use case (3DGAN). +""" +import pytest +import subprocess +import os + +CERN_PATH = "use-cases/3dgan" +CKPT_NAME = "3dgan-inference.pth" +DEFAULT_DATASET_PATH = 'exp_data' + + +@pytest.mark.skip("deprecated") +def test_structure_3dgan(check_folder_structure): + """Test 3DGAN folder structure.""" + check_folder_structure(CERN_PATH) + + +@pytest.mark.functional +def test_3dgan_train(torch_env, tmp_test_dir, install_requirements): + """ + Test 3DGAN torch lightning trainer by running it end-to-end. + + If CERN_DATASET env variable is defined, it is used to + override the default dataset download location: useful + when it contains a local copy of the dataset, preventing + downloading it again. + """ + install_requirements(CERN_PATH, torch_env) + if os.environ.get('CERN_DATASET'): + dataset_path = os.environ.get('CERN_DATASET') + else: + dataset_path = DEFAULT_DATASET_PATH + conf = os.path.join(os.path.abspath(CERN_PATH), 'config.yaml') + cmd = (f"{torch_env}/bin/itwinai exec-pipeline " + f"--config {conf} --pipe-key training_pipeline " + f'-o dataset_location={dataset_path} ' + '-o hw_accelerators=auto ' + '-o distributed_strategy=auto ' + '-o mlflow_tracking_uri=ml_logs/mlflow' + ) + subprocess.run(cmd.split(), check=True, cwd=tmp_test_dir) + + +@pytest.mark.functional +def test_3dgan_inference( + torch_env, + tmp_test_dir, + install_requirements, + # fake_model_checkpoint +): + """ + Test 3DGAN torch lightning trainer by running it end-to-end. + + If CERN_DATASET env variable is defined, it is used to + override the default dataset download location: useful + when it contains a local copy of the dataset, preventing + downloading it again. + """ + install_requirements(CERN_PATH, torch_env) + + # Create fake inference dataset and checkpoint + exec = os.path.join(os.path.abspath(CERN_PATH), + 'create_inference_sample.py') + cmd = (f"{torch_env}/bin/python {exec} " + f"--root {tmp_test_dir} " + f"--ckpt-name {CKPT_NAME}") + subprocess.run(cmd.split(), check=True, cwd=tmp_test_dir) + + # Test inference + if os.environ.get('CERN_DATASET'): + dataset_path = os.environ.get('CERN_DATASET') + else: + dataset_path = DEFAULT_DATASET_PATH + conf = os.path.join(os.path.abspath(CERN_PATH), 'config.yaml') + cmd = ( + f'{torch_env}/bin/itwinai exec-pipeline ' + f'--config {conf} --pipe-key inference_pipeline ' + f'-o dataset_location={dataset_path} ' + f'-o inference_model_uri={CKPT_NAME} ' + '-o hw_accelerators=auto ' + '-o distributed_strategy=auto ' + '-o logs_dir=ml_logs/mlflow_logs ' + '-o inference_results_location=3dgan-generated-data ' + ) + subprocess.run(cmd.split(), check=True, cwd=tmp_test_dir) diff --git a/tests/use-cases/test_cyclones.py b/tests/use-cases/test_cyclones.py new file mode 100644 index 00000000..8fbe376f --- /dev/null +++ b/tests/use-cases/test_cyclones.py @@ -0,0 +1,42 @@ +""" +Tests for MNIST use case. + +Intended to be integration tests, to make sure that updates in the code base +do not break use cases' workflows. +""" + +import pytest +import subprocess +import os + +CYCLONES_PATH = "use-cases/cyclones" + + +@pytest.mark.skip("deprecated") +def test_structure_cyclones(check_folder_structure): + """Test cyclones folder structure.""" + check_folder_structure(CYCLONES_PATH) + + +@pytest.mark.functional +@pytest.mark.memory_heavy +def test_cyclones_train_tf(tf_env, tmp_test_dir, install_requirements): + """ + Test Cyclones tensorflow trainer by running it end-to-end. + + If CMCCC_DATASET env variable is defined, it is used to + override the default dataset download location: useful + when it contains a local copy of the dataset, preventing + downloading it again. + """ + # TODO: create a small sample dataset for tests only + install_requirements(CYCLONES_PATH, tf_env) + if os.environ.get('CMCCC_DATASET'): + dataset_path = os.environ.get('CMCCC_DATASET') + else: + dataset_path = './data/tmp_data' + pipe = os.path.join(os.path.abspath(CYCLONES_PATH), 'pipeline.yaml') + train = os.path.join(os.path.abspath(CYCLONES_PATH), 'train.py') + cmd = (f"{tf_env}/bin/python {train} " + f"-p {pipe} --data_path {dataset_path}") + subprocess.run(cmd.split(), check=True, cwd=tmp_test_dir) diff --git a/tests/use-cases/test_mnist.py b/tests/use-cases/test_mnist.py index 75661ec0..834c55c1 100644 --- a/tests/use-cases/test_mnist.py +++ b/tests/use-cases/test_mnist.py @@ -7,38 +7,111 @@ import pytest import subprocess +import os +# from itwinai.cli import exec_pipeline -# TODO: add tests for use case folder format: -# - structure -# - naming convention -# - file exist +TORCH_PATH = "use-cases/mnist/torch" +LIGHTNING_PATH = "use-cases/mnist/torch-lightning" +TF_PATH = "use-cases/mnist/tensorflow" +DEFAULT_MNIST_DATASET = '.tmp' -@pytest.mark.integration -def test_training_workflow(): +@pytest.mark.skip(reason="structure changed") +def test_structure_mnist_torch(check_folder_structure): + """Test MNIST folder structure for torch native trainer.""" + check_folder_structure(TORCH_PATH) + + +@pytest.mark.skip(reason="structure changed") +def test_structure_mnist_lightning(check_folder_structure): + """Test MNIST folder structure for torch lightning trainer.""" + check_folder_structure(LIGHTNING_PATH) + + +@pytest.mark.skip(reason="structure changed") +def test_structure_mnist_tf(check_folder_structure): + """Test MNIST folder structure for tensorflow trainer.""" + check_folder_structure(TF_PATH) + + +@pytest.mark.functional +def test_mnist_train_torch(torch_env, tmp_test_dir, install_requirements): + """ + Test MNIST torch native trainer by running it end-to-end. + + If MNIST_DATASET env variable is defined, it is used to + override the default dataset download location: useful + when it contains a local copy of the dataset, preventing + downloading it again. + """ + install_requirements(TORCH_PATH, torch_env) + + if os.environ.get('MNIST_DATASET'): + dataset_path = os.environ.get('MNIST_DATASET') + else: + dataset_path = DEFAULT_MNIST_DATASET + conf = os.path.join(os.path.abspath(TORCH_PATH), 'config.yaml') + cmd = (f"{torch_env}/bin/itwinai exec-pipeline " + f"--config {conf} --pipe-key training_pipeline " + f"-o dataset_root={dataset_path}") + subprocess.run(cmd.split(), check=True, cwd=tmp_test_dir) + + +@pytest.mark.functional +def test_mnist_inference_torch(torch_env, tmp_test_dir, install_requirements): """ - Test MNIST training workflow by running it end-to-end. + Test MNIST torch native inference by running it end-to-end. """ - cmd = ( - "micromamba run -p ./.venv python run-workflow.py " - "-f ./use-cases/mnist/training-workflow.yml" - ) - subprocess.run(cmd.split(), check=True) + install_requirements(TORCH_PATH, torch_env) - # CWL - subprocess.run(cmd.split() + ['--cwl'], check=True) + # Create fake inference dataset and checkpoint + exec = os.path.join(os.path.abspath(TORCH_PATH), + 'create_inference_sample.py') + cmd = (f"{torch_env}/bin/python {exec} " + f"--root {tmp_test_dir}") + subprocess.run(cmd.split(), check=True, cwd=tmp_test_dir) + # Test inference + conf = os.path.join(os.path.abspath(TORCH_PATH), 'config.yaml') + cmd = (f"{torch_env}/bin/itwinai exec-pipeline " + f"--config {conf} --pipe-key inference_pipeline") + subprocess.run(cmd.split(), check=True, cwd=tmp_test_dir) -@pytest.mark.integration -def test_inference_workflow(): + +@pytest.mark.functional +def test_mnist_train_torch_lightning( + torch_env, + tmp_test_dir, + install_requirements +): """ - Test MNIST inference workflow by running it end-to-end. + Test MNIST torch lightning trainer by running it end-to-end. + + If MNIST_DATASET env variable is defined, it is used to + override the default dataset download location: useful + when it contains a local copy of the dataset, preventing + downloading it again. """ - cmd = ( - "micromamba run -p ./.venv python run-workflow.py " - "-f ./use-cases/mnist/inference-workflow.yml" - ) - subprocess.run(cmd.split(), check=True) + install_requirements(LIGHTNING_PATH, torch_env) - # CWL - subprocess.run(cmd.split() + ['--cwl'], check=True) + if os.environ.get('MNIST_DATASET'): + dataset_path = os.environ.get('MNIST_DATASET') + else: + dataset_path = DEFAULT_MNIST_DATASET + conf = os.path.join(os.path.abspath(LIGHTNING_PATH), 'config.yaml') + cmd = (f"{torch_env}/bin/itwinai exec-pipeline " + f"--config {conf} --pipe-key training_pipeline " + f"-o dataset_root={dataset_path}") + subprocess.run(cmd.split(), check=True, cwd=tmp_test_dir) + + +@pytest.mark.functional +def test_mnist_train_tf(tf_env, tmp_test_dir, install_requirements): + """ + Test MNIST tensorflow trainer by running it end-to-end. + """ + install_requirements(TF_PATH, tf_env) + conf = os.path.join(os.path.abspath(TF_PATH), 'pipeline.yaml') + cmd = (f"{tf_env}/bin/itwinai exec-pipeline " + f"--config {conf} --pipe-key pipeline") + subprocess.run(cmd.split(), check=True, cwd=tmp_test_dir) diff --git a/tutorials/distributed-ml/tf-scaling-test-jube/README.md b/tutorials/distributed-ml/tf-scaling-test-jube/README.md new file mode 100644 index 00000000..bc2cab1c --- /dev/null +++ b/tutorials/distributed-ml/tf-scaling-test-jube/README.md @@ -0,0 +1,44 @@ +# Benchmarking tutorial using JUBE + +Benchmarking of itwinai can also be performed with the JUBE Benchmarking Environment from JSC. +The JUBE benchmarking tool is already setup in the environment files provided under `env-files`. + +## Source the environment + +Find the location of your environment file along with the module load commands, such as: + +```bash +ml Stages/2024 GCC/12.3.0 OpenMPI CUDA/12 MPI-settings/CUDA Python HDF5 PnetCDF libaio mpi4py CMake cuDNN/8.9.5.29-CUDA-12 +source envAI_hdfml/bin/activate +``` + +## Run benchmark + +The benchmarks are defined in the `general_jobsys.xml` file. +One can specify the configurations in terms of parameters such as the number of nodes. +The benchmark can be simply launched with the command: + +```bash +jube run general_jobsys.xml +``` + +## Monitor status of benchmark run + +The status of the run can be monitored with: + +```bash +jube continue bench_run --id last +``` + +## Check results of the benchmark run + +The results can be viewed with: + +```bash +jube result -a bench_run --id last +``` + +This will create `result-csv.dat` file in the `results` folder. + +The scaling and efficiency plots can be generated with the `bench_plot.ipynb` file +which takes the `result-csv.dat` file as input. diff --git a/tutorials/distributed-ml/tf-scaling-test-jube/bench_plot.ipynb b/tutorials/distributed-ml/tf-scaling-test-jube/bench_plot.ipynb new file mode 100644 index 00000000..fda6cd13 --- /dev/null +++ b/tutorials/distributed-ml/tf-scaling-test-jube/bench_plot.ipynb @@ -0,0 +1,170 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plot benchmark results of itwinai" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import os, pandas as pd, matplotlib.pyplot as plt, numpy as np\n", + "%matplotlib inline\n", + "pd.options.display.max_columns = None" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "plt.rcParams['figure.figsize'] = [12, 6]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "df = pd.read_csv('result-csv.dat',header=0)\n", + "df.rename(columns=lambda x: x.split('[')[0], inplace=True)\n", + "\n", + "# gpus\n", + "df[\"NGPUs\"] = df[\"Nnodes\"]*4\n", + "\n", + "# speedup\n", + "df[\"Speedup - ideal\"] = df[\"Nnodes\"].astype(float)\n", + "df[\"Speedup\"] = df[\"Naet\"].iloc[0] / df[\"Naet\"]\n", + "\n", + "# efficiency\n", + "df[\"Threadscaled Sim. Time / s\"] = df[\"Naet\"] * df[\"Nnodes\"] * df[\"Nworkers\"]\n", + "df[\"Efficiency\"] = df[\"Threadscaled Sim. Time / s\"].iloc[0] / df[\"Threadscaled Sim. Time / s\"]\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Overview" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "ax = df.pivot_table(index=[\"NGPUs\"], columns=[\"Nworkers\"], values=\"Naet\").plot(kind=\"bar\", title=\"Runtime behaviour\");\n", + "ax.set_ylabel(\"Epoch Time / s\");\n", + "ax_abs = ax\n", + "for p in ax.patches:\n", + " ax.annotate(\"{:.2f} s\".format(p.get_height()), (p.get_x() + p.get_width()/1.33, p.get_height() * 1.01), \\\n", + " color=\"dimgray\", horizontalalignment=\"center\", verticalalignment=\"bottom\", rotation=\"vertical\")\n", + "pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Scaling Behaviour" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "ax = df.pivot_table(index=[\"NGPUs\"], columns=[\"Nworkers\"], values=\"Speedup\").plot(style=\"*-\", \\\n", + " loglog=False, title=\"Scaling behaviour\", color=\"r\", legend=False);\n", + "ax.plot(df[\"NGPUs\"].values,df[\"Speedup - ideal\"].values,ls='dashed',lw=1.0,c='k',label=\"ideal\")\n", + "\n", + "ax.legend(ncol=1, title=\"(Nworkers)\")\n", + "ax.set_xticks(df[\"NGPUs\"].values)\n", + "ax.set_yticks(df[\"Speedup - ideal\"].values)\n", + "ax.set_ylabel(r'Speedup')\n", + "ax.set_xlim((0,np.amax(df[\"NGPUs\"].values+1)))\n", + "ax.set_ylim((0,np.amax(df[\"Speedup - ideal\"].values+1)))\n", + "\n", + "pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Runtime Efficiencies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "ax = df.pivot_table(index=[\"NGPUs\"], columns=[\"Nworkers\"], values=\"Efficiency\").plot(kind=\"bar\", \\\n", + " legend=False, title=\"Runtime efficiency\")\n", + "ax.legend(ncol=1, title=\"(Ntasks, Ncells)\",loc=4)\n", + "ax.set_ylabel(\"Efficiency\");\n", + "for p, abs in zip(ax.patches, ax_abs.patches):\n", + " ax.annotate(\"{:.2f}\".format(p.get_height()), (p.get_x() + p.get_width()/1.33, p.get_height() * 1.01), \\\n", + " color=\"dimgray\", horizontalalignment=\"center\", verticalalignment=\"bottom\", rotation=\"vertical\")\n", + " ax.annotate(\"Abs: {:.1f} s\".format(abs.get_height()), (p.get_x() + p.get_width()/1.33, p.get_height() * 0.95), \\\n", + " color=\"white\", horizontalalignment=\"center\", verticalalignment=\"top\", rotation=\"vertical\")\n", + "ax.plot(df[\"NGPUs\"].values-8,df[\"Speedup - ideal\"].values*0+1,ls='dashed',lw=1.0,c='r',label=\"ideal\")\n", + "pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# EOF" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/tutorials/distributed-ml/tf-scaling-test-jube/general_jobsys.xml b/tutorials/distributed-ml/tf-scaling-test-jube/general_jobsys.xml new file mode 100644 index 00000000..6f981f57 --- /dev/null +++ b/tutorials/distributed-ml/tf-scaling-test-jube/general_jobsys.xml @@ -0,0 +1,140 @@ + + + + General benchmark script + + + + + 1,2,4,8 + + 8 + + train.py + + + + + if [ -f /etc/FZJ/systemname ]; then cat /etc/FZJ/systemname | tr -d "\n"; else uname -n | head -c 3; fi + sbatch + $iterNO + $iterNW + ready + jube_ddp.sh + + { "hdfml": 4, + }["${systemname}"] + + intertwin + + 04:00:00 + + { "hdfml": "batch", + }["${systemname}"] + + + 00:10:00 + + { "hdfml": "batch", + }["${systemname}"] + + + + + { + "hdfml": "ml ml Stages/2024 GCC/12.3.0 OpenMPI CUDA/12 MPI-settings/CUDA Python HDF5 PnetCDF libaio mpi4py CMake cuDNN/8.9.5.29-CUDA-12", + }["${systemname}"] + + source /p/project/intertwin/rakesh/repo_push/itwinai/envAItf_hdfml/bin/activate + { + "hdfml": "export CUDA_VISIBLE_DEVICES=0,1,2,3" + }["${systemname}"] + + + + + + $job_file + $script + + + + + + + + + + + + + + + + + + + + + paramset + executeset + envirset + files,sub_job + echo "nID: $jube_wp_id" + + $submit_cmd $job_file + + + + + + ${jube_wp_id} + ${nodes} + ${nnw} + \s*TIMER: total epoch time:\s+$jube_pat_wrd\s* + \s*TIMER: average epoch time:\s+$jube_pat_wrd\s* + ${avgEpochT} + + + + + pattern + + stdout + job.out + + + + + + analyse + + ID + Nnodes + Nworkers + calcTime + avgEpochT + Naet + memoryGPU +
    +
    + + + + analyse + + ID + Nnodes + Nworkers + calcTime + avgEpochT + Naet + memoryGPU +
    +
    + +
    +
    + + + diff --git a/tutorials/distributed-ml/tf-scaling-test-jube/jube_ddp.sh b/tutorials/distributed-ml/tf-scaling-test-jube/jube_ddp.sh new file mode 100644 index 00000000..adafae78 --- /dev/null +++ b/tutorials/distributed-ml/tf-scaling-test-jube/jube_ddp.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# general configuration of the job +#SBATCH --job-name=JUBE_DDP +#SBATCH --account=#ACC# +#SBATCH --mail-user= +#SBATCH --mail-type=ALL +#SBATCH --output=job.out +#SBATCH --error=job.err +#SBATCH --time=#TIMELIM# + +# configure node and process count on the CM +#SBATCH --partition=#QUEUE# +#SBATCH --nodes=#NODES# +#SBATCH --cpus-per-task=#NW# +#SBATCH --gpus-per-node=#NGPU# +#SBATCH --exclusive + +# gres options have to be disabled for deepv +#SBATCH --gres=gpu:4 + +set -x +unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY + +# set modules +ml --force purge +ml Stages/2024 GCC/12.3.0 OpenMPI CUDA/12 MPI-settings/CUDA Python HDF5 PnetCDF libaio mpi4py CMake cuDNN/8.9.5.29-CUDA-12 + +# set env +source /p/project/intertwin/rakesh/repo_push/itwinai/envAItf_hdfml/bin/activate + +# Using legacy (2.16) version of Keras +# Latest version with TF (2.16) installs Keras 3.3 +# which returns an error for multi-node execution +export TF_USE_LEGACY_KERAS=1 + +# sleep a sec +sleep 1 + +# job info +echo "DEBUG: TIME: $(date)" +echo "DEBUG: EXECUTE: $EXEC" +echo "DEBUG: SLURM_SUBMIT_DIR: $SLURM_SUBMIT_DIR" +echo "DEBUG: SLURM_JOB_ID: $SLURM_JOB_ID" +echo "DEBUG: SLURM_JOB_NODELIST: $SLURM_JOB_NODELIST" +echo "DEBUG: SLURM_NNODES: $SLURM_NNODES" +echo "DEBUG: SLURM_NTASKS: $SLURM_NTASKS" +echo "DEBUG: SLURM_TASKS_PER_NODE: $SLURM_TASKS_PER_NODE" +echo "DEBUG: SLURM_SUBMIT_HOST: $SLURM_SUBMIT_HOST" +echo "DEBUG: SLURMD_NODENAME: $SLURMD_NODENAME" +echo "DEBUG: CUDA_VISIBLE_DEVICES: $CUDA_VISIBLE_DEVICES" +echo "DEBUG: SLURM_NODELIST: $SLURM_NODELIST" +echo + +# set comm +export CUDA_VISIBLE_DEVICES="0,1,2,3" +export OMP_NUM_THREADS=1 +if [ "$SLURM_CPUS_PER_TASK" -gt 0 ] ; then + export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK +fi + +dataDir='/p/scratch/intertwin/datasets/imagenet/' + +COMMAND="train.py" + +EXEC="$COMMAND \ + --data_dir $dataDir" + +srun python -u $EXEC + + +#eof diff --git a/tutorials/distributed-ml/tf-scaling-test-jube/train.py b/tutorials/distributed-ml/tf-scaling-test-jube/train.py new file mode 100644 index 00000000..4bd4ff58 --- /dev/null +++ b/tutorials/distributed-ml/tf-scaling-test-jube/train.py @@ -0,0 +1,171 @@ +""" + Show how to use TensorFlow MultiWorkerMirroredStrategy on itwinai. + for an Imagenet dataset + with SLURM: + >>> sbatch tfmirrored_slurm.sh + + """ +import argparse +import sys +from timeit import default_timer as timer + +import tensorflow as tf +from tensorflow import keras +from tensorflow.keras.layers import Dense, GlobalAveragePooling2D +from tensorflow.keras.models import Model + +from itwinai.tensorflow.distributed import get_strategy + + +def parse_args(): + """ + Parse args + """ + parser = argparse.ArgumentParser(description='TensorFlow ImageNet') + + parser.add_argument( + "--strategy", "-s", type=str, + choices=['mirrored'], + default='mirrored' + ) + parser.add_argument( + "--data_dir", type=str, + default='./' + ) + parser.add_argument( + "--batch_size", type=int, + default=128 + ) + parser.add_argument( + "--epochs", type=int, + default=3 + ) + + args = parser.parse_args() + return args + + +def deserialization_fn(serialized_fn): + """Imagenet data processing + + Args: + serialized_example (Any): Input function + + Returns: + Any: Images and associated labels + """ + parsed_example = tf.io.parse_single_example( + serialized_fn, + features={ + 'image/encoded': tf.io.FixedLenFeature([], tf.string), + 'image/class/label': tf.io.FixedLenFeature([], tf.int64), + } + ) + image = tf.image.decode_jpeg(parsed_example['image/encoded'], channels=3) + image = tf.image.resize(image, (224, 224)) + label = tf.cast(parsed_example['image/class/label'], tf.int64) - 1 + return image, label + + +def tf_records_loader(files_path, shuffle=False): + """tf_records dataset reader + + Args: + files_path (String): Path to location of data + shuffle (bool, optional): If dataset should be shuffled. + Defaults to False. + + Returns: + tf.data.Dataset: Returns dataset to be trained + """ + datasets = tf.data.Dataset.from_tensor_slices(files_path) + datasets = datasets.shuffle(len(files_path)) if shuffle else datasets + datasets = datasets.flat_map(tf.data.TFRecordDataset) + datasets = datasets.map( + deserialization_fn, num_parallel_calls=tf.data.AUTOTUNE) + return datasets + + +def main(): + args = parse_args() + + input_shape = (224, 224, 3) + num_classes = 1000 + + if args.strategy == 'mirrored': + strategy = get_strategy()[0] + else: + raise NotImplementedError( + f"Strategy {args.strategy} is not recognized/implemented.") + + with strategy.scope(): + base_model = keras.applications.ResNet50( + weights=None, + input_shape=input_shape, + include_top=False, + ) + + x = base_model.output + x = GlobalAveragePooling2D()(x) + x = Dense(1024, activation='relu')(x) + predictions = Dense(num_classes, activation='softmax')(x) + + model = Model(inputs=base_model.input, outputs=predictions) + + model.compile(loss=keras.losses.sparse_categorical_crossentropy, + optimizer=keras.optimizers.Adam(), + metrics=['accuracy'] + ) + + # scale batch size with number of workers + batch_size = args.batch_size * get_strategy()[1] + + dir_imagenet = args.data_dir+'imagenet-1K-tfrecords' + train_shard_suffix = 'train-*-of-01024' + test_shard_suffix = 'validation-*-of-00128' + + train_set_path = sorted( + tf.io.gfile.glob(dir_imagenet + f'/{train_shard_suffix}') + ) + test_set_path = sorted( + tf.io.gfile.glob(dir_imagenet + f'/{test_shard_suffix}') + ) + + train_dataset = tf_records_loader(train_set_path, shuffle=True) + test_dataset = tf_records_loader(test_set_path) + + train_dataset = train_dataset.batch( + batch_size).prefetch(tf.data.experimental.AUTOTUNE) + test_dataset = test_dataset.batch( + batch_size).prefetch(tf.data.experimental.AUTOTUNE) + + # distribute datasets among mirrored replicas + dist_train = strategy.experimental_distribute_dataset( + train_dataset + ) + dist_test = strategy.experimental_distribute_dataset( + test_dataset + ) + + # TODO: add callbacks to evaluate per epoch time + et = timer() + + # trains the model + model.fit(dist_train, epochs=args.epochs, steps_per_epoch=2000, verbose=10) + + print('TIMER: total epoch time:', + timer() - et, ' s') + print('TIMER: average epoch time:', + (timer() - et) / (args.epochs), ' s') + + test_scores = model.evaluate(dist_test, steps=100, verbose=5) + + print('Test loss:', test_scores[0]) + print('Test accuracy:', test_scores[1]) + + +if __name__ == "__main__": + main() + sys.exit() + +# eof diff --git a/tutorials/distributed-ml/tf-tutorial-0-basics/README.md b/tutorials/distributed-ml/tf-tutorial-0-basics/README.md new file mode 100644 index 00000000..c2c49595 --- /dev/null +++ b/tutorials/distributed-ml/tf-tutorial-0-basics/README.md @@ -0,0 +1,20 @@ +# Tutorial: distributed strategies for Tensorflow + +In this tutorial we show how to use Tensorflow `MultiWorkerMirroredStrategy`. +Note that the environment is tested on the HDFML system at JSC. +For other systems, the module versions might need change accordingly. +Other strategies will be updated here. + +First, from the root of this repository, build the environment containing +Tensorflow. You can *try* with: + +```bash +# Creates a Python venv called envAItf_hdfml +make tf-gpu-jsc +``` + +If you want to distribute the code in `train.py`, run from terminal: + +```bash +sbatch tfmirrored_slurm.sh +``` diff --git a/tutorials/distributed-ml/tf-tutorial-0-basics/tfmirrored_slurm.sh b/tutorials/distributed-ml/tf-tutorial-0-basics/tfmirrored_slurm.sh new file mode 100644 index 00000000..e7721868 --- /dev/null +++ b/tutorials/distributed-ml/tf-tutorial-0-basics/tfmirrored_slurm.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# general configuration of the job +#SBATCH --job-name=TFTest +#SBATCH --account=intertwin +#SBATCH --mail-user= +#SBATCH --mail-type=ALL +#SBATCH --output=job.out +#SBATCH --error=job.err +#SBATCH --time=00:15:00 + +# configure node and process count on the CM +#SBATCH --partition=batch +#SBATCH --nodes=2 +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=32 +#SBATCH --gpus-per-node=4 +#SBATCH --exclusive + +# gres options have to be disabled for deepv +#SBATCH --gres=gpu:4 + +set -x +unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY + +# set modules +ml --force purge +ml Stages/2024 GCC/12.3.0 OpenMPI CUDA/12 MPI-settings/CUDA Python HDF5 PnetCDF libaio mpi4py CMake cuDNN/8.9.5.29-CUDA-12 + +# set env - change to location of your environment +source itwinai/envAItf_hdfml/bin/activate + +# Using legacy (2.16) version of Keras +# Latest version with TF (2.16) installs Keras 3.3 +# which returns an error for multi-node execution +export TF_USE_LEGACY_KERAS=1 + +# sleep a sec +sleep 1 + +# job info +echo "DEBUG: TIME: $(date)" +echo "DEBUG: EXECUTE: $EXEC" +echo "DEBUG: SLURM_SUBMIT_DIR: $SLURM_SUBMIT_DIR" +echo "DEBUG: SLURM_JOB_ID: $SLURM_JOB_ID" +echo "DEBUG: SLURM_JOB_NODELIST: $SLURM_JOB_NODELIST" +echo "DEBUG: SLURM_NNODES: $SLURM_NNODES" +echo "DEBUG: SLURM_NTASKS: $SLURM_NTASKS" +echo "DEBUG: SLURM_TASKS_PER_NODE: $SLURM_TASKS_PER_NODE" +echo "DEBUG: SLURM_SUBMIT_HOST: $SLURM_SUBMIT_HOST" +echo "DEBUG: SLURMD_NODENAME: $SLURMD_NODENAME" +echo "DEBUG: CUDA_VISIBLE_DEVICES: $CUDA_VISIBLE_DEVICES" +echo "DEBUG: SLURM_NODELIST: $SLURM_NODELIST" +echo + +# set comm +export CUDA_VISIBLE_DEVICES="0,1,2,3" +export OMP_NUM_THREADS=1 +if [ "$SLURM_CPUS_PER_TASK" -gt 0 ] ; then + export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK +fi + +COMMAND="train.py" + +EXEC="$COMMAND " + +srun python -u $EXEC diff --git a/tutorials/distributed-ml/tf-tutorial-0-basics/train.py b/tutorials/distributed-ml/tf-tutorial-0-basics/train.py new file mode 100644 index 00000000..4c0afd53 --- /dev/null +++ b/tutorials/distributed-ml/tf-tutorial-0-basics/train.py @@ -0,0 +1,109 @@ +""" +Show how to use TensorFlow MultiWorkerMirroredStrategy on itwinai. + +with SLURM: +>>> sbatch tfmirrored_slurm.sh + +""" +import os +from typing import Any +import argparse +import tensorflow as tf +from tensorflow import keras +from itwinai.tensorflow.distributed import get_strategy + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument( + "--strategy", "-s", type=str, + choices=['mirrored'], + default='mirrored' + ) + parser.add_argument( + "--batch_size", "-bs", type=int, + default=64 + ) + parser.add_argument( + "--shuffle_dataloader", + action=argparse.BooleanOptionalAction + ) + + args = parser.parse_args() + return args + + +def tf_rnd_dataset(): + """Dummy TF dataset.""" + (x_train, y_train), (x_test, y_test) = \ + tf.keras.datasets.mnist.load_data( + path=os.getcwd()+'/.keras/datasets/mnist.npz') + + train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train)) + train_dataset = train_dataset.batch(args.batch_size) + + test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test)) + test_dataset = test_dataset.batch(args.batch_size) + + return train_dataset, test_dataset + + +def trainer_entrypoint_fn( + foo: Any, args: argparse.Namespace, strategy +) -> int: + """Dummy training function, similar to custom code developed + by some use case. + """ + # dataset to be trained + train_dataset, test_dataset = tf_rnd_dataset(args) + + # distribute datasets among mirrored replicas + dist_train = strategy.experimental_distribute_dataset( + train_dataset + ) + dist_test = strategy.experimental_distribute_dataset( + test_dataset + ) + + # define and compile model within strategy.scope() + with strategy.scope(): + # Local model + model = tf.keras.models.Sequential([ + tf.keras.layers.Flatten(input_shape=(28, 28)), + tf.keras.layers.Dense(128, activation='relu'), + tf.keras.layers.Dense(10) + ]) + + model.compile(loss=keras.losses.SparseCategoricalCrossentropy + (from_logits=True), + optimizer=keras.optimizers.RMSprop(), + metrics=['accuracy'] + ) + + model.fit(dist_train, + epochs=5, + steps_per_epoch=2000) + + test_scores = model.evaluate(dist_test, verbose=0, steps=500) + + print('Test loss:', test_scores[0]) + print('Test accuracy:', test_scores[1]) + + return 123 + + +if __name__ == "__main__": + + args = parse_args() + + # Instantiate Strategy + if args.strategy == 'mirrored': + if (len(tf.config.list_physical_devices('GPU')) == 0): + raise RuntimeError('Resources unavailable') + strategy, num_replicas = get_strategy() + else: + raise NotImplementedError( + f"Strategy {args.strategy} is not recognized/implemented.") + + # Launch distributed training + trainer_entrypoint_fn("foobar", args, strategy) diff --git a/tutorials/distributed-ml/tf-tutorial-1-imagenet/README.md b/tutorials/distributed-ml/tf-tutorial-1-imagenet/README.md new file mode 100644 index 00000000..c2c49595 --- /dev/null +++ b/tutorials/distributed-ml/tf-tutorial-1-imagenet/README.md @@ -0,0 +1,20 @@ +# Tutorial: distributed strategies for Tensorflow + +In this tutorial we show how to use Tensorflow `MultiWorkerMirroredStrategy`. +Note that the environment is tested on the HDFML system at JSC. +For other systems, the module versions might need change accordingly. +Other strategies will be updated here. + +First, from the root of this repository, build the environment containing +Tensorflow. You can *try* with: + +```bash +# Creates a Python venv called envAItf_hdfml +make tf-gpu-jsc +``` + +If you want to distribute the code in `train.py`, run from terminal: + +```bash +sbatch tfmirrored_slurm.sh +``` diff --git a/tutorials/distributed-ml/tf-tutorial-1-imagenet/tfmirrored_slurm.sh b/tutorials/distributed-ml/tf-tutorial-1-imagenet/tfmirrored_slurm.sh new file mode 100644 index 00000000..7c886f14 --- /dev/null +++ b/tutorials/distributed-ml/tf-tutorial-1-imagenet/tfmirrored_slurm.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# general configuration of the job +#SBATCH --job-name=TFTest +#SBATCH --account=intertwin +#SBATCH --mail-user= +#SBATCH --mail-type=ALL +#SBATCH --output=job.out +#SBATCH --error=job.err +#SBATCH --time=01:00:00 + +# configure node and process count on the CM +#SBATCH --partition=batch +#SBATCH --nodes=4 +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=32 +#SBATCH --gpus-per-node=4 +#SBATCH --exclusive + +# gres options have to be disabled for deepv +#SBATCH --gres=gpu:4 + +set -x +unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY + +# set modules +ml --force purge +ml Stages/2024 GCC/12.3.0 OpenMPI CUDA/12 MPI-settings/CUDA Python HDF5 PnetCDF libaio mpi4py CMake cuDNN/8.9.5.29-CUDA-12 + +# set env +source /p/project/intertwin/rakesh/repo_push/itwinai/envAItf_hdfml/bin/activate + +# Using legacy (2.16) version of Keras +# Latest version with TF (2.16) installs Keras 3.3 +# which returns an error for multi-node execution +export TF_USE_LEGACY_KERAS=1 + +# sleep a sec +sleep 1 + +# job info +echo "DEBUG: TIME: $(date)" +echo "DEBUG: EXECUTE: $EXEC" +echo "DEBUG: SLURM_SUBMIT_DIR: $SLURM_SUBMIT_DIR" +echo "DEBUG: SLURM_JOB_ID: $SLURM_JOB_ID" +echo "DEBUG: SLURM_JOB_NODELIST: $SLURM_JOB_NODELIST" +echo "DEBUG: SLURM_NNODES: $SLURM_NNODES" +echo "DEBUG: SLURM_NTASKS: $SLURM_NTASKS" +echo "DEBUG: SLURM_TASKS_PER_NODE: $SLURM_TASKS_PER_NODE" +echo "DEBUG: SLURM_SUBMIT_HOST: $SLURM_SUBMIT_HOST" +echo "DEBUG: SLURMD_NODENAME: $SLURMD_NODENAME" +echo "DEBUG: CUDA_VISIBLE_DEVICES: $CUDA_VISIBLE_DEVICES" +echo "DEBUG: SLURM_NODELIST: $SLURM_NODELIST" +echo + +# set comm +export CUDA_VISIBLE_DEVICES="0,1,2,3" +export OMP_NUM_THREADS=1 +if [ "$SLURM_CPUS_PER_TASK" -gt 0 ] ; then + export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK +fi + +dataDir='/p/scratch/intertwin/datasets/imagenet/' + +COMMAND="train.py" + +EXEC="$COMMAND \ + --data_dir $dataDir" + +srun python -u $EXEC diff --git a/tutorials/distributed-ml/tf-tutorial-1-imagenet/train.py b/tutorials/distributed-ml/tf-tutorial-1-imagenet/train.py new file mode 100644 index 00000000..4bd4ff58 --- /dev/null +++ b/tutorials/distributed-ml/tf-tutorial-1-imagenet/train.py @@ -0,0 +1,171 @@ +""" + Show how to use TensorFlow MultiWorkerMirroredStrategy on itwinai. + for an Imagenet dataset + with SLURM: + >>> sbatch tfmirrored_slurm.sh + + """ +import argparse +import sys +from timeit import default_timer as timer + +import tensorflow as tf +from tensorflow import keras +from tensorflow.keras.layers import Dense, GlobalAveragePooling2D +from tensorflow.keras.models import Model + +from itwinai.tensorflow.distributed import get_strategy + + +def parse_args(): + """ + Parse args + """ + parser = argparse.ArgumentParser(description='TensorFlow ImageNet') + + parser.add_argument( + "--strategy", "-s", type=str, + choices=['mirrored'], + default='mirrored' + ) + parser.add_argument( + "--data_dir", type=str, + default='./' + ) + parser.add_argument( + "--batch_size", type=int, + default=128 + ) + parser.add_argument( + "--epochs", type=int, + default=3 + ) + + args = parser.parse_args() + return args + + +def deserialization_fn(serialized_fn): + """Imagenet data processing + + Args: + serialized_example (Any): Input function + + Returns: + Any: Images and associated labels + """ + parsed_example = tf.io.parse_single_example( + serialized_fn, + features={ + 'image/encoded': tf.io.FixedLenFeature([], tf.string), + 'image/class/label': tf.io.FixedLenFeature([], tf.int64), + } + ) + image = tf.image.decode_jpeg(parsed_example['image/encoded'], channels=3) + image = tf.image.resize(image, (224, 224)) + label = tf.cast(parsed_example['image/class/label'], tf.int64) - 1 + return image, label + + +def tf_records_loader(files_path, shuffle=False): + """tf_records dataset reader + + Args: + files_path (String): Path to location of data + shuffle (bool, optional): If dataset should be shuffled. + Defaults to False. + + Returns: + tf.data.Dataset: Returns dataset to be trained + """ + datasets = tf.data.Dataset.from_tensor_slices(files_path) + datasets = datasets.shuffle(len(files_path)) if shuffle else datasets + datasets = datasets.flat_map(tf.data.TFRecordDataset) + datasets = datasets.map( + deserialization_fn, num_parallel_calls=tf.data.AUTOTUNE) + return datasets + + +def main(): + args = parse_args() + + input_shape = (224, 224, 3) + num_classes = 1000 + + if args.strategy == 'mirrored': + strategy = get_strategy()[0] + else: + raise NotImplementedError( + f"Strategy {args.strategy} is not recognized/implemented.") + + with strategy.scope(): + base_model = keras.applications.ResNet50( + weights=None, + input_shape=input_shape, + include_top=False, + ) + + x = base_model.output + x = GlobalAveragePooling2D()(x) + x = Dense(1024, activation='relu')(x) + predictions = Dense(num_classes, activation='softmax')(x) + + model = Model(inputs=base_model.input, outputs=predictions) + + model.compile(loss=keras.losses.sparse_categorical_crossentropy, + optimizer=keras.optimizers.Adam(), + metrics=['accuracy'] + ) + + # scale batch size with number of workers + batch_size = args.batch_size * get_strategy()[1] + + dir_imagenet = args.data_dir+'imagenet-1K-tfrecords' + train_shard_suffix = 'train-*-of-01024' + test_shard_suffix = 'validation-*-of-00128' + + train_set_path = sorted( + tf.io.gfile.glob(dir_imagenet + f'/{train_shard_suffix}') + ) + test_set_path = sorted( + tf.io.gfile.glob(dir_imagenet + f'/{test_shard_suffix}') + ) + + train_dataset = tf_records_loader(train_set_path, shuffle=True) + test_dataset = tf_records_loader(test_set_path) + + train_dataset = train_dataset.batch( + batch_size).prefetch(tf.data.experimental.AUTOTUNE) + test_dataset = test_dataset.batch( + batch_size).prefetch(tf.data.experimental.AUTOTUNE) + + # distribute datasets among mirrored replicas + dist_train = strategy.experimental_distribute_dataset( + train_dataset + ) + dist_test = strategy.experimental_distribute_dataset( + test_dataset + ) + + # TODO: add callbacks to evaluate per epoch time + et = timer() + + # trains the model + model.fit(dist_train, epochs=args.epochs, steps_per_epoch=2000, verbose=10) + + print('TIMER: total epoch time:', + timer() - et, ' s') + print('TIMER: average epoch time:', + (timer() - et) / (args.epochs), ' s') + + test_scores = model.evaluate(dist_test, steps=100, verbose=5) + + print('Test loss:', test_scores[0]) + print('Test accuracy:', test_scores[1]) + + +if __name__ == "__main__": + main() + sys.exit() + +# eof diff --git a/tutorials/distributed-ml/torch-scaling-test/README.md b/tutorials/distributed-ml/torch-scaling-test/README.md new file mode 100644 index 00000000..1344504e --- /dev/null +++ b/tutorials/distributed-ml/torch-scaling-test/README.md @@ -0,0 +1,118 @@ +# Scaling tests for PyTorch of ResNet152 on Imagenet + +## Introduction + +This tutorial contains six training configurations: three baselines plus the itwinai +trainer, which allows to switch from DDP, Horovod, and DeepSpeed in a simplified way. + +The training scripts are: + +- `ddp_trainer.py`: baseline of distributed training with vanilla torch DDP +- `deepspeed_trainer.py`: baseline of distributed training with vanilla Microsoft DeepSpeed +- `horovod_trainer.py`: baseline of distributed training with vanilla Horovod +- `itwinai_trainer.py`: provides the same functionalities as all the above, +using the unified itwinai's distributed training interface. + +Configuration files are stored into `config/` folder. `base.yaml` provides the +configuration common to all training experiments, whereas `ddp.yaml`, `deepspeed.yaml`, +and `horovod.yaml` provide framework-specific configuration. +Thanks to `itwinai.parser.ArgumentParser`, the CLI arguments can be parsed from a list of +configuration files, while also allowing for online override. +Example: + +```bash +# Rather than requiring a LONG list of inline configuration params... +python ddp_trainer.py --data-dir some/dir --log-int 10 --verbose --nworker 4 ... + +# ...itwinai's ArgumentParser allows to load them from a set of configuration files +# with inline override, if needed +python ddp_trainer.py -c config/base.yaml -c config/ddp.yaml --log-int 42 +``` + +## Run a single training + +Training runs are meant to be submitted via SLURM, from a unified job script file: +`slurm.sh`. +You can select the distributed training algorithm and provide the command to execute +setting SLURM environment variables using the `--export` option: + +```bash +# Launch a distributed training setup with Torch DDP +export DIST_MODE="ddp" +export RUN_NAME="ddp-bl-imagenent" +export TRAINING_CMD="ddp_trainer.py -c config/base.yaml -c config/ddp.yaml" +export PYTHON_VENV="../../../envAI_hdfml" +export N=2 # Number of nodes +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + --nodes=$N slurm.sh +``` + +## Run all training configurations + +To run all training configurations you can use the `runall.sh` script, which provides +further insight how different training configurations can be launched using the same +SLURM job script. + +```bash +bash runall.sh +``` + +And check the newly created jobs in the SLURM queue: + +```bash +squeue -u YOUR_USERNAME +``` + +Each execution will generate a `.csv` file recording the time that each training epoch +took to complete. Below you can learn more on how to analyze these files to produce report. + +## Launch scaling test + +Similarly to `runall.sh`, there is another script which is meant to launch a scalability +analysis experiment. This will launch all the training configuration for different number +of node allocations. By default it will run the same distributed trainings on 1, 2, 4, and +8 nodes. Each independent execution will generate a separate `.csv` file which can be +analyzed later to produce a scalability report. + +Launch the scaling test: + +```bash +bash scaling-test.sh +``` + +And check the newly created jobs in the SLURM queue: + +```bash +squeue -u YOUR_USERNAME +``` + +## Analyze results + +Once all jobs have completed, you can automatically generate scalability report +using itwinai's CLI: + +```bash +# First, activate you Python virtual environment + +# For more info run +itwinai scalability-report --help + +# Generate a scalability report +itwinai scalability-report --pattern="^epoch.+\.csv$" \ + --plot-title "ResNet152 on Imagenet" --archive imagenet_results +``` + +The last command prints to terminal the average epoch time per training +configuration and per number of nodes, and it generated scaling test +analysis plot, which is saved as `.png` file. This command will also +create a `.tar.gz` archive of all the analyzed `.csv` files and +the generated plots, allowing you to easily organize different experiments +and reducing the risk of overwriting the logs generated during the scaling +test. + +Example of scalability plot generated by `itwinai scalability-report`: + +![report](img/report.png) diff --git a/tutorials/distributed-ml/torch-scaling-test/config/base.yaml b/tutorials/distributed-ml/torch-scaling-test/config/base.yaml new file mode 100644 index 00000000..3cbadd07 --- /dev/null +++ b/tutorials/distributed-ml/torch-scaling-test/config/base.yaml @@ -0,0 +1,16 @@ +# Data and logging +data_dir: /p/scratch/intertwin/datasets/imagenet/ILSVRC2012/train/ # tmp_data/ +log_int: 10 +verbose: True +nworker: 4 # num workers dataloader +prefetch: 2 + +# Model +batch_size: 64 # micro batch size +epochs: 3 +lr: 0.001 +momentum: 0.5 +shuff: False + +# Reproducibility +rnd_seed: 10 diff --git a/tutorials/distributed-ml/torch-scaling-test/config/ddp.yaml b/tutorials/distributed-ml/torch-scaling-test/config/ddp.yaml new file mode 100644 index 00000000..e872ffc9 --- /dev/null +++ b/tutorials/distributed-ml/torch-scaling-test/config/ddp.yaml @@ -0,0 +1 @@ +backend: nccl \ No newline at end of file diff --git a/tutorials/distributed-ml/torch-scaling-test/config/deepspeed.yaml b/tutorials/distributed-ml/torch-scaling-test/config/deepspeed.yaml new file mode 100644 index 00000000..e872ffc9 --- /dev/null +++ b/tutorials/distributed-ml/torch-scaling-test/config/deepspeed.yaml @@ -0,0 +1 @@ +backend: nccl \ No newline at end of file diff --git a/tutorials/distributed-ml/torch-scaling-test/config/horovod.yaml b/tutorials/distributed-ml/torch-scaling-test/config/horovod.yaml new file mode 100644 index 00000000..fce89755 --- /dev/null +++ b/tutorials/distributed-ml/torch-scaling-test/config/horovod.yaml @@ -0,0 +1,3 @@ +fp16_allreduce: False +use_adasum: False +gradient_predivide_factor: 1.0 \ No newline at end of file diff --git a/tutorials/distributed-ml/torch-scaling-test/ddp_trainer.py b/tutorials/distributed-ml/torch-scaling-test/ddp_trainer.py new file mode 100755 index 00000000..0a25ae5b --- /dev/null +++ b/tutorials/distributed-ml/torch-scaling-test/ddp_trainer.py @@ -0,0 +1,272 @@ +""" +Scaling test of torch Distributed Data Parallel on Imagenet using Resnet. +""" +from typing import Optional +import argparse +import sys +import os +from timeit import default_timer as timer +import time + +import torch +import torch.distributed as dist +import torch.nn as nn +import torch.nn.functional as F +from torch.utils.data import DataLoader +from torch.utils.data.distributed import DistributedSampler +import torchvision + +from itwinai.parser import ArgumentParser as ItAIArgumentParser +from itwinai.loggers import EpochTimeTracker +from itwinai.torch.reproducibility import ( + seed_worker, set_seed +) + +from utils import imagenet_dataset + + +def parse_params(): + parser = ItAIArgumentParser(description='PyTorch Imagenet scaling test') + + # Data and logging + parser.add_argument('--data-dir', default='./', + help=('location of the training dataset in the ' + 'local filesystem')) + parser.add_argument('--log-int', type=int, default=10, + help='log interval per training. Disabled if < 0.') + parser.add_argument('--verbose', + action=argparse.BooleanOptionalAction, + help='Print parsed arguments') + parser.add_argument('--nworker', type=int, default=0, + help=('number of workers in DataLoader ' + '(default: 0 - only main)')) + parser.add_argument('--prefetch', type=int, default=2, + help='prefetch data in DataLoader (default: 2)') + + # Model + parser.add_argument('--batch-size', type=int, default=64, + help='input batch size for training (default: 64)') + parser.add_argument('--epochs', type=int, default=10, + help='number of epochs to train (default: 10)') + parser.add_argument('--lr', type=float, default=0.01, + help='learning rate (default: 0.01)') + parser.add_argument('--momentum', type=float, default=0.5, + help='momentum in SGD optimizer (default: 0.5)') + parser.add_argument('--shuff', action='store_true', default=False, + help='shuffle dataset (default: False)') + + # Reproducibility + parser.add_argument('--rnd-seed', type=Optional[int], default=None, + help='seed integer for reproducibility (default: 0)') + + # Distributed ML + parser.add_argument('--backend', type=str, default='nccl', + help='backend for parrallelisation (default: nccl)') + parser.add_argument('--no-cuda', action='store_true', default=False, + help='disables GPGPUs') + + args = parser.parse_args() + + if args.verbose: + args_list = [f"{key}: {val}" for key, val in args.items()] + print("PARSED ARGS:\n", '\n'.join(args_list)) + return args + + +def train(model, device, train_loader, optimizer, epoch, grank, gwsize, args): + model.train() + t_list = [] + loss_acc = 0 + if grank == 0: + print("\n") + for batch_idx, (data, target) in enumerate(train_loader): + # if grank == 0: + # print(f"BS == DATA: {data.shape}, TARGET: {target.shape}") + t = timer() + data, target = data.to(device), target.to(device) + optimizer.zero_grad() + output = model(data) + loss = F.nll_loss(output, target) + loss.backward() + optimizer.step() + if grank == 0 and args.log_int > 0 and batch_idx % args.log_int == 0: + print( + f'Train epoch: {epoch} [{batch_idx * len(data)}/' + f'{len(train_loader.dataset) / gwsize} ' + f'({100.0 * batch_idx / len(train_loader):.0f}%)]\t\tLoss: ' + f'{loss.item():.6f}') + t_list.append(timer() - t) + loss_acc += loss.item() + if grank == 0: + print('TIMER: train time', sum(t_list) / len(t_list), 's') + return loss_acc + + +def main(): + # Parse CLI args + args = parse_params() + + # Check resources availability + use_cuda = not args.no_cuda and torch.cuda.is_available() + is_distributed = False + if use_cuda and torch.cuda.device_count() > 0: + is_distributed = True + + # Limit # of CPU threads to be used per worker + # torch.set_num_threads(1) + + # Start the timer for profiling + st = timer() + + if is_distributed: + # Initializes the distributed backend which will + # take care of synchronizing the workers (nodes/GPUs) + dist.init_process_group(backend=args.backend) + + # Set random seed for reproducibility + torch_prng = set_seed(args.rnd_seed, deterministic_cudnn=False) + + if is_distributed: + # get job rank info - rank==0 master gpu + lwsize = torch.cuda.device_count() # local world size - per run + gwsize = dist.get_world_size() # global world size - per run + grank = dist.get_rank() # global rank - assign per run + lrank = dist.get_rank() % lwsize # local rank - assign per node + else: + # Use a single worker (either on GPU or CPU) + lwsize = 1 + gwsize = 1 + grank = 0 + lrank = 0 + + if grank == 0: + print('TIMER: initialise:', timer()-st, 's') + print('DEBUG: local ranks:', lwsize, '/ global ranks:', gwsize) + print('DEBUG: sys.version:', sys.version) + print('DEBUG: args.data_dir:', args.data_dir) + print('DEBUG: args.log_int:', args.log_int) + print('DEBUG: args.nworker:', args.nworker) + print('DEBUG: args.prefetch:', args.prefetch) + print('DEBUG: args.batch_size:', args.batch_size) + print('DEBUG: args.epochs:', args.epochs) + print('DEBUG: args.lr:', args.lr) + print('DEBUG: args.momentum:', args.momentum) + print('DEBUG: args.shuff:', args.shuff) + print('DEBUG: args.rnd_seed:', args.rnd_seed) + print('DEBUG: args.backend:', args.backend) + print('DEBUG: args.no_cuda:', args.no_cuda, '\n') + + # Encapsulate the model on the GPU assigned to the current process + device = torch.device('cuda' if use_cuda else 'cpu', lrank) + if use_cuda: + torch.cuda.set_device(lrank) + + # Dataset + train_dataset = imagenet_dataset(args.data_dir) + + if is_distributed: + # Distributed sampler restricts data loading to a subset of the dataset + # exclusive to the current process. + # `mun_replicas` and `rank` are automatically retrieved from + # the current distributed group. + train_sampler = DistributedSampler( + train_dataset, # num_replicas=gwsize, rank=grank, + shuffle=(args.shuff and args.rnd_seed is None) + ) + + train_loader = DataLoader( + train_dataset, batch_size=args.batch_size, + sampler=train_sampler, num_workers=args.nworker, pin_memory=True, + persistent_workers=(args.nworker > 1), + prefetch_factor=args.prefetch, generator=torch_prng, + worker_init_fn=seed_worker + ) + else: + train_loader = DataLoader( + train_dataset, batch_size=args.batch_size, generator=torch_prng, + worker_init_fn=seed_worker + ) + + # Create CNN model + model = torchvision.models.resnet152().to(device) + + # Distribute model to workers + if is_distributed: + model = nn.parallel.DistributedDataParallel( + model, + device_ids=[device], + output_device=device) + + # Optimizer + optimizer = torch.optim.SGD( + model.parameters(), lr=args.lr, momentum=args.momentum) + + # Start training loop + if grank == 0: + print('TIMER: broadcast:', timer()-st, 's') + print('\nDEBUG: start training') + print('--------------------------------------------------------') + nnod = os.environ.get('SLURM_NNODES', 'unk') + epoch_time_tracker = EpochTimeTracker( + series_name="ddp-bl", + csv_file=f"epochtime_ddp-bl_{nnod}N.csv" + ) + + et = timer() + start_epoch = 1 + for epoch in range(start_epoch, args.epochs + 1): + lt = timer() + if is_distributed: + # Inform the sampler that a new epoch started: shuffle + # may be needed + train_sampler.set_epoch(epoch) + + # Training + train(model, device, train_loader, + optimizer, epoch, grank, gwsize, args) + # Save first epoch timer + if epoch == start_epoch: + first_ep_t = timer()-lt + + # Final epoch + if epoch + 1 == args.epochs: + train_loader.last_epoch = True + + if grank == 0: + print('TIMER: epoch time:', timer() - lt, 's') + epoch_time_tracker.add_epoch_time(epoch-1, timer() - lt) + + if is_distributed: + dist.barrier() + + if grank == 0: + print('\n--------------------------------------------------------') + print('DEBUG: training results:\n') + print('TIMER: first epoch time:', first_ep_t, ' s') + print('TIMER: last epoch time:', timer() - lt, ' s') + print('TIMER: average epoch time:', (timer() - et)/args.epochs, ' s') + print('TIMER: total epoch time:', timer() - et, ' s') + if epoch > 1: + print('TIMER: total epoch-1 time:', + timer() - et - first_ep_t, ' s') + print('TIMER: average epoch-1 time:', + (timer() - et - first_ep_t) / (args.epochs - 1), ' s') + if use_cuda: + print('DEBUG: memory req:', + int(torch.cuda.memory_reserved(lrank) / 1024 / 1024), 'MB') + print('DEBUG: memory summary:\n\n', + torch.cuda.memory_summary(0)) + print(f'TIMER: final time: {timer() - st} s\n') + + time.sleep(1) + print(f" - TRAINING FINISHED") + + # Clean-up + if is_distributed: + dist.barrier() + dist.destroy_process_group() + + +if __name__ == "__main__": + main() + sys.exit() diff --git a/tutorials/distributed-ml/torch-scaling-test/deepspeed_trainer.py b/tutorials/distributed-ml/torch-scaling-test/deepspeed_trainer.py new file mode 100644 index 00000000..e6022021 --- /dev/null +++ b/tutorials/distributed-ml/torch-scaling-test/deepspeed_trainer.py @@ -0,0 +1,286 @@ +""" +Scaling test of Microsoft Deepspeed on Imagenet using Resnet. +""" +from typing import Optional +import argparse +import sys +import os +from timeit import default_timer as timer +import time +import deepspeed + +import torch +import torch.distributed as dist +import torch.nn.functional as F +from torch.utils.data import DataLoader +from torch.utils.data.distributed import DistributedSampler +import torchvision + +from itwinai.parser import ArgumentParser as ItAIArgumentParser +from itwinai.loggers import EpochTimeTracker +from itwinai.torch.reproducibility import ( + seed_worker, set_seed +) + +from utils import imagenet_dataset + + +def parse_params(): + parser = ItAIArgumentParser(description='PyTorch Imagenet scaling test') + + # Data and logging + parser.add_argument('--data-dir', default='./', + help=('location of the training dataset in the ' + 'local filesystem')) + parser.add_argument('--log-int', type=int, default=10, + help='log interval per training. Disabled if < 0.') + parser.add_argument('--verbose', + action=argparse.BooleanOptionalAction, + help='Print parsed arguments') + parser.add_argument('--nworker', type=int, default=0, + help=('number of workers in DataLoader ' + '(default: 0 - only main)')) + parser.add_argument('--prefetch', type=int, default=2, + help='prefetch data in DataLoader (default: 2)') + + # Model + parser.add_argument('--batch-size', type=int, default=64, metavar='N', + help='input batch size for training (default: 64)') + parser.add_argument('--epochs', type=int, default=10, metavar='N', + help='number of epochs to train (default: 10)') + parser.add_argument('--lr', type=float, default=0.01, metavar='LR', + help='learning rate (default: 0.01)') + parser.add_argument('--momentum', type=float, default=0.5, + help='momentum in SGD optimizer (default: 0.5)') + parser.add_argument('--shuff', action='store_true', default=False, + help='shuffle dataset (default: False)') + + # Reproducibility + parser.add_argument('--rnd-seed', type=Optional[int], default=None, + help='seed integer for reproducibility (default: 0)') + + # Distributed ML + parser.add_argument('--backend', type=str, default='nccl', metavar='N', + help='backend for parallelization (default: nccl)') + parser.add_argument('--no-cuda', action='store_true', default=False, + help='disables GPGPUs') + parser.add_argument('--local_rank', type=int, default=-1, + help='local rank passed from distributed launcher') + + # parse to deepspeed + parser = deepspeed.add_config_arguments(parser) + args = parser.parse_args() + if args.verbose: + args_list = [f"{key}: {val}" for key, val in args.items()] + print("PARSED ARGS:\n", '\n'.join(args_list)) + + return args + + +def train(args, model, train_loader, optimizer, epoch, grank, gwsize): + device = model.local_rank + t_list = [] + loss_acc = 0 + if grank == 0: + print("\n") + for batch_idx, (data, target) in enumerate(train_loader): + # if grank == 0: + # print(f"BS == DATA: {data.shape}, TARGET: {target.shape}") + t = timer() + data, target = data.to(device), target.to(device) + optimizer.zero_grad() + output = model(data) + loss = F.nll_loss(output, target) + loss.backward() + optimizer.step() + if args.log_int > 0 and batch_idx % args.log_int == 0 and grank == 0: + print( + f'Train epoch: {epoch} [{batch_idx * len(data)}/' + f'{len(train_loader.dataset) / gwsize} ' + f'({100.0 * batch_idx * len(data) / len(train_loader):.0f}%)]' + f'\t\tLoss: {loss.item():.6f}') + t_list.append(timer() - t) + loss_acc += loss.item() + if grank == 0: + print('TIMER: train time', sum(t_list) / len(t_list), 's') + return loss_acc + + +def main(): + # Parse CLI args + args = parse_params() + + # Check resources availability + use_cuda = not args.no_cuda and torch.cuda.is_available() + is_distributed = False + if use_cuda and torch.cuda.device_count() > 0: + is_distributed = True + + # Limit # of CPU threads to be used per worker + # torch.set_num_threads(1) + + # Start the timer for profiling + st = timer() + + # Initializes the distributed backend + if is_distributed: + deepspeed.init_distributed(dist_backend=args.backend) + + # Set random seed for reproducibility + torch_prng = set_seed(args.rnd_seed, deterministic_cudnn=False) + + if is_distributed: + # Get job rank info - rank==0 master gpu + gwsize = dist.get_world_size() # global world size - per run + lwsize = torch.cuda.device_count() # local world size - per node + grank = dist.get_rank() # global rank - assign per run + lrank = dist.get_rank() % lwsize # local rank - assign per node + else: + # Use a single worker (either on GPU or CPU) + lwsize = 1 + gwsize = 1 + grank = 0 + lrank = 0 + + if grank == 0: + print('TIMER: initialise:', timer()-st, 's') + print('DEBUG: local ranks:', lwsize, '/ global ranks:', gwsize) + print('DEBUG: sys.version:', sys.version) + print('DEBUG: args.data_dir:', args.data_dir) + print('DEBUG: args.log_int:', args.log_int) + print('DEBUG: args.nworker:', args.nworker) + print('DEBUG: args.prefetch:', args.prefetch) + print('DEBUG: args.batch_size:', args.batch_size) + print('DEBUG: args.epochs:', args.epochs) + print('DEBUG: args.lr:', args.lr) + print('DEBUG: args.momentum:', args.momentum) + print('DEBUG: args.shuff:', args.shuff) + print('DEBUG: args.rnd_seed:', args.rnd_seed) + print('DEBUG: args.backend:', args.backend) + print('DEBUG: args.local_rank:', args.local_rank) + print('DEBUG: args.no_cuda:', args.no_cuda, '\n') + + # Encapsulate the model on the GPU assigned to the current process + if use_cuda: + torch.cuda.set_device(lrank) + + # Read training dataset + train_dataset = imagenet_dataset(args.data_dir) + + if is_distributed: + # Distributed sampler restricts data loading to a subset of the dataset + # exclusive to the current process. + # `mun_replicas` and `rank` are automatically retrieved from + # the current distributed group. + train_sampler = DistributedSampler( + train_dataset, # num_replicas=gwsize, rank=grank, + shuffle=(args.shuff and args.rnd_seed is None) + ) + + train_loader = DataLoader( + train_dataset, batch_size=args.batch_size, + sampler=train_sampler, num_workers=args.nworker, pin_memory=True, + persistent_workers=(args.nworker > 1), + prefetch_factor=args.prefetch, generator=torch_prng, + worker_init_fn=seed_worker + ) + else: + train_loader = DataLoader( + train_dataset, batch_size=args.batch_size, generator=torch_prng, + worker_init_fn=seed_worker + ) + + # Create CNN model + model = torchvision.models.resnet152() + + # Initialize DeepSpeed and get: + # 1) Distributed model + # 2) DeepSpeed optimizer + # 3) Distributed data loader + deepspeed_config = { + "train_micro_batch_size_per_gpu": args.batch_size, # redundant + "optimizer": { + "type": "SGD", + "params": { + "lr": args.lr, + "momentum": args.momentum + } + }, + "fp16": { + "enabled": False + }, + "zero_optimization": False + } + distrib_model, optimizer, deepspeed_train_loader, _ = deepspeed.initialize( + args=args, model=model, model_parameters=model.parameters(), + training_data=train_dataset, config_params=deepspeed_config) + + # Start training loop + if grank == 0: + print('TIMER: broadcast:', timer()-st, 's') + print('\nDEBUG: start training') + print('--------------------------------------------------------') + nnod = os.environ.get('SLURM_NNODES', 'unk') + epoch_time_tracker = EpochTimeTracker( + series_name="deepspeed-bl", + csv_file=f"epochtime_deepspeed-bl_{nnod}N.csv" + ) + + et = timer() + start_epoch = 1 + for epoch in range(start_epoch, args.epochs + 1): + lt = timer() + if is_distributed: + # Inform the sampler that a new epoch started: shuffle + # may be needed + train_sampler.set_epoch(epoch) + + # Training + train(args, distrib_model, train_loader, + optimizer, epoch, grank, gwsize) + + # Save first epoch timer + if epoch == start_epoch: + first_ep_t = timer()-lt + + # Final epoch + if epoch + 1 == args.epochs: + train_loader.last_epoch = True + + if grank == 0: + print('TIMER: epoch time:', timer()-lt, 's') + epoch_time_tracker.add_epoch_time(epoch-1, timer()-lt) + + if is_distributed: + dist.barrier() + + if grank == 0: + print('\n--------------------------------------------------------') + print('DEBUG: results:\n') + print('TIMER: first epoch time:', first_ep_t, ' s') + print('TIMER: last epoch time:', timer()-lt, ' s') + print('TIMER: average epoch time:', (timer()-et)/args.epochs, ' s') + print('TIMER: total epoch time:', timer()-et, ' s') + if epoch > 1: + print('TIMER: total epoch-1 time:', + timer()-et-first_ep_t, ' s') + print('TIMER: average epoch-1 time:', + (timer()-et-first_ep_t)/(args.epochs-1), ' s') + if use_cuda: + print('DEBUG: memory req:', + int(torch.cuda.memory_reserved(lrank)/1024/1024), 'MB') + print('DEBUG: memory summary:\n\n', + torch.cuda.memory_summary(0)) + print(f'TIMER: final time: {timer()-st} s\n') + + time.sleep(1) + print(f" - TRAINING FINISHED") + + # Clean-up + if is_distributed: + deepspeed.sys.exit() + + +if __name__ == "__main__": + main() + sys.exit() diff --git a/tutorials/distributed-ml/torch-scaling-test/horovod_trainer.py b/tutorials/distributed-ml/torch-scaling-test/horovod_trainer.py new file mode 100755 index 00000000..a4c3eaa4 --- /dev/null +++ b/tutorials/distributed-ml/torch-scaling-test/horovod_trainer.py @@ -0,0 +1,321 @@ +""" +Scaling test of Horovod on Imagenet using Resnet. +""" +from typing import Optional +import argparse +import os +import sys +from timeit import default_timer as timer +import time + +import torch +# import torch.multiprocessing as mp +import torch.nn.functional as F +import torch.optim as optim +from torch.utils.data import DataLoader +from torch.utils.data.distributed import DistributedSampler +import horovod.torch as hvd +import torchvision + +from itwinai.parser import ArgumentParser as ItAIArgumentParser +from itwinai.loggers import EpochTimeTracker +from itwinai.torch.reproducibility import ( + seed_worker, set_seed +) + +from utils import imagenet_dataset + + +def parse_params(): + parser = ItAIArgumentParser(description='PyTorch Imagenet Example') + + # Data and logging + parser.add_argument('--data-dir', default='./', + help=('location of the training dataset in the ' + 'local filesystem')) + parser.add_argument('--log-int', type=int, default=100, + help=('#batches to wait before logging training ' + 'status. Disabled if < 0.')) + parser.add_argument('--verbose', + action=argparse.BooleanOptionalAction, + help='Print parsed arguments') + parser.add_argument('--nworker', type=int, default=0, + help=('number of workers in DataLoader ' + '(default: 0 - only main)')) + parser.add_argument('--prefetch', type=int, default=2, + help='prefetch data in DataLoader (default: 2)') + + # Model + parser.add_argument('--batch-size', type=int, default=64, + help='input batch size for training (default: 64)') + parser.add_argument('--epochs', type=int, default=10, + help='number of epochs to train (default: 10)') + parser.add_argument('--lr', type=float, default=0.01, + help='learning rate (default: 0.01)') + parser.add_argument('--momentum', type=float, default=0.5, + help='SGD momentum (default: 0.5)') + parser.add_argument('--shuff', action='store_true', default=False, + help='shuffle dataset (default: False)') + + # Reproducibility + parser.add_argument('--rnd-seed', type=Optional[int], default=None, + help='seed integer for reproducibility (default: 0)') + + # Distributed ML + parser.add_argument('--no-cuda', action='store_true', default=False, + help='disables CUDA training') + parser.add_argument('--fp16-allreduce', action='store_true', default=False, + help='use fp16 compression during allreduce') + parser.add_argument('--use-adasum', action='store_true', default=False, + help='use adasum algorithm to do reduction') + parser.add_argument('--gradient-predivide-factor', type=float, default=1.0, + help=('apply gradient pre-divide factor in optimizer ' + '(default: 1.0)')) + + args = parser.parse_args() + if args.verbose: + args_list = [f"{key}: {val}" for key, val in args.items()] + print("PARSED ARGS:\n", '\n'.join(args_list)) + + return args + + +def train( + model, optimizer, train_sampler, train_loader, + args, use_cuda, epoch, grank +): + model.train() + t_list = [] + loss_acc = 0 + if grank == 0: + print("\n") + for batch_idx, (data, target) in enumerate(train_loader): + # if hvd.local_rank() == 0 and hvd.rank() == 0: + # print(f"BS == DATA: {data.shape}, TARGET: {target.shape}") + t = timer() + if use_cuda: + data, target = data.cuda(), target.cuda() + optimizer.zero_grad() + output = model(data) + loss = F.nll_loss(output, target) + loss.backward() + optimizer.step() + if grank == 0 and args.log_int > 0 and batch_idx % args.log_int == 0: + # Use train_sampler to determine the number of examples in + # this worker's partition + print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( + epoch, batch_idx * len(data), len(train_sampler), + 100. * batch_idx / len(train_loader), loss.item())) + t_list.append(timer() - t) + loss_acc += loss.item() + if grank == 0: + print('TIMER: train time', sum(t_list) / len(t_list), 's') + return loss_acc + + +def main(): + # Parse CLI args + args = parse_params() + + # Check resources availability + use_cuda = not args.no_cuda and torch.cuda.is_available() + is_distributed = False + if use_cuda and torch.cuda.device_count() > 0: + is_distributed = True + + # Start the time.time for profiling + st = timer() + + if is_distributed: + # Initializes the distributed backend which will + # take care of synchronizing the workers (nodes/GPUs) + hvd.init() + + # Set random seed for reproducibility + torch_prng = set_seed(args.rnd_seed, deterministic_cudnn=False) + + # is_main_worker = True + # if is_distributed and (hvd.rank() != 0 or hvd.local_rank() != 0): + # is_main_worker = False + + # Get local rank + if is_distributed: + lrank = hvd.local_rank() + grank = hvd.rank() + gwsize = hvd.size() + lwsize = torch.cuda.device_count() + else: + # Use a single worker (either on GPU or CPU) + lrank = 0 + grank = 0 + gwsize = 1 + lwsize = 1 + + if grank == 0: + print('TIMER: initialise:', timer()-st, 's') + print('DEBUG: local ranks:', lwsize, '/ global ranks:', gwsize) + print('DEBUG: sys.version:', sys.version) + print('DEBUG: args.data_dir:', args.data_dir) + print('DEBUG: args.log_int:', args.log_int) + print('DEBUG: args.nworker:', args.nworker) + print('DEBUG: args.prefetch:', args.prefetch) + print('DEBUG: args.batch_size:', args.batch_size) + print('DEBUG: args.epochs:', args.epochs) + print('DEBUG: args.lr:', args.lr) + print('DEBUG: args.momentum:', args.momentum) + print('DEBUG: args.shuff:', args.shuff) + print('DEBUG: args.rnd_seed:', args.rnd_seed) + print('DEBUG: args.no_cuda:', args.no_cuda) + print('DEBUG: args.fp16_allreduce:', args.fp16_allreduce) + print('DEBUG: args.use_adasum:', args.use_adasum) + print('DEBUG: args.gradient_predivide_factor:', + args.gradient_predivide_factor) + if use_cuda: + print('DEBUG: torch.cuda.is_available():', + torch.cuda.is_available()) + print('DEBUG: torch.cuda.current_device():', + torch.cuda.current_device()) + print('DEBUG: torch.cuda.device_count():', + torch.cuda.device_count()) + print('DEBUG: torch.cuda.get_device_properties(hvd.local_rank()):', + torch.cuda.get_device_properties(hvd.local_rank())) + + if use_cuda: + # Pin GPU to local rank + torch.cuda.set_device(lrank) + + # Limit # of CPU threads to be used per worker + # torch.set_num_threads(1) + + # Dataset + train_dataset = imagenet_dataset(args.data_dir) + + # kwargs = {} + # # When supported, use 'forkserver' to spawn dataloader workers instead... + # # issues with Infiniband implementations that are not fork-safe + # if (args.nworker > 0 and hasattr(mp, '_supports_context') + # and + # mp._supports_context and + # 'forkserver' in mp.get_all_start_methods()): + # kwargs['multiprocessing_context'] = 'forkserver' + + if is_distributed: + # Use DistributedSampler to partition the training data + # Since Horovod is not based on torch.distributed, + # `num_replicas` and `rank` cannot be retrieved from the + # current distributed group, thus they need to be provided explicitly. + train_sampler = DistributedSampler( + train_dataset, num_replicas=gwsize, rank=grank, + shuffle=(args.shuff and args.rnd_seed is None) + ) + train_loader = DataLoader( + train_dataset, batch_size=args.batch_size, + sampler=train_sampler, num_workers=args.nworker, pin_memory=True, + persistent_workers=(args.nworker > 1), + prefetch_factor=args.prefetch, generator=torch_prng, + worker_init_fn=seed_worker + ) # , **kwargs) + else: + train_loader = DataLoader( + train_dataset, batch_size=args.batch_size, generator=torch_prng, + worker_init_fn=seed_worker + ) # , **kwargs) + + # Create CNN model + model = torchvision.models.resnet152() + + if use_cuda: + model.cuda() + + if is_distributed: + # By default, Adasum doesn't need scaling up learning rate + lr_scaler = hvd.size() if not args.use_adasum else 1 + # If using GPU Adasum allreduce, scale learning rate by local_size + if args.use_adasum and hvd.nccl_built(): + lr_scaler = hvd.local_size() + # Scale learning rate by lr_scaler + args.lr *= lr_scaler + + optimizer = optim.SGD(model.parameters(), lr=args.lr, + momentum=args.momentum) + + if is_distributed: + # Broadcast parameters & optimizer state + hvd.broadcast_parameters(model.state_dict(), root_rank=0) + hvd.broadcast_optimizer_state(optimizer, root_rank=0) + + # Compression algorithm + compression = ( + hvd.Compression.fp16 if args.fp16_allreduce + else hvd.Compression.none + ) + + # Wrap optimizer with DistributedOptimizer + optimizer = hvd.DistributedOptimizer( + optimizer, + named_parameters=model.named_parameters(), + compression=compression, + op=hvd.Adasum if args.use_adasum else hvd.Average, + gradient_predivide_factor=args.gradient_predivide_factor) + + if grank == 0: + print('TIMER: broadcast:', timer()-st, 's') + print('\nDEBUG: start training') + print('--------------------------------------------------------') + nnod = os.environ.get('SLURM_NNODES', 'unk') + epoch_time_tracker = EpochTimeTracker( + series_name="horovod-bl", + csv_file=f"epochtime_horovod-bl_{nnod}N.csv" + ) + + et = timer() + start_epoch = 1 + for epoch in range(start_epoch, args.epochs + 1): + lt = timer() + if is_distributed: + # Inform the sampler that a new epoch started: shuffle + # may be needed + train_sampler.set_epoch(epoch) + + # Training + train(model, optimizer, train_sampler, + train_loader, args, use_cuda, epoch, grank) + + # Save first epoch timer + if epoch == start_epoch: + first_ep_t = timer()-lt + + # Final epoch + if epoch + 1 == args.epochs: + train_loader.last_epoch = True + + if grank == 0: + print('TIMER: epoch time:', timer()-lt, 's') + epoch_time_tracker.add_epoch_time(epoch-1, timer()-lt) + + if grank == 0: + print('\n--------------------------------------------------------') + print('DEBUG: training results:\n') + print('TIMER: first epoch time:', first_ep_t, ' s') + print('TIMER: last epoch time:', timer()-lt, 's') + print('TIMER: average epoch time:', (timer()-et)/args.epochs, ' s') + print('TIMER: total epoch time:', timer()-et, ' s') + if epoch > 1: + print('TIMER: total epoch-1 time:', + timer()-et-first_ep_t, ' s') + print('TIMER: average epoch-1 time:', + (timer()-et-first_ep_t)/(args.epochs-1), ' s') + if use_cuda: + print('DEBUG: memory req:', + int(torch.cuda.memory_reserved(lrank)/1024/1024), 'MB') + print('DEBUG: memory summary:\n\n', + torch.cuda.memory_summary(0)) + print(f'TIMER: final time: {timer()-st} s\n') + + time.sleep(1) + print(f" - TRAINING FINISHED") + + +if __name__ == "__main__": + main() + sys.exit() diff --git a/tutorials/distributed-ml/torch-scaling-test/img/report.png b/tutorials/distributed-ml/torch-scaling-test/img/report.png new file mode 100644 index 0000000000000000000000000000000000000000..4e81996e1e22505410cb7b19852a7a9558e3c0d5 GIT binary patch literal 198864 zcmeFZWmHyc+cm6P-EMmu7)Tg^lprlg*homjB`s3Y(hY7~L{L(Ylx_tPX}3s&DB&eY zh=7!Ubi+GO^uE9E`ToB@zA>IPhGV1qVqI&UaUAoQa~|h?1v$wr8)-MLTD5A6)P=K3 zt5*Hlvuf4agunj8zl_D$AH;uz?ap1YQ?@d)bJVjjTy;^;?wYxkow>=C0}h5ZwkB4V zd|b!5csUPTwX?fsE5glf@!t<{S=ktKADCZyhnsA;c0t{C)he#Ta( zI(tgRDeOm^qmzp2Qu%0IjY0j14TX-64sE<)r}Fo%{m(K5pX+H3%I~Jx_3w>7_Sb4E zyZ0C#y>#v9U+m|0t&P1u;rBsi<3X{8;St}*6$UJNER{?r+cSDA{abD2XWMxk{~QtO zrSsR@dk0VO`&#=jH*d{<{<3P-dpjn)@bByTfx~R;|MQnsYxhYB|MxEkDlSyvfq!48 z&MR&G&(Ev=;`Oun?_W+ndK~co-2R4lEZu+q*WU*J`&IuLjQ=@+nU4m34d;zgqb!rb zPiZX$&RO*h4fa1i^Qfn(F*7l-3Ja_EM!2Ta@?TYG)BA(m#&p%nuzN~hyr`OGRJ)MQ zxO;c}=g*f)OG}f^J=i@^ANSzB#IoBK2GL8=(b1>8Cni3*?=Ylv z`t<4C`K=o^JZ|Xc6d$H$MDFI+(HdTQzgAdSSR>2mp~w7C&bN&46OL8KHv77;v9X!9 zyfnF8KG;!Q9K@-v)ak+0|Ng{Cqo!3Mjl%x?w)D~mdj`uHJsY2$d+^ezmR6X~hxZG= z`DX>HmhHpgqIt_~)28G@2V-bw=E74_JB-O&B+9NVimIf<#0w@SCeku84L6ro^e^3J z#|F0cz8q^Y%P<*fFKnVOGqD(M&2{OEzRT}AWp2@ymwle8o2h@iPJ&vzyf|`wr_B-G z&1d+GY81+S=(8=pyps0*HQh#JO9im8vr8YJ0p+bQ2yINhEyte_xOm3LPzUNPo84TA(Hr)*oEy>eZIm};?y zdsDi$rd)#Z`fUu2nTFN;R_&rRG&IgHo3o64xn6($hV>kIvIez%!x7VQP@^6k5KgEgkV{(F;_ z&&b=TE@DWr$aC4F&1Eo6=$fobL>c)9Hg3key*p} z>p0w+)ZdUmIb)D;B)B?GlF|KTUdi#a5LuRNlZNv`t=DeGUwj%L>A9rd+}zyqT<@mQ z`>-UIi=irx_fBo!zCHRbgUbci>EWheU5_M=jDpOXTVYI1DHM4|x1ayuW8L0% z!-wcIg;mD2p{n~jS7zfR39C`O7_Gq<uWUT4VhRNuX7Fbr9bhC z_L=Y^^N}MQEG+SPC5bj-E)%R2-r=)5kH}51Yd*jHkGY`c?YWgfJ$Xf*Ot7>NW+8(Y zgNLQohx>3l7GsH+eMB}xCs8(57iCr3)XV-tozf$G#2QdR9qk-uT zPwM;d6M^C3X$1wMx9{9x=jBzxpr|#(Uu?>=H!CeKr#Pjay7_1Svj<%D+awkgv|e2) z-RM5opFk__=5%{LBQZ@g#}tuG#HRZmA{z~2Rd=_6fNA4}wtPp4{vG#^i-p;~7f#m9 zN#9B@qN1gC=~Cj&_1n}2{UYz^T`jq@rKQL{zrMacAS5K|_3Ptgh1Z4(Gzj;G9nKP- z@w9Tr&@avnW{{5|fW-IpUB≠+~3#1vkY#?Ae^#Iy*Z}KSce5u()vDgL^C8@hH_4 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 TdYwP8-z#a=2!sCq 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 argparse.Namespace: + """ + Parse CLI args, which can also be loaded from a configuration file + using the --config flag: + + >>> train.py --strategy ddp --config base-config.yaml --config foo.yaml + """ + parser = ItAIArgumentParser(description='PyTorch Imagenet Example') + + # Distributed ML strategy + parser.add_argument( + "--strategy", "-s", type=str, + choices=['ddp', 'horovod', 'deepspeed'], + default='ddp' + ) + + # Data and logging + parser.add_argument('--data-dir', default='./', + help=('location of the training dataset in the local ' + 'filesystem')) + parser.add_argument('--log-int', type=int, default=10, + help='log interval per training') + parser.add_argument('--verbose', + action=argparse.BooleanOptionalAction, + help='Print parsed arguments') + parser.add_argument('--nworker', type=int, default=0, + help=('number of workers in DataLoader (default: 0 -' + ' only main)')) + parser.add_argument('--prefetch', type=int, default=2, + help='prefetch data in DataLoader (default: 2)') + + # Model + parser.add_argument('--batch-size', type=int, default=64, + help='input batch size for training (default: 64)') + parser.add_argument('--epochs', type=int, default=10, + help='number of epochs to train (default: 10)') + parser.add_argument('--lr', type=float, default=0.01, + help='learning rate (default: 0.01)') + parser.add_argument('--momentum', type=float, default=0.5, + help='momentum in SGD optimizer (default: 0.5)') + parser.add_argument('--shuff', action='store_true', default=False, + help='shuffle dataset (default: False)') + + # Reproducibility + parser.add_argument('--rnd-seed', type=Optional[int], default=None, + help='seed integer for reproducibility (default: 0)') + + # Distributed ML + parser.add_argument('--backend', type=str, default='nccl', + help='backend for parrallelisation (default: nccl)') + parser.add_argument('--no-cuda', action='store_true', default=False, + help='disables GPGPUs') + parser.add_argument('--local_rank', type=int, default=-1, + help='local rank passed from distributed launcher') + + # Horovod + parser.add_argument('--fp16-allreduce', action='store_true', default=False, + help='use fp16 compression during allreduce') + parser.add_argument('--use-adasum', action='store_true', default=False, + help='use adasum algorithm to do reduction') + parser.add_argument('--gradient-predivide-factor', type=float, default=1.0, + help=('apply gradient pre-divide factor in optimizer ' + '(default: 1.0)')) + + # DeepSpeed + parser = deepspeed.add_config_arguments(parser) + args = parser.parse_args() + + if args.verbose: + args_list = [f"{key}: {val}" for key, val in args.items()] + print("PARSED ARGS:\n", '\n'.join(args_list)) + + return args + + +def train( + model, device, train_loader, optimizer, epoch, + strategy: TorchDistributedStrategy, args +): + """ + Training function, representing an epoch. + """ + model.train() + t_list = [] + loss_acc = 0 + gwsize = strategy.global_world_size() + if strategy.is_main_worker: + print("\n") + for batch_idx, (data, target) in enumerate(train_loader): + t = timer() + data, target = data.to(device), target.to(device) + optimizer.zero_grad() + output = model(data) + loss = F.nll_loss(output, target) + loss.backward() + optimizer.step() + if (strategy.is_main_worker and args.log_int > 0 + and batch_idx % args.log_int == 0): + print( + f'Train epoch: {epoch} ' + f'[{batch_idx * len(data)}/{len(train_loader.dataset)/gwsize} ' + f'({100.0 * batch_idx / len(train_loader):.0f}%)]\t\t' + f'Loss: {loss.item():.6f}') + t_list.append(timer() - t) + loss_acc += loss.item() + if strategy.is_main_worker: + print('TIMER: train time', sum(t_list) / len(t_list), 's') + return loss_acc + + +def main(): + # Parse CLI args + args = parse_params() + + # Instantiate Strategy + if args.strategy == 'ddp': + if (not torch.cuda.is_available() + or not torch.cuda.device_count() > 1): + raise RuntimeError('Resources unavailable') + + strategy = TorchDDPStrategy(backend=args.backend) + distribute_kwargs = {} + elif args.strategy == 'horovod': + strategy = HorovodStrategy() + distribute_kwargs = dict( + compression=( + hvd.Compression.fp16 if args.fp16_allreduce + else hvd.Compression.none + ), + op=hvd.Adasum if args.use_adasum else hvd.Average, + gradient_predivide_factor=args.gradient_predivide_factor + ) + elif args.strategy == 'deepspeed': + strategy = DeepSpeedStrategy(backend=args.backend) + distribute_kwargs = dict( + config_params=dict(train_micro_batch_size_per_gpu=args.batch_size) + ) + else: + raise NotImplementedError( + f"Strategy {args.strategy} is not recognized/implemented.") + strategy.init() + + # Check resources availability + use_cuda = not args.no_cuda and torch.cuda.is_available() + is_distributed = False + if use_cuda and torch.cuda.device_count() > 0: + is_distributed = True + + # Limit # of CPU threads to be used per worker + # torch.set_num_threads(1) + + # Start the timer for profiling + st = timer() + + # Set random seed for reproducibility + torch_prng = set_seed(args.rnd_seed, deterministic_cudnn=False) + + # Get job rank info - rank==0 master gpu + if is_distributed: + # local world size - per node + lwsize = strategy.local_world_size() # local world size - per run + gwsize = strategy.global_world_size() # global world size - per run + grank = strategy.global_rank() # global rank - assign per run + lrank = strategy.local_rank() # local rank - assign per node + else: + # Use a single worker (either on GPU or CPU) + lwsize = 1 + gwsize = 1 + grank = 0 + lrank = 0 + + if strategy.is_main_worker: + print('TIMER: initialise:', timer()-st, 's') + print('DEBUG: local ranks:', lwsize, '/ global ranks:', gwsize) + print('DEBUG: sys.version:', sys.version) + print('DEBUG: args.data_dir:', args.data_dir) + print('DEBUG: args.log_int:', args.log_int) + print('DEBUG: args.nworker:', args.nworker) + print('DEBUG: args.prefetch:', args.prefetch) + print('DEBUG: args.batch_size:', args.batch_size) + print('DEBUG: args.epochs:', args.epochs) + print('DEBUG: args.lr:', args.lr) + print('DEBUG: args.momentum:', args.momentum) + print('DEBUG: args.shuff:', args.shuff) + print('DEBUG: args.rnd_seed:', args.rnd_seed) + print('DEBUG: args.backend:', args.backend) + print('DEBUG: args.no_cuda:', args.no_cuda, '\n') + + # Encapsulate the model on the GPU assigned to the current process + device = torch.device( + strategy.device() if use_cuda + else 'cpu') + if use_cuda: + torch.cuda.set_device(lrank) + + # Dataset + train_dataset = imagenet_dataset(args.data_dir) + + if is_distributed: + # Distributed sampler restricts data loading to a subset of the dataset + # exclusive to the current process. + train_sampler = DistributedSampler( + train_dataset, num_replicas=gwsize, rank=grank, + shuffle=(args.shuff and args.rnd_seed is None) + ) + + train_loader = DataLoader( + train_dataset, batch_size=args.batch_size, + sampler=train_sampler, num_workers=args.nworker, pin_memory=True, + persistent_workers=(args.nworker > 1), + prefetch_factor=args.prefetch, generator=torch_prng, + worker_init_fn=seed_worker + ) + else: + train_loader = DataLoader( + train_dataset, batch_size=args.batch_size, generator=torch_prng, + worker_init_fn=seed_worker + ) + + # Create CNN model: resnet 50, resnet101, resnet152 + model = torchvision.models.resnet152() + + # Optimizer + optimizer = torch.optim.SGD( + model.parameters(), lr=args.lr, momentum=args.momentum) + + if is_distributed: + distrib_model, optimizer, _ = strategy.distributed( + model, optimizer, lr_scheduler=None, **distribute_kwargs + ) + + # Start training loop + if strategy.is_main_worker: + print('TIMER: broadcast:', timer()-st, 's') + print('\nDEBUG: start training') + print('--------------------------------------------------------') + nnod = os.environ.get('SLURM_NNODES', 'unk') + s_name = f"{args.strategy}-it" + epoch_time_tracker = EpochTimeTracker( + series_name=s_name, + csv_file=f"epochtime_{s_name}_{nnod}N.csv" + ) + + et = timer() + start_epoch = 1 + for epoch in range(start_epoch, args.epochs + 1): + lt = timer() + if is_distributed: + # Inform the sampler that a new epoch started: shuffle + # may be needed + train_sampler.set_epoch(epoch) + + # Training + train( + model=distrib_model, + device=device, + train_loader=train_loader, + optimizer=optimizer, + epoch=epoch, + strategy=strategy, + args=args + ) + + # Save first epoch timer + if epoch == start_epoch: + first_ep_t = timer()-lt + + # Final epoch + if epoch + 1 == args.epochs: + train_loader.last_epoch = True + + if strategy.is_main_worker: + print('TIMER: epoch time:', timer()-lt, 's') + epoch_time_tracker.add_epoch_time(epoch-1, timer()-lt) + + if strategy.is_main_worker: + print('\n--------------------------------------------------------') + print('DEBUG: training results:\n') + print('TIMER: first epoch time:', first_ep_t, ' s') + print('TIMER: last epoch time:', timer()-lt, ' s') + print('TIMER: average epoch time:', (timer()-et)/args.epochs, ' s') + print('TIMER: total epoch time:', timer()-et, ' s') + if epoch > 1: + print('TIMER: total epoch-1 time:', + timer()-et-first_ep_t, ' s') + print('TIMER: average epoch-1 time:', + (timer()-et-first_ep_t)/(args.epochs-1), ' s') + if use_cuda: + print('DEBUG: memory req:', + int(torch.cuda.memory_reserved(lrank)/1024/1024), 'MB') + print('DEBUG: memory summary:\n\n', + torch.cuda.memory_summary(0)) + + print(f'TIMER: final time: {timer()-st} s\n') + + time.sleep(1) + print(f" - TRAINING FINISHED") + + # Clean-up + if is_distributed: + strategy.clean_up() + + +if __name__ == "__main__": + main() + sys.exit() diff --git a/tutorials/distributed-ml/torch-scaling-test/runall.sh b/tutorials/distributed-ml/torch-scaling-test/runall.sh new file mode 100644 index 00000000..22958c16 --- /dev/null +++ b/tutorials/distributed-ml/torch-scaling-test/runall.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# Run all versions of distributed ML version +# $1 (Optional[int]): number of nodes. Default: 2 +# $2 (Optional[str]): timeout. Default: "00:30:00" + +if [ -z "$1" ] ; then + N=2 +else + N=$1 +fi +if [ -z "$2" ] ; then + T="00:30:00" +else + T=$2 +fi + +# Common options +CMD="--nodes=$N --time=$T --account=intertwin --partition=batch slurm.sh" +PYTHON_VENV="../../../envAI_hdfml" + +echo "Distributing training over $N nodes. Timeout set to: $T" + +# Clear SLURM logs (*.out and *.err files) +rm -rf logs_slurm +mkdir logs_slurm +rm -rf logs_torchrun + +# Clear scaling test logs +rm *.csv # *checkpoint.pth.tar + +# DDP baseline +DIST_MODE="ddp" +RUN_NAME="ddp-bl-imagenent" +TRAINING_CMD="ddp_trainer.py -c config/base.yaml -c config/ddp.yaml" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + $CMD + +# DeepSpeed baseline +DIST_MODE="deepspeed" +RUN_NAME="deepspeed-bl-imagenent" +TRAINING_CMD="deepspeed_trainer.py -c config/base.yaml -c config/deepspeed.yaml" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + $CMD + +# Horovod baseline +DIST_MODE="horovod" +RUN_NAME="horovod-bl-imagenent" +TRAINING_CMD="horovod_trainer.py -c config/base.yaml -c config/horovod.yaml" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + $CMD + +# DDP itwinai +DIST_MODE="ddp" +RUN_NAME="ddp-itwinai-imagenent" +TRAINING_CMD="itwinai_trainer.py -c config/base.yaml -c config/ddp.yaml -s ddp" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + $CMD + +# DeepSpeed itwinai +DIST_MODE="deepspeed" +RUN_NAME="deepspeed-itwinai-imagenent" +TRAINING_CMD="itwinai_trainer.py -c config/base.yaml -c config/deepspeed.yaml -s deepspeed" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + $CMD + +# Horovod itwinai +DIST_MODE="horovod" +RUN_NAME="horovod-itwinai-imagenent" +TRAINING_CMD="itwinai_trainer.py -c config/base.yaml -c config/horovod.yaml -s horovod" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + $CMD \ No newline at end of file diff --git a/tutorials/distributed-ml/torch-scaling-test/scaling-test.sh b/tutorials/distributed-ml/torch-scaling-test/scaling-test.sh new file mode 100644 index 00000000..29a32705 --- /dev/null +++ b/tutorials/distributed-ml/torch-scaling-test/scaling-test.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +rm *checkpoint.pth.tar *.out *.err *.csv + +timeout="03:30:00" +for N in 1 2 4 8 16 32 64 128 +do + bash runall.sh $N $timeout + echo +done \ No newline at end of file diff --git a/tutorials/distributed-ml/torch-scaling-test/slurm.sh b/tutorials/distributed-ml/torch-scaling-test/slurm.sh new file mode 100644 index 00000000..c53e3da5 --- /dev/null +++ b/tutorials/distributed-ml/torch-scaling-test/slurm.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +# SLURM jobscript for JSC systems + +# Job configuration +#SBATCH --job-name=distributed_training +#SBATCH --account=intertwin +#SBATCH --mail-user= +#SBATCH --mail-type=ALL +#SBATCH --output=job.out +#SBATCH --error=job.err +#SBATCH --time=00:30:00 + +# Resources allocation +#SBATCH --partition=batch +#SBATCH --nodes=2 +#SBATCH --gpus-per-node=4 +#SBATCH --cpus-per-gpu=4 +#SBATCH --exclusive + +# gres options have to be disabled for deepv +#SBATCH --gres=gpu:4 + +# Load environment modules +ml Stages/2024 GCC OpenMPI CUDA/12 MPI-settings/CUDA Python HDF5 PnetCDF libaio mpi4py + +# Job info +echo "DEBUG: TIME: $(date)" +sysN="$(uname -n | cut -f2- -d.)" +sysN="${sysN%%[0-9]*}" +echo "Running on system: $sysN" +echo "DEBUG: EXECUTE: $EXEC" +echo "DEBUG: SLURM_SUBMIT_DIR: $SLURM_SUBMIT_DIR" +echo "DEBUG: SLURM_JOB_ID: $SLURM_JOB_ID" +echo "DEBUG: SLURM_JOB_NODELIST: $SLURM_JOB_NODELIST" +echo "DEBUG: SLURM_NNODES: $SLURM_NNODES" +echo "DEBUG: SLURM_NTASKS: $SLURM_NTASKS" +echo "DEBUG: SLURM_TASKS_PER_NODE: $SLURM_TASKS_PER_NODE" +echo "DEBUG: SLURM_SUBMIT_HOST: $SLURM_SUBMIT_HOST" +echo "DEBUG: SLURMD_NODENAME: $SLURMD_NODENAME" +echo "DEBUG: CUDA_VISIBLE_DEVICES: $CUDA_VISIBLE_DEVICES" +if [ "$DEBUG" = true ] ; then + echo "DEBUG: NCCL_DEBUG=INFO" + export NCCL_DEBUG=INFO +fi +echo + +# Setup env for distributed ML +export CUDA_VISIBLE_DEVICES="0,1,2,3" +export OMP_NUM_THREADS=1 +if [ "$SLURM_CPUS_PER_GPU" -gt 0 ] ; then + export OMP_NUM_THREADS=$SLURM_CPUS_PER_GPU +fi + +# Env vairables check +if [ -z "$DIST_MODE" ]; then + >&2 echo "ERROR: env variable DIST_MODE is not set. Allowed values are 'horovod', 'ddp' or 'deepspeed'" + exit 1 +fi +if [ -z "$RUN_NAME" ]; then + >&2 echo "WARNING: env variable RUN_NAME is not set. It's a way to identify some specific run of an experiment." + RUN_NAME=$DIST_MODE +fi +if [ -z "$TRAINING_CMD" ]; then + >&2 echo "ERROR: env variable TRAINING_CMD is not set. It's the python command to execute." + exit 1 +fi +if [ -z "$PYTHON_VENV" ]; then + >&2 echo "WARNING: env variable PYTHON_VENV is not set. It's the path to a python virtual environment." +else + # Activate Python virtual env + source $PYTHON_VENV/bin/activate +fi + +# Get GPUs info per node +srun --cpu-bind=none --ntasks-per-node=1 bash -c 'echo -e "NODE hostname: $(hostname)\n$(nvidia-smi)\n\n"' + +# Launch training +if [ "$DIST_MODE" == "ddp" ] ; then + echo "DDP training: $TRAINING_CMD" + srun --cpu-bind=none --ntasks-per-node=1 \ + bash -c "torchrun \ + --log_dir='logs_torchrun' \ + --nnodes=$SLURM_NNODES \ + --nproc_per_node=$SLURM_GPUS_PER_NODE \ + --rdzv_id=$SLURM_JOB_ID \ + --rdzv_conf=is_host=\$(((SLURM_NODEID)) && echo 0 || echo 1) \ + --rdzv_backend=c10d \ + --rdzv_endpoint='$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)'i:29500 \ + $TRAINING_CMD" +elif [ "$DIST_MODE" == "deepspeed" ] ; then + echo "DEEPSPEED training: $TRAINING_CMD" + MASTER_ADDR=$(scontrol show hostnames "\$SLURM_JOB_NODELIST" | head -n 1)i + export MASTER_ADDR + export MASTER_PORT=29500 + + srun --cpu-bind=none --ntasks-per-node=$SLURM_GPUS_PER_NODE --cpus-per-task=$SLURM_CPUS_PER_GPU \ + python -u $TRAINING_CMD --deepspeed + + # # Run with deepspeed launcher: set --ntasks-per-node=1 + # # https://www.deepspeed.ai/getting-started/#multi-node-environment-variables + # export NCCL_IB_DISABLE=1 + # export NCCL_SOCKET_IFNAME=eth0 + # nodelist=$(scontrol show hostname $SLURM_NODELIST) + # echo "$nodelist" | sed -e 's/$/ slots=4/' > .hostfile + # # Requires passwordless SSH access among compute node + # srun --cpu-bind=none deepspeed --hostfile=.hostfile $TRAINING_CMD --deepspeed + # rm .hostfile +elif [ "$DIST_MODE" == "horovod" ] ; then + echo "HOROVOD training: $TRAINING_CMD" + srun --cpu-bind=none --ntasks-per-node=$SLURM_GPUS_PER_NODE --cpus-per-task=$SLURM_CPUS_PER_GPU \ + python -u $TRAINING_CMD +else + >&2 echo "ERROR: unrecognized \$DIST_MODE env variable" + exit 1 +fi + diff --git a/tutorials/distributed-ml/torch-scaling-test/utils.py b/tutorials/distributed-ml/torch-scaling-test/utils.py new file mode 100644 index 00000000..a5dc591e --- /dev/null +++ b/tutorials/distributed-ml/torch-scaling-test/utils.py @@ -0,0 +1,21 @@ +from torchvision import datasets, transforms + + +def imagenet_dataset(data_root: str): + """Create a torch dataset object for Imagenet.""" + transform = transforms.Compose([ + transforms.Resize(256), + transforms.RandomHorizontalFlip(), + transforms.RandomVerticalFlip(), + transforms.RandomRotation(degrees=45), + transforms.ColorJitter( + brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5), + transforms.CenterCrop(224), + transforms.ToTensor(), + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) + ]) + imagenet = datasets.ImageFolder( + root=data_root, + transform=transform + ) + return imagenet diff --git a/tutorials/distributed-ml/torch-tutorial-0-basics/README.md b/tutorials/distributed-ml/torch-tutorial-0-basics/README.md new file mode 100644 index 00000000..bee09dcf --- /dev/null +++ b/tutorials/distributed-ml/torch-tutorial-0-basics/README.md @@ -0,0 +1,126 @@ +# Tutorial: distributed strategies for PyTorch + +In this tutorial we show how to use torch `DistributedDataParallel` (DDP), Horovod and +DeepSpeed from the same client code. +Note that the environment is tested on the HDFML system at JSC. For other systems, +the module versions might need change accordingly. + +## Setup + +First, from the root of this repository, build the environment containing +pytorch, horovod and deepspeed. You can *try* with: + +```bash +# Creates a Python venv called envAI_hdfml +make torch-gpu-jsc +``` + +## Distributed training on a single node (interactive) + +If you want to use SLURM in interactive mode, do the following: + +```bash +# Allocate resources +$ salloc --partition=batch --nodes=1 --account=intertwin --gres=gpu:4 --time=1:59:00 +job ID is XXXX +# Get a shell in the compute node (if using SLURM) +$ srun --jobid XXXX --overlap --pty /bin/bash +# Now you are inside the compute node + +# On JSC, you may need to load some modules... +ml --force purge +ml Stages/2024 GCC OpenMPI CUDA/12 MPI-settings/CUDA Python HDF5 PnetCDF libaio mpi4py + +# ...before activating the Python environment (adapt this to your env name/path) +source ../../../envAI_hdfml/bin/activate +``` + +To launch the training with torch DDP use: + +```bash +torchrun --standalone --nnodes=1 --nproc-per-node=gpu train.py -s ddp + +# Optional -- from a SLURM login node: +srun --jobid XXXX --ntasks-per-node=1 torchrun --standalone --nnodes=1 --nproc-per-node=gpu train.py -s ddp +``` + +To launch the training with Microsoft DeepSpeed use: + +```bash +deepspeed train.py -s deepspeed --deepspeed + +# Optional -- from a SLURM login node: +srun --jobid XXXX --ntasks-per-node=1 deepspeed train.py -s deepspeed --deepspeed +``` + +To launch the training with Horovod use: + +> [!NOTE] +> NOTE: Assuming 4 GPUs are available. + +If your setup has a different number of GPUs, change the `-np 4 -H localhost:4` part. + +> [!WARNING] +> To use `horovodrun`, make sure that `mpirun` is available in your environment. Otherwise +> you cannot use Horovod in interactive mode. + +```bash +# Assuming 4 GPUs are available (-np=4) +horovodrun -np 4 -H localhost:4 train.py -s horovod + +# Optional -- from a SLURM login node: +srun --jobid XXXX --ntasks-per-node=1 horovodrun -np 4 -H localhost:4 python -u train.py -s horovod +``` + +## Distributed training with SLURM (batch mode) + +Each distributed strategy has its own SLURM job script, which +should be used to run it: + +If you want to distribute the code in `train.py` with **torch DDP**, run from terminal: + +```bash +export DIST_MODE="ddp" +export RUN_NAME="ddp-itwinai" +export TRAINING_CMD="train.py -s ddp" +export PYTHON_VENV="../../../envAI_hdfml" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh +``` + +If you want to distribute the code in `train.py` with **DeepSpeed**, run from terminal: + +```bash +export DIST_MODE="deepspeed" +export RUN_NAME="deepspeed-itwinai" +export TRAINING_CMD="train.py -s deepspeed" +export PYTHON_VENV="../../../envAI_hdfml" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh +``` + +If you want to distribute the code in `train.py` with **Horovod**, run from terminal: + +```bash +export DIST_MODE="deepspeed" +export RUN_NAME="deepspeed-itwinai" +export TRAINING_CMD="train.py -s deepspeed" +export PYTHON_VENV="../../../envAI_hdfml" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh +``` + +You can run all of them with: + +```bash +bash runall.sh +``` diff --git a/tutorials/distributed-ml/torch-tutorial-0-basics/runall.sh b/tutorials/distributed-ml/torch-tutorial-0-basics/runall.sh new file mode 100644 index 00000000..48a8f1e0 --- /dev/null +++ b/tutorials/distributed-ml/torch-tutorial-0-basics/runall.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Python virtual environment +PYTHON_VENV="../../../envAI_hdfml" + +# Clear SLURM logs (*.out and *.err files) +rm -rf logs_slurm +mkdir logs_slurm +rm -rf logs_torchrun + +# DDP itwinai +DIST_MODE="ddp" +RUN_NAME="ddp-itwinai" +TRAINING_CMD="train.py -s ddp" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + +# DeepSpeed itwinai +DIST_MODE="deepspeed" +RUN_NAME="deepspeed-itwinai" +TRAINING_CMD="train.py -s deepspeed" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + +# Horovod itwinai +DIST_MODE="horovod" +RUN_NAME="horovod-itwinai" +TRAINING_CMD="train.py -s horovod" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh \ No newline at end of file diff --git a/tutorials/distributed-ml/torch-tutorial-0-basics/slurm.sh b/tutorials/distributed-ml/torch-tutorial-0-basics/slurm.sh new file mode 100644 index 00000000..c53e3da5 --- /dev/null +++ b/tutorials/distributed-ml/torch-tutorial-0-basics/slurm.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +# SLURM jobscript for JSC systems + +# Job configuration +#SBATCH --job-name=distributed_training +#SBATCH --account=intertwin +#SBATCH --mail-user= +#SBATCH --mail-type=ALL +#SBATCH --output=job.out +#SBATCH --error=job.err +#SBATCH --time=00:30:00 + +# Resources allocation +#SBATCH --partition=batch +#SBATCH --nodes=2 +#SBATCH --gpus-per-node=4 +#SBATCH --cpus-per-gpu=4 +#SBATCH --exclusive + +# gres options have to be disabled for deepv +#SBATCH --gres=gpu:4 + +# Load environment modules +ml Stages/2024 GCC OpenMPI CUDA/12 MPI-settings/CUDA Python HDF5 PnetCDF libaio mpi4py + +# Job info +echo "DEBUG: TIME: $(date)" +sysN="$(uname -n | cut -f2- -d.)" +sysN="${sysN%%[0-9]*}" +echo "Running on system: $sysN" +echo "DEBUG: EXECUTE: $EXEC" +echo "DEBUG: SLURM_SUBMIT_DIR: $SLURM_SUBMIT_DIR" +echo "DEBUG: SLURM_JOB_ID: $SLURM_JOB_ID" +echo "DEBUG: SLURM_JOB_NODELIST: $SLURM_JOB_NODELIST" +echo "DEBUG: SLURM_NNODES: $SLURM_NNODES" +echo "DEBUG: SLURM_NTASKS: $SLURM_NTASKS" +echo "DEBUG: SLURM_TASKS_PER_NODE: $SLURM_TASKS_PER_NODE" +echo "DEBUG: SLURM_SUBMIT_HOST: $SLURM_SUBMIT_HOST" +echo "DEBUG: SLURMD_NODENAME: $SLURMD_NODENAME" +echo "DEBUG: CUDA_VISIBLE_DEVICES: $CUDA_VISIBLE_DEVICES" +if [ "$DEBUG" = true ] ; then + echo "DEBUG: NCCL_DEBUG=INFO" + export NCCL_DEBUG=INFO +fi +echo + +# Setup env for distributed ML +export CUDA_VISIBLE_DEVICES="0,1,2,3" +export OMP_NUM_THREADS=1 +if [ "$SLURM_CPUS_PER_GPU" -gt 0 ] ; then + export OMP_NUM_THREADS=$SLURM_CPUS_PER_GPU +fi + +# Env vairables check +if [ -z "$DIST_MODE" ]; then + >&2 echo "ERROR: env variable DIST_MODE is not set. Allowed values are 'horovod', 'ddp' or 'deepspeed'" + exit 1 +fi +if [ -z "$RUN_NAME" ]; then + >&2 echo "WARNING: env variable RUN_NAME is not set. It's a way to identify some specific run of an experiment." + RUN_NAME=$DIST_MODE +fi +if [ -z "$TRAINING_CMD" ]; then + >&2 echo "ERROR: env variable TRAINING_CMD is not set. It's the python command to execute." + exit 1 +fi +if [ -z "$PYTHON_VENV" ]; then + >&2 echo "WARNING: env variable PYTHON_VENV is not set. It's the path to a python virtual environment." +else + # Activate Python virtual env + source $PYTHON_VENV/bin/activate +fi + +# Get GPUs info per node +srun --cpu-bind=none --ntasks-per-node=1 bash -c 'echo -e "NODE hostname: $(hostname)\n$(nvidia-smi)\n\n"' + +# Launch training +if [ "$DIST_MODE" == "ddp" ] ; then + echo "DDP training: $TRAINING_CMD" + srun --cpu-bind=none --ntasks-per-node=1 \ + bash -c "torchrun \ + --log_dir='logs_torchrun' \ + --nnodes=$SLURM_NNODES \ + --nproc_per_node=$SLURM_GPUS_PER_NODE \ + --rdzv_id=$SLURM_JOB_ID \ + --rdzv_conf=is_host=\$(((SLURM_NODEID)) && echo 0 || echo 1) \ + --rdzv_backend=c10d \ + --rdzv_endpoint='$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)'i:29500 \ + $TRAINING_CMD" +elif [ "$DIST_MODE" == "deepspeed" ] ; then + echo "DEEPSPEED training: $TRAINING_CMD" + MASTER_ADDR=$(scontrol show hostnames "\$SLURM_JOB_NODELIST" | head -n 1)i + export MASTER_ADDR + export MASTER_PORT=29500 + + srun --cpu-bind=none --ntasks-per-node=$SLURM_GPUS_PER_NODE --cpus-per-task=$SLURM_CPUS_PER_GPU \ + python -u $TRAINING_CMD --deepspeed + + # # Run with deepspeed launcher: set --ntasks-per-node=1 + # # https://www.deepspeed.ai/getting-started/#multi-node-environment-variables + # export NCCL_IB_DISABLE=1 + # export NCCL_SOCKET_IFNAME=eth0 + # nodelist=$(scontrol show hostname $SLURM_NODELIST) + # echo "$nodelist" | sed -e 's/$/ slots=4/' > .hostfile + # # Requires passwordless SSH access among compute node + # srun --cpu-bind=none deepspeed --hostfile=.hostfile $TRAINING_CMD --deepspeed + # rm .hostfile +elif [ "$DIST_MODE" == "horovod" ] ; then + echo "HOROVOD training: $TRAINING_CMD" + srun --cpu-bind=none --ntasks-per-node=$SLURM_GPUS_PER_NODE --cpus-per-task=$SLURM_CPUS_PER_GPU \ + python -u $TRAINING_CMD +else + >&2 echo "ERROR: unrecognized \$DIST_MODE env variable" + exit 1 +fi + diff --git a/tutorials/distributed-ml/torch-tutorial-0-basics/train.py b/tutorials/distributed-ml/torch-tutorial-0-basics/train.py new file mode 100644 index 00000000..7c124ec3 --- /dev/null +++ b/tutorials/distributed-ml/torch-tutorial-0-basics/train.py @@ -0,0 +1,148 @@ +""" +Show how to use DDP, Horovod and DeepSpeed strategies interchangeably +with an extremely simple neural network. +""" +from typing import Dict +import argparse +import time + +import torch +from torch import nn +from torch.utils.data import Dataset + +import horovod.torch as hvd + +from itwinai.torch.distributed import ( + distributed_resources_available, + TorchDistributedStrategy, + TorchDDPStrategy, + HorovodStrategy, + DeepSpeedStrategy, + NonDistributedStrategy +) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument( + "--strategy", "-s", type=str, + choices=['ddp', 'horovod', 'deepspeed'], + default='ddp' + ) + parser.add_argument( + "--shuffle_dataloader", + action=argparse.BooleanOptionalAction + ) + parser.add_argument( + '--batch-size', type=int, default=10, + help='input batch size for training (default: 10)') + + # DeepSpeed: needs to be removed + import deepspeed + parser.add_argument('--local_rank', type=int, default=-1, + help='local rank passed from distributed launcher') + parser = deepspeed.add_config_arguments(parser) + args = parser.parse_args() + return args + + +class UniformRndDataset(Dataset): + """Dummy torch dataset.""" + + def __init__(self, x_size: int, y_size: int, len: int = 100): + super().__init__() + self.x_size = x_size + self.y_size = y_size + self.len = len + + def __len__(self): + return self.len + + def __getitem__(self, index): + return torch.rand(self.x_size), torch.rand(self.y_size) + + +def training_fn( + args: argparse.Namespace, + strategy: TorchDistributedStrategy, + distribute_kwargs: Dict +) -> int: + """Dummy training function.""" + strategy.init() + + # Local model + model = nn.Linear(3, 4) + optim = torch.optim.Adam(model.parameters(), lr=1e-3) + loss_fn = nn.MSELoss() + # Distributed model + model, optim, lr_sched = strategy.distributed( + model, optim, lr_scheduler=None, **distribute_kwargs + ) + + # Data + train_set = UniformRndDataset(x_size=3, y_size=4) + # Distributed dataloader + train_loader = strategy.create_dataloader( + train_set, batch_size=args.batch_size, num_workers=1) + + # Device allocated for this worker + device = strategy.device() + + for epoch in range(2): + for (x, y) in train_loader: + # print(f"tensor to cuda:{device}") + x = x.to(device) + y = y.to(device) + + optim.zero_grad() + + y_pred = model(x) + + loss = loss_fn(y_pred, y) + loss.backward() + + optim.step() + + if strategy.is_main_worker: + print(f"Loss [epoch={epoch}]: {loss.item()}") + # print(f"NNLoss [epoch={epoch}]: {loss.item()}") + + # Update scheduler + if lr_sched: + lr_sched.step() + + time.sleep(1) + print(f" - TRAINING FINISHED") + strategy.clean_up() + return 123 + + +if __name__ == "__main__": + + args = parse_args() + + # Instantiate Strategy + if not distributed_resources_available(): + print("WARNING: falling back to non-distributed strategy.") + strategy = NonDistributedStrategy() + distribute_kwargs = {} + elif args.strategy == 'ddp': + strategy = TorchDDPStrategy(backend='nccl') + distribute_kwargs = {} + elif args.strategy == 'horovod': + strategy = HorovodStrategy() + distribute_kwargs = dict( + compression=hvd.Compression.none, + op=hvd.Average, + gradient_predivide_factor=1.0 + ) + elif args.strategy == 'deepspeed': + strategy = DeepSpeedStrategy(backend='nccl') + distribute_kwargs = dict( + config_params=dict(train_micro_batch_size_per_gpu=args.batch_size) + ) + else: + raise NotImplementedError( + f"Strategy {args.strategy} is not recognized/implemented.") + # Launch distributed training + training_fn(args, strategy, distribute_kwargs) diff --git a/tutorials/distributed-ml/torch-tutorial-1-mnist/README.md b/tutorials/distributed-ml/torch-tutorial-1-mnist/README.md new file mode 100644 index 00000000..70178f0d --- /dev/null +++ b/tutorials/distributed-ml/torch-tutorial-1-mnist/README.md @@ -0,0 +1,79 @@ +# Tutorial: distributed strategies for PyTorch model trained on MNIST dataset + +In this tutorial we show how to use torch `DistributedDataParallel` (DDP), Horovod and +DeepSpeed from the same client code. +Note that the environment is tested on the HDFML system at JSC. For other systems, +the module versions might need change accordingly. + +## Setup + +First, from the root of this repository, build the environment containing +pytorch, horovod and deepspeed. You can *try* with: + +```bash +# Creates a Python venv called envAI_hdfml +make torch-gpu-jsc +``` + +Before launching training, since on JSC's compute nodes there is not internet connection, +you need to download the dataset before while on the login lode: + +```bash +source ../../../envAI_hdfml/bin/activate +python train.py --download-only +``` + +This command creates a local folder called "MNIST" with the dataset. + +## Distributed training + +Each distributed strategy has its own SLURM job script, which +should be used to run it: + +If you want to distribute the code in `train.py` with **torch DDP**, run from terminal: + +```bash +export DIST_MODE="ddp" +export RUN_NAME="ddp-itwinai" +export TRAINING_CMD="train.py -s ddp -c config.yaml" +export PYTHON_VENV="../../../envAI_hdfml" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh +``` + +If you want to distribute the code in `train.py` with **DeepSpeed**, run from terminal: + +```bash +export DIST_MODE="deepspeed" +export RUN_NAME="deepspeed-itwinai" +export TRAINING_CMD="train.py -s deepspeed -c config.yaml" +export PYTHON_VENV="../../../envAI_hdfml" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh +``` + +If you want to distribute the code in `train.py` with **Horovod**, run from terminal: + +```bash +export DIST_MODE="horovod" +export RUN_NAME="horovod-itwinai" +export TRAINING_CMD="train.py -s horovod -c config.yaml" +export PYTHON_VENV="../../../envAI_hdfml" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh +``` + +You can run all of them with: + +```bash +bash runall.sh +``` diff --git a/tutorials/distributed-ml/torch-tutorial-1-mnist/config.yaml b/tutorials/distributed-ml/torch-tutorial-1-mnist/config.yaml new file mode 100644 index 00000000..331d6d04 --- /dev/null +++ b/tutorials/distributed-ml/torch-tutorial-1-mnist/config.yaml @@ -0,0 +1,28 @@ +# Data and logging +data_dir: ./ +log_int: 10 +verbose: True +restart_int: 10 +download_only: False +dataset_replication: 10 +shuff: False +nworker: 4 # num workers dataloader +prefetch: 2 + +# Model +batch_size: 64 +epochs: 2 +lr: 0.001 +momentum: 0.5 + +# Reproducibility +rnd_seed: 10 + +# Distributed ML +backend: nccl # ignored when using Horovod + +# Horovod: ignored when NOT using Horovod +fp16_allreduce: False +use_adasum: False +gradient_predivide_factor: 1.0 + diff --git a/tutorials/distributed-ml/torch-tutorial-1-mnist/runall.sh b/tutorials/distributed-ml/torch-tutorial-1-mnist/runall.sh new file mode 100644 index 00000000..5a89b4fe --- /dev/null +++ b/tutorials/distributed-ml/torch-tutorial-1-mnist/runall.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Python virtual environment +PYTHON_VENV="../../../envAI_hdfml" + +# Clear SLURM logs (*.out and *.err files) +rm -rf logs_slurm +mkdir logs_slurm +rm -rf logs_torchrun + +# DDP itwinai +DIST_MODE="ddp" +RUN_NAME="ddp-itwinai" +TRAINING_CMD="train.py -s ddp -c config.yaml" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + +# DeepSpeed itwinai +DIST_MODE="deepspeed" +RUN_NAME="deepspeed-itwinai" +TRAINING_CMD="train.py -s deepspeed -c config.yaml" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + +# Horovod itwinai +DIST_MODE="horovod" +RUN_NAME="horovod-itwinai" +TRAINING_CMD="train.py -s horovod -c config.yaml" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh \ No newline at end of file diff --git a/tutorials/distributed-ml/torch-tutorial-1-mnist/slurm.sh b/tutorials/distributed-ml/torch-tutorial-1-mnist/slurm.sh new file mode 100644 index 00000000..3eef38ae --- /dev/null +++ b/tutorials/distributed-ml/torch-tutorial-1-mnist/slurm.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +# SLURM jobscript for JSC systems + +# Job configuration +#SBATCH --job-name=distributed_training +#SBATCH --account=intertwin +#SBATCH --mail-user= +#SBATCH --mail-type=ALL +#SBATCH --output=job.out +#SBATCH --error=job.err +#SBATCH --time=00:30:00 + +# Resources allocation +#SBATCH --partition=batch +#SBATCH --nodes=2 +#SBATCH --gpus-per-node=4 +#SBATCH --cpus-per-gpu=4 +#SBATCH --exclusive + +# gres options have to be disabled for deepv +#SBATCH --gres=gpu:4 + +# Load environment modules +ml Stages/2024 GCC OpenMPI CUDA/12 MPI-settings/CUDA Python HDF5 PnetCDF libaio mpi4py + +# Job info +echo "DEBUG: TIME: $(date)" +sysN="$(uname -n | cut -f2- -d.)" +sysN="${sysN%%[0-9]*}" +echo "Running on system: $sysN" +echo "DEBUG: EXECUTE: $EXEC" +echo "DEBUG: SLURM_SUBMIT_DIR: $SLURM_SUBMIT_DIR" +echo "DEBUG: SLURM_JOB_ID: $SLURM_JOB_ID" +echo "DEBUG: SLURM_JOB_NODELIST: $SLURM_JOB_NODELIST" +echo "DEBUG: SLURM_NNODES: $SLURM_NNODES" +echo "DEBUG: SLURM_NTASKS: $SLURM_NTASKS" +echo "DEBUG: SLURM_TASKS_PER_NODE: $SLURM_TASKS_PER_NODE" +echo "DEBUG: SLURM_SUBMIT_HOST: $SLURM_SUBMIT_HOST" +echo "DEBUG: SLURMD_NODENAME: $SLURMD_NODENAME" +echo "DEBUG: CUDA_VISIBLE_DEVICES: $CUDA_VISIBLE_DEVICES" +if [ "$DEBUG" = true ] ; then + echo "DEBUG: NCCL_DEBUG=INFO" + export NCCL_DEBUG=INFO +fi +echo + +# Setup env for distributed ML +export CUDA_VISIBLE_DEVICES="0,1,2,3" +export OMP_NUM_THREADS=1 +if [ "$SLURM_CPUS_PER_GPU" -gt 0 ] ; then + export OMP_NUM_THREADS=$SLURM_CPUS_PER_GPU +fi + +# Env vairables check +if [ -z "$DIST_MODE" ]; then + >&2 echo "ERROR: env variable DIST_MODE is not set. Allowed values are 'horovod', 'ddp' or 'deepspeed'" + exit 1 +fi +if [ -z "$RUN_NAME" ]; then + >&2 echo "WARNING: env variable RUN_NAME is not set. It's a way to identify some specific run of an experiment." + RUN_NAME=$DIST_MODE +fi +if [ -z "$TRAINING_CMD" ]; then + >&2 echo "ERROR: env variable TRAINING_CMD is not set. It's the python command to execute." + exit 1 +fi +if [ -z "$PYTHON_VENV" ]; then + >&2 echo "WARNING: env variable PYTHON_VENV is not set. It's the path to a python virtual environment." +else + # Activate Python virtual env + source $PYTHON_VENV/bin/activate +fi + +# Get GPUs info per node +srun --cpu-bind=none --ntasks-per-node=1 bash -c 'echo -e "NODE hostname: $(hostname)\n$(nvidia-smi)\n\n"' + +# Launch training +if [ "$DIST_MODE" == "ddp" ] ; then + echo "DDP training: $TRAINING_CMD" + srun --cpu-bind=none --ntasks-per-node=1 \ + bash -c "torchrun \ + --log_dir='logs_torchrun' \ + --nnodes=$SLURM_NNODES \ + --nproc_per_node=$SLURM_GPUS_PER_NODE \ + --rdzv_id=$SLURM_JOB_ID \ + --rdzv_conf=is_host=\$(((SLURM_NODEID)) && echo 0 || echo 1) \ + --rdzv_backend=c10d \ + --rdzv_endpoint='$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)'i:29500 \ + $TRAINING_CMD" +elif [ "$DIST_MODE" == "deepspeed" ] ; then + echo "DEEPSPEED training: $TRAINING_CMD" + MASTER_ADDR=$(scontrol show hostnames "\$SLURM_JOB_NODELIST" | head -n 1)i + export MASTER_ADDR + export MASTER_PORT=29500 + + srun --cpu-bind=none --ntasks-per-node=$SLURM_GPUS_PER_NODE --cpus-per-task=$SLURM_CPUS_PER_GPU \ + python -u $TRAINING_CMD --deepspeed + + # # Run with deepspeed launcher: set --ntasks-per-node=1 + # # https://www.deepspeed.ai/getting-started/#multi-node-environment-variables + # export NCCL_IB_DISABLE=1 + # export NCCL_SOCKET_IFNAME=eth0 + # nodelist=$(scontrol show hostname $SLURM_NODELIST) + # echo "$nodelist" | sed -e 's/$/ slots=4/' > .hostfile + # # Requires passwordless SSH access among compute node + # srun --cpu-bind=none deepspeed --hostfile=.hostfile $TRAINING_CMD --deepspeed + # rm .hostfile +elif [ "$DIST_MODE" == "horovod" ] ; then + echo "HOROVOD training: $TRAINING_CMD" + srun --cpu-bind=none --ntasks-per-node=$SLURM_GPUS_PER_NODE --cpus-per-task=$SLURM_CPUS_PER_GPU \ + python -u $TRAINING_CMD +else + >&2 echo "ERROR: unrecognized \$DIST_MODE env variable" + exit 1 +fi diff --git a/tutorials/distributed-ml/torch-tutorial-1-mnist/train.py b/tutorials/distributed-ml/torch-tutorial-1-mnist/train.py new file mode 100644 index 00000000..809480dd --- /dev/null +++ b/tutorials/distributed-ml/torch-tutorial-1-mnist/train.py @@ -0,0 +1,426 @@ +""" +Show how to use DDP, Horovod and DeepSpeed strategies interchangeably +with a simple neural network trained on MNIST dataset. +""" +from typing import Tuple +import argparse +import sys +import time +from timeit import default_timer as timer + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torchvision import datasets, transforms +from torch.utils.data import Dataset + +import horovod.torch as hvd + +import deepspeed + +from itwinai.torch.distributed import ( + distributed_resources_available, + TorchDistributedStrategy, + TorchDDPStrategy, + HorovodStrategy, + DeepSpeedStrategy, + NonDistributedStrategy +) +from itwinai.parser import ArgumentParser as ItAIArgumentParser +from itwinai.torch.reproducibility import ( + seed_worker, set_seed +) + + +def parse_params() -> argparse.Namespace: + """ + Parse CLI args, which can also be loaded from a configuration file + using the --config flag: + + >>> train.py --strategy ddp --config config.yaml + """ + parser = ItAIArgumentParser(description='PyTorch MNIST Example') + + # Distributed ML strategy + parser.add_argument( + "--strategy", "-s", type=str, + choices=['ddp', 'horovod', 'deepspeed'], + default='ddp' + ) + + # Data and logging + parser.add_argument('--data-dir', default='./', + help=('location of the training dataset in the local ' + 'filesystem')) + parser.add_argument('--log-int', type=int, default=10, + help='log interval per training') + parser.add_argument('--verbose', + action=argparse.BooleanOptionalAction, + help='Print parsed arguments') + parser.add_argument('--restart-int', type=int, default=10, + help='restart interval per epoch (default: 10)') + parser.add_argument('--download-only', + action=argparse.BooleanOptionalAction, + help='Download dataset and exit') + parser.add_argument('--dataset-replication', type=int, default=100, + help='concatenate MNIST to this factor (default: 100)') + parser.add_argument('--shuff', action='store_true', default=False, + help='shuffle dataset (default: False)') + parser.add_argument('--nworker', type=int, default=0, + help=('number of workers in DataLoader (default: 0 -' + ' only main)')) + parser.add_argument('--prefetch', type=int, default=2, + help='prefetch data in DataLoader (default: 2)') + + # Model + parser.add_argument('--batch-size', type=int, default=64, + help='input batch size for training (default: 64)') + parser.add_argument('--epochs', type=int, default=10, + help='number of epochs to train (default: 10)') + parser.add_argument('--lr', type=float, default=0.01, + help='learning rate (default: 0.01)') + parser.add_argument('--momentum', type=float, default=0.5, + help='momentum in SGD optimizer (default: 0.5)') + + # Reproducibility + parser.add_argument('--rnd-seed', type=int, default=0, + help='seed integer for reproducibility (default: 0)') + + # Distributed ML + parser.add_argument('--backend', type=str, default='nccl', + help='backend for parrallelisation (default: nccl)') + parser.add_argument('--local_rank', type=int, default=-1, + help='local rank passed from distributed launcher') + + # Horovod: ignored when not using Horovod + parser.add_argument('--fp16-allreduce', action='store_true', default=False, + help='use fp16 compression during allreduce') + parser.add_argument('--use-adasum', action='store_true', default=False, + help='use adasum algorithm to do reduction') + parser.add_argument('--gradient-predivide-factor', type=float, default=1.0, + help=('apply gradient pre-divide factor in optimizer ' + '(default: 1.0)')) + + # DeepSpeed + parser = deepspeed.add_config_arguments(parser) + args = parser.parse_args() + + if args.verbose: + args_list = [f"{key}: {val}" for key, val in args.items()] + print("PARSED ARGS:\n", '\n'.join(args_list)) + + return args + + +class Net(nn.Module): + """ + Simple neural network classifier for MNIST images. + """ + + def __init__(self): + super(Net, self).__init__() + self.conv1 = nn.Conv2d(1, 10, kernel_size=5) + self.conv2 = nn.Conv2d(10, 20, kernel_size=5) + self.conv2_drop = nn.Dropout2d() + self.fc1 = nn.Linear(320, 50) + self.fc2 = nn.Linear(50, 10) + + def forward(self, x): + x = F.relu(F.max_pool2d(self.conv1(x), 2)) + x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2)) + x = x.view(-1, 320) + x = F.relu(self.fc1(x)) + x = F.dropout(x, training=self.training) + x = self.fc2(x) + return F.log_softmax(x, dim=-1) + + +def train( + model, train_loader, optimizer, epoch, + strategy: TorchDistributedStrategy, args +): + """ + Training function, representing an epoch. + """ + model.train() + t_list = [] + loss_acc = 0 + if strategy.is_main_worker: + print("\n") + for batch_idx, (data, target) in enumerate(train_loader): + t = timer() + data = data.to(strategy.device()) + target = target.to(strategy.device()) + optimizer.zero_grad() + output = model(data) + loss = F.nll_loss(output, target) + loss.backward() + optimizer.step() + if (strategy.is_main_worker and args.log_int > 0 + and batch_idx % args.log_int == 0): + dl_size = len(train_loader.dataset)//strategy.global_world_size() + print( + f'Train epoch: {epoch} ' + f'[{batch_idx * len(data)}/{dl_size} ' + f'({100.0 * batch_idx / len(train_loader):.0f}%)]\t\t' + f'Loss: {loss.item():.6f}') + t_list.append(timer() - t) + loss_acc += loss.item() + if strategy.is_main_worker: + print('TIMER: train time', sum(t_list) / len(t_list), 's') + return loss_acc + + +def test(model, test_loader, strategy: TorchDistributedStrategy): + """ + Model validation. + """ + model.eval() + test_loss = 0 + correct = 0 + with torch.no_grad(): + for data, target in test_loader: + data = data.to(strategy.device()) + target = target.to(strategy.device()) + output = model(data) + # Sum up batch loss + test_loss += F.nll_loss(output, target, reduction="sum").item() + # Get the index of the max log-probability + pred = output.argmax(dim=1, keepdim=True) + correct += pred.eq(target.view_as(pred)).sum().item() + test_loss /= len(test_loader.dataset) + if strategy.is_main_worker: + dl_size = len(test_loader.dataset)//strategy.global_world_size() + print( + f'Test set: average loss: {test_loss:.4f}\t' + f'accurate samples: {correct}/{dl_size}') + acc_test = ( + 100.0 * correct * strategy.global_world_size() + / len(test_loader.dataset) + ) + return acc_test + + +def download_mnist(): + """ + Use built-in torch datasets functions to pull MNIST dataset. + """ + + _ = datasets.MNIST( + args.data_dir, train=True, download=True, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)) + ])) + _ = datasets.MNIST( + args.data_dir, train=False, download=True, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)) + ])) + + +def mnist_dataset(dataset_replication: int = 1) -> Tuple[Dataset, Dataset]: + """Load MNIST train and test datasets, replicating them. + + Args: + dataset_replication (int): dataset replication factor. Default 1. + + Returns: + Tuple[Dataset, Dataset]: train dataset and test dataset. + """ + replicated_data = [ + datasets.MNIST(args.data_dir, train=True, download=False, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)) + ])) + for _ in range(dataset_replication) + ] + train_dataset = torch.utils.data.ConcatDataset(replicated_data) + + replicated_data = [ + datasets.MNIST(args.data_dir, train=False, download=False, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)) + ])) + for _ in range(dataset_replication) + ] + test_dataset = torch.utils.data.ConcatDataset(replicated_data) + return train_dataset, test_dataset + + +if __name__ == "__main__": + + args = parse_params() + + if args.download_only: + # Download datasets from a location with internet access and exit. + # This is convenient when submitting training jobs to + # a batch system where worker nodes have no internet + # access, like in some HPCs. + download_mnist() + sys.exit() + + # Instantiate Strategy + if not distributed_resources_available(): + print("WARNING: falling back to non-distributed strategy.") + strategy = NonDistributedStrategy() + distribute_kwargs = {} + elif args.strategy == 'ddp': + strategy = TorchDDPStrategy(backend=args.backend) + distribute_kwargs = {} + elif args.strategy == 'horovod': + strategy = HorovodStrategy() + distribute_kwargs = dict( + compression=( + hvd.Compression.fp16 if args.fp16_allreduce + else hvd.Compression.none + ), + op=hvd.Adasum if args.use_adasum else hvd.Average, + gradient_predivide_factor=args.gradient_predivide_factor + ) + elif args.strategy == 'deepspeed': + strategy = DeepSpeedStrategy(backend=args.backend) + distribute_kwargs = dict( + config_params=dict(train_micro_batch_size_per_gpu=args.batch_size) + ) + else: + raise NotImplementedError( + f"Strategy {args.strategy} is not recognized/implemented.") + + # Initialize strategy + strategy.init() + + # Start the timer for profiling + st = timer() + + # Set random seed for reproducibility + torch_prng = set_seed(args.rnd_seed) + + if strategy.is_main_worker: + print('TIMER: initialise:', timer()-st, 's') + print('DEBUG: local ranks:', strategy.local_world_size(), + '/ global ranks:', strategy.global_world_size()) + print('DEBUG: sys.version:', sys.version) + print('DEBUG: args.data_dir:', args.data_dir) + print('DEBUG: args.log_int:', args.log_int) + print('DEBUG: args.nworker:', args.nworker) + print('DEBUG: args.prefetch:', args.prefetch) + print('DEBUG: args.batch_size:', args.batch_size) + print('DEBUG: args.epochs:', args.epochs) + print('DEBUG: args.lr:', args.lr) + print('DEBUG: args.momentum:', args.momentum) + print('DEBUG: args.shuff:', args.shuff) + print('DEBUG: args.rnd_seed:', args.rnd_seed) + print('DEBUG: args.backend:', args.backend) + + # Dataset + train_dataset, test_dataset = mnist_dataset(args.dataset_replication) + # Distributed dataloaders + train_loader = strategy.create_dataloader( + train_dataset, batch_size=args.batch_size, + num_workers=args.nworker, pin_memory=True, + persistent_workers=(args.nworker > 1), + prefetch_factor=args.prefetch, generator=torch_prng, + worker_init_fn=seed_worker + ) + test_loader = strategy.create_dataloader( + test_dataset, batch_size=args.batch_size, + num_workers=args.nworker, pin_memory=True, + persistent_workers=(args.nworker > 1), + prefetch_factor=args.prefetch, generator=torch_prng, + worker_init_fn=seed_worker + ) + + if strategy.is_main_worker: + print('TIMER: read and concat data:', timer()-st, 's') + + # Create CNN model + model = Net().to(strategy.device()) + + # Optimizer + optimizer = torch.optim.SGD( + model.parameters(), lr=args.lr, momentum=args.momentum) + + # Distributed model, optimizer, and scheduler + model, optimizer, _ = strategy.distributed( + model, optimizer, lr_scheduler=None, **distribute_kwargs + ) + + # Start training and test loop + if strategy.is_main_worker: + print('TIMER: broadcast:', timer()-st, 's') + print('\nDEBUG: start training') + print('--------------------------------------------------------') + + et = timer() + start_epoch = 1 + for epoch in range(start_epoch, args.epochs + 1): + lt = timer() + if strategy.is_distributed: + # Inform the sampler that a new epoch started: shuffle + # may be needed + train_loader.sampler.set_epoch(epoch) + test_loader.sampler.set_epoch(epoch) + + # Training + loss_acc = train( + model=model, + train_loader=train_loader, + optimizer=optimizer, + epoch=epoch, + strategy=strategy, + args=args + ) + + # Testing + acc_test = test( + model=model, + test_loader=test_loader, + strategy=strategy + ) + + # Save first epoch timer + if epoch == start_epoch: + first_ep_t = timer()-lt + + # Final epoch + if epoch + 1 == args.epochs: + train_loader.last_epoch = True + test_loader.last_epoch = True + + if strategy.is_main_worker: + print('TIMER: epoch time:', timer()-lt, 's') + print('DEBUG: accuracy:', acc_test, '%') + + if strategy.is_main_worker: + print('\n--------------------------------------------------------') + print('DEBUG: training results:\n') + print('TIMER: first epoch time:', first_ep_t, ' s') + print('TIMER: last epoch time:', timer()-lt, ' s') + print('TIMER: average epoch time:', (timer()-et)/args.epochs, ' s') + print('TIMER: total epoch time:', timer()-et, ' s') + if epoch > 1: + print('TIMER: total epoch-1 time:', + timer()-et-first_ep_t, ' s') + print('TIMER: average epoch-1 time:', + (timer()-et-first_ep_t)/(args.epochs-1), ' s') + print('DEBUG: last accuracy:', acc_test, '%') + if torch.cuda.is_available(): + print('DEBUG: memory req:', + int(torch.cuda.memory_reserved( + strategy.local_rank())/1024/1024), + 'MB') + print('DEBUG: memory summary:\n\n', + torch.cuda.memory_summary(0)) + + print(f'TIMER: final time: {timer()-st} s\n') + + time.sleep(1) + print(f" - TRAINING FINISHED") + + # Clean-up + strategy.clean_up() + sys.exit() diff --git a/tutorials/ml-workflows/basic_components.py b/tutorials/ml-workflows/basic_components.py new file mode 100644 index 00000000..1fca03d8 --- /dev/null +++ b/tutorials/ml-workflows/basic_components.py @@ -0,0 +1,85 @@ +""" +Here we show how to implement component interfaces in a simple way. +""" +from typing import List, Optional, Tuple, Any +from itwinai.components import ( + DataGetter, DataSplitter, Trainer, Saver, monitor_exec +) + + +class MyDataGetter(DataGetter): + def __init__(self, data_size: int, name: Optional[str] = None) -> None: + super().__init__(name) + self.save_parameters(data_size=data_size) + + @monitor_exec + def execute(self) -> List[int]: + """Return a list dataset. + + Returns: + List[int]: dataset + """ + return list(range(self.data_size)) + + +class MyDatasetSplitter(DataSplitter): + @monitor_exec + def execute( + self, + dataset: List[int] + ) -> Tuple[List[int], List[int], List[int]]: + """Splits a list dataset into train, validation and test datasets. + + Args: + dataset (List[int]): input list dataset. + + Returns: + Tuple[List[int], List[int], List[int]]: train, validation, and + test datasets. + """ + train_n = int(len(dataset)*self.train_proportion) + valid_n = int(len(dataset)*self.validation_proportion) + train_set = dataset[:train_n] + vaild_set = dataset[train_n:train_n+valid_n] + test_set = dataset[train_n+valid_n:] + return train_set, vaild_set, test_set + + +class MyTrainer(Trainer): + def __init__(self, lr: float = 1e-3, name: Optional[str] = None) -> None: + super().__init__(name) + self.save_parameters(name=name, lr=lr) + + @monitor_exec + def execute( + self, + train_set: List[int], + vaild_set: List[int], + test_set: List[int] + ) -> Tuple[List[int], List[int], List[int], str]: + """Dummy ML trainer mocking a ML training algorithm. + + Args: + train_set (List[int]): training dataset. + vaild_set (List[int]): validation dataset. + test_set (List[int]): test dataset. + + Returns: + Tuple[List[int], List[int], List[int], str]: train, validation, + test datasets, and trained model. + """ + return train_set, vaild_set, test_set, "my_trained_model" + + +class MySaver(Saver): + @monitor_exec + def execute(self, artifact: Any) -> Any: + """Saves an artifact to disk. + + Args: + artifact (Any): artifact to save (e.g., dataset, model). + + Returns: + Any: input artifact. + """ + return artifact diff --git a/tutorials/ml-workflows/tutorial_0_basic_workflow.py b/tutorials/ml-workflows/tutorial_0_basic_workflow.py new file mode 100644 index 00000000..98861777 --- /dev/null +++ b/tutorials/ml-workflows/tutorial_0_basic_workflow.py @@ -0,0 +1,71 @@ +""" +The most simple workflow that you can write is a sequential pipeline of steps, +where the outputs of a component are fed as input to the following component, +employing a scikit-learn-like Pipeline. + +In itwinai, a step is also called "component" and is implemented by extending +the ``itwinai.components.BaseComponent`` class. Each component implements +the `execute(...)` method, which provides a unified interface to interact with +each component. + +The aim of itwinai components is to provide reusable machine learning best +practices, and some common operations are already encoded in some abstract +components. Some examples are: +- ``DataGetter``: has no input and returns a dataset, collected from somewhere +(e.g., downloaded). +- ``DataSplitter``: splits an input dataset into train, validation and test. +- ``DataPreproc``: perform preprocessing on train, validation, and test +datasets. +- ``Trainer``: trains an ML model and returns the trained model. +- ``Saver``: saved an ML artifact (e.g., dataset, model) to disk. + +In this tutorial you will see how to create new components and how they +are assembled into sequential pipelines. Newly created components are +in a separate file called 'basic_components.py'. +""" +from itwinai.pipeline import Pipeline + +# Import the custom components from file +from basic_components import MyDataGetter, MyDatasetSplitter, MyTrainer + +if __name__ == "__main__": + # Assemble them in a scikit-learn like pipeline + pipeline = Pipeline([ + MyDataGetter(data_size=100), + MyDatasetSplitter( + train_proportion=.5, + validation_proportion=.25, + test_proportion=0.25 + ), + MyTrainer() + ]) + + # Inspect steps + print(pipeline[0]) + print(pipeline[2].name) + print(pipeline[1].train_proportion) + + # Run pipeline + _, _, _, trained_model = pipeline.execute() + print("Trained model: ", trained_model) + + # You can also create a Pipeline from a dict of components, which + # simplifies their retrieval by name + pipeline = Pipeline({ + "datagetter": MyDataGetter(data_size=100), + "splitter": MyDatasetSplitter( + train_proportion=.5, + validation_proportion=.25, + test_proportion=0.25 + ), + "trainer": MyTrainer() + }) + + # Inspect steps + print(pipeline["datagetter"]) + print(pipeline["trainer"].name) + print(pipeline["splitter"].train_proportion) + + # Run pipeline + _, _, _, trained_model = pipeline.execute() + print("Trained model: ", trained_model) diff --git a/tutorials/ml-workflows/tutorial_1_intermediate_workflow.py b/tutorials/ml-workflows/tutorial_1_intermediate_workflow.py new file mode 100644 index 00000000..6604df13 --- /dev/null +++ b/tutorials/ml-workflows/tutorial_1_intermediate_workflow.py @@ -0,0 +1,98 @@ +""" +In the previous tutorial we saw how to create new components and assemble them +into a Pipeline for a simplified workflow execution. The Pipeline executes +the components in the order in which they are given, *assuming* that the +outputs of a component will fit as inputs of the following component. +This is not always true, thus you can use the ``Adapter`` component to +compensate for mismatches. This component allows to define a policy to +rearrange intermediate results between two components. + +Moreover, it is good for reproducibility to keep track of the pipeline +configuration used to achieve some outstanding ML results. It would be a shame +to forget how you achieved state-of-the-art results! + +itwinai allows to export the Pipeline form Python code to configuration file, +to persist both parameters and workflow structure. Exporting to configuration +file assumes that each component class resides in a separate python file, so +that the pipeline configuration is agnostic from the current python script. + +Once the Pipeline has been exported to configuration file (YAML), it can +be executed directly from CLI: + +>>> itwinai exec-pipeline --config my-pipeline.yaml --override nested.key=42 + +The itwinai CLI allows for dynamic override of configuration fields, by means +of nested key notation. Also list indices are supported: + +>>> itwinai exec-pipeline --config my-pipe.yaml --override nested.list.2.0=42 + +""" +import subprocess +from itwinai.pipeline import Pipeline +from itwinai.parser import ConfigParser +from itwinai.components import Adapter + +from basic_components import ( + MyDataGetter, MyDatasetSplitter, MyTrainer, MySaver +) + +if __name__ == "__main__": + + # In this pipeline, the MyTrainer produces 4 elements as output: train, + # validation, test datasets, and trained model. The Adapter selects the + # trained model only, and forwards it to the saver, which expects a single + # item as input. + pipeline = Pipeline([ + MyDataGetter(data_size=100), + MyDatasetSplitter( + train_proportion=.5, + validation_proportion=.25, + test_proportion=0.25 + ), + MyTrainer(), + Adapter(policy=[f"{Adapter.INPUT_PREFIX}-1"]), + MySaver() + ]) + + # Run pipeline + trained_model = pipeline.execute() + print("Trained model: ", trained_model) + print("\n" + "="*50 + "\n") + + # Serialize pipeline to YAML + pipeline.to_yaml("basic_pipeline_example.yaml", "pipeline") + + # Below, we show how to run a pre-existing pipeline stored as + # a configuration file, with the possibility of dynamically + # override some fields + + # Load pipeline from saved YAML (dynamic serialization) + parser = ConfigParser( + config="basic_pipeline_example.yaml", + override_keys={ + "pipeline.init_args.steps.0.init_args.data_size": 200 + } + ) + pipeline = parser.parse_pipeline() + print(f"MyDataGetter's data_size is now: {pipeline.steps[0].data_size}\n") + + # Run parsed pipeline, with new data_size for MyDataGetter + trained_model = pipeline.execute() + print("Trained model (2): ", trained_model) + + # Save new pipeline to YAML file + pipeline.to_yaml("basic_pipeline_example_v2.yaml", "pipeline") + + print("\n" + "="*50 + "\n") + + # Emulate pipeline execution from CLI, with dynamic override of + # pipeline configuration fields + subprocess.run( + ["itwinai", "exec-pipeline", "--config", + "basic_pipeline_example_v2.yaml", + "--override", + "pipeline.init_args.steps.0.init_args.data_size=300", + "--override", + "pipeline.init_args.steps.1.init_args.train_proportion=0.4" + ] + ) diff --git a/tutorials/ml-workflows/tutorial_2_advanced_workflow.py b/tutorials/ml-workflows/tutorial_2_advanced_workflow.py new file mode 100644 index 00000000..fbd159d4 --- /dev/null +++ b/tutorials/ml-workflows/tutorial_2_advanced_workflow.py @@ -0,0 +1,87 @@ +""" +In the first two tutorials we saw how to define simple sequential workflows by +means of the Pipeline object, which feds the outputs of the previous component +as inputs of the following one. +In this tutorial we show how to create more complex workflows, with +non-sequential data flows. Here, components can be arranges as an directed +acyclic graph (DAG). Under the DAG assumption, outputs of each block can be fed +as input potentially to any other component, granting great flexibility to the +experimenter. +The trade-off for improved flexibility is a change in the way we define +configuration files. From now on, it will only be possible to configure the +parameters used by the training script, but not its structure through the +Pipeline. + +itwinai provides a wrapper of jsonarparse's ArgumentParser which supports +configuration files by default. + +To run as usual: +>>> python my_script.py -d 20 --train-prop 0.7 --val-prop 0.2 --lr 1e-5 + +To reuse the parameters saved in a configuration file and override some +parameter (e.g., learning rate): +>>> python my_script.py --config advanced_tutorial_conf.yaml --lr 2e-3 + + +""" +from typing import Any +from itwinai.parser import ArgumentParser +from itwinai.components import Predictor, monitor_exec + +from basic_components import ( + MyDataGetter, MyDatasetSplitter, MyTrainer, MySaver +) + + +class MyEnsemblePredictor(Predictor): + @monitor_exec + def execute(self, dataset, model_ensemble) -> Any: + """ + do some predictions with model on dataset... + """ + return dataset + + +if __name__ == "__main__": + parser = ArgumentParser(description="itwinai advanced workflows tutorial") + parser.add_argument( + "--data-size", "-d", type=int, required=True, + help="Dataset cardinality.") + parser.add_argument( + "--train-prop", type=float, required=True, + help="Train split proportion.") + parser.add_argument( + "--val-prop", type=float, required=True, + help="Validation split proportion.") + parser.add_argument( + "--lr", type=float, help="Training learning rate.") + args = parser.parse_args() + + # Save parsed arguments to configuration file. + # Previous configurations are overwritten, which is not good, + # but the versioning of configuration files is out of the scope + # of this tutorial. + parser.save( + args, "advanced_tutorial_conf.yaml", format='yaml', overwrite=True) + + # Define workflow components + getter = MyDataGetter(data_size=args.data_size) + splitter = MyDatasetSplitter( + train_proportion=args.train_prop, + validation_proportion=args.val_prop, + test_proportion=1-args.train_prop-args.val_prop + ) + trainer1 = MyTrainer(lr=args.lr) + trainer2 = MyTrainer(lr=args.lr) + saver = MySaver() + predictor = MyEnsemblePredictor(model=None) + + # Define ML workflow + dataset = getter.execute() + train_spl, val_spl, test_spl = splitter.execute(dataset) + _, _, _, trained_model1 = trainer1.execute(train_spl, val_spl, test_spl) + _, _, _, trained_model2 = trainer2.execute(train_spl, val_spl, test_spl) + _ = saver.execute(trained_model1) + predictions = predictor.execute(test_spl, [trained_model1, trained_model2]) + print() + print("Predictions: " + str(predictions)) diff --git a/use-cases/3dgan/Dockerfile b/use-cases/3dgan/Dockerfile new file mode 100644 index 00000000..26cc3f29 --- /dev/null +++ b/use-cases/3dgan/Dockerfile @@ -0,0 +1,25 @@ +FROM nvcr.io/nvidia/pytorch:23.09-py3 +# FROM python:3.11 + +WORKDIR /usr/src/app + +# Install itwinai +COPY pyproject.toml ./ +COPY src ./ +RUN pip install --upgrade pip \ + && pip install --no-cache-dir lightning \ + && pip install --no-cache-dir . + +# Add 3DGAN use case files and install additional requirements +COPY use-cases/3dgan/requirements.txt ./ +COPY use-cases/3dgan/* ./ +RUN pip install --no-cache-dir -r requirements.txt + +# Create non-root user +RUN groupadd -g 10001 dotnet \ + && useradd -m -u 10000 -g dotnet dotnet \ + && chown -R dotnet:dotnet /usr/src/app +USER dotnet:dotnet + +# ENTRYPOINT [ "itwinai", "exec-pipeline" ] +# CMD [ "--config", "pipeline.yaml" ] \ No newline at end of file diff --git a/use-cases/3dgan/README.md b/use-cases/3dgan/README.md new file mode 100644 index 00000000..53501e89 --- /dev/null +++ b/use-cases/3dgan/README.md @@ -0,0 +1,257 @@ +# 3DGAN use case + +First of all, from the repository root, create a torch environment: + +```bash +make torch-gpu +``` + +Now, install custom requirements for 3DGAN: + +```bash +micromamba activate ./.venv-pytorch +cd use-cases/3dgan +pip install -r requirements.txt +``` + +**NOTE**: Python commands below assumed to be executed from within the +micromamba virtual environment. + +## Training + +Launch training using `itwinai` and the training configuration: + +```bash +cd use-cases/3dgan +itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline + +# Or better: +micromamba run -p ../../.venv-pytorch/ torchrun --nproc_per_node gpu \ + itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline +``` + +To visualize the logs with MLFLow, if you set a local path as tracking URI, +run the following in the terminal: + +```bash +micromamba run -p ../../.venv-pytorch mlflow ui --backend-store-uri LOCAL_TRACKING_URI +``` + +And select the "3DGAN" experiment. + +## Inference + +Disclaimer: the following is preliminary and not 100% ML/scientifically sound. + +1. As inference dataset we can reuse training/validation dataset, +for instance the one downloaded from Google Drive folder: if the +dataset root folder is not present, the dataset will be downloaded. +The inference dataset is a set of H5 files stored inside `exp_data` +sub-folders: + + ```text + ├── exp_data + │ ├── data + | │ ├── file_0.h5 + | │ ├── file_1.h5 + ... + | │ ├── file_N.h5 + ``` + +2. As model, if a pre-trained checkpoint is not available, +we can create a dummy version of it with: + + ```bash + python create_inference_sample.py + ``` + +3. Run inference command. This will generate a `3dgan-generated-data` +folder containing generated particle traces in form of torch tensors +(.pth files) and 3D scatter plots (.jpg images). + + ```bash + itwinai exec-pipeline --config config.yaml --pipe-key inference_pipeline + ``` + +The inference execution will produce a folder called +`3dgan-generated-data` containing +generated 3D particle trajectories (overwritten if already +there). Each generated 3D image is stored both as a +torch tensor (.pth) and 3D scatter plot (.jpg): + +```text +├── 3dgan-generated-data +| ├── energy=1.296749234199524&angle=1.272539496421814.pth +| ├── energy=1.296749234199524&angle=1.272539496421814.jpg +... +| ├── energy=1.664689540863037&angle=1.4906378984451294.pth +| ├── energy=1.664689540863037&angle=1.4906378984451294.jpg +``` + +However, if `aggregate_predictions` in the `ParticleImagesSaver` step is set to `True`, +only one pickled file will be generated inside `3dgan-generated-data` folder. +Notice that multiple inference calls will create new files under `3dgan-generated-data` folder. + +With fields overriding: + +```bash +# Override variables +export CERN_DATA_ROOT="../.." # data root +export TMP_DATA_ROOT=$CERN_DATA_ROOT +export CERN_CODE_ROOT="." # where code and configuration are stored +export MAX_DATA_SAMPLES=20000 # max dataset size +export BATCH_SIZE=1024 # increase to fill up GPU memory +export NUM_WORKERS_DL=4 # num worker processes used by the dataloader to pre-fetch data +export AGGREGATE_PREDS="true" # write predictions in a single file +export ACCELERATOR="gpu" # choose "cpu" or "gpu" +export STRATEGY="auto" # distributed strategy +export DEVICES="0," # GPU devices list + + +itwinai exec-pipeline --print-config --config $CERN_CODE_ROOT/config.yaml \ + --pipe-key inference_pipeline \ + -o dataset_location=$CERN_DATA_ROOT/exp_data \ + -o logs_dir=$TMP_DATA_ROOT/ml_logs/mlflow_logs \ + -o distributed_strategy=$STRATEGY \ + -o devices=$DEVICES \ + -o hw_accelerators=$ACCELERATOR \ + -o checkpoints_path=\\$TMP_DATA_ROOT/checkpoints \ + -o inference_model_uri=$CERN_CODE_ROOT/3dgan-inference.pth \ + -o max_dataset_size=$MAX_DATA_SAMPLES \ + -o batch_size=$BATCH_SIZE \ + -o num_workers_dataloader=$NUM_WORKERS_DL \ + -o inference_results_location=$TMP_DATA_ROOT/3dgan-generated-data \ + -o aggregate_predictions=$AGGREGATE_PREDS +``` + +### Docker image + +Build from project root with + +```bash +# Local +docker buildx build -t itwinai:0.0.1-3dgan-0.1 -f use-cases/3dgan/Dockerfile . + +# Ghcr.io +docker buildx build -t ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.1 -f use-cases/3dgan/Dockerfile . +docker push ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.1 +``` + +You can run inference from wherever a sample of H5 files is available +(folder called `exp_data/`'): + +```text +├── $PWD +| ├── exp_data +| │ ├── data +| | │ ├── file_0.h5 +| | │ ├── file_1.h5 +... +| | │ ├── file_N.h5 +``` + +```bash +docker run -it --rm --name running-inference -v "$PWD":/tmp/data ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.1 +``` + +This command will store the results in a folder called `3dgan-generated-data`: + +```text +├── $PWD +| ├── 3dgan-generated-data +| │ ├── energy=1.296749234199524&angle=1.272539496421814.pth +| │ ├── energy=1.296749234199524&angle=1.272539496421814.jpg +... +| │ ├── energy=1.664689540863037&angle=1.4906378984451294.pth +| │ ├── energy=1.664689540863037&angle=1.4906378984451294.jpg +``` + +To override fields in the configuration file at runtime, you can use the `-o` +flag. Example: `-o path.to.config.element=NEW_VALUE`. + +Please find a complete exampled below, showing how to override default configurations +by setting some env variables: + +```bash +# Override variables +export CERN_DATA_ROOT="/usr/data" +export CERN_CODE_ROOT="/usr/src/app" +export MAX_DATA_SAMPLES=10 # max dataset size +export BATCH_SIZE=64 # increase to fill up GPU memory +export NUM_WORKERS_DL=4 # num worker processes used by the dataloader to pre-fetch data +export AGGREGATE_PREDS="true" # write predictions in a single file +export ACCELERATOR="gpu" # choose "cpu" or "gpu" + +docker run -it --rm --name running-inference \ +-v "$PWD":/usr/data ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.1 \ +/bin/bash -c "itwinai exec-pipeline \ + --print-config --config $CERN_CODE_ROOT/config.yaml \ + --pipe-key inference_pipeline \ + -o dataset_location=$CERN_DATA_ROOT/exp_data \ + -o logs_dir=$TMP_DATA_ROOT/ml_logs/mlflow_logs \ + -o distributed_strategy=$STRATEGY \ + -o devices=$DEVICES \ + -o hw_accelerators=$ACCELERATOR \ + -o checkpoints_path=\\$TMP_DATA_ROOT/checkpoints \ + -o inference_model_uri=$CERN_CODE_ROOT/3dgan-inference.pth \ + -o max_dataset_size=$MAX_DATA_SAMPLES \ + -o batch_size=$BATCH_SIZE \ + -o num_workers_dataloader=$NUM_WORKERS_DL \ + -o inference_results_location=$TMP_DATA_ROOT/3dgan-generated-data \ + -o aggregate_predictions=$AGGREGATE_PREDS " +``` + +#### How to fully exploit GPU resources + +Keeping the example above as reference, increase the value of `BATCH_SIZE` as much as possible +(just below "out of memory" errors). Also, make sure that `ACCELERATOR="gpu"`. Also, make sure +to use a dataset large enough by changing the value of `MAX_DATA_SAMPLES` to collect meaningful +performance data. Consider that each H5 file contains roughly 5k items, thus setting +`MAX_DATA_SAMPLES=10000` should be enough to use all items in each input H5 file. + +You can try: + +```bash +export MAX_DATA_SAMPLES=10000 # max dataset size +export BATCH_SIZE=1024 # increase to fill up GPU memory +export ACCELERATOR="gpu +``` + +### Singularity + +Run Docker container with Singularity: + +```bash +singularity run --nv -B "$PWD":/usr/data docker://ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.1 /bin/bash -c \ +"cd /usr/src/app && itwinai exec-pipeline --config config.yaml --pipe-key inference_pipeline" +``` + +Example with overrides (as above for Docker): + +```bash +# Override variables +export CERN_DATA_ROOT="/usr/data" +export CERN_CODE_ROOT="/usr/src/app" +export MAX_DATA_SAMPLES=10 # max dataset size +export BATCH_SIZE=64 # increase to fill up GPU memory +export NUM_WORKERS_DL=4 # num worker processes used by the dataloader to pre-fetch data +export AGGREGATE_PREDS="true" # write predictions in a single file +export ACCELERATOR="gpu" # choose "cpu" or "gpu" + +singularity run --nv -B "$PWD":/usr/data docker://ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.1 /bin/bash -c \ +"cd /usr/src/app && itwinai exec-pipeline \ + --print-config --config $CERN_CODE_ROOT/config.yaml \ + --pipe-key inference_pipeline \ + -o dataset_location=$CERN_DATA_ROOT/exp_data \ + -o logs_dir=$TMP_DATA_ROOT/ml_logs/mlflow_logs \ + -o distributed_strategy=$STRATEGY \ + -o devices=$DEVICES \ + -o hw_accelerators=$ACCELERATOR \ + -o checkpoints_path=\\$TMP_DATA_ROOT/checkpoints \ + -o inference_model_uri=$CERN_CODE_ROOT/3dgan-inference.pth \ + -o max_dataset_size=$MAX_DATA_SAMPLES \ + -o batch_size=$BATCH_SIZE \ + -o num_workers_dataloader=$NUM_WORKERS_DL \ + -o inference_results_location=$TMP_DATA_ROOT/3dgan-generated-data \ + -o aggregate_predictions=$AGGREGATE_PREDS " +``` diff --git a/use-cases/3dgan/__init__.py b/use-cases/3dgan/__init__.py new file mode 100644 index 00000000..079a8283 --- /dev/null +++ b/use-cases/3dgan/__init__.py @@ -0,0 +1 @@ +## This file can be empty but must be present \ No newline at end of file diff --git a/use-cases/3dgan/config.yaml b/use-cases/3dgan/config.yaml new file mode 100644 index 00000000..d23288d5 --- /dev/null +++ b/use-cases/3dgan/config.yaml @@ -0,0 +1,208 @@ +# Main configurations +dataset_location: exp_data/ +dataset_url: https://drive.google.com/drive/folders/1uPpz0tquokepptIfJenTzGpiENfo2xRX +hw_accelerators: auto +distributed_strategy: auto #ddp_find_unused_parameters_true +devices: auto #[0] +checkpoints_path: checkpoints +logs_dir: ml_logs +mlflow_tracking_uri: https://131.154.99.166.myip.cloud.infn.it +batch_size: 4 +num_workers_dataloader: 0 +max_epochs: 2 +max_dataset_size: 48 +random_seed: 4231162351 +inference_results_location: 3dgan-generated-data/ +inference_model_uri: 3dgan-inference.pth +aggregate_predictions: false + +# Dataloading step is common and can be reused +dataloading_step: + class_path: dataloader.Lightning3DGANDownloader + init_args: + data_path: ${dataset_location} # Set to null to skip dataset download + data_url: ${dataset_url} + +# AI workflows +training_pipeline: + class_path: itwinai.pipeline.Pipeline + init_args: + steps: + dataloading_step: ${dataloading_step} + + training_step: + class_path: trainer.Lightning3DGANTrainer + init_args: + exp_root: ${logs_dir} + # Pytorch lightning config for training + config: + seed_everything: ${random_seed} + trainer: + accelerator: ${hw_accelerators} + accumulate_grad_batches: 1 + barebones: false + benchmark: null + callbacks: + - class_path: lightning.pytorch.callbacks.early_stopping.EarlyStopping + init_args: + monitor: val_generator_loss + patience: 2 + - class_path: lightning.pytorch.callbacks.lr_monitor.LearningRateMonitor + init_args: + logging_interval: step + - class_path: lightning.pytorch.callbacks.ModelCheckpoint + init_args: + dirpath: ${checkpoints_path} + filename: best-checkpoint + mode: min + monitor: val_generator_loss + save_top_k: 1 + verbose: true + check_val_every_n_epoch: 1 + default_root_dir: null + detect_anomaly: false + deterministic: null + devices: ${devices} + enable_checkpointing: true + enable_model_summary: null + enable_progress_bar: null + fast_dev_run: false + gradient_clip_algorithm: null + gradient_clip_val: null + inference_mode: true + limit_predict_batches: null + limit_test_batches: null + limit_train_batches: null + limit_val_batches: null + log_every_n_steps: 1 + logger: + - class_path: lightning.pytorch.loggers.CSVLogger + init_args: + name: 3DGAN + save_dir: ${logs_dir} + - class_path: lightning.pytorch.loggers.MLFlowLogger + init_args: + experiment_name: 3DGAN + save_dir: null #ml_logs/mlflow_logs + tracking_uri: ${mlflow_tracking_uri} + log_model: all + max_epochs: ${max_epochs} + max_time: null + min_epochs: null + min_steps: null + num_sanity_val_steps: null + overfit_batches: 0.0 + plugins: null + profiler: null + reload_dataloaders_every_n_epochs: 0 + strategy: ${distributed_strategy} + sync_batchnorm: false + use_distributed_sampler: true + val_check_interval: null + + # Lightning Model configuration + model: + class_path: model.ThreeDGAN + init_args: + latent_size: 256 + loss_weights: [3, 0.1, 25, 0.1] + power: 0.85 + lr: 0.001 + checkpoints_dir: ${checkpoints_path} + + # Lightning data module configuration + data: + class_path: dataloader.ParticlesDataModule + init_args: + datapath: ${dataset_location} + batch_size: ${batch_size} + num_workers: ${num_workers_dataloader} + max_samples: ${max_dataset_size} + +inference_pipeline: + class_path: itwinai.pipeline.Pipeline + init_args: + steps: + dataloading_step: ${dataloading_step} + + inference_step: + class_path: trainer.Lightning3DGANPredictor + init_args: + model: + class_path: trainer.LightningModelLoader + init_args: + model_uri: ${inference_model_uri} + + # Pytorch lightning config for training + config: + seed_everything: ${random_seed} + trainer: + accelerator: ${hw_accelerators} + accumulate_grad_batches: 1 + barebones: false + benchmark: null + check_val_every_n_epoch: 1 + default_root_dir: null + detect_anomaly: false + deterministic: null + devices: ${devices} + enable_checkpointing: true + enable_model_summary: null + enable_progress_bar: null + fast_dev_run: false + gradient_clip_algorithm: null + gradient_clip_val: null + inference_mode: true + limit_predict_batches: null + limit_test_batches: null + limit_train_batches: null + limit_val_batches: null + log_every_n_steps: 2 + logger: + # - class_path: lightning.pytorch.loggers.CSVLogger + # init_args: + # save_dir: ml_logs/csv_logs + class_path: lightning.pytorch.loggers.MLFlowLogger + init_args: + experiment_name: 3DGAN + save_dir: ${logs_dir} + log_model: all + max_epochs: ${max_epochs} + max_steps: 20 + max_time: null + min_epochs: null + min_steps: null + num_sanity_val_steps: null + overfit_batches: 0.0 + plugins: null + profiler: null + reload_dataloaders_every_n_epochs: 0 + strategy: ${distributed_strategy} + sync_batchnorm: false + use_distributed_sampler: true + val_check_interval: null + + # Lightning Model configuration + model: + class_path: model.ThreeDGAN + init_args: + latent_size: 256 + loss_weights: [3, 0.1, 25, 0.1] + power: 0.85 + lr: 0.001 + checkpoints_dir: ${checkpoints_path} + + # Lightning data module configuration + data: + class_path: dataloader.ParticlesDataModule + init_args: + datapath: ${dataset_location} + batch_size: ${batch_size} #1024 + num_workers: ${num_workers_dataloader} #4 + max_samples: ${max_dataset_size} #null, 10000 + + saver_step: + class_path: saver.ParticleImagesSaver + init_args: + save_dir: ${inference_results_location} + aggregate_predictions: ${aggregate_predictions} \ No newline at end of file diff --git a/use-cases/3dgan/create_inference_sample.py b/use-cases/3dgan/create_inference_sample.py new file mode 100644 index 00000000..14b88870 --- /dev/null +++ b/use-cases/3dgan/create_inference_sample.py @@ -0,0 +1,23 @@ +"""Create a simple inference dataset sample and a checkpoint.""" + +import argparse +import os +import torch +from model import ThreeDGAN + + +def create_checkpoint( + root: str = '.', + ckpt_name: str = "3dgan-inference.pth" +): + ckpt_path = os.path.join(root, ckpt_name) + net = ThreeDGAN() + torch.save(net, ckpt_path) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--root", type=str, default='.') + parser.add_argument("--ckpt-name", type=str, default="3dgan-inference.pth") + args = parser.parse_args() + create_checkpoint(**vars(args)) diff --git a/use-cases/3dgan/dataloader.py b/use-cases/3dgan/dataloader.py new file mode 100644 index 00000000..3aab7cc5 --- /dev/null +++ b/use-cases/3dgan/dataloader.py @@ -0,0 +1,212 @@ +from typing import Optional +import os +from lightning.pytorch.utilities.types import EVAL_DATALOADERS + +import numpy as np +import torch +from torch.utils.data import DataLoader, Dataset +import lightning as pl +import glob +import h5py +import gdown + +from itwinai.components import DataGetter, monitor_exec + + +class Lightning3DGANDownloader(DataGetter): + def __init__( + self, + data_path: str, + data_url: Optional[str] = None, + name: Optional[str] = None, + ) -> None: + self.save_parameters(**self.locals2params(locals())) + super().__init__(name) + self.data_path = data_path + self.data_url = data_url + + @monitor_exec + def execute(self): + # Download data + if not os.path.exists(self.data_path): + if self.data_url is None: + print("WARNING! Data URL is None. " + "Skipping dataset downloading") + + gdown.download_folder( + url=self.data_url, quiet=False, + output=self.data_path, + # verify=False + ) + + +class ParticlesDataset(Dataset): + def __init__(self, datapath: str, max_samples: Optional[int] = None): + self.datapath = datapath + self.max_samples = max_samples + self.data = dict() + + self.fetch_data() + + def __len__(self): + return len(self.data["X"]) + + def __getitem__(self, idx): + return {"X": self.data["X"][idx], "Y": self.data["Y"][idx], + "ang": self.data["ang"][idx], "ecal": self.data["ecal"][idx]} + + def fetch_data(self) -> None: + + print("Searching in :", self.datapath) + files = sorted(glob.glob(os.path.join( + self.datapath, '**/*.h5'), recursive=True)) + print("Found {} files. ".format(len(files))) + if len(files) == 0: + raise RuntimeError(f"No H5 files found at '{self.datapath}'!") + + # concatenated_datasets = [] + # for datafile in files: + # f = h5py.File(datafile, 'r') + # dataset = self.GetDataAngleParallel(f) + # concatenated_datasets.append(dataset) + # # Initialize result dictionary + # result = {key: [] for key in concatenated_datasets[0].keys()} + # for d in concatenated_datasets: + # for key in result.keys(): + # result[key].extend(d[key]) + # return result + + for datafile in files: + f = h5py.File(datafile, 'r') + dataset = self.GetDataAngleParallel(f) + for field, vals_array in dataset.items(): + if self.data.get(field) is not None: + # Resize to include the new array + new_shape = list(self.data[field].shape) + new_shape[0] += len(vals_array) + self.data[field].resize(new_shape) + self.data[field][-len(vals_array):] = vals_array + else: + self.data[field] = vals_array + + # Stop loading data, if self.max_samples reached + if (self.max_samples is not None + and len(self.data[field]) >= self.max_samples): + for field, vals_array in self.data.items(): + self.data[field] = vals_array[:self.max_samples] + break + + def GetDataAngleParallel( + self, + dataset, + xscale=1, + xpower=0.85, + yscale=100, + angscale=1, + angtype="theta", + thresh=1e-4, + daxis=-1 + ): + """Preprocess function for the dataset + + Args: + dataset (str): Dataset file path + xscale (int, optional): Value to scale the ECAL values. + Defaults to 1. + xpower (int, optional): Value to scale the ECAL values, + exponentially. Defaults to 1. + yscale (int, optional): Value to scale the energy values. + Defaults to 100. + angscale (int, optional): Value to scale the angle values. + Defaults to 1. + angtype (str, optional): Which type of angle to use. + Defaults to "theta". + thresh (_type_, optional): Maximum value for ECAL values. + Defaults to 1e-4. + daxis (int, optional): Axis to expand values. Defaults to -1. + + Returns: + Dict: Dictionary containning the preprocessed dataset + """ + X = np.array(dataset.get("ECAL")) * xscale + Y = np.array(dataset.get("energy")) / yscale + X[X < thresh] = 0 + X = X.astype(np.float32) + Y = Y.astype(np.float32) + ecal = np.sum(X, axis=(1, 2, 3)) + indexes = np.where(ecal > 10.0) + X = X[indexes] + Y = Y[indexes] + if angtype in dataset: + ang = np.array(dataset.get(angtype))[indexes] + # else: + # ang = gan.measPython(X) + X = np.expand_dims(X, axis=daxis) + ecal = ecal[indexes] + ecal = np.expand_dims(ecal, axis=daxis) + if xpower != 1.0: + X = np.power(X, xpower) + + Y = np.array([[el] for el in Y]) + ang = np.array([[el] for el in ang]) + ecal = np.array([[el] for el in ecal]) + + final_dataset = {"X": X, "Y": Y, "ang": ang, "ecal": ecal} + + return final_dataset + + +class ParticlesDataModule(pl.LightningDataModule): + def __init__( + self, + datapath: str, + batch_size: int, + num_workers: int = 4, + max_samples: Optional[int] = None + ) -> None: + super().__init__() + self.batch_size = batch_size + self.num_workers = num_workers + self.datapath = datapath + self.max_samples = max_samples + + def setup(self, stage: str = None): + # make assignments here (val/train/test split) + # called on every process in DDP + + if stage == 'fit' or stage is None: + self.dataset = ParticlesDataset( + self.datapath, + max_samples=self.max_samples + ) + dataset_length = len(self.dataset) + split_point = int(dataset_length * 0.9) + self.train_dataset, self.val_dataset = \ + torch.utils.data.random_split( + self.dataset, [split_point, dataset_length - split_point]) + + if stage == 'predict': + # TODO: inference dataset should be different in that it + # does not contain images! + self.predict_dataset = ParticlesDataset( + self.datapath, + max_samples=self.max_samples + ) + + # if stage == 'test' or stage is None: + # self.test_dataset = MyDataset(self.data_dir, train=False) + + def train_dataloader(self): + return DataLoader(self.train_dataset, num_workers=self.num_workers, + batch_size=self.batch_size, drop_last=True) + + def val_dataloader(self): + return DataLoader(self.val_dataset, num_workers=self.num_workers, + batch_size=self.batch_size, drop_last=True) + + def predict_dataloader(self) -> EVAL_DATALOADERS: + return DataLoader(self.predict_dataset, num_workers=self.num_workers, + batch_size=self.batch_size, drop_last=True) + + # def test_dataloader(self): + # return DataLoader(self.test_dataset, batch_size=self.batch_size) diff --git a/use-cases/3dgan/interLink/3dgan-inference-cpu.yaml b/use-cases/3dgan/interLink/3dgan-inference-cpu.yaml new file mode 100644 index 00000000..ef9016b4 --- /dev/null +++ b/use-cases/3dgan/interLink/3dgan-inference-cpu.yaml @@ -0,0 +1,76 @@ +apiVersion: v1 +kind: Pod +metadata: + name: 3dgan-inference-cpu + annotations: + slurm-job.vk.io/flags: "-p gpu --gres=gpu:1 --cpus-per-task=4 --mem=100G --ntasks-per-node=1 --nodes=1 --time=00:55:00" + job.vk.io/singularity-mounts: "--bind /ceph/hpc/data/st2301-itwin-users/egarciagarcia:/exp_data" + #job.vk.io/pre-exec: "singularity pull /ceph/hpc/data/st2301-itwin-users/itwinai_v9.5.sif docker://ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.4" +spec: + automountServiceAccountToken: false + containers: + - args: + - -c + - "\" cd /usr/src/app && itwinai exec-pipeline --print-config \ + --config \\$CERN_CODE_ROOT/config.yaml \ + --pipe-key inference_pipeline \ + -o dataset_location=\\$CERN_DATA_ROOT \ + -o logs_dir=\\$TMP_DATA_ROOT/ml_logs/mlflow_logs \ + -o distributed_strategy=\\$STRATEGY \ + -o devices=\\$DEVICES \ + -o hw_accelerators=\\$ACCELERATOR \ + -o inference_model_uri=\\$CERN_CODE_ROOT/3dgan-inference.pth \ + -o checkpoints_path=\\$TMP_DATA_ROOT/checkpoints \ + -o max_dataset_size=\\$MAX_DATA_SAMPLES \ + -o batch_size=\\$BATCH_SIZE \ + -o num_workers_dataloader=\\$NUM_WORKERS_DL \ + -o inference_results_location=\\$TMP_DATA_ROOT/3dgan-generated-data \ + -o aggregate_predictions=\\$AGGREGATE_PREDS \"" + command: + - /bin/sh + env: + - name: CERN_DATA_ROOT + value: "/exp_data" + - name: CERN_CODE_ROOT + value: "/usr/src/app" + - name: TMP_DATA_ROOT + value: "/exp_data" + - name: MAX_DATA_SAMPLES + value: "5000" + - name: BATCH_SIZE + value: "1024" + - name: NUM_WORKERS_DL + value: "4" + - name: AGGREGATE_PREDS + value: "true" + - name: ACCELERATOR + value: "cpu" + - name: STRATEGY + value: "auto" + - name: DEVICES + value: "auto" + image: /ceph/hpc/data/st2301-itwin-users/itwinai_v9.5.sif + imagePullPolicy: Always + name: oscar-container + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: "1" + memory: 1Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + nodeSelector: + kubernetes.io/hostname: vega-new-vk + tolerations: + - key: virtual-node.interlink/no-schedule + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 300 + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 300 \ No newline at end of file diff --git a/use-cases/3dgan/interLink/3dgan-inference.yaml b/use-cases/3dgan/interLink/3dgan-inference.yaml new file mode 100644 index 00000000..c07997de --- /dev/null +++ b/use-cases/3dgan/interLink/3dgan-inference.yaml @@ -0,0 +1,76 @@ +apiVersion: v1 +kind: Pod +metadata: + name: 3dgan-inference + annotations: + slurm-job.vk.io/flags: "-p gpu --gres=gpu:1 --cpus-per-task=4 --mem=100G --ntasks-per-node=1 --nodes=1 --time=00:55:00" + job.vk.io/singularity-mounts: "--bind /ceph/hpc/data/st2301-itwin-users/egarciagarcia:/exp_data" + # job.vk.io/pre-exec: "singularity pull /ceph/hpc/data/st2301-itwin-users/itwinai_v9.5.sif docker://ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.4" +spec: + automountServiceAccountToken: false + containers: + - args: + - -c + - "\" cd /usr/src/app && itwinai exec-pipeline --print-config \ + --config \\$CERN_CODE_ROOT/config.yaml \ + --pipe-key inference_pipeline \ + -o dataset_location=\\$CERN_DATA_ROOT \ + -o logs_dir=\\$TMP_DATA_ROOT/ml_logs/mlflow_logs \ + -o distributed_strategy=\\$STRATEGY \ + -o devices=\\$DEVICES \ + -o hw_accelerators=\\$ACCELERATOR \ + -o checkpoints_path=\\$TMP_DATA_ROOT/checkpoints \ + -o inference_model_uri=\\$CERN_CODE_ROOT/3dgan-inference.pth \ + -o max_dataset_size=\\$MAX_DATA_SAMPLES \ + -o batch_size=\\$BATCH_SIZE \ + -o num_workers_dataloader=\\$NUM_WORKERS_DL \ + -o inference_results_location=\\$TMP_DATA_ROOT/3dgan-generated-data \ + -o aggregate_predictions=\\$AGGREGATE_PREDS \"" + command: + - /bin/sh + env: + - name: CERN_DATA_ROOT + value: "/exp_data" + - name: CERN_CODE_ROOT + value: "/usr/src/app" + - name: TMP_DATA_ROOT + value: "/exp_data" + - name: MAX_DATA_SAMPLES + value: "5000" + - name: BATCH_SIZE + value: "2501" + - name: NUM_WORKERS_DL + value: "4" + - name: AGGREGATE_PREDS + value: "true" + - name: ACCELERATOR + value: "gpu" + - name: STRATEGY + value: "auto" + - name: DEVICES + value: "auto" + image: /ceph/hpc/data/st2301-itwin-users/itwinai_v9.5.sif + imagePullPolicy: Always + name: oscar-container + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: "1" + memory: 1Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + nodeSelector: + kubernetes.io/hostname: vega-new-vk + tolerations: + - key: virtual-node.interlink/no-schedule + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 300 + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 300 \ No newline at end of file diff --git a/use-cases/3dgan/interLink/3dgan-train.yaml b/use-cases/3dgan/interLink/3dgan-train.yaml new file mode 100644 index 00000000..e0885fd9 --- /dev/null +++ b/use-cases/3dgan/interLink/3dgan-train.yaml @@ -0,0 +1,88 @@ +apiVersion: v1 +kind: Pod +metadata: + name: 3dgan-train + annotations: + slurm-job.vk.io/flags: "-p gpu --gres=gpu:1 --cpus-per-task=4 --mem=100G --ntasks-per-node=1 --nodes=1 --time=00:55:00" + job.vk.io/singularity-mounts: "--bind /ceph/hpc/data/st2301-itwin-users/egarciagarcia:/exp_data" + # job.vk.io/pre-exec: "singularity pull /ceph/hpc/data/st2301-itwin-users/itwinai_v9.5.sif docker://ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.4" +spec: + automountServiceAccountToken: false + containers: + - args: + - -c + - "\" cd /usr/src/app && itwinai exec-pipeline --print-config \ + --config \\$CERN_CODE_ROOT/config.yaml \ + --pipe-key training_pipeline \ + -o dataset_location=\\$CERN_DATA_ROOT \ + -o pipeline.init_args.steps.training_step.init_args.exp_root=\\$TMP_DATA_ROOT \ + -o logs_dir=\\$TMP_DATA_ROOT/ml_logs \ + -o distributed_strategy=\\$STRATEGY \ + -o devices=\\$DEVICES \ + -o hw_accelerators=\\$ACCELERATOR \ + -o checkpoints_path=\\$TMP_DATA_ROOT/checkpoints \ + -o max_samples=\\$MAX_DATA_SAMPLES \ + -o batch_size=\\$BATCH_SIZE \ + -o max_dataset_size=\\$NUM_WORKERS_DL \"" + command: + - /bin/sh + env: + - name: CERN_DATA_ROOT + value: "/exp_data" + - name: CERN_CODE_ROOT + value: "/usr/src/app" + - name: TMP_DATA_ROOT + value: "/exp_data" + - name: MAX_DATA_SAMPLES + value: "1000" + - name: BATCH_SIZE + value: "512" + - name: NUM_WORKERS_DL + value: "4" + - name: ACCELERATOR + value: "gpu" + - name: STRATEGY + value: "auto" + - name: DEVICES + value: "auto" + + # - name: MLFLOW_TRACKING_USERNAME + # valueFrom: + # secretKeyRef: + # name: mlflow-server + # key: username + # - name: MLFLOW_TRACKING_PASSWORD + # valueFrom: + # secretKeyRef: + # name: mlflow-server + # key: password + + - name: MLFLOW_TRACKING_USERNAME + value: "XXX" + - name: MLFLOW_TRACKING_PASSWORD + value: "XXX" + image: /ceph/hpc/data/st2301-itwin-users/itwinai_v9.5.sif + imagePullPolicy: Always + name: oscar-container + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: "1" + memory: 1Gi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + nodeSelector: + kubernetes.io/hostname: vega-new-vk + tolerations: + - key: virtual-node.interlink/no-schedule + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 300 + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 300 \ No newline at end of file diff --git a/use-cases/3dgan/interLink/README.md b/use-cases/3dgan/interLink/README.md new file mode 100644 index 00000000..c2831f7b --- /dev/null +++ b/use-cases/3dgan/interLink/README.md @@ -0,0 +1,66 @@ +# Offloading through interLink + +This folder contains kubernetes pod examples to be used alongside with +[interLink](https://github.com/interTwin-eu/interLink), +to offload computation to remote HPC providers. + +To use these pods, you will need to install `kubectl` and setup a `kubeconfig`. + +## Manage pod + +```bash +# A pod needs to be deleted before re-submitting another one with the same name +kubectl delete pod POD_NAME +# Alternatively +kubectl apply --overwrite --force -f test.yaml + +# Submit pod +kubectl apply -f my-pod.yaml + +# Get status +kubectl get nodes +kubectl get pods + +# Get pod STDOUT +kubectl logs --insecure-skip-tls-verify-backend POD_NAME +``` + +## Pod annotations + +Allocate resources through SLURM: + +```yaml +slurm-job.vk.io/flags: "-p gpu --gres=gpu:1 --cpus-per-task=4 --mem=100G --ntasks-per-node=1 --nodes=1" +``` + +On some HPC system it may be needed to download the docker +container before submitting the offloaded job. T0 do so, you can use the +following annotation: + +```yaml +job.vk.io/pre-exec: "singularity pull /ceph/hpc/data/st2301-itwin-users/itwinaiv6_1.sif docker://ghcr.io/intertwin-eu/itwinai:0.0.1-3dgan-0.2" +``` + +IMPORTANT: add this annotation only once, when the image is not there. + +## Node selector + +To select to which remote system to offload, change the value in the node selector: + +```yaml +nodeSelector: + kubernetes.io/hostname: vega-new-vk +``` + +Additional info in [interLink](https://github.com/interTwin-eu/interLink) docs. + +## Secrets + +See [this guide](https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#define-container-environment-variables-using-secret-data) +on how to set Kubernetes secretes as env variables of a container. + +Example: + +```bash +kubectl create secret generic mlflow-server --from-literal=username='XYZ' --from-literal=password='ABC' +``` diff --git a/use-cases/3dgan/model.py b/use-cases/3dgan/model.py new file mode 100644 index 00000000..60dd48f9 --- /dev/null +++ b/use-cases/3dgan/model.py @@ -0,0 +1,786 @@ +import sys +import os +# import pickle +from collections import defaultdict +import math +from typing import Any + +import torch +import torch.nn as nn +import torch.nn.functional as F +import lightning as pl +import numpy as np + + +class Generator(nn.Module): + def __init__(self, latent_dim): # img_shape + super().__init__() + # self.img_shape = img_shape + self.latent_dim = latent_dim + + self.l1 = nn.Linear(self.latent_dim, 5184) + self.up1 = nn.Upsample( + scale_factor=(6, 6, 6), + mode='trilinear', + align_corners=False + ) + self.conv1 = nn.Conv3d( + in_channels=8, out_channels=8, + kernel_size=(6, 6, 8), padding=0 + ) + nn.init.kaiming_uniform_(self.conv1.weight) + # num_features is the number of channels (see doc) + self.bn1 = nn.BatchNorm3d(num_features=8, eps=1e-6) + self.pad1 = nn.ConstantPad3d((1, 1, 2, 2, 2, 2), 0) + + self.conv2 = nn.Conv3d( + in_channels=8, out_channels=6, + kernel_size=(4, 4, 6), padding=0 + ) + nn.init.kaiming_uniform_(self.conv2.weight) + self.bn2 = nn.BatchNorm3d(num_features=6, eps=1e-6) + self.pad2 = nn.ConstantPad3d((1, 1, 2, 2, 2, 2), 0) + + self.conv3 = nn.Conv3d( + in_channels=6, out_channels=6, + kernel_size=(4, 4, 6), padding=0 + ) + nn.init.kaiming_uniform_(self.conv3.weight) + self.bn3 = nn.BatchNorm3d(num_features=6, eps=1e-6) + self.pad3 = nn.ConstantPad3d((1, 1, 2, 2, 2, 2), 0) + + self.conv4 = nn.Conv3d( + in_channels=6, out_channels=6, + kernel_size=(4, 4, 6), padding=0 + ) + nn.init.kaiming_uniform_(self.conv4.weight) + self.bn4 = nn.BatchNorm3d(num_features=6, eps=1e-6) + self.pad4 = nn.ConstantPad3d((0, 0, 1, 1, 1, 1), 0) + + self.conv5 = nn.Conv3d( + in_channels=6, out_channels=6, + kernel_size=(3, 3, 5), padding=0 + ) + nn.init.kaiming_uniform_(self.conv5.weight) + self.bn5 = nn.BatchNorm3d(num_features=6, eps=1e-6) + self.pad5 = nn.ConstantPad3d((0, 0, 1, 1, 1, 1), 0) + + self.conv6 = nn.Conv3d( + in_channels=6, out_channels=6, + kernel_size=(3, 3, 3), padding=0 + ) + nn.init.kaiming_uniform_(self.conv6.weight) + + self.conv7 = nn.Conv3d( + in_channels=6, out_channels=1, + kernel_size=(2, 2, 2), padding=0 + ) + nn.init.xavier_normal_(self.conv7.weight) + + def forward(self, z): + img = self.l1(z) + img = img.view(-1, 8, 9, 9, 8) + img = self.up1(img) + img = self.conv1(img) + img = F.relu(img) + img = self.bn1(img) + img = self.pad1(img) + + img = self.conv2(img) + img = F.relu(img) + img = self.bn2(img) + img = self.pad2(img) + + img = self.conv3(img) + img = F.relu(img) + img = self.bn3(img) + img = self.pad3(img) + + img = self.conv4(img) + img = F.relu(img) + img = self.bn4(img) + img = self.pad4(img) + + img = self.conv5(img) + img = F.relu(img) + img = self.bn5(img) + img = self.pad5(img) + + img = self.conv6(img) + img = F.relu(img) + + img = self.conv7(img) + img = F.relu(img) + + return img + + +class Discriminator(nn.Module): + def __init__(self, power): + super().__init__() + + self.power = power + + self.conv1 = nn.Conv3d( + in_channels=1, out_channels=16, + kernel_size=(5, 6, 6), padding=(2, 3, 3) + ) + self.drop1 = nn.Dropout(0.2) + self.pad1 = nn.ConstantPad3d((1, 1, 0, 0, 0, 0), 0) + + self.conv2 = nn.Conv3d( + in_channels=16, out_channels=8, + kernel_size=(5, 6, 6), padding=0 + ) + self.bn1 = nn.BatchNorm3d(num_features=8, eps=1e-6) + self.drop2 = nn.Dropout(0.2) + self.pad2 = nn.ConstantPad3d((1, 1, 0, 0, 0, 0), 0) + + self.conv3 = nn.Conv3d( + in_channels=8, out_channels=8, + kernel_size=(5, 6, 6), padding=0 + ) + self.bn2 = nn.BatchNorm3d(num_features=8, eps=1e-6) + self.drop3 = nn.Dropout(0.2) + + self.conv4 = nn.Conv3d( + in_channels=8, out_channels=8, + kernel_size=(5, 6, 6), padding=0 + ) + self.bn3 = nn.BatchNorm3d(num_features=8, eps=1e-6) + self.drop4 = nn.Dropout(0.2) + + self.avgpool = nn.AvgPool3d((2, 2, 2)) + self.flatten = nn.Flatten() + + # The input features for the Linear layer need to be calculated based + # on the output shape from the previous layers. + self.fakeout = nn.Linear(19152, 1) + self.auxout = nn.Linear(19152, 1) # The same as above for this layer. + + # calculate sum of intensities + def ecal_sum(self, image, daxis): + sum = torch.sum(image, dim=daxis) + return sum + + # angle calculation + def ecal_angle(self, image, daxis1): + image = torch.squeeze(image, dim=daxis1) # squeeze along channel axis + + # get shapes + x_shape = image.shape[1] + y_shape = image.shape[2] + z_shape = image.shape[3] + sumtot = torch.sum(image, dim=(1, 2, 3)) # sum of events + + # get 1. where event sum is 0 and 0 elsewhere + amask = torch.where(sumtot == 0.0, torch.ones_like( + sumtot), torch.zeros_like(sumtot)) + # masked_events = torch.sum(amask) # counting zero sum events + + # ref denotes barycenter as that is our reference point + x_ref = torch.sum(torch.sum(image, dim=(2, 3)) + * (torch.arange(x_shape, device=image.device, + dtype=torch.float32).unsqueeze(0) + 0.5), + dim=1,) # sum for x position * x index + y_ref = torch.sum( + torch.sum(image, dim=(1, 3)) + * (torch.arange(y_shape, device=image.device, + dtype=torch.float32).unsqueeze(0) + 0.5), + dim=1,) + z_ref = torch.sum( + torch.sum(image, dim=(1, 2)) + * (torch.arange(z_shape, device=image.device, + dtype=torch.float32).unsqueeze(0) + 0.5), + dim=1,) + + # return max position if sumtot=0 and divide by sumtot otherwise + x_ref = torch.where( + sumtot == 0.0, torch.ones_like(x_ref), x_ref / sumtot) + y_ref = torch.where( + sumtot == 0.0, torch.ones_like(y_ref), y_ref / sumtot) + z_ref = torch.where( + sumtot == 0.0, torch.ones_like(z_ref), z_ref / sumtot) + + # reshape + x_ref = x_ref.unsqueeze(1) + y_ref = y_ref.unsqueeze(1) + z_ref = z_ref.unsqueeze(1) + + sumz = torch.sum(image, dim=(1, 2)) # sum for x,y planes going along z + + # Get 0 where sum along z is 0 and 1 elsewhere + zmask = torch.where(sumz == 0.0, torch.zeros_like( + sumz), torch.ones_like(sumz)) + + x = torch.arange(x_shape, device=image.device).unsqueeze( + 0) # x indexes + x = (x.unsqueeze(2).float()) + 0.5 + y = torch.arange(y_shape, device=image.device).unsqueeze( + 0) # y indexes + y = (y.unsqueeze(2).float()) + 0.5 + + # barycenter for each z position + x_mid = torch.sum(torch.sum(image, dim=2) * x, dim=1) + y_mid = torch.sum(torch.sum(image, dim=1) * y, dim=1) + + x_mid = torch.where(sumz == 0.0, torch.zeros_like( + sumz), x_mid / sumz) # if sum != 0 then divide by sum + y_mid = torch.where(sumz == 0.0, torch.zeros_like( + sumz), y_mid / sumz) # if sum != 0 then divide by sum + + # Angle Calculations + z = (torch.arange( + z_shape, + device=image.device, + dtype=torch.float32 + # Make an array of z indexes for all events + ) + 0.5) * torch.ones_like(z_ref) + + # projection from z axis with stability check + zproj = torch.sqrt( + torch.max( + (x_mid - x_ref) ** 2.0 + (z - z_ref) ** 2.0, + torch.tensor( + [torch.finfo(torch.float32).eps] + ).to(x_mid.device) + ) + ) + # torch.finfo(torch.float32).eps)) + # to avoid divide by zero for zproj =0 + m = torch.where(zproj == 0.0, torch.zeros_like( + zproj), (y_mid - y_ref) / zproj) + m = torch.where(z < z_ref, -1 * m, m) # sign inversion + ang = (math.pi / 2.0) - torch.atan(m) # angle correction + zmask = torch.where(zproj == 0.0, torch.zeros_like(zproj), zmask) + ang = ang * zmask # place zero where zsum is zero + ang = ang * z # weighted by position + sumz_tot = z * zmask # removing indexes with 0 energies or angles + + # zunmasked = K.sum(zmask, axis=1) # used for simple mean + # Mean does not include positions where zsum=0 + # ang = K.sum(ang, axis=1)/zunmasked + + # sum ( measured * weights)/sum(weights) + ang = torch.sum(ang, dim=1) / torch.sum(sumz_tot, dim=1) + # Place 100 for measured angle where no energy is deposited in events + ang = torch.where(amask == 0.0, ang, 100.0 * torch.ones_like(ang)) + ang = ang.unsqueeze(1) + return ang + + def forward(self, x): + z = self.conv1(x) + z = F.leaky_relu(z) + z = self.drop1(z) + z = self.pad1(z) + + z = self.conv2(z) + z = F.leaky_relu(z) + z = self.bn1(z) + z = self.drop2(z) + z = self.pad2(z) + + z = self.conv3(z) + z = F.leaky_relu(z) + z = self.bn2(z) + z = self.drop3(z) + + z = self.conv4(z) + z = F.leaky_relu(z) + z = self.bn3(z) + z = self.drop4(z) + z = self.avgpool(z) + z = self.flatten(z) + + # generation output that says fake/real + fake = torch.sigmoid(self.fakeout(z)) + aux = self.auxout(z) # auxiliary output + inv_image = x.pow(1.0 / self.power) + ang = self.ecal_angle(inv_image, 1) # angle calculation + ecal = self.ecal_sum(inv_image, (2, 3, 4)) # sum of energies + + return fake, aux, ang, ecal + + +class ThreeDGAN(pl.LightningModule): + def __init__( + self, + latent_size=256, + loss_weights=[3, 0.1, 25, 0.1], + power=0.85, + lr=0.001, + checkpoints_dir: str = '.' + # checkpoint_path: str = '3Dgan.pth' + ): + super().__init__() + self.save_hyperparameters() + self.automatic_optimization = False + + self.latent_size = latent_size + self.loss_weights = loss_weights + self.lr = lr + self.power = power + self.checkpoints_dir = checkpoints_dir + os.makedirs(self.checkpoints_dir, exist_ok=True) + + self.generator = Generator(self.latent_size) + self.discriminator = Discriminator(self.power) + + self.epoch_gen_loss = [] + self.epoch_disc_loss = [] + self.disc_epoch_test_loss = [] + self.gen_epoch_test_loss = [] + self.index = 0 + self.train_history = defaultdict(list) + self.test_history = defaultdict(list) + # self.pklfile = checkpoint_path + # checkpoint_dir = os.path.dirname(checkpoint_path) + # os.makedirs(checkpoint_dir, exist_ok=True) + + def BitFlip(self, x, prob=0.05): + """ + Flips a single bit according to a certain probability. + + Args: + x (list): list of bits to be flipped + prob (float): probability of flipping one bit + + Returns: + list: List of flipped bits + + """ + x = np.array(x) + selection = np.random.uniform(0, 1, x.shape) < prob + x[selection] = 1 * np.logical_not(x[selection]) + return x + + def mean_absolute_percentage_error(self, y_true, y_pred): + return torch.mean(torch.abs((y_true - y_pred) / (y_true + 1e-7))) * 100 + + def compute_global_loss( + self, + labels, + predictions, + loss_weights=(3, 0.1, 25, 0.1) + ): + # Can be initialized outside + binary_crossentropy_object = nn.BCEWithLogitsLoss(reduction='none') + # there is no equivalent in pytorch for + # tf.keras.losses.MeanAbsolutePercentageError --> using the + # custom "mean_absolute_percentage_error" above! + mean_absolute_percentage_error_object1 = \ + self.mean_absolute_percentage_error(predictions[1], labels[1]) + mean_absolute_percentage_error_object2 = \ + self.mean_absolute_percentage_error(predictions[3], labels[3]) + mae_object = nn.L1Loss(reduction='none') + + binary_example_loss = binary_crossentropy_object( + predictions[0], labels[0]) * loss_weights[0] + + # mean_example_loss_1 = mean_absolute_percentage_error_object( + # predictions[1], labels[1]) * loss_weights[1] + mean_example_loss_1 = \ + mean_absolute_percentage_error_object1 * loss_weights[1] + + mae_example_loss = mae_object( + predictions[2], labels[2]) * loss_weights[2] + + # mean_example_loss_2 = mean_absolute_percentage_error_object( + # predictions[3], labels[3]) * loss_weights[3] + mean_example_loss_2 = \ + mean_absolute_percentage_error_object2 * loss_weights[3] + + binary_loss = binary_example_loss.mean() + mean_loss_1 = mean_example_loss_1.mean() + mae_loss = mae_example_loss.mean() + mean_loss_2 = mean_example_loss_2.mean() + + return [binary_loss, mean_loss_1, mae_loss, mean_loss_2] + + def forward(self, z): + return self.generator(z) + + def training_step(self, batch, batch_idx): + image_batch, energy_batch, ang_batch, ecal_batch = \ + batch['X'], batch['Y'], batch['ang'], batch['ecal'] + + image_batch = image_batch.permute(0, 4, 1, 2, 3) + + image_batch = image_batch.to(self.device) + energy_batch = energy_batch.to(self.device) + ang_batch = ang_batch.to(self.device) + ecal_batch = ecal_batch.to(self.device) + + optimizer_discriminator, optimizer_generator = self.optimizers() + batch_size = energy_batch.shape[0] + + noise = torch.randn( + (batch_size, self.latent_size - 2), + dtype=torch.float32, + device=self.device + ) + # print(f'Energy elements: {energy_batch.numel} {energy_batch.shape}') + # print(f'Angle elements: {ang_batch.numel} {ang_batch.shape}') + generator_ip = torch.cat( + (energy_batch.view(-1, 1), ang_batch.view(-1, 1), noise), + dim=1 + ) + generated_images = self.generator(generator_ip) + + # Train discriminator first on real batch + fake_batch = self.BitFlip(np.ones(batch_size).astype(np.float32)) + fake_batch = torch.tensor([[el] for el in fake_batch]).to(self.device) + labels = [fake_batch, energy_batch, ang_batch, ecal_batch] + + predictions = self.discriminator(image_batch) + # print("calculating real_batch_loss...") + real_batch_loss = self.compute_global_loss( + labels, predictions, self.loss_weights) + self.log("real_batch_loss", sum(real_batch_loss), + prog_bar=True, on_step=True, on_epoch=True, sync_dist=True) + # print("real batch disc train") + # the following 3 lines correspond in tf version to: + # gradients = tape.gradient(real_batch_loss, + # discriminator.trainable_variables) + # optimizer_discriminator.apply_gradients(zip(gradients, + # discriminator.trainable_variables)) in Tensorflow + optimizer_discriminator.zero_grad() + self.manual_backward(sum(real_batch_loss)) + # sum(real_batch_loss).backward() + # real_batch_loss.backward() + optimizer_discriminator.step() + + # Train discriminator on the fake batch + fake_batch = self.BitFlip(np.zeros(batch_size).astype(np.float32)) + fake_batch = torch.tensor([[el] for el in fake_batch]).to(self.device) + labels = [fake_batch, energy_batch, ang_batch, ecal_batch] + + predictions = self.discriminator(generated_images) + + fake_batch_loss = self.compute_global_loss( + labels, predictions, self.loss_weights) + self.log("fake_batch_loss", sum(fake_batch_loss), + prog_bar=True, on_step=True, on_epoch=True, sync_dist=True) + # print("fake batch disc train") + # the following 3 lines correspond to + # gradients = tape.gradient(fake_batch_loss, + # discriminator.trainable_variables) + # optimizer_discriminator.apply_gradients(zip(gradients, + # discriminator.trainable_variables)) in Tensorflow + optimizer_discriminator.zero_grad() + self.manual_backward(sum(fake_batch_loss)) + # sum(fake_batch_loss).backward() + optimizer_discriminator.step() + + # avg_disc_loss = (sum(real_batch_loss) + sum(fake_batch_loss)) / 2 + + trick = np.ones(batch_size).astype(np.float32) + fake_batch = torch.tensor([[el] for el in trick]).to(self.device) + labels = [fake_batch, energy_batch.view(-1, 1), ang_batch, ecal_batch] + + gen_losses_train = [] + # Train generator twice using combined model + for _ in range(2): + noise = torch.randn( + (batch_size, self.latent_size - 2)).to(self.device) + generator_ip = torch.cat( + (energy_batch.view(-1, 1), ang_batch.view(-1, 1), noise), + dim=1 + ) + + generated_images = self.generator(generator_ip) + predictions = self.discriminator(generated_images) + + loss = self.compute_global_loss( + labels, predictions, self.loss_weights) + self.log("gen_loss", sum(loss), prog_bar=True, + on_step=True, on_epoch=True, sync_dist=True) + # print("gen train") + optimizer_generator.zero_grad() + self.manual_backward(sum(loss)) + # sum(loss).backward() + optimizer_generator.step() + + for el in loss: + gen_losses_train.append(el) + + avg_generator_loss = sum(gen_losses_train) / len(gen_losses_train) + self.log("generator_loss", avg_generator_loss.item(), + prog_bar=True, on_step=True, on_epoch=True, sync_dist=True) + # avg_generator_loss = [(a + b) / 2 for a, b in zip(*gen_losses_train)] + # self.log("generator_loss", sum(avg_generator_loss), prog_bar=True, + # on_step=True, on_epoch=True, sync_dist=True) + + gen_losses = [] + # I'm not returning anything as in pl you do not return anything when + # you back-propagate manually + # return_loss = real_batch_loss + real_batch_loss = [real_batch_loss[0], real_batch_loss[1], + real_batch_loss[2], real_batch_loss[3]] + fake_batch_loss = [fake_batch_loss[0], fake_batch_loss[1], + fake_batch_loss[2], fake_batch_loss[3]] + gen_batch_loss = [gen_losses_train[0], gen_losses_train[1], + gen_losses_train[2], gen_losses_train[3]] + gen_losses.append(gen_batch_loss) + gen_batch_loss = [gen_losses_train[4], gen_losses_train[5], + gen_losses_train[6], gen_losses_train[7]] + gen_losses.append(gen_batch_loss) + + real_batch_loss = [el.cpu().detach().numpy() for el in real_batch_loss] + real_batch_loss_total_loss = np.sum(real_batch_loss) + new_real_batch_loss = [real_batch_loss_total_loss] + for i_weights in range(len(real_batch_loss)): + new_real_batch_loss.append( + real_batch_loss[i_weights] / self.loss_weights[i_weights]) + real_batch_loss = new_real_batch_loss + + fake_batch_loss = [el.cpu().detach().numpy() for el in fake_batch_loss] + fake_batch_loss_total_loss = np.sum(fake_batch_loss) + new_fake_batch_loss = [fake_batch_loss_total_loss] + for i_weights in range(len(fake_batch_loss)): + new_fake_batch_loss.append( + fake_batch_loss[i_weights] / self.loss_weights[i_weights]) + fake_batch_loss = new_fake_batch_loss + + # if ecal sum has 100% loss(generating empty events) then end + # the training + if fake_batch_loss[3] == 100.0 and self.index > 10: + # print("Empty image with Ecal loss equal to 100.0 " + # f"for {self.index} batch") + torch.save(self.generator.state_dict(), os.path.join( + self.checkpoints_dir, "generator_weights.pth")) + torch.save(self.discriminator.state_dict(), os.path.join( + self.checkpoints_dir, "discriminator_weights.pth")) + # print("real_batch_loss", real_batch_loss) + # print("fake_batch_loss", fake_batch_loss) + sys.exit() + + # append mean of discriminator loss for real and fake events + self.epoch_disc_loss.append( + [(a + b) / 2 for a, b in zip(real_batch_loss, fake_batch_loss)]) + + gen_losses[0] = [el.cpu().detach().numpy() for el in gen_losses[0]] + gen_losses_total_loss = np.sum(gen_losses[0]) + new_gen_losses = [gen_losses_total_loss] + for i_weights in range(len(gen_losses[0])): + new_gen_losses.append( + gen_losses[0][i_weights] / self.loss_weights[i_weights]) + gen_losses[0] = new_gen_losses + + gen_losses[1] = [el.cpu().detach().numpy() for el in gen_losses[1]] + gen_losses_total_loss = np.sum(gen_losses[1]) + new_gen_losses = [gen_losses_total_loss] + for i_weights in range(len(gen_losses[1])): + new_gen_losses.append( + gen_losses[1][i_weights] / self.loss_weights[i_weights]) + gen_losses[1] = new_gen_losses + + generator_loss = [(a + b) / 2 for a, b in zip(*gen_losses)] + + self.epoch_gen_loss.append(generator_loss) + + # # MB: verify weight synchronization among workers + # # Ref: https://github.com/Lightning-AI/lightning/issues/9237 + # disc_w = self.discriminator.conv1.weight.reshape(-1)[0:5] + # gen_w = self.generator.conv1.weight.reshape(-1)[0:5] + # print(f"DISC w: {disc_w}") + # print(f"GEN w: {gen_w}") + + # self.index += 1 #this might be moved after test cycle + + # logging of gen and disc loss done by Trainer + # self.log('epoch_gen_loss', self.epoch_gen_loss, on_step=True, + # on_epoch=True, sync_dist=True) + # self.log('epoch_disc_loss', self.epoch_disc_loss, on_step=True, o + # n_epoch=True, sync_dist=True) + + # return avg_disc_loss + avg_generator_loss + + def on_train_epoch_end(self): # outputs + discriminator_train_loss = np.mean( + np.array(self.epoch_disc_loss), axis=0) + generator_train_loss = np.mean(np.array(self.epoch_gen_loss), axis=0) + + self.train_history["generator"].append(generator_train_loss) + self.train_history["discriminator"].append(discriminator_train_loss) + + print("-" * 65) + ROW_FMT = ( + "{0:<20s} | {1:<4.2f} | {2:<10.2f} | " + "{3:<10.2f}| {4:<10.2f} | {5:<10.2f}") + print(ROW_FMT.format("generator (train)", + *self.train_history["generator"][-1])) + print(ROW_FMT.format("discriminator (train)", + *self.train_history["discriminator"][-1])) + + torch.save(self.generator.state_dict(), os.path.join( + self.checkpoints_dir, "generator_weights.pth")) + torch.save(self.discriminator.state_dict(), os.path.join( + self.checkpoints_dir, "discriminator_weights.pth")) + + # with open(self.pklfile, "wb") as f: + # pickle.dump({"train": self.train_history, + # "test": self.test_history}, f) + + # pickle.dump({"train": self.train_history}, open(self.pklfile, "wb")) + print("train-loss:" + str(self.train_history["generator"][-1][0])) + + def validation_step(self, batch, batch_idx): + image_batch, energy_batch, ang_batch, ecal_batch = batch[ + 'X'], batch['Y'], batch['ang'], batch['ecal'] + + image_batch = image_batch.permute(0, 4, 1, 2, 3) + + image_batch = image_batch.to(self.device) + energy_batch = energy_batch.to(self.device) + ang_batch = ang_batch.to(self.device) + ecal_batch = ecal_batch.to(self.device) + + batch_size = energy_batch.shape[0] + + # Generate Fake events with same energy and angle as data batch + noise = torch.randn( + (batch_size, self.latent_size - 2), + dtype=torch.float32, + device=self.device + ) + + generator_ip = torch.cat( + (energy_batch.view(-1, 1), ang_batch.view(-1, 1), noise), dim=1) + generated_images = self.generator(generator_ip) + + # concatenate to fake and real batches + X = torch.cat((image_batch, generated_images), dim=0) + + # y = np.array([1] * batch_size \ + # + [0] * batch_size).astype(np.float32) + y = torch.tensor([1] * batch_size + [0] * + batch_size, dtype=torch.float32).to(self.device) + y = y.view(-1, 1) + + ang = torch.cat((ang_batch, ang_batch), dim=0) + ecal = torch.cat((ecal_batch, ecal_batch), dim=0) + aux_y = torch.cat((energy_batch, energy_batch), dim=0) + + # y = [[el] for el in y] + labels = [y, aux_y, ang, ecal] + + # Calculate discriminator loss + disc_eval = self.discriminator(X) + disc_eval_loss = self.compute_global_loss( + labels, disc_eval, self.loss_weights) + + # Calculate generator loss + trick = np.ones(batch_size).astype(np.float32) + fake_batch = torch.tensor([[el] for el in trick]).to(self.device) + # fake_batch = [[el] for el in trick] + labels = [fake_batch, energy_batch, ang_batch, ecal_batch] + + generated_images = self.generator(generator_ip) + gen_eval = self.discriminator(generated_images) + gen_eval_loss = self.compute_global_loss( + labels, gen_eval, self.loss_weights) + + self.log('val_discriminator_loss', sum( + disc_eval_loss), on_epoch=True, prog_bar=True, sync_dist=True) + self.log('val_generator_loss', sum(gen_eval_loss), + on_epoch=True, prog_bar=True, sync_dist=True) + + disc_test_loss = [disc_eval_loss[0], disc_eval_loss[1], + disc_eval_loss[2], disc_eval_loss[3]] + gen_test_loss = [gen_eval_loss[0], gen_eval_loss[1], + gen_eval_loss[2], gen_eval_loss[3]] + + # Configure the loss so it is equal to the original values + disc_eval_loss = [el.cpu().detach().numpy() for el in disc_test_loss] + disc_eval_loss_total_loss = np.sum(disc_eval_loss) + new_disc_eval_loss = [disc_eval_loss_total_loss] + for i_weights in range(len(disc_eval_loss)): + new_disc_eval_loss.append( + disc_eval_loss[i_weights] / self.loss_weights[i_weights]) + disc_eval_loss = new_disc_eval_loss + + gen_eval_loss = [el.cpu().detach().numpy() for el in gen_test_loss] + gen_eval_loss_total_loss = np.sum(gen_eval_loss) + new_gen_eval_loss = [gen_eval_loss_total_loss] + for i_weights in range(len(gen_eval_loss)): + new_gen_eval_loss.append( + gen_eval_loss[i_weights] / self.loss_weights[i_weights]) + gen_eval_loss = new_gen_eval_loss + + self.index += 1 + # evaluate discriminator loss + self.disc_epoch_test_loss.append(disc_eval_loss) + # evaluate generator loss + self.gen_epoch_test_loss.append(gen_eval_loss) + + def on_validation_epoch_end(self): + discriminator_test_loss = np.mean( + np.array(self.disc_epoch_test_loss), axis=0) + generator_test_loss = np.mean( + np.array(self.gen_epoch_test_loss), axis=0) + + self.test_history["generator"].append(generator_test_loss) + self.test_history["discriminator"].append(discriminator_test_loss) + + print("-" * 65) + ROW_FMT = ( + "{0:<20s} | {1:<4.2f} | {2:<10.2f} | " + "{3:<10.2f}| {4:<10.2f} | {5:<10.2f}") + print(ROW_FMT.format("generator (test)", + *self.test_history["generator"][-1])) + print(ROW_FMT.format("discriminator (test)", + *self.test_history["discriminator"][-1])) + + # # save loss dict to pkl file + # with open(self.pklfile, "wb") as f: + # pickle.dump({"train": self.train_history, + # "test": self.test_history}, f) + # pickle.dump({"test": self.test_history}, open(self.pklfile, "wb")) + # print("train-loss:" + str(self.train_history["generator"][-1][0])) + + def predict_step( + self, + batch: Any, + batch_idx: int, + dataloader_idx: int = 0 + ) -> Any: + energy_batch, ang_batch = batch['Y'], batch['ang'] + + energy_batch = energy_batch.to(self.device) + ang_batch = ang_batch.to(self.device) + + # Generate Fake events with same energy and angle as data batch + noise = torch.randn( + (energy_batch.shape[0], self.latent_size - 2), + dtype=torch.float32, + device=self.device + ) + + # print(f"Reshape energy: {energy_batch.view(-1, 1).shape}") + # print(f"Reshape angle: {ang_batch.view(-1, 1).shape}") + # print(f"Noise: {noise.shape}") + + generator_ip = torch.cat( + [energy_batch.view(-1, 1), ang_batch.view(-1, 1), noise], + dim=1 + ) + # print(f"Generator input: {generator_ip.shape}") + generated_images = self.generator(generator_ip) + # print(f"Generated batch size {generated_images.shape}") + return {'images': generated_images, + 'energies': energy_batch, + 'angles': ang_batch} + + def configure_optimizers(self): + lr = self.lr + + optimizer_discriminator = torch.optim.RMSprop( + self.discriminator.parameters(), + lr + ) + optimizer_generator = torch.optim.RMSprop( + self.generator.parameters(), + lr + ) + return [optimizer_discriminator, optimizer_generator], [] diff --git a/use-cases/3dgan/requirements.txt b/use-cases/3dgan/requirements.txt new file mode 100644 index 00000000..f1f3b0bf --- /dev/null +++ b/use-cases/3dgan/requirements.txt @@ -0,0 +1,6 @@ +h5py>=3.7.0 +google>=3.0.0 +protobuf>=4.24.3 +gdown>=4.7.1 +# plotly>=5.18.0 +# kaleido>=0.2.1 \ No newline at end of file diff --git a/use-cases/3dgan/saver.py b/use-cases/3dgan/saver.py new file mode 100644 index 00000000..bacf9ab7 --- /dev/null +++ b/use-cases/3dgan/saver.py @@ -0,0 +1,122 @@ +from typing import Dict +import os +import shutil +import pickle +import random + +import torch +from torch import Tensor +import matplotlib.pyplot as plt +import numpy as np + +from itwinai.components import Saver, monitor_exec + + +class ParticleImagesSaver(Saver): + """Saves generated particle trajectories to disk.""" + + def __init__( + self, + save_dir: str = '3dgan-generated', + aggregate_predictions: bool = False + ) -> None: + self.save_parameters(**self.locals2params(locals())) + super().__init__() + self.save_dir = save_dir + self.aggregate_predictions = aggregate_predictions + + @monitor_exec + def execute(self, generated_images: Dict[str, Tensor]) -> None: + """Saves generated images to disk. + + Args: + generated_images (Dict[str, Tensor]): maps unique item ID to + the generated image. + """ + if self.aggregate_predictions: + os.makedirs(self.save_dir, exist_ok=True) + sparse_generated_images = dict() + for name, res in generated_images.items(): + sparse_generated_images[name] = res.to_sparse() + del generated_images + with open(self._random_file(), 'wb') as fp: + pickle.dump(sparse_generated_images, fp) + else: + if os.path.exists(self.save_dir): + shutil.rmtree(self.save_dir) + os.makedirs(self.save_dir) + # Save as torch tensor and jpg image + for img_id, img in generated_images.items(): + img_path = os.path.join(self.save_dir, img_id) + torch.save(img, img_path + '.pth') + self._save_image(img, img_id, img_path + '.jpg') + + def _random_file(self, extension: str = 'pkl') -> str: + fname = "%032x.%s" % (random.getrandbits(128), extension) + fpath = os.path.join(self.save_dir, fname) + while os.path.exists(fpath): + fname = "%032x.%s" % (random.getrandbits(128), extension) + fpath = os.path.join(self.save_dir, fname) + return fpath + + def _save_image( + self, + img: Tensor, + img_idx: str, + img_path: str, + center: bool = True + ) -> None: + """Converts a 3D tensor to a 3D scatter plot and saves it + to disk as jpg image. + """ + x_offset = img.shape[0] // 2 if center else 0 + y_offset = img.shape[1] // 2 if center else 0 + z_offset = img.shape[2] // 2 if center else 0 + + # Convert tensor dimension IDs to coordinates + x_coords = [] + y_coords = [] + z_coords = [] + values = [] + + for x in range(img.shape[0]): + for y in range(img.shape[1]): + for z in range(img.shape[2]): + if img[x, y, z] > 0.0: + x_coords.append(x - x_offset) + y_coords.append(y - y_offset) + z_coords.append(z - z_offset) + values.append(img[x, y, z]) + + # import plotly.graph_objects as go + # normalize_intensity_by = 1 + # trace = go.Scatter3d( + # x=x_coords, + # y=y_coords, + # z=z_coords, + # mode='markers', + # marker_symbol='square', + # marker_color=[ + # f"rgba(0,0,255,{i*100//normalize_intensity_by/10})" + # for i in values], + # ) + # fig = go.Figure() + # fig.add_trace(trace) + # fig.write_image(img_path) + + values = np.array(values) + # 0-1 scaling + values = (values - values.min()) / (values.max() - values.min()) + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.scatter(x_coords, y_coords, z_coords, alpha=values) + ax.set_xlabel('X') + ax.set_ylabel('Y') + ax.set_zlabel('Z') + + # Extract energy and angle from idx + en, ang = img_idx.split('&') + en = en[7:] + ang = ang[6:] + ax.set_title(f"Energy: {en} - Angle: {ang}") + fig.savefig(img_path) diff --git a/HPC_startscripts/workflow_startscript b/use-cases/3dgan/startscript old mode 100755 new mode 100644 similarity index 68% rename from HPC_startscripts/workflow_startscript rename to use-cases/3dgan/startscript index 8cad4639..7e9ce673 --- a/HPC_startscripts/workflow_startscript +++ b/use-cases/3dgan/startscript @@ -13,8 +13,9 @@ #SBATCH --partition=batch #SBATCH --nodes=2 #SBATCH --ntasks-per-node=1 -#SBATCH --cpus-per-task=1 +#SBATCH --cpus-per-task=4 #SBATCH --gpus-per-node=4 + #SBATCH --exclusive # gres options have to be disabled for deepv @@ -26,8 +27,8 @@ ml Stages/2023 StdEnv/2023 NVHPC/23.1 OpenMPI/4.1.4 cuDNN/8.6.0.163-CUDA-11.7 Py # shellcheck source=/dev/null source ~/.bashrc -#set env -micromamba activate ../.venv - -srun conda run -p ../.venv python run-workflow.py -f ../use-cases/mnist/training-workflow.yml +# ON LOGIN NODE download datasets: +# ../../.venv-pytorch/bin/itwinai exec-pipeline --config pipeline.yaml --pipe-key training_pipeline --steps dataloading_step +source ../../.venv-pytorch/bin/activate +srun itwinai exec-pipeline --config pipeline.yaml --pipe-key training_pipeline \ No newline at end of file diff --git a/use-cases/3dgan/trainer.py b/use-cases/3dgan/trainer.py new file mode 100644 index 00000000..f51b5d5a --- /dev/null +++ b/use-cases/3dgan/trainer.py @@ -0,0 +1,157 @@ +import os +import sys +from typing import Union, Dict, Optional, Any + +import torch +from torch import Tensor +import lightning as pl +from lightning.pytorch.cli import LightningCLI + +from itwinai.components import Trainer, Predictor, monitor_exec +from itwinai.serialization import ModelLoader +from itwinai.torch.inference import TorchModelLoader +from itwinai.torch.types import Batch +from itwinai.utils import load_yaml +from itwinai.torch.mlflow import ( + init_lightning_mlflow, + teardown_lightning_mlflow +) + + +from model import ThreeDGAN +from dataloader import ParticlesDataModule + + +class Lightning3DGANTrainer(Trainer): + def __init__(self, config: Union[Dict, str], exp_root: str = '.'): + self.save_parameters(**self.locals2params(locals())) + super().__init__() + if isinstance(config, str) and os.path.isfile(config): + # Load from YAML + config = load_yaml(config) + self.conf = config + self.exp_root = exp_root + + @monitor_exec + def execute(self) -> Any: + init_lightning_mlflow( + self.conf, + tmp_dir=os.path.join(self.exp_root, '.tmp'), + registered_model_name='3dgan-lite' + ) + old_argv = sys.argv + sys.argv = ['some_script_placeholder.py'] + cli = LightningCLI( + args=self.conf, + model_class=ThreeDGAN, + datamodule_class=ParticlesDataModule, + run=False, + save_config_kwargs={ + "overwrite": True, + "config_filename": "pl-training.yml", + }, + subclass_mode_model=True, + subclass_mode_data=True, + ) + sys.argv = old_argv + cli.trainer.fit(cli.model, datamodule=cli.datamodule) + teardown_lightning_mlflow() + + +class LightningModelLoader(TorchModelLoader): + """Loads a torch lightning model from somewhere. + + Args: + model_uri (str): Can be a path on local filesystem + or an mlflow 'locator' in the form: + 'mlflow+MLFLOW_TRACKING_URI+RUN_ID+ARTIFACT_PATH' + """ + + def __call__(self) -> pl.LightningModule: + """"Loads model from model URI. + + Raises: + ValueError: if the model URI is not recognized + or the model is not found. + + Returns: + pl.LightningModule: torch lightning module. + """ + # TODO: improve + # # Load best model + # loaded_model = cli.model.load_from_checkpoint( + # ckpt_path, + # lightning_conf['model']['init_args'] + # ) + return super().__call__() + + +class Lightning3DGANPredictor(Predictor): + + def __init__( + self, + model: Union[ModelLoader, pl.LightningModule], + config: Union[Dict, str], + name: Optional[str] = None + ): + self.save_parameters(**self.locals2params(locals())) + super().__init__(model, name) + if isinstance(config, str) and os.path.isfile(config): + # Load from YAML + config = load_yaml(config) + self.conf = config + + @monitor_exec + def execute( + self, + datamodule: Optional[pl.LightningDataModule] = None, + model: Optional[pl.LightningModule] = None + ) -> Dict[str, Tensor]: + old_argv = sys.argv + sys.argv = ['some_script_placeholder.py'] + cli = LightningCLI( + args=self.conf, + model_class=ThreeDGAN, + datamodule_class=ParticlesDataModule, + run=False, + save_config_kwargs={ + "overwrite": True, + "config_filename": "pl-training.yml", + }, + subclass_mode_model=True, + subclass_mode_data=True, + ) + sys.argv = old_argv + + # Override config file with inline arguments, if given + if datamodule is None: + datamodule = cli.datamodule + if model is None: + model = cli.model + + predictions = cli.trainer.predict(model, datamodule=datamodule) + + # Transpose predictions into images, energies and angles + images = torch.cat(list(map( + lambda pred: self.transform_predictions( + pred['images']), predictions + ))) + energies = torch.cat(list(map( + lambda pred: pred['energies'], predictions + ))) + angles = torch.cat(list(map( + lambda pred: pred['angles'], predictions + ))) + + predictions_dict = dict() + for img, en, ang in zip(images, energies, angles): + sample_key = f"energy={en.item()}&angle={ang.item()}" + predictions_dict[sample_key] = img + + return predictions_dict + + def transform_predictions(self, batch: Batch) -> Batch: + """ + Post-process the predictions of the torch model. + """ + return batch.squeeze(1) diff --git a/use-cases/README.md b/use-cases/README.md new file mode 100644 index 00000000..7c9ed4a9 --- /dev/null +++ b/use-cases/README.md @@ -0,0 +1,33 @@ +# interTwin use cases integrated into itwinai + +Show how `itwinai` can be used to support scientific use cases. Each use case folder contains: + +- A YAML configuration file describing the ML workflows for that use case. +- A SLURM job script, used to execute the ML workflows on a SLURM-based cluster. +- `requirements.txt`: (optional) use case-specific requirements. can be installed with: + + ```bash + cd use/case/folder + # After activating the correct environment... + pip install -r requirements.txt + ``` + +## How to run a use case + +First, create the use case's Python environment (i.e., PyTorch or TensorFlow) +as described [in the main README](../README.md#environment-setup), and activate it. +Then, install use case-specific dependencies, if any: + +```bash +pip install -r /use/case/path/requirements.txt +``` + +Alternatively, you can use the use case Docker image, if available. + +Then, go to the use case's directory: + +```bash +cd use/case/path +``` + +From there you can run the use case following the instruction provided in the use case's folder. diff --git a/use-cases/cyclones/.gitignore b/use-cases/cyclones/.gitignore new file mode 100644 index 00000000..255b69f5 --- /dev/null +++ b/use-cases/cyclones/.gitignore @@ -0,0 +1,2 @@ +data +experiments \ No newline at end of file diff --git a/use-cases/cyclones/README.md b/use-cases/cyclones/README.md new file mode 100644 index 00000000..0e3d9f9d --- /dev/null +++ b/use-cases/cyclones/README.md @@ -0,0 +1,47 @@ +# Tropical cyclone detection + +The code is adapted from the CMCC use case's +[repository](https://github.com/CMCC-Foundation/ml-tropical-cyclones-detection). + +## Setup env + +```bash +# After activating the environment +pip install -r requirements.txt +``` + +## Dataset + +If the automatic download from python does not work, try from the command line from +within the virtual environment: + +```bash +gdown https://drive.google.com/drive/folders/1TnmujO4T-8_j4bCxqNe5HEw9njJIIBQD -O data/tmp_data/trainval --folder +``` + +For more info visit the [gdown](https://github.com/wkentaro/gdown) repository. + +## Training + +Launch training: + +```bash +# # ONLY IF tensorflow>=2.16 +# export TF_USE_LEGACY_KERAS=1 + +source ../../.venv-tf/bin/activate +python train.py -p pipeline.yaml +``` + +On JSC, the dataset is pre-downloaded and you can use the following command: + +```bash +# # ONLY IF tensorflow>=2.16 +# export TF_USE_LEGACY_KERAS=1 + +source ../../envAItf_hdfml/bin/activate +python train.py -p pipeline.yaml --data_path /p/project/intertwin/smalldata/cmcc + +# Launch a job with SLURM +sbatch startscript.sh +``` diff --git a/use-cases/cyclones/cyclones_vgg.py b/use-cases/cyclones/cyclones_vgg.py new file mode 100644 index 00000000..a2272505 --- /dev/null +++ b/use-cases/cyclones/cyclones_vgg.py @@ -0,0 +1,729 @@ +import tensorflow as tf + + +def custom_VGG_V1(patch_size, channels, activation, regularizer): + model = tf.keras.Sequential() + + model.add( + tf.keras.layers.Conv2D( + input_shape=(patch_size, patch_size, channels[0]), + filters=64, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=64, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=64, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) + + model.add( + tf.keras.layers.Conv2D( + filters=128, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=128, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) + + model.add( + tf.keras.layers.Conv2D( + filters=256, + kernel_size=(2, 2), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=256, + kernel_size=(2, 2), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) + + model.add( + tf.keras.layers.Conv2D( + filters=512, + kernel_size=(2, 2), + padding="valid", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=512, + kernel_size=(2, 2), + padding="valid", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=512, + kernel_size=(2, 2), + padding="valid", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2))) + + model.add(tf.keras.layers.Flatten()) + model.add( + tf.keras.layers.Dense( + units=512, activation=activation, kernel_regularizer=regularizer + ) + ) + model.add( + tf.keras.layers.Dense( + units=256, activation=activation, kernel_regularizer=regularizer + ) + ) + model.add( + tf.keras.layers.Dense( + units=128, activation=activation, kernel_regularizer=regularizer + ) + ) + model.add( + tf.keras.layers.Dense( + units=64, activation=activation, kernel_regularizer=regularizer + ) + ) + model.add(tf.keras.layers.Dense(channels[1])) + + return model + + +def custom_VGG_V2(patch_size, channels, activation, regularizer): + model = tf.keras.Sequential() + + model.add( + tf.keras.layers.Input(shape=(patch_size, patch_size, channels[0])) + ) + + model.add( + tf.keras.layers.Conv2D( + filters=32, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=32, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=32, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) + + model.add( + tf.keras.layers.Conv2D( + filters=64, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=64, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=64, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) + + model.add( + tf.keras.layers.Conv2D( + filters=128, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=128, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=128, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) + + model.add( + tf.keras.layers.Conv2D( + filters=256, + kernel_size=(2, 2), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=256, + kernel_size=(2, 2), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=256, + kernel_size=(2, 2), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add( + tf.keras.layers.Conv2D( + filters=512, + kernel_size=(2, 2), + padding="valid", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=512, + kernel_size=(2, 2), + padding="valid", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=512, + kernel_size=(2, 2), + padding="valid", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=512, + kernel_size=(2, 2), + padding="valid", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add(tf.keras.layers.Flatten()) + + model.add( + tf.keras.layers.Dense( + units=1024, activation=activation, kernel_regularizer=regularizer + ) + ) + model.add( + tf.keras.layers.Dense( + units=512, activation=activation, kernel_regularizer=regularizer + ) + ) + model.add( + tf.keras.layers.Dense( + units=256, activation=activation, kernel_regularizer=regularizer + ) + ) + model.add( + tf.keras.layers.Dense( + units=128, activation=activation, kernel_regularizer=regularizer + ) + ) + + model.add(tf.keras.layers.Dense(channels[1])) + + return model + + +def custom_VGG_V3(patch_size, channels, activation, regularizer): + model = tf.keras.Sequential() + + model.add(tf.keras.layers.Input( + shape=(patch_size, patch_size, channels[0]))) + + model.add( + tf.keras.layers.Conv2D( + filters=32, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=32, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=32, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) + + model.add( + tf.keras.layers.Conv2D( + filters=64, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=64, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=64, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) + + model.add( + tf.keras.layers.Conv2D( + filters=128, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=128, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=128, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))) + + model.add( + tf.keras.layers.Conv2D( + filters=256, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=256, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=256, + kernel_size=(3, 3), + padding="same", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add( + tf.keras.layers.Conv2D( + filters=512, + kernel_size=(2, 2), + padding="valid", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=512, + kernel_size=(2, 2), + padding="valid", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add( + tf.keras.layers.Conv2D( + filters=1024, + kernel_size=(2, 2), + padding="valid", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + model.add( + tf.keras.layers.Conv2D( + filters=1024, + kernel_size=(2, 2), + padding="valid", + activation=activation, + kernel_regularizer=regularizer, + ) + ) + + model.add(tf.keras.layers.Flatten()) + + model.add( + tf.keras.layers.Dense( + units=1024, activation=activation, kernel_regularizer=regularizer + ) + ) + model.add( + tf.keras.layers.Dense( + units=512, activation=activation, kernel_regularizer=regularizer + ) + ) + model.add( + tf.keras.layers.Dense( + units=512, activation=activation, kernel_regularizer=regularizer + ) + ) + model.add( + tf.keras.layers.Dense( + units=256, activation=activation, kernel_regularizer=regularizer + ) + ) + + model.add(tf.keras.layers.Dense(channels[1])) + + return model + + +""" +def VGG_V4(patch_size, label_no_cyclone, channels, activation, regularizer): + model = tf.keras.Sequential() + + model.add(tf.keras.layers.Input(shape=(patch_size, patch_size, + channels[0]))) + + model.add(tf.keras.layers.Conv2D(filters=32, kernel_size=(3,3), + padding="same", activation=activation, kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Conv2D(filters=32, kernel_size=(3,3), + padding="same", activation=activation, kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Conv2D(filters=32, kernel_size=(3,3), + padding="same", activation=activation, kernel_regularizer=regularizer)) + + model.add(tf.keras.layers.MaxPooling2D(pool_size=(2,2), strides=(2,2))) + + model.add(tf.keras.layers.Conv2D(filters=64, kernel_size=(3,3), + padding="same", activation=activation, kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Conv2D(filters=64, kernel_size=(3,3), + padding="same", activation=activation, kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Conv2D(filters=64, kernel_size=(3,3), + padding="same", activation=activation, kernel_regularizer=regularizer)) + + model.add(tf.keras.layers.MaxPooling2D(pool_size=(2,2), strides=(2,2))) + + model.add(tf.keras.layers.Conv2D(filters=128, kernel_size=(3,3), + padding="same", activation=activation, kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Conv2D(filters=128, kernel_size=(3,3), + padding="same", activation=activation, kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Conv2D(filters=128, kernel_size=(3,3), + padding="same", activation=activation, kernel_regularizer=regularizer)) + + model.add(tf.keras.layers.MaxPooling2D(pool_size=(2,2), strides=(2,2))) + + model.add(tf.keras.layers.Conv2D(filters=256, kernel_size=(2,2), + padding="same", activation=activation, kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Conv2D(filters=256, kernel_size=(2,2), + padding="same", activation=activation, kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Conv2D(filters=256, kernel_size=(2,2), + padding="same", activation=activation, kernel_regularizer=regularizer)) + + model.add(tf.keras.layers.Conv2D(filters=512, kernel_size=(2,2), + padding="valid", activation=activation, kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Conv2D(filters=512, kernel_size=(2,2), + padding="valid", activation=activation, kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Conv2D(filters=512, kernel_size=(2,2), + padding="valid", activation=activation, kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Conv2D(filters=512, kernel_size=(2,2), + padding="valid", activation=activation, kernel_regularizer=regularizer)) + + model.add(tf.keras.layers.Flatten()) + + model.add(tf.keras.layers.Dense(units=1024, activation=activation, + kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Dense(units=512, activation=activation, + kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Dense(units=256, activation=activation, + kernel_regularizer=regularizer)) + model.add(tf.keras.layers.Dense(units=128, activation=activation, + kernel_regularizer=regularizer)) + + model.add(tf.keras.layers.Dense(channels[1])) + model.add(PositionDiscretizationLayer(label_no_cyclone=label_no_cyclone, + patch_size=patch_size)) + + return model +""" + + +def ModelV5(patch_size, channels, last_activation, kernel_size=3): + # kernel initializer + initializer = tf.random_normal_initializer(0.0, 0.02) + + # input layer + inputs = tf.keras.layers.Input(shape=(patch_size, patch_size, +channels[0])) + + conv_blocks = [ + ConvBlock( + filters=32, + initializer=initializer, + kernel_size=kernel_size, + strides=2, + apply_batchnorm=True, + apply_dropout=False, + apply_gaussian_noise=True, + ), + ConvBlock( + filters=64, + initializer=initializer, + kernel_size=kernel_size, + strides=2, + apply_batchnorm=False, + apply_dropout=False, + apply_gaussian_noise=False, + ), + ConvBlock( + filters=128, + initializer=initializer, + kernel_size=3, + strides=2, + apply_batchnorm=False, + apply_dropout=True, + apply_gaussian_noise=False, + ), + ConvBlock( + filters=256, + initializer=initializer, + kernel_size=3, + strides=2, + apply_batchnorm=False, + apply_dropout=False, + apply_gaussian_noise=True, + ), + ConvBlock( + filters=512, + initializer=initializer, + kernel_size=3, + strides=2, + apply_batchnorm=False, + apply_dropout=False, + apply_gaussian_noise=False, + ), + ConvBlock( + filters=1024, + initializer=initializer, + kernel_size=3, + strides=2, + apply_batchnorm=True, + apply_dropout=True, + apply_gaussian_noise=False, + ), + ] + x = inputs + for block in conv_blocks: + x = block(x) + + x = tf.keras.layers.Flatten()(x) + x = tf.keras.layers.Dense( + units=1024, activation="relu", kernel_initializer=initializer + )(x) + x = tf.keras.layers.Dense( + units=512, activation="relu", kernel_initializer=initializer + )(x) + x = tf.keras.layers.Dense( + units=256, activation="relu", kernel_initializer=initializer + )(x) + x = tf.keras.layers.Dense( + units=128, activation="relu", kernel_initializer=initializer + )(x) + + outputs = tf.keras.layers.Dense( + channels[1], activation=last_activation, +kernel_initializer=initializer + )(x) + + return tf.keras.Model(inputs=inputs, outputs=outputs, name="model_V5") + + +""" +from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense +from tensorflow.keras.models import Sequential +def custom_VGG_V1(patch_size, channels, activation, regularizer): + model = Sequential() + #model.add(Conv2D(input_shape=(40,40,len(variables_list)), filters=64, + kernel_size=(3,3), padding="same", activation=activation, + kernel_regularizer=regularizer)) + model.add(Conv2D(input_shape=(patch_size,patch_size,channels[0]), + filters=64, kernel_size=(3,3), padding="same", activation=activation, + kernel_regularizer=regularizer)) + model.add(Conv2D(filters=64, kernel_size=(3,3), padding="same", + activation=activation, kernel_regularizer=regularizer)) + model.add(Conv2D(filters=64, kernel_size=(3,3), padding="same", + activation=activation, kernel_regularizer=regularizer)) + model.add(MaxPooling2D(pool_size=(2,2), strides=(2,2))) + model.add(Conv2D(filters=128, kernel_size=(3,3), padding="same", + activation=activation, kernel_regularizer=regularizer)) + model.add(Conv2D(filters=128, kernel_size=(3,3), padding="same", + activation=activation, kernel_regularizer=regularizer)) + model.add(MaxPooling2D(pool_size=(2,2), strides=(2,2))) + model.add(Conv2D(filters=256, kernel_size=(2,2), padding="same", + activation=activation, kernel_regularizer=regularizer)) + model.add(Conv2D(filters=256, kernel_size=(2,2), padding="same", + activation=activation, kernel_regularizer=regularizer)) + #model.add(Conv2D(filters=256, kernel_size=(2,2), padding="same", + activation=activation, kernel_regularizer=regularizer)) + model.add(MaxPooling2D(pool_size=(2,2), strides=(2,2))) + model.add(Conv2D(filters=512, kernel_size=(2,2), padding="valid", + activation=activation, kernel_regularizer=regularizer)) + model.add(Conv2D(filters=512, kernel_size=(2,2), padding="valid", + activation=activation, kernel_regularizer=regularizer)) + model.add(Conv2D(filters=512, kernel_size=(2,2), padding="valid", + activation=activation, kernel_regularizer=regularizer)) + model.add(MaxPooling2D(pool_size=(2,2))) #,strides=(2,2))) + # model.add(Conv2D(filters=512, kernel_size=(3,3), padding="same", + activation="relu")) + # model.add(Conv2D(filters=512, kernel_size=(3,3), padding="same", + activation="relu")) + # model.add(Conv2D(filters=512, kernel_size=(3,3), padding="same", + activation="relu")) + # model.add(MaxPooling2D(pool_size=(2,2),strides=(2,2))) + model.add(Flatten()) + model.add(Dense(units=512, activation=activation, + kernel_regularizer=regularizer)) + model.add(Dense(units=256, activation=activation, + kernel_regularizer=regularizer)) + model.add(Dense(units=128, activation=activation, + kernel_regularizer=regularizer)) + model.add(Dense(units=64, activation=activation, + kernel_regularizer=regularizer)) + model.add(Dense(channels[1])) + + return model +""" diff --git a/use-cases/cyclones/dataloader.py b/use-cases/cyclones/dataloader.py new file mode 100644 index 00000000..6238e499 --- /dev/null +++ b/use-cases/cyclones/dataloader.py @@ -0,0 +1,223 @@ +from typing import List, Dict, Optional +from os import listdir +from os.path import join, exists + +import gdown +import numpy as np + +from itwinai.components import DataGetter, monitor_exec + +from src.macros import ( + PatchType, + LabelNoCyclone, + AugmentationType, +) +from src.tfrecords.functions import read_tfrecord_as_tensor +from src.scaling import save_tf_minmax +from src.tfrecords.dataset import eFlowsTFRecordDataset +from src.transform import ( + coo_left_right, + coo_up_down, + coo_rot180, + msk_left_right, + msk_up_down, + msk_rot180, +) + + +class CyclonesDataGetter(DataGetter): + def __init__( + self, + dataset_url: str, + patch_type: PatchType, + shuffle: bool, + split_ratio: List[float], + augment: bool, + epochs: int, + target_scale: bool, + label_no_cyclone: LabelNoCyclone, + aug_type: AugmentationType, + experiment: Dict, + global_config: Dict, + shuffle_buffer: Optional[int] = None, + dataset_root: str = "tmp_data", + tfrecords_dir: str = "trainval" + ): + super().__init__() + self.save_parameters(**self.locals2params(locals())) + self.dataset_url = dataset_url + self.split_ratio = split_ratio + self.epochs = epochs + self.target_scale = target_scale + self.label_no_cyclone = label_no_cyclone.value + self.shuffle_buffer = shuffle_buffer + self.aug_type = aug_type.value + self.patch_type = patch_type.value + self.augment = augment + self.global_config = global_config + self.shuffle = shuffle + self.dataset_root = dataset_root + self.tfrecords_dir = tfrecords_dir + self.drv_vars, self.coo_vars = ( + experiment["DRV_VARS_1"], + experiment["COO_VARS_1"], + ) + self.msk_var = ( + None if experiment["MSK_VAR_1"] == "None" + else experiment["MSK_VAR_!"] + ) + self.channels = [len(self.drv_vars), len(self.coo_vars)] + + # Shuffle + if shuffle: + np.random.shuffle(self.cyclone_files) + np.random.shuffle(self.adj_files) + np.random.shuffle(self.random_files) + + # Patches types + if self.augment: + if self.msk_var: + self.aug_fns = { + "left_right": msk_left_right, + "up_down": msk_up_down, + "rot180": msk_rot180, + } + else: + self.aug_fns = { + "left_right": coo_left_right, + "up_down": coo_up_down, + "rot180": coo_rot180, + } + else: + self.aug_fns = {} + + # Parse global config + self.setup_config(self.global_config) + + def setup_config(self, config: Dict) -> None: + self.shape = config["shape"] + root_dir = config["root_dir"] + + # Download data + if not exists(self.dataset_root): + gdown.download_folder( + url=self.dataset_url, quiet=False, + # verify=False, + output=self.dataset_root + ) + + # Scalar fields + self.root_dir = root_dir + self.tfrecords_path = join(self.dataset_root, + self.tfrecords_dir) + self.scaler_file = join(config["scaler_dir"], "minmax.tfrecord") + + # get records filenames + self.cyclone_files = sorted( + [ + join(self.tfrecords_path, f) + for f in listdir(self.tfrecords_path) + if f.endswith(".tfrecord") and f.startswith( + PatchType.CYCLONE.value) + ] + ) + if self.patch_type == PatchType.NEAREST.value: + self.adj_files = sorted( + [ + join(self.tfrecords_path, f) + for f in listdir(self.tfrecords_path) + if f.endswith(".tfrecord") and f.startswith( + PatchType.NEAREST.value) + ] + ) + elif self.patch_type == PatchType.ALLADJACENT.value: + self.adj_files = sorted( + [ + join(self.tfrecords_path, f) + for f in listdir(self.tfrecords_path) + if f.endswith(".tfrecord") + and f.startswith(PatchType.ALLADJACENT.value) + ] + ) + self.random_files = sorted( + [ + join(self.tfrecords_path, f) + for f in listdir(self.tfrecords_path) + if f.endswith(".tfrecord") and f.startswith( + PatchType.RANDOM.value) + ] + ) + + def split_files(self, files, ratio): + n = len(files) + return ( + files[0: int(ratio[0] * n)], + files[int(ratio[0] * n): int((ratio[0] + ratio[1]) * n)], + ) + + @monitor_exec + def execute(self): + # divide into train, valid and test dataset files + train_c_fs, valid_c_fs = self.split_files( + files=self.cyclone_files, ratio=self.split_ratio + ) + train_a_fs, valid_a_fs = self.split_files( + files=self.adj_files, ratio=self.split_ratio + ) + train_r_fs, valid_r_fs = self.split_files( + files=self.random_files, ratio=self.split_ratio + ) + + # merge all the files together + train_files = train_c_fs + train_a_fs + train_r_fs + # valid_files = valid_c_fs + valid_a_fs + valid_r_fs + + # compute scaler on training data + Xt, _ = read_tfrecord_as_tensor( + filenames=train_files, + shape=self.shape, + drv_vars=self.drv_vars, + coo_vars=self.coo_vars, + msk_var=self.msk_var, + ) + X_scaler = save_tf_minmax(Xt.numpy(), outfile=self.scaler_file) + scalers = [X_scaler, None] + Xt = None + + # instantiate training, validation and test sets + # Contains: (dataset, n_count) + train_dataset = eFlowsTFRecordDataset( + cyc_fnames=train_c_fs, + adj_fnames=train_a_fs, + rnd_fnames=train_r_fs, + epochs=self.epochs, + scalers=scalers, + target_scale=self.target_scale, + shape=self.shape, + label_no_cyclone=self.label_no_cyclone, + drv_vars=self.drv_vars, + coo_vars=self.coo_vars, + msk_var=self.msk_var, + shuffle_buffer=self.shuffle_buffer, + aug_fns=self.aug_fns, + patch_type=self.patch_type, + aug_type=self.aug_type, + ) + valid_dataset = eFlowsTFRecordDataset( + cyc_fnames=valid_c_fs, + adj_fnames=valid_a_fs, + rnd_fnames=valid_r_fs, + epochs=self.epochs, + scalers=scalers, + target_scale=self.target_scale, + shape=self.shape, + label_no_cyclone=self.label_no_cyclone, + drv_vars=self.drv_vars, + coo_vars=self.coo_vars, + msk_var=self.msk_var, + shuffle_buffer=self.shuffle_buffer, + aug_fns=self.aug_fns, + patch_type=self.patch_type, + aug_type=self.aug_type, + ) + return train_dataset, valid_dataset, self.channels diff --git a/use-cases/cyclones/pipeline.yaml b/use-cases/cyclones/pipeline.yaml new file mode 100644 index 00000000..b29f38ce --- /dev/null +++ b/use-cases/cyclones/pipeline.yaml @@ -0,0 +1,45 @@ +# General configuration +epochs: 3 +micro_batch_size: 32 +dataset_url: https://drive.google.com/drive/folders/1TnmujO4T-8_j4bCxqNe5HEw9njJIIBQD #https://drive.google.com/drive/folders/15DEq33MmtRvIpe2bNCg44lnfvEiHcPaf +dataset_root: tmp_cyclones_data +verbose: auto +global_config: null + +# Workflows +training_pipeline: + class_path: itwinai.pipeline.Pipeline + init_args: + steps: + download-step: + class_path: dataloader.CyclonesDataGetter + init_args: + dataset_url: ${dataset_url} + dataset_root: ${dataset_root} + global_config: ${global_config} + patch_type: NEAREST + shuffle: False + split_ratio: [0.75, 0.25] + augment: True + epochs: ${epochs} + target_scale: False + label_no_cyclone: NONE + aug_type: ONLY_TCS + experiment: { + 'DRV_VARS_1': ['fg10', 'msl', 't_500', 't_300'], + 'COO_VARS_1': ['patch_cyclone'], + 'MSK_VAR_1': None + } + + training-step: + class_path: trainer.CyclonesTrainer + init_args: + epochs: ${epochs} + micro_batch_size: ${micro_batch_size} + global_config: ${global_config} + network: VGG_V1 + activation: LINEAR + regularization_strength: NONE + learning_rate: 0.0001 + loss: MAE + verbose: ${verbose} diff --git a/use-cases/cyclones/requirements.txt b/use-cases/cyclones/requirements.txt new file mode 100644 index 00000000..023006ca --- /dev/null +++ b/use-cases/cyclones/requirements.txt @@ -0,0 +1 @@ +gdown \ No newline at end of file diff --git a/use-cases/cyclones/src/callbacks.py b/use-cases/cyclones/src/callbacks.py new file mode 100644 index 00000000..42c530d0 --- /dev/null +++ b/use-cases/cyclones/src/callbacks.py @@ -0,0 +1,73 @@ +import psutil +import os +import time +import pandas as pd +import tensorflow as tf + + +class ProcessBenchmark(tf.keras.callbacks.Callback): + """ + Gets several benchmarks parameters about the process during the training, + including execution time. + + Parameters + ---------- + filename : pathlike + The file in which will be put the resulting history. + + """ + + def __init__(self, filename): + # get process pid + self.pid = os.getpid() + # instantiate a process object from which we'll get status information + self.process = psutil.Process(self.pid) + # get initial process cpu percent, so that the next call returns + # the correct cpu percentage + self.process.cpu_percent() + + self.filename = filename + # create time history dataframe + self.benchmark_df = pd.DataFrame(columns=[ + 'time', + 'start_cpu_percent', + 'end_cpu_percent', + 'start_mem_rss', + 'end_mem_rss', + 'start_mem_vms', + 'end_mem_vms', + 'start_mem_uss', + 'end_mem_uss', + ]) + # save the dataframe to csv file + self.benchmark_df.to_csv(self.filename) + + def on_epoch_begin(self, batch, logs={}): + self.epoch_time = -time.time() + self.start_cpu_percent = self.process.cpu_percent() + mem_info = self.process.memory_full_info() + self.start_mem_rss = mem_info[0] / float(2 ** 20) + self.start_mem_vms = mem_info[1] / float(2 ** 20) + self.start_mem_uss = mem_info[3] / float(2 ** 20) + + def on_epoch_end(self, batch, logs={}): + self.epoch_time += time.time() + self.end_cpu_percent = self.process.cpu_percent() + mem_info = self.process.memory_full_info() + self.end_mem_rss = mem_info[0] / float(2 ** 20) + self.end_mem_vms = mem_info[1] / float(2 ** 20) + self.end_mem_uss = mem_info[3] / float(2 ** 20) + + self.benchmark_df.loc[len(self.benchmark_df.index)] = [ + self.epoch_time, + self.start_cpu_percent, + self.end_cpu_percent, + self.start_mem_rss, + self.end_mem_rss, + self.start_mem_vms, + self.end_mem_vms, + self.start_mem_uss, + self.end_mem_uss, + ] + + self.benchmark_df.to_csv(self.filename) diff --git a/use-cases/cyclones/src/macros.py b/use-cases/cyclones/src/macros.py new file mode 100644 index 00000000..5dd8fbe2 --- /dev/null +++ b/use-cases/cyclones/src/macros.py @@ -0,0 +1,107 @@ +import tensorflow as tf +from enum import Enum + +# default loss configuration reduction +REDUCTION = tf.keras.losses.Reduction.NONE + +#  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# NetCFD Variables # +# # +#  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +# TUTTE LE VARIABILI DISPONIBILI +ALL_DRIVER_VARS = ['fg10', 'i10fg', 'msl', 'sst', 't_500', 't_300', 'vo_850'] +ALL_COORDINATE_VARS = ['real_cyclone', + 'rounded_cyclone', 'global_cyclone', 'patch_cyclone'] +CYCLONE_VAR = 'patch_cyclone' +MASK_VAR = 'cyclone_mask' + +# ESPERIMENTI TIPO 1 +# variabili per la prima parte degli esperimenti (regressione per trovare +# coordinate row-col intra-patch) +EXPERIMENT_1 = { + 'DRV_VARS_1': ['fg10', 'msl', 't_500', 't_300'], + 'COO_VARS_1': ['patch_cyclone'], + 'MSK_VAR_1': None +} + +# dataset parameters +PATCH_SIZE = 40 +SHAPE = (PATCH_SIZE, PATCH_SIZE) + +TEST_YEARS = [] # for test purposes +TRAINVAL_YEARS = [2000, 2001, 2002] # for test purposes + + +#  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# Enumerations # +# # +#  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +# descrive il tipo di patch che bisogna prendere +class PatchType(Enum): + ALLADJACENT = 'alladjacent' + CYCLONE = 'cyclone' + NEAREST = 'nearest' + RANDOM = 'random' + NOCYCLONE = 'nocyclone' + +# descrive il tipo di augmentation che deve essere effettuata + + +class AugmentationType(Enum): + ALL_PATCHES = 'all_patches' + ONLY_TCS = 'only_tcs' + +# descrive il nome del modello di rete neurale da utilizzare + + +class Network(Enum): + VGG_V1 = 'vgg_v1' # map-to-coord + VGG_V2 = 'vgg_v2' # map-to-coord + VGG_V3 = 'vgg_v3' # map-to-coord + MODEL_V5 = 'model_v5' # map-to-coord + +# ritorna nome della loss utilizzata in fase di training + + +class Losses(Enum): + # Mean Absolute Error + MAE = ('mae', 'mae') + # Mean Squared Error + MSE = ('mse', 'mse') + # No specified loss + NONE = ('none', None) + +# descrive la forza della regolarizzazione + + +class RegularizationStrength(Enum): + WEAK = ('weak', tf.keras.regularizers.l1_l2( + l1=0.0, l2=0.0001)) # l1=0 - l2=0.0001 + MEDIUM = ('medium', tf.keras.regularizers.l1_l2( + l1=0.0001, l2=0.0001)) # l1=0.0001 - l2=0.0001 + STRONG = ('strong', tf.keras.regularizers.l1_l2( + l1=0.001, l2=0.001)) # l1=0.001 - l2=0.001 + VERY_STRONG = ('very_strong', tf.keras.regularizers.l1_l2( + l1=0.01, l2=0.01)) # l1=0.01 - l2=0.01 + NONE = ('none', None) # no regularization + +# descrive l'attivazione dell'ultimo layer del modello + + +class Activation(Enum): + RELU = 'relu' + LINEAR = 'linear' + SIGMOID = 'sigmoid' + TANH = 'tanh' + +# label assegnata ad un ciclone assente + + +class LabelNoCyclone(Enum): + ZERO_3 = -0.3 + ONE = -1.0 + NONE = None diff --git a/use-cases/cyclones/src/scaling.py b/use-cases/cyclones/src/scaling.py new file mode 100644 index 00000000..a3b9b4c6 --- /dev/null +++ b/use-cases/cyclones/src/scaling.py @@ -0,0 +1,297 @@ +from sklearn.preprocessing import MinMaxScaler +import tensorflow as tf +import joblib + + +def fit_transform( + volume, shape, channel, feature_range=(0, 1), + type='minmax', save=False, filename=None +): + """ + Creates the scaler on the input volume and scales the data + + Parameters + ---------- + volume : np.array + Input 4-dimensional data volume. + shape : (int, int) + Height and width of the input volume. + channel : int + Number of channels of the input volume. + feature_range : (int, int) | (0,1) + Desired range of the scaled features. Default is (0,1). + type : {'minmax', 'std', ...} + Type of scaling. Default MinMax scaler + save : bool | False + Whether or not to save the computed scaler. Default to false + filename : pathlike + The filename to which the scaler must be saved to disk. Checked only + if 'save' is set to True. + + Returns + ------- + volume : np.array + Scaled 4-dimensional input volume. + scaler : scikit-learn scaler + Computed scaler. + """ + + can_save = False + if save: + if filename is None: + raise ValueError( + 'Must specify the filename when saving the scaler') + else: + can_save = True + + if type == 'minmax': + scaler = MinMaxScaler(feature_range=feature_range) + + volume = scaler.fit_transform( + volume.reshape(-1, channel)).reshape(-1, *shape, channel) + + if can_save: + joblib.dump(scaler, filename) + + return volume, scaler + + +def inv_transform(scaled_image, scaler, shape, channel): + """ + Scale the data from scaler's feature_range to data range + + Parameters + ---------- + scaled_image : np.array + 4-dimensional input data volume. + scaler : scikit-learn scaler + Desired data scaler. + shape : tuple + (int, int) height and width of the input volume. + channel : int + Number of channels of the input volume. + + Returns + ------- + image : np.array + Scaled input volume. + """ + return scaler.inverse_transform( + scaled_image.reshape(-1, channel) + ).reshape(*shape) + + +def transform(image, scaler, shape, channel): + """ + Scale the data from data range to scaler's feature range + + Parameters + ---------- + image : np.array + 4-dimensional input data volume. + scaler : scikit-learn scaler + Desired data scaler. + shape : tuple + (int, int) height and width of the input volume. + channel : int + Number of channels of the input volume. + + Returns + ------- + scaled_image : np.array + Scaled input volume. + """ + return scaler.transform( + image.reshape(-1, channel) + ).reshape(-1, *shape, channel) + + +def get_scalers(scaler_X_file=None, scaler_y_file=None): + """ + Reads the X and y scalers from file + Input: + @ (string) scaler_X_file : X data scaler of joblib. + None if data must not be scaled + @ (string) scaler_y_file : y data scaler of joblib. + None if data must not be scaled + Output: + @ ([scaler,scaler]) scalers : loaded data scalers + """ + X_scaler = None + y_scaler = None + if scaler_X_file: + X_scaler = joblib.load(scaler_X_file) + if scaler_y_file: + y_scaler = joblib.load(scaler_y_file) + return [X_scaler, y_scaler] + + +def save_tf_minmax(Xt, outfile): + """ + Saves a MinMax Scaler as a Tensorflow Record. + + """ + def tensor_feature(value): + """Returns a bytes_list from a string / byte.""" + return tf.train.Feature(bytes_list=tf.train.BytesList( + value=[tf.io.serialize_tensor(tf.convert_to_tensor(value)).numpy()] + ) + ) + + def scaler_encoding_fn(min, max): + """Builds a serialized version of the dataset. X and y + must be np.array. + """ + features = tf.train.Features(feature={ + "min": tensor_feature(min), + "max": tensor_feature(max), + }) + return tf.train.Example(features=features).SerializeToString() + + def write_record_to_file(min, max, record_file): + with tf.io.TFRecordWriter(record_file) as writer: + record = scaler_encoding_fn(min=min, max=max) + writer.write(record) + return + + # passed Xt is a numpy array of shape N x H x W x C + batch_size = 8192 + n_batches = Xt.shape[0] // batch_size + if Xt.shape[0] % batch_size: + n_batches += 1 + + # compute min + cur_min = tf.math.reduce_min(input_tensor=Xt[0, ], axis=(0, 1)).numpy() + for i in range(n_batches): + X_batch = Xt[(i * batch_size):((i+1) * batch_size)] + i_min = tf.math.reduce_min( + input_tensor=X_batch, axis=(0, 1, 2)).numpy() + for c in range(i_min.shape[-1]): + if i_min[c] <= cur_min[c]: + cur_min[c] = i_min[c] + X_min = cur_min + + # compute max + cur_max = tf.math.reduce_max(input_tensor=Xt[0, ], axis=(0, 1)).numpy() + for i in range(n_batches): + X_batch = Xt[(i * batch_size):((i+1) * batch_size)] + i_max = tf.math.reduce_max( + input_tensor=X_batch, axis=(0, 1, 2)).numpy() + for c in range(i_max.shape[-1]): + if i_max[c] >= cur_max[c]: + cur_max[c] = i_max[c] + X_max = cur_max + + # write min and max to file + if outfile: + write_record_to_file(min=X_min, max=X_max, record_file=outfile) + + # return scaler dictionary + return {'min': tf.convert_to_tensor(X_min), + 'max': tf.convert_to_tensor(X_max)} + + +def save_tf_minmax_by_min_and_max(min, max, outfile): + """ + Saves a MinMax Scaler as a Tensorflow Record. + """ + def tensor_feature(value): + """Returns a bytes_list from a string / byte.""" + return tf.train.Feature(bytes_list=tf.train.BytesList(value=[ + tf.io.serialize_tensor(tf.convert_to_tensor(value)).numpy()])) + + def scaler_encoding_fn(min, max): + """Builds a serialized version of the dataset. X and y must be + np.array. + """ + features = tf.train.Features(feature={ + "min": tensor_feature(min), + "max": tensor_feature(max), + }) + return tf.train.Example(features=features).SerializeToString() + + def write_record_to_file(min, max, record_file): + with tf.io.TFRecordWriter(record_file) as writer: + record = scaler_encoding_fn(min=min, max=max) + writer.write(record) + return + + # write min and max to file + if outfile: + write_record_to_file(min=min, max=max, record_file=outfile) + + # return scaler dictionary + return {'min': tf.convert_to_tensor(min), 'max': tf.convert_to_tensor(max)} + + +def load_tf_minmax(scalerfile, vars): + """ + Loads a MinMax Scaler from disk as a Tensorflow Record. + """ + AUTOTUNE = tf.data.AUTOTUNE + + def scaler_decoding_fn(serialized_data): + """Decoding function for a dataset written to disk as + tensor_encoding_fn(). + """ + features = { + 'min': tf.io.FixedLenFeature([], tf.string), + 'max': tf.io.FixedLenFeature([], tf.string) + } + # Parse the serialized data so we get a dict with our data. + parsed_data = tf.io.parse_single_example( + serialized_data, features=features) + # Get X and y raw data + raw_min = parsed_data['min'] + raw_max = parsed_data['max'] + # Decode the raw bytes so it becomes a tensor with type. + min = tf.ensure_shape(tf.io.parse_tensor( + raw_min, tf.float32), (len(vars))) + max = tf.ensure_shape(tf.io.parse_tensor( + raw_max, tf.float32), (len(vars))) + return min, max + + # load scaler set + scaler_set = ( + tf.data.TFRecordDataset(scalerfile, num_parallel_reads=AUTOTUNE) + .map(scaler_decoding_fn, num_parallel_calls=AUTOTUNE) + ) + # get min and max from the dataset + for data in scaler_set: + min, max = data + + # return scaler dictionary + return {'min': min, 'max': max} + + +def minmax_transform(data, scaler): + """ + Applies the transform of TFMinMaxScaler to the provided dataset. + """ + if scaler: + num = tf.subtract(data, scaler['min']) + den = tf.subtract(scaler['max'], scaler['min']) + res = tf.math.divide(num, den) + else: + res = data + return res + + +def minmax_inverse_transform(scaled_data, scaler): + """ + Applies the inverse transform of TFMinMaxScaler to the provided + scaled dataset. + """ + sub = tf.subtract(scaler['max'], scaler['min']) + mul = tf.multiply(scaled_data, sub) + return mul + scaler['min'] + + +def minmax_inverse_target_transform(y_scaled, label_no_cyclone, patch_size): + """ + Applies the inverse transform on y data when scaled in (0,1) + """ + sub = tf.subtract( + tf.cast(patch_size-1, dtype=tf.float32), label_no_cyclone) + mul = tf.multiply(y_scaled, sub) + return mul + label_no_cyclone diff --git a/use-cases/cyclones/src/strategy.py b/use-cases/cyclones/src/strategy.py new file mode 100644 index 00000000..950eb7c0 --- /dev/null +++ b/use-cases/cyclones/src/strategy.py @@ -0,0 +1,16 @@ +import tensorflow as tf + + +# gets the mirrored strategy based on whether or not we are running the model +# with CPU or GPU +def get_mirrored_strategy(cores=4): + if cores: + CPUs = ['CPU:'+str(i) for i in range(cores)] + mirrored_strategy = tf.distribute.MirroredStrategy(CPUs) + else: + mirrored_strategy = tf.distribute.MirroredStrategy() + + print('Number of devices: {}'.format( + mirrored_strategy.num_replicas_in_sync)) + + return mirrored_strategy, mirrored_strategy.num_replicas_in_sync diff --git a/use-cases/cyclones/src/tfrecords/dataset.py b/use-cases/cyclones/src/tfrecords/dataset.py new file mode 100644 index 00000000..da8fcd60 --- /dev/null +++ b/use-cases/cyclones/src/tfrecords/dataset.py @@ -0,0 +1,210 @@ +from typing import Optional +import tensorflow as tf + +from .functions import ( + get_tensor_decoding_fn, + get_scaling_fn, + get_masking_fn, get_scale_target_fn +) +from ..macros import PatchType, AugmentationType + + +def get_interleave(cyc_weights, nocyc_weights): + """ + Returns the interleaved dataset indexes based on cyclone and + nocyclone weights. + """ + # define cyclone interleave + cyc_interleave = [i for i, w in enumerate(cyc_weights) for _ in range(w)] + # define nocyclone interleave + nocyc_interleave = [i+len(cyc_interleave) + for i, w in enumerate(nocyc_weights) for _ in range(w)] + + # compute the number of blocks + the remainder of the interleaves + blocks = len(nocyc_interleave) // len(cyc_interleave) + remainder = len(nocyc_interleave) % len(cyc_interleave) + + interleave = [] + for i in cyc_interleave: + interleave += [i] + nocyc_interleave[i*blocks:(i+1)*blocks] + if remainder: + interleave += nocyc_interleave[-remainder:] + + return tf.cast(interleave, dtype=tf.int64) + + +def eFlowsTFRecordDataset( + cyc_fnames, adj_fnames, rnd_fnames, epochs, # batch_size, + scalers, target_scale=False, drv_vars=[], coo_vars=None, + msk_var=None, shape=(40, 40), + label_no_cyclone: Optional[float] = -0.3, + shuffle_buffer=None, patch_type=PatchType.NEAREST.value, + aug_type=AugmentationType.ONLY_TCS.value, aug_fns={}, + # drop_remainder=True +): + # set autotune parameter to automatically manage resourches + AUTOTUNE = tf.data.AUTOTUNE + + # compute the weight associated to an adjacent dataset + adj_w = 3 if patch_type == PatchType.NEAREST.value else 8 + + # setup dynamical lambda functions to be applied to this dataset + tensor_decoding_fn = get_tensor_decoding_fn( + shape=shape, drv_vars=drv_vars, coo_vars=coo_vars, msk_var=msk_var) + scaling_fn = get_scaling_fn(scalers=scalers) + masking_fn = get_masking_fn(mask=label_no_cyclone) + scale_target_fn = get_scale_target_fn( + label_no_cyclone=label_no_cyclone, patch_size=shape[0]) + + # multiplier for the augmentation + mul = 1 if not aug_fns else (len(aug_fns.keys()) + 1) + + # compute the number of samples into the dataset + cyc_n_elems = sum([int(fname.split('/')[-1].split('.tfrecord') + [0].split('_')[-1]) for fname in cyc_fnames]) + rnd_n_elems = sum([int(fname.split('/')[-1].split('.tfrecord') + [0].split('_')[-1]) for fname in rnd_fnames]) + adj_n_elems = sum([int(fname.split('/')[-1].split('.tfrecord') + [0].split('_')[-1]) for fname in adj_fnames]) + n_elems = mul * cyc_n_elems + rnd_n_elems + adj_n_elems + + # total number of samples that will be yielded by this dataset + count = n_elems * epochs + + # create standard datasets for each patch category + cyc_dataset = tf.data.TFRecordDataset( + cyc_fnames, num_parallel_reads=AUTOTUNE + ).map( + tensor_decoding_fn, num_parallel_calls=AUTOTUNE) + rnd_dataset = tf.data.TFRecordDataset( + rnd_fnames, num_parallel_reads=AUTOTUNE).map( + tensor_decoding_fn, num_parallel_calls=AUTOTUNE) + adj_dataset = tf.data.TFRecordDataset( + adj_fnames, num_parallel_reads=AUTOTUNE).map( + tensor_decoding_fn, num_parallel_calls=AUTOTUNE) + + # add cyclone dataset to the cyclone datasets + cyc_datasets = [cyc_dataset] + # add the weight of each cyclone dataset + cyc_weights = [1] + + # add cyclone dataset to the nocyclone datasets + nocyc_datasets = [rnd_dataset, adj_dataset] + # add the weight of each nocyclone dataset + nocyc_weights = [1, adj_w] + + # Create augmented TC datasets and interleave them to the original + if aug_fns: + #  augmentation of all patches + if aug_type == AugmentationType.ALL_PATCHES.value: + # define augmented datasets for each augmentation function + aug_cyc_datasets = [ + (tf.data.TFRecordDataset( + cyc_fnames, num_parallel_reads=AUTOTUNE).map( + tensor_decoding_fn, num_parallel_calls=AUTOTUNE).map( + lambda x, y: (aug_fn((x, y))), num_parallel_calls=AUTOTUNE) + ) + for aug_fn in aug_fns.values()] + + aug_rnd_datasets = [(tf.data.TFRecordDataset( + rnd_fnames, num_parallel_reads=AUTOTUNE).map( + tensor_decoding_fn, num_parallel_calls=AUTOTUNE).map( + lambda x, y: (aug_fn((x, y))), num_parallel_calls=AUTOTUNE)) + for aug_fn in aug_fns.values()] + + aug_adj_datasets = [(tf.data.TFRecordDataset( + adj_fnames, num_parallel_reads=AUTOTUNE).map( + tensor_decoding_fn, num_parallel_calls=AUTOTUNE).map( + lambda x, y: (aug_fn((x, y))), num_parallel_calls=AUTOTUNE)) + for aug_fn in aug_fns.values()] + + # augmentation of only TC patches + elif aug_type == AugmentationType.ONLY_TCS.value: + # define augmented datasets for each augmentation function + aug_cyc_datasets = [(tf.data.TFRecordDataset( + cyc_fnames, num_parallel_reads=AUTOTUNE).map( + tensor_decoding_fn, + num_parallel_calls=AUTOTUNE).map( + lambda x, y: (aug_fn((x, y))), + num_parallel_calls=AUTOTUNE)) + for aug_fn in aug_fns.values()] + aug_rnd_datasets = [] + aug_adj_datasets = [] + else: + aug_cyc_datasets = [] + aug_rnd_datasets = [] + aug_adj_datasets = [] + + # add weights from the augmented datasets + cyc_weights += [1 for _ in range(len(aug_cyc_datasets))] + nocyc_weights += [1 for _ in range(len(aug_rnd_datasets))] + nocyc_weights += [adj_w for _ in range(len(aug_adj_datasets))] + + # add to cyclone and nocyclone datasets the augmentations + cyc_datasets += aug_cyc_datasets + nocyc_datasets += aug_rnd_datasets + aug_adj_datasets + + # create a list of all datasets to interleave on + datasets = cyc_datasets + nocyc_datasets + + # get the interleave of all datasets + interleave = get_interleave( + cyc_weights=cyc_weights, nocyc_weights=nocyc_weights) + + # compute the choice dataset with the interleave + choice_dataset = tf.data.Dataset.from_tensor_slices( + interleave).repeat(count=count) + + # statically interleave elements from all the datasets + dataset = tf.data.experimental.choose_from_datasets( + datasets=datasets, choice_dataset=choice_dataset) + + # shuffle if necessary + if shuffle_buffer: + dataset = dataset.shuffle( + shuffle_buffer, reshuffle_each_iteration=True) + + # NOTE: when running distributed training, the dataset + # should be batched knowing the number of parallel + # workers taking part to the pool! Skipping it now... + # Example: + # batch_size = num_workers * worker_batch_size + # # separate in batches + # if batch_size: + # dataset = dataset.batch( + # batch_size, drop_remainder=drop_remainder, + # num_parallel_calls=AUTOTUNE) + # else: + # dataset = dataset.batch( + # n_elems, drop_remainder=drop_remainder, + # num_parallel_calls=AUTOTUNE) + + # apply mask on target if label_no_cyclone is provided + if label_no_cyclone: + dataset = dataset.map(lambda X, y: ( + masking_fn((X, y))), num_parallel_calls=AUTOTUNE) + + # scale the data + if scalers: + dataset = dataset.map(lambda X, y: ( + scaling_fn((X, y))), num_parallel_calls=AUTOTUNE) + if target_scale: + dataset = dataset.map(lambda X, y: ( + scale_target_fn((X, y))), num_parallel_calls=AUTOTUNE) + + # set number of epochs that can be repeated on this dataset + dataset = dataset.repeat(count=epochs) + + # add parallelism option + options = tf.data.Options() + options.experimental_distribute.auto_shard_policy = ( + tf.data.experimental.AutoShardPolicy.OFF + ) + options.experimental_threading.max_intra_op_parallelism = 1 + dataset = dataset.with_options(options) + + # prefetch + dataset = dataset.prefetch(buffer_size=AUTOTUNE) + + # return the dataset + return dataset, n_elems diff --git a/use-cases/cyclones/src/tfrecords/functions.py b/use-cases/cyclones/src/tfrecords/functions.py new file mode 100644 index 00000000..5c535d92 --- /dev/null +++ b/use-cases/cyclones/src/tfrecords/functions.py @@ -0,0 +1,140 @@ +import tensorflow as tf + +from ..scaling import minmax_transform + + +def get_tensor_decoding_fn( + shape, drv_vars=[], coo_vars=None, msk_var=None, + dtype=tf.float32 +): + + def tensor_decoding_fn(serialized_data): + """ Decoding function for a dataset written to disk as + tensor_encoding_fn(). + """ + # define features dictionary + features = {} + + # define all variables list + vars = drv_vars.copy() + if coo_vars: + vars += coo_vars.copy() + if msk_var: + vars += [msk_var] + + # add driver + coordinate + mask vars to features + for var in vars: + features.update({var: tf.io.FixedLenFeature([], tf.string)}) + + # parse the serialized data so we get a dict with our data. + parsed_data = tf.io.parse_single_example( + serialized_data, features=features) + + # accumulator for data elements + data = [] + + # get x raw data + Xdrv = tf.stack([tf.ensure_shape(tf.io.parse_tensor( + serialized=parsed_data[var], out_type=dtype), + shape=shape)for var in drv_vars], axis=-1) + data.append(Xdrv) + + # if coordinate vars are provided + if coo_vars: + if len(coo_vars) == 1: + Ycoo = tf.ensure_shape(tf.io.parse_tensor( + serialized=parsed_data[coo_vars[0]], out_type=dtype), + shape=(2,)) + else: + Ycoo = tf.stack([tf.ensure_shape(tf.io.parse_tensor( + serialized=parsed_data[var], out_type=dtype), + shape=(2)) for var in coo_vars], axis=-1) + data.append(Ycoo) + + # if mask var is provided + if msk_var: + Ymsk = tf.expand_dims(tf.ensure_shape(tf.io.parse_tensor( + serialized=parsed_data[msk_var], out_type=dtype), + shape=shape), axis=-1) + data.append(Ymsk) + + return tuple(data) + + return tensor_decoding_fn + + +def get_resize_fn(shape): + + def resize_fn(data): + """Resize function that resizes the input data to the target shape.""" + resized_data = [] + for x in data: + resized_data.append(tf.image.resize( + x, shape, tf.image.ResizeMethod.NEAREST_NEIGHBOR)) + return tuple(resized_data) + + return resize_fn + + +def get_scaling_fn(scalers): + + def scaling_fn(data): + """Function to scale the data according to the provided scalers.""" + data_scaled = [] + for x, scaler in zip(data, scalers): + data_scaled.append(minmax_transform(x, scaler)) + return tuple(data_scaled) + + return scaling_fn + + +def get_scale_target_fn(label_no_cyclone, patch_size): + + def scale_target_fn(data): + """Function to scale the target according to the provided + label_no_cyclone""" + x, y = data + # scale y + y_scaled = tf.math.divide( + tf.subtract(tf.cast(y, dtype=tf.float32), label_no_cyclone), + tf.subtract(tf.cast(patch_size-1, dtype=tf.float32), + label_no_cyclone) + ) + return (x, y_scaled) + + return scale_target_fn + + +def get_masking_fn(mask): + + def masking_fn(data): + # TODO : parametrize this function as the others + X, y = data + y_masked = tf.where(y < 0, mask, y) + return (X, y_masked) + + return masking_fn + + +def read_tfrecord_as_tensor(filenames, shape, drv_vars, coo_vars, msk_var): + # set autotune parameter to automatically manage resourches + AUTOTUNE = tf.data.AUTOTUNE + + # get lambda functions to be applied to this dataset + tensor_decoding_fn = get_tensor_decoding_fn( + shape, drv_vars=drv_vars, coo_vars=coo_vars, msk_var=msk_var) + + # compute the number of samples into the dataset + n_elems = sum(1 for _ in tf.data.TFRecordDataset(filenames)) + + # Create standard dataset + dataset = tf.data.TFRecordDataset( + filenames, num_parallel_reads=AUTOTUNE).map( + tensor_decoding_fn, num_parallel_calls=AUTOTUNE) + + # read data as numpy + Xdata, ydata = dataset.batch(batch_size=n_elems).as_numpy_iterator().next() + Xt = tf.convert_to_tensor(Xdata) + yt = tf.convert_to_tensor(ydata) + + return Xt, yt diff --git a/use-cases/cyclones/src/transform.py b/use-cases/cyclones/src/transform.py new file mode 100644 index 00000000..351dacd9 --- /dev/null +++ b/use-cases/cyclones/src/transform.py @@ -0,0 +1,52 @@ +import tensorflow as tf + + +def coo_rot180(data): + X, y = data + patch_size = X.shape[0] + X = tf.image.rot90(X, k=2) + y1 = [-1., -1.] + if y[0] != -1: + y1 = [-y[0] + patch_size - 1, -y[1] + patch_size - 1] + return (X, y1) + + +def coo_left_right(data): + X, y = data + patch_size = X.shape[0] + X = tf.image.flip_left_right(X) + y1 = [-1., -1.] + if y[0] != -1: + y1 = [y[0], - y[1] + patch_size - 1] + return (X, y1) + + +def coo_up_down(data): + X, y = data + patch_size = X.shape[0] + X = tf.image.flip_up_down(X) + y1 = [-1., -1.] + if y[0] != -1: + y1 = [- y[0] + patch_size - 1, y[1]] + return (X, y1) + + +def msk_rot180(data): + X, Y = data + X = tf.image.rot90(X, k=2) + Y = tf.image.rot90(Y, k=2) + return (X, Y) + + +def msk_left_right(data): + X, Y = data + X = tf.image.flip_left_right(X) + Y = tf.image.flip_left_right(Y) + return (X, Y) + + +def msk_up_down(data): + X, Y = data + X = tf.image.flip_up_down(X) + Y = tf.image.flip_up_down(Y) + return (X, Y) diff --git a/use-cases/cyclones/src/utils.py b/use-cases/cyclones/src/utils.py new file mode 100644 index 00000000..4586ebc7 --- /dev/null +++ b/use-cases/cyclones/src/utils.py @@ -0,0 +1,89 @@ +import tensorflow as tf +import joblib +import time + +from .macros import Network +from cyclones_vgg import ( + custom_VGG_V1, custom_VGG_V2, custom_VGG_V3 # , ModelV5 +) + + +def saveparams(file, **kwargs): + joblib.dump(kwargs, file) + + +def readparams(file): + return joblib.load(file) + + +class Timer(): + + def __init__( + self, + timers=['tot_exec_elapsed_time', + 'io_elapsed_time', 'training_elapsed_time'] + ): + # initialize execution times data structure + self.exec_times = {} + self.partials = {} + for t in timers: + self.exec_times.update({t: 0}) + self.partials.update({t: 0}) + + def start(self, timer): + # update partial timers to start counting + self.partials.update({timer: -time.time()}) + + def stop(self, timer): + # add ( stop - start ) time to global execution time + self.exec_times[timer] += self.partials[timer] + time.time() + # reset partial + self.partials[timer] = 0 + + +def get_network_config(network, **kwargs): + # choose the network configuration based on the passed network type + if network == Network.VGG_V1.value: + print('Using custom VGG V1') + model = custom_VGG_V1( + patch_size=kwargs['patch_size'], channels=kwargs['channels'], + activation=kwargs['activation'], regularizer=kwargs['regularizer']) + + elif network == Network.VGG_V2.value: + print('Using custom VGG V2') + model = custom_VGG_V2( + patch_size=kwargs['patch_size'], channels=kwargs['channels'], + activation=kwargs['activation'], regularizer=kwargs['regularizer']) + + elif network == Network.VGG_V3.value: + print('Using custom VGG V3') + model = custom_VGG_V3( + patch_size=kwargs['patch_size'], channels=kwargs['channels'], + activation=kwargs['activation'], regularizer=kwargs['regularizer']) + + # elif network == Network.MODEL_V5.value: + # print('Using Model V5') + # model = ModelV5( + # patch_size=kwargs['patch_size'], channels=kwargs['channels'], + # last_activation=kwargs['activation'], + # kernel_size=kwargs['kernel_size']) + + return model + + +def load_model(model_fpath): + """ + Loads a keras model from a file, recognizing whether or not it is a weight + file or a model file. + """ + model_fname = model_fpath.split('/')[-1] + if 'model' in model_fname: + try: + model = tf.keras.models.load_model(model_fpath) + except Exception as e: + print(f'Cannot load model. Caused by error: {e}') + elif 'weight' in model_fname: + model.compile() + model.built = True + model.load_weights(model_fpath) + return model diff --git a/use-cases/cyclones/startscript.sh b/use-cases/cyclones/startscript.sh new file mode 100644 index 00000000..97f72c80 --- /dev/null +++ b/use-cases/cyclones/startscript.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# general configuration of the job +#SBATCH --job-name=cyclones +#SBATCH --account=intertwin +#SBATCH --mail-user= +#SBATCH --mail-type=ALL +#SBATCH --output=job.out +#SBATCH --error=job.err +#SBATCH --time=00:30:00 + +# configure node and process count on the CM +#SBATCH --partition=batch +#SBATCH --nodes=2 +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=4 +#SBATCH --gpus-per-node=4 + +# SBATCH --exclusive + +# gres options have to be disabled for deepv +#SBATCH --gres=gpu:4 + +set -x +unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY + +# load modules +ml --force purge +ml Stages/2024 GCC/12.3.0 OpenMPI CUDA/12 MPI-settings/CUDA Python/3.11 HDF5 PnetCDF libaio mpi4py CMake cuDNN/8.9.5.29-CUDA-12 + +source ../../envAItf_hdfml/bin/activate + +# job info +echo "DEBUG: TIME: $(date)" +echo "DEBUG: EXECUTE: $EXEC" +echo "DEBUG: SLURM_SUBMIT_DIR: $SLURM_SUBMIT_DIR" +echo "DEBUG: SLURM_JOB_ID: $SLURM_JOB_ID" +echo "DEBUG: SLURM_JOB_NODELIST: $SLURM_JOB_NODELIST" +echo "DEBUG: SLURM_NNODES: $SLURM_NNODES" +echo "DEBUG: SLURM_NTASKS: $SLURM_NTASKS" +echo "DEBUG: SLURM_TASKS_PER_NODE: $SLURM_TASKS_PER_NODE" +echo "DEBUG: SLURM_SUBMIT_HOST: $SLURM_SUBMIT_HOST" +echo "DEBUG: SLURMD_NODENAME: $SLURMD_NODENAME" +echo "DEBUG: CUDA_VISIBLE_DEVICES: $CUDA_VISIBLE_DEVICES" +echo "DEBUG: SLURM_NODELIST: $SLURM_NODELIST" +echo + +# ONLY IF TENSORFLOW >= 2.16: +# # Using legacy (2.16) version of Keras +# # Latest version with TF (2.16) installs Keras 3.3 +# # which returns an error for multi-node execution +# export TF_USE_LEGACY_KERAS=1 + +# set comm +export CUDA_VISIBLE_DEVICES="0,1,2,3" +export OMP_NUM_THREADS=1 +if [ "$SLURM_CPUS_PER_TASK" -gt 0 ] ; then + export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK +fi + +# ON LOGIN NODE download datasets: +# ../../.venv-tf/bin/python train.py -p pipeline.yaml --download-only + +# --data_path argument is optional, but on JSC we use the dataset we previously downloaded +srun python train.py -p pipeline.yaml --data_path /p/project/intertwin/smalldata/cmcc \ No newline at end of file diff --git a/use-cases/cyclones/train.py b/use-cases/cyclones/train.py new file mode 100644 index 00000000..cf367ba1 --- /dev/null +++ b/use-cases/cyclones/train.py @@ -0,0 +1,120 @@ +""" +Training pipeline. To run this script, use the following commands. + +On login node: + +>>> python train.py -p pipeline.yaml -d + +On compute nodes: + +>>> python train.py -p pipeline.yaml + +""" + +from typing import Dict +import argparse +import logging +from os.path import join +from os import makedirs +from datetime import datetime + +# # the mock-0.3.1 dir contains testcase.py, testutils.py & mock.py +from itwinai.parser import ConfigParser, ArgumentParser + +from src.macros import PATCH_SIZE, SHAPE + + +def dynamic_config(args) -> Dict: + """Generates a configuration with values computed at runtime. + + Args: + args (argparse.Namespace): arguments parsed from command line. + + Returns: + Dict: configuration. + """ + config = {} + + # Paths, Folders + FORMATTED_DATETIME = str(datetime.now().strftime("%Y-%m-%d_%H-%M-%S")) + MODEL_BACKUP_DIR = join(args.root_dir, "models/") + EXPERIMENTS_DIR = join(args.root_dir, "experiments") + RUN_DIR = join(EXPERIMENTS_DIR, args.run_name + + "_" + FORMATTED_DATETIME) + SCALER_DIR = join(RUN_DIR, "scalers") + TENSORBOARD_DIR = join(RUN_DIR, "tensorboard") + CHECKPOINTS_DIR = join(RUN_DIR, "checkpoints") + + # Files + LOG_FILE = join(RUN_DIR, "run.log") + + # Create folders + makedirs(EXPERIMENTS_DIR, exist_ok=True) + makedirs(RUN_DIR, exist_ok=True) + makedirs(SCALER_DIR, exist_ok=True) + makedirs(TENSORBOARD_DIR, exist_ok=True) + makedirs(CHECKPOINTS_DIR, exist_ok=True) + + config = { + "root_dir": args.root_dir, + "experiment_dir": EXPERIMENTS_DIR, + "run_dir": RUN_DIR, + "scaler_dir": SCALER_DIR, + "tensorboard_dir": TENSORBOARD_DIR, + "checkpoints_dir": CHECKPOINTS_DIR, + "backup_dir": MODEL_BACKUP_DIR, + "log_file": LOG_FILE, + "shape": SHAPE, + "patch_size": PATCH_SIZE, + # "epochs": args.epochs, + # "batch_size": args.batch_size + } + + # initialize logger + logging.basicConfig( + format="[%(asctime)s] %(levelname)s : %(message)s", + level=logging.DEBUG, + filename=LOG_FILE, + datefmt="%Y-%m-%d %H:%M:%S", + ) + return config + + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument( + "-p", "--pipeline", type=str, required=True, + help='Configuration file to the pipeline to execute.' + ) + parser.add_argument("-r", "--root_dir", type=str, default='./data') + parser.add_argument("--data_path", type=str, + default='./data/data_path') + parser.add_argument("-n", "--run_name", default="noname", type=str) + parser.add_argument( + '-d', '--download-only', + action=argparse.BooleanOptionalAction, + default=False, + help=('Whether to download only the dataset and exit execution ' + '(suggested on login nodes of HPC systems)') + ) + + args = parser.parse_args() + global_config = dynamic_config(args) + + # Create parser for the pipeline + pipe_parser = ConfigParser( + config=args.pipeline, + override_keys={ + "dataset_root": args.data_path, + "global_config": global_config + } + ) + pipeline = pipe_parser.parse_pipeline( + pipeline_nested_key='training_pipeline' + ) + + if args.download_only: + print('Downloading datasets and exiting...') + pipeline = pipeline[:1] + + pipeline.execute() diff --git a/use-cases/cyclones/trainer.py b/use-cases/cyclones/trainer.py new file mode 100644 index 00000000..930b4acb --- /dev/null +++ b/use-cases/cyclones/trainer.py @@ -0,0 +1,175 @@ +from typing import Dict, Any, Optional, Union +import logging +from os.path import join, exists + + +import tensorflow as tf +import tensorflow.keras as keras + +from itwinai.tensorflow.distributed import get_strategy +from itwinai.tensorflow.trainer import TensorflowTrainer +from itwinai.components import monitor_exec + +from src.utils import get_network_config, load_model +from src.callbacks import ProcessBenchmark +from src.macros import ( + Network, + Losses, + RegularizationStrength, + Activation +) + + +class CyclonesTrainer(TensorflowTrainer): + strategy: tf.distribute.Strategy + num_workers: int + + def __init__( + self, + network: Network, + activation: Activation, + regularization_strength: RegularizationStrength, + learning_rate: float, + loss: Losses, + epochs: int, + micro_batch_size: int, + global_config: Dict[str, Any], + kernel_size: Optional[int] = None, + model_backup: Optional[str] = None, + rnd_seed: Optional[int] = None, + verbose: Union[str, int] = 'auto' + ): + super().__init__( + epochs=epochs, + micro_batch_size=micro_batch_size, + rnd_seed=rnd_seed, + verbose=verbose + ) + self.save_parameters(**self.locals2params(locals())) + self.global_config = global_config + self.model_backup = model_backup + self.network = network.value + self.activation = activation.value + self.kernel_size = kernel_size + self.regularization_strength, self.regularizer = ( + regularization_strength.value + ) + + # Loss name and learning rate + self.loss_name, self.loss = loss.value + self.learning_rate = learning_rate + + # Parse global config + self.dynamic_config(self.global_config) + + @monitor_exec + def execute(self, train_data, validation_data, channels) -> None: + # train_size and valid_size are the number of unique elements + # in the dataset, before calling tf.data.Dataset.repeat(num_epochs) + train_dataset, train_size = train_data + valid_dataset, valid_size = validation_data + + # Batch and distribute datasets among strategy's replica. + # Each batch is further split among the workers + dist_train_dataset = self.strategy.experimental_distribute_dataset( + train_dataset.batch( + self.macro_batch_size, drop_remainder=True, + num_parallel_calls=tf.data.AUTOTUNE + ) + ) + dist_valid_dataset = self.strategy.experimental_distribute_dataset( + valid_dataset.batch( + self.macro_batch_size, drop_remainder=True, + num_parallel_calls=tf.data.AUTOTUNE + ) + ) + + # Inside the strategy load the model, data generators and train + with self.strategy.scope(): + if not self.model_backup: + model = get_network_config( + network=self.network, + patch_size=self.patch_size, + activation=self.activation, + regularizer=self.regularizer, + kernel_size=self.kernel_size, + channels=channels, + ) + logging.debug("New model created") + else: + model = load_model(model_fpath=self.best_model_name) + logging.debug( + f"Model loaded from backup at {self.best_model_name}") + + optimizer = keras.optimizers.Adam(learning_rate=self.learning_rate) + metrics = [keras.metrics.MeanAbsoluteError(name="mae")] + model.compile(loss=self.loss_name, + optimizer=optimizer, metrics=metrics) + logging.debug("Model compiled") + + # Print model summary to check if model's architecture is correct + print(model.summary()) + + # Compute the steps per epoch for train and valid + steps_per_epoch = train_size // self.macro_batch_size + validation_steps = valid_size // self.macro_batch_size + + print("macro_batch_size: ", self.macro_batch_size, flush=True) + + # Train the model + model.fit( + dist_train_dataset, + validation_data=dist_valid_dataset, + steps_per_epoch=steps_per_epoch, + validation_steps=validation_steps, + epochs=self.epochs, + callbacks=self.callbacks, + ) + logging.debug("Model trained") + + # Save the best model + model.save(self.last_model_name) + logging.debug("Saved training history") + + def dynamic_config(self, config: Dict) -> None: + """Parse configuration generated at runtime.""" + self.experiment_dir = config["experiment_dir"] + self.run_dir = config["run_dir"] + self.patch_size = config["patch_size"] + + # Paths + CHECKPOINTS_DIR = join(self.run_dir, "checkpoints") + + # files and csvs definition + CHECKPOINTS_FILEPATH = join(CHECKPOINTS_DIR, "model_{epoch:02d}.keras") + LOSS_METRICS_HISTORY_CSV = join( + self.run_dir, "loss_metrics_history.csv") + BENCHMARK_HISTORY_CSV = join(self.run_dir, "benchmark_history.csv") + + self.callbacks = [ + keras.callbacks.EarlyStopping( + monitor="val_loss", + patience=100, + min_delta=0.0001, + restore_best_weights=True, + verbose=1, + mode="min", + ), + keras.callbacks.CSVLogger(LOSS_METRICS_HISTORY_CSV), + ProcessBenchmark(BENCHMARK_HISTORY_CSV), + keras.callbacks.ModelCheckpoint( + filepath=CHECKPOINTS_FILEPATH, + save_best_only=True, + monitor="val_loss", + mode="min", + save_weights_only=False, + verbose=1, + ), + ] + + # Check if model backup exists + if self.model_backup is not None and not exists(self.model_backup): + raise FileNotFoundError("Model backup file not found") + if self.model_backup: + self.best_model_name = join(self.model_backup, "best_model.keras") + self.last_model_name = join(self.run_dir, "last_model.keras") diff --git a/use-cases/mnist/README.md b/use-cases/mnist/README.md deleted file mode 100644 index e0d97b11..00000000 --- a/use-cases/mnist/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# MNIST: toy example for DT workflows - -Read the latest version of the docs [here](https://intertwin-eu.github.io/T6.5-AI-and-ML/docs/use-cases/mnist.html). diff --git a/use-cases/mnist/env-files/preproc-env.yml b/use-cases/mnist/env-files/preproc-env.yml deleted file mode 100644 index 79decc54..00000000 --- a/use-cases/mnist/env-files/preproc-env.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: preproc-env -channels: - - pytorch - - conda-forge -dependencies: - - python=3.9.12 - - torchvision - - pytorch::cpuonly diff --git a/use-cases/mnist/inference-workflow.yml b/use-cases/mnist/inference-workflow.yml deleted file mode 100644 index 24382ee0..00000000 --- a/use-cases/mnist/inference-workflow.yml +++ /dev/null @@ -1,49 +0,0 @@ -# Load with OmegaConf - -# Other configuration files to merge with this file via OmegaConf -conf-dependencies: - - meta.yml - -steps: - - preprocessing: - doc: Download and split MNIST dataset into train and test sets - command: python ${root}/mnist-preproc.py - env: - file: ${root}/env-files/preproc-env.yml - prefix: ${root}/.venv-preproc - args: - output: ${datasets.preproc-images.location} - stage: test - - run-mlflow-server: - doc: Run MLFlow server on localhost - command: python ${root}/mlflow-server.py - env: - file: ${ai-root}/env-files/pytorch-lock.yml - prefix: ${ai-root}/.venv-pytorch - source: ${ai-root} - args: - path: ${datasets.mlflow-backend-store-uri.location} - port: ${mlflow.port} - - ml-inference: - doc: Apply a pre-trained neural network on unseen data and store them - command: itwinai predict - env: - file: ${ai-root}/env-files/pytorch-lock.yml - prefix: ${ai-root}/.venv-pytorch - source: ${ai-root} - args: - config: ${root}/mnist-ai-inference.yml - input-dataset: ${datasets.preproc-images.location} - predictions-location: ${datasets.ml-predictions.location} - ml-logs: ${datasets.ml-logs.location} - - stop-mlflow-server: - doc: Stop MLFlow server on localhost, if running - command: python ${root}/mlflow-server.py - env: - file: ${ai-root}/env-files/pytorch-lock.yml - prefix: ${ai-root}/.venv-pytorch - source: ${ai-root} - args: - mode: kill - port: ${mlflow.port} - diff --git a/use-cases/mnist/meta.yml b/use-cases/mnist/meta.yml deleted file mode 100644 index 1707cf92..00000000 --- a/use-cases/mnist/meta.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Configuration file for use case metadata -# Load with OmegaConf - -# Use case root location. End without path '/' char! -root: ./use-cases/mnist - -# AI folder location. End without path '/' char! -ai-root: ./ai - -# Datasets registry -datasets: - preproc-images: - doc: Preprocessed MNIST images - location: ${root}/data/preproc-images - ml-logs: - doc: MLflow tracking URI for local logging - location: http://127.0.0.1:${mlflow.port} - mlflow-backend-store-uri: - doc: MLFlow server storage location - location: ${root}/data/ml-logs - ml-predictions: - doc: predictions on unseen data - location: ${root}/data/ml-predictions - -mlflow: - port: 5000 \ No newline at end of file diff --git a/use-cases/mnist/mlflow-server.py b/use-cases/mnist/mlflow-server.py deleted file mode 100644 index 0421efa1..00000000 --- a/use-cases/mnist/mlflow-server.py +++ /dev/null @@ -1,47 +0,0 @@ -import argparse -import sys -import subprocess - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Manage MLFLow server." - ) - parser.add_argument( - "-p", "--port", - type=int, - help="MLFlow server port.", - default=5000 - ) - parser.add_argument( - "-m", "--mode", - help="Start or kill MlFlow server.", - type=str, - default='run', - choices=('run', 'kill') - ) - parser.add_argument( - "--path", - type=str, - help="MLFlow server storage path (backend-store-uri).", - default=None - ) - args = parser.parse_args() - - if args.mode == 'kill': - # Kill server - print(f"Killing MLFlow server on localhost port {args.port}") - subprocess.run( - f"kill -9 $(lsof -t -i:{args.port})", - shell=True, - check=True, - stderr=subprocess.DEVNULL - ) - sys.exit() - - # Start server - print("Starting MLFlow server") - subprocess.Popen( - ('mlflow server --backend-store-uri ' - f'file:{args.path}').split(), - stderr=subprocess.DEVNULL - ) diff --git a/use-cases/mnist/mnist-ai-inference.yml b/use-cases/mnist/mnist-ai-inference.yml deleted file mode 100644 index da2fae31..00000000 --- a/use-cases/mnist/mnist-ai-inference.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Inference configuration -inference: - type: lightning - experiment_name: MNIST classification lite - # Run ID in MLFlow server: pre-trained model - run_id: unk - ckpt_path: model/checkpoints/best-checkpoint/best-checkpoint.ckpt - train_config_artifact_path: pl-training.yml - conf: - # Lightning data module configuration - data: - class_path: itwinai.plmodels.mnist.MNISTDataModule - init_args: - data_dir: ${cli.input_dataset} - batch_size: 32 \ No newline at end of file diff --git a/use-cases/mnist/mnist-ai-train.yml b/use-cases/mnist/mnist-ai-train.yml deleted file mode 100644 index 7f423f00..00000000 --- a/use-cases/mnist/mnist-ai-train.yml +++ /dev/null @@ -1,120 +0,0 @@ -# Configuration file of AI workflows for MNIST use case -# Load with OmegaConf - -# Pytorch lightning config for training -train: - type: lightning - # Follows lightning config file format: - # https://pytorch-lightning.readthedocs.io/en/1.6.5/common/lightning_cli.html#multiple-models-and-or-datasets - conf: - seed_everything: 4231162351 - - # Lightning Trainer configuration - trainer: - # Set to "cpu" when using pytorch "cpuonly" package - accelerator: auto - strategy: auto - devices: auto - num_nodes: 1 - precision: 32-true - - # MLFlow logger (initial) configuration. - # Do not modify this field - logger: - class_path: lightning.pytorch.loggers.MLFlowLogger - init_args: - experiment_name: ${logger.experiment_name} - run_name: null - tracking_uri: null - tags: null - save_dir: ./mlruns - log_model: false - prefix: '' - artifact_location: null - run_id: null - - # Callbacks - callbacks: - - class_path: lightning.pytorch.callbacks.early_stopping.EarlyStopping - init_args: - monitor: val_loss - patience: 2 - - class_path: lightning.pytorch.callbacks.lr_monitor.LearningRateMonitor - init_args: - logging_interval: step - - class_path: lightning.pytorch.callbacks.ModelCheckpoint - init_args: - dirpath: checkpoints - filename: best-checkpoint - save_top_k: 1 - verbose: true - monitor: val_loss - mode: min - - fast_dev_run: false - max_epochs: 1 - min_epochs: null - max_steps: -1 - min_steps: null - max_time: null - limit_train_batches: null - limit_val_batches: null - limit_test_batches: null - limit_predict_batches: null - overfit_batches: 0.0 - val_check_interval: null - check_val_every_n_epoch: 1 - num_sanity_val_steps: null - log_every_n_steps: null - enable_checkpointing: null - enable_progress_bar: null - enable_model_summary: null - accumulate_grad_batches: 1 - gradient_clip_val: null - gradient_clip_algorithm: null - deterministic: null - benchmark: null - inference_mode: true - use_distributed_sampler: true - profiler: null - detect_anomaly: false - barebones: false - plugins: null - sync_batchnorm: false - reload_dataloaders_every_n_epochs: 0 - default_root_dir: null - - # Lightning Model configuration - model: - class_path: itwinai.plmodels.mnist.LitMNIST - init_args: - hidden_size: 64 - - # Lightning data module configuration - data: - class_path: itwinai.plmodels.mnist.MNISTDataModule - init_args: - data_dir: ${cli.train_dataset} - batch_size: 32 - - # Torch Optimizer configuration - optimizer: - class_path: torch.optim.AdamW - init_args: - lr: 0.001 - - # Torch LR scheduler configuration - lr_scheduler: - class_path: torch.optim.lr_scheduler.ExponentialLR - init_args: - gamma: 0.1 - -# Mlflow -logger: - experiment_name: MNIST classification lite - description: A MLP classifier for MNIST dataset. - log_every_n_epoch: 1 - log_every_n_steps: 1 - # Name used in Models Registry. If given, it is automatically - # registered in the Models Registry. - registered_model_name: MNIST-clf-lite diff --git a/use-cases/mnist/mnist-preproc.cwl b/use-cases/mnist/mnist-preproc.cwl deleted file mode 100644 index 11f7af3a..00000000 --- a/use-cases/mnist/mnist-preproc.cwl +++ /dev/null @@ -1,69 +0,0 @@ -cwlVersion: v1.2 # Specifies the version of the Common Workflow Language (CWL) being used -class: CommandLineTool - -baseCommand: [conda, run] -# The command to be executed by the tool. It runs the 'conda' command with 'run' subcommand, -# then sets the path to the virtual environment to be used, and finally runs the 'mnist-preproc.py' -# script using the 'python' interpreter. - -requirements: - EnvVarRequirement: - envDef: - FILE_READ_BUFFER_SIZE: "10" -# The following requirement sets the environment variable 'FILE_READ_BUFFER_SIZE' to a value of 10. -# This requirement defines an environment variable requirement, specifying the value of the environment -# variable to be set. - -stdout: ./logs/mnist-preproc-stdout.txt -# Specifies that the standard output of the command will be redirected to the file 'mnist-preproc-stdout.txt' -# located in the 'logs' directory. - -inputs: - preprocessEnvironment: - type: Directory - inputBinding: - position: 1 - prefix: -p - # Specifies that the 'preprocessEnvironment' input is a directory. - # The 'inputBinding' section provides additional information on how this input should be passed - # to the command line tool. 'position' specifies the position of the argument in the command line, - # and 'prefix' specifies the prefix to be used for the argument. - - preprocessScript: - type: File - inputBinding: - position: 2 - prefix: python - # Specifies that the 'preprocessScript' input is a file. - # 'position' and 'prefix' are used to pass this input to the command line tool. - - rawDatasetPath: - type: Directory? - inputBinding: - position: 3 - prefix: --input - # Specifies that the 'rawDatasetPath' input is an optional directory. - # 'position' and 'prefix' are used to pass this input to the command line tool. - - preprocessOutput: - type: string? - inputBinding: - position: 4 - prefix: --output - # Specifies that the 'preprocessOutput' input is an optional string. - # 'position' and 'prefix' are used to pass this input to the command line tool. - -outputs: - preprocessingStdout: - type: stdout - # Specifies that the 'preprocessingStdout' output is the standard output of the command. - - preprocessedDatasetPath: - type: Directory - outputBinding: - glob: "use-cases/mnist/data/preproc-images" - # Specifies that the 'preprocessedDatasetPath' output is a directory. - # 'glob' specifies the glob pattern to find the output directory. - - - diff --git a/use-cases/mnist/mnist-preproc.py b/use-cases/mnist/mnist-preproc.py deleted file mode 100644 index ead73ef5..00000000 --- a/use-cases/mnist/mnist-preproc.py +++ /dev/null @@ -1,42 +0,0 @@ -import argparse -from torchvision.datasets import MNIST - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Preprocessing of MNIST dataset.") - parser.add_argument( - "-o", "--output", - type=str, - help="Path where to store preprocessed datasets.", - default=None - ) - # Syntactic sugar - parser.add_argument( - "-s", "--stage", - type=str, - help="Kind of dataset split to use.", - default='train', - choices=('train', 'test') - ) - args = parser.parse_args() - - if args.stage == 'train': - # Download and store training dataset - MNIST(args.output, train=True, download=True) - if args.stage == 'test': - # Download and store test dataset - MNIST(args.output, train=False, download=True) - - print( - """ - ****************************** - * Called MNIST preprocessing * - ****************************** - - - Download dataset - - Split the dataset in training and inference - - Preprocess it - - Store it to local filesystem - - """ - ) diff --git a/use-cases/mnist/tensorflow/dataloader.py b/use-cases/mnist/tensorflow/dataloader.py new file mode 100644 index 00000000..d1db3ded --- /dev/null +++ b/use-cases/mnist/tensorflow/dataloader.py @@ -0,0 +1,40 @@ +from typing import Tuple +import tensorflow.keras as keras +import tensorflow as tf + +from itwinai.components import DataGetter, DataProcessor, monitor_exec + + +class MNISTDataGetter(DataGetter): + def __init__(self): + super().__init__() + self.save_parameters(**self.locals2params(locals())) + + @monitor_exec + def execute(self) -> Tuple: + train, test = keras.datasets.mnist.load_data() + return train, test + + +class MNISTDataPreproc(DataProcessor): + def __init__(self, classes: int): + super().__init__() + self.save_parameters(**self.locals2params(locals())) + self.classes = classes + + @monitor_exec + def execute( + self, + *datasets, + ) -> Tuple: + options = tf.data.Options() + options.experimental_distribute.auto_shard_policy = ( + tf.data.experimental.AutoShardPolicy.DATA) + preprocessed = [] + for dataset in datasets: + x, y = dataset + y = keras.utils.to_categorical(y, self.classes) + sliced = tf.data.Dataset.from_tensor_slices((x, y)) + sliced = sliced.with_options(options) + preprocessed.append(sliced) + return tuple(preprocessed) diff --git a/use-cases/mnist/tensorflow/pipeline.yaml b/use-cases/mnist/tensorflow/pipeline.yaml new file mode 100644 index 00000000..c9b6ff2f --- /dev/null +++ b/use-cases/mnist/tensorflow/pipeline.yaml @@ -0,0 +1,50 @@ +# General config +verbose: auto +micro_batch_size: 17 +epochs: 3 +checkpoints_path: checkpoints +tb_log_dir: ./logs + +# Training pipeline +pipeline: + class_path: itwinai.pipeline.Pipeline + init_args: + steps: + - class_path: dataloader.MNISTDataGetter + + - class_path: dataloader.MNISTDataPreproc + init_args: + classes: 10 + + - class_path: itwinai.tensorflow.trainer.TensorflowTrainer + init_args: + epochs: ${epochs} + micro_batch_size: ${micro_batch_size} + verbose: ${verbose} + model_compile_config: + loss: + class_path: tensorflow.keras.losses.CategoricalCrossentropy + init_args: + from_logits: False + + optimizer: + class_path: tensorflow.keras.optimizers.Adam + init_args: + learning_rate: 0.001 + + model_config: + class_path: itwinai.tensorflow.models.mnist.MNIST_Model + init_args: + input_shape: [ 28, 28, 1 ] + output_shape: 10 + + callbacks: + - class_path: keras.callbacks.EarlyStopping + init_args: + patience: 2 + - class_path: keras.callbacks.ModelCheckpoint + init_args: + filepath: ${checkpoints_path}/model.{epoch:02d}-{val_loss:.2f}.keras + - class_path: keras.callbacks.TensorBoard + init_args: + log_dir: ${tb_log_dir} diff --git a/use-cases/mnist/tensorflow/startscript.sh b/use-cases/mnist/tensorflow/startscript.sh new file mode 100644 index 00000000..38d2b9b1 --- /dev/null +++ b/use-cases/mnist/tensorflow/startscript.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# general configuration of the job +#SBATCH --job-name=PrototypeTest +#SBATCH --account=intertwin +#SBATCH --mail-user= +#SBATCH --mail-type=ALL +#SBATCH --output=job.out +#SBATCH --error=job.err +#SBATCH --time=00:30:00 + +# configure node and process count on the CM +#SBATCH --partition=batch +#SBATCH --nodes=2 +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=4 +#SBATCH --gpus-per-node=4 + +#SBATCH --exclusive + +# gres options have to be disabled for deepv +#SBATCH --gres=gpu:4 + +# load modules +ml --force purge +ml Stages/2024 GCC/12.3.0 OpenMPI CUDA/12 MPI-settings/CUDA Python/3.11 HDF5 PnetCDF libaio mpi4py CMake cuDNN/8.9.5.29-CUDA-12 + +# shellcheck source=/dev/null +source ~/.bashrc + +# ON LOGIN NODE download datasets: +# ../../../.venv-tf/bin/itwinai exec-pipeline --config pipeline.yaml --pipe-key pipeline --steps 0 +source ../../../envAItf_hdfml/bin/activate +srun itwinai exec-pipeline --config pipeline.yaml --pipe-key pipeline -o verbose=2 diff --git a/use-cases/mnist/torch-lightning/README.md b/use-cases/mnist/torch-lightning/README.md new file mode 100644 index 00000000..bd769c70 --- /dev/null +++ b/use-cases/mnist/torch-lightning/README.md @@ -0,0 +1,17 @@ +# Torch Lightning example on MNIST dataset + +## Training + +```bash +# Download dataset and exit: only run first step in the pipeline (index=0) +itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline --steps 0 + +# Run the whole training pipeline +itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline +``` + +View training logs on MLFLow server (if activated from the configuration): + +```bash +mlflow ui --backend-store-uri mllogs/mlflow/ +``` diff --git a/use-cases/mnist/torch-lightning/config.yaml b/use-cases/mnist/torch-lightning/config.yaml new file mode 100644 index 00000000..3d602b6a --- /dev/null +++ b/use-cases/mnist/torch-lightning/config.yaml @@ -0,0 +1,96 @@ +# General config +dataset_root: .tmp/ + +training_pipeline: + class_path: itwinai.pipeline.Pipeline + init_args: + steps: + - class_path: dataloader.LightningMNISTDownloader + init_args: + data_path: ${dataset_root} + + - class_path: itwinai.torch.trainer.TorchLightningTrainer #trainer.LightningMNISTTrainer + init_args: + # Pytorch lightning config for training + config: + seed_everything: 4231162351 + trainer: + accelerator: auto + accumulate_grad_batches: 1 + barebones: false + benchmark: null + callbacks: + - class_path: lightning.pytorch.callbacks.early_stopping.EarlyStopping + init_args: + monitor: val_loss + patience: 2 + - class_path: lightning.pytorch.callbacks.lr_monitor.LearningRateMonitor + init_args: + logging_interval: step + - class_path: lightning.pytorch.callbacks.ModelCheckpoint + init_args: + dirpath: checkpoints + filename: best-checkpoint + mode: min + monitor: val_loss + save_top_k: 1 + verbose: true + check_val_every_n_epoch: 1 + default_root_dir: null + detect_anomaly: false + deterministic: null + devices: auto + enable_checkpointing: null + enable_model_summary: null + enable_progress_bar: null + fast_dev_run: false + gradient_clip_algorithm: null + gradient_clip_val: null + inference_mode: true + limit_predict_batches: null + limit_test_batches: null + limit_train_batches: null + limit_val_batches: null + log_every_n_steps: null + logger: null + max_epochs: 5 + max_steps: -1 + max_time: null + min_epochs: null + min_steps: null + num_sanity_val_steps: null + overfit_batches: 0.0 + plugins: null + profiler: null + reload_dataloaders_every_n_epochs: 0 + strategy: auto + sync_batchnorm: false + use_distributed_sampler: true + val_check_interval: null + + # Lightning Model configuration + model: + class_path: itwinai.torch.models.mnist.MNISTModel + init_args: + hidden_size: 64 + + # Lightning data module configuration + data: + class_path: dataloader.MNISTDataModule + init_args: + batch_size: 32 + data_path: ${dataset_root} + download: false + train_prop: 0.8 + + # Torch Optimizer configuration + optimizer: + class_path: torch.optim.AdamW + init_args: + lr: 0.001 + + # Torch LR scheduler configuration + lr_scheduler: + class_path: torch.optim.lr_scheduler.ExponentialLR + init_args: + gamma: 0.1 \ No newline at end of file diff --git a/use-cases/mnist/torch-lightning/dataloader.py b/use-cases/mnist/torch-lightning/dataloader.py new file mode 100644 index 00000000..b7e8d46e --- /dev/null +++ b/use-cases/mnist/torch-lightning/dataloader.py @@ -0,0 +1,95 @@ +from typing import Optional +import lightning as L + +from torchvision.datasets import MNIST +from torch.utils.data import DataLoader, random_split +from torchvision import transforms + +from itwinai.components import DataGetter, monitor_exec + + +class LightningMNISTDownloader(DataGetter): + def __init__( + self, + data_path: str, + name: Optional[str] = None + ) -> None: + super().__init__(name) + self.save_parameters(**self.locals2params(locals())) + self.data_path = data_path + self._downloader = MNISTDataModule( + data_path=self.data_path, download=True, + # Mock other args... + batch_size=1, train_prop=.5, + ) + + @monitor_exec + def execute(self) -> None: + # Simulate dataset creation to force data download + self._downloader.setup(stage='fit') + self._downloader.setup(stage='test') + self._downloader.setup(stage='predict') + + +class MNISTDataModule(L.LightningDataModule): + def __init__( + self, + data_path: str, + batch_size: int, + train_prop: float, + download: bool = True + ) -> None: + super().__init__() + self.data_path = data_path + self.download = download + self.batch_size = batch_size + self.train_prop = train_prop + self.transform = transforms.Compose( + [ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)), + ] + ) + + def setup(self, stage=None): + if stage == "fit": + mnist_full = MNIST( + self.data_path, train=True, + download=self.download, + transform=self.transform + ) + n_train_samples = int(self.train_prop * len(mnist_full)) + n_val_samples = len(mnist_full) - n_train_samples + self.mnist_train, self.mnist_val = random_split( + mnist_full, [n_train_samples, n_val_samples] + ) + + if stage == "test": + self.mnist_test = MNIST( + self.data_path, train=False, + download=self.download, + transform=self.transform + ) + + if stage == "predict": + self.mnist_predict = MNIST( + self.data_path, train=False, + download=self.download, + transform=self.transform + ) + + def train_dataloader(self): + return DataLoader( + self.mnist_train, batch_size=self.batch_size, num_workers=4) + + def val_dataloader(self): + return DataLoader( + self.mnist_val, batch_size=self.batch_size, num_workers=4) + + def test_dataloader(self): + return DataLoader( + self.mnist_test, batch_size=self.batch_size, num_workers=4) + + def predict_dataloader(self): + return DataLoader( + self.mnist_predict, batch_size=self.batch_size, num_workers=4) diff --git a/use-cases/mnist/torch-lightning/startscript b/use-cases/mnist/torch-lightning/startscript new file mode 100644 index 00000000..ba0199e6 --- /dev/null +++ b/use-cases/mnist/torch-lightning/startscript @@ -0,0 +1,34 @@ +#!/bin/bash + +# general configuration of the job +#SBATCH --job-name=PrototypeTest +#SBATCH --account=intertwin +#SBATCH --mail-user= +#SBATCH --mail-type=ALL +#SBATCH --output=job.out +#SBATCH --error=job.err +#SBATCH --time=00:30:00 + +# configure node and process count on the CM +#SBATCH --partition=batch +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=4 +#SBATCH --gpus-per-node=4 + +#SBATCH --exclusive + +# gres options have to be disabled for deepv +#SBATCH --gres=gpu:4 + +# load modules +ml --force purge +ml Stages/2023 StdEnv/2023 NVHPC/23.1 OpenMPI/4.1.4 cuDNN/8.6.0.163-CUDA-11.7 Python/3.10.4 HDF5 libaio/0.3.112 GCC/11.3.0 + +# shellcheck source=/dev/null +source ~/.bashrc + +# ON LOGIN NODE download datasets: +# ../../../.venv-pytorch/bin/itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline --steps dataloading_step +source ../../../.venv-pytorch/bin/activate +srun itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline \ No newline at end of file diff --git a/ai/src/itwinai/utils.py b/use-cases/mnist/torch-lightning/utils.py similarity index 75% rename from ai/src/itwinai/utils.py rename to use-cases/mnist/torch-lightning/utils.py index 844dc001..d04f9e63 100644 --- a/ai/src/itwinai/utils.py +++ b/use-cases/mnist/torch-lightning/utils.py @@ -1,34 +1,15 @@ """ Utilities for itwinai package. """ -from typing import Dict import os -from collections.abc import MutableMapping import yaml + +from collections.abc import MutableMapping +from typing import Dict from omegaconf import OmegaConf from omegaconf.dictconfig import DictConfig -def check_server(uri: str) -> bool: - """Check if an HTTP server is reachable - - Args: - uri (str): Server URL - - Returns: - bool: True if reachable. - """ - import requests - from requests import ConnectionError - - success = True - try: - _ = requests.get(uri) - except ConnectionError: - success = False - return success - - def load_yaml(path: str) -> Dict: """Load YAML file as dict. @@ -50,7 +31,7 @@ def load_yaml(path: str) -> Dict: return loaded_config -def load_yaml_with_deps(path: str) -> DictConfig: +def load_yaml_with_deps_from_file(path: str) -> DictConfig: """ Load YAML file with OmegaConf and merge it with its dependencies specified in the `conf-dependencies` field. @@ -69,18 +50,23 @@ def load_yaml_with_deps(path: str) -> DictConfig: yaml_conf = load_yaml(path) use_case_dir = os.path.dirname(path) deps = [] - if yaml_conf.get('conf-dependencies'): - for dependency in yaml_conf['conf-dependencies']: - deps.append(load_yaml( - os.path.join( - use_case_dir, - dependency - )) - ) + if yaml_conf.get("conf-dependencies"): + for dependency in yaml_conf["conf-dependencies"]: + deps.append(load_yaml(os.path.join(use_case_dir, dependency))) return OmegaConf.merge(yaml_conf, *deps) +def load_yaml_with_deps_from_dict(dict_conf, use_case_dir) -> DictConfig: + deps = [] + + if dict_conf.get("conf-dependencies"): + for dependency in dict_conf["conf-dependencies"]: + deps.append(load_yaml(os.path.join(use_case_dir, dependency))) + + return OmegaConf.merge(dict_conf, *deps) + + def dynamically_import_class(name: str): """ Dynamically import class by module path. @@ -99,9 +85,7 @@ def dynamically_import_class(name: str): def flatten_dict( - d: MutableMapping, - parent_key: str = '', - sep: str = '.' + d: MutableMapping, parent_key: str = "", sep: str = "." ) -> MutableMapping: """Flatten dictionary diff --git a/use-cases/mnist/torch/Dockerfile b/use-cases/mnist/torch/Dockerfile new file mode 100644 index 00000000..5b96feb5 --- /dev/null +++ b/use-cases/mnist/torch/Dockerfile @@ -0,0 +1,16 @@ +# FROM python:3.9 +FROM nvcr.io/nvidia/pytorch:23.09-py3 + +WORKDIR /usr/src/app + +# Install pytorch (cpuonly) +# Ref:https://pytorch.org/get-started/previous-versions/#linux-and-windows-5 +RUN pip install --no-cache-dir torch==1.13.1+cpu torchvision==0.14.1+cpu torchaudio==0.13.1 --extra-index-url https://download.pytorch.org/whl/cpu + +# Install itwinai and dependencies +COPY pyproject.toml ./ +COPY src ./ +RUN pip install --no-cache-dir . + +# Add torch MNIST use case +COPY use-cases/mnist/torch/* ./ diff --git a/use-cases/mnist/torch/README.md b/use-cases/mnist/torch/README.md new file mode 100644 index 00000000..e333f14b --- /dev/null +++ b/use-cases/mnist/torch/README.md @@ -0,0 +1,102 @@ +# Pure torch example on MNIST dataset + +## Training + +```bash +# Download dataset and exit +itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline --steps dataloading_step + +# Run the whole training pipeline +itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline +``` + +View training logs on MLFLow server (if activated from the configuration): + +```bash +mlflow ui --backend-store-uri mllogs/mlflow/ +``` + +## Inference + +1. Create sample dataset + + ```python + from dataloader import InferenceMNIST + InferenceMNIST.generate_jpg_sample('mnist-sample-data/', 10) + ``` + +2. Generate a dummy pre-trained neural network + + ```python + import torch + from model import Net + dummy_nn = Net() + torch.save(dummy_nn, 'mnist-pre-trained.pth') + ``` + +3. Run inference command. This will generate a "mnist-predictions" +folder containing a CSV file with the predictions as rows. + + ```bash + itwinai exec-pipeline --config config.yaml --pipe-key inference_pipeline + ``` + +Note the same entry point as for training. + +## Docker image + +Build from project root with + +```bash +# Local +docker buildx build -t itwinai:0.0.1-mnist-torch-0.1 -f use-cases/mnist/torch/Dockerfile . + +# Ghcr.io +docker buildx build -t ghcr.io/intertwin-eu/itwinai:0.0.1-mnist-torch-0.1 -f use-cases/mnist/torch/Dockerfile . +docker push ghcr.io/intertwin-eu/itwinai:0.0.1-mnist-torch-0.1 +``` + +### Training with Docker container + +```bash +docker run -it --rm --name running-inference \ + -v "$PWD":/usr/data ghcr.io/intertwin-eu/itwinai:0.01-mnist-torch-0.1 \ + /bin/bash -c "itwinai exec-pipeline --print-config \ + --config /usr/src/app/config.yaml \ + --pipe-key training_pipeline \ + -o dataset_root=/usr/data/mnist-dataset " +``` + +### Inference with Docker container + +From wherever a sample of MNIST jpg images is available +(folder called 'mnist-sample-data/'): + +```text +├── $PWD +│ ├── mnist-sample-data +| │ ├── digit_0.jpg +| │ ├── digit_1.jpg +| │ ├── digit_2.jpg +... +| │ ├── digit_N.jpg +``` + +```bash +docker run -it --rm --name running-inference \ + -v "$PWD":/usr/data ghcr.io/intertwin-eu/itwinai:0.01-mnist-torch-0.1 \ + /bin/bash -c "itwinai exec-pipeline --print-config \ + --config /usr/src/app/config.yaml \ + --pipe-key inference_pipeline \ + -o test_data_path=/usr/data/mnist-sample-data \ + -o inference_model_mlflow_uri=/usr/src/app/mnist-pre-trained.pth \ + -o predictions_dir=/usr/data/mnist-predictions " +``` + +This command will store the results in a folder called "mnist-predictions": + +```text +├── $PWD +│ ├── mnist-predictions +| │ ├── predictions.csv +``` diff --git a/use-cases/mnist/torch/config.yaml b/use-cases/mnist/torch/config.yaml new file mode 100644 index 00000000..e7606859 --- /dev/null +++ b/use-cases/mnist/torch/config.yaml @@ -0,0 +1,98 @@ +# General config +dataset_root: .tmp/ +num_classes: 10 +batch_size: 64 +num_workers_dataloader: 4 +pin_memory: False +lr: 0.001 +momentum: 0.9 +fp16_allreduce: False +use_adasum: False +gradient_predivide_factor: 1.0 +epochs: 2 +strategy: ddp +test_data_path: mnist-sample-data +inference_model_mlflow_uri: mnist-pre-trained.pth +predictions_dir: mnist-predictions +predictions_file: predictions.csv +class_labels: null +checkpoints_location: checkpoints +checkpoint_every: 1 + +# Workflows configuration +training_pipeline: + class_path: itwinai.pipeline.Pipeline + init_args: + steps: + dataloading_step: + class_path: dataloader.MNISTDataModuleTorch + init_args: + save_path: ${dataset_root} + + training_step: + class_path: itwinai.torch.trainer.TorchTrainer + init_args: + config: + batch_size: ${batch_size} + num_workers: ${num_workers_dataloader} + pin_memory: ${pin_memory} + lr: ${lr} + momentum: ${momentum} + fp16_allreduce: ${fp16_allreduce} + use_adasum: ${use_adasum} + gradient_predivide_factor: ${gradient_predivide_factor} + + model: + class_path: model.Net + epochs: ${epochs} + metrics: + accuracy: + class_path: torchmetrics.classification.MulticlassAccuracy + init_args: + num_classes: ${num_classes} + precision: + class_path: torchmetrics.classification.MulticlassPrecision + init_args: + num_classes: ${num_classes} + recall: + class_path: torchmetrics.classification.MulticlassRecall + init_args: + num_classes: ${num_classes} + logger: + class_path: itwinai.loggers.LoggersCollection + init_args: + loggers: + - class_path: itwinai.loggers.ConsoleLogger + init_args: + log_freq: 10000 + - class_path: itwinai.loggers.MLFlowLogger + init_args: + experiment_name: MNIST classifier + log_freq: batch + strategy: ${strategy} + checkpoint_every: ${checkpoint_every} + checkpoints_location: ${checkpoints_location} + + +inference_pipeline: + class_path: itwinai.pipeline.Pipeline + init_args: + steps: + - class_path: dataloader.MNISTPredictLoader + init_args: + test_data_path: ${test_data_path} + + - class_path: itwinai.torch.inference.MulticlassTorchPredictor + init_args: + model: + class_path: itwinai.torch.inference.TorchModelLoader + init_args: + model_uri: ${inference_model_mlflow_uri} + test_dataloader_kwargs: + batch_size: ${batch_size} + + - class_path: saver.TorchMNISTLabelSaver + init_args: + save_dir: ${predictions_dir} + predictions_file: ${predictions_file} + class_labels: ${class_labels} \ No newline at end of file diff --git a/use-cases/mnist/torch/create_inference_sample.py b/use-cases/mnist/torch/create_inference_sample.py new file mode 100644 index 00000000..1c588c48 --- /dev/null +++ b/use-cases/mnist/torch/create_inference_sample.py @@ -0,0 +1,42 @@ +"""Create a simple inference dataset sample and a checkpoint.""" + +import torch +import os +import argparse + +from model import Net +from dataloader import InferenceMNIST + + +def mnist_torch_inference_files( + root: str = '.', + samples_path: str = 'mnist-sample-data/', + model_name: str = 'mnist-pre-trained.pth' +): + """Create sample dataset and fake model to test mnist + inference workflow. Assumes to be run from + the use case folder. + + Args: + root (str, optional): where to create the files. + Defaults to '.'. + """ + + sample = os.path.join(root, samples_path) + InferenceMNIST.generate_jpg_sample(sample, 10) + + # Fake checkpoint + dummy_nn = Net() + mdl_ckpt = os.path.join(root, model_name) + torch.save(dummy_nn, mdl_ckpt) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--root", type=str, default='.') + parser.add_argument("--samples-path", type=str, + default='mnist-sample-data') + parser.add_argument("--model-name", type=str, + default='mnist-pre-trained.pth') + args = parser.parse_args() + mnist_torch_inference_files(**vars(args)) diff --git a/use-cases/mnist/torch/dataloader.py b/use-cases/mnist/torch/dataloader.py new file mode 100644 index 00000000..a19c647e --- /dev/null +++ b/use-cases/mnist/torch/dataloader.py @@ -0,0 +1,120 @@ +"""Dataloader for Torch-based MNIST use case.""" + +from typing import Optional, Tuple, Callable, Any +import os +import shutil + +from PIL import Image +from torch.utils.data import Dataset +from torchvision import transforms, datasets + +from itwinai.components import DataGetter, monitor_exec + + +class MNISTDataModuleTorch(DataGetter): + """Download MNIST dataset for torch.""" + + def __init__(self, save_path: str = '.tmp/',) -> None: + super().__init__() + self.save_parameters(**self.locals2params(locals())) + self.save_path = save_path + + @monitor_exec + def execute(self) -> Tuple[Dataset, Dataset]: + train_dataset = datasets.MNIST( + self.save_path, train=True, download=True, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)) + ])) + validation_dataset = datasets.MNIST( + self.save_path, train=False, download=True, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)) + ])) + print("Train and validation datasets loaded.") + return train_dataset, validation_dataset, None + + +class InferenceMNIST(Dataset): + """Loads a set of MNIST images from a folder of JPG files.""" + + def __init__( + self, + root: str, + transform: Optional[Callable] = None, + supported_format: str = '.jpg' + ) -> None: + self.root = root + self.transform = transform + self.supported_format = supported_format + self.data = dict() + self._load() + + def _load(self): + for img_file in os.listdir(self.root): + if not img_file.lower().endswith(self.supported_format): + continue + filename = os.path.basename(img_file) + img = Image.open(os.path.join(self.root, img_file)) + self.data[filename] = img + + def __len__(self) -> int: + return len(self.data) + + def __getitem__(self, index: int) -> Tuple[Any, Any]: + """ + Args: + index (int): Index + + Returns: + tuple: (image_identifier, image) where image_identifier + is the unique identifier for the image (e.g., filename). + """ + img_id, img = list(self.data.items())[index] + + if self.transform is not None: + img = self.transform(img) + + return img_id, img + + @staticmethod + def generate_jpg_sample( + root: str, + max_items: int = 100 + ): + """Generate a sample dataset of JPG images starting from + LeCun's test dataset. + + Args: + root (str): sample path on disk + max_items (int, optional): max number of images to + generate. Defaults to 100. + """ + if os.path.exists(root): + shutil.rmtree(root) + os.makedirs(root) + + test_data = datasets.MNIST(root='.tmp', train=False, download=True) + for idx, (img, _) in enumerate(test_data): + if idx >= max_items: + break + savepath = os.path.join(root, f'digit_{idx}.jpg') + img.save(savepath) + + +class MNISTPredictLoader(DataGetter): + def __init__(self, test_data_path: str) -> None: + super().__init__() + self.save_parameters(**self.locals2params(locals())) + self.test_data_path = test_data_path + + @monitor_exec + def execute(self) -> Dataset: + return InferenceMNIST( + root=self.test_data_path, + transform=transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)) + ])) diff --git a/use-cases/mnist/torch/model.py b/use-cases/mnist/torch/model.py new file mode 100644 index 00000000..759cec87 --- /dev/null +++ b/use-cases/mnist/torch/model.py @@ -0,0 +1,22 @@ +from torch import nn +import torch.nn.functional as F + + +class Net(nn.Module): + + def __init__(self): + super(Net, self).__init__() + self.conv1 = nn.Conv2d(1, 10, kernel_size=5) + self.conv2 = nn.Conv2d(10, 20, kernel_size=5) + self.conv2_drop = nn.Dropout2d() + self.fc1 = nn.Linear(320, 50) + self.fc2 = nn.Linear(50, 10) + + def forward(self, x): + x = F.relu(F.max_pool2d(self.conv1(x), 2)) + x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2)) + x = x.view(-1, 320) + x = F.relu(self.fc1(x)) + x = F.dropout(x, training=self.training) + x = self.fc2(x) + return F.log_softmax(x, dim=0) diff --git a/use-cases/mnist/torch/runall.sh b/use-cases/mnist/torch/runall.sh new file mode 100644 index 00000000..2117c48a --- /dev/null +++ b/use-cases/mnist/torch/runall.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Python virtual environment (no conda/micromamba) +PYTHON_VENV="../../../envAI_hdfml" + +# Clear SLURM logs (*.out and *.err files) +rm -rf logs_slurm checkpoints* mllogs* +mkdir logs_slurm +rm -rf logs_torchrun + +# DDP itwinai +DIST_MODE="ddp" +RUN_NAME="ddp-itwinai" +TRAINING_CMD="$PYTHON_VENV/bin/itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline -o strategy=ddp -o checkpoints_location=checkpoints_ddp" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + +# DeepSpeed itwinai +DIST_MODE="deepspeed" +RUN_NAME="deepspeed-itwinai" +TRAINING_CMD="$PYTHON_VENV/bin/itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline -o strategy=deepspeed -o checkpoints_location=checkpoints_deepspeed" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + +# Horovod itwinai +DIST_MODE="horovod" +RUN_NAME="horovod-itwinai" +TRAINING_CMD="$PYTHON_VENV/bin/itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline -o strategy=horovod -o checkpoints_location=checkpoints_hvd" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh \ No newline at end of file diff --git a/use-cases/mnist/torch/saver.py b/use-cases/mnist/torch/saver.py new file mode 100644 index 00000000..e1ce56ac --- /dev/null +++ b/use-cases/mnist/torch/saver.py @@ -0,0 +1,59 @@ +""" +This module is used during inference to save predicted labels to file. +""" + +from typing import Optional, List, Dict +import os +import shutil +import csv + +from itwinai.components import Saver, monitor_exec + + +class TorchMNISTLabelSaver(Saver): + """Serializes to disk the labels predicted for MNIST dataset.""" + + def __init__( + self, + save_dir: str = 'mnist_predictions', + predictions_file: str = 'predictions.csv', + class_labels: Optional[List] = None + ) -> None: + super().__init__() + self.save_parameters(**self.locals2params(locals())) + self.save_dir = save_dir + self.predictions_file = predictions_file + self.class_labels = ( + class_labels if class_labels is not None + else [f'Digit {i}' for i in range(10)] + ) + + @monitor_exec + def execute(self, predicted_classes: Dict[str, int],) -> Dict[str, int]: + """Translate predictions from class idx to class label and save + them to disk. + + Args: + predicted_classes (Dict[str, int]): maps unique item ID to + the predicted class ID. + + Returns: + Dict[str, int]: predicted classes. + """ + if os.path.exists(self.save_dir): + shutil.rmtree(self.save_dir) + os.makedirs(self.save_dir) + + # Map class idx (int) to class label (str) + predicted_labels = { + itm_name: self.class_labels[cls_idx] + for itm_name, cls_idx in predicted_classes.items() + } + + # Save to disk + filepath = os.path.join(self.save_dir, self.predictions_file) + with open(filepath, 'w') as csv_file: + writer = csv.writer(csv_file) + for key, value in predicted_labels.items(): + writer.writerow([key, value]) + return predicted_labels diff --git a/use-cases/mnist/torch/slurm.sh b/use-cases/mnist/torch/slurm.sh new file mode 100644 index 00000000..2a2a15d8 --- /dev/null +++ b/use-cases/mnist/torch/slurm.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +# SLURM jobscript for JSC systems + +# Job configuration +#SBATCH --job-name=distributed_training +#SBATCH --account=intertwin +#SBATCH --mail-user= +#SBATCH --mail-type=ALL +#SBATCH --output=job.out +#SBATCH --error=job.err +#SBATCH --time=00:30:00 + +# Resources allocation +#SBATCH --partition=batch +#SBATCH --nodes=2 +#SBATCH --gpus-per-node=4 +#SBATCH --cpus-per-gpu=4 +#SBATCH --exclusive + +# gres options have to be disabled for deepv +#SBATCH --gres=gpu:4 + +# Load environment modules +ml Stages/2024 GCC OpenMPI CUDA/12 MPI-settings/CUDA Python HDF5 PnetCDF libaio mpi4py + +# Job info +echo "DEBUG: TIME: $(date)" +sysN="$(uname -n | cut -f2- -d.)" +sysN="${sysN%%[0-9]*}" +echo "Running on system: $sysN" +echo "DEBUG: EXECUTE: $EXEC" +echo "DEBUG: SLURM_SUBMIT_DIR: $SLURM_SUBMIT_DIR" +echo "DEBUG: SLURM_JOB_ID: $SLURM_JOB_ID" +echo "DEBUG: SLURM_JOB_NODELIST: $SLURM_JOB_NODELIST" +echo "DEBUG: SLURM_NNODES: $SLURM_NNODES" +echo "DEBUG: SLURM_NTASKS: $SLURM_NTASKS" +echo "DEBUG: SLURM_TASKS_PER_NODE: $SLURM_TASKS_PER_NODE" +echo "DEBUG: SLURM_SUBMIT_HOST: $SLURM_SUBMIT_HOST" +echo "DEBUG: SLURMD_NODENAME: $SLURMD_NODENAME" +echo "DEBUG: CUDA_VISIBLE_DEVICES: $CUDA_VISIBLE_DEVICES" +if [ "$DEBUG" = true ] ; then + echo "DEBUG: NCCL_DEBUG=INFO" + export NCCL_DEBUG=INFO +fi +echo + +# Setup env for distributed ML +export CUDA_VISIBLE_DEVICES="0,1,2,3" +export OMP_NUM_THREADS=1 +if [ "$SLURM_CPUS_PER_GPU" -gt 0 ] ; then + export OMP_NUM_THREADS=$SLURM_CPUS_PER_GPU +fi + +# Env vairables check +if [ -z "$DIST_MODE" ]; then + >&2 echo "ERROR: env variable DIST_MODE is not set. Allowed values are 'horovod', 'ddp' or 'deepspeed'" + exit 1 +fi +if [ -z "$RUN_NAME" ]; then + >&2 echo "WARNING: env variable RUN_NAME is not set. It's a way to identify some specific run of an experiment." + RUN_NAME=$DIST_MODE +fi +if [ -z "$TRAINING_CMD" ]; then + >&2 echo "ERROR: env variable TRAINING_CMD is not set. It's the python command to execute." + exit 1 +fi +if [ -z "$PYTHON_VENV" ]; then + >&2 echo "WARNING: env variable PYTHON_VENV is not set. It's the path to a python virtual environment." +else + # Activate Python virtual env + source $PYTHON_VENV/bin/activate +fi + +# Get GPUs info per node +srun --cpu-bind=none --ntasks-per-node=1 bash -c 'echo -e "NODE hostname: $(hostname)\n$(nvidia-smi)\n\n"' + +# Launch training +if [ "$DIST_MODE" == "ddp" ] ; then + echo "DDP training: $TRAINING_CMD" + srun --cpu-bind=none --ntasks-per-node=1 \ + bash -c "torchrun \ + --log_dir='logs_torchrun' \ + --nnodes=$SLURM_NNODES \ + --nproc_per_node=$SLURM_GPUS_PER_NODE \ + --rdzv_id=$SLURM_JOB_ID \ + --rdzv_conf=is_host=\$(((SLURM_NODEID)) && echo 0 || echo 1) \ + --rdzv_backend=c10d \ + --rdzv_endpoint='$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)'i:29500 \ + $TRAINING_CMD" +elif [ "$DIST_MODE" == "deepspeed" ] ; then + echo "DEEPSPEED training: $TRAINING_CMD" + MASTER_ADDR=$(scontrol show hostnames "\$SLURM_JOB_NODELIST" | head -n 1)i + export MASTER_ADDR + export MASTER_PORT=29500 + + srun --cpu-bind=none --ntasks-per-node=$SLURM_GPUS_PER_NODE --cpus-per-task=$SLURM_CPUS_PER_GPU \ + $TRAINING_CMD + + # # Run with deepspeed launcher: set --ntasks-per-node=1 + # # https://www.deepspeed.ai/getting-started/#multi-node-environment-variables + # export NCCL_IB_DISABLE=1 + # export NCCL_SOCKET_IFNAME=eth0 + # nodelist=$(scontrol show hostname $SLURM_NODELIST) + # echo "$nodelist" | sed -e 's/$/ slots=4/' > .hostfile + # # Requires passwordless SSH access among compute node + # srun --cpu-bind=none deepspeed --hostfile=.hostfile $TRAINING_CMD --deepspeed + # rm .hostfile +elif [ "$DIST_MODE" == "horovod" ] ; then + echo "HOROVOD training: $TRAINING_CMD" + srun --cpu-bind=none --ntasks-per-node=$SLURM_GPUS_PER_NODE --cpus-per-task=$SLURM_CPUS_PER_GPU \ + $TRAINING_CMD +else + >&2 echo "ERROR: unrecognized \$DIST_MODE env variable" + exit 1 +fi diff --git a/use-cases/mnist/torch/startscript.sh b/use-cases/mnist/torch/startscript.sh new file mode 100644 index 00000000..ba0199e6 --- /dev/null +++ b/use-cases/mnist/torch/startscript.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# general configuration of the job +#SBATCH --job-name=PrototypeTest +#SBATCH --account=intertwin +#SBATCH --mail-user= +#SBATCH --mail-type=ALL +#SBATCH --output=job.out +#SBATCH --error=job.err +#SBATCH --time=00:30:00 + +# configure node and process count on the CM +#SBATCH --partition=batch +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=4 +#SBATCH --gpus-per-node=4 + +#SBATCH --exclusive + +# gres options have to be disabled for deepv +#SBATCH --gres=gpu:4 + +# load modules +ml --force purge +ml Stages/2023 StdEnv/2023 NVHPC/23.1 OpenMPI/4.1.4 cuDNN/8.6.0.163-CUDA-11.7 Python/3.10.4 HDF5 libaio/0.3.112 GCC/11.3.0 + +# shellcheck source=/dev/null +source ~/.bashrc + +# ON LOGIN NODE download datasets: +# ../../../.venv-pytorch/bin/itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline --steps dataloading_step +source ../../../.venv-pytorch/bin/activate +srun itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline \ No newline at end of file diff --git a/use-cases/mnist/training-workflow.yml b/use-cases/mnist/training-workflow.yml deleted file mode 100644 index 4ee3d220..00000000 --- a/use-cases/mnist/training-workflow.yml +++ /dev/null @@ -1,76 +0,0 @@ -# Workflow definition in classical style - -# Load with OmegaConf - -# Other configuration files to merge with this file via OmegaConf -conf-dependencies: - - meta.yml - -steps: - - preprocessing: - doc: Download and split MNIST dataset into train and test sets - command: python ${root}/mnist-preproc.py - env: - file: ${root}/env-files/preproc-env.yml - prefix: ${root}/.venv-preproc - args: - output: ${datasets.preproc-images.location} - stage: train - - run-mlflow-server: - doc: Run MLFlow server on localhost - command: python ${root}/mlflow-server.py - env: - file: ${ai-root}/env-files/pytorch-gpu-lock.yml - prefix: ${ai-root}/.venv-pytorch - source: ${ai-root} - args: - path: ${datasets.mlflow-backend-store-uri.location} - port: ${mlflow.port} - - ml-training: - doc: Train a neural network to classify MNIST images - command: itwinai train - env: - file: ${ai-root}/env-files/pytorch-gpu-lock.yml - prefix: ${ai-root}/.venv-pytorch - source: ${ai-root} - args: - train-dataset: ${datasets.preproc-images.location} - ml-logs: ${datasets.ml-logs.location} - config: ${root}/mnist-ai-train.yml - - stop-mlflow-server: - doc: Stop MLFlow server on localhost, if running - command: python ${root}/mlflow-server.py - env: - file: ${ai-root}/env-files/pytorch-gpu-lock.yml - prefix: ${ai-root}/.venv-pytorch - source: ${ai-root} - args: - mode: kill - port: ${mlflow.port} - -# #Workflow definition in CWL style - -workflowFileCWL: use-cases/mnist/workflow.cwl - -preprocessEnvironment: - class: Directory - path: .venv-preproc - -preprocessScript: - class: File - path: mnist-preproc.py - -# OmegaConf referencing does not work in CWL definition -#preprocessOutput: ${datasets.preproc-images.location} -preprocessOutput: use-cases/mnist/data/preproc-images - -#training step -trainingConfig: - class: File - path: mnist-ai-train.yml - -trainingEnvironment: - class: Directory - path: ../../ai/.venv-pytorch - -trainingCommand: train \ No newline at end of file diff --git a/use-cases/mnist/workflow.cwl b/use-cases/mnist/workflow.cwl deleted file mode 100644 index 79202558..00000000 --- a/use-cases/mnist/workflow.cwl +++ /dev/null @@ -1,56 +0,0 @@ -cwlVersion: v1.2 -class: Workflow - -inputs: - preprocessEnvironment: # Input directory for the preprocess environment - type: Directory - preprocessScript: # Input file for the preprocess script - type: File - preprocessInput: # Optional input directory for preprocess - type: Directory? - preprocessOutput: # Optional output string for preprocess - type: string? - - trainingConfig: # Input file for the training configuration - type: File - trainingEnvironment: # Input directory for the training environment - type: Directory - trainingCommand: # Input string for the training command - type: string - -outputs: - preprocessingStdout: # Output file for the preprocessing stdout - type: File - outputSource: preprocess/preprocessingStdout - - outputCheckpoint: # Output directory for the trained model checkpoint - type: Directory - outputSource: training/outputCheckpoint - - preprocessedDatasetPath: # Output directory for the preprocessed dataset - type: Directory - outputSource: preprocess/preprocessedDatasetPath - - mlLogs: # Output directory for the machine learning logs - type: Directory - outputSource: training/mlLogs - -steps: - preprocess: # Step for preprocessing - run: mnist-preproc.cwl - in: - preprocessEnvironment: preprocessEnvironment - preprocessScript: preprocessScript - rawDatasetPath: preprocessInput - preprocessOutput: preprocessOutput - out: [preprocessingStdout, preprocessedDatasetPath] - - training: # Step for training - run: ../../ai/training.cwl - in: - preprocessedDatasetPath: preprocess/preprocessedDatasetPath - trainingConfig: trainingConfig - trainingEnvironment: trainingEnvironment - trainingCommand: trainingCommand - preprocessingFlag: preprocess/preprocessingStdout - out: [outputCheckpoint, mlLogs] diff --git a/use-cases/virgo/.gitignore b/use-cases/virgo/.gitignore new file mode 100644 index 00000000..adbb97d2 --- /dev/null +++ b/use-cases/virgo/.gitignore @@ -0,0 +1 @@ +data/ \ No newline at end of file diff --git a/use-cases/virgo/README.md b/use-cases/virgo/README.md new file mode 100644 index 00000000..52b7ef41 --- /dev/null +++ b/use-cases/virgo/README.md @@ -0,0 +1,61 @@ +# Noise Simulation for Gravitational Waves Detector (Virgo) + +The code is adapted from +[this notebook](https://github.com/interTwin-eu/DT-Virgo-notebooks/blob/main/WP_4_4/interTwin_wp_4.4_synthetic_data.ipynb) +available on the Virgo use case's [repository](https://github.com/interTwin-eu/DT-Virgo-notebooks). + +## Installation + +Before continuing, install the required libraries in the pre-existing itwinai environment. + +```bash +pip install -r requirements.txt +``` + +## Training + +You can run the whole pipeline in one shot, including dataset generation, or you can +execute it from the second step (after the synthetic dataset have been generated). + +```bash +itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline + +# Run from the second step (use python-like slicing syntax). +# In this case, the dataset is loaded from "data/Image_dataset_synthetic_64x64.pkl" +itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline --steps 1: +``` + +Launch distributed training with SLURM using the dedicated `slurm.sh` job script: + +```bash +# Distributed training with torch DistributedDataParallel +PYTHON_VENV="../../envAI_hdfml" +DIST_MODE="ddp" +RUN_NAME="ddp-virgo" +TRAINING_CMD="$PYTHON_VENV/bin/itwinai exec-pipeline --config config.yaml --steps 1: --pipe-key training_pipeline -o strategy=ddp" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + slurm.sh +``` + +...and check the results in `job.out` and `job.err` log files. + +To understand how to use all the distributed strategies supported by `itwinai`, +check the content of `runall.sh`: + +```bash +bash runall.sh +``` + +> [!WARNING] +> The file `train.py` is not to be considered the suggested way to launch training, +> as it is deprecated and is there to testify an intermediate integration step +> of the use case into `itwinai`. + +When using MLFLow logger, you can visualize the logs in from the MLFlow UI: + +```bash +mlflow ui --backend-store-uri mllogs/mlflow + +# In background +mlflow ui --backend-store-uri mllogs/mlflow > /dev/null 2>&1 & +``` diff --git a/use-cases/virgo/config.yaml b/use-cases/virgo/config.yaml new file mode 100644 index 00000000..da14342e --- /dev/null +++ b/use-cases/virgo/config.yaml @@ -0,0 +1,39 @@ +# General configuration +data_root: data +epochs: 2 +batch_size: 20 +strategy: ddp +checkpoint_path: checkpoints/epoch_{}.pth + +training_pipeline: + class_path: itwinai.pipeline.Pipeline + init_args: + steps: + - class_path: data.TimeSeriesDatasetGenerator + init_args: + data_root: ${data_root} + - class_path: data.TimeSeriesDatasetSplitter + init_args: + train_proportion: 0.9 + rnd_seed: 42 + images_dataset: data/Image_dataset_synthetic_64x64.pkl + - class_path: data.TimeSeriesProcessor + - class_path: trainer.NoiseGeneratorTrainer + init_args: + generator: unet + batch_size: ${batch_size} + num_epochs: ${epochs} + strategy: ${strategy} + checkpoint_path: ${checkpoint_path} + logger: + class_path: itwinai.loggers.LoggersCollection + init_args: + loggers: + - class_path: itwinai.loggers.ConsoleLogger + init_args: + log_freq: 100 + - class_path: itwinai.loggers.MLFlowLogger + init_args: + experiment_name: Noise simulator (Virgo) + log_freq: batch + diff --git a/use-cases/virgo/data.py b/use-cases/virgo/data.py new file mode 100644 index 00000000..537de202 --- /dev/null +++ b/use-cases/virgo/data.py @@ -0,0 +1,219 @@ +from typing import Optional, Tuple, Any +import os +import pandas as pd +import torch +from sklearn.model_selection import train_test_split +from itwinai.components import ( + DataGetter, DataProcessor, DataSplitter, monitor_exec +) + +from src.dataset import ( + generate_dataset_aux_channels, + generate_dataset_main_channel, + generate_cut_image_dataset, + normalize_ +) + + +class TimeSeriesDatasetGenerator(DataGetter): + # TODO: move configuration to the constructor. + def __init__( + self, + data_root: str = "data", + name: Optional[str] = None + ) -> None: + super().__init__(name) + self.save_parameters(**self.locals2params(locals())) + self.data_root = data_root + if not os.path.exists(data_root): + os.makedirs(data_root, exist_ok=True) + + @monitor_exec + def execute(self) -> pd.DataFrame: + """Generate a time-series dataset, convert it to Q-plots, + save it to disk, and return it. + + Returns: + pd.DataFrame: dataset of Q-plot images. + """ + df_aux_ts = generate_dataset_aux_channels( + 1000, 3, duration=16, sample_rate=500, + num_waves_range=(20, 25), noise_amplitude=0.6 + ) + df_main_ts = generate_dataset_main_channel( + df_aux_ts, weights=None, noise_amplitude=0.1 + ) + + # save datasets + save_name_main = 'TimeSeries_dataset_synthetic_main.pkl' + save_name_aux = 'TimeSeries_dataset_synthetic_aux.pkl' + df_main_ts.to_pickle(os.path.join(self.data_root, save_name_main)) + df_aux_ts.to_pickle(os.path.join(self.data_root, save_name_aux)) + + # Transform to images and save to disk + df_ts = pd.concat([df_main_ts, df_aux_ts], axis=1) + df = generate_cut_image_dataset( + df_ts, list(df_ts.columns), + num_processes=20, square_size=64 + ) + save_name = 'Image_dataset_synthetic_64x64.pkl' + df.to_pickle(os.path.join(self.data_root, save_name)) + return df + + +class TimeSeriesDatasetSplitter(DataSplitter): + def __init__( + self, + train_proportion: int | float, + validation_proportion: int | float = 0.0, + test_proportion: int | float = 0.0, + rnd_seed: int | None = None, + images_dataset: str = "data/Image_dataset_synthetic_64x64.pkl", + name: str | None = None + ) -> None: + super().__init__( + train_proportion, validation_proportion, + test_proportion, name + ) + self.save_parameters(**self.locals2params(locals())) + self.validation_proportion = 1-train_proportion + self.rnd_seed = rnd_seed + self.images_dataset = images_dataset + + def get_or_load(self, dataset: Optional[pd.DataFrame] = None): + """If the dataset is not given, load it from disk.""" + if dataset is None: + print("WARNING: loading time series dataset from disk.") + return pd.read_pickle(self.images_dataset) + return dataset + + @monitor_exec + def execute( + self, + dataset: Optional[pd.DataFrame] = None + ) -> Tuple: + """Splits a dataset into train, validation and test splits. + + Args: + dataset (pd.DataFrame): input dataset. + + Returns: + Tuple: tuple of train, validation and test splits. Test is None. + """ + dataset = self.get_or_load(dataset) + + # Convert data to torch + df = dataset.applymap(lambda x: torch.tensor(x)) + + # Divide Image dataset in main and aux channels. Note that df + # generated in the section Generate Synthetic Dataset will always have + # the main channel as its first column + main_channel = list(df.columns)[0] + aux_channels = list(df.columns)[1:] + + df_aux_all_2d = pd.DataFrame(df[aux_channels]) + df_main_all_2d = pd.DataFrame(df[main_channel]) + X_train_2d, X_test_2d, y_train_2d, y_test_2d = train_test_split( + df_aux_all_2d, df_main_all_2d, + test_size=self.validation_proportion, random_state=self.rnd_seed) + return (X_train_2d, y_train_2d), (X_test_2d, y_test_2d), None + + +class TimeSeriesProcessor(DataProcessor): + def __init__(self, name: str | None = None) -> None: + super().__init__(name) + self.save_parameters(**self.locals2params(locals())) + + @monitor_exec + def execute( + self, + train_dataset: Tuple, + validation_dataset: Tuple, + test_dataset: Any = None + ) -> Tuple[torch.Tensor, torch.Tensor, None]: + """Pre-process datasets: rearrange and normalize before training. + + Args: + train_dataset (Tuple): training dataset. + validation_dataset (Tuple): validation dataset. + test_dataset (Any, optional): unused placeholder. Defaults to None. + + Returns: + Tuple[torch.Tensor, torch.Tensor, None]: train, validation, and + test (placeholder) datasets. Ready to be used for training. + """ + X_train_2d, y_train_2d = train_dataset + X_test_2d, y_test_2d = validation_dataset + + # Name of the main channel (assuming it's in position 0) + main_channel = list(y_train_2d.columns)[0] + + # TRAINING SET + + # # smaller dataset + # signal_data_train_small_2d = torch.stack([ + # torch.stack([y_train_2d[main_channel].iloc[i]]) + # for i in range(100) + # ]) # for i in range(y_train.shape[0]) + # aux_data_train_small_2d = torch.stack([ + # torch.stack([X_train_2d.iloc[i][0], X_train_2d.iloc[i] + # [1], X_train_2d.iloc[i][2]]) + # for i in range(100) + # ]) # for i in range(X_train.shape[0]) + + # whole dataset + signal_data_train_2d = torch.stack([ + torch.stack([y_train_2d[main_channel].iloc[i]]) + for i in range(y_train_2d.shape[0]) + ]) + aux_data_train_2d = torch.stack([ + torch.stack( + [X_train_2d.iloc[i][0], X_train_2d.iloc[i][1], + X_train_2d.iloc[i][2]]) + for i in range(X_train_2d.shape[0]) + ]) + + # concatenate torch.tensors + train_data_2d = torch.cat( + [signal_data_train_2d, aux_data_train_2d], dim=1) + # train_data_small_2d = torch.cat( + # [signal_data_train_small_2d, aux_data_train_small_2d], dim=1) + + # VALIDATION SET + + # # smaller dataset + # signal_data_test_small_2d = torch.stack([ + # torch.stack( + # [y_test_2d[main_channel].iloc[i]]) + # for i in range(100) + # ]) # for i in range(y_test.shape[0]) + # aux_data_test_small_2d = torch.stack([ + # torch.stack( + # [X_test_2d.iloc[i][0], X_test_2d.iloc[i][1], + # X_test_2d.iloc[i][2]]) + # for i in range(100) + # ]) # for i in range(X_test.shape[0]) + + # whole dataset + signal_data_test_2d = torch.stack([ + torch.stack( + [y_test_2d[main_channel].iloc[i]]) + for i in range(y_test_2d.shape[0]) + ]) + aux_data_test_2d = torch.stack([ + torch.stack( + [X_test_2d.iloc[i][0], X_test_2d.iloc[i][1], + X_test_2d.iloc[i][2]]) + for i in range(X_test_2d.shape[0]) + ]) + + test_data_2d = torch.cat( + [signal_data_test_2d, aux_data_test_2d], dim=1) + # test_data_small_2d = torch.cat( + # [signal_data_test_small_2d, aux_data_test_small_2d], dim=1) + + # NORMALIZE + train_data_2d = normalize_(train_data_2d) + test_data_2d = normalize_(test_data_2d) + + return train_data_2d, test_data_2d, None diff --git a/use-cases/virgo/requirements.txt b/use-cases/virgo/requirements.txt new file mode 100644 index 00000000..eba0b051 --- /dev/null +++ b/use-cases/virgo/requirements.txt @@ -0,0 +1,5 @@ +gwpy +h5py +pandas +scikit-learn +matplotlib diff --git a/use-cases/virgo/runall.sh b/use-cases/virgo/runall.sh new file mode 100644 index 00000000..25c2f606 --- /dev/null +++ b/use-cases/virgo/runall.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Python virtual environment (no conda/micromamba) +PYTHON_VENV="../../envAI_hdfml" + +# Clear SLURM logs (*.out and *.err files) +rm -rf logs_slurm checkpoints* +mkdir logs_slurm +rm -rf logs_torchrun + +# DDP itwinai +DIST_MODE="ddp" +RUN_NAME="ddp-itwinai" +TRAINING_CMD="$PYTHON_VENV/bin/itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline --steps 1: -o strategy=ddp -o checkpoint_path=checkpoints_ddp/epoch_{}.pth" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + +# DeepSpeed itwinai +DIST_MODE="deepspeed" +RUN_NAME="deepspeed-itwinai" +TRAINING_CMD="$PYTHON_VENV/bin/itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline --steps 1: -o strategy=deepspeed -o checkpoint_path=checkpoints_deepspeed/epoch_{}.pth" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh + +# Horovod itwinai +DIST_MODE="horovod" +RUN_NAME="horovod-itwinai" +TRAINING_CMD="$PYTHON_VENV/bin/itwinai exec-pipeline --config config.yaml --pipe-key training_pipeline --steps 1: -o strategy=horovod -o checkpoint_path=checkpoints_horovod/epoch_{}.pth" +sbatch --export=ALL,DIST_MODE="$DIST_MODE",RUN_NAME="$RUN_NAME",TRAINING_CMD="$TRAINING_CMD",PYTHON_VENV="$PYTHON_VENV" \ + --job-name="$RUN_NAME-n$N" \ + --output="logs_slurm/job-$RUN_NAME-n$N.out" \ + --error="logs_slurm/job-$RUN_NAME-n$N.err" \ + slurm.sh \ No newline at end of file diff --git a/use-cases/virgo/slurm.sh b/use-cases/virgo/slurm.sh new file mode 100644 index 00000000..2a2a15d8 --- /dev/null +++ b/use-cases/virgo/slurm.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +# SLURM jobscript for JSC systems + +# Job configuration +#SBATCH --job-name=distributed_training +#SBATCH --account=intertwin +#SBATCH --mail-user= +#SBATCH --mail-type=ALL +#SBATCH --output=job.out +#SBATCH --error=job.err +#SBATCH --time=00:30:00 + +# Resources allocation +#SBATCH --partition=batch +#SBATCH --nodes=2 +#SBATCH --gpus-per-node=4 +#SBATCH --cpus-per-gpu=4 +#SBATCH --exclusive + +# gres options have to be disabled for deepv +#SBATCH --gres=gpu:4 + +# Load environment modules +ml Stages/2024 GCC OpenMPI CUDA/12 MPI-settings/CUDA Python HDF5 PnetCDF libaio mpi4py + +# Job info +echo "DEBUG: TIME: $(date)" +sysN="$(uname -n | cut -f2- -d.)" +sysN="${sysN%%[0-9]*}" +echo "Running on system: $sysN" +echo "DEBUG: EXECUTE: $EXEC" +echo "DEBUG: SLURM_SUBMIT_DIR: $SLURM_SUBMIT_DIR" +echo "DEBUG: SLURM_JOB_ID: $SLURM_JOB_ID" +echo "DEBUG: SLURM_JOB_NODELIST: $SLURM_JOB_NODELIST" +echo "DEBUG: SLURM_NNODES: $SLURM_NNODES" +echo "DEBUG: SLURM_NTASKS: $SLURM_NTASKS" +echo "DEBUG: SLURM_TASKS_PER_NODE: $SLURM_TASKS_PER_NODE" +echo "DEBUG: SLURM_SUBMIT_HOST: $SLURM_SUBMIT_HOST" +echo "DEBUG: SLURMD_NODENAME: $SLURMD_NODENAME" +echo "DEBUG: CUDA_VISIBLE_DEVICES: $CUDA_VISIBLE_DEVICES" +if [ "$DEBUG" = true ] ; then + echo "DEBUG: NCCL_DEBUG=INFO" + export NCCL_DEBUG=INFO +fi +echo + +# Setup env for distributed ML +export CUDA_VISIBLE_DEVICES="0,1,2,3" +export OMP_NUM_THREADS=1 +if [ "$SLURM_CPUS_PER_GPU" -gt 0 ] ; then + export OMP_NUM_THREADS=$SLURM_CPUS_PER_GPU +fi + +# Env vairables check +if [ -z "$DIST_MODE" ]; then + >&2 echo "ERROR: env variable DIST_MODE is not set. Allowed values are 'horovod', 'ddp' or 'deepspeed'" + exit 1 +fi +if [ -z "$RUN_NAME" ]; then + >&2 echo "WARNING: env variable RUN_NAME is not set. It's a way to identify some specific run of an experiment." + RUN_NAME=$DIST_MODE +fi +if [ -z "$TRAINING_CMD" ]; then + >&2 echo "ERROR: env variable TRAINING_CMD is not set. It's the python command to execute." + exit 1 +fi +if [ -z "$PYTHON_VENV" ]; then + >&2 echo "WARNING: env variable PYTHON_VENV is not set. It's the path to a python virtual environment." +else + # Activate Python virtual env + source $PYTHON_VENV/bin/activate +fi + +# Get GPUs info per node +srun --cpu-bind=none --ntasks-per-node=1 bash -c 'echo -e "NODE hostname: $(hostname)\n$(nvidia-smi)\n\n"' + +# Launch training +if [ "$DIST_MODE" == "ddp" ] ; then + echo "DDP training: $TRAINING_CMD" + srun --cpu-bind=none --ntasks-per-node=1 \ + bash -c "torchrun \ + --log_dir='logs_torchrun' \ + --nnodes=$SLURM_NNODES \ + --nproc_per_node=$SLURM_GPUS_PER_NODE \ + --rdzv_id=$SLURM_JOB_ID \ + --rdzv_conf=is_host=\$(((SLURM_NODEID)) && echo 0 || echo 1) \ + --rdzv_backend=c10d \ + --rdzv_endpoint='$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)'i:29500 \ + $TRAINING_CMD" +elif [ "$DIST_MODE" == "deepspeed" ] ; then + echo "DEEPSPEED training: $TRAINING_CMD" + MASTER_ADDR=$(scontrol show hostnames "\$SLURM_JOB_NODELIST" | head -n 1)i + export MASTER_ADDR + export MASTER_PORT=29500 + + srun --cpu-bind=none --ntasks-per-node=$SLURM_GPUS_PER_NODE --cpus-per-task=$SLURM_CPUS_PER_GPU \ + $TRAINING_CMD + + # # Run with deepspeed launcher: set --ntasks-per-node=1 + # # https://www.deepspeed.ai/getting-started/#multi-node-environment-variables + # export NCCL_IB_DISABLE=1 + # export NCCL_SOCKET_IFNAME=eth0 + # nodelist=$(scontrol show hostname $SLURM_NODELIST) + # echo "$nodelist" | sed -e 's/$/ slots=4/' > .hostfile + # # Requires passwordless SSH access among compute node + # srun --cpu-bind=none deepspeed --hostfile=.hostfile $TRAINING_CMD --deepspeed + # rm .hostfile +elif [ "$DIST_MODE" == "horovod" ] ; then + echo "HOROVOD training: $TRAINING_CMD" + srun --cpu-bind=none --ntasks-per-node=$SLURM_GPUS_PER_NODE --cpus-per-task=$SLURM_CPUS_PER_GPU \ + $TRAINING_CMD +else + >&2 echo "ERROR: unrecognized \$DIST_MODE env variable" + exit 1 +fi diff --git a/use-cases/virgo/src/dataset.py b/use-cases/virgo/src/dataset.py new file mode 100644 index 00000000..9e11d13c --- /dev/null +++ b/use-cases/virgo/src/dataset.py @@ -0,0 +1,398 @@ +""" +Utils to create a synthetic dataset to train generative models with. + +The section is split in two parts: +- 1) **TimeSeries Dataset** Generation of a gwpy TimeSeries dataset + making use of random noise and sinusoidal functions +- 2) **Q-plot Dataset** Conversion of TimeSeries dataset into 2D + Image dataset making use of q_transform + +The dataset used to train the NN with is created as a 2D images. Note that +you do not need to run the two sections each time, but can rather save +the dataset after creating it once and loading it at the beginning of +Process Data that to save time. +""" + + +import pandas as pd +import numpy as np +from tqdm import tqdm +import matplotlib.pyplot as plt +import multiprocessing +from gwpy.timeseries import TimeSeries +import matplotlib.transforms as mtrans + + +def generate_dataset_aux_channels( + rows, columns, duration=10, sample_rate=500, + num_waves_range=(10, 15), noise_amplitude=0.1 +): + """ + Generate a Pandas DataFrame with randomly generated smooth sine wave time + series with added smooth random noise. + + Parameters: + - rows (int): Number of rows in the DataFrame. + - columns (int): Number of columns in the DataFrame. + - duration (float): Duration of the time series data in seconds + (default is 6 seconds). + - sample_rate (int): Sampling rate of the time series data in samples + per second (default is 500 samples per second). + - num_waves_range (tuple): Range for the random number of sine waves + to be generated for each time series. + Format: (min_num_waves, max_num_waves) (default is (10, 15)). + - noise_amplitude (float): Amplitude of the smooth random noise added + to the time series data (default is 0.1). + + Returns: + - DataFrame: Pandas DataFrame containing the generated time series + data. + """ + # Generate time array + times = np.linspace(0, duration, duration * sample_rate) + + # Initialize an empty list to store individual DataFrames for each row + dfs = [] + # columns_list = [f'Aux_{i+1}' for i in range(columns)] + + for index in range(rows): + df_dict = {} + for col in range(columns): + # Initialize an array to store the generated wave data for this row + wave_data = np.zeros(len(times)) + # Determine the number of sine waves to generate for this column + # randomly + num_waves = np.random.randint(*num_waves_range) + + # Generate each sine wave + for _ in range(num_waves): + # Randomly generate parameters for the sine wave (amplitude, + # frequency, phase) + amplitude = np.random.uniform(0.5, 2.0) + frequency = np.random.uniform(0.5, 5.0) + phase = np.random.uniform(0, 2*np.pi) + + # Generate the sine wave and add it to the wave_data + wave_data += amplitude * \ + np.sin(2 * np.pi * frequency * times + phase) + + # Add smooth random noise to the wave data + smooth_noise = np.random.normal(0, noise_amplitude, len(times)) + wave_data += smooth_noise + + # Create a TimeSeries object from the wave data + ts = TimeSeries(wave_data, t0=0, dt=1/sample_rate) + df_dict[f'Aux_{col+1}'] = [ts] + + # Create a DataFrame with the TimeSeries + df_row = pd.DataFrame(df_dict) + + # Append the DataFrame to the list + dfs.append(df_row) + + # Concatenate the list of DataFrames into a single DataFrame along + # rows axis + df = pd.concat(dfs, ignore_index=True, axis=0) + + return df + + +def generate_dataset_main_channel(input_df, weights=None, noise_amplitude=0.1): + """ + Generate a dataset where each row of a single column is a weighted linear + combination of the entries + in the corresponding row in the input DataFrame plus random noise. + + Parameters: + - input_df (DataFrame): Input DataFrame generated by + ``generate_smooth_noisy_sine_wave_series``. + - weights (array-like): Optional weights for each entry in the row. + If None, random weights are generated (default is None). + - noise_amplitude (float): Amplitude of the random noise added to the + linear combination (default is 0.1). + + Returns: + - DataFrame: Pandas DataFrame containing the generated linear + combination dataset. + """ + dt = input_df.iloc[0, 0].dt + # Initialize an empty list to store the linear combination values + linear_combination_data = [] + + # Generate random weights if not provided + if weights is None: + # randomly generate weights in range [0.5,1.5] + weights = np.random.rand(len(list(input_df.columns))) + 0.5 + print(weights) + # Iterate over rows of the input DataFrame + for index, row in input_df.iterrows(): + + # Compute the weighted linear combination of the row values + linear_combination = np.sum(row * weights) + + # Add random noise to the linear combination + linear_combination += np.random.normal(0, noise_amplitude) + + # Append the result to the list + linear_combination_data.append( + [TimeSeries(linear_combination, dt=dt, t0=0)]) + + # Create a DataFrame with the linear combination data + linear_combination_df = pd.DataFrame( + linear_combination_data, columns=['Main']) + + return linear_combination_df + + +def extract_peak_frequency(hq): + """ + Calculates peak frequency (and relative time) of a given qplot + + Input: + -hq (gwpy.Spectrgram) : Qtransform + + Return: + -index_time (int) : time index in qtransform relative to peak frequency + -index_freq (int) : frequency index in qtransorm relative to peak frequency + """ + + # Calculate peak frequency, time and energy density + index_flat = np.argmax(hq.value) + + # Convert the flattened index to 2D index + index_time, index_freq = np.unravel_index(index_flat, hq.shape) + # /converting_factor_frequency + # peak_freq = hq.frequencies.value[index_freq] + # peak_value = np.max(hq.value) + # peak_time = hq.times.value[index_time] + + return index_time, index_freq + + +def cut_image(qplot_array, index_freq, index_time, square_size=64): + """ + Cut qplot as square_size X square_size 2D np.array centered at peak + frequency and corresponding time + + Input: + - qplot_array (np.array) : qplot relative to the whole TimeSeries + - index_time (int) : time index in qtransform relative to peak frequency + - index_freq (int) : frequency index in qtransorm relative to peak + frequency + - square_size (int) : Size in pixels of qplot image (default 64) + + Return: + - subarray (np.array) : qplot cutted as square_size X square_size np.array + + """ + + center_x = index_time + center_y = index_freq # Replace with the actual y-coordinate + + original_width, original_height = qplot_array.shape + + # Calculate the starting and ending indices for the subarray + start_x = max(center_x - square_size // 2, 0) + # print(start_x) + end_x = min(start_x + square_size, original_width) + # print(end_x) + start_y = max(center_y - square_size // 2, 0) + # print(start_y) + end_y = min(start_y + square_size, original_height) + # print(end_y) + # start_y=0 + # end_y=square_size + + # Adjust indices if needed to make sure the resulting subarray is + # (square_size X square_size) + if end_x - start_x < square_size: + diff_x = square_size - (end_x - start_x) + if end_x < original_width: + end_x += diff_x + else: + start_x -= diff_x + if end_y - start_y < square_size: + diff_y = square_size - (end_y - start_y) + if end_y < original_height: + end_y += diff_y + else: + start_y -= diff_y + + subarray = qplot_array[start_x:end_x, start_y:end_y] + # print(subarray.shape) + # print(type(subarray)) + + return subarray + + +def process_image(row, row_idx, channels, square_size): + """ + Processes df's row to generate qplot images + + Input: + - row (pd.Series) : row of TimeSeries Dataframe + - row_idx (int) : index relative to row in DataFrame + - channels (list): Name of columns of DataFrame + - square_size (int): Size in pixels of qplot image + + Return: + df_row (DataFrame): Row containing qplot images as 2D np.array + """ + + # res_list = [] + df_row = pd.DataFrame(columns=channels) + for i, channel in enumerate(channels): + + qplot = row[channel].q_transform(frange=(10, 150)) + + # calculate peak frequency and time indices for strain channel + if i == 0: + index_time, index_freq = extract_peak_frequency(qplot) + + # Convert the spectrogram to a NumPy array + qplot_array = qplot.value + qplot_array_cut = cut_image( + qplot_array, index_freq, index_time, square_size) + df_row[channel] = [qplot_array_cut] + + return df_row + + +def generate_cut_image_dataset( + df, channels, num_processes=20, square_size=128 +): + """ + Generates qplot dataset taking pandas df containing main+aux channels as + input. The output is a df containing qtransforms (frequency range 10-150Hz) + in the form of square_sizexsquare_size 2d np.array. + + + Parameters: + - df (DataFrame): DataFrame containing Main and Aux channels' + gwpy TimeSeries (Main channel is always first). + - channels (list): Name of columns in the DataFrame. + - num_processes (int): Number of cores for multiprocess (default 20) + - square_size (int): Size in pixels of qplot image (default is + 500 samples per second). + + Returns: + - DataFrame: Pandas DataFrame containing the q_transform np.array data. + """ + + print(channels) + args = [(df.iloc[row], row, channels, square_size) + for row in range(df.shape[0])] + with multiprocessing.Pool(processes=num_processes) as pool: + # Use map to pass multiple arguments to process_image + results = list( + tqdm(pool.starmap(process_image, args), total=len(args))) + + df = pd.concat(results, ignore_index=True) + return df + + +def show_dataset(df, size, num_plots=10): + """ + Plots qtransforms for first 4 columns in given df + + Input: + - df (DataFrame) : DataFrame containing qtransforms in the form of 2d + np.array + - size (int) : square size in pixels of qplots + - num_plots (int) : number of rows of df to make the plot for (default 10) + + Return + - nothing, it just displays plots + """ + + ch_list = list(df.columns) + fig, axes = plt.subplots(2*num_plots, 2, figsize=(18, 12*num_plots)) + for j in range(num_plots): + + qplt_r = np.flipud(df.iloc[j, 0].T) + qplt_aux1 = np.flipud(df.iloc[j, 1].T) + qplt_aux2 = np.flipud(df.iloc[j, 2].T) + qplt_aux3 = np.flipud(df.iloc[j, 3].T) + + # Create a single subfigure with two plots + # fig, axes = plt.subplots(2, 3, figsize=(18, 12)) + + # Plot for Real + im_r = axes[2*j, 0].imshow( + qplt_r, aspect='auto', + extent=[0, size, 0, size], vmin=0, vmax=25) + axes[2*j, 0].set_title(ch_list[0]) + axes[2*j, 0].set_xlabel('Time') + axes[2*j, 0].set_ylabel('Frequency') + fig.colorbar(im_r, ax=axes[2*j, 0]) # Add colorbar for Real + + # Plot for aux + im_g = axes[2*j, 1].imshow( + qplt_aux1, aspect='auto', + extent=[0, size, 0, size], vmin=0, vmax=25) + axes[2*j, 1].set_title(ch_list[1]) + axes[2*j, 1].set_xlabel('Time') + axes[2*j, 1].set_ylabel('Frequency') + fig.colorbar(im_g, ax=axes[2*j, 1]) # Add colorbar for Generated + # + im_g = axes[2*j+1, 0].imshow( + qplt_aux2, aspect='auto', + extent=[0, size, 0, size], vmin=0, vmax=25) + axes[2*j+1, 0].set_title(ch_list[2]) + axes[2*j+1, 0].set_xlabel('Time') + axes[2*j+1, 0].set_ylabel('Frequency') + fig.colorbar(im_g, ax=axes[2*j+1, 0]) # Add colorbar for Generated + # + im_g = axes[2*j+1, 1].imshow( + qplt_aux3, aspect='auto', + extent=[0, size, 0, size], vmin=0, vmax=25) + axes[2*j+1, 1].set_title(ch_list[3]) + axes[2*j+1, 1].set_xlabel('Time') + axes[2*j+1, 1].set_ylabel('Frequency') + fig.colorbar(im_g, ax=axes[2*j+1, 1]) # Add colorbar for Generated + + # Get the bounding boxes of the axes including text decorations + r = fig.canvas.get_renderer() + + def get_bbox(ax): return ax.get_tightbbox( + r).transformed(fig.transFigure.inverted()) + bboxes = np.array(list(map(get_bbox, axes.flat)), + mtrans.Bbox).reshape(axes.shape) + + # Get the minimum and maximum extent, get the coordinate half-way + # between those + ymax = np.array(list(map(lambda b: b.y1, bboxes.flat)) + ).reshape(axes.shape).max(axis=1) + ymin = np.array(list(map(lambda b: b.y0, bboxes.flat)) + ).reshape(axes.shape).min(axis=1) + ys = np.c_[ymax[1:], ymin[:-1]].mean(axis=1) + + # Draw a horizontal lines at those coordinates + for y in ys[1::2]: + line = plt.Line2D( + [0, 1], [y, y], transform=fig.transFigure, color="black") + fig.add_artist(line) + + # plt.savefig('very high loss qplots.png') + plt.show() + + +def normalize_(data, chan=4): + """ + Normalizes the qplot data to the range [0,1] for NN convergence purposes + + Input: + - data (torch.Tensor) : dataset of qtransforms + - chan (int) : number of channels in dataset (default 4) + + Return: + - data (torch.tensor) : normalized dataset + """ + # Compute the maximum value for each channel across all 900 tensors + max_vals = data.view(data.shape[0], data.shape[1], -1).max(0)[0].max( + 1)[0] + print("Maximum values for each channel across all tensors:", + max_vals, max_vals.shape) + # Divide each element by the maximum value of its channel + data /= max_vals.view(1, chan, 1, 1) + return data diff --git a/use-cases/virgo/src/model.py b/use-cases/virgo/src/model.py new file mode 100644 index 00000000..cda17bb4 --- /dev/null +++ b/use-cases/virgo/src/model.py @@ -0,0 +1,513 @@ +""" +In this section we define different NN architectures models, and initialise +one of them as the generator to use in training and inference. + +This section is split in three parts: +- 1) **Weight Initialization**, where we define the function to initialise the + weights of the NN models according to certain parameters and distributions + given as input. +- 2) **NN Models**, where we define different NN models exploiting different + architectures. +- 3) **Generator**, where we initialise one of the above models as the + generator to use in training and inference +""" + +import torch.nn as nn +import torch + + +# SHALLOW DECODER + + +class Decoder(nn.Module): + """ + Decoder network. + + Args: + - in_channels (int): Number of input channels. + - kernel_size (int): Size of the convolutional kernel (default is 7). + - a (float): Scaling factor (default is 80.0). + - norm (bool): Whether to apply normalization (default is True). + """ + + def __init__(self, in_channels, kernel_size=7, norm=True): + super(Decoder, self).__init__() + + self.conv1 = nn.Conv2d( + in_channels, 32, kernel_size=kernel_size, stride=1, + padding=kernel_size // 2) + self.relu1 = nn.LeakyReLU(0.2, inplace=True) + + self.conv2 = nn.Conv2d( + 32, 64, kernel_size=kernel_size, stride=1, + padding=kernel_size // 2) + self.relu2 = nn.LeakyReLU(0.2, inplace=True) + + self.conv3 = nn.Conv2d( + 64, 64, kernel_size=kernel_size, stride=1, + padding=kernel_size // 2) + self.relu3 = nn.LeakyReLU(0.2, inplace=True) + + self.conv4 = nn.Conv2d( + 64, 1, kernel_size=kernel_size, stride=1, + padding=kernel_size // 2) + + if norm: + self.activation = torch.nn.Sigmoid() + else: + self.activation = torch.nn.ReLU() + + def _forward_features(self, x): + """ + Perform forward pass through the network layers. + + Args: + - x (torch.Tensor): Input tensor. + + Returns: + - torch.Tensor: Output tensor. + """ + x = self.relu1(self.conv1(x)) + x = self.relu2(self.conv2(x)) + x = self.relu3(self.conv3(x)) + x = self.conv4(x) + x = self.activation(x) + return x + + def forward(self, x): + """ + Forward pass through the network. + + Args: + - x (torch.Tensor): Input tensor. + + Returns: + - torch.Tensor: Output tensor. + """ + return self._forward_features(x) + + +# DEEP DECODER + + +class Decoder_2d_deep(nn.Module): + """ + Deep 2D decoder network. + + Args: + - in_channels (int): Number of input channels. + - kernel_size (int): Size of the convolutional kernel (default is 5). + """ + + def __init__(self, in_channels, kernel_size=5, norm=True): + super(Decoder_2d_deep, self).__init__() + + self.conv1 = nn.Conv2d( + in_channels, 64, kernel_size=kernel_size, stride=1, + padding=kernel_size // 2) + self.relu1 = nn.LeakyReLU(0.3, inplace=True) + + self.conv2 = nn.Conv2d( + 64, 128, kernel_size=kernel_size, stride=1, + padding=kernel_size // 2) + self.relu2 = nn.LeakyReLU(0.3, inplace=True) + + self.conv3 = nn.Conv2d( + 128, 256, kernel_size=kernel_size, stride=1, + padding=kernel_size // 2) + self.relu3 = nn.LeakyReLU(0.3, inplace=True) + + self.conv4 = nn.Conv2d( + 256, 1, kernel_size=kernel_size, stride=1, + padding=kernel_size // 2) + if norm: + self.activation = torch.nn.Sigmoid() + else: + self.activation = torch.nn.ReLU() + + def _forward_features(self, x): + """ + Perform forward pass through the network layers. + + Args: + - x (torch.Tensor): Input tensor. + + Returns: + - torch.Tensor: Output tensor. + """ + x = self.relu1(self.conv1(x)) + x = self.relu2(self.conv2(x)) + x = self.relu3(self.conv3(x)) + x = self.conv4(x) + x = self.activation(x) + return x + + def forward(self, x): + """ + Forward pass through the network. + + Args: + - x (torch.Tensor): Input tensor. + + Returns: + - torch.Tensor: Output tensor. + """ + return self._forward_features(x) + + +# RESNET + + +class ResidualBlock(nn.Module): + """ + Residual block module. + + Args: + - in_features (int): Number of input features/channels. + """ + + def __init__(self, in_features): + super(ResidualBlock, self).__init__() + + # Define the block sequence + self.block = nn.Sequential( + # Pads the input tensor using the reflection of the input boundary + nn.ReflectionPad2d(1), + nn.Conv2d(in_features, in_features, 3), # 2D convolutional layer + nn.InstanceNorm2d(in_features), # Instance normalization + nn.ReLU(inplace=True), # ReLU activation function + nn.ReflectionPad2d(1), + nn.Conv2d(in_features, in_features, 3), + nn.InstanceNorm2d(in_features) + ) + + def forward(self, x): + """ + Forward pass through the residual block. + + Args: + - x (torch.Tensor): Input tensor. + + Returns: + - torch.Tensor: Output tensor. + """ + return x + self.block(x) + + +class GeneratorResNet(nn.Module): + """ + Generator network using ResNet architecture. + + Args: + - input_shape (int): Number of input features/channels. + - num_residual_block (int): Number of residual blocks. + - output_shape (int): Number of output features/channels. + """ + + def __init__( + self, input_shape, num_residual_block, output_shape, norm=False + ): + super(GeneratorResNet, self).__init__() + + channels = input_shape + target_channels = output_shape + # Initial Convolution Block + out_features = 64 + if norm: + self.final_activation = torch.nn.Sigmoid() + else: + self.final_activation = torch.nn.ReLU() + + model = [ + nn.ReflectionPad2d(channels), + nn.Conv2d(channels, out_features, 7), + nn.InstanceNorm2d(out_features), + nn.ReLU(inplace=True) + ] + in_features = out_features + + # Downsampling + for _ in range(2): + out_features *= 2 + model += [ + nn.Conv2d(in_features, out_features, 3, stride=2, padding=1), + nn.InstanceNorm2d(out_features), + nn.ReLU(inplace=True) + ] + in_features = out_features + + # Residual blocks + for _ in range(num_residual_block): + model += [ResidualBlock(out_features)] + + # Upsampling + for _ in range(2): + out_features //= 2 + model += [ + nn.Upsample(scale_factor=2), # Upsampling layer + nn.Conv2d(in_features, out_features, 3, stride=1, padding=1), + nn.ReLU(inplace=True) + ] + in_features = out_features + + # Output Layer + model += [nn.ReflectionPad2d(target_channels), + nn.Conv2d(out_features, target_channels, 3), + self.final_activation # Sigmoid activation function + ] + + # Unpacking + self.model = nn.Sequential(*model) + + def forward(self, x): + """ + Forward pass through the generator network. + + Args: + - x (torch.Tensor): Input tensor. + + Returns: + - torch.Tensor: Output tensor. + """ + return self.model(x) + + +# U-NET + + +class Conv2dBlock(nn.Module): + """ + Convolutional block module. + + Args: + - in_channels (int): Number of input channels. + - out_channels (int): Number of output channels. + - kernel_size (int): Size of the convolutional kernel (default is 3). + """ + + def __init__(self, in_channels, out_channels, kernel_size=3): + super(Conv2dBlock, self).__init__() + self.conv1 = nn.Conv2d(in_channels, out_channels, + kernel_size, padding=1) + self.conv2 = nn.Conv2d( + out_channels, out_channels, kernel_size, padding=1) + self.activation = nn.ReLU() + + def forward(self, x): + """ + Forward pass through the convolutional block. + + Args: + - x (torch.Tensor): Input tensor. + + Returns: + - torch.Tensor: Output tensor. + """ + x = self.conv1(x) + x = self.activation(x) + x = self.conv2(x) + x = self.activation(x) + return x + + +class EncoderBlock(nn.Module): + """ + Encoder block module. + + Args: + - in_channels (int): Number of input channels. + - out_channels (int): Number of output channels. + - pool_size (tuple): Size of the pooling kernel (default is (2, 2)). + - dropout (float): Dropout rate (default is 0.3). + """ + + def __init__(self, in_channels, out_channels, pool_size=(2, 2), + dropout=0.3): + super(EncoderBlock, self).__init__() + self.conv_block = Conv2dBlock(in_channels, out_channels) + self.maxpool = nn.MaxPool2d(pool_size) + self.dropout = nn.Dropout2d(dropout) + + def forward(self, x): + """ + Forward pass through the encoder block. + + Args: + - x (torch.Tensor): Input tensor. + + Returns: + - torch.Tensor: Output tensor. + """ + features = self.conv_block(x) + pooled = self.maxpool(features) + pooled = self.dropout(pooled) + return features, pooled + + +class UNetEncoder(nn.Module): + """ + Encoder network module. + + Args: + - input_channels (int): Number of input channels. + """ + + def __init__(self, input_channels): + super(UNetEncoder, self).__init__() + self.block1 = EncoderBlock(input_channels, 64) + self.block2 = EncoderBlock(64, 128) + self.block3 = EncoderBlock(128, 256) + self.block4 = EncoderBlock(256, 512) + + def forward(self, x): + """ + Forward pass through the encoder network. + + Args: + - x (torch.Tensor): Input tensor. + + Returns: + - torch.Tensor: Output tensor. + """ + f1, p1 = self.block1(x) + f2, p2 = self.block2(p1) + f3, p3 = self.block3(p2) + f4, p4 = self.block4(p3) + return p4, (f1, f2, f3, f4) + + +class Bottleneck(nn.Module): + """ + Bottleneck module. + + """ + + def __init__(self): + super(Bottleneck, self).__init__() + self.conv_block = Conv2dBlock(512, 1024) + + def forward(self, x): + """ + Forward pass through the bottleneck module. + + Args: + - x (torch.Tensor): Input tensor. + + Returns: + - torch.Tensor: Output tensor. + """ + return self.conv_block(x) + + +class DecoderBlock(nn.Module): + """ + Decoder block module. + + Args: + - in_channels (int): Number of input channels. + - out_channels (int): Number of output channels. + - kernel_size (int): Size of the convolutional kernel (default is 3). + - stride (int): Stride size for the convolutional operation + (default is 2). + - dropout (float): Dropout rate (default is 0.3). + """ + + def __init__(self, in_channels, out_channels, kernel_size=3, stride=2, + dropout=0.3): + super(DecoderBlock, self).__init__() + self.deconv = nn.ConvTranspose2d( + in_channels, out_channels, kernel_size, stride=stride, padding=1, + output_padding=1) + self.dropout = nn.Dropout2d(dropout) + self.conv_block = Conv2dBlock(out_channels * 2, out_channels) + + def forward(self, x, conv_output): + """ + Forward pass through the decoder block. + + Args: + - x (torch.Tensor): Input tensor. + - conv_output (torch.Tensor): Output tensor from the corresponding + encoder block. + + Returns: + - torch.Tensor: Output tensor. + """ + x = self.deconv(x, output_size=conv_output.size()) + x = torch.cat([x, conv_output], dim=1) + x = self.dropout(x) + x = self.conv_block(x) + return x + + +class UNetDecoder(nn.Module): + """ + Decoder network module. + + Args: + - output_channels (int): Number of output channels. + """ + + def __init__(self, output_channels): + super(UNetDecoder, self).__init__() + self.block6 = DecoderBlock(1024, 512) + self.block7 = DecoderBlock(512, 256) + self.block8 = DecoderBlock(256, 128) + self.block9 = DecoderBlock(128, 64) + self.final_conv = nn.Conv2d(64, output_channels, kernel_size=1) + + def forward(self, x, convs): + """ + Forward pass through the decoder network. + + Args: + - x (torch.Tensor): Input tensor. + - convs (tuple): Tuple containing the output tensors from the + encoder blocks. + + Returns: + - torch.Tensor: Output tensor. + """ + f1, f2, f3, f4 = convs + x = self.block6(x, f4) + x = self.block7(x, f3) + x = self.block8(x, f2) + x = self.block9(x, f1) + outputs = self.final_conv(x) + + return outputs + + +class UNet(nn.Module): + """ + UNet network module. + + Args: + - input_channels (int): Number of input channels. + - output_channels (int): Number of output channels. + """ + + def __init__(self, input_channels=3, output_channels=1, norm=True): + super(UNet, self).__init__() + self.encoder = UNetEncoder(input_channels) + self.bottleneck = Bottleneck() + self.decoder = UNetDecoder(output_channels) + if norm: + self.final_activation = torch.nn.Sigmoid() + else: + self.final_activation = torch.nn.ReLU() + + def forward(self, x): + """ + Forward pass through the UNet network. + + Args: + - x (torch.Tensor): Input tensor. + + Returns: + - torch.Tensor: Output tensor. + """ + encoder_output, convs = self.encoder(x) + bottleneck_output = self.bottleneck(encoder_output) + outputs = self.decoder(bottleneck_output, convs) + return self.final_activation(outputs) diff --git a/use-cases/virgo/src/utils.py b/use-cases/virgo/src/utils.py new file mode 100644 index 00000000..8c0a16f9 --- /dev/null +++ b/use-cases/virgo/src/utils.py @@ -0,0 +1,71 @@ + +import torch +import numpy as np +from gwpy.timeseries import TimeSeries + + +def init_weights(net, init_type='normal', scaling=0.02): + """ + Initialize the weights of the neural network according to the specified + initialization type. + + Parameters: + - net (nn.Module): The neural network model. + - init_type (str): Type of initialization. Options: 'normal', 'xavier' + (default is 'normal'). + - scaling (float): Scaling factor for weight initialization + (default is 0.02). + """ + def init_func(m): # define the initialization function + classname = m.__class__.__name__ + if hasattr(m, 'weight') and (classname.find('Conv')) != -1: + torch.nn.init.normal_(m.weight.data, 0.0, scaling) + # BatchNorm Layer's weight is not a matrix; only normal + # distribution applies. + elif classname.find('BatchNorm2d') != -1: + torch.nn.init.normal_(m.weight.data, 1.0, scaling) + torch.nn.init.constant_(m.bias.data, 0.0) + + print('initialize network with %s' % init_type) + net.apply(init_func) # apply the initialization function + + +def calculate_iou_2d(generated, target, threshold): + """ + Calculate Intersection over Union (IoU) in the 2D plane at the specified + intensity threshold. + + Parameters: + - generated: List of time series representing the generated spectrograms + - target: List of time series representing the target spectrograms + - threshold: Intensity threshold for determining the binary masks + + Returns: + - IoU: Intersection over Union + """ + # Extract spectrogram values from time series + # print(generated[0][0]) + # print(generated[0][0].shape) + # print(type(generated[0][0])) + + spectrograms_gen = [TimeSeries( + t[0], dt=1/4096.0).q_transform(frange=(10, 1000)).value + for t in generated] + spectrograms_real = [TimeSeries( + t[0], dt=1/4096.0).q_transform(frange=(10, 1000)).value + for t in target] + + # Create binary masks based on the intensity threshold + mask1 = [spectrogram >= threshold for spectrogram in spectrograms_gen] + mask2 = [spectrogram >= threshold for spectrogram in spectrograms_real] + + # Calculate the intersection and union of the binary masks + intersection = [np.logical_and(m1, m2) for m1, m2 in zip(mask1, mask2)] + union = [np.logical_or(m1, m2) for m1, m2 in zip(mask1, mask2)] + + # Calculate Intersection over Union (IoU) + iou_list = np.array([np.sum(inter) / np.sum(uni) + for inter, uni in zip(intersection, union)]) + + iou = iou_list.mean() + return iou diff --git a/use-cases/virgo/train.py b/use-cases/virgo/train.py new file mode 100644 index 00000000..503f0990 --- /dev/null +++ b/use-cases/virgo/train.py @@ -0,0 +1,278 @@ +""" +Simplified training script: data generation + training in one +procedural script. This is an INTERMEDIATE step of integration in itwinai. +""" +import os +import pandas as pd +import torch +import torch.nn as nn +from torch.utils.data import DataLoader +import time +import numpy as np +from sklearn.model_selection import train_test_split +from tqdm import tqdm + +from src.dataset import ( + generate_dataset_aux_channels, + generate_dataset_main_channel, + generate_cut_image_dataset, + normalize_ +) +from src.model import UNet # ,Decoder, Decoder_2d_deep, GeneratorResNet +from src.utils import init_weights, calculate_iou_2d + +# Global parameters +DATA_ROOT = "data" +LOAD_DATASET = True +BATCH_SIZE = 20 +LR = 0.00005 +SAVE_CHECKPOINT = 'choose_your_path.checkpoint_epoch_{}.pth' +N_EPOCHS = 50 + + +def generate_dataset(): + df_aux_ts = generate_dataset_aux_channels( + 1000, 3, duration=16, sample_rate=500, + num_waves_range=(20, 25), noise_amplitude=0.6 + ) + df_main_ts = generate_dataset_main_channel( + df_aux_ts, weights=None, noise_amplitude=0.1 + ) + + # save datasets + save_name_main = 'TimeSeries_dataset_synthetic_main.pkl' + save_name_aux = 'TimeSeries_dataset_synthetic_aux.pkl' + df_main_ts.to_pickle(os.path.join(DATA_ROOT, save_name_main)) + df_aux_ts.to_pickle(os.path.join(DATA_ROOT, save_name_aux)) + + # Transform to images and save to disk + df_ts = pd.concat([df_main_ts, df_aux_ts], axis=1) + df = generate_cut_image_dataset( + df_ts, list(df_ts.columns), + num_processes=20, square_size=64 + ) + save_name = 'Image_dataset_synthetic_64x64.pkl' + df.to_pickle(os.path.join(DATA_ROOT, save_name)) + + +def train_decoder( + num_epochs, generator, criterion, optimizer, dataloader, + val_loader, accuracy, checkpoint_path, save_best=True +): + # num_epochs: (int) number of epochs for training + # generator: (NN.Module) NN model to train + # criterion: (torch.optim) optimizer to use in training + # dataloader: (DataLoader) training data + # val_loader: (Dataloader) validation data + # accuracy: (function) metric to measure performance of the model + # (Note not to be confused with loss) + # checkpoint_path: (str) full path (including filename in the form + # filename_{}.pkl so to insert num_epoch) to save checkpoints at + # save_best: (bool) if you want to save best performing model + + # uncomment all lines relative to accuracy if you want to measure IOU + # between generated and real spectrograms. + # Note that it significantly slows down the whole process + # it also might not work as the function has not been fully implemented yet + + loss_plot = [] + val_loss_plot = [] + acc_plot = [] + val_acc_plot = [] + best_val_loss = 5000000 + for epoch in tqdm(range(1, num_epochs+1)): + st = time.time() + epoch_loss = [] + epoch_acc = [] + for i, batch in enumerate(dataloader): + # batch= transform(batch) + target = batch[:, 0].unsqueeze(1).to(device) + # print(f'TARGET ON DEVICE: {target.get_device()}') + target = target.float() + input = batch[:, 1:].to(device) + # print(f'INPUT ON DEVICE: {input.get_device()}') + + optimizer.zero_grad() + generated = generator(input.float()) + # generated=normalize_(generated,1) + loss = criterion(generated, target) + loss.backward() + optimizer.step() + epoch_loss.append(loss.detach().cpu().numpy()) + # acc=accuracy(generated.detach().cpu().numpy(),target.detach().cpu().numpy(),20) + # epoch_acc.append(acc) + val_loss = [] + val_acc = [] + for batch in (val_loader): + # batch= transform(batch) + target = batch[:, 0].unsqueeze(1).to(device) + target = target.float() + input = batch[:, 1:].to(device) + with torch.no_grad(): + generated = generator(input.float()) + # generated=normalize_(generated,1) + loss = criterion(generated, target) + val_loss.append(loss.detach().cpu().numpy()) + # acc=accuracy(generated.detach().cpu().numpy(),target.detach().cpu().numpy(),20) + # val_acc.append(acc) + loss_plot.append(np.mean(epoch_loss)) + val_loss_plot.append(np.mean(val_loss)) + acc_plot.append(np.mean(epoch_acc)) + val_acc_plot.append(np.mean(val_acc)) + # print('epoch: {} loss: {} val loss: {} accuracy: {} val accuracy: + # {}'.format(epoch,loss_plot[-1],val_loss_plot[-1],acc_plot[-1],val_acc_plot[-1])) + et = time.time() + print('epoch: {} loss: {} val loss: {} time:{}s'.format( + epoch, loss_plot[-1], val_loss_plot[-1], et-st)) + + # Save checkpoint every 100 epochs + if (epoch+1) % 100 == 0: + # uncomment the following if you want to save checkpoint every 100 + # epochs regardless of the performance of the model + # checkpoint = { + # 'epoch': epoch, + # 'model_state_dict': generator.state_dict(), + # 'optimizer_state_dict': optimizer.state_dict(), + # 'loss': loss_plot[-1], + # 'val_loss': val_loss_plot[-1], + # } + + # checkpoint_filename = checkpoint_path.format(epoch) + # torch.save(checkpoint, checkpoint_filename) + + # instead of val_loss and best_val loss we should use accuracy!!! + if save_best and val_loss_plot[-1] < best_val_loss: + # create checkpoint + checkpoint = { + 'epoch': epoch, + 'model_state_dict': generator.state_dict(), + 'optimizer_state_dict': optimizer.state_dict(), + 'loss': loss_plot[-1], + 'val_loss': val_loss_plot[-1], + } + + # save checkpoint only if it is better than the previous ones + checkpoint_filename = checkpoint_path.format(epoch) + torch.save(checkpoint, checkpoint_filename) + + # update best model + best_val_loss = val_loss_plot[-1] + best_checkpoint_filename = checkpoint_path.format('best') + torch.save(checkpoint, best_checkpoint_filename) + + return loss_plot, val_loss_plot, acc_plot, val_acc_plot + + +if __name__ == "__main__": + if torch.cuda.is_available(): + device = 'cuda' + print(torch.cuda.get_device_name(torch.cuda.current_device())) + else: + device = 'cpu' + + # Generate dataset + if not LOAD_DATASET: + generate_dataset() + + # Split data + file_path = os.path.join(DATA_ROOT, 'Image_dataset_synthetic_64x64.pkl') + df = pd.read_pickle(file_path) + # Convert data to torch + df = df.applymap(lambda x: torch.tensor(x)) + + # Divide Image dataset in main and aux channels. Note that df generated in + # the section Generate Synthetic Dataset will always have the main channel + # as its first column + main_channel = list(df.columns)[0] + aux_channels = list(df.columns)[1:] + + df_aux_all_2d = pd.DataFrame(df[aux_channels]) + df_main_all_2d = pd.DataFrame(df[main_channel]) + + X_train_2d, X_test_2d, y_train_2d, y_test_2d = train_test_split( + df_aux_all_2d, df_main_all_2d, test_size=0.1, random_state=42) + + # TRAINING SET + + # smaller dataset + signal_data_train_small_2d = torch.stack([torch.stack( + [y_train_2d[main_channel].iloc[i]]) for i in range(100)]) + # for i in range(y_train.shape[0]) + aux_data_train_small_2d = torch.stack([torch.stack( + [X_train_2d.iloc[i][0], X_train_2d.iloc[i][1], X_train_2d.iloc[i][2]]) + for i in range(100)]) # for i in range(X_train.shape[0]) + + # whole dataset + signal_data_train_2d = torch.stack([torch.stack( + [y_train_2d[main_channel].iloc[i]]) + for i in range(y_train_2d.shape[0])]) + aux_data_train_2d = torch.stack([torch.stack( + [X_train_2d.iloc[i][0], X_train_2d.iloc[i][1], X_train_2d.iloc[i][2]]) + for i in range(X_train_2d.shape[0])]) + + # concatenate torch.tensors + train_data_2d = torch.cat([signal_data_train_2d, aux_data_train_2d], dim=1) + train_data_small_2d = torch.cat( + [signal_data_train_small_2d, aux_data_train_small_2d], dim=1) + + print(signal_data_train_2d.shape) + print(aux_data_train_2d.shape) + # -------------------------------------------- + + # TEST SET + + # smaller dataset + signal_data_test_small_2d = torch.stack([torch.stack( + [y_test_2d[main_channel].iloc[i]]) for i in range(100)]) + # for i in range(y_test.shape[0]) + aux_data_test_small_2d = torch.stack([torch.stack( + [X_test_2d.iloc[i][0], X_test_2d.iloc[i][1], X_test_2d.iloc[i][2]]) + for i in range(100)]) # for i in range(X_test.shape[0]) + + # whole dataset + signal_data_test_2d = torch.stack([torch.stack( + [y_test_2d[main_channel].iloc[i]]) for i in range(y_test_2d.shape[0])]) + aux_data_test_2d = torch.stack([torch.stack( + [X_test_2d.iloc[i][0], X_test_2d.iloc[i][1], X_test_2d.iloc[i][2]]) + for i in range(X_test_2d.shape[0])]) + + test_data_2d = torch.cat([signal_data_test_2d, aux_data_test_2d], dim=1) + test_data_small_2d = torch.cat( + [signal_data_test_small_2d, aux_data_test_small_2d], dim=1) + + print(signal_data_test_2d.shape) + print(aux_data_test_2d.shape) + # ----------------------------------------------- + + # Normalize + train_data_2d = normalize_(train_data_2d) + test_data_2d = normalize_(test_data_2d) + + # Create dataloader objects with preprocessed dataset + dataloader = DataLoader( + train_data_2d, + batch_size=BATCH_SIZE, + shuffle=True, + ) + test_dataloader = DataLoader( + test_data_2d, + batch_size=BATCH_SIZE, + shuffle=False, + ) + + # Model to train + generator_2d = UNet( + input_channels=3, output_channels=1, norm=False).to(device) + init_weights(generator_2d, 'normal', scaling=.02) + + # loss function, learning rate, and optimizer + l2_loss = nn.MSELoss() # this is l2!!! + l1_loss = nn.L1Loss() # this is L1!!! + loss = l1_loss # LogCoshLoss() + G_optimizer = torch.optim.Adam(generator_2d.parameters(), lr=LR) + + # Training + loss_plot, val_loss_plot, acc_plot, val_acc_plot = train_decoder( + N_EPOCHS, generator_2d, loss, G_optimizer, dataloader, test_dataloader, + calculate_iou_2d, SAVE_CHECKPOINT + ) diff --git a/use-cases/virgo/trainer.py b/use-cases/virgo/trainer.py new file mode 100644 index 00000000..ea4dae28 --- /dev/null +++ b/use-cases/virgo/trainer.py @@ -0,0 +1,245 @@ +from typing import Literal, Optional +import os +import torch.nn as nn +import torch +import time +import numpy as np + +from itwinai.torch.trainer import TorchTrainer +from itwinai.torch.distributed import ( + DeepSpeedStrategy, +) +from itwinai.torch.config import TrainingConfiguration +from itwinai.loggers import Logger + +from src.model import Decoder, Decoder_2d_deep, UNet, GeneratorResNet +from src.utils import init_weights + + +from tqdm import tqdm + + +class NoiseGeneratorTrainer(TorchTrainer): + + def __init__( + self, + batch_size: int, + learning_rate: float = 1e-3, + num_epochs: int = 2, + generator: Literal["simple", "deep", "resnet", "unet"] = "unet", + loss: Literal["L1", "L2"] = "L1", + strategy: Literal["ddp", "deepspeed", "horovod"] = 'ddp', + checkpoint_path: str = "checkpoints/epoch_{}.pth", + save_best: bool = True, + logger: Optional[Logger] = None, + name: str | None = None + ) -> None: + super().__init__( + epochs=num_epochs, + config={}, + strategy=strategy, + logger=logger, + name=name + ) + self.save_parameters(**self.locals2params(locals())) + self.num_epochs = num_epochs + self.batch_size = batch_size + self.learning_rate = learning_rate + self._generator = generator + self._loss = loss + self.checkpoint_path = checkpoint_path + os.makedirs(os.path.dirname(checkpoint_path), exist_ok=True) + # Global configuration + _config = dict( + batch_size=batch_size, + save_best=save_best + ) + self.config = TrainingConfiguration(**_config) + + def create_model_loss_optimizer(self) -> None: + # Select generator + generator = self._generator.lower() + if generator == "simple": + self.model = Decoder(3, norm=False) + init_weights(self.model, 'normal', scaling=.02) + elif generator == "deep": + self.model = Decoder_2d_deep(3) + init_weights(self.model, 'normal', scaling=.02) + elif generator == "resnet": + self.model = GeneratorResNet(3, 12, 1) + init_weights(self.model, 'normal', scaling=.01) + elif generator == "unet": + self.model = UNet( + input_channels=3, output_channels=1, norm=False) + init_weights(self.model, 'normal', scaling=.02) + else: + raise ValueError("Unrecognized generator type! Got", generator) + + # Select loss + loss = self._loss.upper() + if loss == "L1": + self.loss = nn.L1Loss() + elif loss == "L2": + self.loss = nn.MSELoss() + else: + raise ValueError("Unrecognized loss type! Got", loss) + + # Optimizer + self.optimizer = torch.optim.Adam( + self.model.parameters(), lr=self.learning_rate) + + # IMPORTANT: model, optimizer, and scheduler need to be distributed + + # First, define strategy-wise optional configurations + if isinstance(self.strategy, DeepSpeedStrategy): + # Batch size definition is not optional for DeepSpeedStrategy! + distribute_kwargs = dict( + config_params=dict( + train_micro_batch_size_per_gpu=self.config.batch_size + ) + ) + else: + distribute_kwargs = {} + + # Distributed model, optimizer, and scheduler + self.model, self.optimizer, _ = self.strategy.distributed( + self.model, self.optimizer, **distribute_kwargs + ) + + def train(self): + # uncomment all lines relative to accuracy if you want to measure + # IOU between generated and real spectrograms. + # Note that it significantly slows down the whole process + # it also might not work as the function has not been fully + # implemented yet + + loss_plot = [] + val_loss_plot = [] + acc_plot = [] + val_acc_plot = [] + best_val_loss = float('inf') + for epoch in tqdm(range(1, self.num_epochs+1)): + st = time.time() + epoch_loss = [] + epoch_acc = [] + for i, batch in enumerate(self.train_dataloader): + # batch= transform(batch) + target = batch[:, 0].unsqueeze(1).to(self.device) + # print(f'TARGET ON DEVICE: {target.get_device()}') + target = target.float() + input = batch[:, 1:].to(self.device) + # print(f'INPUT ON DEVICE: {input.get_device()}') + + self.optimizer.zero_grad() + generated = self.model(input.float()) + # generated=normalize_(generated,1) + loss = self.loss(generated, target) + loss.backward() + self.optimizer.step() + epoch_loss.append(loss.detach().cpu().numpy()) + self.log(loss.detach().cpu().numpy(), + 'epoch_loss_batch', + kind='metric', + step=epoch*len(self.train_dataloader) + i, + batch_idx=i) + # acc=accuracy(generated.detach().cpu().numpy(),target.detach().cpu().numpy(),20) + # epoch_acc.append(acc) + val_loss = [] + val_acc = [] + for batch in (self.validation_dataloader): + # batch= transform(batch) + target = batch[:, 0].unsqueeze(1).to(self.device) + target = target.float() + input = batch[:, 1:].to(self.device) + with torch.no_grad(): + generated = self.model(input.float()) + # generated=normalize_(generated,1) + loss = self.loss(generated, target) + val_loss.append(loss.detach().cpu().numpy()) + self.log(loss.detach().cpu().numpy(), + 'val_loss_batch', + kind='metric', + step=epoch*len(self.validation_dataloader) + i, + batch_idx=i) + # acc=accuracy(generated.detach().cpu().numpy(),target.detach().cpu().numpy(),20) + # val_acc.append(acc) + loss_plot.append(np.mean(epoch_loss)) + val_loss_plot.append(np.mean(val_loss)) + acc_plot.append(np.mean(epoch_acc)) + val_acc_plot.append(np.mean(val_acc)) + + # Log metrics/losses + self.log(np.mean(epoch_loss), 'epoch_loss', + kind='metric', step=epoch) + self.log(np.mean(val_loss), 'val_loss', + kind='metric', step=epoch) + # self.log(np.mean(epoch_acc), 'epoch_acc', + # kind='metric', step=epoch) + # self.log(np.mean(val_acc), 'val_acc', + # kind='metric', step=epoch) + + # print('epoch: {} loss: {} val loss: {} accuracy: {} val + # accuracy: {}'.format(epoch,loss_plot[-1],val_loss_plot[-1], + # acc_plot[-1],val_acc_plot[-1])) + et = time.time() + if self.strategy.is_main_worker: + print('epoch: {} loss: {} val loss: {} time:{}s'.format( + epoch, loss_plot[-1], val_loss_plot[-1], et-st)) + + # Save checkpoint every 100 epochs + if (epoch+1) % 1 == 0: + # uncomment the following if you want to save checkpoint every + # 100 epochs regardless of the performance of the model + # checkpoint = { + # 'epoch': epoch, + # 'model_state_dict': generator.state_dict(), + # 'optim_state_dict': optimizer.state_dict(), + # 'loss': loss_plot[-1], + # 'val_loss': val_loss_plot[-1], + # } + # if self.strategy.is_main_worker: + # # Save only in the main worker + # checkpoint_filename = checkpoint_path.format(epoch) + # torch.save(checkpoint, checkpoint_filename) + + # Average loss among all workers + worker_val_losses = self.strategy.gather_obj(val_loss_plot[-1]) + if self.strategy.is_main_worker: + # Save only in the main worker + + # avg_loss has a meaning only in the main worker + avg_loss = np.mean(worker_val_losses) + + # instead of val_loss and best_val loss we should + # use accuracy!!! + if self.config.save_best and avg_loss < best_val_loss: + # create checkpoint + checkpoint = { + 'epoch': epoch, + 'model_state_dict': self.model.state_dict(), + 'optim_state_dict': self.optimizer.state_dict(), + 'loss': loss_plot[-1], + 'val_loss': val_loss_plot[-1], + } + + # save checkpoint only if it is better than + # the previous ones + checkpoint_filename = self.checkpoint_path.format( + epoch) + torch.save(checkpoint, checkpoint_filename) + self.log(checkpoint_filename, + os.path.basename(checkpoint_filename), + kind='artifact') + + # update best model + best_val_loss = val_loss_plot[-1] + best_checkpoint_filename = ( + self.checkpoint_path.format('best') + ) + torch.save(checkpoint, best_checkpoint_filename) + self.log(best_checkpoint_filename, + os.path.basename(best_checkpoint_filename), + kind='artifact') + # return (loss_plot, val_loss_plot, + # acc_plot, val_acc_plot ,acc_plot, val_acc_plot) + return loss_plot, val_loss_plot, acc_plot, val_acc_plot