From 5f750f01d535a0ad83d8e1c3c1b7712322a97681 Mon Sep 17 00:00:00 2001 From: Matthew Silverman Date: Tue, 13 Feb 2024 16:19:17 -0500 Subject: [PATCH] maintain stable versions of dependencies (#5158) **Context:** There are many difficulties with dependency management, and this PR tries to address two of them: - reproducible versions of PL+deps from a past version - ability to view which deps have been auto-updated (eg. a new version of an unbound dep is released) and when **Description of the Change:** - Factor out the python environment setup to its own file. All default package versions are here now - Update `interface-unit-tests` to trigger the upload of `pip freeze` results as artifacts for later use. happens in the `core`, `jax`, `tf`, `torch`, `all-interfaces` and `external` test suites today (ensured that it only occurs on one worker using `strategy.job-index`) - Add the `upload-stable-deps` workflow to run after tests complete. The action only runs on merge commits to master, so users shouldn't see it. This is the real new feature, and here's how it works: 1. create the branch `bot/stable-deps-update` (or rebases it on master if it already existed) 2. delete the existing requirement files in `.github/stable/` to avoid merge conflicts 3. download the previously-uploaded requirement files generated in this flow to that same folder and check if there's a diff 4. if no diff, do nothing! 5. if there's a diff and no PR exists, it opens one and tags me (and hopefully the team because of CODEOWNERS) 6. if there's a diff and a PR already exists, it just pushes to the existing PR branch (confirmed this is the case despite the open-pr step running - it just does nothing in that case) **Benefits:** Helps with both difficulties mentioned in the context section above **Possible Drawbacks:** - the team will have to review this PR (if it becomes too noisy, we can turn this into a cronjob) - commits made by bots (eg. actions that use `${{ secrets.GITHUB_TOKEN }}`) will not trigger CI, so the automated PRs need to have it triggered manually. A hack to fix this is proposed [here](https://github.com/peter-evans/create-pull-request/issues/48#issuecomment-536204092) if we want to consider that, but this is a feature of GitHub (not a bug), intended to avoid infinite CI loops I will do something similar for the docs builds after this PR is merged, it's complex enough for now. [sc-56376] --- .github/CODEOWNERS | 1 + .github/workflows/install_deps/action.yml | 113 +++++++++++++++++++++ .github/workflows/interface-unit-tests.yml | 40 ++------ .github/workflows/tests.yml | 67 ++++++++++++ .github/workflows/unit-test.yml | 59 +++-------- 5 files changed, 202 insertions(+), 78 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/workflows/install_deps/action.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..d8f4ea6dee1 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +/.github/stable/ @PennyLaneAI/Core diff --git a/.github/workflows/install_deps/action.yml b/.github/workflows/install_deps/action.yml new file mode 100644 index 00000000000..77ceb1389d3 --- /dev/null +++ b/.github/workflows/install_deps/action.yml @@ -0,0 +1,113 @@ +name: Install Dependencies +description: | + This workflow installs Python, PennyLane and all its dependencies. If a + requirements file is provided, it will upload the results of pip freeze + as well. +inputs: + python_version: + description: The version of Python to use in order to run unit tests + required: false + default: '3.9' + install_jax: + description: Indicate if JAX should be installed or not + required: false + default: 'true' + jax_version: + description: The version of JAX to install for any job that requires JAX + required: false + default: 0.4.23 + install_tensorflow: + description: Indicate if TensorFlow should be installed or not + required: false + default: 'true' + tensorflow_version: + description: The version of TensorFlow to install for any job that requires TensorFlow + required: false + default: 2.15.0 + install_pytorch: + description: Indicate if PyTorch should be installed or not + required: false + default: 'true' + pytorch_version: + description: The version of PyTorch to install for any job that requires PyTorch + required: false + default: 2.2.0 + install_pennylane_lightning_master: + description: Indicate if PennyLane-Lightning should be installed from the master branch + required: false + default: 'true' + additional_pip_packages: + description: Additional packages to install. Values will be passed to pip install {value} + required: false + default: '' + requirements_file: + description: File name to store stable version of requirements for a test group + required: false + default: '' + +runs: + using: composite + steps: + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '${{ inputs.python_version }}' + + - name: Upgrade PIP + shell: bash + run: pip install --upgrade pip && pip install wheel --upgrade + + - name: Install PennyLane dependencies + shell: bash + run: | + pip install -r requirements-ci.txt --upgrade + pip install -r requirements-dev.txt --upgrade + + - name: Install PyTorch + shell: bash + if: inputs.install_pytorch == 'true' + env: + TORCH_VERSION: ${{ inputs.pytorch_version != '' && format('=={0}', inputs.pytorch_version) || '' }} + run: pip install "torch${{ env.TORCH_VERSION }}" -f https://download.pytorch.org/whl/torch_stable.html + + - name: Install TensorFlow + shell: bash + if: inputs.install_tensorflow == 'true' + env: + TF_VERSION: ${{ inputs.tensorflow_version != '' && format('~={0}', inputs.tensorflow_version) || '' }} + run: pip install "tensorflow${{ env.TF_VERSION }}" "keras${{ env.TF_VERSION }}" + + - name: Install JAX + shell: bash + if: inputs.install_jax == 'true' + env: + JAX_VERSION: ${{ inputs.jax_version != '' && format('=={0}', inputs.jax_version) || '' }} + run: pip install "jax${{ env.JAX_VERSION}}" "jaxlib${{ env.JAX_VERSION }}" + + - name: Install additional PIP packages + shell: bash + if: inputs.additional_pip_packages != '' + run: pip install ${{ inputs.additional_pip_packages }} + + - name: Install PennyLane + shell: bash + run: | + python setup.py bdist_wheel + pip install dist/PennyLane*.whl + + - name: Install PennyLane-Lightning master + shell: bash + if: inputs.install_pennylane_lightning_master == 'true' + run: pip install -i https://test.pypi.org/simple/ PennyLane-Lightning --pre --upgrade + + - name: Freeze dependencies + shell: bash + if: inputs.requirements_file != '' + run: pip freeze | grep -v "file:///" > ${{ inputs.requirements_file }} + + - name: Upload frozen requirements + if: inputs.requirements_file != '' + uses: actions/upload-artifact@v4 + with: + name: frozen-${{ inputs.requirements_file }} + path: ${{ inputs.requirements_file }} diff --git a/.github/workflows/interface-unit-tests.yml b/.github/workflows/interface-unit-tests.yml index f94686642b7..2dfee7df77b 100644 --- a/.github/workflows/interface-unit-tests.yml +++ b/.github/workflows/interface-unit-tests.yml @@ -11,21 +11,6 @@ on: required: false type: string default: 'unit-tests' - jax_version: - description: The version of JAX to install for any job that requires JAX - required: false - type: string - default: 0.4.23 - tensorflow_version: - description: The version of TensorFlow to install for any job that requires TensorFlow - required: false - type: string - default: 2.15.0 - pytorch_version: - description: The version of PyTorch to install for any job that requires PyTorch - required: false - type: string - default: 2.2.0 pytest_coverage_flags: description: PyTest Coverage flags to pass to all jobs required: false @@ -162,10 +147,10 @@ jobs: install_jax: false install_tensorflow: false install_pytorch: true - pytorch_version: ${{ inputs.pytorch_version }} install_pennylane_lightning_master: true pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: torch and not qcut and not finite-diff and not param-shift + requirements_file: ${{ strategy.job-index == 0 && 'torch.txt' || '' }} autograd-tests: @@ -225,13 +210,13 @@ jobs: pipeline_mode: ${{ inputs.pipeline_mode }} install_jax: false install_tensorflow: true - tensorflow_version: ${{ inputs.tensorflow_version }} install_pytorch: false install_pennylane_lightning_master: true pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: tf and not qcut and not finite-diff and not param-shift pytest_additional_args: --splits 3 --group ${{ matrix.group }} --durations-path='.github/workflows/tf_tests_durations.json' additional_pip_packages: pytest-split + requirements_file: ${{ strategy.job-index == 0 && 'tf.txt' || '' }} jax-tests: @@ -259,7 +244,6 @@ jobs: python_version: ${{ matrix.python-version }} pipeline_mode: ${{ inputs.pipeline_mode }} install_jax: true - jax_version: ${{ inputs.jax_version }} install_tensorflow: false install_pytorch: false install_pennylane_lightning_master: true @@ -267,6 +251,7 @@ jobs: pytest_markers: jax and not qcut and not finite-diff and not param-shift pytest_additional_args: --splits 5 --group ${{ matrix.group }} --durations-path='.github/workflows/jax_tests_durations.json' additional_pip_packages: pytest-split + requirements_file: ${{ strategy.job-index == 0 && 'jax.txt' || '' }} core-tests: @@ -303,6 +288,7 @@ jobs: pytest_markers: core and not qcut and not finite-diff and not param-shift pytest_additional_args: --splits 2 --group ${{ matrix.group }} additional_pip_packages: pytest-split + requirements_file: ${{ strategy.job-index == 0 && 'core.txt' || '' }} all-interfaces-tests: @@ -329,14 +315,12 @@ jobs: python_version: ${{ matrix.python-version }} pipeline_mode: ${{ inputs.pipeline_mode }} install_jax: true - jax_version: ${{ inputs.jax_version }} install_tensorflow: true - tensorflow_version: ${{ inputs.tensorflow_version }} install_pytorch: true - pytorch_version: ${{ inputs.pytorch_version }} install_pennylane_lightning_master: false pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: all_interfaces + requirements_file: ${{ strategy.job-index == 0 && 'all_interfaces.txt' || '' }} external-libraries-tests: @@ -363,14 +347,13 @@ jobs: python_version: ${{ matrix.python-version }} pipeline_mode: ${{ inputs.pipeline_mode }} install_jax: true - jax_version: ${{ inputs.jax_version }} install_tensorflow: true - tensorflow_version: ${{ inputs.tensorflow_version }} install_pytorch: false install_pennylane_lightning_master: false pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: external - additional_pip_packages: git+https://github.com/Quantomatic/pyzx.git@master pennylane-catalyst matplotlib stim + additional_pip_packages: pyzx pennylane-catalyst matplotlib stim + requirements_file: ${{ strategy.job-index == 0 && 'external.txt' || '' }} qcut-tests: @@ -397,11 +380,8 @@ jobs: python_version: ${{ matrix.python-version }} pipeline_mode: ${{ inputs.pipeline_mode }} install_jax: true - jax_version: ${{ inputs.jax_version }} install_tensorflow: true - tensorflow_version: ${{ inputs.tensorflow_version }} install_pytorch: true - pytorch_version: ${{ inputs.pytorch_version }} install_pennylane_lightning_master: false pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: qcut @@ -466,11 +446,8 @@ jobs: python_version: ${{ matrix.python-version }} pipeline_mode: ${{ inputs.pipeline_mode }} install_jax: true - jax_version: ${{ inputs.jax_version }} install_tensorflow: true - tensorflow_version: ${{ inputs.tensorflow_version }} install_pytorch: true - pytorch_version: ${{ inputs.pytorch_version }} install_pennylane_lightning_master: false pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: ${{ matrix.config.suite }} @@ -543,11 +520,8 @@ jobs: python_version: ${{ matrix.python-version }} pipeline_mode: ${{ inputs.pipeline_mode }} install_jax: ${{ contains(matrix.config.device, 'jax') }} - jax_version: ${{ inputs.jax_version }} install_tensorflow: ${{ contains(matrix.config.device, 'tf') }} - tensorflow_version: ${{ inputs.tensorflow_version }} install_pytorch: ${{ contains(matrix.config.device, 'torch') }} - pytorch_version: ${{ inputs.pytorch_version }} install_pennylane_lightning_master: false pytest_test_directory: pennylane/devices/tests pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1108fe67a9c..9741f7224ee 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,6 +9,9 @@ concurrency: group: unit-tests-${{ github.ref }} cancel-in-progress: true +env: + DEPS_BRANCH: bot/stable-deps-update + jobs: tests: uses: ./.github/workflows/interface-unit-tests.yml @@ -24,3 +27,67 @@ jobs: && !contains(github.event.pull_request.labels.*.name, 'ci:run-full-test-suite') || false }} + + upload-stable-deps: + needs: tests + runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' }} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-tags: true + sparse-checkout: .github + + - name: Prepare local repo + run: | + git fetch + git config user.name "GitHub Actions Bot" + git config user.email "<>" + if git ls-remote --exit-code origin "refs/heads/${{ env.DEPS_BRANCH }}"; then + git checkout "${{ env.DEPS_BRANCH }}" + git rebase origin/master + else + git checkout master + git checkout -b "${{ env.DEPS_BRANCH }}" + fi + rm -f .github/stable/*.txt + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + pattern: frozen-* + path: .github/stable/ + merge-multiple: true + + - name: Determine if changes have been made + id: changed + run: | + echo "has_changes=$(git status --porcelain | wc -l | awk '{print $1}')" >> $GITHUB_OUTPUT + + - name: Stage changes + if: steps.changed.outputs.has_changes != '0' + run: | + git add .github/stable/ + git commit -m "Update changed dependencies" + git push -f --set-upstream origin "${{ env.DEPS_BRANCH }}" + + # Create PR to master + - name: Create pull request + if: steps.changed.outputs.has_changes != '0' + uses: repo-sync/pull-request@v2 + with: + source_branch: "${{ env.DEPS_BRANCH }}" + destination_branch: "master" + github_token: "${{ secrets.GITHUB_TOKEN }}" + pr_title: "Update stable dependency files" + pr_body: | + Automatic update of stable requirement files to snapshot valid python environments. + Because bots are not able to trigger CI on their own, please do so by pushing an empty commit to this branch using the following command: + + ``` + git commit --allow-empty -m 'trigger ci' + ``` + pr_allow_empty: false + pr_draft: false + pr_reviewer: "timmysilv" diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 3e6751f7d7f..c4d7d792764 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -89,6 +89,11 @@ on: required: false type: string default: '' + requirements_file: + description: File name to store stable version of requirements for a test group + required: false + type: string + default: '' jobs: test: @@ -128,53 +133,17 @@ jobs: || (inputs.pipeline_mode == 'reference-benchmarks' && steps.benchmark-cache.outputs.cache-hit != 'true' )}}" >> $GITHUB_OUTPUT - - name: Setup Python + - name: Install PennyLane and dependencies if: steps.continue.outputs.confirm == 'true' - uses: actions/setup-python@v4 + uses: ./.github/workflows/install_deps with: - python-version: '${{ inputs.python_version }}' - - - name: Upgrade PIP - if: steps.continue.outputs.confirm == 'true' - run: pip install --upgrade pip && pip install wheel --upgrade - - - name: Install PennyLane dependencies - if: steps.continue.outputs.confirm == 'true' - run: | - pip install -r requirements-ci.txt --upgrade - pip install -r requirements-dev.txt --upgrade - - - name: Install PyTorch - if: inputs.install_pytorch == true && steps.continue.outputs.confirm == 'true' - env: - TORCH_VERSION: ${{ inputs.pytorch_version != '' && format('=={0}', inputs.pytorch_version) || '' }} - run: pip install "torch${{ env.TORCH_VERSION }}" -f https://download.pytorch.org/whl/torch_stable.html - - - name: Install TensorFlow - if: inputs.install_tensorflow == true && steps.continue.outputs.confirm == 'true' - env: - TF_VERSION: ${{ inputs.tensorflow_version != '' && format('~={0}', inputs.tensorflow_version) || '' }} - run: pip install "tensorflow${{ env.TF_VERSION }}" "keras${{ env.TF_VERSION }}" - - - name: Install JAX - if: inputs.install_jax == true && steps.continue.outputs.confirm == 'true' - env: - JAX_VERSION: ${{ inputs.jax_version != '' && format('=={0}', inputs.jax_version) || '' }} - run: pip install "jax${{ env.JAX_VERSION}}" "jaxlib${{ env.JAX_VERSION }}" - - - name: Install additional PIP packages - if: inputs.additional_pip_packages != '' && steps.continue.outputs.confirm == 'true' - run: pip install ${{ inputs.additional_pip_packages }} - - - name: Install PennyLane - if: steps.continue.outputs.confirm == 'true' - run: | - python setup.py bdist_wheel - pip install dist/PennyLane*.whl - - - name: Install PennyLane-Lightning master - if: inputs.install_pennylane_lightning_master == true && steps.continue.outputs.confirm == 'true' - run: pip install -i https://test.pypi.org/simple/ PennyLane-Lightning --pre --upgrade + python_version: ${{ inputs.python_version }} + install_pytorch: ${{ inputs.install_pytorch }} + install_tensorflow: ${{ inputs.install_tensorflow }} + install_jax: ${{ inputs.install_jax }} + additional_pip_packages: ${{ inputs.additional_pip_packages }} + install_pennylane_lightning_master: ${{ inputs.install_pennylane_lightning_master }} + requirements_file: ${{ inputs.requirements_file }} - name: Set PyTest Args if: steps.continue.outputs.confirm == 'true'