From d3d0b1a87e03fa7a5c9c1d2269df83bc48297cb4 Mon Sep 17 00:00:00 2001 From: Zhan Rongrui Date: Wed, 16 Sep 2026 18:14:11 +0800 Subject: [PATCH 1/8] Preserve configured gradient clipping in accuracy mode Signed-off-by: Zhan Rongrui --- swift/megatron/pipelines/train/sft.py | 3 -- swift/megatron/trainers/base.py | 4 ++ tests/megatron/test_reproducible_clipping.py | 44 ++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 tests/megatron/test_reproducible_clipping.py diff --git a/swift/megatron/pipelines/train/sft.py b/swift/megatron/pipelines/train/sft.py index 381a02b24d..9b007a2f14 100644 --- a/swift/megatron/pipelines/train/sft.py +++ b/swift/megatron/pipelines/train/sft.py @@ -43,9 +43,6 @@ def __init__(self, args: Optional[Union[List[str], MegatronSftArguments]] = None self.train_msg = {} super(SwiftSft, self).__init__(args) args = self.args - from megatron.core.transformer.module import _use_accuracy_compatible - if _use_accuracy_compatible() and getattr(args, 'clip_grad', 0) > 0: - args.clip_grad = 0.0 if repatch is not None: megatron_args = asdict(self.args) if args.attention_backend != 'local': diff --git a/swift/megatron/trainers/base.py b/swift/megatron/trainers/base.py index 0b0d5da9ad..aba658544d 100644 --- a/swift/megatron/trainers/base.py +++ b/swift/megatron/trainers/base.py @@ -209,6 +209,10 @@ def _prepare_peft_model(self, models): def get_optimizer_and_scheduler(self): args = self.args + if args.use_accuracy_compatible and args.clip_grad > 0: + from megatron.core.optimizer import clip_grads + if not hasattr(clip_grads, 'get_reproducible_grad_norm_bins'): + raise ValueError('Accuracy-compatible clipping requires Megatron-Core reproducible norm support') if mcore_016: from megatron.core.optimizer import AdamOptimizerConfig, SGDOptimizerConfig if args.optimizer == 'adam' or 'muon' in args.optimizer: diff --git a/tests/megatron/test_reproducible_clipping.py b/tests/megatron/test_reproducible_clipping.py new file mode 100644 index 0000000000..4c82d4f193 --- /dev/null +++ b/tests/megatron/test_reproducible_clipping.py @@ -0,0 +1,44 @@ +"""Explicit clipping survives accuracy-mode initialization.""" + +import pytest +from types import SimpleNamespace +from unittest.mock import patch + +from swift.megatron.pipelines.train import sft as sft_module +from swift.megatron.trainers import base as trainer_module +from swift.pipelines.base import SwiftPipeline + + +@pytest.mark.parametrize('accuracy', [False, True]) +@pytest.mark.parametrize('clip', [0.0, 0.25, 1.0]) +def test_constructor_preserves_explicit_clipping(accuracy, clip): + args = SimpleNamespace( + clip_grad=clip, + use_accuracy_compatible=accuracy, + template_meta=SimpleNamespace(template_cls=None), + model_meta=SimpleNamespace(is_multimodal=False), + mcore_model='existing-model', + output_dir='unused', + get_model_processor=lambda **kwargs: (None, None), + save_args=lambda output_dir: None) + + def pipeline_init(instance, values): + instance.args = values + + def prepare_template(instance): + instance.template = SimpleNamespace() + + with patch.object(SwiftPipeline, '__init__', pipeline_init), \ + patch.object(sft_module, 'repatch', None), \ + patch.object(sft_module.MegatronSft, '_prepare_template', prepare_template), \ + patch('megatron.core.transformer.module._use_accuracy_compatible', return_value=accuracy): + instance = sft_module.MegatronSft(args) + assert instance.args.clip_grad == clip + + +def test_unsupported_megatron_rejects_requested_norm(monkeypatch): + from megatron.core.optimizer import clip_grads + trainer = SimpleNamespace(args=SimpleNamespace(use_accuracy_compatible=True, clip_grad=1.0)) + monkeypatch.delattr(clip_grads, 'get_reproducible_grad_norm_bins', raising=False) + with pytest.raises(ValueError, match='Megatron-Core reproducible norm support'): + trainer_module.BaseMegatronTrainer.get_optimizer_and_scheduler(trainer) From 6d31ce772e083c760d5dc579a6ea48f5a69d0143 Mon Sep 17 00:00:00 2001 From: Zhan Rongrui Date: Wed, 16 Sep 2026 19:39:25 +0800 Subject: [PATCH 2/8] Sync accuracy CI CUDA 12.9 image and Fleet ops wheel Apply the equivalent of PFCCLab/Megatron-LM commit 8aa1d8a30b1983debbae1bdea1a080d7eebe01d8 to the Swift accuracy workflow. Signed-off-by: Zhan Rongrui --- .github/workflows/alignment_model_accuracy.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/alignment_model_accuracy.yaml b/.github/workflows/alignment_model_accuracy.yaml index 76faf444ba..213e3e5fe7 100644 --- a/.github/workflows/alignment_model_accuracy.yaml +++ b/.github/workflows/alignment_model_accuracy.yaml @@ -37,7 +37,7 @@ jobs: determine_gpu_runner ${runner_name} - name: Determine Image Name run: | - echo "IMAGE_NAME=ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddle:ubuntu24-cuda130-py312-dev" >> "$GITHUB_ENV" + echo "IMAGE_NAME=ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddle:ubuntu24-cuda129-py312-dev" >> "$GITHUB_ENV" - name: Run Container run: | container_name=${TASK}-$(date +%Y%m%d-%H%M%S) @@ -114,7 +114,7 @@ jobs: cd /workspace for url in \ $BOS/PaddleFleet/develop/latest/paddlefleet-0.0.0-py3-none-linux_x86_64.whl \ - $BOS/PaddleFleet/develop/latest/cu130/paddle-release/paddlefleet_ops-0.0.0-cp312-cp312-linux_x86_64.whl \ + $BOS/PaddleFleet/develop/latest/cu129/paddle-release/paddlefleet_ops-0.0.0-cp312-cp312-linux_x86_64.whl \ $BOS/whl/megatron_core-0.0.0-cp312-cp312-linux_x86_64.whl \ $BOS/whl/mcore_bridge-0.0.0-py3-none-any.whl ; do echo "Downloading $url" From db012ce890d20edeb5a71acac7743df9c82cfd4f Mon Sep 17 00:00:00 2001 From: zrr1999 <2742392377@qq.com> Date: Sat, 3 Oct 2026 23:34:30 +0800 Subject: [PATCH 3/8] style: satisfy repository formatting hooks --- .github/workflows/alignment_model_accuracy.yaml | 4 ++-- scripts/dependence/build.sh | 1 - swift/megatron/init.py | 1 + tests/megatron/test_model_config.py | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/alignment_model_accuracy.yaml b/.github/workflows/alignment_model_accuracy.yaml index 213e3e5fe7..269d3824c8 100644 --- a/.github/workflows/alignment_model_accuracy.yaml +++ b/.github/workflows/alignment_model_accuracy.yaml @@ -65,7 +65,7 @@ jobs: wget -q --no-proxy https://paddle-qa.bj.bcebos.com/CodeSync/develop/PaddleFleet.tar --no-check-certificate rm -rf PaddleFleet && tar xf PaddleFleet.tar && rm -rf PaddleFleet.tar cd PaddleFleet && git pull && cd - - + echo "Download ms-swift form https://paddle-github-action.bj.bcebos.com/whl/ms-swift.tar.gz" wget -q --no-proxy https://paddle-github-action.bj.bcebos.com/whl/ms-swift.tar.gz --no-check-certificate rm -rf ms-swift && tar zxf ms-swift.tar.gz && rm -rf ms-swift.tar.gz @@ -145,7 +145,7 @@ jobs: export MEGATRON_CORE_WHEEL_PATH=/workspace/megatron_core-0.0.0-cp312-cp312-linux_x86_64.whl export MS_SWIFT_WHEEL_PATH=/workspace/upload/ms_swift-0.0.0-py3-none-any.whl export MCORE_BRIDGE_WHEEL_PATH=/workspace/mcore_bridge-0.0.0-py3-none-any.whl - # The BOS wheels are republished under a fixed 0.0.0 filename + # The BOS wheels are republished under a fixed 0.0.0 filename export UV_SKIP_WHEEL_FILENAME_CHECK=1 for whl in "$PADDLEFLEET_WHEEL_PATH" "$PADDLEFLEET_OPS_WHEEL_PATH" \ diff --git a/scripts/dependence/build.sh b/scripts/dependence/build.sh index de73702692..6b9129513c 100644 --- a/scripts/dependence/build.sh +++ b/scripts/dependence/build.sh @@ -79,4 +79,3 @@ echo -e "\033[32m ---- make ms-swift.tar.gz \033[0m" swift_tar echo -e "\033[32m ---- build ms-swift whl \033[0m" swift_build - diff --git a/swift/megatron/init.py b/swift/megatron/init.py index 4a5e173af2..277cae201c 100644 --- a/swift/megatron/init.py +++ b/swift/megatron/init.py @@ -183,6 +183,7 @@ def _patch_mcore_bridge_disable_te(): import mcore_bridge.model.register as mcb_register def _force_local_spec(orig): + def wrapper(*args, **kwargs): kwargs['use_transformer_engine'] = False return orig(*args, **kwargs) diff --git a/tests/megatron/test_model_config.py b/tests/megatron/test_model_config.py index bbf85f99d0..4874d8768f 100644 --- a/tests/megatron/test_model_config.py +++ b/tests/megatron/test_model_config.py @@ -103,7 +103,7 @@ def test_get_mcore_model_config_does_not_enable_mtp_from_nested_checkpoint(monke def test_get_mcore_model_config_prefers_n_routed_experts(monkeypatch): _patch_model_config(monkeypatch) - hf_config = PretrainedConfig(model_type="glm_moe_dsa", num_experts=256, n_routed_experts=16) + hf_config = PretrainedConfig(model_type='glm_moe_dsa', num_experts=256, n_routed_experts=16) config = utils.get_mcore_model_config(_make_args(), hf_config) @@ -132,9 +132,9 @@ def test_get_padding_to_sequence_parallel_uses_tp_times_two(): attention_backend='unfused', ) assert get_padding_to(args) == 2 - args.megatron_extra_kwargs = {"dsa_accuracy_compatible": True} + args.megatron_extra_kwargs = {'dsa_accuracy_compatible': True} assert get_padding_to(args) == 4 - args.megatron_extra_kwargs = {"dsa_accuracy_compatible": False} + args.megatron_extra_kwargs = {'dsa_accuracy_compatible': False} assert get_padding_to(args) == 2 seq_len = 57 assert math.ceil(seq_len / 4) * 4 == 60 From 69581db1918aa46a6075ba59ff8bb29c8999dcb6 Mon Sep 17 00:00:00 2001 From: zrr1999 <2742392377@qq.com> Date: Sun, 4 Oct 2026 07:27:31 +0800 Subject: [PATCH 4/8] Isolate GPU CI checkouts from container writes --- .dev_scripts/ci_source_copy.sh | 17 ++++++ .dev_scripts/dockerci.sh | 11 ++-- .dev_scripts/test_ci_source_copy.py | 86 +++++++++++++++++++++++++++++ .github/workflows/citest.yaml | 28 +++++----- .github/workflows/lint.yaml | 6 +- 5 files changed, 128 insertions(+), 20 deletions(-) create mode 100644 .dev_scripts/ci_source_copy.sh create mode 100644 .dev_scripts/test_ci_source_copy.py diff --git a/.dev_scripts/ci_source_copy.sh b/.dev_scripts/ci_source_copy.sh new file mode 100644 index 0000000000..216e2ebdf2 --- /dev/null +++ b/.dev_scripts/ci_source_copy.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -e + +if [ "$#" -lt 2 ]; then + echo "Usage: $0 SOURCE_DIR COMMAND [ARG...]" >&2 + exit 2 +fi + +source_dir=$1 +shift +test_dir="$(mktemp -d /tmp/swift-ci.XXXXXX)" +trap 'rm -rf -- "${test_dir:?}"' EXIT + +# Build products and Git changes stay in the container, owned by its test user. +cp -R "$source_dir/." "$test_dir/" +cd "$test_dir" +"$@" diff --git a/.dev_scripts/dockerci.sh b/.dev_scripts/dockerci.sh index f42e819fb4..099d1c7fd9 100644 --- a/.dev_scripts/dockerci.sh +++ b/.dev_scripts/dockerci.sh @@ -2,6 +2,7 @@ MODELSCOPE_CACHE_DIR_IN_CONTAINER=/modelscope_cache CODE_DIR=$PWD CODE_DIR_IN_CONTAINER=/ms-swift +SOURCE_DIR_IN_CONTAINER=/ms-swift-source MODELSCOPE_SDK_DEBUG=True echo "$USER" gpus='0,1 2,3' @@ -31,7 +32,7 @@ do docker run --rm --name $CONTAINER_NAME --shm-size=16gb \ --cpuset-cpus=${cpu_sets_arr[$idx]} \ --gpus='"'"device=$gpu"'"' \ - -v $CODE_DIR:$CODE_DIR_IN_CONTAINER \ + -v "$CODE_DIR:$SOURCE_DIR_IN_CONTAINER:ro" \ -v $MODELSCOPE_CACHE:$MODELSCOPE_CACHE_DIR_IN_CONTAINER \ -v $MODELSCOPE_HOME_CACHE/$idx:/root \ -v /home/admin/pre-commit:/home/admin/pre-commit \ @@ -51,12 +52,13 @@ do -e PR_CHANGED_FILES=$PR_CHANGED_FILES \ --workdir=$CODE_DIR_IN_CONTAINER \ ${IMAGE_NAME}:${IMAGE_VERSION} \ - $CI_COMMAND + bash "$SOURCE_DIR_IN_CONTAINER/.dev_scripts/ci_source_copy.sh" \ + "$SOURCE_DIR_IN_CONTAINER" $CI_COMMAND else docker run --rm --name $CONTAINER_NAME --shm-size=16gb \ --cpuset-cpus=${cpu_sets_arr[$idx]} \ --gpus='"'"device=$gpu"'"' \ - -v $CODE_DIR:$CODE_DIR_IN_CONTAINER \ + -v "$CODE_DIR:$SOURCE_DIR_IN_CONTAINER:ro" \ -v $MODELSCOPE_CACHE:$MODELSCOPE_CACHE_DIR_IN_CONTAINER \ -v $MODELSCOPE_HOME_CACHE/$idx:/root \ -v /home/admin/pre-commit:/home/admin/pre-commit \ @@ -75,7 +77,8 @@ do -e PR_CHANGED_FILES=$PR_CHANGED_FILES \ --workdir=$CODE_DIR_IN_CONTAINER \ ${IMAGE_NAME}:${IMAGE_VERSION} \ - $CI_COMMAND + bash "$SOURCE_DIR_IN_CONTAINER/.dev_scripts/ci_source_copy.sh" \ + "$SOURCE_DIR_IN_CONTAINER" $CI_COMMAND fi if [ $? -ne 0 ]; then echo "Running test case failed, please check the log!" diff --git a/.dev_scripts/test_ci_source_copy.py b/.dev_scripts/test_ci_source_copy.py new file mode 100644 index 0000000000..87db867698 --- /dev/null +++ b/.dev_scripts/test_ci_source_copy.py @@ -0,0 +1,86 @@ +import json +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +SCRIPT = Path(__file__).with_name('ci_source_copy.sh') + + +class SourceCopyTest(unittest.TestCase): + + def setUp(self): + self.directory = tempfile.TemporaryDirectory() + self.addCleanup(self.directory.cleanup) + self.source = Path(self.directory.name) / 'source with spaces' + self.source.mkdir() + (self.source / 'input.txt').write_text('original') + (self.source / '.git').mkdir() + (self.source / '.git/config').write_text('original git configuration') + (self.source / 'linked.txt').symlink_to('input.txt') + + def run_copy(self, *command, source=None): + return subprocess.run(['bash', str(SCRIPT), str(source or self.source), *command], + capture_output=True, + text=True, + check=False) + + def test_private_copy_preserves_inputs_and_command_arguments(self): + result = self.run_copy( + sys.executable, '-c', ''' +import json +import sys +from pathlib import Path + +cwd = Path.cwd() +assert (cwd / 'input.txt').read_text() == 'original' +assert (cwd / 'linked.txt').is_symlink() +assert (cwd / 'linked.txt').read_text() == 'original' +(cwd / 'input.txt').write_text('modified') +(cwd / '.git/config').write_text('modified git configuration') +(cwd / 'build-output').write_text('output') +print(json.dumps({'cwd': str(cwd), 'args': sys.argv[1:]})) +''', 'argument with spaces', '*') + self.assertEqual(result.returncode, 0, result.stderr) + report = json.loads(result.stdout) + self.assertEqual(report['args'], ['argument with spaces', '*']) + self.assertNotEqual(Path(report['cwd']), self.source) + self.assertFalse(Path(report['cwd']).exists()) + self.assertEqual((self.source / 'input.txt').read_text(), 'original') + self.assertEqual((self.source / '.git/config').read_text(), 'original git configuration') + self.assertFalse((self.source / 'build-output').exists()) + + def test_failure_is_preserved_and_private_copy_is_removed(self): + result = self.run_copy( + sys.executable, '-c', ''' +import sys +from pathlib import Path + +print(Path.cwd()) +Path('build-output').write_text('partial output') +sys.exit(23) +''') + self.assertEqual(result.returncode, 23, result.stderr) + self.assertFalse(Path(result.stdout.strip()).exists()) + self.assertFalse((self.source / 'build-output').exists()) + + def test_copy_failure_prevents_command_execution(self): + marker = Path(self.directory.name) / 'command-ran' + result = self.run_copy( + sys.executable, + '-c', + 'from pathlib import Path; import sys; Path(sys.argv[1]).touch()', + str(marker), + source=self.source / 'missing') + self.assertNotEqual(result.returncode, 0) + self.assertFalse(marker.exists()) + + def test_missing_command_is_rejected(self): + result = self.run_copy() + self.assertEqual(result.returncode, 2) + self.assertIn('Usage:', result.stderr) + + +if __name__ == '__main__': + unittest.main() diff --git a/.github/workflows/citest.yaml b/.github/workflows/citest.yaml index 101e9f3b1a..bb22b76b2a 100644 --- a/.github/workflows/citest.yaml +++ b/.github/workflows/citest.yaml @@ -11,7 +11,6 @@ on: - "requirements/**" - "docs/**" - "tools/**" - - ".dev_scripts/**" - "README.md" - "README_*.md" - "NOTICE" @@ -25,7 +24,6 @@ on: - "requirements/**" - "docs/**" - "tools/**" - - ".dev_scripts/**" - "README.md" - "README_*.md" - "NOTICE" @@ -41,37 +39,39 @@ jobs: # The type of runner that the job will run on runs-on: [self-hosted] timeout-minutes: 240 + env: + CI_CHECKOUT_PATH: .citest-${{ github.run_id }}-${{ github.run_attempt }} steps: - - name: ResetFileMode - shell: bash - run: | - # reset filemode to allow action runner to delete files - # generated by root in docker - set -e - source ~/.bashrc - sudo chown -R $USER:$USER $GITHUB_WORKSPACE - - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 env: GIT_CONFIG_PARAMETERS: "'core.hooksPath='" with: + path: ${{ env.CI_CHECKOUT_PATH }} + persist-credentials: false lfs: 'true' submodules: 'false' fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} - name: Get changed files id: changed-files + working-directory: ${{ env.CI_CHECKOUT_PATH }} run: | if ${{ github.event_name == 'pull_request' }}; then - echo "PR_CHANGED_FILES=$(git diff --name-only -r HEAD^1 HEAD | xargs)" >> $GITHUB_ENV + echo "PR_CHANGED_FILES=$(git diff --name-only -r HEAD^1 HEAD | xargs)" >> "$GITHUB_ENV" else - echo "PR_CHANGED_FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.event.after }} | xargs)" >> $GITHUB_ENV + echo "PR_CHANGED_FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.event.after }} | xargs)" >> "$GITHUB_ENV" fi - name: Checkout LFS objects + working-directory: ${{ env.CI_CHECKOUT_PATH }} run: git lfs checkout - name: Run unittest shell: bash + working-directory: ${{ env.CI_CHECKOUT_PATH }} run: | set -e source /mnt/modelscope/ci_env.sh bash .dev_scripts/dockerci.sh + - name: Remove this run's checkout + if: always() + shell: bash + run: rm -rf -- "${GITHUB_WORKSPACE:?}/${CI_CHECKOUT_PATH:?}" diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 6ff84517d8..8ae4a2ec6f 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -10,9 +10,9 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Set up Python 3.10 - uses: actions/setup-python@v2 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 with: python-version: '3.10' - name: Install pre-commit hook @@ -20,3 +20,5 @@ jobs: pip install pre-commit - name: Linting run: pre-commit run --all-files + - name: CI source isolation regression tests + run: python .dev_scripts/test_ci_source_copy.py From c61824926db4ad315fd496371604aa602c8bbf08 Mon Sep 17 00:00:00 2001 From: zrr1999 <2742392377@qq.com> Date: Sun, 4 Oct 2026 07:33:23 +0800 Subject: [PATCH 5/8] Use native Git on legacy GPU runner hosts --- .github/workflows/citest.yaml | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/citest.yaml b/.github/workflows/citest.yaml index bb22b76b2a..463032f797 100644 --- a/.github/workflows/citest.yaml +++ b/.github/workflows/citest.yaml @@ -43,15 +43,21 @@ jobs: CI_CHECKOUT_PATH: .citest-${{ github.run_id }}-${{ github.run_attempt }} steps: - name: Checkout - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + shell: bash env: GIT_CONFIG_PARAMETERS: "'core.hooksPath='" - with: - path: ${{ env.CI_CHECKOUT_PATH }} - persist-credentials: false - lfs: 'true' - submodules: 'false' - fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} + run: | + set -euo pipefail + mkdir -- "$CI_CHECKOUT_PATH" + cd "$CI_CHECKOUT_PATH" + git init + git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" + fetch_options=() + if [[ "$GITHUB_EVENT_NAME" == pull_request ]]; then + fetch_options+=(--depth=2) + fi + git fetch --no-tags "${fetch_options[@]}" origin "$GITHUB_SHA" + GIT_LFS_SKIP_SMUDGE=1 git checkout --detach "$GITHUB_SHA" - name: Get changed files id: changed-files working-directory: ${{ env.CI_CHECKOUT_PATH }} @@ -63,7 +69,9 @@ jobs: fi - name: Checkout LFS objects working-directory: ${{ env.CI_CHECKOUT_PATH }} - run: git lfs checkout + run: | + git lfs fetch origin "$GITHUB_SHA" + git lfs checkout - name: Run unittest shell: bash working-directory: ${{ env.CI_CHECKOUT_PATH }} From 88568ec22a00424cd7aa1ee9816ef537c33f88f0 Mon Sep 17 00:00:00 2001 From: zrr1999 <2742392377@qq.com> Date: Sun, 4 Oct 2026 07:44:34 +0800 Subject: [PATCH 6/8] Provide checksum-verified temporary Git LFS for GPU CI --- .github/workflows/citest.yaml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/citest.yaml b/.github/workflows/citest.yaml index 463032f797..590ab0f340 100644 --- a/.github/workflows/citest.yaml +++ b/.github/workflows/citest.yaml @@ -70,6 +70,30 @@ jobs: - name: Checkout LFS objects working-directory: ${{ env.CI_CHECKOUT_PATH }} run: | + set -euo pipefail + if ! git lfs version; then + case "$(uname -m)" in + x86_64) + architecture=amd64 + digest=e455e00f15d9b95661b8d53498ffb0c3367962cf1ec73c31ab7369516cd6ab8d + ;; + aarch64) + architecture=arm64 + digest=ac9c8efac980bb0505ead384d087e2acb6486fd8498691a2165fa174ec6118c2 + ;; + *) echo "Unsupported Git LFS host architecture" >&2; exit 1 ;; + esac + tool_dir="$(mktemp -d "${RUNNER_TEMP:?}/git-lfs.XXXXXX")" + trap 'rm -rf -- "${tool_dir:?}"' EXIT + archive="$tool_dir/git-lfs.tar.gz" + curl --fail --location --retry 3 \ + "https://github.com/git-lfs/git-lfs/releases/download/v3.8.0/git-lfs-linux-$architecture-v3.8.0.tar.gz" \ + --output "$archive" + echo "$digest $archive" | sha256sum --check - + tar -xzf "$archive" -C "$tool_dir" --strip-components=1 git-lfs-3.8.0/git-lfs + export PATH="$tool_dir:$PATH" + git lfs version + fi git lfs fetch origin "$GITHUB_SHA" git lfs checkout - name: Run unittest From a4736be39c78b5feefff12fe0dfdd70094b090ce Mon Sep 17 00:00:00 2001 From: zrr1999 <2742392377@qq.com> Date: Sun, 4 Oct 2026 07:52:58 +0800 Subject: [PATCH 7/8] Initialize and verify LFS within the temporary checkout --- .github/workflows/citest.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/citest.yaml b/.github/workflows/citest.yaml index 590ab0f340..6c29ad736e 100644 --- a/.github/workflows/citest.yaml +++ b/.github/workflows/citest.yaml @@ -94,8 +94,10 @@ jobs: export PATH="$tool_dir:$PATH" git lfs version fi + git lfs install --local --skip-smudge --skip-repo git lfs fetch origin "$GITHUB_SHA" git lfs checkout + git lfs fsck - name: Run unittest shell: bash working-directory: ${{ env.CI_CHECKOUT_PATH }} From 6c8af193d272183059f6f9958a0a9f0f131433b1 Mon Sep 17 00:00:00 2001 From: zrr1999 <2742392377@qq.com> Date: Fri, 16 Oct 2026 15:19:35 +0800 Subject: [PATCH 8/8] Skip LFS hydration when the revision contains no pointers --- .github/workflows/citest.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/citest.yaml b/.github/workflows/citest.yaml index 6c29ad736e..575dc01efa 100644 --- a/.github/workflows/citest.yaml +++ b/.github/workflows/citest.yaml @@ -71,6 +71,15 @@ jobs: working-directory: ${{ env.CI_CHECKOUT_PATH }} run: | set -euo pipefail + # Revisions without LFS pointers need no hydration or Git LFS installation. + if git grep -I -q '^version https://git-lfs.github.com/spec/v1$' HEAD --; then + echo "Hydrating this revision's LFS objects" + else + status=$? + if [ "$status" -ne 1 ]; then exit "$status"; fi + echo "No LFS pointers in this revision" + exit 0 + fi if ! git lfs version; then case "$(uname -m)" in x86_64)