Skip to content

Fuzz (nightly exploratory) #47

Fuzz (nightly exploratory)

Fuzz (nightly exploratory) #47

Workflow file for this run

name: Fuzz (nightly exploratory)
# Exploratory libFuzzer run, NOT a PR gate. Each int8 kernel harness mutates
# for MAX_TIME seconds under ASan+UBSan. A new crash uploads the minimized
# reproducer as an artifact; triage then archives it into fuzz/corpus/<kernel>/
# so the deterministic fuzz-replay gate (analysis.yml) guards the fix forever.
#
# Targets run as a matrix so wall-clock is one harness, not the sum.
#
# The working corpus accumulates across nights via actions/cache: each run
# restores the previous night's corpus (falling back to the committed seeds on
# a cache miss) and saves the grown corpus afterwards -- including on crash
# nights, so the input that reached the crash is not lost. Without this, every
# night re-derives the same shallow coverage from the committed seeds inside
# its MAX_TIME budget instead of pushing the frontier deeper. The cache is
# runner-side state only: the PR replay gate replays just the *committed*
# corpus and never sees it. Periodically fold the accumulated corpus back into
# fuzz/corpus/ with `./fuzz_<kernel> -merge=1 corpus/<kernel> <accumulated>`
# and commit, so the replay gate inherits the coverage. Deleting the cache key
# is always safe -- it regrows from the committed seeds.
on:
schedule:
- cron: "0 5 * * *" # 05:00 UTC daily
workflow_dispatch:
inputs:
max_time:
description: "Seconds per target"
default: "300"
permissions:
contents: read
jobs:
fuzz:
name: Fuzz ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [fuzz_qlayernorm, fuzz_qsoftmax, fuzz_qfft1d, fuzz_requantize,
fuzz_qattention_softmax, fuzz_qmha, fuzz_qlstm, fuzz_qgru,
fuzz_qcfc, fuzz_simd_avx2_diff]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Install Clang
uses: ./.github/actions/apt-install
with:
packages: clang
- name: Derive kernel name
id: kernel
run: |
k="${{ matrix.target }}"
echo "kernel=${k#fuzz_}" >> "$GITHUB_OUTPUT"
# Key is unique per run so the post-run save never collides; restore
# falls back to the newest previous night via the prefix restore-key.
# The restore overlays the checked-out committed seeds, so a cache miss
# just means starting from the seeds alone.
- name: Restore accumulated corpus
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: fuzz/corpus/${{ steps.kernel.outputs.kernel }}
key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }}
restore-keys: |
fuzz-corpus-${{ matrix.target }}-
- name: Build ${{ matrix.target }}
run: make -C fuzz ${{ matrix.target }} FUZZ_CXX=clang++
- name: Fuzz ${{ matrix.target }} (time-boxed, ASan+UBSan)
run: |
cd fuzz
k="${{ steps.kernel.outputs.kernel }}"
mkdir -p "corpus/$k"
./${{ matrix.target }} -max_total_time="${{ github.event.inputs.max_time || 300 }}" \
-print_final_stats=1 "corpus/$k"
# if: always() -- a crash night must still save the grown corpus; the
# units leading up to the crash are the most valuable ones.
- name: Save accumulated corpus
if: always()
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: fuzz/corpus/${{ steps.kernel.outputs.kernel }}
key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }}
- name: Upload crash reproducers
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: crash-${{ matrix.target }}
path: |
fuzz/crash-*
fuzz/timeout-*
fuzz/leak-*
fuzz/oom-*
if-no-files-found: ignore