From a6e3b27cc13667f155db62c912f870e217e9276e Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Thu, 17 Jul 2025 16:55:22 -0500 Subject: [PATCH 1/9] Add workflow that downloads a .gguf, signs it, and uploads the signature Signed-off-by: Stefan Berger --- .../reusable-sign-model-in-hf-repo.yml | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 .github/workflows/reusable-sign-model-in-hf-repo.yml diff --git a/.github/workflows/reusable-sign-model-in-hf-repo.yml b/.github/workflows/reusable-sign-model-in-hf-repo.yml new file mode 100644 index 00000000..f2d8228d --- /dev/null +++ b/.github/workflows/reusable-sign-model-in-hf-repo.yml @@ -0,0 +1,210 @@ +name: sign-model-in-hf-repo-reusable + +on: + workflow_call: + secrets: + hf_token: + required: true + sigstore_sts_client_secret: + required: true + inputs: + repo_id: + type: string + required: true + quantization: + type: string + required: true + target_repo_owner: + type: string + required: true + target_repo_name_ext: + type: string + required: true + do_token_exchange: + type: boolean + required: false + default: false + debug: + type: boolean + required: false + default: false + +env: + EXT_GGUF: .gguf + MODEL_DOWNLOAD_DIR: models + +jobs: + sign-model-in-hf-repo: + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v4 + with: + sparse-checkout: | + scripts/hf_model_upload.py + scripts/hf_model_file_exists.py + requirements.txt + + - name: Dump GitHub inputs + env: + GITHUB_INPUTS: ${{ toJson(inputs) }} + if: ${{ github.event.inputs.debug }} + run: echo "$GITHUB_INPUTS" + + - name: List all environment variables + if: ${{ github.event.inputs.debug }} + run: env | sort + + # Note: at the current time, we cannot use Python versions > 3.11 due to HF and langchain deps. + # Note: you can verify in a step using: run: python -c "import sys; print(sys.version)" + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + # primarily huggingface_hub + - name: install-dependencies + run: | + python -m pip install -r ./requirements.txt + pip list + + # Use this step to set values to the github context (shared across jobs/steps) + # Note: using $GITHUB_OUTPUT sets values under the current step's namespace + # whereas using $GITHUB_ENV sets values in the job's underlying environment. + # Note: for each 'repo_id' we parse out e.g., REPO_ORG=ibm-granite REPO_NAME=granite-3.0-2b-instruct + - name: set-github-env + id: set_github_env + run: | + echo "REPO_ORG=$(dirname '${{ inputs.repo_id }}')" >> $GITHUB_ENV + echo "REPO_NAME=$(basename '${{ inputs.repo_id }}')" >> $GITHUB_ENV + + - name: set-derivative-env-vars-1 + run: | + echo "TARGET_REPO_ID=${{inputs.target_repo_owner}}/${{env.REPO_NAME}}${{inputs.target_repo_name_ext}}" >> $GITHUB_ENV + quantization=${{inputs.quantization}} + if [ "${quantization:0:9}" = "fullname:" ]; then + echo "BASE_FNAME_QUANTIZED_GGUF=${quantization:9}" >> $GITHUB_ENV + else + echo "BASE_FNAME_QUANTIZED_GGUF=${{ env.REPO_NAME }}-${{inputs.quantization}}${{env.EXT_GGUF}}" >> $GITHUB_ENV + fi + + - name: set-derivative-env-vars-2 + run: | + echo "LOCAL_MODEL_PATH=${{env.MODEL_DOWNLOAD_DIR}}/${{ env.TARGET_REPO_ID }}" >> $GITHUB_ENV + + - name: set-derivative-env-vars-3 + run: | + echo "LOCAL_FNAME_QUANTIZED_GGUF=${{env.LOCAL_MODEL_PATH}}/${{env.BASE_FNAME_QUANTIZED_GGUF}}" >> $GITHUB_ENV + + - name: verify-github-env + run: | + echo "================== Derivative Environment Variables 1 ==================" + echo "TARGET_REPO_ID='$TARGET_REPO_ID' (${{ env.TARGET_REPO_ID }})" + echo "BASE_FNAME_QUANTIZED_GGUF='$BASE_FNAME_QUANTIZED_GGUF' (${{ env.BASE_FNAME_QUANTIZED_GGUF }})" + echo "================== Derivative Environment Variables 2 ==================" + echo "LOCAL_MODEL_PATH='$LOCAL_MODEL_PATH' (${{ env.LOCAL_MODEL_PATH }})" + echo "================== Derivative Environment Variables 3 ==================" + echo "LOCAL_FNAME_QUANTIZED_GGUF='$LOCAL_FNAME_QUANTIZED_GGUF' (${{ env.LOCAL_FNAME_QUANTIZED_GGUF }})" + + - name: test-quantized-model-exists + run: | + exists=$(python ./scripts/hf_model_file_exists.py ${{ env.TARGET_REPO_ID }} ${{ env.BASE_FNAME_QUANTIZED_GGUF }} ${{secrets.hf_token}}) + echo "exists: '$exists'" + if [[ "$exists" == "False" ]]; then + echo "FAILURE: model file: '${{env.TARGET_REPO_ID}}/${{env.BASE_FNAME_QUANTIZED_GGUF}}' does not exist." + exit 2 + else + echo "SUCCESS: model file: '${{env.TARGET_REPO_ID}}/${{env.BASE_FNAME_QUANTIZED_GGUF}}' exists." + echo setting environment variable: QUANTIZED_MODEL_EXISTS='true'... + echo "QUANTIZED_MODEL_EXISTS=true" >> $GITHUB_ENV + fi + + # Most models signature is being removed before they are uploaded. + # Some models are only generated once (*-f16.gguf, mmproj-model-f16.gguf) + # and need not be re-signed. + - name: test-signature-does-not-exist + run: | + signature=${{ env.BASE_FNAME_QUANTIZED_GGUF }}.sig + exists=$(python ./scripts/hf_model_file_exists.py ${{ env.TARGET_REPO_ID }} ${signature} ${{secrets.hf_token}}) + echo "exists: '$exists'" + if [[ "$exists" == "False" ]]; then + echo "FAILURE: signature for model file: '${{env.TARGET_REPO_ID}}/${{env.BASE_FNAME_QUANTIZED_GGUF}}' does not exist." + echo setting environment variable: QUANTIZED_MODEL_SIGNATURE_EXISTS='false'... + echo "QUANTIZED_MODEL_SIGNATURE_EXISTS=false" >> $GITHUB_ENV + else + echo "SUCCESS: signature for model file: '${{env.TARGET_REPO_ID}}/${{env.BASE_FNAME_QUANTIZED_GGUF}}' exists." + echo setting environment variable: QUANTIZED_MODEL_SIGNATURE_EXISTS='true'... + echo "QUANTIZED_MODEL_SIGNATURE_EXISTS=true" >> $GITHUB_ENV + fi + + - name: download-quantized-gguf-hf-hub-download + if: env.QUANTIZED_MODEL_EXISTS == 'true' && env.QUANTIZED_MODEL_SIGNATURE_EXISTS == 'false' + run: | + echo "Downloading model to: ${{env.LOCAL_FNAME_QUANTIZED_GGUF}}..." + echo "--------------------" + python ./scripts/hf_file_download.py ${{ env.MODEL_DOWNLOAD_DIR}} ${{ env.TARGET_REPO_ID }} ${{ env.BASE_FNAME_QUANTIZED_GGUF }} ${{secrets.hf_token}} + ls -al ${{env.MODEL_DOWNLOAD_DIR}}/${{ env.TARGET_REPO_ID }}/*.gguf + + - name: Install model signing tool and dependencies + if: env.QUANTIZED_MODEL_EXISTS == 'true' && env.QUANTIZED_MODEL_SIGNATURE_EXISTS == 'false' + run: | + pip install model_signing + + - name: Get github OIDC token and maybe run token exchange + id: get-oidc-token + if: env.QUANTIZED_MODEL_EXISTS == 'true' && env.QUANTIZED_MODEL_SIGNATURE_EXISTS == 'false' + run: | + identity_token=$( + curl -H \ + "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ + "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sigstore" \ + | jq -r .value + ) + #echo "Identity token from github" + #echo "${identity_token}" | base64 + if [ ${{ inputs.do_token_exchange }} = true ]; then + if ! resjson=$( + curl 'https://sigstore.verify.ibm.com/oauth2/token' \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --data-urlencode 'client_id=sigstore' \ + --data-urlencode "client_secret=${{ secrets.sigstore_sts_client_secret }}" \ + --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \ + --data-urlencode "subject_token=${identity_token}" \ + --data-urlencode 'subject_token_type=GitHubIdentityToken' -s \ + ); then + echo "Error: ${resjson}" + exit 1 + else + identity_token=$(echo ${resjson} | jq -r .access_token) + if [ "${identity_token}" = "null" ]; then + echo "Error: ${resjson}" + exit 1 + fi + fi + #echo "Identity token from sigstore.verify.ibm.com" + #echo "${identity_token}" | base64 + fi + echo "identity-token=$identity_token" >> $GITHUB_OUTPUT + + - name: Sign the model + if: env.QUANTIZED_MODEL_EXISTS == 'true' && env.QUANTIZED_MODEL_SIGNATURE_EXISTS == 'false' + run: | + full_model_file=${{env.LOCAL_FNAME_QUANTIZED_GGUF}} + gguffile=$(basename "${full_model_file}") + signature=${gguffile}.sig + + # Move to-be-signed file into a directory all for itself + work_dir=signingdir + mkdir "${work_dir}" + mv "${full_model_file}" "${work_dir}" + + python -m model_signing sign sigstore \ + --identity_token "${{ steps.get-oidc-token.outputs.identity-token }}" \ + --signature "${signature}" \ + "${work_dir}" + + echo "SIGNATURE=${signature}" >> $GITHUB_ENV + + - name: Upload new model signature + if: env.QUANTIZED_MODEL_EXISTS == 'true' && env.QUANTIZED_MODEL_SIGNATURE_EXISTS == 'false' + run: | + python ./scripts/hf_model_upload.py ${{ env.TARGET_REPO_ID }} ${{env.SIGNATURE}} ${{secrets.hf_token}} ${{github.workflow_ref}} ${{github.run_id}} From 9d760bac0476964c4efc6a719bb621cd1c23a273 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 21 Jul 2025 07:37:45 -0500 Subject: [PATCH 2/9] Delete existing signature before upload of quanitized model Instrument the quantization code for all models to delete any existing signature before uploading the new quanitized model. Signed-off-by: Stefan Berger --- .../workflows/reusable-quantize-llava-upload-gguf.yml | 9 +++++++++ .github/workflows/reusable-quantize-upload-gguf.yml | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/.github/workflows/reusable-quantize-llava-upload-gguf.yml b/.github/workflows/reusable-quantize-llava-upload-gguf.yml index 3b8cf5df..9a5a7b17 100644 --- a/.github/workflows/reusable-quantize-llava-upload-gguf.yml +++ b/.github/workflows/reusable-quantize-llava-upload-gguf.yml @@ -228,6 +228,15 @@ jobs: ls -al . echo "--------------------" + - name: delete-previous-signature + run: | + signature=${{ env.LOCAL_FNAME_QUANTIZED_GGUF }}.sig + exists=$(python ./scripts/hf_model_file_exists.py ${{ env.TARGET_REPO_ID }} ${signature} ${{secrets.hf_token}}) + echo "exists: '$exists'" + if [[ "$exists" == "True" ]]; then + python ./scripts/hf_file_delete.py ${{ env.TARGET_REPO_ID }} ${signature} ${{secrets.hf_token}} + fi + - name: upload-quantized-gguf-model run: | echo "TARGET_REPO_ID=${{env.TARGET_REPO_ID}}" diff --git a/.github/workflows/reusable-quantize-upload-gguf.yml b/.github/workflows/reusable-quantize-upload-gguf.yml index f07a97ce..471fbaee 100644 --- a/.github/workflows/reusable-quantize-upload-gguf.yml +++ b/.github/workflows/reusable-quantize-upload-gguf.yml @@ -291,6 +291,16 @@ jobs: ls -al . echo "--------------------" + - name: delete-previous-signature + if: env.F16_OPT_ENABLED == 'true' + run: | + signature=${{ env.LOCAL_FNAME_QUANTIZED_GGUF }}.sig + exists=$(python ./scripts/hf_model_file_exists.py ${{ env.TARGET_REPO_ID }} ${signature} ${{secrets.hf_token}}) + echo "exists: '$exists'" + if [[ "$exists" == "True" ]]; then + python ./scripts/hf_file_delete.py ${{ env.TARGET_REPO_ID }} ${signature} ${{secrets.hf_token}} + fi + - name: delete-previous-upload if: env.F16_OPT_ENABLED == 'true' run: | From 1673c6b6af918dc443fe3c4869de6b860a31365a Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 21 Jul 2025 14:34:40 -0500 Subject: [PATCH 3/9] Introduce switch to signing of models Introduce TARGET_HF_REPO_SIGN_MODELS to enable or disable the signing of models. Set it to true by default. Signed-off-by: Stefan Berger --- .github/workflows/granite-3.2-release-test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/granite-3.2-release-test.yml b/.github/workflows/granite-3.2-release-test.yml index ac3957c0..d91b8731 100644 --- a/.github/workflows/granite-3.2-release-test.yml +++ b/.github/workflows/granite-3.2-release-test.yml @@ -83,6 +83,7 @@ env: TARGET_HF_REPO_NAME_EXT: -GGUF TARGET_HF_REPO_OWNER: mrutkows TARGET_HF_REPO_PRIVATE: false + TARGET_HF_REPO_SIGN_MODELS: true COLLECTION_CONFIG: "resources/json/latest/hf_collection_mapping_gguf.json" jobs: @@ -104,6 +105,7 @@ jobs: target_repo_private: ${{ steps.set-vars.outputs.target_repo_private }} target_repo_owner: ${{ steps.set-vars.outputs.target_repo_owner }} target_repo_name_ext: ${{ steps.set-vars.outputs.target_repo_name_ext }} + target_repo_sign_models: ${{ steps.set-vars.outputs.target_repo_sign_models }} source_language_repos: "${{ steps.set-vars.outputs.source_language_repos }}" target_language_quantizations: "${{ steps.set-vars.outputs.target_language_quantizations }}" source_vision_repos: "${{ steps.set-vars.outputs.source_vision_repos }}" @@ -130,6 +132,7 @@ jobs: echo "hf_collection_config=$COLLECTION_CONFIG" >> "$GITHUB_OUTPUT" echo "target_repo_owner=$TARGET_HF_REPO_OWNER" >> "$GITHUB_OUTPUT" echo "target_repo_name_ext=$TARGET_HF_REPO_NAME_EXT" >> "$GITHUB_OUTPUT" + echo "target_repo_sign_models=$TARGET_HF_REPO_SIGN_MODELS" >> "$GITHUB_OUTPUT" echo "source_language_repos=$SOURCE_LANGUAGE_REPOS" >> "$GITHUB_OUTPUT" echo "target_language_quantizations=$TARGET_LANGUAGE_QUANTIZATIONS" >> "$GITHUB_OUTPUT" echo "target_repo_private=$TARGET_HF_REPO_PRIVATE" >> "$GITHUB_OUTPUT" @@ -151,6 +154,7 @@ jobs: echo "hf_collection_config: '${{ steps.set-vars.outputs.hf_collection_config }}'" echo "target_repo_owner: '${{ steps.set-vars.outputs.target_repo_owner }}'" echo "target_repo_name_ext: '${{ steps.set-vars.outputs.target_repo_name_ext }}'" + echo "target_repo_sign_models: '${{ steps.set-vars.outputs.target_repo_sign_models }}'" echo "target_repo_private: '${{ steps.set-vars.outputs.target_repo_private }}'" echo "source_language_repos: '${{ steps.set-vars.outputs.source_language_repos }}'" echo "target_language_quantizations: '${{ steps.set-vars.outputs.target_language_quantizations }}'" From 0d233db3fb9e4c969677ad7758c438e02c3ef539 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Fri, 18 Jul 2025 19:36:37 -0500 Subject: [PATCH 4/9] Introduce switch to enable doing a token exchange for signing Introduce TARGET_HF_REPO_DO_TOKEN_EXCHANGE switch to enable a token exchange of the GitHub token against and IBM sigstore signing token so that the model signature appears to be from an IBM account rather than the github identity that was used to run the build. Note that not everyone can sign with the IBM sigstore signing token since it requires per-user setup by 'me'. Signed-off-by: Stefan Berger --- .github/workflows/granite-3.2-release-test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/granite-3.2-release-test.yml b/.github/workflows/granite-3.2-release-test.yml index d91b8731..c5659a10 100644 --- a/.github/workflows/granite-3.2-release-test.yml +++ b/.github/workflows/granite-3.2-release-test.yml @@ -84,6 +84,7 @@ env: TARGET_HF_REPO_OWNER: mrutkows TARGET_HF_REPO_PRIVATE: false TARGET_HF_REPO_SIGN_MODELS: true + TARGET_HF_REPO_DO_TOKEN_EXCHANGE: false COLLECTION_CONFIG: "resources/json/latest/hf_collection_mapping_gguf.json" jobs: @@ -106,6 +107,7 @@ jobs: target_repo_owner: ${{ steps.set-vars.outputs.target_repo_owner }} target_repo_name_ext: ${{ steps.set-vars.outputs.target_repo_name_ext }} target_repo_sign_models: ${{ steps.set-vars.outputs.target_repo_sign_models }} + target_repo_do_token_exchange: ${{ steps.set-vars.outputs.target_repo_do_token_exchange }} source_language_repos: "${{ steps.set-vars.outputs.source_language_repos }}" target_language_quantizations: "${{ steps.set-vars.outputs.target_language_quantizations }}" source_vision_repos: "${{ steps.set-vars.outputs.source_vision_repos }}" @@ -133,6 +135,7 @@ jobs: echo "target_repo_owner=$TARGET_HF_REPO_OWNER" >> "$GITHUB_OUTPUT" echo "target_repo_name_ext=$TARGET_HF_REPO_NAME_EXT" >> "$GITHUB_OUTPUT" echo "target_repo_sign_models=$TARGET_HF_REPO_SIGN_MODELS" >> "$GITHUB_OUTPUT" + echo "target_repo_do_token_exchange=$TARGET_HF_REPO_DO_TOKEN_EXCHANGE" >> "$GITHUB_OUTPUT" echo "source_language_repos=$SOURCE_LANGUAGE_REPOS" >> "$GITHUB_OUTPUT" echo "target_language_quantizations=$TARGET_LANGUAGE_QUANTIZATIONS" >> "$GITHUB_OUTPUT" echo "target_repo_private=$TARGET_HF_REPO_PRIVATE" >> "$GITHUB_OUTPUT" @@ -155,6 +158,7 @@ jobs: echo "target_repo_owner: '${{ steps.set-vars.outputs.target_repo_owner }}'" echo "target_repo_name_ext: '${{ steps.set-vars.outputs.target_repo_name_ext }}'" echo "target_repo_sign_models: '${{ steps.set-vars.outputs.target_repo_sign_models }}'" + echo "target_repo_do_token_exchange: '${{ steps.set-vars.outputs.target_repo_do_token_exchange }}'" echo "target_repo_private: '${{ steps.set-vars.outputs.target_repo_private }}'" echo "source_language_repos: '${{ steps.set-vars.outputs.source_language_repos }}'" echo "target_language_quantizations: '${{ steps.set-vars.outputs.target_language_quantizations }}'" From ce386a1dbff4d5f2fccf803a7817c894071a4330 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Fri, 18 Jul 2025 23:17:20 -0500 Subject: [PATCH 5/9] Enable signing of embedding models Signed-off-by: Stefan Berger --- .../workflows/granite-3.2-release-test.yml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/granite-3.2-release-test.yml b/.github/workflows/granite-3.2-release-test.yml index c5659a10..dfdf6363 100644 --- a/.github/workflows/granite-3.2-release-test.yml +++ b/.github/workflows/granite-3.2-release-test.yml @@ -601,3 +601,27 @@ jobs: target_collection_private: ${{ needs.environment-setup.outputs.target_repo_private == 'true' }} secrets: hf_token: ${{ secrets.HF_TOKEN_TEST }} + + embedding-sign-models-in-repos: + if: ${{ needs.environment-setup.outputs.target_repo_sign_models == 'true' }} + needs: [ environment-setup, embedding-quantize-upload-gguf ] + permissions: + id-token: write + strategy: + fail-fast: false + matrix: + repo_id: ${{ fromJson(needs.environment-setup.outputs.source_embedding_repos) }} + quantization: + - f16 + - ${{ fromJson(needs.environment-setup.outputs.target_embedding_quantizations) }} + uses: IBM/gguf/.github/workflows/reusable-sign-model-in-hf-repo.yml@main + with: + debug: ${{ needs.environment-setup.outputs.debug == 'true' }} + do_token_exchange: ${{ needs.environment-setup.outputs.target_repo_do_token_exchange == 'true' }} + repo_id: "${{ matrix.repo_id }}" + quantization: "${{ matrix.quantization }}" + target_repo_owner: ${{ needs.environment-setup.outputs.target_repo_owner }} + target_repo_name_ext: ${{ needs.environment-setup.outputs.target_repo_name_ext }} + secrets: + hf_token: ${{ secrets.HF_TOKEN_TEST }} + sigstore_sts_client_secret: ${{ secrets.SIGSTORE_STS_CLIENT_SECRET }} From ead255c74671abec20fc16095a01d0d7366d4075 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Thu, 17 Jul 2025 21:17:44 -0500 Subject: [PATCH 6/9] Enable signing of language models Signed-off-by: Stefan Berger --- .../workflows/granite-3.2-release-test.yml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/granite-3.2-release-test.yml b/.github/workflows/granite-3.2-release-test.yml index dfdf6363..28ea76c4 100644 --- a/.github/workflows/granite-3.2-release-test.yml +++ b/.github/workflows/granite-3.2-release-test.yml @@ -274,6 +274,30 @@ jobs: secrets: hf_token: ${{ secrets.HF_TOKEN_TEST }} + language-sign-models-in-repos: + if: ${{ needs.environment-setup.outputs.target_repo_sign_models == 'true' }} + needs: [ environment-setup, bvt-language-quantized-gguf-models ] + permissions: + id-token: write + strategy: + fail-fast: false + matrix: + repo_id: ${{ fromJson(needs.environment-setup.outputs.source_language_repos) }} + quantization: + - f16 + - ${{ fromJson(needs.environment-setup.outputs.target_language_quantizations) }} + uses: IBM/gguf/.github/workflows/reusable-sign-model-in-hf-repo.yml@main + with: + debug: ${{ needs.environment-setup.outputs.debug == 'true' }} + do_token_exchange: ${{ needs.environment-setup.outputs.target_repo_do_token_exchange == 'true' }} + target_repo_owner: ${{ needs.environment-setup.outputs.target_repo_owner }} + target_repo_name_ext: ${{ needs.environment-setup.outputs.target_repo_name_ext }} + repo_id: "${{ matrix.repo_id }}" + quantization: "${{ matrix.quantization }}" + secrets: + hf_token: ${{ secrets.HF_TOKEN_TEST }} + sigstore_sts_client_secret: ${{ secrets.SIGSTORE_STS_CLIENT_SECRET }} + # TODO: change collection from private to public (on success) create-hf-collections: needs: [ From 11363e63d7f5f7135090fc4a4fa4809905529278 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Fri, 18 Jul 2025 08:21:56 -0500 Subject: [PATCH 7/9] Enable signing of Guardian models Signed-off-by: Stefan Berger --- .../workflows/granite-3.2-release-test.yml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/granite-3.2-release-test.yml b/.github/workflows/granite-3.2-release-test.yml index 28ea76c4..eb60e30e 100644 --- a/.github/workflows/granite-3.2-release-test.yml +++ b/.github/workflows/granite-3.2-release-test.yml @@ -504,6 +504,30 @@ jobs: secrets: hf_token: ${{ secrets.HF_TOKEN_TEST }} + guardian-sign-models-in-repos: + if: ${{ needs.environment-setup.outputs.target_repo_sign_models == 'true' }} + needs: [ environment-setup, guardian-bvt-quantized-gguf-models ] + permissions: + id-token: write + strategy: + fail-fast: false + matrix: + repo_id: ${{ fromJson(needs.environment-setup.outputs.source_guardian_repos) }} + quantization: + - f16 + - ${{ fromJson(needs.environment-setup.outputs.target_guardian_quantizations) }} + uses: IBM/gguf/.github/workflows/reusable-sign-model-in-hf-repo.yml@main + with: + debug: ${{ needs.environment-setup.outputs.debug == 'true' }} + do_token_exchange: ${{ needs.environment-setup.outputs.target_repo_do_token_exchange == 'true' }} + target_repo_owner: ${{ needs.environment-setup.outputs.target_repo_owner }} + target_repo_name_ext: "${{ needs.environment-setup.outputs.target_repo_name_ext }}" + repo_id: ${{ matrix.repo_id }} + quantization: "${{ matrix.quantization }}" + secrets: + hf_token: ${{ secrets.HF_TOKEN_TEST }} + sigstore_sts_client_secret: ${{ secrets.SIGSTORE_STS_CLIENT_SECRET }} + # TODO: change collection from private to public (on success) guardian-create-hf-collections: needs: [ From c0187cb292004f1762a654abbfa65b5103eaa55d Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Fri, 18 Jul 2025 09:21:57 -0500 Subject: [PATCH 8/9] Enable signing of vision models Signed-off-by: Stefan Berger --- .../workflows/granite-3.2-release-test.yml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/granite-3.2-release-test.yml b/.github/workflows/granite-3.2-release-test.yml index eb60e30e..2924a168 100644 --- a/.github/workflows/granite-3.2-release-test.yml +++ b/.github/workflows/granite-3.2-release-test.yml @@ -401,6 +401,31 @@ jobs: secrets: hf_token: ${{ secrets.HF_TOKEN_TEST }} + vision-sign-models-in-repos: + if: ${{ needs.environment-setup.outputs.target_repo_sign_models == 'true' }} + needs: [ environment-setup, bvt-vision-quantized-gguf-models ] + permissions: + id-token: write + strategy: + fail-fast: false + matrix: + repo_id: ${{ fromJson(needs.environment-setup.outputs.source_vision_repos) }} + quantization: + - f16 + - ${{ fromJson(needs.environment-setup.outputs.target_vision_quantizations) }} + - "fullname:mmproj-model-f16.gguf" + uses: IBM/gguf/.github/workflows/reusable-sign-model-in-hf-repo.yml@main + with: + debug: ${{ needs.environment-setup.outputs.debug == 'true' }} + do_token_exchange: ${{ needs.environment-setup.outputs.target_repo_do_token_exchange == 'true' }} + target_repo_owner: "${{ needs.environment-setup.outputs.target_repo_owner }}" + target_repo_name_ext: "${{ needs.environment-setup.outputs.target_repo_name_ext }}" + repo_id: "${{ matrix.repo_id }}" + quantization: "${{ matrix.quantization }}" + secrets: + hf_token: ${{ secrets.HF_TOKEN_TEST }} + sigstore_sts_client_secret: ${{ secrets.SIGSTORE_STS_CLIENT_SECRET }} + # TODO: change collection from private to public (on success) vision-create-hf-collections: needs: [ From 17b05689e4d6530ebf104017a9fffbd6e717191d Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 21 Jul 2025 15:14:02 -0500 Subject: [PATCH 9/9] WIP: README.md: Add documentation for model signing to the README.md Signed-off-by: Stefan Berger --- README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/README.md b/README.md index 5616c9cc..368752bf 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This repository provides an automated CI/CD process to convert, test and deploy - [Embedding](#embedding-dense) - [GGUF Conversion & Quantization](#gguf-conversion--quantization) - [GGUF Verification Testing](#gguf-verification-testing) +- [GGUF Model Signing](#gguf-model-signing) - [References](#references) - [Releasing GGUF model conversions & quantizations](#releasing-gguf-model-conversions--quantizations) @@ -157,6 +158,42 @@ As a baseline, each converted model MUST successfully be run in the following pr --- +### GGUF Model Signing + +To enable model signing, simply set the `TARGET_HF_REPO_SIGN_MODELS` build +switch to 'true'. + +If the `TARGET_HF_REPO_DO_TOKEN_EXCHANGE` build switch is set to 'false', +then the signatures will appear to have been made by an identity expressed +through a URL associated with the repository from which the build was +inititated. To change this to an IBM identity, such as a functional Id, this +option must be set to 'true' to run a token exchange with +sigstore.verify.ibm.com. However, for the token exchange to work, it requires +that there exist a mapping in sigstore.verify.ibm.com from the github +identity to an IBM email/identity, otherwise the signing will fail. + +For signature verification a version of the model_signing library +later than v1.0.1 is needed: + +``` +pip install model_signing>v1.0.1 + +``` + +To for example verify one of the signatures of granite-embedding-30m-english, +use the following command in the directory of the huggingface git checkout: + +``` +model_signing verify sigstore \ + --signature granite-embedding-30m-english-Q8_0.gguf.sig \ + --identity Garnite.GGUF@ibm.com \ + --identity_provider https://sigstore.verify.ibm.com/oauth2 \ + --ignore_unsigned_files \ + . +``` + +--- + ## References - GGUF format