456 lines
14 KiB
YAML
456 lines
14 KiB
YAML
# Edge-Net Model Optimization Workflow
|
|
#
|
|
# Automatically optimizes ONNX models when a new release is created
|
|
# - Quantizes models to INT4 and INT8
|
|
# - Uploads to Google Cloud Storage
|
|
# - Optionally pins to IPFS
|
|
# - Updates registry.json
|
|
|
|
name: Edge-Net Model Optimization
|
|
|
|
on:
|
|
# Trigger on release creation
|
|
release:
|
|
types: [published]
|
|
|
|
# Manual trigger with model selection
|
|
workflow_dispatch:
|
|
inputs:
|
|
models:
|
|
description: 'Comma-separated list of model IDs to optimize (or "all")'
|
|
required: true
|
|
default: 'minilm-l6,distilgpt2'
|
|
quantization:
|
|
description: 'Quantization types to generate'
|
|
required: true
|
|
default: 'int4,int8'
|
|
type: choice
|
|
options:
|
|
- 'int4,int8'
|
|
- 'int4'
|
|
- 'int8'
|
|
- 'fp16'
|
|
- 'int4,int8,fp16'
|
|
upload_ipfs:
|
|
description: 'Upload to IPFS'
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
upload_gcs:
|
|
description: 'Upload to Google Cloud Storage'
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
|
|
env:
|
|
NODE_VERSION: '20'
|
|
PYTHON_VERSION: '3.11'
|
|
GCS_BUCKET: ruvector-models
|
|
MODELS_DIR: examples/edge-net/pkg/models
|
|
ONNX_CACHE_DIR: /tmp/.ruvector/models/onnx
|
|
|
|
jobs:
|
|
# Determine which models to process
|
|
prepare:
|
|
name: Prepare Model List
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
|
quantizations: ${{ steps.set-matrix.outputs.quantizations }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Set model matrix
|
|
id: set-matrix
|
|
run: |
|
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
MODELS="${{ github.event.inputs.models }}"
|
|
QUANT="${{ github.event.inputs.quantization }}"
|
|
else
|
|
# On release, optimize tier 1 models by default
|
|
MODELS="minilm-l6,e5-small,distilgpt2,tinystories,starcoder-tiny"
|
|
QUANT="int4,int8"
|
|
fi
|
|
|
|
if [ "$MODELS" = "all" ]; then
|
|
MODELS=$(jq -r '.models | keys | join(",")' ${{ env.MODELS_DIR }}/registry.json)
|
|
fi
|
|
|
|
# Convert to JSON array
|
|
MODELS_JSON=$(echo "$MODELS" | tr ',' '\n' | jq -R . | jq -s .)
|
|
QUANT_JSON=$(echo "$QUANT" | tr ',' '\n' | jq -R . | jq -s .)
|
|
|
|
echo "matrix=$MODELS_JSON" >> $GITHUB_OUTPUT
|
|
echo "quantizations=$QUANT_JSON" >> $GITHUB_OUTPUT
|
|
|
|
echo "Models to process: $MODELS"
|
|
echo "Quantizations: $QUANT"
|
|
|
|
# Optimize models in parallel
|
|
optimize:
|
|
name: Optimize ${{ matrix.model }}
|
|
runs-on: ubuntu-latest
|
|
needs: prepare
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
model: ${{ fromJson(needs.prepare.outputs.matrix) }}
|
|
quantization: ${{ fromJson(needs.prepare.outputs.quantizations) }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: 'npm'
|
|
cache-dependency-path: ${{ env.MODELS_DIR }}/../package-lock.json
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
cache: 'pip'
|
|
|
|
- name: Install dependencies
|
|
working-directory: ${{ env.MODELS_DIR }}/..
|
|
run: |
|
|
npm ci
|
|
pip install onnxruntime onnx onnxruntime-tools
|
|
|
|
- name: Cache ONNX models
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ${{ env.ONNX_CACHE_DIR }}
|
|
key: onnx-${{ matrix.model }}-${{ hashFiles('**/registry.json') }}
|
|
restore-keys: |
|
|
onnx-${{ matrix.model }}-
|
|
onnx-
|
|
|
|
- name: Download model
|
|
working-directory: ${{ env.MODELS_DIR }}/..
|
|
run: |
|
|
echo "Downloading model: ${{ matrix.model }}"
|
|
node models/models-cli.js download ${{ matrix.model }} --verify
|
|
env:
|
|
ONNX_CACHE_DIR: ${{ env.ONNX_CACHE_DIR }}
|
|
|
|
- name: Optimize model
|
|
working-directory: ${{ env.MODELS_DIR }}/..
|
|
run: |
|
|
echo "Optimizing ${{ matrix.model }} with ${{ matrix.quantization }}"
|
|
|
|
# Create output directory
|
|
OUTPUT_DIR="optimized/${{ matrix.model }}/${{ matrix.quantization }}"
|
|
mkdir -p "$OUTPUT_DIR"
|
|
|
|
# Run optimization via CLI
|
|
node models/models-cli.js optimize ${{ matrix.model }} \
|
|
--quantize ${{ matrix.quantization }} \
|
|
--output "$OUTPUT_DIR"
|
|
|
|
# List output files
|
|
echo "Optimized files:"
|
|
ls -lh "$OUTPUT_DIR"
|
|
env:
|
|
ONNX_CACHE_DIR: ${{ env.ONNX_CACHE_DIR }}
|
|
|
|
- name: Calculate checksums
|
|
working-directory: ${{ env.MODELS_DIR }}/..
|
|
run: |
|
|
OUTPUT_DIR="optimized/${{ matrix.model }}/${{ matrix.quantization }}"
|
|
|
|
# Generate checksums
|
|
cd "$OUTPUT_DIR"
|
|
sha256sum *.onnx > checksums.sha256 2>/dev/null || true
|
|
|
|
echo "Checksums:"
|
|
cat checksums.sha256 || echo "No ONNX files found"
|
|
|
|
- name: Upload optimized model artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: model-${{ matrix.model }}-${{ matrix.quantization }}
|
|
path: ${{ env.MODELS_DIR }}/../optimized/${{ matrix.model }}/${{ matrix.quantization }}
|
|
retention-days: 7
|
|
|
|
# Upload to Google Cloud Storage
|
|
upload-gcs:
|
|
name: Upload to GCS
|
|
runs-on: ubuntu-latest
|
|
needs: [prepare, optimize]
|
|
if: github.event.inputs.upload_gcs != 'false'
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: artifacts
|
|
|
|
- name: Authenticate to GCP
|
|
uses: google-github-actions/auth@v2
|
|
with:
|
|
credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}
|
|
|
|
- name: Setup gcloud CLI
|
|
uses: google-github-actions/setup-gcloud@v2
|
|
with:
|
|
project_id: ${{ secrets.GCP_PROJECT_ID }}
|
|
|
|
- name: Upload to GCS
|
|
run: |
|
|
echo "Uploading models to gs://${{ env.GCS_BUCKET }}"
|
|
|
|
for model_dir in artifacts/model-*; do
|
|
if [ -d "$model_dir" ]; then
|
|
MODEL_NAME=$(basename "$model_dir" | sed 's/model-//' | sed 's/-int[48]//' | sed 's/-fp16//')
|
|
QUANT=$(basename "$model_dir" | grep -oE '(int4|int8|fp16)')
|
|
|
|
echo "Uploading $MODEL_NAME ($QUANT)"
|
|
gsutil -m cp -r "$model_dir/*" "gs://${{ env.GCS_BUCKET }}/$MODEL_NAME/$QUANT/"
|
|
fi
|
|
done
|
|
|
|
echo "Upload complete!"
|
|
gsutil ls -l "gs://${{ env.GCS_BUCKET }}/"
|
|
|
|
# Upload to IPFS via Pinata
|
|
upload-ipfs:
|
|
name: Upload to IPFS
|
|
runs-on: ubuntu-latest
|
|
needs: [prepare, optimize]
|
|
if: github.event.inputs.upload_ipfs == 'true'
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: artifacts
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Pin to IPFS via Pinata
|
|
run: |
|
|
echo "Pinning models to IPFS via Pinata"
|
|
|
|
for model_dir in artifacts/model-*; do
|
|
if [ -d "$model_dir" ]; then
|
|
MODEL_NAME=$(basename "$model_dir")
|
|
|
|
echo "Pinning $MODEL_NAME"
|
|
|
|
# Create a tar archive for the model
|
|
tar -czf "${MODEL_NAME}.tar.gz" -C "$model_dir" .
|
|
|
|
# Pin to Pinata
|
|
RESPONSE=$(curl -s -X POST \
|
|
-H "pinata_api_key: ${{ secrets.PINATA_API_KEY }}" \
|
|
-H "pinata_secret_api_key: ${{ secrets.PINATA_SECRET }}" \
|
|
-F "file=@${MODEL_NAME}.tar.gz" \
|
|
-F "pinataMetadata={\"name\":\"${MODEL_NAME}\",\"keyvalues\":{\"type\":\"onnx-model\",\"repo\":\"ruvector\"}}" \
|
|
"https://api.pinata.cloud/pinning/pinFileToIPFS")
|
|
|
|
CID=$(echo "$RESPONSE" | jq -r '.IpfsHash')
|
|
echo " IPFS CID: $CID"
|
|
echo " Gateway URL: https://ipfs.io/ipfs/$CID"
|
|
|
|
# Save CID for registry update
|
|
echo "$MODEL_NAME=$CID" >> ipfs_cids.txt
|
|
fi
|
|
done
|
|
env:
|
|
PINATA_API_KEY: ${{ secrets.PINATA_API_KEY }}
|
|
PINATA_SECRET: ${{ secrets.PINATA_SECRET }}
|
|
|
|
- name: Upload IPFS CIDs
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ipfs-cids
|
|
path: ipfs_cids.txt
|
|
retention-days: 30
|
|
|
|
# Update registry with new model information
|
|
update-registry:
|
|
name: Update Registry
|
|
runs-on: ubuntu-latest
|
|
needs: [optimize, upload-gcs]
|
|
if: always() && needs.optimize.result == 'success'
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ github.head_ref || github.ref_name }}
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: artifacts
|
|
|
|
- name: Update registry.json
|
|
working-directory: ${{ env.MODELS_DIR }}
|
|
run: |
|
|
echo "Updating registry with new model artifacts"
|
|
|
|
# Load current registry
|
|
REGISTRY=$(cat registry.json)
|
|
|
|
for model_dir in ../../../artifacts/model-*; do
|
|
if [ -d "$model_dir" ]; then
|
|
MODEL_INFO=$(basename "$model_dir" | sed 's/model-//')
|
|
MODEL_NAME=$(echo "$MODEL_INFO" | sed 's/-int[48]$//' | sed 's/-fp16$//')
|
|
QUANT=$(echo "$MODEL_INFO" | grep -oE '(int4|int8|fp16)' || echo "original")
|
|
|
|
echo "Processing: $MODEL_NAME ($QUANT)"
|
|
|
|
# Get file sizes
|
|
TOTAL_SIZE=0
|
|
FILES_JSON="[]"
|
|
for file in "$model_dir"/*.onnx; do
|
|
if [ -f "$file" ]; then
|
|
FILENAME=$(basename "$file")
|
|
SIZE=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file")
|
|
HASH=$(sha256sum "$file" | cut -d' ' -f1)
|
|
|
|
FILES_JSON=$(echo "$FILES_JSON" | jq --arg f "$FILENAME" --argjson s "$SIZE" --arg h "$HASH" \
|
|
'. + [{"file": $f, "size": $s, "sha256": $h}]')
|
|
|
|
TOTAL_SIZE=$((TOTAL_SIZE + SIZE))
|
|
fi
|
|
done
|
|
|
|
# Update registry
|
|
REGISTRY=$(echo "$REGISTRY" | jq \
|
|
--arg model "$MODEL_NAME" \
|
|
--arg quant "$QUANT" \
|
|
--argjson files "$FILES_JSON" \
|
|
--arg size "$TOTAL_SIZE" \
|
|
--arg updated "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
--arg gcs "https://storage.googleapis.com/${{ env.GCS_BUCKET }}/$MODEL_NAME/$QUANT" \
|
|
'.models[$model].artifacts[$quant] = {
|
|
"files": $files,
|
|
"totalSize": ($size | tonumber),
|
|
"gcsUrl": $gcs,
|
|
"updated": $updated
|
|
} | .updated = $updated')
|
|
fi
|
|
done
|
|
|
|
# Save updated registry
|
|
echo "$REGISTRY" | jq '.' > registry.json
|
|
|
|
echo "Registry updated:"
|
|
jq '.models | to_entries[] | "\(.key): \(.value.artifacts | keys)"' registry.json
|
|
|
|
- name: Commit registry updates
|
|
run: |
|
|
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
|
git config --local user.name "github-actions[bot]"
|
|
|
|
git add ${{ env.MODELS_DIR }}/registry.json
|
|
|
|
if git diff --staged --quiet; then
|
|
echo "No changes to commit"
|
|
else
|
|
git commit -m "chore(models): update registry with optimized models
|
|
|
|
Updated models: ${{ needs.prepare.outputs.matrix }}
|
|
Quantizations: ${{ needs.prepare.outputs.quantizations }}
|
|
|
|
[skip ci]"
|
|
git push
|
|
fi
|
|
|
|
# Run benchmarks on optimized models
|
|
benchmark:
|
|
name: Benchmark Models
|
|
runs-on: ubuntu-latest
|
|
needs: [optimize]
|
|
if: github.event_name == 'release'
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: 'npm'
|
|
cache-dependency-path: ${{ env.MODELS_DIR }}/../package-lock.json
|
|
|
|
- name: Install dependencies
|
|
working-directory: ${{ env.MODELS_DIR }}/..
|
|
run: npm ci
|
|
|
|
- name: Download artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: artifacts
|
|
|
|
- name: Run benchmarks
|
|
working-directory: ${{ env.MODELS_DIR }}/..
|
|
run: |
|
|
echo "Running model benchmarks"
|
|
|
|
RESULTS="[]"
|
|
|
|
for model_dir in ../artifacts/model-*; do
|
|
if [ -d "$model_dir" ]; then
|
|
MODEL_INFO=$(basename "$model_dir" | sed 's/model-//')
|
|
MODEL_NAME=$(echo "$MODEL_INFO" | sed 's/-int[48]$//' | sed 's/-fp16$//')
|
|
|
|
echo "Benchmarking: $MODEL_NAME"
|
|
|
|
# Run benchmark
|
|
RESULT=$(node models/models-cli.js benchmark "$MODEL_NAME" \
|
|
--iterations 5 \
|
|
--warmup 1 \
|
|
--output /tmp/bench_${MODEL_NAME}.json 2>/dev/null || echo "{}")
|
|
|
|
if [ -f "/tmp/bench_${MODEL_NAME}.json" ]; then
|
|
RESULTS=$(echo "$RESULTS" | jq --slurpfile r "/tmp/bench_${MODEL_NAME}.json" '. + $r')
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Save all results
|
|
echo "$RESULTS" | jq '.' > benchmark_results.json
|
|
|
|
echo "Benchmark Results:"
|
|
jq '.' benchmark_results.json
|
|
|
|
- name: Upload benchmark results
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: benchmark-results
|
|
path: ${{ env.MODELS_DIR }}/../benchmark_results.json
|
|
retention-days: 30
|
|
|
|
- name: Comment on release
|
|
if: github.event_name == 'release'
|
|
uses: peter-evans/create-or-update-comment@v4
|
|
with:
|
|
issue-number: ${{ github.event.release.id }}
|
|
body: |
|
|
## Model Optimization Complete
|
|
|
|
Optimized models have been processed and uploaded.
|
|
|
|
**Models processed:**
|
|
${{ needs.prepare.outputs.matrix }}
|
|
|
|
**Quantizations:**
|
|
${{ needs.prepare.outputs.quantizations }}
|
|
|
|
**Artifacts:**
|
|
- GCS: `gs://${{ env.GCS_BUCKET }}/`
|
|
- Benchmark results available in workflow artifacts
|