diff --git a/.github/workflows/studio-inference-smoke.yml b/.github/workflows/studio-inference-smoke.yml index 3a8d68f88f..000974f715 100644 --- a/.github/workflows/studio-inference-smoke.yml +++ b/.github/workflows/studio-inference-smoke.yml @@ -289,11 +289,19 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 25 env: + # Tool calling is the highest-volume GGUF in this workflow + # (Qwen3.5-2B at IQ3_XXS = ~890 MiB). Caching HF_HOME would + # store xet chunks + blobs + snapshots = ~4 GiB compressed -- + # 4-5x file-size inflation, dominated by xet chunks. Use main's + # `--local-dir gguf-cache` pattern to cache the flat .gguf only. + # Studio's /api/inference/load accepts either a HF repo (which + # uses HF_HOME) or an absolute file path; passing the absolute + # path keeps the test off HF_HOME entirely so the cache size + # tracks the GGUF file 1:1. The OpenAI/Anth and JSON+images + # jobs still cover the gguf_variant resolution path. GGUF_REPO: unsloth/Qwen3.5-2B-GGUF - GGUF_VARIANT: UD-IQ3_XXS GGUF_FILE: Qwen3.5-2B-UD-IQ3_XXS.gguf STUDIO_PORT: '18889' - HF_HOME: ${{ github.workspace }}/hf-cache steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 @@ -314,20 +322,20 @@ jobs: python-version: '3.12' cache: 'pip' - - name: Cache HF_HOME for ${{ env.GGUF_REPO }} - id: cache-hf + - name: Cache GGUF model file + id: cache-gguf uses: actions/cache@v4 with: - path: hf-cache - key: ${{ runner.os }}-hf-${{ env.GGUF_REPO }}-${{ env.GGUF_VARIANT }}-v1 + path: gguf-cache + key: ${{ runner.os }}-gguf-${{ env.GGUF_REPO }}-${{ env.GGUF_FILE }}-v1 - - name: Prime HF_HOME with the GGUF - if: steps.cache-hf.outputs.cache-hit != 'true' + - name: Download GGUF if cache miss + if: steps.cache-gguf.outputs.cache-hit != 'true' run: | python -m pip install --upgrade huggingface_hub hf_transfer - mkdir -p hf-cache + mkdir -p gguf-cache HF_HUB_ENABLE_HF_TRANSFER=1 \ - hf download "$GGUF_REPO" "$GGUF_FILE" + hf download "$GGUF_REPO" "$GGUF_FILE" --local-dir gguf-cache - name: Install Studio (--local, --no-torch) env: @@ -376,10 +384,12 @@ jobs: -H 'content-type: application/json' \ -d "{\"username\":\"unsloth\",\"password\":\"$NEW\"}" | jq -r .access_token) echo "API_KEY=$TOKEN" >> "$GITHUB_ENV" + GGUF_PATH="$GITHUB_WORKSPACE/gguf-cache/${GGUF_FILE}" + ls -lh "$GGUF_PATH" curl -fs -X POST "http://127.0.0.1:${STUDIO_PORT}/api/inference/load" \ -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \ --max-time 600 \ - -d "{\"model_path\":\"$GGUF_REPO\",\"gguf_variant\":\"$GGUF_VARIANT\",\"is_lora\":false,\"max_seq_length\":2048}" \ + -d "{\"model_path\":\"$GGUF_PATH\",\"is_lora\":false,\"max_seq_length\":2048}" \ | jq '{status, display_name}' - name: Tool calling, server-side tools, thinking on/off diff --git a/.github/workflows/studio-mac-inference-smoke.yml b/.github/workflows/studio-mac-inference-smoke.yml index d613507c89..20123663b5 100644 --- a/.github/workflows/studio-mac-inference-smoke.yml +++ b/.github/workflows/studio-mac-inference-smoke.yml @@ -291,16 +291,17 @@ jobs: runs-on: macos-14 timeout-minutes: 25 env: + # Tool calling is the highest-volume GGUF in this workflow + # (Qwen3.5-2B at Q4_K_XL = ~1.28 GiB on Mac, where IQ3_XXS + # collapses for tool-call grammar under Metal at temperature=0). + # Caching HF_HOME stores xet chunks + blobs + snapshots = ~4.6 + # GiB compressed -- 3.6x file-size inflation. Use main's + # `--local-dir gguf-cache` pattern to cache the flat .gguf only. + # The OpenAI/Anth and JSON+images jobs still cover the + # gguf_variant resolution path. GGUF_REPO: unsloth/Qwen3.5-2B-GGUF - # The Linux smoke uses UD-IQ3_XXS, but on Mac Metal that quant - # collapses for tool-call grammar at temperature=0 (model emits - # repeated padding tokens until max_tokens). UD-Q4_K_XL is the - # smallest published variant that produces well-formed tool - # calls + non-pathological text on M1. - GGUF_VARIANT: UD-Q4_K_XL GGUF_FILE: Qwen3.5-2B-UD-Q4_K_XL.gguf STUDIO_PORT: '18898' - HF_HOME: ${{ github.workspace }}/hf-cache steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 @@ -315,20 +316,20 @@ jobs: python-version: '3.12' cache: 'pip' - - name: Cache HF_HOME for ${{ env.GGUF_REPO }} - id: cache-hf + - name: Cache GGUF model file + id: cache-gguf uses: actions/cache@v4 with: - path: hf-cache - key: ${{ runner.os }}-hf-${{ env.GGUF_REPO }}-${{ env.GGUF_VARIANT }}-v1 + path: gguf-cache + key: ${{ runner.os }}-gguf-${{ env.GGUF_REPO }}-${{ env.GGUF_FILE }}-v1 - - name: Prime HF_HOME with the GGUF - if: steps.cache-hf.outputs.cache-hit != 'true' + - name: Download GGUF if cache miss + if: steps.cache-gguf.outputs.cache-hit != 'true' run: | python -m pip install --upgrade huggingface_hub hf_transfer - mkdir -p hf-cache + mkdir -p gguf-cache HF_HUB_ENABLE_HF_TRANSFER=1 \ - hf download "$GGUF_REPO" "$GGUF_FILE" + hf download "$GGUF_REPO" "$GGUF_FILE" --local-dir gguf-cache - name: Install Studio (--local, --no-torch) env: @@ -385,10 +386,12 @@ jobs: -H 'content-type: application/json' \ -d "{\"username\":\"unsloth\",\"password\":\"$NEW\"}" | jq -r .access_token) echo "API_KEY=$TOKEN" >> "$GITHUB_ENV" + GGUF_PATH="$GITHUB_WORKSPACE/gguf-cache/${GGUF_FILE}" + ls -lh "$GGUF_PATH" curl -fs -X POST "http://127.0.0.1:${STUDIO_PORT}/api/inference/load" \ -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \ --max-time 600 \ - -d "{\"model_path\":\"$GGUF_REPO\",\"gguf_variant\":\"$GGUF_VARIANT\",\"is_lora\":false,\"max_seq_length\":2048}" \ + -d "{\"model_path\":\"$GGUF_PATH\",\"is_lora\":false,\"max_seq_length\":2048}" \ | jq '{status, display_name}' - name: Tool calling, server-side tools, thinking on/off diff --git a/.github/workflows/studio-windows-inference-smoke.yml b/.github/workflows/studio-windows-inference-smoke.yml index 9a2fb46e62..98ccccc1c9 100644 --- a/.github/workflows/studio-windows-inference-smoke.yml +++ b/.github/workflows/studio-windows-inference-smoke.yml @@ -342,15 +342,19 @@ jobs: run: shell: bash env: + # Tool calling is the highest-volume GGUF in this workflow + # (Qwen3.5-2B at Q4_K_XL = ~1.28 GiB). The previous HF_HOME + # cache stored xet chunks + blobs + snapshots = ~4.7 GiB -- + # 3.7x file-size inflation, dominating the post-step upload + # (211 s on first run; subsequent runs hit the cache, but the + # one-time cost recurs every time the cache key bumps). Use + # main's `--local-dir gguf-cache` pattern: cache the flat .gguf + # only, pass an absolute path to Studio's /api/inference/load. + # The OpenAI/Anth and JSON+images jobs still cover the + # gguf_variant resolution path. GGUF_REPO: unsloth/Qwen3.5-2B-GGUF - # Mirror the Mac job's variant choice (UD-Q4_K_XL). On the - # CPU-only windows-latest runner the smaller IQ3_XXS quant - # behaves OK, but keeping parity with the Mac job means one HF - # asset shared across runners. - GGUF_VARIANT: UD-Q4_K_XL GGUF_FILE: Qwen3.5-2B-UD-Q4_K_XL.gguf STUDIO_PORT: '18898' - HF_HOME: ${{ github.workspace }}/hf-cache # Force UTF-8 for stdio (Windows defaults to cp1252; hf # download / Studio CLI print "✓" checkmarks and crash # otherwise). @@ -369,20 +373,20 @@ jobs: with: python-version: '3.12' - - name: Cache HF_HOME for ${{ env.GGUF_REPO }} - id: cache-hf + - name: Cache GGUF model file + id: cache-gguf uses: actions/cache@v4 with: - path: hf-cache - key: ${{ runner.os }}-hf-${{ env.GGUF_REPO }}-${{ env.GGUF_VARIANT }}-v1 + path: gguf-cache + key: ${{ runner.os }}-gguf-${{ env.GGUF_REPO }}-${{ env.GGUF_FILE }}-v1 - - name: Prime HF_HOME with the GGUF - if: steps.cache-hf.outputs.cache-hit != 'true' + - name: Download GGUF if cache miss + if: steps.cache-gguf.outputs.cache-hit != 'true' run: | python -m pip install --upgrade huggingface_hub hf_transfer - mkdir -p hf-cache + mkdir -p gguf-cache HF_HUB_ENABLE_HF_TRANSFER=1 \ - hf download "$GGUF_REPO" "$GGUF_FILE" + hf download "$GGUF_REPO" "$GGUF_FILE" --local-dir gguf-cache - name: Pre-install Windows tweaks (npm 11 + Defender exclusions) shell: pwsh @@ -506,10 +510,19 @@ jobs: -H 'content-type: application/json' \ -d "{\"username\":\"unsloth\",\"password\":\"$NEW\"}" | jq -r .access_token) echo "API_KEY=$TOKEN" >> "$GITHUB_ENV" + # GITHUB_WORKSPACE on windows-latest is a Windows path with + # backslashes ("D:\a\unsloth\unsloth"). Bash handles it as a + # raw string, but we cannot embed `\a` etc. in JSON without + # JSON-string-escaping every backslash. Replace `\` with `/` + # via bash parameter expansion -- pathlib.Path on Windows + # accepts forward slashes natively, so Studio's loader sees + # a normal path. + GGUF_PATH="${GITHUB_WORKSPACE//\\//}/gguf-cache/${GGUF_FILE}" + ls -lh "$GGUF_PATH" curl -fs -X POST "http://127.0.0.1:${STUDIO_PORT}/api/inference/load" \ -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \ --max-time 600 \ - -d "{\"model_path\":\"$GGUF_REPO\",\"gguf_variant\":\"$GGUF_VARIANT\",\"is_lora\":false,\"max_seq_length\":2048}" \ + -d "{\"model_path\":\"$GGUF_PATH\",\"is_lora\":false,\"max_seq_length\":2048}" \ | jq '{status, display_name}' - name: Tool calling, server-side tools, thinking on/off