|  | 
|  | 1 | +name: Test Metal Backend | 
|  | 2 | + | 
|  | 3 | +on: | 
|  | 4 | +  pull_request: | 
|  | 5 | +  push: | 
|  | 6 | +    branches: | 
|  | 7 | +      - main | 
|  | 8 | +      - release/* | 
|  | 9 | + | 
|  | 10 | +concurrency: | 
|  | 11 | +  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.event_name == 'workflow_dispatch' }}-${{ github.event_name == 'schedule' }} | 
|  | 12 | +  cancel-in-progress: false | 
|  | 13 | + | 
|  | 14 | +jobs: | 
|  | 15 | +  test-metal-builds: | 
|  | 16 | +    name: test-executorch-metal-build | 
|  | 17 | +    uses: pytorch/test-infra/.github/workflows/macos_job.yml@main | 
|  | 18 | +    with: | 
|  | 19 | +      runner: macos-m2-stable | 
|  | 20 | +      python-version: '3.11' | 
|  | 21 | +      submodules: 'recursive' | 
|  | 22 | +      ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} | 
|  | 23 | +      timeout: 90 | 
|  | 24 | +      script: | | 
|  | 25 | +        set -eux | 
|  | 26 | +
 | 
|  | 27 | +        echo "::group::Test ExecuTorch Metal build" | 
|  | 28 | +        PYTHON_EXECUTABLE=python CMAKE_ARGS="-DEXECUTORCH_BUILD_METAL=ON" ${CONDA_RUN} --no-capture-output ./install_executorch.sh | 
|  | 29 | +        echo "::endgroup::" | 
|  | 30 | +
 | 
|  | 31 | +  export-voxtral-metal-artifact: | 
|  | 32 | +    name: export-voxtral-metal-artifact | 
|  | 33 | +    uses: pytorch/test-infra/.github/workflows/macos_job.yml@main | 
|  | 34 | +    secrets: inherit | 
|  | 35 | +    with: | 
|  | 36 | +      runner: macos-m2-stable | 
|  | 37 | +      python-version: '3.11' | 
|  | 38 | +      submodules: 'recursive' | 
|  | 39 | +      ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} | 
|  | 40 | +      timeout: 90 | 
|  | 41 | +      secrets-env: EXECUTORCH_HF_TOKEN | 
|  | 42 | +      upload-artifact: voxtral-metal-export | 
|  | 43 | +      script: | | 
|  | 44 | +        set -eux | 
|  | 45 | +
 | 
|  | 46 | +        echo "::group::Setup Huggingface" | 
|  | 47 | +        ${CONDA_RUN} pip install -U "huggingface_hub[cli]" accelerate | 
|  | 48 | +        ${CONDA_RUN} huggingface-cli login --token $SECRET_EXECUTORCH_HF_TOKEN | 
|  | 49 | +        echo "::endgroup::" | 
|  | 50 | +
 | 
|  | 51 | +        echo "::group::Setup Optimum-ExecuTorch" | 
|  | 52 | +        OPTIMUM_ET_VERSION=$(cat .ci/docker/ci_commit_pins/optimum-executorch.txt) | 
|  | 53 | +        echo "Using optimum-executorch version: ${OPTIMUM_ET_VERSION}" | 
|  | 54 | +        ${CONDA_RUN} pip install git+https://github.com/huggingface/optimum-executorch.git@${OPTIMUM_ET_VERSION} | 
|  | 55 | +        ${CONDA_RUN} pip install mistral-common librosa | 
|  | 56 | +        echo "::endgroup::" | 
|  | 57 | +
 | 
|  | 58 | +        echo "::group::Setup ExecuTorch" | 
|  | 59 | +        PYTHON_EXECUTABLE=python ${CONDA_RUN} ./install_executorch.sh | 
|  | 60 | +        echo "::endgroup::" | 
|  | 61 | +
 | 
|  | 62 | +        echo "::group::Pip List" | 
|  | 63 | +        ${CONDA_RUN} pip list | 
|  | 64 | +        echo "::endgroup::" | 
|  | 65 | +
 | 
|  | 66 | +        echo "::group::Export Voxtral" | 
|  | 67 | +        ${CONDA_RUN} optimum-cli export executorch \ | 
|  | 68 | +            --model "mistralai/Voxtral-Mini-3B-2507" \ | 
|  | 69 | +            --task "multimodal-text-to-text" \ | 
|  | 70 | +            --recipe "metal" \ | 
|  | 71 | +            --dtype bfloat16 \ | 
|  | 72 | +            --max_seq_len 1024 \ | 
|  | 73 | +            --output_dir ./ | 
|  | 74 | +        ${CONDA_RUN} python -m executorch.extension.audio.mel_spectrogram \ | 
|  | 75 | +            --feature_size 128 \ | 
|  | 76 | +            --stack_output \ | 
|  | 77 | +            --max_audio_len 300 \ | 
|  | 78 | +            --output_file voxtral_preprocessor.pte | 
|  | 79 | +
 | 
|  | 80 | +        test -f model.pte | 
|  | 81 | +        test -f aoti_metal_blob.ptd | 
|  | 82 | +        test -f voxtral_preprocessor.pte | 
|  | 83 | +        echo "::endgroup::" | 
|  | 84 | +
 | 
|  | 85 | +        echo "::group::Store Voxtral Artifacts" | 
|  | 86 | +        mkdir -p "${RUNNER_ARTIFACT_DIR}" | 
|  | 87 | +        cp model.pte "${RUNNER_ARTIFACT_DIR}/" | 
|  | 88 | +        cp aoti_metal_blob.ptd "${RUNNER_ARTIFACT_DIR}/" | 
|  | 89 | +        cp voxtral_preprocessor.pte "${RUNNER_ARTIFACT_DIR}/" | 
|  | 90 | +        ls -al "${RUNNER_ARTIFACT_DIR}" | 
|  | 91 | +        echo "::endgroup::" | 
|  | 92 | +
 | 
|  | 93 | +  test-voxtral-metal-e2e: | 
|  | 94 | +    name: test-voxtral-metal-e2e | 
|  | 95 | +    needs: export-voxtral-metal-artifact | 
|  | 96 | +    uses: pytorch/test-infra/.github/workflows/macos_job.yml@main | 
|  | 97 | +    with: | 
|  | 98 | +      runner: macos-m2-stable | 
|  | 99 | +      python-version: '3.11' | 
|  | 100 | +      submodules: 'recursive' | 
|  | 101 | +      ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} | 
|  | 102 | +      timeout: 90 | 
|  | 103 | +      download-artifact: voxtral-metal-export | 
|  | 104 | +      script: | | 
|  | 105 | +        set -eux | 
|  | 106 | +
 | 
|  | 107 | +        echo "::group::Print machine info" | 
|  | 108 | +        uname -a | 
|  | 109 | +        if [ $(uname -s) == Darwin ]; then | 
|  | 110 | +          sw_vers | 
|  | 111 | +          # Print RAM in GB | 
|  | 112 | +          RAM_BYTES=$(sysctl -n hw.memsize) | 
|  | 113 | +          RAM_GB=$(echo "scale=2; $RAM_BYTES/1024/1024/1024" | bc) | 
|  | 114 | +          echo "Available RAM (GB): $RAM_GB" | 
|  | 115 | +          sysctl machdep.cpu.brand_string | 
|  | 116 | +          sysctl machdep.cpu.core_count | 
|  | 117 | +          # Print number of GPU cores (Apple Silicon) | 
|  | 118 | +          if command -v system_profiler &> /dev/null; then | 
|  | 119 | +            GPU_CORES=$(system_profiler SPDisplaysDataType | awk '/Total Number of Cores/ {print $5; exit}') | 
|  | 120 | +            if [ -z "$GPU_CORES" ]; then | 
|  | 121 | +              # Fallback: try to parse "Core Count" from Apple GPU section | 
|  | 122 | +              GPU_CORES=$(system_profiler SPDisplaysDataType | awk '/Core Count/ {print $3; exit}') | 
|  | 123 | +            fi | 
|  | 124 | +            echo "GPU Cores: ${GPU_CORES:-Unknown}" | 
|  | 125 | +          else | 
|  | 126 | +            echo "system_profiler not available, cannot determine GPU cores." | 
|  | 127 | +          fi | 
|  | 128 | +        fi | 
|  | 129 | +        echo "::endgroup::" | 
|  | 130 | +
 | 
|  | 131 | +        echo "::group::Setup ExecuTorch Requirements" | 
|  | 132 | +        CMAKE_ARGS="-DEXECUTORCH_BUILD_METAL=ON" ${CONDA_RUN} --no-capture-output ./install_requirements.sh | 
|  | 133 | +        echo "::endgroup::" | 
|  | 134 | +
 | 
|  | 135 | +        echo "::group::Pip List" | 
|  | 136 | +        ${CONDA_RUN} pip list | 
|  | 137 | +        echo "::endgroup::" | 
|  | 138 | +
 | 
|  | 139 | +        echo "::group::Prepare Voxtral Artifacts" | 
|  | 140 | +        cp "${RUNNER_ARTIFACT_DIR}/model.pte" . | 
|  | 141 | +        cp "${RUNNER_ARTIFACT_DIR}/aoti_metal_blob.ptd" . | 
|  | 142 | +        cp "${RUNNER_ARTIFACT_DIR}/voxtral_preprocessor.pte" . | 
|  | 143 | +        TOKENIZER_URL="https://huggingface.co/mistralai/Voxtral-Mini-3B-2507/resolve/main/tekken.json" | 
|  | 144 | +        curl -L $TOKENIZER_URL -o tekken.json | 
|  | 145 | +        ls -al model.pte aoti_metal_blob.ptd voxtral_preprocessor.pte tekken.json | 
|  | 146 | +        echo "::endgroup::" | 
|  | 147 | +
 | 
|  | 148 | +        echo "::group::Create Test Audio File" | 
|  | 149 | +        say -o call_samantha_hall.aiff "Call Samantha Hall" | 
|  | 150 | +        afconvert -f WAVE -d LEI16 call_samantha_hall.aiff call_samantha_hall.wav | 
|  | 151 | +        echo "::endgroup::" | 
|  | 152 | +
 | 
|  | 153 | +        echo "::group::Build Voxtral Runner" | 
|  | 154 | +        ${CONDA_RUN} cmake --preset llm \ | 
|  | 155 | +              -DEXECUTORCH_BUILD_METAL=ON \ | 
|  | 156 | +              -DCMAKE_INSTALL_PREFIX=cmake-out \ | 
|  | 157 | +              -DCMAKE_BUILD_TYPE=Release \ | 
|  | 158 | +              -Bcmake-out -S. | 
|  | 159 | +        ${CONDA_RUN} cmake --build cmake-out -j$(( $(sysctl -n hw.ncpu) - 1 )) --target install --config Release | 
|  | 160 | +
 | 
|  | 161 | +        ${CONDA_RUN} cmake -DEXECUTORCH_BUILD_METAL=ON \ | 
|  | 162 | +              -DCMAKE_BUILD_TYPE=Release \ | 
|  | 163 | +              -Sexamples/models/voxtral \ | 
|  | 164 | +              -Bcmake-out/examples/models/voxtral/ | 
|  | 165 | +        ${CONDA_RUN} cmake --build cmake-out/examples/models/voxtral --target voxtral_runner --config Release | 
|  | 166 | +        echo "::endgroup::" | 
|  | 167 | +
 | 
|  | 168 | +        echo "::group::Run Voxtral Runner" | 
|  | 169 | +        set +e | 
|  | 170 | +        OUTPUT=$(cmake-out/examples/models/voxtral/voxtral_runner \ | 
|  | 171 | +              --model_path model.pte \ | 
|  | 172 | +              --data_path aoti_metal_blob.ptd \ | 
|  | 173 | +              --tokenizer_path tekken.json \ | 
|  | 174 | +              --audio_path call_samantha_hall.wav \ | 
|  | 175 | +              --processor_path voxtral_preprocessor.pte \ | 
|  | 176 | +              --temperature 0 2>&1) | 
|  | 177 | +        EXIT_CODE=$? | 
|  | 178 | +        set -e | 
|  | 179 | +
 | 
|  | 180 | +        echo "$OUTPUT" | 
|  | 181 | +
 | 
|  | 182 | +        if ! echo "$OUTPUT" | grep -iq "Samantha"; then | 
|  | 183 | +          echo "Expected output 'Samantha' not found in output" | 
|  | 184 | +          exit 1 | 
|  | 185 | +        fi | 
|  | 186 | +
 | 
|  | 187 | +        if [ $EXIT_CODE -ne 0 ]; then | 
|  | 188 | +          echo "Unexpected exit code: $EXIT_CODE" | 
|  | 189 | +          exit $EXIT_CODE | 
|  | 190 | +        fi | 
|  | 191 | +        echo "::endgroup::" | 
0 commit comments