forked from tinygrad/tinygrad
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48dd1d6543 | ||
|
|
a06023cd36 | ||
|
|
ac232bceb5 | ||
|
|
f19fbadce4 | ||
|
|
8eb762d6fa |
@@ -5,12 +5,11 @@ runs:
|
||||
steps:
|
||||
- name: Run process replay tests
|
||||
shell: bash
|
||||
if: env.CAPTURE_PROCESS_REPLAY == '1'
|
||||
run: |
|
||||
export PR_TITLE=$(jq -r .pull_request.title "$GITHUB_EVENT_PATH")
|
||||
export CURRENT_SHA=${{ github.event.pull_request && github.event.pull_request.head.sha || github.sha }}
|
||||
git fetch origin $CURRENT_SHA
|
||||
export COMMIT_MESSAGE=$(git show -s --format=%B "$CURRENT_SHA")
|
||||
export CURRENT_HEAD=$(git rev-parse HEAD)
|
||||
cp test/external/process_replay/process_replay.py ./process_replay.py && git fetch origin master && git -c advice.detachedHead=false checkout origin/master && CHECK_OOB=0 PYTHONPATH=. python3 process_replay.py
|
||||
cp test/external/process_replay/process_replay.py ./process_replay.py && git fetch origin master && git -c advice.detachedHead=false checkout origin/master && IGNORE_OOB=1 PYTHONPATH=. python3 process_replay.py
|
||||
git checkout $CURRENT_HEAD # restore to branch
|
||||
|
||||
@@ -4,13 +4,13 @@ inputs:
|
||||
python-version:
|
||||
description: 'Python version to use'
|
||||
required: false
|
||||
default: '' # if you don't set a version, the native python version will be used
|
||||
default: '3.12'
|
||||
key:
|
||||
description: 'Key for the python cache'
|
||||
required: false
|
||||
default: '' # if you don't set a key, it doesn't cache
|
||||
deps:
|
||||
description: 'Extra dependency groups (space separated)'
|
||||
description: 'Extra dependency groups (comma separated)'
|
||||
required: false
|
||||
default: ''
|
||||
pydeps:
|
||||
@@ -41,91 +41,74 @@ inputs:
|
||||
description: "Install LLVM?"
|
||||
required: false
|
||||
default: 'false'
|
||||
tinydreno:
|
||||
description: "Install tinydreno"
|
||||
required: false
|
||||
default: 'false'
|
||||
qemu:
|
||||
description: "Install qemu"
|
||||
mesa:
|
||||
description: "Install mesa"
|
||||
required: false
|
||||
default: 'false'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup environment
|
||||
shell: bash
|
||||
run: |
|
||||
echo "UV_CACHE_DIR=/tmp/.uv-cache" >> "$GITHUB_ENV"
|
||||
echo "OMP_NUM_THREADS=1" >> "$GITHUB_ENV"
|
||||
# no buffers should be over 300MB in CI
|
||||
echo "MAX_BUFFER_SIZE=300000000" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
||||
with:
|
||||
enable-cache: 'false' # see below for manual caching
|
||||
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@v6
|
||||
if: inputs.python-version != ''
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
|
||||
# **** Caching packages ****
|
||||
|
||||
- name: Cache Python packages (PR)
|
||||
if: github.event_name == 'pull_request'
|
||||
id: restore-venv-pr
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: /tmp/.uv-cache
|
||||
key: uv-${{ runner.os }}-${{ runner.arch }}-python-${{ inputs.python-version }}-${{ inputs.deps }}-${{ inputs.pydeps }}-${{ env.CACHE_VERSION }}
|
||||
- name: Cache Python packages
|
||||
if: github.event_name != 'pull_request'
|
||||
id: restore-venv
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /tmp/.uv-cache
|
||||
key: uv-${{ runner.os }}-${{ runner.arch }}-python-${{ inputs.python-version }}-${{ inputs.deps }}-${{ inputs.pydeps }}-${{ env.CACHE_VERSION }}
|
||||
path: ${{ github.workspace }}/.venv
|
||||
key: venv-${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-${{ inputs.deps }}-${{ inputs.pydeps }}-${{ env.CACHE_VERSION }}
|
||||
|
||||
# **** Caching downloads ****
|
||||
|
||||
- name: Cache downloads (PR)
|
||||
if: inputs.key != '' && github.event_name == 'pull_request'
|
||||
uses: actions/cache/restore@v5
|
||||
- name: Cache downloads (Linux)
|
||||
if: inputs.key != '' && runner.os == 'Linux'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ runner.os == 'Linux' && '~/.cache/tinygrad/downloads/' || '~/Library/Caches/tinygrad/downloads/' }}
|
||||
path: ~/.cache/tinygrad/downloads/
|
||||
key: downloads-${{ github.job }}-${{ inputs.key }}-${{ env.CACHE_VERSION }}
|
||||
- name: Cache downloads
|
||||
if: inputs.key != '' && github.event_name != 'pull_request'
|
||||
uses: actions/cache@v5
|
||||
- name: Cache downloads (macOS)
|
||||
if: inputs.key != '' && runner.os == 'macOS'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ runner.os == 'Linux' && '~/.cache/tinygrad/downloads/' || '~/Library/Caches/tinygrad/downloads/' }}
|
||||
path: ~/Library/Caches/tinygrad/downloads/
|
||||
key: downloads-${{ github.job }}-${{ inputs.key }}-${{ env.CACHE_VERSION }}
|
||||
|
||||
# **** Python deps ****
|
||||
|
||||
- name: Install dependencies in venv (with extra)
|
||||
if: inputs.deps != ''
|
||||
if: inputs.deps != '' && steps.restore-venv.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
uv venv .venv
|
||||
DEPS="${{ inputs.deps }}"
|
||||
uv pip install --python .venv -e ".[${DEPS// /,}]" ${{ inputs.pydeps }} --torch-backend cpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/Triton-Nightly/pypi/simple/
|
||||
python -m venv .venv
|
||||
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||
source .venv/Scripts/activate
|
||||
else
|
||||
. .venv/bin/activate
|
||||
fi
|
||||
python -m pip install -e ".[${{ inputs.deps }}]" ${{ inputs.pydeps }} --extra-index-url https://download.pytorch.org/whl/cpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/Triton-Nightly/pypi/simple/
|
||||
- name: Install dependencies in venv (without extra)
|
||||
if: inputs.deps == ''
|
||||
if: inputs.deps == '' && steps.restore-venv.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
uv venv .venv
|
||||
uv pip install --python .venv -e . ${{ inputs.pydeps }}
|
||||
- name: Prune uv cache
|
||||
if: github.event_name != 'pull_request'
|
||||
shell: bash
|
||||
run: uv cache prune --ci
|
||||
- name: Configure venv
|
||||
python -m venv .venv
|
||||
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||
source .venv/Scripts/activate
|
||||
else
|
||||
. .venv/bin/activate
|
||||
fi
|
||||
python -m pip install -e . ${{ inputs.pydeps }}
|
||||
- name: Set up venv environment
|
||||
shell: bash
|
||||
run: |
|
||||
echo "VIRTUAL_ENV=${{ github.workspace }}/.venv" >> "$GITHUB_ENV"
|
||||
echo "OMP_NUM_THREADS=1" >> "$GITHUB_ENV"
|
||||
# no buffers should be over 300MB in CI
|
||||
echo "MAX_BUFFER_SIZE=300000000" >> "$GITHUB_ENV"
|
||||
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||
echo "${{ github.workspace }}/.venv/Scripts" >> "$GITHUB_PATH"
|
||||
else
|
||||
@@ -134,23 +117,27 @@ runs:
|
||||
|
||||
# ******************* apt *******************
|
||||
- name: Setup apt
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true')
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.cuda == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true')
|
||||
shell: bash
|
||||
run: |
|
||||
sudo mkdir -p /var/cache/apt/archives
|
||||
sudo chown -R $USER:$USER /var/cache/apt/archives
|
||||
|
||||
echo 'Acquire::GzipIndexes "true";' | sudo tee /etc/apt/apt.conf.d/gzip
|
||||
echo 'Acquire::http::Pipeline-Depth "5";' | sudo tee -a /etc/apt/apt.conf.d/99parallel
|
||||
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' | sudo tee -a /etc/apt/apt.conf.d/99keep-debs
|
||||
|
||||
- name: Add OpenCL Repo
|
||||
if: inputs.opencl == 'true' && runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: echo "deb [ allow-insecure=yes ] https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list
|
||||
|
||||
- name: Add AMD Repo (Linux)
|
||||
if: inputs.amd == 'true' && runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
wget https://repo.radeon.com/rocm/rocm.gpg.key -O - | gpg --dearmor | sudo tee /etc/apt/keyrings/rocm.gpg > /dev/null
|
||||
sudo tee /etc/apt/sources.list.d/rocm.list <<EOF
|
||||
deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/7.1 $(lsb_release -cs) main
|
||||
deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/6.2 $(lsb_release -cs) main
|
||||
EOF
|
||||
echo -e 'Package: *\nPin: release o=repo.radeon.com\nPin-Priority: 600' | sudo tee /etc/apt/preferences.d/rocm-pin-600
|
||||
|
||||
@@ -162,50 +149,48 @@ runs:
|
||||
echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-20 main" | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||
|
||||
- name: Compute Package List + Hash
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true')
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.cuda == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true')
|
||||
id: apt-pkgs
|
||||
shell: bash
|
||||
run: |
|
||||
pkgs=""
|
||||
# **** OpenCL ****
|
||||
if [[ "${{ inputs.opencl }}" == "true" ]]; then
|
||||
pkgs+=" ocl-icd-opencl-dev"
|
||||
pkgs+=" opencl-headers \
|
||||
intel-oneapi-runtime-openmp=2023.2.1-16 intel-oneapi-runtime-compilers-common=2023.2.1-16 intel-oneapi-runtime-compilers=2023.2.1-16 \
|
||||
intel-oneapi-runtime-dpcpp-sycl-opencl-cpu=2023.2.1-16 intel-oneapi-runtime-tbb-common=2021.10.0-49541 \
|
||||
intel-oneapi-runtime-tbb=2021.10.0-49541 intel-oneapi-runtime-opencl=2023.2.1-16"
|
||||
fi
|
||||
# **** AMD ****
|
||||
if [[ "${{ inputs.amd }}" == "true" ]]; then
|
||||
pkgs+=" comgr"
|
||||
pkgs+=" hsa-rocr comgr hsa-rocr-dev liburing-dev libibverbs-dev libc6-dev"
|
||||
fi
|
||||
# **** CUDA ****
|
||||
if [[ "${{ inputs.cuda }}" == "true" ]]; then
|
||||
pkgs+=" git g++ cmake ninja-build llvm-15-dev zlib1g-dev libglew-dev \
|
||||
flex bison libfl-dev libboost-thread-dev libboost-filesystem-dev nvidia-cuda-toolkit-gcc libzstd-dev"
|
||||
fi
|
||||
# **** WebGPU (dependencies for software-based vulkan) ****
|
||||
if [[ "${{ inputs.webgpu }}" == "true" ]]; then
|
||||
pkgs+=" mesa-vulkan-drivers"
|
||||
pkgs+=" libgl1 libglx-mesa0 libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers"
|
||||
fi
|
||||
# **** LLVM ****
|
||||
if [[ "${{ inputs.llvm }}" == "true" ]]; then
|
||||
pkgs+=" libllvm20 clang-20 lld-20"
|
||||
fi
|
||||
# **** QEMU ****
|
||||
if [[ "${{ inputs.qemu }}" == "true" ]]; then
|
||||
pkgs+=" qemu-user-static"
|
||||
fi
|
||||
|
||||
echo "pkgs=$pkgs" >> "$GITHUB_OUTPUT"
|
||||
echo "hash=$(echo -n "$pkgs" | sha256sum | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache apt (PR)
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true') && github.event_name == 'pull_request'
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: /var/cache/apt/archives/
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-apt-${{ steps.apt-pkgs.outputs.hash }}-${{ env.CACHE_VERSION }}
|
||||
- name: Cache apt
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true') && github.event_name != 'pull_request'
|
||||
uses: actions/cache@v5
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.cuda == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true')
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /var/cache/apt/archives/
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-apt-${{ steps.apt-pkgs.outputs.hash }}-${{ env.CACHE_VERSION }}
|
||||
key: ${{ runner.os }}-apt-${{ steps.apt-pkgs.outputs.hash }}-${{ env.CACHE_VERSION }}
|
||||
|
||||
- name: Run apt Update + Install
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true')
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.cuda == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true')
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt -qq update || true
|
||||
@@ -215,60 +200,92 @@ runs:
|
||||
sudo apt-get -y --allow-unauthenticated --no-install-recommends install ${{ steps.apt-pkgs.outputs.pkgs }}
|
||||
fi
|
||||
|
||||
sudo mkdir -p /var/cache/apt/archives
|
||||
sudo chown -R $USER:$USER /var/cache/apt/archives/
|
||||
|
||||
- name: Add clang to PATH (Linux)
|
||||
if: inputs.llvm == 'true' && runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: echo "/usr/lib/llvm-20/bin" >> "$GITHUB_PATH"
|
||||
|
||||
# **** AMD ****
|
||||
- name: Setup AMD (Linux)
|
||||
if: inputs.amd == 'true' && runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
cargo build --release --manifest-path ./extra/remu/Cargo.toml
|
||||
sudo ln -sf ${{ github.workspace }}/extra/remu/target/release/libremu.so /usr/local/lib/libremu.so
|
||||
sudo tee --append /etc/ld.so.conf.d/rocm.conf <<'EOF'
|
||||
/opt/rocm/lib
|
||||
/opt/rocm/lib64
|
||||
EOF
|
||||
sudo ldconfig
|
||||
- name: Setup AMD comgr (macOS)
|
||||
- name: Setup AMD comgr+remu (macOS)
|
||||
if: inputs.amd == 'true' && runner.os == 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
sudo mkdir -p /usr/local/lib
|
||||
curl -s -H "Authorization: token $GH_TOKEN" curl -s https://api.github.com/repos/tinygrad/amdcomgr_dylib/releases/latest | \
|
||||
curl -s -H "Authorization: token $GH_TOKEN" curl -s https://api.github.com/repos/nimlgen/amdcomgr_dylib/releases/latest | \
|
||||
jq -r '.assets[] | select(.name == "libamd_comgr.dylib").browser_download_url' | \
|
||||
sudo xargs curl -fL -o /usr/local/lib/libamd_comgr.dylib
|
||||
|
||||
# **** CUDA ****
|
||||
- name: Install CUDA
|
||||
if: inputs.cuda == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
sudo mkdir -p /usr/local/cuda/targets/x86_64-linux
|
||||
curl -fL https://developer.download.nvidia.com/compute/cuda/redist/cuda_nvrtc/linux-x86_64/cuda_nvrtc-linux-x86_64-11.5.119-archive.tar.xz \
|
||||
| sudo tar -xJ -C /usr/local/cuda/targets/x86_64-linux --strip-components=1
|
||||
echo /usr/local/cuda/targets/x86_64-linux/lib | sudo tee /etc/ld.so.conf.d/cuda-nvrtc.conf
|
||||
sudo ldconfig
|
||||
cargo build --release --manifest-path ./extra/remu/Cargo.toml
|
||||
|
||||
# **** gpuocelot ****
|
||||
|
||||
- name: Install gpuocelot dependencies (MacOS)
|
||||
if: inputs.ocelot == 'true' && runner.os == 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
pkgs=(cmake ninja llvm@15 zlib glew flex bison [email protected] zstd ncurses)
|
||||
for f in "${pkgs[@]}"; do
|
||||
brew ls --versions "$f" >/dev/null 2>&1 || brew install --quiet "$f"
|
||||
done
|
||||
|
||||
# Fix boost 1.85 for gpuocelot
|
||||
ln -s /opt/homebrew/opt/[email protected] /opt/homebrew/opt/boost || true
|
||||
ln -s /opt/homebrew/opt/boost/lib/libboost_atomic-mt.dylib /opt/homebrew/opt/boost/lib/libboost_atomic.dylib || true
|
||||
ln -s /opt/homebrew/opt/boost/lib/libboost_thread-mt.dylib /opt/homebrew/opt/boost/lib/libboost_thread.dylib || true
|
||||
- name: Cache gpuocelot
|
||||
if: inputs.ocelot == 'true'
|
||||
id: cache-build
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-gpuocelot-build-1
|
||||
with:
|
||||
path: ${{ github.workspace }}/gpuocelot/ocelot
|
||||
key: ${{ runner.os }}-gpuocelot-b16039dc940dc6bc4ea0a98380495769ff35ed99-rebuild-${{ env.CACHE_VERSION }}
|
||||
- name: Clone/compile gpuocelot
|
||||
if: inputs.ocelot == 'true' && steps.cache-build.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
git clone --recurse-submodules https://github.com/gpuocelot/gpuocelot.git ${{ github.workspace }}/gpuocelot
|
||||
cd ${{ github.workspace }}/gpuocelot/ocelot
|
||||
git checkout b16039dc940dc6bc4ea0a98380495769ff35ed99
|
||||
mkdir build
|
||||
cd build
|
||||
|
||||
CMAKE_ARGS="-Wno-dev -G Ninja -DOCELOT_BUILD_TOOLS=OFF -DCMAKE_BUILD_ALWAYS=0 -DBUILD_TESTS_CUDA=OFF -DCMAKE_POLICY_VERSION_MINIMUM=3.5"
|
||||
if [[ "${{ runner.os }}" == "macOS" ]]; then
|
||||
CMAKE_ARGS="$CMAKE_ARGS -DBoost_INCLUDE_DIR=$(brew --prefix boost)/include -DBoost_LIBRARY_DIR=$(brew --prefix boost)/lib"
|
||||
fi
|
||||
|
||||
cmake .. $CMAKE_ARGS
|
||||
ninja
|
||||
- name: Install gpuocelot
|
||||
if: inputs.ocelot == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
sudo mkdir -p /usr/local/lib
|
||||
sudo curl --output-dir /usr/local/lib -fLO https://github.com/tinygrad/gpuocelot/releases/download/v0.1.0/libgpuocelot.${{ runner.os == 'Linux' && 'so' || 'dylib' }}
|
||||
cd ${{ github.workspace }}/gpuocelot/ocelot/build
|
||||
sudo cp libgpuocelot.${{ runner.os == 'macOS' && 'dylib' || 'so' }} /usr/${{ runner.os == 'macOS' && 'local/' || '' }}lib/
|
||||
|
||||
# **** WebGPU ****
|
||||
|
||||
- name: Install WebGPU dawn
|
||||
if: inputs.webgpu == 'true'
|
||||
- name: Install WebGPU dawn (Linux)
|
||||
if: inputs.webgpu == 'true' && runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
sudo mkdir -p /usr/local/lib
|
||||
sudo curl --output-dir /usr/local/lib -fLO https://github.com/wpmed92/pydawn/releases/download/v0.1.6/libwebgpu_dawn.${{ runner.os == 'Linux' && 'so' || 'dylib' }}
|
||||
sudo curl -fL https://github.com/wpmed92/pydawn/releases/download/v0.1.6/libwebgpu_dawn.so -o /usr/local/lib/libwebgpu_dawn.so
|
||||
sudo ldconfig
|
||||
- name: Install WebGPU dawn (macOS)
|
||||
if: inputs.webgpu == 'true' && runner.os == 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
brew tap wpmed92/dawn
|
||||
brew install dawn
|
||||
|
||||
# **** LLVM ****
|
||||
|
||||
@@ -277,18 +294,12 @@ runs:
|
||||
shell: bash
|
||||
run: brew install llvm@20
|
||||
|
||||
# *** tinydreno ***
|
||||
- name: Install tinydreno (linux)
|
||||
if: inputs.tinydreno == 'true' && runner.os == 'Linux'
|
||||
# **** mesa ****
|
||||
- name: Install mesa (linux)
|
||||
if: inputs.mesa == 'true' && runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: sudo curl -fL https://github.com/sirhcm/tinydreno/raw/refs/heads/master/libllvm-qcom.so -o /usr/lib/libllvm-qcom.so
|
||||
|
||||
# *** OpenCL ***
|
||||
- name: Install rusticl
|
||||
if: inputs.opencl == 'true'
|
||||
run: sudo curl -fL https://github.com/sirhcm/tinymesa/releases/download/v1/libtinymesa_cpu-mesa-25.2.7-linux-amd64.so -o /usr/lib/libtinymesa_cpu.so
|
||||
- name: Install mesa (macOS)
|
||||
if: inputs.mesa == 'true' && runner.os == 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
sudo curl -fL https://github.com/sirhcm/tinymesa/releases/download/rusticl-v1/libRusticlOpenCL.so.1.0.0 -o /usr/lib/libRusticlOpenCL.so
|
||||
sudo mkdir -p /etc/OpenCL/vendors
|
||||
echo "/usr/lib/libRusticlOpenCL.so" | sudo tee /etc/OpenCL/vendors/rusticl.icd
|
||||
echo "RUSTICL_ENABLE=llvmpipe" >> "$GITHUB_ENV"
|
||||
run: brew install sirhcm/tinymesa/tinymesa_cpu
|
||||
|
||||
@@ -28,46 +28,44 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: 'autogen'
|
||||
opencl: 'true'
|
||||
amd: 'true'
|
||||
cuda: 'true'
|
||||
llvm: 'true'
|
||||
webgpu: 'true'
|
||||
mesa: 'true'
|
||||
pydeps: 'pyyaml mako'
|
||||
- name: Install autogen support packages
|
||||
run: sudo apt-get install -y --no-install-recommends libclang-20-dev llvm-20-dev hip-dev libusb-1.0-0-dev libdrm-dev liburing-dev
|
||||
run: sudo apt-get install -y --no-install-recommends libclang-20-dev llvm-20-dev hip-dev libusb-1.0-0-dev
|
||||
- name: Regenerate autogen files
|
||||
run: |
|
||||
find tinygrad/runtime/autogen -type f -name "*.py" -not -path "*/amd/*" -not -name "__init__.py" -not -name "comgr.py" -not -name "metal.py" -not -name "iokit.py" -not -name "corefoundation.py" -not -name "libclang.py" -delete
|
||||
find tinygrad/runtime/autogen -type f -name "*.py" -not -name "__init__.py" -not -name "comgr_3.py" -not -name "metal.py" -not -name "iokit.py" -not -name "corefoundation.py" -not -name "libclang.py" -delete
|
||||
python3 -c "from tinygrad.runtime.autogen import opencl"
|
||||
python3 -c "from tinygrad.runtime.autogen import cuda, nvrtc, nvjitlink, nv_570, nv_580, nv_610, nv"
|
||||
python3 -c "from tinygrad.runtime.autogen import comgr_3, hsa, hip, amd_gpu, sqtt, rocprof, amdgpu_kd, amdgpu_drm"
|
||||
python3 -c "from tinygrad.runtime.autogen.am import *"
|
||||
python3 -c "from tinygrad.runtime.autogen.nv_regs import *"
|
||||
python3 -c "from tinygrad.runtime.autogen import libc, kfd, io_uring, pci, vfio"
|
||||
python3 -c "from tinygrad.runtime.autogen import cuda, nvrtc, nvjitlink, nv_570, nv_580, nv"
|
||||
python3 -c "from tinygrad.runtime.autogen import comgr, hsa, hip, amd_gpu, sqtt, rocprof, amdgpu_kd"
|
||||
python3 -c "from tinygrad.runtime.autogen.am import am, pm4_soc15, pm4_nv, sdma_4_0_0, sdma_5_0_0, sdma_6_0_0, smu_v13_0_0, smu_v13_0_6, smu_v14_0_2"
|
||||
python3 -c "from tinygrad.runtime.autogen import libc, kfd, io_uring, ib, pci, vfio"
|
||||
python3 -c "from tinygrad.runtime.autogen import llvm"
|
||||
python3 -c "from tinygrad.runtime.autogen import webgpu"
|
||||
python3 -c "from tinygrad.runtime.autogen import kgsl, qcom_dsp"
|
||||
python3 -c "from tinygrad.runtime.autogen import libusb"
|
||||
python3 -c "from tinygrad.runtime.autogen import mesa"
|
||||
python3 -c "from tinygrad.runtime.autogen import avcodec"
|
||||
python3 -c "from tinygrad.runtime.autogen import llvm_qcom"
|
||||
python3 -c "from tinygrad.runtime.autogen import mlx5"
|
||||
python3 -c "from tinygrad.runtime.autogen import ggml_common"
|
||||
REGEN=1 python3 -c "from tinygrad.runtime.autogen import libclang"
|
||||
- name: Check for differences
|
||||
run: |
|
||||
if ! git diff --quiet; then
|
||||
git diff
|
||||
git diff > autogen-ubuntu.patch
|
||||
echo "Autogen mismatch detected. Patch available at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts"
|
||||
echo "Autogen files out of date. Apply patch from: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts"
|
||||
exit 1
|
||||
fi
|
||||
- name: Upload patch artifact
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: autogen-ubuntu-patch
|
||||
path: autogen-ubuntu.patch
|
||||
@@ -78,66 +76,61 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: 'autogen-mac'
|
||||
llvm: 'true'
|
||||
- name: Regenerate autogen files
|
||||
run: |
|
||||
rm tinygrad/runtime/autogen/metal.py tinygrad/runtime/autogen/iokit.py tinygrad/runtime/autogen/corefoundation.py
|
||||
python3 -c "from tinygrad.runtime.autogen import metal, iokit, corefoundation"
|
||||
LIBCLANG_PATH=/opt/homebrew/opt/llvm@20/lib/libclang.dylib python3 -c "from tinygrad.runtime.autogen import metal, iokit, corefoundation"
|
||||
- name: Check for differences
|
||||
run: |
|
||||
if ! git diff --quiet; then
|
||||
git diff
|
||||
git diff > autogen-macos.patch
|
||||
echo "Autogen mismatch detected. Patch available at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts"
|
||||
echo "Autogen files out of date. Apply patch from: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts"
|
||||
exit 1
|
||||
fi
|
||||
- name: Upload patch artifact
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: autogen-macos-patch
|
||||
path: autogen-macos.patch
|
||||
|
||||
autogen-comgr-2:
|
||||
name: In-tree Autogen (comgr 2)
|
||||
autogen-comgr-3:
|
||||
name: In-tree Autogen (comgr 3)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: 'autogen-comgr'
|
||||
- name: Install autogen support packages
|
||||
run: |
|
||||
wget https://repo.radeon.com/rocm/rocm.gpg.key -O - | gpg --dearmor | sudo tee /etc/apt/keyrings/rocm.gpg > /dev/null
|
||||
sudo tee /etc/apt/sources.list.d/rocm.list <<EOF
|
||||
deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/6.2 $(lsb_release -cs) main
|
||||
deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/6.4 $(lsb_release -cs) main
|
||||
EOF
|
||||
echo -e 'Package: *\nPin: release o=repo.radeon.com\nPin-Priority: 600' | sudo tee /etc/apt/preferences.d/rocm-pin-600
|
||||
sudo apt -qq update || true
|
||||
sudo apt-get install -y --no-install-recommends libclang-20-dev comgr
|
||||
- name: Regenerate autogen files
|
||||
run: |
|
||||
rm tinygrad/runtime/autogen/comgr.py
|
||||
python3 -c "from tinygrad.runtime.autogen import comgr"
|
||||
rm tinygrad/runtime/autogen/comgr_3.py
|
||||
python3 -c "from tinygrad.runtime.autogen import comgr_3"
|
||||
- name: Check for differences
|
||||
run: |
|
||||
if ! git diff --quiet; then
|
||||
git diff
|
||||
git diff > autogen-comgr2.patch
|
||||
echo "Autogen mismatch detected. Patch available at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts"
|
||||
git diff > autogen-comgr3.patch
|
||||
echo "Autogen files out of date. Apply patch from: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts"
|
||||
exit 1
|
||||
fi
|
||||
- name: Upload patch artifact
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: autogen-comgr2-patch
|
||||
path: autogen-comgr2.patch
|
||||
name: autogen-comgr3-patch
|
||||
path: autogen-comgr3.patch
|
||||
|
||||
+522
-460
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Remove amdgpu
|
||||
run: sudo rmmod amdgpu || true
|
||||
- name: Cleanup running AM processes
|
||||
@@ -22,10 +22,10 @@ jobs:
|
||||
- name: Run SDXL with new search
|
||||
# TODO: GCVM_L2_PROTECTION_FAULT_STATUS with llvm19
|
||||
run: |
|
||||
BENCHMARK_LOG=search_sdxl PYTHONPATH=. DEV=AMD JITBEAM=2 IGNORE_BEAM_CACHE=1 CCACHE=0 python examples/sdxl.py --noshow --timing --seed 0
|
||||
BENCHMARK_LOG=search_sdxl PYTHONPATH=. AMD=1 JITBEAM=2 IGNORE_BEAM_CACHE=1 CCACHE=0 python examples/sdxl.py --noshow --timing --seed 0
|
||||
- name: Run SDXL with cached search
|
||||
run: |
|
||||
BENCHMARK_LOG=search_sdxl_cached PYTHONPATH=. DEV=AMD JITBEAM=2 python examples/sdxl.py --noshow --timing --seed 0
|
||||
BENCHMARK_LOG=search_sdxl_cached PYTHONPATH=. AMD=1 JITBEAM=2 python examples/sdxl.py --noshow --timing --seed 0
|
||||
- name: Run winograd cifar with new search
|
||||
run: |
|
||||
BENCHMARK_LOG=search_wino_cifar WINO=1 DEFAULT_FLOAT=HALF JITBEAM=4 IGNORE_BEAM_CACHE=1 CCACHE=0 BS=1024 STEPS=500 python examples/hlb_cifar10.py
|
||||
|
||||
@@ -10,16 +10,16 @@ jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Configure Git Credentials
|
||||
run: |
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
|
||||
- uses: actions/cache@v5
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
key: mkdocs-material-${{ env.cache_id }}
|
||||
path: .cache
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
name: Run MLPerf Training
|
||||
|
||||
on:
|
||||
#schedule:
|
||||
# - cron: '5 8 * * *' # Runs at 08:05 UTC (12:05 AM Pacific Time)
|
||||
schedule:
|
||||
- cron: '5 8 * * *' # Runs at 08:05 UTC (12:05 AM Pacific Time)
|
||||
push:
|
||||
branches:
|
||||
- update_mlperf
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Cleanup running AM processes
|
||||
run: python extra/amdpci/am_smi.py --pids --kill
|
||||
- name: Symlink datasets
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
name: Platform Tests
|
||||
env:
|
||||
# increment this when downloads substantially change to avoid the internet
|
||||
CACHE_VERSION: '19'
|
||||
CAPTURE_PROCESS_REPLAY: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.title, '[pr]') && '1' || '0' }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
CHECK_OOB: 1
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: platform-${{ github.event_name }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
|
||||
# ****** OSX Tests ******
|
||||
|
||||
unittestmacos:
|
||||
name: MacOS (unit)
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: unittest-macos
|
||||
deps: testing_unit
|
||||
- name: Run unit tests
|
||||
run: DEV=METAL python -m pytest -n=auto test/unit/ --durations=20
|
||||
- name: Test tensor core ops (fake)
|
||||
run: DEV=METAL DEBUG=3 TC=2 python test/backend/test_ops.py TestOps.test_gemm
|
||||
- name: Test tensor core ops (real)
|
||||
run: DEV=METAL DEBUG=3 python test/backend/test_ops.py TestOps.test_big_gemm
|
||||
- name: Test Beam Search
|
||||
run: DEV=METAL IGNORE_BEAM_CACHE=1 python3 -m pytest extra/optimization/test_beam_search.py
|
||||
- name: Test Device Specific
|
||||
run: DEV=METAL python3 -m pytest test/device/test_metal.py
|
||||
#- name: Fuzz Test linearizer
|
||||
# run: DEV=METAL DEPTH=4 FUZZ_N=50 FUZZ_MAX_SIZE=1000000 python test/external/fuzz_linearizer.py
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
unittestmacosmock:
|
||||
name: MacOS (unit, mock)
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: unittest-macos-mock
|
||||
deps: testing_unit
|
||||
amd: 'true'
|
||||
ocelot: 'true'
|
||||
- name: Run NULL backend tests
|
||||
run: SPEC=2 DEV=NULL python -m pytest -n=auto test/null/ --durations=20
|
||||
- name: Run pytest (amd)
|
||||
env:
|
||||
DEV: MOCKKFD+AMD
|
||||
FORWARD_ONLY: 1
|
||||
run: |
|
||||
python3 -m pytest -n=auto test/device/test_hcq.py test/test_tiny.py --durations=20
|
||||
- name: Run pytest (ptx)
|
||||
env:
|
||||
DEV: "MOCK+NV:PTX"
|
||||
FORWARD_ONLY: 1
|
||||
# TODO: failing due to library loading error
|
||||
CAPTURE_PROCESS_REPLAY: 0
|
||||
run: |
|
||||
python3 -m pytest -n=auto test/device/test_hcq.py test/test_tiny.py \
|
||||
test/testextra/test_hevc.py::TestHevc::test_hevc_decode_compile --durations=20
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
testmetal:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
group: [1, 2]
|
||||
name: MacOS (DEV=METAL) (${{ matrix.group }})
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
DEV: METAL
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: macos-metal
|
||||
deps: testing_unit
|
||||
- name: Check Device.DEFAULT and print some source
|
||||
run: |
|
||||
python -c "from tinygrad import Device; assert Device.DEFAULT == 'METAL'"
|
||||
DEBUG=4 python test/test_tiny.py TestTiny.test_plus
|
||||
- name: Run backend tests
|
||||
run: python -m pytest -n=auto test/backend --durations=20 --splits 2 --group ${{ matrix.group }}
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
testmacos:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dev:
|
||||
- 'CPU:CLANG'
|
||||
- 'CPU:LLVM'
|
||||
- 'CPU:LVP'
|
||||
- 'WEBGPU'
|
||||
|
||||
name: MacOS (DEV=${{ matrix.dev }})
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: macos-${{ matrix.dev }}
|
||||
deps: "testing_unit${{ contains(matrix.dev, 'LVP') && ' mesa' || '' }}"
|
||||
llvm: ${{ contains(matrix.dev, 'LLVM') || contains(matrix.dev, 'LVP') }}
|
||||
webgpu: ${{ matrix.dev == 'WEBGPU' }}
|
||||
- name: Set env
|
||||
run: printf "DEV=${{ matrix.dev }}${{ matrix.dev == 'CPU:CLANG' && '\nCPU_COUNT=2' || '' }}" >> $GITHUB_ENV
|
||||
- name: Check Device.DEFAULT and print some source
|
||||
run: |
|
||||
python -c "from tinygrad import Device; from tinygrad.helpers import Target; assert Device.DEFAULT == Target.parse('${{ matrix.dev }}').device"
|
||||
DEBUG=4 python test/test_tiny.py TestTiny.test_plus
|
||||
- name: Run test_tiny
|
||||
run: python -m pytest -n=auto test/test_tiny.py --durations=20
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
# ****** Windows Tests ******
|
||||
|
||||
testwindows:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dev:
|
||||
- 'CPU:CLANG'
|
||||
- 'CPU:LLVM'
|
||||
- 'CPU:X86'
|
||||
- 'WEBGPU'
|
||||
|
||||
name: Windows (DEV=${{ matrix.dev }})
|
||||
runs-on: windows-2025
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: windows-${{ matrix.dev }}-minimal
|
||||
deps: testing_unit
|
||||
pydeps: ${{ matrix.dev == 'WEBGPU' && 'dawn-python' || '' }}
|
||||
- name: Set env
|
||||
shell: bash
|
||||
run: printf "DEV=${{ matrix.dev }}${{ matrix.dev == 'CPU:CLANG' && '\nCPU_COUNT=2' || '' }}" >> $GITHUB_ENV
|
||||
- name: Check Device.DEFAULT and print some source
|
||||
shell: bash
|
||||
run: |
|
||||
python -c "from tinygrad import Device; from tinygrad.helpers import Target; assert Device.DEFAULT == Target.parse('${{ matrix.dev }}').device"
|
||||
DEBUG=4 python test/test_tiny.py TestTiny.test_plus
|
||||
- name: Run test_tiny
|
||||
shell: bash
|
||||
run: python -m pytest -n=auto test/test_tiny.py --durations=20
|
||||
|
||||
|
||||
qcomclcompiletests:
|
||||
name: Compile-only (QCOM CL)
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: compile-qcomcl
|
||||
deps: testing_unit
|
||||
tinydreno: 'true'
|
||||
- name: Set env
|
||||
shell: bash
|
||||
run: printf "DEV=NULL:QCOMCL:a630\nNULL_ALLOW_COPYOUT=1" >> $GITHUB_ENV
|
||||
- name: Run test_ops
|
||||
shell: bash
|
||||
run: |
|
||||
python -c "from tinygrad import Device; assert Device.DEFAULT == 'NULL'"
|
||||
DEBUG=4 python3 test/backend/test_ops.py TestOps.test_add
|
||||
python -m pytest -n=auto test/backend/test_ops.py --durations=20
|
||||
- name: Run test_ops (IMAGE)
|
||||
shell: bash
|
||||
env:
|
||||
IMAGE: 1
|
||||
DEV: "NULL:QCOMCL:a630,IMAGE_PITCH_ALIGNMENT=64"
|
||||
run: |
|
||||
DEBUG=4 python test/backend/test_ops.py TestOps.test_gemm | grep read_imagef
|
||||
python -m pytest -n=auto test/backend/test_ops.py --durations=20
|
||||
@@ -12,9 +12,9 @@ jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -14,15 +14,12 @@ jobs:
|
||||
outputs:
|
||||
branchstat: ${{ steps.brstat.outputs.stat}}
|
||||
steps:
|
||||
- name: Check code from PR branch
|
||||
uses: actions/checkout@v6
|
||||
- name: Check code from PR branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
# PR code is only inspected with git rev-list, never executed
|
||||
allow-unsafe-pr-checkout: true
|
||||
persist-credentials: false
|
||||
- name: Check whether branch is up-to-date
|
||||
id: brstat
|
||||
run: |
|
||||
@@ -49,21 +46,18 @@ jobs:
|
||||
if: needs.checkbranch.outputs.branchstat == 'false'
|
||||
steps:
|
||||
- name: Checkout code from PR branch
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
path: pr
|
||||
# PR code is only line-counted by master's sz.py, never executed
|
||||
allow-unsafe-pr-checkout: true
|
||||
persist-credentials: false
|
||||
# the base default to tinygrad master and cannot be other fork branch for security purpose
|
||||
- name: Checkout code from tinygrad master
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: base
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Count Line Diff
|
||||
@@ -72,16 +66,18 @@ jobs:
|
||||
PR="$GITHUB_WORKSPACE/pr"
|
||||
pip install tabulate $BASE
|
||||
cp "$BASE/sz.py" .
|
||||
python sz.py "$BASE" "$PR" > loc_content.txt
|
||||
echo "loc_content<<EOF" >> "$GITHUB_ENV"
|
||||
python sz.py "$BASE" "$PR" >> "$GITHUB_ENV"
|
||||
echo "EOF" >> "$GITHUB_ENV"
|
||||
- name: Comment Code Line Diff
|
||||
continue-on-error: false
|
||||
uses: marocchino/sticky-pull-request-comment@v3
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ignore_empty: true
|
||||
skip_unchanged: true
|
||||
recreate: true
|
||||
path: loc_content.txt
|
||||
message: ${{ env.loc_content }}
|
||||
|
||||
rebase:
|
||||
name: Core Library Line Difference
|
||||
@@ -93,7 +89,7 @@ jobs:
|
||||
steps:
|
||||
- name: Comment Rebase
|
||||
continue-on-error: false
|
||||
uses: marocchino/sticky-pull-request-comment@v3
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
skip_unchanged: true
|
||||
|
||||
+632
-342
File diff suppressed because it is too large
Load Diff
@@ -66,6 +66,3 @@ target
|
||||
.mypy_cache
|
||||
mutants
|
||||
.mutmut-cache
|
||||
dagre/
|
||||
graphlib/
|
||||
uv.lock
|
||||
|
||||
@@ -28,7 +28,7 @@ repos:
|
||||
pass_filenames: false
|
||||
- id: tests
|
||||
name: comprehensive test suite
|
||||
entry: env OMP_NUM_THREADS=1 SKIP_SLOW_TEST=1 PYTHONPATH="." python3 -m pytest -n=6 test/backend/test_ops.py test/backend/test_schedule.py test/unit/test_assign.py test/backend/test_tensor.py test/backend/test_jit.py test/unit/test_schedule_cache.py test/null/test_pattern_matcher.py test/null/test_uop_symbolic.py test/unit/test_helpers.py
|
||||
entry: env OMP_NUM_THREADS=1 SKIP_SLOW_TEST=1 PYTHONPATH="." python3 -m pytest -n=6 test/test_ops.py test/test_schedule.py test/unit/test_assign.py test/test_tensor.py test/test_jit.py test/unit/test_schedule_cache.py test/unit/test_pattern_matcher.py test/unit/test_uop_symbolic.py test/unit/test_helpers.py
|
||||
language: system
|
||||
always_run: true
|
||||
pass_filenames: false
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
# Notes
|
||||
# tinygrad agents
|
||||
|
||||
- Run tests with `-n12` for speed (e.g. `python -m pytest test/null/test_dtype.py -x -q -n12`)
|
||||
- Run `python -m mypy tinygrad/` to typecheck
|
||||
- Run `python -m ruff check .` to lint
|
||||
- Read `./tinygrad/viz/README.md` for profiling and debugging rewrite rules
|
||||
Hello agent. You are one of the most talented programmers of your generation.
|
||||
|
||||
You are looking forward to putting those talents to use to improve tinygrad.
|
||||
|
||||
## philosophy
|
||||
|
||||
tinygrad is a **tensor** library focused on beauty and minimalism, while still matching the functionality of PyTorch and JAX.
|
||||
|
||||
Every line must earn its keep. Prefer readability over cleverness. We believe that if carefully designed, 10 lines can have the impact of 1000.
|
||||
|
||||
Never mix functionality changes with whitespace changes. All functionality changes must be tested.
|
||||
|
||||
## style
|
||||
|
||||
Use **2-space indentation**, and keep lines to a maximum of **150 characters**. Match the existing style.
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
# Claude Code Guide for tinygrad
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
tinygrad compiles tensor operations into optimized kernels. The pipeline:
|
||||
|
||||
1. **Tensor** (`tensor.py`) - User-facing API, creates UOp graph
|
||||
2. **UOp** (`uop/ops.py`) - Unified IR for all operations (both tensor and kernel level)
|
||||
3. **Schedule** (`engine/schedule.py`, `schedule/`) - Converts tensor UOps to kernel UOps
|
||||
4. **Codegen** (`codegen/`) - Converts kernel UOps to device code
|
||||
5. **Runtime** (`runtime/`) - Device-specific execution
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### UOp (Universal Operation)
|
||||
Everything is a UOp - tensors, operations, buffers, kernels. Key properties:
|
||||
- `op`: The operation type (Ops enum)
|
||||
- `dtype`: Data type
|
||||
- `src`: Tuple of source UOps
|
||||
- `arg`: Operation-specific argument
|
||||
- `tag`: Optional tag for graph transformations
|
||||
|
||||
UOps are **immutable and cached** - creating the same UOp twice returns the same object (ucache).
|
||||
|
||||
### PatternMatcher
|
||||
Used extensively for graph transformations:
|
||||
```python
|
||||
pm = PatternMatcher([
|
||||
(UPat(Ops.ADD, src=(UPat.cvar("x"), UPat.cvar("x"))), lambda x: x * 2),
|
||||
])
|
||||
result = graph_rewrite(uop, pm)
|
||||
```
|
||||
|
||||
### Schedule Cache
|
||||
Schedules are cached by graph structure. BIND nodes (variables with bound values) are unbound before cache key computation so different values hit the same cache.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run specific test
|
||||
python -m pytest test/unit/test_schedule_cache.py -xvs
|
||||
|
||||
# Run with timeout
|
||||
python -m pytest test/test_symbolic_ops.py -x --timeout=60
|
||||
|
||||
# Debug with print
|
||||
DEBUG=2 python -m pytest test/test_schedule.py::test_name -xvs
|
||||
|
||||
# Visualize UOp graphs
|
||||
VIZ=1 python -c "from tinygrad import Tensor; Tensor.ones(10).sum().realize()"
|
||||
```
|
||||
|
||||
## Common Environment Variables
|
||||
|
||||
- `DEBUG=1-7` - Increasing verbosity (7 shows assembly output)
|
||||
- `VIZ=1` - Enable graph visualization
|
||||
- `SPEC=1` - Enable UOp spec verification
|
||||
- `NOOPT=1` - Disable optimizations
|
||||
- `DEVICE=CPU/CUDA/AMD/METAL` - Set default device
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
1. **Print UOp graphs**: `print(tensor.uop)` or `print(tensor.uop.sink())`
|
||||
2. **Check schedule**: `tensor.schedule()` returns list of ExecItems
|
||||
3. **Trace graph rewrites**: Use `VIZ=1` or add print in PatternMatcher callbacks
|
||||
4. **Find UOps by type**: `[u for u in uop.toposort() if u.op is Ops.SOMETHING]`
|
||||
|
||||
## Workflow Rules
|
||||
|
||||
- **NEVER commit without explicit user approval** - always show the diff and wait for approval
|
||||
- **NEVER amend commits** - always create a new commit instead
|
||||
- Run `pre-commit run --all-files` before committing to catch linting/type errors
|
||||
- Run tests before proposing commits
|
||||
- Test with `SPEC=2` when modifying UOp-related code
|
||||
|
||||
## Auto-generated Files (DO NOT EDIT)
|
||||
|
||||
The following files are auto-generated and should never be edited manually:
|
||||
- `extra/assembly/amd/autogen/{arch}/__init__.py` - Generated by `python -m extra.assembly.amd.dsl --arch {arch}`
|
||||
- `extra/assembly/amd/autogen/{arch}/gen_pcode.py` - Generated by `python -m extra.assembly.amd.pcode --arch {arch}`
|
||||
|
||||
Where `{arch}` is one of: `rdna3`, `rdna4`, `cdna`
|
||||
|
||||
To add missing instruction implementations, add them to `extra/assembly/amd/emu.py` instead.
|
||||
|
||||
## Style Notes
|
||||
|
||||
- 2-space indentation, 150 char line limit
|
||||
- PatternMatchers should be defined at module level (slow to construct)
|
||||
- Prefer `graph_rewrite` over manual graph traversal
|
||||
- UOp methods like `.replace()` preserve tags unless explicitly changed
|
||||
- Use `.rtag(value)` to add tags to UOps
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
### UOp ucache Behavior
|
||||
UOps are cached by their contents - creating a UOp with identical (op, dtype, src, arg) returns the **same object**. This means:
|
||||
- `uop.replace(tag=None)` on a tagged UOp returns the original untagged UOp if it exists in cache
|
||||
- Two UOps with same structure are identical (`is` comparison works)
|
||||
|
||||
### Spec Validation
|
||||
When adding new UOp patterns, update `tinygrad/uop/spec.py`. Test with:
|
||||
```bash
|
||||
SPEC=2 python3 test/unit/test_something.py
|
||||
```
|
||||
Spec issues appear as `RuntimeError: SPEC ISSUE None: UOp(...)`.
|
||||
|
||||
### Schedule Cache Key Normalization
|
||||
The schedule cache strips values from BIND nodes so different bound values (e.g., KV cache positions) hit the same cache entry:
|
||||
- `pm_pre_sched_cache`: BIND(DEFINE_VAR, CONST) → BIND(DEFINE_VAR) for cache key
|
||||
- `pm_post_sched_cache`: restores original BIND from context
|
||||
- When accessing `bind.src[1]`, check `len(bind.src) > 1` first (might be stripped)
|
||||
- Extract var_vals from `input_buffers` dict after graph_rewrite (avoids extra toposort)
|
||||
|
||||
### Avoiding Extra Work
|
||||
- Use ctx dict from graph_rewrite to collect info during traversal instead of separate toposort
|
||||
- Only extract var_vals when schedule is non-empty (no kernels = no vars needed)
|
||||
- PatternMatchers are slow to construct - define at module level, not in functions
|
||||
|
||||
### Readability Over Speed
|
||||
Don't add complexity for marginal performance gains. Simpler code that's slightly slower is often better:
|
||||
```python
|
||||
# BAD: "optimized" with extra complexity
|
||||
if has_afters: # skip toposort if no AFTERs
|
||||
after_map = [(u, u.buf_uop) for u in big_sink.toposort() if u.op is Ops.AFTER]
|
||||
|
||||
# GOOD: simple, always works
|
||||
after_map = [(u, u.buf_uop) for u in big_sink.toposort() if u.op is Ops.AFTER]
|
||||
```
|
||||
The conditional check adds complexity, potential bugs, and often negligible speedup. Only optimize when profiling shows a real bottleneck.
|
||||
|
||||
### Testing LLM Changes
|
||||
```bash
|
||||
# Quick smoke test
|
||||
echo "Hello" | DEBUG=1 python tinygrad/apps/llm.py --model "llama3.2:1b"
|
||||
|
||||
# Check cache hits (should see "cache hit" after warmup)
|
||||
echo "Hello world" | DEBUG=1 python tinygrad/apps/llm.py --model "llama3.2:1b" 2>&1 | grep cache
|
||||
|
||||
# Test with beam search
|
||||
echo "Hello" | BEAM=2 python tinygrad/apps/llm.py --model "llama3.2:1b"
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Graph Transformation
|
||||
```python
|
||||
def my_transform(ctx, x):
|
||||
# Return new UOp or None to skip
|
||||
return x.replace(arg=new_arg)
|
||||
|
||||
pm = PatternMatcher([
|
||||
(UPat(Ops.SOMETHING, name="x"), my_transform),
|
||||
])
|
||||
result = graph_rewrite(input_uop, pm, ctx={})
|
||||
```
|
||||
|
||||
### Finding Variables
|
||||
```python
|
||||
# Get all variables in a UOp graph
|
||||
variables = uop.variables()
|
||||
|
||||
# Get bound variable values
|
||||
var, val = bind_uop.unbind()
|
||||
```
|
||||
|
||||
### Shape Handling
|
||||
```python
|
||||
# Shapes can be symbolic (contain UOps)
|
||||
shape = tensor.shape # tuple[sint, ...] where sint = int | UOp
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
When optimizing tinygrad internals:
|
||||
|
||||
1. **Measure wall time, not just call counts** - Reducing `graph_rewrite` calls doesn't always improve wall time. The overhead of conditional checks can exceed the cost of the operation being skipped.
|
||||
|
||||
2. **Profile each optimization individually** - Run benchmarks with and without each change to measure actual impact. Use `test/external/external_benchmark_schedule.py` for schedule/rewrite timing.
|
||||
|
||||
3. **Early exits in hot paths are effective** - Simple checks like `if self.op is Ops.CONST: return self` in `simplify()` can eliminate many unnecessary `graph_rewrite` calls.
|
||||
|
||||
4. **`graph_rewrite` is expensive** - Each call has overhead even for small graphs. Avoid calling it when the result is trivially known (e.g., simplifying a CONST returns itself).
|
||||
|
||||
5. **Beware iterator overhead** - Checks like `all(x.op is Ops.CONST for x in self.src)` can be slower than just running the operation, especially for small sequences.
|
||||
|
||||
6. **Verify cache hit rates before adding/keeping caches** - Measure actual hit rates with real workloads. A cache with 0% hit rate is pure overhead (e.g., `pm_cache` was removed because the algorithm guarantees each UOp is only passed to `pm_rewrite` once).
|
||||
|
||||
7. **Use `TRACK_MATCH_STATS=2` to profile pattern matching** - This shows match rates and time per pattern. Look for patterns with 0% match rate that still cost significant time - these are pure overhead for that workload.
|
||||
|
||||
8. **Cached properties beat manual traversal** - `backward_slice` uses `@functools.cached_property`. A DFS with early-exit sounds faster but is actually slower because it doesn't benefit from caching. The cache hit benefit often outweighs algorithmic improvements.
|
||||
|
||||
9. **Avoid creating intermediate objects in hot paths** - For example, `any(x.op in ops for x in self.backward_slice)` is faster than `any(x.op in ops for x in {self:None, **self.backward_slice})` because it avoids dict creation.
|
||||
|
||||
## Pattern Matching Analysis
|
||||
|
||||
**Use the right tool:**
|
||||
|
||||
- `TRACK_MATCH_STATS=2` - **Profiling**: identify expensive patterns
|
||||
- `VIZ=-1` - **Inspection**: see all transformations, what every match pattern does, the before/after diffs
|
||||
|
||||
```bash
|
||||
TRACK_MATCH_STATS=2 PYTHONPATH="." python3 test/external/external_benchmark_schedule.py
|
||||
```
|
||||
|
||||
Output format: `matches / attempts -- match_time / total_time ms -- location`
|
||||
|
||||
Key patterns to watch (from ResNet50 benchmark):
|
||||
- `split_load_store`: ~146ms, 31% match rate - does real work
|
||||
- `simplify_valid`: ~75ms, 0% match rate in this workload - checks AND ops for INDEX in backward slice
|
||||
- `vmin==vmax folding`: ~55ms, 0.33% match rate - checks 52K ops but rarely matches
|
||||
|
||||
Patterns with 0% match rate are workload-specific overhead. They may be useful in other workloads, so don't remove them without understanding their purpose.
|
||||
|
||||
```bash
|
||||
# Save the trace
|
||||
VIZ=-1 python test/test_tiny.py TestTiny.test_gemm
|
||||
|
||||
# Explore it
|
||||
./extra/viz/cli.py --help
|
||||
```
|
||||
|
||||
## AMD Performance Counter Profiling
|
||||
|
||||
Set VIZ to `-2` to save performance counters traces for the AMD backend.
|
||||
|
||||
Use the CLI in `./extra/sqtt/roc.py` to explore the trace.
|
||||
@@ -72,7 +72,7 @@ As it turns out, 90% of what you need for neural networks are a decent autograd/
|
||||
Throw in an optimizer, a data loader, and some compute, and you have all you need.
|
||||
|
||||
```python
|
||||
from tinygrad import Tensor, nn, Context
|
||||
from tinygrad import Tensor, nn
|
||||
|
||||
class LinearNet:
|
||||
def __init__(self):
|
||||
@@ -86,7 +86,7 @@ optim = nn.optim.Adam([model.l1, model.l2], lr=0.001)
|
||||
|
||||
x, y = Tensor.rand(4, 1, 28, 28), Tensor([2,4,3,7]) # replace with real mnist dataloader
|
||||
|
||||
with Context(TRAINING=1):
|
||||
with Tensor.train():
|
||||
for i in range(10):
|
||||
optim.zero_grad()
|
||||
loss = model(x).sparse_categorical_crossentropy(y).backward()
|
||||
@@ -140,8 +140,8 @@ Documentation along with a quick start guide can be found on the [docs website](
|
||||
```python
|
||||
from tinygrad import Tensor
|
||||
|
||||
x = Tensor.eye(3)
|
||||
y = Tensor([[2.0,0,-2.0]])
|
||||
x = Tensor.eye(3, requires_grad=True)
|
||||
y = Tensor([[2.0,0,-2.0]], requires_grad=True)
|
||||
z = y.matmul(x).sum()
|
||||
z.backward()
|
||||
|
||||
@@ -164,9 +164,7 @@ print(y.grad.tolist()) # dz/dy
|
||||
|
||||
## Contributing
|
||||
|
||||
There has been a lot of interest in tinygrad lately. Following these guidelines will help your PR get accepted. If you do submit a PR, please include a sentence or two about why you want this merged and why you think it will improve the project.
|
||||
|
||||
If you are a new contributor with something that looks even close to AI written, it will be closed without feedback and you may be banned from our GitHub. No human should waste time reading AI slop. And for everyone, if you used AI, disclose what you used it for.
|
||||
There has been a lot of interest in tinygrad lately. Following these guidelines will help your PR get accepted.
|
||||
|
||||
We'll start with what will get your PR closed with a pointer to this section:
|
||||
|
||||
@@ -194,12 +192,10 @@ For more examples on how to run the full test suite please refer to the [CI work
|
||||
Some examples of running tests locally:
|
||||
```sh
|
||||
python3 -m pip install -e '.[testing]' # install extra deps for testing
|
||||
python3 test/backend/test_ops.py # just the ops tests
|
||||
python3 test/test_ops.py # just the ops tests
|
||||
python3 -m pytest test/ # whole test suite
|
||||
```
|
||||
|
||||
For agents, always run tests with `-n12` for speed.
|
||||
|
||||
#### Process replay tests
|
||||
|
||||
[Process replay](https://github.com/tinygrad/tinygrad/blob/master/test/external/process_replay/README.md) compares your PR's generated kernels against master. If your PR is a refactor or speedup without any expected behavior change, It should include [pr] in the pull request title.
|
||||
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
import os, pytest, signal, threading
|
||||
|
||||
@pytest.hookimpl(wrapper=True)
|
||||
def pytest_runtest_call(item):
|
||||
t = threading.Timer(int(os.getenv("TEST_TIMEOUT", 300)), os.kill, args=(os.getpid(), signal.SIGABRT))
|
||||
t.start()
|
||||
try: yield
|
||||
finally:
|
||||
t.cancel()
|
||||
t.join()
|
||||
+15
-13
@@ -1,4 +1,6 @@
|
||||
# abstractions2 goes from back to front, here we will go from front to back
|
||||
from typing import List
|
||||
from tinygrad.helpers import tqdm
|
||||
|
||||
# *****
|
||||
# 0. Load mnist on the device
|
||||
@@ -11,7 +13,7 @@ X_train -= X_train.mean()
|
||||
# *****
|
||||
# 1. Define an MNIST model.
|
||||
|
||||
from tinygrad import Tensor, Context
|
||||
from tinygrad import Tensor
|
||||
|
||||
l1 = Tensor.kaiming_uniform(128, 784)
|
||||
l2 = Tensor.kaiming_uniform(10, 128)
|
||||
@@ -24,28 +26,28 @@ l1n, l2n = l1.numpy(), l2.numpy()
|
||||
from tinygrad.nn.optim import SGD
|
||||
optim = SGD([l1, l2])
|
||||
|
||||
with Context(TRAINING=1):
|
||||
X, Y = X_train[(samples:=Tensor.randint(128, high=X_train.shape[0]))], Y_train[samples]
|
||||
optim.zero_grad()
|
||||
model(X).sparse_categorical_crossentropy(Y).backward()
|
||||
optim.schedule_step() # this will step the optimizer without running realize
|
||||
Tensor.training = True
|
||||
X, Y = X_train[(samples:=Tensor.randint(128, high=X_train.shape[0]))], Y_train[samples]
|
||||
optim.zero_grad()
|
||||
model(X).sparse_categorical_crossentropy(Y).backward()
|
||||
optim.schedule_step() # this will step the optimizer without running realize
|
||||
|
||||
# *****
|
||||
# 3. Create a schedule (linear uop).
|
||||
# 3. Create a schedule.
|
||||
|
||||
# The weight Tensors have been assigned to, but not yet realized. Everything is still lazy at this point
|
||||
# l1.uop and l2.uop define a computation graph
|
||||
|
||||
from tinygrad.engine.realize import run_linear
|
||||
linear = Tensor.schedule_linear(l1, l2)
|
||||
from tinygrad.engine.schedule import ExecItem
|
||||
schedule: List[ExecItem] = Tensor.schedule(l1, l2)
|
||||
|
||||
print(f"The schedule contains {len(linear.src)} items.")
|
||||
for call in linear.src: print(str(call)[:80])
|
||||
print(f"The schedule contains {len(schedule)} items.")
|
||||
for si in schedule: print(str(si)[:80])
|
||||
|
||||
# *****
|
||||
# 4. Lower and run the schedule (linear uop).
|
||||
# 4. Lower and run the schedule.
|
||||
|
||||
run_linear(linear)
|
||||
for si in tqdm(schedule): si.run()
|
||||
|
||||
# *****
|
||||
# 5. Print the weight change
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
# tinygrad allows you to write kernels at many different abstractions levels.
|
||||
# This is for RDNA3, but if you don't have one you can run with the emulator
|
||||
# PYTHONPATH="." DEV=MOCKPCI+AMD
|
||||
|
||||
from tinygrad import Tensor, Context, GlobalCounters, UOp, Device
|
||||
from tinygrad.helpers import DEV, DEBUG, getenv
|
||||
from tinygrad.uop.ops import AxisType, KernelInfo, Ops
|
||||
from tinygrad.dtype import AddrSpace, dtypes
|
||||
from tinygrad.runtime.autogen.amd.rdna3.ins import *
|
||||
|
||||
def eval_harness(name, tensor, fxn, check=None):
|
||||
print(f"***** {name}")
|
||||
GlobalCounters.reset()
|
||||
with Context(DEBUG=max(DEBUG.value, 2)): out = fxn(tensor).item()
|
||||
assert check is None or abs(out - check) < abs(check) * 1e-3, f"out was wrong {out}, expected {check}, off by {out/check}x"
|
||||
print(f"computed in {GlobalCounters.time_sum_s*1000:.2f} ms, {(a.nbytes()/1e9)/GlobalCounters.time_sum_s:.2f} GB/s")
|
||||
return out
|
||||
|
||||
SZ = 256*1024 if DEV.interface.startswith("MOCK") else 1024*1024*1024
|
||||
|
||||
def example_2_hip(a:Tensor, correct):
|
||||
GLOBALS = 1024
|
||||
THREADS = 256
|
||||
def hip_reduce_sum(out:UOp, buf:UOp) -> UOp:
|
||||
assert SZ % (GLOBALS * THREADS) == 0
|
||||
CHUNK = SZ // (GLOBALS * THREADS)
|
||||
# NOTE: tinygrad doesn't populate HIP hidden kernargs, so blockDim.x/gridDim.x read as 0.
|
||||
# We hardcode block/grid sizes as constexpr to avoid any dependency on those builtins.
|
||||
code = f"""
|
||||
#include <hip/hip_runtime.h>
|
||||
constexpr unsigned int BLOCK = {THREADS};
|
||||
constexpr unsigned int CHUNK = {CHUNK};
|
||||
extern "C" __global__ void hip_reduce_sum_kernel(float* __restrict__ block_sums, const float* __restrict__ x) {{
|
||||
__shared__ float sdata[BLOCK];
|
||||
|
||||
unsigned int tid = threadIdx.x;
|
||||
unsigned int gid = blockIdx.x * BLOCK + tid;
|
||||
|
||||
// Each thread sums CHUNK consecutive elements from its own region
|
||||
float sum = 0.0f;
|
||||
const float* base = x + gid * CHUNK;
|
||||
#pragma unroll 16
|
||||
for (unsigned int k = 0; k < CHUNK; k++) {{
|
||||
sum += base[k];
|
||||
}}
|
||||
|
||||
sdata[tid] = sum;
|
||||
__syncthreads();
|
||||
|
||||
// Block reduction in shared memory
|
||||
for (unsigned int s = BLOCK / 2; s > 0; s >>= 1) {{
|
||||
if (tid < s) {{
|
||||
sdata[tid] += sdata[tid + s];
|
||||
}}
|
||||
__syncthreads();
|
||||
}}
|
||||
|
||||
// One partial sum per block
|
||||
if (tid == 0) {{
|
||||
block_sums[blockIdx.x] = sdata[0];
|
||||
}}
|
||||
}}"""
|
||||
|
||||
# TODO: remove the need for the compiler here, you should just be able to remove Ops.BINARY
|
||||
from tinygrad.runtime.support.compiler_amd import HIPCCCompiler
|
||||
lib = HIPCCCompiler(Device[Device.DEFAULT].renderer.target.arch, []).compile_cached(code)
|
||||
# the sink specifies the GLOBAL and LOCAL sizes, along with the input buffers and name
|
||||
sink = UOp.sink(UOp.special(GLOBALS, 'gidx0'), UOp.special(THREADS, 'lidx0'), out, buf,
|
||||
arg=KernelInfo(name="hip_reduce_sum_kernel"))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=code), UOp(Ops.BINARY, arg=lib)))
|
||||
eval_harness("HIP kernel", a, lambda x: Tensor.empty(GLOBALS).custom_kernel(x, fxn=hip_reduce_sum)[0].sum(), check=correct)
|
||||
|
||||
def example_3_custom_uop(a:Tensor, correct):
|
||||
# This GPU has 32 CUs, keep them all busy
|
||||
CU_COUNT = 32
|
||||
def custom_sum(out:UOp, buf:UOp) -> UOp:
|
||||
LCLS = 256
|
||||
buf = buf.reshape(CU_COUNT, -1, LCLS)
|
||||
|
||||
glbl = UOp.range(CU_COUNT, 0, AxisType.GLOBAL)
|
||||
lane = UOp.range(LCLS, 1, AxisType.LOCAL)
|
||||
|
||||
# accumulate the globals into a per lane accumulator
|
||||
reduce_loop = UOp.range(buf.shape[1], 2, AxisType.REDUCE)
|
||||
acc = UOp.placeholder((1,), dtypes.float, slot=6, addrspace=AddrSpace.REG)
|
||||
acc = acc.after(acc.store(0))
|
||||
acc = acc.after(acc[0].store(acc.after(reduce_loop)[0] + buf[glbl, reduce_loop, lane]).end(reduce_loop))
|
||||
|
||||
# store all the per lane accumulators to LOCAL
|
||||
local_accs = UOp.placeholder((LCLS,), dtypes.float, slot=0, addrspace=AddrSpace.LOCAL)
|
||||
local_accs = local_accs.after(local_accs[lane].store(acc[0]))
|
||||
|
||||
# accumulate LOCALs into a single per CU accumulator
|
||||
late_reduce_loop = UOp.range(LCLS, 3, AxisType.REDUCE)
|
||||
acc2 = UOp.placeholder((1,), dtypes.float, slot=7, addrspace=AddrSpace.REG)
|
||||
acc2 = acc2.after(acc2.store(0))
|
||||
acc2 = acc2.after(acc2[0].store(acc2.after(late_reduce_loop)[0] + local_accs[late_reduce_loop]).end(late_reduce_loop))[0]
|
||||
|
||||
# store (NOTE: since the address doesn't depend on the warp, this will be automatically gated)
|
||||
return out[glbl].store(acc2).end(lane, glbl).sink(arg=KernelInfo(opts_to_apply=()))
|
||||
|
||||
eval_harness("custom UOp kernel", a, lambda x: Tensor.empty(CU_COUNT).custom_kernel(x, fxn=custom_sum)[0].sum(), check=correct)
|
||||
|
||||
def example_5_custom_assembly(a:Tensor, correct):
|
||||
# Kernel class copied from amd_asm_matmul
|
||||
class Kernel:
|
||||
def __init__(self): self.instructions, self.labels, self.pos = [], {}, 0
|
||||
def label(self, name): self.labels[name] = self.pos
|
||||
def emit(self, inst, target=None):
|
||||
self.instructions.append(inst)
|
||||
inst._target, inst._pos = target, self.pos
|
||||
self.pos += inst.size()
|
||||
return inst
|
||||
def waitcnt(self, lgkm=None, vm=None):
|
||||
# Wait for memory operations. lgkm=N waits until N lgkm ops remain, vm=N waits until N vmem ops remain.
|
||||
vmcnt, lgkmcnt, expcnt = vm if vm is not None else 63, lgkm if lgkm is not None else 63, 7
|
||||
waitcnt = (expcnt & 0x7) | ((lgkmcnt & 0x3f) << 4) | ((vmcnt & 0x3f) << 10)
|
||||
self.emit(s_waitcnt(simm16=waitcnt))
|
||||
def finalize(self, sink:UOp) -> UOp:
|
||||
for inst in self.instructions:
|
||||
if inst._target is None: continue
|
||||
offset_dwords = (self.labels[inst._target] - inst._pos - inst.size()) // 4
|
||||
if not -32768 <= offset_dwords <= 32767: raise ValueError(f"branch to '{inst._target}' offset {offset_dwords} exceeds simm16 range")
|
||||
inst.simm16 = offset_dwords
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=x) for x in self.instructions]))))
|
||||
|
||||
CU_COUNT = 32
|
||||
LANES = 64
|
||||
def asm_sum(out:UOp, buf:UOp) -> UOp:
|
||||
V_LANE_ID = 0 # lane_id set on startup
|
||||
S_WORKGROUP_X = 2 # workgroup_id_x
|
||||
S_LOOP_CTR = 3
|
||||
k = Kernel()
|
||||
# mul lane id by 16 for offsets (4 for float, 4 for b128)
|
||||
k.emit(v_mul_lo_u32(v[0], v[V_LANE_ID], 16))
|
||||
k.emit(v_add_nc_u32_e32(v[1], 4096, v[0]))
|
||||
k.emit(v_add_nc_u32_e32(v[2], 4096, v[1]))
|
||||
k.emit(v_add_nc_u32_e32(v[3], 4096, v[2]))
|
||||
# load both addresses
|
||||
k.emit(s_load_b128(sdata=s[4:7], sbase=s[0:1], offset=0x0, soffset=NULL))
|
||||
k.waitcnt(lgkm=0)
|
||||
# offset buffer pointer by workgroup_id_x * chunk_size_bytes
|
||||
k.emit(s_mul_i32(s[S_LOOP_CTR], s[S_WORKGROUP_X], buf.numel()*4//CU_COUNT))
|
||||
k.emit(s_add_u32(s[6], s[6], s[S_LOOP_CTR]))
|
||||
k.emit(s_addc_u32(s[7], s[7], 0))
|
||||
# zero the accumulators
|
||||
k.emit(VOPD(VOPDOp.V_DUAL_MOV_B32, VOPDOp.V_DUAL_MOV_B32, vdstx=v[4], vdsty=v[5], srcx0=0, srcy0=0))
|
||||
k.emit(VOPD(VOPDOp.V_DUAL_MOV_B32, VOPDOp.V_DUAL_MOV_B32, vdstx=v[6], vdsty=v[7], srcx0=0, srcy0=0))
|
||||
|
||||
def emit_loads(base_vreg, reg_len):
|
||||
assert reg_len%4 == 0
|
||||
k.emit(s_clause(simm16=(reg_len//4)-1))
|
||||
for i in range(reg_len//4):
|
||||
offset = i*LANES*16
|
||||
assert offset < 16384
|
||||
k.emit(global_load_b128(vdst=v[base_vreg+i*4:base_vreg+i*4+3], addr=v[offset//4096], saddr=s[6:7], offset=offset%4096))
|
||||
k.emit(s_add_u32(s[6], s[6], reg_len * LANES * 4))
|
||||
k.emit(s_addc_u32(s[7], s[7], 0))
|
||||
|
||||
def tree_reduce_to_4567(base_vreg, reg_len):
|
||||
assert reg_len%4 == 0
|
||||
reg_len //= 4
|
||||
while reg_len > 1:
|
||||
half = reg_len // 2
|
||||
for j in range(half):
|
||||
a, b = base_vreg + j*4, base_vreg + (j+half)*4
|
||||
# v[a+0](bank0) += v[b+2](bank2), v[a+1](bank1) += v[b+3](bank3) — src0 and src1 on different banks
|
||||
k.emit(VOPD(VOPDOp.V_DUAL_ADD_F32, VOPDOp.V_DUAL_ADD_F32, vdstx=v[a], vdsty=v[a+1], srcx0=v[a], vsrcx1=v[b+2], srcy0=v[a+1], vsrcy1=v[b+3]))
|
||||
# v[a+2](bank2) += v[b+0](bank0), v[a+3](bank3) += v[b+1](bank1) — src0 and src1 on different banks
|
||||
k.emit(VOPD(VOPDOp.V_DUAL_ADD_F32, VOPDOp.V_DUAL_ADD_F32, vdstx=v[a+2], vdsty=v[a+3], srcx0=v[a+2], vsrcx1=v[b], srcy0=v[a+3], vsrcy1=v[b+1]))
|
||||
reg_len = half
|
||||
k.emit(VOPD(VOPDOp.V_DUAL_ADD_F32, VOPDOp.V_DUAL_ADD_F32, vdstx=v[4], vdsty=v[5], srcx0=v[4], vsrcx1=v[base_vreg], srcy0=v[5], vsrcy1=v[base_vreg+1]))
|
||||
k.emit(VOPD(VOPDOp.V_DUAL_ADD_F32, VOPDOp.V_DUAL_ADD_F32, vdstx=v[6], vdsty=v[7], srcx0=v[6], vsrcx1=v[base_vreg+2], srcy0=v[7], vsrcy1=v[base_vreg+3]))
|
||||
|
||||
BASE_REG = 8
|
||||
LOAD_UNROLL = 64
|
||||
INNER_UNROLL = 2
|
||||
|
||||
assert buf.numel() % (CU_COUNT*LANES*LOAD_UNROLL*INNER_UNROLL) == 0
|
||||
total_batches = buf.numel()//(CU_COUNT*LANES*LOAD_UNROLL*INNER_UNROLL)
|
||||
k.emit(s_mov_b32(s[S_LOOP_CTR], total_batches-1))
|
||||
|
||||
k.label('LOOP')
|
||||
for _ in range(INNER_UNROLL):
|
||||
emit_loads(BASE_REG, reg_len=LOAD_UNROLL)
|
||||
k.waitcnt(vm=0)
|
||||
tree_reduce_to_4567(BASE_REG, reg_len=LOAD_UNROLL)
|
||||
k.emit(s_sub_u32(s[S_LOOP_CTR], s[S_LOOP_CTR], 1))
|
||||
k.emit(s_cbranch_scc0(), target='LOOP')
|
||||
|
||||
# add into v[4]
|
||||
k.emit(v_add_f32_e32(v[4], v[4], v[5]))
|
||||
k.emit(v_add_f32_e32(v[6], v[6], v[7]))
|
||||
k.emit(v_add_f32_e32(v[4], v[4], v[6]))
|
||||
|
||||
# warp shuffle into v[4] on lane 0 using DPP row_shl within each 16-lane row
|
||||
for shift in [1, 2, 4, 8]:
|
||||
k.emit(v_add_f32_e32(v[4], DPP, v[4], vsrc0=v[4], dpp=0x100 | shift, row_mask=0xf, bank_mask=0xf, bc=1))
|
||||
# combine rows: get lane 16's value to lane 0 via permlanex16
|
||||
k.emit(v_permlanex16_b32(v[5], v[4], 0, 0))
|
||||
k.emit(v_add_f32_e32(v[4], v[4], v[5]))
|
||||
|
||||
# atomic store (only on lane 0)
|
||||
k.emit(s_mov_b32(EXEC_LO, 1))
|
||||
k.emit(v_mov_b32_e32(v[0], 0))
|
||||
k.emit(global_atomic_add_f32(addr=v[0], saddr=s[4:5], data=v[4]))
|
||||
|
||||
k.emit(s_sendmsg(simm16=3)) # DEALLOC_VGPRS
|
||||
k.emit(s_endpgm())
|
||||
return k.finalize(UOp.sink(UOp.special(CU_COUNT, 'gidx0'), UOp.special(LANES, 'lidx0'), out, buf, arg=KernelInfo(name="asm_reduce")))
|
||||
|
||||
out = Tensor.zeros(1,).contiguous().realize()
|
||||
eval_harness("RDNA3 assembly kernel", a, lambda x: out.custom_kernel(x, fxn=asm_sum)[0], check=correct)
|
||||
|
||||
if __name__ == "__main__":
|
||||
examples = [int(x) for x in getenv("EXAMPLES", "1,2,3,4,5").split(",")]
|
||||
|
||||
correct = None
|
||||
# First define a Tensor and realize it. We will focus on a 1GB sum kernel on RDNA3
|
||||
a = (Tensor.randn(SZ) if getenv("RAND") else Tensor.ones(SZ)).contiguous().realize()
|
||||
|
||||
if 1 in examples:
|
||||
# *****
|
||||
# This is the high level tinygrad way.
|
||||
# Note that this is split into multiple kernels for speed.
|
||||
correct = eval_harness("basic kernel", a, lambda x: x.sum())
|
||||
|
||||
if 2 in examples:
|
||||
# *****
|
||||
# You can import kernels from CUDA/HIP/Metal.
|
||||
# ChatGPT is great at writing these Kernel
|
||||
example_2_hip(a, correct)
|
||||
|
||||
if 3 in examples:
|
||||
# *****
|
||||
# Now we get to the lower abstraction layers of tinygrad.
|
||||
# You can write a kernel in UOps, and it's 2.5x faster than normal.
|
||||
example_3_custom_uop(a, correct)
|
||||
|
||||
if 4 in examples:
|
||||
# *****
|
||||
# You can also BEAM search stock tinygrad for a faster kernel.
|
||||
# This does even better than all the kernels to date in this simple case.
|
||||
with Context(BEAM=2):
|
||||
eval_harness("BEAMed kernel", a, lambda x: x.sum(), check=correct)
|
||||
|
||||
if 5 in examples:
|
||||
# *****
|
||||
# If you really want to go crazy with speed, you can code in assembly.
|
||||
# There's not too much to gain here over BEAM, but it's a few percent faster.
|
||||
example_5_custom_assembly(a, correct)
|
||||
@@ -3,7 +3,7 @@
|
||||
AM driver is a userspace driver targeting AMD's RDNA3/RDNA4. You only need tinygrad to send compute tasks to your GPU!
|
||||
|
||||
## How to run?
|
||||
Make sure that amdgpu module is unloaded and just run tinygrad with `DEV=AMD`!
|
||||
Make sure that amdgpu module is unloaded and just run tinygrad with `AMD=1`!
|
||||
|
||||
Optional requirements:
|
||||
|
||||
|
||||
@@ -17,13 +17,15 @@ The `UOp` graph specifies the compute in terms of low level tinygrad ops. Not al
|
||||
|
||||
## Scheduling
|
||||
|
||||
The [scheduler](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/schedule/__init__.py) converts the graph of UOps into a `LINEAR` UOp whose `src` is a list of `CALL` UOps. One `CALL` is one kernel on the GPU, and the scheduler is responsible for breaking the large compute graph into subgraphs that can fit in a kernel. The `CALL`'s `src[0]` (a `SINK` ast) specifies what compute to run, and the remaining `src` are the buffers to run it on.
|
||||
The [scheduler](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/engine/schedule.py) converts the graph of UOps into a list of `ExecItem`. One `ExecItem` is one kernel on the GPU, and the scheduler is responsible for breaking the large compute graph into subgraphs that can fit in a kernel. `ast` specifies what compute to run, and `bufs` specifies what buffers to run it on.
|
||||
|
||||
::: tinygrad.engine.schedule.ExecItem
|
||||
|
||||
## Lowering
|
||||
|
||||
The code in [realize](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/engine/realize.py) lowers each `CALL` by compiling its ast into a `PROGRAM` and running it.
|
||||
The code in [realize](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/engine/realize.py) lowers `ExecItem` by populating its `prg` field with
|
||||
|
||||
::: tinygrad.engine.realize.run_linear
|
||||
::: tinygrad.engine.realize.run_schedule
|
||||
|
||||
There's a ton of complexity hidden behind this, see the `codegen/` directory.
|
||||
|
||||
@@ -33,7 +35,13 @@ Then we render the UOps into code with a `Renderer`, then we compile the code to
|
||||
|
||||
## Execution
|
||||
|
||||
`run_linear` walks the `LINEAR` UOp, dispatching each `CALL` to a runner (kernel, copy, view, encdec, or graph).
|
||||
Creating `ExecItem`, which has a run method
|
||||
|
||||
::: tinygrad.engine.realize.ExecItem
|
||||
options:
|
||||
members: true
|
||||
|
||||
Lists of `ExecItem` can be condensed into a single ExecItem with the Graph API (rename to Queue?)
|
||||
|
||||
## Runtime
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Directories are listed in order of how they are processed.
|
||||
|
||||
Group UOps into kernels.
|
||||
|
||||
::: tinygrad.schedule.rangeify.get_kernel_graph
|
||||
::: tinygrad.schedule.rangeify.get_rangeify_map
|
||||
options:
|
||||
members: false
|
||||
show_labels: false
|
||||
@@ -28,7 +28,7 @@ Transforms the ast into an optimized ast. This is where BEAM search and heuristi
|
||||
|
||||
Transform the optimized ast into a linearized and rendered program.
|
||||
|
||||
::: tinygrad.codegen.to_program
|
||||
::: tinygrad.codegen.get_program
|
||||
options:
|
||||
members: false
|
||||
show_labels: false
|
||||
@@ -53,7 +53,7 @@ Transform the linearized list of UOps into a program, represented as a string.
|
||||
|
||||
Abstracted high level interface to the runtimes.
|
||||
|
||||
::: tinygrad.engine.realize.to_program
|
||||
::: tinygrad.engine.realize.get_program
|
||||
options:
|
||||
members: false
|
||||
show_labels: false
|
||||
|
||||
@@ -62,7 +62,7 @@ A lot of work can still be done here. For example, we never copy the inputs to o
|
||||
|
||||
Many accelerators have Tensor Cores / MAC arrays / systolic arrays. The main value of these is that, since they are 2-D, they create an n^2 ratio between the compute and the input data.
|
||||
|
||||
GPUs use Tensor Cores instead of MAC arrays to fit better in the GPU warp paradigm. This is because the output of Tensor Cores is O(n) wrt the input, while the output of MAC arrays is O(n^2)
|
||||
GPUs use Tensor Cores instead of MAC arrays to fit better in the GPU warp paradigm. This is because the output of Tensor Cores is O(n) wrt the input, while the output of MAC arrays like the AMX is O(n^2)
|
||||
|
||||
We have a simple framework in tinygrad for adding these ALU blocks and achieving good performance from them.
|
||||
|
||||
|
||||
+1
-2
@@ -1,8 +1,7 @@
|
||||
::: tinygrad.dtype.DType
|
||||
|
||||
::: tinygrad.dtype.DTypes
|
||||
::: tinygrad.dtype.dtypes
|
||||
options:
|
||||
heading: dtypes
|
||||
members: true
|
||||
members_order: source
|
||||
show_labels: false
|
||||
|
||||
+12
-24
@@ -3,7 +3,7 @@
|
||||
This is a list of environment variable that control the runtime behavior of tinygrad and its examples.
|
||||
Most of these are self-explanatory, and are usually used to set an option at runtime.
|
||||
|
||||
Example: `DEV=CL DEBUG=4 python3 -m pytest`
|
||||
Example: `CL=1 DEBUG=4 python3 -m pytest`
|
||||
|
||||
However you can also decorate a function to set a value only inside that function.
|
||||
|
||||
@@ -31,43 +31,31 @@ These control the behavior of core tinygrad even when used as a library.
|
||||
Variable | Possible Value(s) | Description
|
||||
---|---|---
|
||||
DEBUG | [1-7] | enable debugging output (operations, timings, speed, generated code and more)
|
||||
DEV | [AMD, NV, ...] | enable a specific backend, see [below](#dev-variable)
|
||||
CL | [1] | enable OpenCL backend
|
||||
CUDA | [1] | enable CUDA backend
|
||||
AMD | [1] | enable AMD backend
|
||||
NV | [1] | enable NV backend
|
||||
METAL | [1] | enable Metal backend (for Mac M1 and after)
|
||||
CPU | [1] | enable CPU backend
|
||||
BEAM | [#] | number of beams in kernel beam search
|
||||
DEFAULT_FLOAT | [HALF, ...]| specify the default float dtype (FLOAT32, HALF, BFLOAT16, FLOAT64, ...), default to FLOAT32
|
||||
IMAGE | [1] | enable 2d specific optimizations
|
||||
IMAGE | [1-2] | enable 2d specific optimizations
|
||||
FLOAT16 | [1] | use float16 for images instead of float32
|
||||
HCQ_VISIBLE_DEVICES | [list[int]]| restricts the HCQ devices that are available. The format is a comma-separated list of identifiers (indexing starts with 0).
|
||||
JIT | [0-2] | 0=disabled, 1=[jit enabled](quickstart.md#jit) (default), 2=jit enabled, but graphs are disabled
|
||||
VIZ | [1] | 0=disabled, 1=[viz enabled](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/viz)
|
||||
ALLOW_TF32 | [1] | enable TensorFloat-32 tensor cores on Ampere or newer GPUs.
|
||||
WEBGPU_BACKEND | [WGPUBackendType_Metal, ...] | Force select a backend for WebGPU (Metal, DirectX, OpenGL, Vulkan...)
|
||||
CUDA_PATH | str | Use `CUDA_PATH/include` for CUDA headers for CUDA and NV backends. If not set, TinyGrad will use `/usr/local/cuda/include`, `/usr/include` and `/opt/cuda/include`.
|
||||
|
||||
### DEV variable
|
||||
|
||||
The `DEV` variable deserves special note due to its more nuanced syntax.
|
||||
`DEV` is used to specify the target device, target renderer and target architecture for said device, separated by colons.
|
||||
Specifying the renderer and architecture is optional, omitting a preference will cause tinygrad to automatically determine a suitable setting.
|
||||
The `DEV` variable may also be used to specify the interface through which to access the device (eg. `PCI`, `USB`). Interfaces may be specified preceding the target triple,
|
||||
separated by a plus (eg. `DEV=USB+AMD:LLVM`). Similarly as above, the interface may be omitted. Example usage follows:
|
||||
|
||||
`DEV` contents | Interpretation
|
||||
--- | ---
|
||||
AMD | use the AMD device
|
||||
AMD:LLVM | use the AMD device with the LLVM renderer
|
||||
NV:CUDA:sm_70 | use the NV device with the CUDA renderer targetting sm_70
|
||||
AMD::gfx950 | use the AMD device targetting gfx950
|
||||
USB+AMD | use the AMD device over the USB interface
|
||||
CPU:LLVM | use the CPU device with the LLVM renderer
|
||||
CPU:LLVM:x86_64,znver2,avx2,-avx512f | use the CPU device with the LLVM renderer, with [additional arch flags](runtime.md#cpu-arch)
|
||||
|
||||
### Debug breakdown
|
||||
## Debug breakdown
|
||||
|
||||
Variable | Value | Description
|
||||
---|---|---
|
||||
DEBUG | >= 1 | Enables debugging and lists devices being used
|
||||
DEBUG | >= 2 | Provides performance metrics for operations, including timing, memory usage, bandwidth for each kernel execution
|
||||
DEBUG | >= 3 | Outputs the applied optimizations at a kernel level
|
||||
DEBUG | >= 3 | Outputs buffers used for each kernel (shape, dtype and strides) and the applied optimizations at a kernel level
|
||||
DEBUG | >= 4 | Outputs the generated kernel code
|
||||
DEBUG | >= 5 | Displays the intermediate representation of the computation UOps
|
||||
DEBUG | >= 5 | Displays the intermediate representation of the computation UOps (AST)
|
||||
DEBUG | >= 6 | Displays the intermediate representation of the computation UOps in a linearized manner, detailing the operation sequence
|
||||
DEBUG | >= 7 | Outputs the assembly code generated for the target hardware
|
||||
|
||||
+3
-2
@@ -24,7 +24,7 @@ You will see `CUDA` here on a GPU instance, or `CPU` here on a CPU instance.
|
||||
We'll use the model from [the Keras tutorial](https://keras.io/examples/vision/mnist_convnet/).
|
||||
|
||||
```python
|
||||
from tinygrad import Tensor, nn, Context
|
||||
from tinygrad import Tensor, nn
|
||||
|
||||
class Model:
|
||||
def __init__(self):
|
||||
@@ -74,8 +74,8 @@ We'll use the Adam optimizer. The `nn.state.get_parameters` will walk the model
|
||||
```python
|
||||
optim = nn.optim.Adam(nn.state.get_parameters(model))
|
||||
batch_size = 128
|
||||
@Context(TRAINING=1)
|
||||
def step():
|
||||
Tensor.training = True # makes dropout work
|
||||
samples = Tensor.randint(batch_size, high=X_train.shape[0])
|
||||
X, Y = X_train[samples], Y_train[samples]
|
||||
optim.zero_grad()
|
||||
@@ -143,6 +143,7 @@ Since we are just randomly sampling from the dataset, there's no real concept of
|
||||
for step in range(7000):
|
||||
loss = jit_step()
|
||||
if step%100 == 0:
|
||||
Tensor.training = False
|
||||
acc = (model(X_test).argmax(axis=1) == Y_test).mean().item()
|
||||
print(f"step {step:4d}, loss {loss.item():.2f}, acc {acc*100.:.2f}%")
|
||||
```
|
||||
|
||||
+1
-1
@@ -37,4 +37,4 @@
|
||||
options:
|
||||
show_signature: false
|
||||
separate_signature: false
|
||||
::: tinygrad.llm.gguf.gguf_load
|
||||
::: tinygrad.nn.state.gguf_load
|
||||
|
||||
+6
-7
@@ -133,7 +133,7 @@ For our loss function we will be using sparse categorical cross entropy loss. Th
|
||||
```python
|
||||
def sparse_categorical_crossentropy(self, Y, ignore_index=-1) -> Tensor:
|
||||
loss_mask = Y != ignore_index
|
||||
y_counter = Tensor.arange(self.shape[-1], dtype=dtypes.int32).unsqueeze(0).expand(Y.numel(), self.shape[-1])
|
||||
y_counter = Tensor.arange(self.shape[-1], dtype=dtypes.int32, requires_grad=False, device=self.device).unsqueeze(0).expand(Y.numel(), self.shape[-1])
|
||||
y = ((y_counter == Y.flatten().reshape(-1, 1)).where(-1.0, 0) * loss_mask.reshape(-1, 1)).reshape(*Y.shape, self.shape[-1])
|
||||
return self.log_softmax().mul(y).sum() / loss_mask.sum()
|
||||
```
|
||||
@@ -165,18 +165,17 @@ from extra.datasets import fetch_mnist
|
||||
Now we have everything we need to start training our neural network.
|
||||
We will be training for 1000 steps with a batch size of 64.
|
||||
|
||||
We use `with Context(TRAINING=1)` to enable training mode.
|
||||
We use `with Tensor.train()` to set the internal flag `Tensor.training` to `True` during training.
|
||||
Upon exit, the flag is restored to its previous value by the context manager.
|
||||
|
||||
```python
|
||||
from tinygrad import Context
|
||||
X_train, Y_train, X_test, Y_test = fetch_mnist()
|
||||
|
||||
with Context(TRAINING=1):
|
||||
with Tensor.train():
|
||||
for step in range(1000):
|
||||
# random sample a batch
|
||||
samp = np.random.randint(0, X_train.shape[0], size=(64))
|
||||
batch = Tensor(X_train[samp])
|
||||
batch = Tensor(X_train[samp], requires_grad=False)
|
||||
# get the corresponding labels
|
||||
labels = Tensor(Y_train[samp])
|
||||
|
||||
@@ -214,7 +213,7 @@ with Timing("Time: "):
|
||||
for step in range(1000):
|
||||
# random sample a batch
|
||||
samp = np.random.randint(0, X_test.shape[0], size=(64))
|
||||
batch = Tensor(X_test[samp])
|
||||
batch = Tensor(X_test[samp], requires_grad=False)
|
||||
# get the corresponding labels
|
||||
labels = Y_test[samp]
|
||||
|
||||
@@ -258,7 +257,7 @@ with Timing("Time: "):
|
||||
for step in range(1000):
|
||||
# random sample a batch
|
||||
samp = np.random.randint(0, X_test.shape[0], size=(64))
|
||||
batch = Tensor(X_test[samp])
|
||||
batch = Tensor(X_test[samp], requires_grad=False)
|
||||
# get the corresponding labels
|
||||
labels = Y_test[samp]
|
||||
|
||||
|
||||
+6
-12
@@ -1,16 +1,16 @@
|
||||
# Runtimes
|
||||
|
||||
tinygrad supports various runtimes, enabling your code to scale across a wide range of devices. The default runtime can be automatically selected based on the available hardware, or you can force a specific runtime to be default using environment variables (e.g., `DEV=CPU`).
|
||||
tinygrad supports various runtimes, enabling your code to scale across a wide range of devices. The default runtime can be automatically selected based on the available hardware, or you can force a specific runtime to be default using environment variables (e.g., `CPU=1`).
|
||||
|
||||
| Runtime | Description | Compiler Options | Requirements |
|
||||
|---------|-------------|------------------|--------------|
|
||||
| [NV](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_nv.py) | Provides acceleration for NVIDIA GPUs | nvrtc (default)<br>PTX (`DEV=NV:PTX`) | Ampere/Ada/Blackwell series GPUs.<br>You can select an interface via [the `DEV` variable](env_vars.md#dev-variable). See [NV interfaces](#nv-interfaces) for details. |
|
||||
| [AMD](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_amd.py) | Provides acceleration for AMD GPUs | LLVM (`DEV=AMD:LLVM`)<br>HIP/COMGR (`DEV=AMD:HIP`) | CDNA3, CDNA4, RDNA3 or RDNA4 GPUs.<br>You can select an interface via [the `DEV` variable](env_vars.md#dev-variable). See [AMD interfaces](#amd-interfaces) for details. |
|
||||
| [NV](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_nv.py) | Provides acceleration for NVIDIA GPUs | nvrtc (default)<br>PTX (`NV_PTX=1`) | Ampere/Ada/Blackwell series GPUs.<br>You can select an interface via `NV_IFACE=(NVK\|PCI)`. See [NV interfaces](#nv-interfaces) for details. |
|
||||
| [AMD](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_amd.py) | Provides acceleration for AMD GPUs | LLVM (`AMD_LLVM=1`)<br>HIP/COMGR (`AMD_HIP=1`) | RDNA2 or newer GPUs.<br>You can select an interface via `AMD_IFACE=(KFD\|PCI\|USB)`. See [AMD interfaces](#amd-interfaces) for details. |
|
||||
| [QCOM](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_qcom.py) | Provides acceleration for QCOM GPUs | - | 6xx series GPUs |
|
||||
| [METAL](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_metal.py) | Utilizes Metal for acceleration on Apple devices | - | M1+ Macs; Metal 3.0+ for `bfloat` support |
|
||||
| [CUDA](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_cuda.py) | Utilizes CUDA for acceleration on NVIDIA GPUs | nvrtc (default)<br> PTX (`DEV=CUDA:PTX`) | NVIDIA GPU with CUDA support |
|
||||
| [CUDA](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_cuda.py) | Utilizes CUDA for acceleration on NVIDIA GPUs | nvrtc (default)<br> PTX (`CUDA_PTX=1`) | NVIDIA GPU with CUDA support |
|
||||
| [CL](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_cl.py) | Accelerates computations using OpenCL on GPUs | - | OpenCL 2.0 compatible device |
|
||||
| [CPU](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_cpu.py) | Runs on CPU using the clang or llvm compiler | Clang JIT (default)<br>LLVM IR (`DEV=CPU:LLVM`) | `clang` compiler in system `PATH`<br>You can specify additional arch parameters via [the `DEV` variable](env_vars.md#dev-variable). See [CPU arch](#cpu-arch) for details. |
|
||||
| [CPU](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_cpu.py) | Runs on CPU using the clang or llvm compiler | Clang JIT (default)<br>LLVM IR (`CPU_LLVM=1`) | `clang` compiler in system `PATH` |
|
||||
| [WEBGPU](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/ops_webgpu.py) | Runs on GPU using the Dawn WebGPU engine (used in Google Chrome) | - | Dawn library installed and discoverable. Binaries: [pydawn v0.3.0](https://github.com/wpmed92/pydawn/releases/tag/v0.3.0) |
|
||||
|
||||
|
||||
@@ -72,16 +72,10 @@ AMD backend supports several interfaces for communicating with devices:
|
||||
* `PCI`: uses the [AM driver](developer/am.md)
|
||||
* `USB`: USB3 interface for asm24xx chips.
|
||||
|
||||
You can force an interface by setting the interface component of [the `DEV` environment variable](env_vars.md#dev-variable) to one of these values. When set to `PCI`, this may unbind your GPU from the amdgpu driver.
|
||||
You can force an interface by setting `AMD_IFACE` to one of these values. In the case of `AMD_IFACE=PCI`, this may unbind your GPU from the amdgpu driver.
|
||||
|
||||
## NV Interfaces
|
||||
NV backend supports several interfaces for communicating with devices:
|
||||
|
||||
* `NVK`: uses the nvidia driver
|
||||
* `PCI`: uses the [NV driver](https://github.com/tinygrad/tinygrad/tree/master/tinygrad/runtime/support/nv/nvdev.py)
|
||||
|
||||
## CPU Arch
|
||||
The CPU renderers may be additionally configured using the arch component of [the `DEV` environment variable](env_vars.md#dev-variable).
|
||||
CPU arch should be specified as a comma-separated list of parameters, and must contain at least two values: the architecture family (ie. x86_64, arm64, or riscv64) and the cpu type (as accepted by `clang`'s `-march`).
|
||||
If native is specified as the cpu type, tinygrad (or delegate compiler) will query the host cpu type. Additional comma-separated values are interpreted as cpu feature flags. When a value is preceded by a `-` character, the corresponding feature flag will be disabled, otherwise the flag will be enabled.
|
||||
Note that enabled feature flags should not be preceded by a `+`.
|
||||
|
||||
@@ -66,8 +66,8 @@ Elementwise ops operate on a per element basis. They don't change the shape of t
|
||||
::: tinygrad.Tensor.sub
|
||||
::: tinygrad.Tensor.mul
|
||||
::: tinygrad.Tensor.div
|
||||
::: tinygrad.Tensor.idiv
|
||||
::: tinygrad.Tensor.mod
|
||||
::: tinygrad.Tensor.fmod
|
||||
::: tinygrad.Tensor.bitwise_xor
|
||||
::: tinygrad.Tensor.bitwise_and
|
||||
::: tinygrad.Tensor.bitwise_or
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
## tinygrad ops
|
||||
|
||||
::: tinygrad.Tensor.linear_with_vars
|
||||
::: tinygrad.Tensor.schedule_linear
|
||||
::: tinygrad.Tensor.schedule_with_vars
|
||||
::: tinygrad.Tensor.schedule
|
||||
::: tinygrad.Tensor.realize
|
||||
::: tinygrad.Tensor.replace
|
||||
::: tinygrad.Tensor.assign
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# TinyGPU
|
||||
|
||||
TinyGPU app lets you use AMD and NVIDIA GPUs on macOS over USB4/Thunderbolt with tinygrad.
|
||||
|
||||
## Requirements
|
||||
|
||||
- macOS (13.0+)
|
||||
- USB4/Thunderbolt port
|
||||
- A supported GPU (AMD RDNA3+ or NVIDIA Ampere+)
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Connect your GPU
|
||||
|
||||
Plug the supported GPU into your Mac over USB4/Thunderbolt.
|
||||
|
||||
### 2. Initiate the driver install
|
||||
|
||||
> **Note:** If tinygrad is cloned but not installed, run commands with `PYTHONPATH=.`
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/tinygrad/tinygrad/master/extra/setup_tinygpu_osx.sh | sh
|
||||
```
|
||||
|
||||
This downloads TinyGPU.app and triggers a system prompt to install the driver extension.
|
||||
|
||||
### 3. Enable the driver
|
||||
|
||||
You should see a system prompt: **"TinyGPU" would like to use a new driver extension**. Click **Open System Settings** and toggle TinyGPU on.
|
||||
|
||||
If you missed the prompt, go to **System Settings > General > Login Items & Extensions > Driver Extensions** and toggle TinyGPU on.
|
||||
|
||||
### 4. Compiler Setup
|
||||
|
||||
#### AMD
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/tinygrad/tinygrad/master/extra/setup_hipcomgr_osx.sh | sh
|
||||
```
|
||||
|
||||
#### NV
|
||||
|
||||
Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) if you don't have it.
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/tinygrad/tinygrad/master/extra/setup_nvcc_osx.sh | sh
|
||||
```
|
||||
|
||||
Make sure `~/.local/bin` is on your `PATH`:
|
||||
|
||||
```bash
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
```
|
||||
|
||||
### 5. Use it!
|
||||
|
||||
```bash
|
||||
DEV={AMD|NV} python3 -m tinygrad.llm
|
||||
```
|
||||
|
||||
**Note:** Use `JITBEAM=2` to search for faster kernels (one-time search cost, results cached).
|
||||
@@ -4,10 +4,10 @@ from tinygrad.dtype import DTypeLike, dtypes
|
||||
import math
|
||||
|
||||
# rewritten from numpy
|
||||
def rfftfreq(n: int, d: float = 1.0) -> Tensor:
|
||||
def rfftfreq(n: int, d: float = 1.0, device=None) -> Tensor:
|
||||
val = 1.0 / (n * d)
|
||||
N = n // 2 + 1
|
||||
results = Tensor.arange(N)
|
||||
results = Tensor.arange(N, device=device)
|
||||
return results * val
|
||||
|
||||
# just like in librosa
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Tuple
|
||||
import time
|
||||
from tinygrad import Tensor, TinyJit, nn, Context
|
||||
from tinygrad import Tensor, TinyJit, nn
|
||||
import gymnasium as gym
|
||||
from tinygrad.helpers import trange
|
||||
import numpy as np # TODO: remove numpy import
|
||||
@@ -55,7 +55,7 @@ if __name__ == "__main__":
|
||||
|
||||
@TinyJit
|
||||
def train_step(x:Tensor, selected_action:Tensor, reward:Tensor, old_log_dist:Tensor) -> Tuple[Tensor, Tensor, Tensor]:
|
||||
with Context(TRAINING=1):
|
||||
with Tensor.train():
|
||||
log_dist, value = model(x)
|
||||
action_mask = (selected_action.reshape(-1, 1) == Tensor.arange(log_dist.shape[1]).reshape(1, -1).expand(selected_action.shape[0], -1)).float()
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ from extra.lr_scheduler import OneCycleLR
|
||||
GPUS = [f'{Device.DEFAULT}:{i}' for i in range(getenv("GPUS", 1))]
|
||||
|
||||
# override tinygrad defaults
|
||||
Context(DEFAULT_FLOAT=dtypes.half, FUSE_OPTIM=1).__enter__()
|
||||
dtypes.default_float = dtypes.half
|
||||
Context(FUSE_OPTIM=1).__enter__()
|
||||
|
||||
# from https://github.com/tysam-code/hlb-CIFAR10/blob/main/main.py
|
||||
batchsize = getenv("BS", 1024)
|
||||
@@ -66,8 +67,8 @@ class ConvGroup:
|
||||
self.conv2 = nn.Conv2d(channels_out, channels_out, kernel_size=3, padding=1, bias=False)
|
||||
self.norm1 = nn.BatchNorm(channels_out, track_running_stats=False, eps=1e-12, momentum=hyp['net']['batch_norm_momentum'])
|
||||
self.norm2 = nn.BatchNorm(channels_out, track_running_stats=False, eps=1e-12, momentum=hyp['net']['batch_norm_momentum'])
|
||||
cast(Tensor, self.norm1.weight).is_param_(False)
|
||||
cast(Tensor, self.norm2.weight).is_param_(False)
|
||||
cast(Tensor, self.norm1.weight).requires_grad = False
|
||||
cast(Tensor, self.norm2.weight).requires_grad = False
|
||||
def __call__(self, x:Tensor) -> Tensor:
|
||||
x = self.norm1(self.conv1(x).max_pool2d().float()).cast(dtypes.default_float).quick_gelu()
|
||||
return self.norm2(self.conv2(x).float()).cast(dtypes.default_float).quick_gelu() + x
|
||||
@@ -121,7 +122,7 @@ if __name__ == "__main__":
|
||||
return ret.mul(hyp['opt']['loss_scale_scaler']*loss_batchsize_scaler).sum().div(hyp['opt']['loss_scale_scaler'])
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=1)
|
||||
@Tensor.train()
|
||||
def train_step(idxs:Tensor) -> Tensor:
|
||||
X, Y = X_train[idxs], Y_train[idxs]
|
||||
if len(GPUS) > 1:
|
||||
|
||||
+14
-15
@@ -1,6 +1,6 @@
|
||||
# model based off https://medium.com/data-science/going-beyond-99-mnist-handwritten-digits-recognition-cfff96337392
|
||||
from typing import Callable
|
||||
from tinygrad import Tensor, TinyJit, nn, GlobalCounters, function, Context
|
||||
from tinygrad import Tensor, TinyJit, nn, GlobalCounters
|
||||
from tinygrad.helpers import getenv, colored, trange
|
||||
from tinygrad.nn.datasets import mnist
|
||||
|
||||
@@ -15,31 +15,30 @@ class Model:
|
||||
nn.BatchNorm(64), Tensor.max_pool2d,
|
||||
lambda x: x.flatten(1), nn.Linear(576, 10)]
|
||||
|
||||
@function
|
||||
def __call__(self, x:Tensor) -> Tensor: return x.sequential(self.layers)
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=1)
|
||||
def train_step(self, X_train:Tensor, Y_train:Tensor) -> Tensor:
|
||||
opt.zero_grad()
|
||||
samples = Tensor.randint(getenv("BS", 512), high=X_train.shape[0])
|
||||
loss = self(X_train[samples]).sparse_categorical_crossentropy(Y_train[samples]).backward()
|
||||
return loss.realize(*opt.schedule_step())
|
||||
|
||||
@TinyJit
|
||||
def get_test_acc(self, X_test:Tensor, Y_test:Tensor) -> Tensor: return (self(X_test).argmax(axis=1) == Y_test).mean()*100
|
||||
|
||||
if __name__ == "__main__":
|
||||
X_train, Y_train, X_test, Y_test = mnist(fashion=getenv("FASHION"))
|
||||
|
||||
model = Model()
|
||||
opt = (nn.optim.Muon if getenv("MUON") else nn.optim.SGD if getenv("SGD") else nn.optim.Adam)(nn.state.get_parameters(model))
|
||||
|
||||
@TinyJit
|
||||
@Tensor.train()
|
||||
def train_step() -> Tensor:
|
||||
opt.zero_grad()
|
||||
samples = Tensor.randint(getenv("BS", 512), high=X_train.shape[0])
|
||||
loss = model(X_train[samples]).sparse_categorical_crossentropy(Y_train[samples]).backward()
|
||||
return loss.realize(*opt.schedule_step())
|
||||
|
||||
@TinyJit
|
||||
def get_test_acc() -> Tensor: return (model(X_test).argmax(axis=1) == Y_test).mean()*100
|
||||
|
||||
test_acc = float('nan')
|
||||
for i in (t:=trange(getenv("STEPS", 70))):
|
||||
GlobalCounters.reset() # NOTE: this makes it nice for DEBUG=2 timing
|
||||
loss = model.train_step(X_train, Y_train)
|
||||
if i%10 == 9: test_acc = model.get_test_acc(X_test, Y_test).item()
|
||||
loss = train_step()
|
||||
if i%10 == 9: test_acc = get_test_acc().item()
|
||||
t.set_description(f"loss: {loss.item():6.2f} test_accuracy: {test_acc:5.2f}%")
|
||||
|
||||
# verify eval acc
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# model based off https://towardsdatascience.com/going-beyond-99-mnist-handwritten-digits-recognition-cfff96337392
|
||||
from typing import List, Callable
|
||||
from tinygrad import Tensor, TinyJit, nn, GlobalCounters, Device, Context
|
||||
from tinygrad import Tensor, TinyJit, nn, GlobalCounters, Device
|
||||
from tinygrad.helpers import getenv, colored, trange
|
||||
from tinygrad.nn.datasets import mnist
|
||||
|
||||
@@ -31,7 +31,7 @@ if __name__ == "__main__":
|
||||
|
||||
@TinyJit
|
||||
def train_step() -> Tensor:
|
||||
with Context(TRAINING=1):
|
||||
with Tensor.train():
|
||||
opt.zero_grad()
|
||||
samples = Tensor.randint(getenv("BS", 512), high=X_train.shape[0])
|
||||
Xt, Yt = X_train[samples].shard_(GPUS, axis=0), Y_train[samples].shard_(GPUS, axis=0) # we shard the data on axis 0
|
||||
|
||||
@@ -5,7 +5,7 @@ from extra.onnx_helpers import get_example_inputs, validate
|
||||
|
||||
def load_onnx_model(onnx_file):
|
||||
run_onnx = OnnxRunner(onnx_file)
|
||||
run_onnx_jit = TinyJit(lambda **kwargs: next(iter(run_onnx({k:v.to(None) for k,v in kwargs.items()}).values())), prune=True)
|
||||
run_onnx_jit = TinyJit(lambda **kwargs: next(iter(run_onnx({k:v.to(None) for k,v in kwargs.items()}).values())), prune=True, optimize=True)
|
||||
return run_onnx_jit, run_onnx.graph_inputs
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from pathlib import Path
|
||||
from extra.models.efficientnet import EfficientNet
|
||||
from tinygrad.tensor import Tensor
|
||||
from tinygrad.device import Device
|
||||
from tinygrad.nn.state import get_state_dict, safe_save, safe_load, load_state_dict
|
||||
from extra.export_model import export_model
|
||||
from tinygrad.helpers import fetch
|
||||
from tinygrad.helpers import getenv, fetch
|
||||
import ast
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -13,13 +12,13 @@ if __name__ == "__main__":
|
||||
dirname = Path(__file__).parent
|
||||
# exporting a model that's loaded from safetensors doesn't work without loading in from safetensors first
|
||||
# loading the state dict from a safetensor file changes the generated kernels
|
||||
if Device.DEFAULT == "WEBGPU":
|
||||
if getenv("WEBGPU"):
|
||||
safe_save(get_state_dict(model), (dirname / "net.safetensors").as_posix())
|
||||
load_state_dict(model, safe_load(str(dirname / "net.safetensors")))
|
||||
mode = "clang" if Device.DEFAULT == "CPU" else "webgpu" if Device.DEFAULT == "WEBGPU" else ""
|
||||
mode = "clang" if getenv("CPU", "") != "" else "webgpu" if getenv("WEBGPU", "") != "" else ""
|
||||
prg, inp_sizes, out_sizes, state = export_model(model, mode, Tensor.randn(1,3,224,224))
|
||||
if Device.DEFAULT != "CPU":
|
||||
ext = "js" if Device.DEFAULT == "WEBGPU" else "json"
|
||||
if getenv("CPU", "") == "":
|
||||
ext = "js" if getenv("WEBGPU", "") != "" else "json"
|
||||
with open(dirname / f"net.{ext}", "w") as text_file:
|
||||
text_file.write(prg)
|
||||
else:
|
||||
@@ -69,6 +68,6 @@ if __name__ == "__main__":
|
||||
else printf("%s\\n", lbls[best_idx]);
|
||||
}""")
|
||||
|
||||
# DEV=CPU python3 examples/compile_efficientnet.py | clang -O2 -lm -x c - -o recognize && DEBUG=1 time ./recognize docs/showcase/stable_diffusion_by_tinygrad.jpg
|
||||
# CPU=1 python3 examples/compile_efficientnet.py | clang -O2 -lm -x c - -o recognize && DEBUG=1 time ./recognize docs/showcase/stable_diffusion_by_tinygrad.jpg
|
||||
# category : 281 (tabby, tabby cat) with 9.452788
|
||||
print('\n'.join(cprog))
|
||||
|
||||
@@ -35,11 +35,12 @@ def compile_onnx_model(onnx_model):
|
||||
tinyonnx = TinyOnnx(onnx_model)
|
||||
the_input = Tensor.randn(1,32)
|
||||
|
||||
linear, output_bufs = jit_model(tinyonnx, the_input)
|
||||
the_output = [tinyonnx.forward(the_input)]
|
||||
run, special_names = jit_model(tinyonnx, the_input)
|
||||
|
||||
functions, statements, bufs, bufs_to_save = compile_net(linear, output_bufs)
|
||||
functions, statements, bufs, bufs_to_save = compile_net(run, special_names)
|
||||
prg = export_model_clang(functions, statements, bufs, {}, ["input0"], ["output0"])
|
||||
|
||||
the_output = run(the_input)
|
||||
cprog = ["#include <string.h>", "#include <stdio.h>", "#include <stdlib.h>"]
|
||||
cprog.append(prg)
|
||||
|
||||
|
||||
+15
-7
@@ -5,9 +5,8 @@ with contextlib.suppress(ImportError): import tiktoken
|
||||
from tinygrad import Tensor, TinyJit, Device, GlobalCounters, Variable, dtypes
|
||||
from tinygrad.uop.ops import UOp
|
||||
from tinygrad.helpers import Timing, DEBUG, JIT, getenv, fetch, colored, trange
|
||||
from tinygrad.llm.gguf import gguf_load
|
||||
from tinygrad.nn import Embedding, Linear, LayerNorm
|
||||
from tinygrad.nn.state import torch_load, load_state_dict, get_state_dict
|
||||
from tinygrad.nn.state import gguf_load, torch_load, load_state_dict, get_state_dict
|
||||
from extra.bench_log import BenchEvent, WallTimeEvent
|
||||
|
||||
MAX_CONTEXT = getenv("MAX_CONTEXT", 128)
|
||||
@@ -22,6 +21,10 @@ class Attention:
|
||||
self.head_dim = dim // n_heads
|
||||
|
||||
def __call__(self, x:Tensor, start_pos:Variable, mask:Optional[Tensor]) -> Tensor:
|
||||
if mask is not None or start_pos.val == 0:
|
||||
# no symbolic shape qkv when consuming prompts
|
||||
start_pos = start_pos.val
|
||||
|
||||
if HALF: x = x.half()
|
||||
xqkv = self.c_attn(x).reshape(None, None, 3, self.n_heads, self.head_dim)
|
||||
xq, xk, xv = [xqkv[:, :, i, :, :] for i in range(3)]
|
||||
@@ -34,8 +37,12 @@ class Attention:
|
||||
# update the cache
|
||||
self.cache_kv[:, :, start_pos:start_pos+seqlen, :, :].assign(Tensor.stack(xk, xv)).realize()
|
||||
|
||||
keys = self.cache_kv[0][:, :start_pos+seqlen, :, :]
|
||||
values = self.cache_kv[1][:, :start_pos+seqlen, :, :]
|
||||
if start_pos > 0:
|
||||
keys = self.cache_kv[0][:, :start_pos+seqlen, :, :]
|
||||
values = self.cache_kv[1][:, :start_pos+seqlen, :, :]
|
||||
else:
|
||||
keys = xk
|
||||
values = xv
|
||||
|
||||
xq, keys, values = xq.transpose(1, 2), keys.transpose(1, 2), values.transpose(1, 2)
|
||||
return self.c_proj(xq.scaled_dot_product_attention(keys, values, mask).transpose(1, 2).reshape(bsz, seqlen, self.dim))
|
||||
@@ -78,14 +85,15 @@ class Transformer:
|
||||
seqlen = tokens.shape[1]
|
||||
tok_emb = self.wte(tokens)
|
||||
|
||||
# start_pos is a bound Variable, so everything below it stays symbolic
|
||||
pos_emb = self.wpe(self.allpos.shrink((None, (start_pos, start_pos+seqlen))))
|
||||
# not symbolic when consuming the prompt
|
||||
selected_pos = (0, seqlen) if start_pos.val == 0 else (start_pos, start_pos+1)
|
||||
pos_emb = self.wpe(self.allpos.shrink((None, selected_pos)))
|
||||
|
||||
h = tok_emb + pos_emb
|
||||
|
||||
if HALF: h = h.half()
|
||||
|
||||
mask = Tensor.full((1, 1, seqlen, start_pos+seqlen), float("-inf"), dtype=h.dtype).triu(start_pos+1) if seqlen > 1 else None
|
||||
mask = Tensor.full((1, 1, seqlen, start_pos.val+seqlen), float("-inf"), dtype=h.dtype).triu(start_pos.val+1) if seqlen > 1 else None
|
||||
|
||||
for hi in self.h: h = hi(h, start_pos, mask)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import itertools
|
||||
from typing import Callable
|
||||
from tinygrad import nn, Tensor, dtypes, Device, TinyJit, Context
|
||||
from tinygrad import nn, Tensor, dtypes, Device, TinyJit
|
||||
from tinygrad.helpers import getenv, trange, partition
|
||||
|
||||
class Model:
|
||||
@@ -35,21 +35,22 @@ if __name__ == "__main__":
|
||||
|
||||
params = nn.state.get_parameters(model)
|
||||
|
||||
# init params
|
||||
# init params, set requires grad on the ones we need gradients of
|
||||
for x in params:
|
||||
if x.requires_grad is None: x.requires_grad_()
|
||||
x.replace(x.contiguous())
|
||||
Tensor.realize(*params)
|
||||
|
||||
# split params (with grads) and buffers (without)
|
||||
params, buffers = partition(params, lambda x: x.is_param)
|
||||
params, buffers = partition(params, lambda x: x.requires_grad)
|
||||
print(f"params: {len(params)} buffers: {len(buffers)}")
|
||||
|
||||
# optim params
|
||||
pos_params = list(itertools.accumulate(params, lambda x,y: x+y.numel(), initial=0))
|
||||
adam_m = Tensor.zeros(pos_params[-1], device="CPU").contiguous()
|
||||
adam_v = Tensor.zeros(pos_params[-1], device="CPU").contiguous()
|
||||
adam_b1_t = Tensor.ones((1,), dtype=dtypes.float32, device="CPU").contiguous()
|
||||
adam_b2_t = Tensor.ones((1,), dtype=dtypes.float32, device="CPU").contiguous()
|
||||
adam_b1_t = Tensor.ones((1,), dtype=dtypes.float32, device="CPU", requires_grad=False).contiguous()
|
||||
adam_b2_t = Tensor.ones((1,), dtype=dtypes.float32, device="CPU", requires_grad=False).contiguous()
|
||||
adam_params = [adam_m, adam_v, adam_b1_t, adam_b2_t]
|
||||
|
||||
# create loss and grads. init all state so the JIT works on microbatch
|
||||
@@ -59,7 +60,7 @@ if __name__ == "__main__":
|
||||
Tensor.realize(*params, *buffers, *adam_params, loss, grads)
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=1)
|
||||
@Tensor.train()
|
||||
def microbatch():
|
||||
samples = Tensor.randint(BS // ACC_STEPS, high=X_train.shape[0])
|
||||
for t in params: t.grad = None
|
||||
|
||||
+31
-25
@@ -10,7 +10,7 @@ from extra.lr_scheduler import OneCycleLR
|
||||
from tinygrad import nn, dtypes, Tensor, Device, GlobalCounters, TinyJit, Variable
|
||||
from tinygrad.nn.state import get_state_dict
|
||||
from tinygrad.nn import optim
|
||||
from tinygrad.helpers import Context, BEAM, WINO, getenv, colored, prod, TRAINING
|
||||
from tinygrad.helpers import Context, BEAM, WINO, getenv, colored, prod
|
||||
from extra.bench_log import BenchEvent, WallTimeEvent
|
||||
|
||||
cifar_mean = [0.4913997551666284, 0.48215855929893703, 0.4465309133731618]
|
||||
@@ -19,8 +19,8 @@ cifar_std = [0.24703225141799082, 0.24348516474564, 0.26158783926049628]
|
||||
BS, STEPS = getenv("BS", 512), getenv("STEPS", 1000)
|
||||
EVAL_BS = getenv("EVAL_BS", BS)
|
||||
GPUS = [f'{Device.DEFAULT}:{i}' for i in range(getenv("GPUS", 1))]
|
||||
assert BS % len(GPUS) == 0, f"{BS=} is not a multiple of {len(GPUS)=}"
|
||||
assert EVAL_BS % len(GPUS) == 0, f"{EVAL_BS=} is not a multiple of {len(GPUS)=}"
|
||||
assert BS % len(GPUS) == 0, f"{BS=} is not a multiple of {len(GPUS)=}, uneven multi GPU is slow"
|
||||
assert EVAL_BS % len(GPUS) == 0, f"{EVAL_BS=} is not a multiple of {len(GPUS)=}, uneven multi GPU is slow"
|
||||
|
||||
class UnsyncedBatchNorm:
|
||||
def __init__(self, sz:int, eps=1e-5, affine=True, track_running_stats=True, momentum=0.1, num_devices=len(GPUS)):
|
||||
@@ -30,9 +30,9 @@ class UnsyncedBatchNorm:
|
||||
if affine: self.weight, self.bias = Tensor.ones(sz, dtype=dtypes.float32), Tensor.zeros(sz, dtype=dtypes.float32)
|
||||
else: self.weight, self.bias = None, None
|
||||
|
||||
self.running_mean = Tensor.zeros(num_devices, sz, dtype=dtypes.float32).is_param_(False)
|
||||
self.running_var = Tensor.ones(num_devices, sz, dtype=dtypes.float32).is_param_(False)
|
||||
self.num_batches_tracked = Tensor.zeros(1, dtype=dtypes.int).is_param_(False)
|
||||
self.running_mean = Tensor.zeros(num_devices, sz, dtype=dtypes.float32, requires_grad=False)
|
||||
self.running_var = Tensor.ones(num_devices, sz, dtype=dtypes.float32, requires_grad=False)
|
||||
self.num_batches_tracked = Tensor.zeros(1, dtype=dtypes.int, requires_grad=False)
|
||||
|
||||
def __call__(self, x:Tensor):
|
||||
xr = x.reshape(self.num_devices, -1, *x.shape[1:]).cast(dtypes.float32)
|
||||
@@ -44,7 +44,7 @@ class UnsyncedBatchNorm:
|
||||
return ret.reshape(x.shape).cast(x.dtype)
|
||||
|
||||
def calc_stats(self, x:Tensor):
|
||||
if TRAINING:
|
||||
if Tensor.training:
|
||||
# This requires two full memory accesses to x
|
||||
# https://github.com/pytorch/pytorch/blob/c618dc13d2aa23625cb0d7ada694137532a4fa33/aten/src/ATen/native/cuda/Normalization.cuh
|
||||
# There's "online" algorithms that fix this, like https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_Online_algorithm
|
||||
@@ -68,7 +68,8 @@ class UnsyncedBatchNorm:
|
||||
class BatchNorm(nn.BatchNorm2d if getenv("SYNCBN") else UnsyncedBatchNorm):
|
||||
def __init__(self, num_features):
|
||||
super().__init__(num_features, track_running_stats=False, eps=1e-12, momentum=0.85, affine=True)
|
||||
self.weight.is_param_(False)
|
||||
self.weight.requires_grad = False
|
||||
self.bias.requires_grad = True
|
||||
|
||||
class ConvGroup:
|
||||
def __init__(self, channels_in, channels_out):
|
||||
@@ -152,21 +153,26 @@ def train_cifar():
|
||||
|
||||
# ========== Model ==========
|
||||
def whitening(X, kernel_size=hyp['net']['kernel_size']):
|
||||
def _patches(data:Tensor, patch_size=(kernel_size,kernel_size)):
|
||||
def _cov(X):
|
||||
return (X.T @ X) / (X.shape[0] - 1)
|
||||
|
||||
def _patches(data, patch_size=(kernel_size,kernel_size)):
|
||||
h, w = patch_size
|
||||
_, c, _, _ = data.shape
|
||||
return data._pool((h, w)).permute(1, 4, 5, 0, 3, 2).reshape(c*h*w, -1)
|
||||
c = data.shape[1]
|
||||
axis = (2, 3)
|
||||
return np.lib.stride_tricks.sliding_window_view(data, window_shape=(h,w), axis=axis).transpose((0,3,2,1,4,5)).reshape((-1,c,h,w))
|
||||
|
||||
def _eigens(patches):
|
||||
cov = ((patches @ patches.T) / (patches.shape[1] - 1)).numpy()
|
||||
eigvals, eigvecs = np.linalg.eigh(cov, UPLO='U')
|
||||
return np.flip(eigvals, 0), np.flip(eigvecs.T.reshape(patches.shape[0], X.shape[1], kernel_size, kernel_size), 0)
|
||||
n,c,h,w = patches.shape
|
||||
Σ = _cov(patches.reshape(n, c*h*w))
|
||||
Λ, V = np.linalg.eigh(Σ, UPLO='U')
|
||||
return np.flip(Λ, 0), np.flip(V.T.reshape(c*h*w, c, h, w), 0)
|
||||
|
||||
# NOTE: np.linalg.eigh only supports float32 so the whitening layer weights need to be converted to float16 manually
|
||||
eigvals, eigvecs = _eigens(_patches(X.float()))
|
||||
W = eigvecs/np.sqrt(eigvals+1e-2)[:,None,None,None]
|
||||
Λ, V = _eigens(_patches(X.float().numpy()))
|
||||
W = V/np.sqrt(Λ+1e-2)[:,None,None,None]
|
||||
|
||||
return Tensor(W.astype(np.float32)).cast(dtypes.default_float).is_param_(False)
|
||||
return Tensor(W.astype(np.float32), requires_grad=False).cast(dtypes.default_float)
|
||||
|
||||
# ========== Loss ==========
|
||||
def cross_entropy(x:Tensor, y:Tensor, reduction:str='mean', label_smoothing:float=0.0) -> Tensor:
|
||||
@@ -218,7 +224,7 @@ def train_cifar():
|
||||
|
||||
@TinyJit
|
||||
def augmentations(X:Tensor, Y:Tensor):
|
||||
perms = Tensor.randperm(X.shape[0], device=X.device) # We reuse perms for cutmix, because they are expensive to generate
|
||||
perms = Tensor.randperm(X.shape[0], device=X.device) # We reuse perms for cutmix, because they are expensivne to generate
|
||||
if getenv("RANDOM_CROP", 1):
|
||||
X = random_crop(X, crop_size=32)
|
||||
if getenv("RANDOM_FLIP", 1):
|
||||
@@ -258,6 +264,7 @@ def train_cifar():
|
||||
# self.model_ema = copy.deepcopy(net) # won't work for opencl due to unpickeable pyopencl._cl.Buffer
|
||||
self.net_ema = SpeedyResNet(w)
|
||||
for net_ema_param, net_param in zip(get_state_dict(self.net_ema).values(), get_state_dict(net).values()):
|
||||
net_ema_param.requires_grad = False
|
||||
net_ema_param.assign(net_param.numpy())
|
||||
|
||||
@TinyJit
|
||||
@@ -300,7 +307,7 @@ def train_cifar():
|
||||
params_bias = []
|
||||
params_non_bias = []
|
||||
for params in params_dict:
|
||||
if params_dict[params].is_param:
|
||||
if params_dict[params].requires_grad is not False:
|
||||
if 'bias' in params:
|
||||
params_bias.append(params_dict[params])
|
||||
else:
|
||||
@@ -309,9 +316,6 @@ def train_cifar():
|
||||
opt_bias = optim.SGD(params_bias, lr=0.01, momentum=hyp['opt']['momentum'], nesterov=True, weight_decay=hyp['opt']['bias_decay'])
|
||||
opt_non_bias = optim.SGD(params_non_bias, lr=0.01, momentum=hyp['opt']['momentum'], nesterov=True, weight_decay=hyp['opt']['non_bias_decay'])
|
||||
|
||||
# realize model params and optimizer state before JIT to avoid cache misses
|
||||
Tensor.realize(*params_dict.values(), *opt_bias.b, *opt_non_bias.b)
|
||||
|
||||
# NOTE taken from the hlb_CIFAR repository, might need to be tuned
|
||||
initial_div_factor = hyp['opt']['initial_div_factor']
|
||||
final_lr_ratio = hyp['opt']['final_lr_ratio']
|
||||
@@ -328,7 +332,9 @@ def train_cifar():
|
||||
# index 0 for bias and 1 for non-bias
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
return loss.realize(*optimizer.schedule_step(), *lr_scheduler[0].schedule_step(), *lr_scheduler[1].schedule_step())
|
||||
optimizer.step()
|
||||
lr_scheduler[0].step()
|
||||
lr_scheduler[1].step()
|
||||
return loss.realize()
|
||||
|
||||
train_step_jitted = TinyJit(train_step)
|
||||
@@ -355,11 +361,11 @@ def train_cifar():
|
||||
i = 0
|
||||
eval_acc_pct = 0.0
|
||||
batcher = fetch_batches(X_train, Y_train, BS=BS, is_train=True)
|
||||
with Context(TRAINING=1):
|
||||
with Tensor.train():
|
||||
st = time.monotonic()
|
||||
while i <= STEPS:
|
||||
if i % getenv("EVAL_STEPS", STEPS) == 0 and i > 1 and not getenv("DISABLE_BACKWARD"):
|
||||
# Using Context(TRAINING=0) here actually bricks batchnorm, even with track_running_stats=True
|
||||
# Use Tensor.training = False here actually bricks batchnorm, even with track_running_stats=True
|
||||
corrects = []
|
||||
corrects_ema = []
|
||||
losses = []
|
||||
|
||||
+1
-1
@@ -445,7 +445,7 @@ After you are done speaking, output [EOS]. You are not Chad.
|
||||
print(f"using LLaMA{LLAMA_SUFFIX}-{args.size} model")
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(args.shard)) if args.shard > 1 else Device.DEFAULT
|
||||
llama = LLaMa.build(MODEL_PATH, TOKENIZER_PATH, model_gen=args.gen, model_size=args.size, quantize=args.quantize, device=device)
|
||||
param_bytes = sum(x.nbytes() for x in get_parameters(llama.model))
|
||||
param_bytes = sum(x.uop.size * x.dtype.itemsize for x in get_parameters(llama.model))
|
||||
|
||||
outputted = pre_prompt if chatbot else args.prompt
|
||||
start_pos, toks = 0, [llama.tokenizer.bos_id()] + llama.tokenizer.encode(outputted)
|
||||
|
||||
+4
-5
@@ -2,8 +2,7 @@ from pathlib import Path
|
||||
from typing import List
|
||||
import json, argparse, random, time, os
|
||||
from extra.models.llama import Transformer, convert_from_huggingface, convert_from_gguf, fix_bf16
|
||||
from tinygrad.llm.gguf import gguf_load
|
||||
from tinygrad.nn.state import safe_load, torch_load, load_state_dict, get_parameters
|
||||
from tinygrad.nn.state import safe_load, torch_load, load_state_dict, get_parameters, gguf_load
|
||||
from tinygrad import Tensor, dtypes, nn, Context, Device, GlobalCounters
|
||||
from tinygrad.helpers import Profiling, Timing, DEBUG, colored, fetch, tqdm
|
||||
from extra.bench_log import BenchEvent, WallTimeEvent
|
||||
@@ -102,7 +101,7 @@ class Int8Embedding:
|
||||
self.weight, self.scale = Tensor.ones(vocab_size, embed_size, dtype=dtypes.int8), Tensor.ones(vocab_size, dtype=dtypes.half)
|
||||
|
||||
def __call__(self, idx:Tensor) -> Tensor:
|
||||
if not hasattr(self, 'arange'): self.arange = Tensor.arange(self.vocab_sz).unsqueeze(-1)
|
||||
if not hasattr(self, 'arange'): self.arange = Tensor.arange(self.vocab_sz, requires_grad=False, device=self.weight.device).unsqueeze(-1)
|
||||
big_shp = idx.shape+(self.vocab_sz, self.embed_sz)
|
||||
arange, idx, vals = self.arange.expand(big_shp), idx.reshape(idx.shape+(1, 1)).expand(big_shp), (self.weight.cast(self.scale.dtype).T*self.scale).T
|
||||
return (arange == idx).mul(vals).sum(-2, dtype=vals.dtype)
|
||||
@@ -123,7 +122,7 @@ def NF4Linear(block_size):
|
||||
def __call__(self, x: Tensor) -> Tensor:
|
||||
high_bits = self.weight
|
||||
low_bits = (self.weight * 2 ** 4).contiguous()
|
||||
unpacked = Tensor.stack(high_bits, low_bits, dim=-1).div(2 ** 4, rounding_mode="trunc")
|
||||
unpacked = Tensor.stack(high_bits, low_bits, dim=-1).idiv(2 ** 4)
|
||||
unscaled = CODE[unpacked].to(x.device).reshape(-1, block_size) * self.scale
|
||||
return x.linear(unscaled.reshape(self.out_features, self.in_features).T)
|
||||
|
||||
@@ -325,7 +324,7 @@ if __name__ == "__main__":
|
||||
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(args.shard)) if args.shard > 1 else Device.DEFAULT
|
||||
model = build_transformer(args.model, model_size=args.size, quantize=args.quantize, device=device)
|
||||
param_bytes = sum(x.nbytes() for x in get_parameters(model))
|
||||
param_bytes = sum(x.uop.size * x.dtype.itemsize for x in get_parameters(model))
|
||||
|
||||
if not args.no_api and not args.benchmark:
|
||||
from bottle import Bottle, request, response, HTTPResponse, abort, static_file
|
||||
|
||||
+18
-19
@@ -2,14 +2,13 @@
|
||||
import os
|
||||
if "NOOPT" not in os.environ: os.environ["NOOPT"] = "1"
|
||||
from tinygrad import Device, nn, Tensor, dtypes
|
||||
Device.DEFAULT = "CPU"
|
||||
from train_gpt2 import GPT, GPTConfig
|
||||
from tinygrad.helpers import DEV, dedup, flatten, getenv, GlobalCounters, to_function_name, Context
|
||||
from tinygrad.helpers import dedup, flatten, getenv, GlobalCounters, to_function_name
|
||||
from tinygrad.engine.realize import get_kernel
|
||||
from tinygrad.schedule.memory import memory_planner
|
||||
from tinygrad.engine.memory import memory_planner
|
||||
from tinygrad.uop.ops import Ops
|
||||
|
||||
DEV.value = "CPU"
|
||||
|
||||
TIMING = getenv("TIMING")
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -23,23 +22,23 @@ if __name__ == "__main__":
|
||||
#B, T = Variable("B", 1, 128).bind(4), 64 #Variable("T", 1, 1024).bind(64)
|
||||
B, T = 4, 64
|
||||
|
||||
Tensor.training = True
|
||||
optimizer = nn.optim.Adam(nn.state.get_parameters(model), lr=1e-4)
|
||||
warmup_count = getenv("WARMUP", 3)
|
||||
with Context(TRAINING=1):
|
||||
for i in range(warmup_count): # TODO: why does it take three and not two to stabilize
|
||||
GlobalCounters.reset()
|
||||
X = Tensor.empty(4, 64, dtype=dtypes.int).reshape(B, T)
|
||||
Y = Tensor.empty(4, 64, dtype=dtypes.int).reshape(B, T)
|
||||
_, loss = model(X, Y)
|
||||
optimizer.zero_grad()
|
||||
if getenv("BACKWARD", 1):
|
||||
loss.backward()
|
||||
tensors = optimizer.schedule_step()
|
||||
else:
|
||||
tensors = []
|
||||
sched = loss.schedule(*tensors)
|
||||
print(f"calls {i}:", len(sched))
|
||||
#run_schedule(sched[:])
|
||||
for i in range(warmup_count): # TODO: why does it take three and not two to stabilize
|
||||
GlobalCounters.reset()
|
||||
X = Tensor.empty(4, 64, dtype=dtypes.int).reshape(B, T)
|
||||
Y = Tensor.empty(4, 64, dtype=dtypes.int).reshape(B, T)
|
||||
_, loss = model(X, Y)
|
||||
optimizer.zero_grad()
|
||||
if getenv("BACKWARD", 1):
|
||||
loss.backward()
|
||||
tensors = optimizer.schedule_step()
|
||||
else:
|
||||
tensors = []
|
||||
sched = loss.schedule(*tensors)
|
||||
print(f"calls {i}:", len(sched))
|
||||
#run_schedule(sched[:])
|
||||
sched = memory_planner(sched)
|
||||
ast_dedup = dedup([si.ast for si in sched if si.ast.op is Ops.SINK])
|
||||
srcs = {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import os, math, time
|
||||
import numpy as np
|
||||
from tinygrad import Tensor, nn, fetch, Device, TinyJit, GlobalCounters, Context
|
||||
from tinygrad import Tensor, nn, fetch, Device, TinyJit, GlobalCounters
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
@@ -25,7 +25,7 @@ class CausalSelfAttention:
|
||||
self.n_embd = config.n_embd
|
||||
# not really a 'bias', more of a mask, but following the OpenAI/HF naming though
|
||||
self.bias = Tensor.ones(1, 1, config.block_size, config.block_size).tril()
|
||||
self.bias.is_param_(False)
|
||||
self.bias.requires_grad = False
|
||||
|
||||
def __call__(self, x:Tensor):
|
||||
B, T, C = x.shape
|
||||
@@ -99,7 +99,7 @@ class GPT:
|
||||
|
||||
def __call__(self, idx:Tensor, targets=None):
|
||||
b, t = idx.shape
|
||||
pos = Tensor.arange(0, t)
|
||||
pos = Tensor.arange(0, t, device=idx.device)
|
||||
|
||||
tok_emb = self.wte(idx) # token embeddings of shape (b, t, n_embd)
|
||||
pos_emb = self.wpe(pos) # position embeddings of shape (t, n_embd)
|
||||
@@ -177,7 +177,7 @@ if __name__ == "__main__":
|
||||
if args.gpus > 1: x, y = x.shard(GPUS, axis=0), y.shard(GPUS, axis=0)
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=1)
|
||||
@Tensor.train()
|
||||
def step(x:Tensor, y:Tensor) -> Tensor:
|
||||
_, loss = model(x, y)
|
||||
optimizer.zero_grad()
|
||||
@@ -204,3 +204,4 @@ if __name__ == "__main__":
|
||||
top_k = 40
|
||||
y = model.generate(x, max_new_tokens, temperature=temperature, top_k=top_k)
|
||||
print(decode(y[0].tolist()))
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
# much taken from https://github.com/cloneofsimo/minRF
|
||||
from tinygrad import Tensor, nn, GlobalCounters, TinyJit, Context
|
||||
from tinygrad import Tensor, nn, GlobalCounters, TinyJit
|
||||
from tinygrad.helpers import getenv, trange
|
||||
from extra.models.llama import Attention, FeedForward, precompute_freqs_cis
|
||||
|
||||
@@ -135,7 +135,7 @@ if __name__ == "__main__":
|
||||
optimizer = nn.optim.Adam(nn.state.get_parameters(model), lr=5e-4)
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=1)
|
||||
@Tensor.train()
|
||||
def train_step():
|
||||
if getenv("OVERFIT"): samples = Tensor.zeros(getenv("BS", 256), dtype='int')
|
||||
else: samples = Tensor.randint(getenv("BS", 256), high=X_train.shape[0])
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import functools, argparse, pathlib
|
||||
from tinygrad import Tensor, nn, Device, GlobalCounters, Variable
|
||||
from tinygrad.helpers import Timing, Profiling, tqdm
|
||||
from tinygrad.helpers import Timing, Profiling, CI, tqdm
|
||||
from tinygrad.nn.state import torch_load, get_state_dict
|
||||
from extra.models.llama import FeedForward, Transformer
|
||||
from extra.bench_log import BenchEvent, WallTimeEvent
|
||||
@@ -36,7 +36,7 @@ if __name__ == "__main__":
|
||||
model = Transformer(n_layers=32, dim=4096, hidden_dim=14336, n_heads=32, n_kv_heads=8, norm_eps=1e-5, vocab_size=32000, feed_forward=functools.partial(MixtureFeedForward, 8), jit=False)
|
||||
model_state_dict = get_state_dict(model)
|
||||
|
||||
for k in (t := tqdm(state, disable=None)):
|
||||
for k in (t := tqdm(state, disable=CI)):
|
||||
if 'feed_forward.experts.' in k:
|
||||
expert_no = int(k.split('feed_forward.experts.')[1].split('.')[0])
|
||||
device = Device.DEFAULT + ":" + str((expert_no//2)+1)
|
||||
@@ -44,7 +44,7 @@ if __name__ == "__main__":
|
||||
device = Device.DEFAULT
|
||||
t.set_description(f"ram used: {GlobalCounters.mem_used/1e9:5.2f} GB, loading {k} to {device}")
|
||||
model_state_dict[k].replace(state[k].to(device).half()).realize()
|
||||
if t.disable: print(f"ram used: {GlobalCounters.mem_used/1e9:5.2f} GB")
|
||||
if CI: print(f"ram used: {GlobalCounters.mem_used/1e9:5.2f} GB")
|
||||
|
||||
from sentencepiece import SentencePieceProcessor
|
||||
spp = SentencePieceProcessor(model_file=args.weights + "/tokenizer.model")
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import os, random, pickle, queue, struct, math, functools, hashlib, time
|
||||
from typing import List
|
||||
from pathlib import Path
|
||||
from multiprocessing import Queue, Process, shared_memory, connection, Lock
|
||||
from multiprocessing import Queue, Process, shared_memory, connection, Lock, cpu_count
|
||||
|
||||
import numpy as np
|
||||
from tinygrad import dtypes, Tensor
|
||||
from tinygrad.helpers import getenv, prod, Context, round_up, tqdm, OSX, NUM_CPU_THREADS
|
||||
from tinygrad.helpers import getenv, prod, Context, round_up, tqdm, OSX
|
||||
from tinygrad.nn.state import TensorIO
|
||||
|
||||
### ResNet
|
||||
@@ -65,7 +65,17 @@ def loader_process(q_in, q_out, X:Tensor, seed):
|
||||
else:
|
||||
# pad data with training mean
|
||||
img = np.tile(np.array([[[123.68, 116.78, 103.94]]], dtype=np.uint8), (224, 224, 1))
|
||||
X[idx].flatten().assign(img.tobytes())
|
||||
|
||||
# broken out
|
||||
#img_tensor = Tensor(img.tobytes(), device='CPU')
|
||||
#storage_tensor = X[idx].contiguous().realize().lazydata.base.realized
|
||||
#storage_tensor._copyin(img_tensor.numpy())
|
||||
|
||||
# faster
|
||||
X[idx].contiguous().realize().uop.base.realized.as_buffer(force_zero_copy=True)[:] = img.tobytes()
|
||||
|
||||
# ideal
|
||||
#X[idx].assign(img.tobytes()) # NOTE: this is slow!
|
||||
q_out.put(idx)
|
||||
q_out.put(None)
|
||||
|
||||
@@ -131,7 +141,7 @@ def batch_load_resnet(batch_size=64, val=False, shuffle=True, seed=None, pad_fir
|
||||
else: X = Tensor.empty(*sz, dtype=dtypes.uint8, device=f"disk:/dev/shm/{shm_name}")
|
||||
Y = [None] * (batch_size*BATCH_COUNT)
|
||||
|
||||
for _ in range(NUM_CPU_THREADS.value):
|
||||
for _ in range(cpu_count()):
|
||||
p = Process(target=loader_process, args=(q_in, q_out, X, seed))
|
||||
p.daemon = True
|
||||
p.start()
|
||||
@@ -212,7 +222,7 @@ def batch_load_train_bert(BS:int, seed:int|None=None):
|
||||
rng.shuffle(fs)
|
||||
train_files.append(fs.pop(0))
|
||||
|
||||
cycle_length = min(NUM_CPU_THREADS.value, len(train_files))
|
||||
cycle_length = min(getenv("NUM_CPU_THREADS", min(os.cpu_count(), 8)), len(train_files))
|
||||
assert cycle_length > 0, "cycle_length must be greater than 0"
|
||||
|
||||
dataset = InterleavedDataset(train_files, cycle_length)
|
||||
@@ -254,8 +264,8 @@ def load_unet3d_data(preprocessed_dataset_dir, seed, queue_in, queue_out, X:Tens
|
||||
x = random_brightness_augmentation(x)
|
||||
x = gaussian_noise(x)
|
||||
|
||||
X[idx].flatten().assign(x.tobytes())
|
||||
Y[idx].flatten().assign(y.tobytes())
|
||||
X[idx].contiguous().realize().uop.base.realized.as_buffer(force_zero_copy=True)[:] = x.tobytes()
|
||||
Y[idx].contiguous().realize().uop.base.realized.as_buffer(force_zero_copy=True)[:] = y.tobytes()
|
||||
|
||||
queue_out.put(idx)
|
||||
queue_out.put(None)
|
||||
@@ -301,7 +311,7 @@ def batch_load_unet3d(preprocessed_dataset_dir:Path, batch_size:int=6, val:bool=
|
||||
X = Tensor.empty(*sz, dtype=dtypes.float32, device=f"disk:/dev/shm/{shm_name_x}")
|
||||
Y = Tensor.empty(*sz, dtype=dtypes.uint8, device=f"disk:/dev/shm/{shm_name_y}")
|
||||
|
||||
for _ in range(NUM_CPU_THREADS.value):
|
||||
for _ in range(cpu_count()):
|
||||
proc = Process(target=load_unet3d_data, args=(preprocessed_dataset_dir, seed, queue_in, queue_out, X, Y))
|
||||
proc.daemon = True
|
||||
proc.start()
|
||||
@@ -369,12 +379,12 @@ def load_retinanet_data(base_dir:Path, val:bool, queue_in:Queue, queue_out:Queue
|
||||
clipped_match_idxs = np.clip(match_idxs, 0, None)
|
||||
clipped_boxes, clipped_labels = tgt["boxes"][clipped_match_idxs], tgt["labels"][clipped_match_idxs]
|
||||
|
||||
boxes[idx].flatten().assign(clipped_boxes.tobytes())
|
||||
labels[idx].flatten().assign(clipped_labels.tobytes())
|
||||
matches[idx].flatten().assign(match_idxs.tobytes())
|
||||
anchors[idx].flatten().assign(anchor.tobytes())
|
||||
boxes[idx].contiguous().realize().uop.base.realized.as_buffer(force_zero_copy=True)[:] = clipped_boxes.tobytes()
|
||||
labels[idx].contiguous().realize().uop.base.realized.as_buffer(force_zero_copy=True)[:] = clipped_labels.tobytes()
|
||||
matches[idx].contiguous().realize().uop.base.realized.as_buffer(force_zero_copy=True)[:] = match_idxs.tobytes()
|
||||
anchors[idx].contiguous().realize().uop.base.realized.as_buffer(force_zero_copy=True)[:] = anchor.tobytes()
|
||||
|
||||
imgs[idx].flatten().assign(img.tobytes())
|
||||
imgs[idx].contiguous().realize().uop.base.realized.as_buffer(force_zero_copy=True)[:] = img.tobytes()
|
||||
|
||||
queue_out.put(idx)
|
||||
queue_out.put(None)
|
||||
@@ -396,7 +406,6 @@ def batch_load_retinanet(dataset, val:bool, base_dir:Path, batch_size:int=32, sh
|
||||
queue_in.put((idx, img, tgt))
|
||||
|
||||
def _setup_shared_mem(shm_name:str, size:tuple[int, ...], dtype:dtypes) -> tuple[shared_memory.SharedMemory, Tensor]:
|
||||
shm_name = f"{shm_name}_{os.getpid()}"
|
||||
if os.path.exists(f"/dev/shm/{shm_name}"): os.unlink(f"/dev/shm/{shm_name}")
|
||||
shm = shared_memory.SharedMemory(name=shm_name, create=True, size=prod(size))
|
||||
shm_tensor = Tensor.empty(*size, dtype=dtype, device=f"disk:/dev/shm/{shm_name}")
|
||||
@@ -437,7 +446,7 @@ def batch_load_retinanet(dataset, val:bool, base_dir:Path, batch_size:int=32, sh
|
||||
dataset_iter = iter(image_ids)
|
||||
|
||||
try:
|
||||
for _ in range(NUM_CPU_THREADS.value):
|
||||
for _ in range(cpu_count()):
|
||||
proc = Process(
|
||||
target=load_retinanet_data,
|
||||
args=(base_dir, val, queue_in, queue_out, imgs, boxes, labels),
|
||||
@@ -543,7 +552,7 @@ class BinIdxDataset:
|
||||
version, = struct.unpack("<Q", self.idx.read(8))
|
||||
assert version == 1, "unsupported index version"
|
||||
dtype_code, = struct.unpack("<B", self.idx.read(1))
|
||||
self.dtype = {1:np.dtype(np.uint8), 2:np.dtype(np.int8), 3:np.dtype(np.int16), 4:np.dtype(np.int32), 5:np.dtype(np.int64), 6:np.dtype(np.float64), 7:np.dtype(np.double), 8:np.dtype(np.uint16)}[dtype_code]
|
||||
self.dtype = {1:dtypes.uint8, 2:dtypes.int8, 3:dtypes.int16, 4:dtypes.int32, 5:dtypes.int64, 6:dtypes.float64, 7:dtypes.double, 8:dtypes.uint16}[dtype_code]
|
||||
self.count, = struct.unpack("<Q", self.idx.read(8))
|
||||
doc_count, = struct.unpack("<Q", self.idx.read(8))
|
||||
|
||||
@@ -560,7 +569,7 @@ class BinIdxDataset:
|
||||
self.doc_idx = self.idx_t[start:end].bitcast(dtypes.int64).numpy()
|
||||
|
||||
# bin file
|
||||
self.bin_t = Tensor(base_path.with_name(f"{base_path.name}.bin")).numpy()
|
||||
self.bin_t = Tensor(base_path.with_name(f"{base_path.name}.bin"))
|
||||
|
||||
def _index(self, idx) -> tuple[int, int]:
|
||||
return int(self.pointers[idx]), int(self.sizes[idx])
|
||||
@@ -569,7 +578,7 @@ class BinIdxDataset:
|
||||
ptr, size = self._index(idx)
|
||||
if length is None: length = size - offset
|
||||
ptr += offset * self.dtype.itemsize
|
||||
return self.bin_t[ptr:ptr+length*self.dtype.itemsize].view(self.dtype)
|
||||
return self.bin_t[ptr:ptr+length*self.dtype.itemsize].bitcast(self.dtype).to(None)
|
||||
|
||||
# https://docs.nvidia.com/megatron-core/developer-guide/latest/api-guide/datasets.html
|
||||
class GPTDataset:
|
||||
@@ -628,7 +637,7 @@ class GPTDataset:
|
||||
sample_parts.append(self.indexed_dataset.get(int(self.doc_idx[i]), offset=int(offset), length=length))
|
||||
|
||||
# concat all parts
|
||||
text = np.concatenate(sample_parts, axis=0)
|
||||
text = Tensor.cat(*sample_parts)
|
||||
|
||||
return text
|
||||
|
||||
@@ -771,8 +780,7 @@ def get_llama3_dataset(samples:int, seqlen:int, base_dir:Path, seed:int=0, val:b
|
||||
def iterate_llama3_dataset(dataset:BlendedGPTDataset, bs:int):
|
||||
for b in range(math.ceil(dataset.samples / bs)):
|
||||
batch = [dataset.get(b * bs + i) for i in range(bs)]
|
||||
stacked = np.stack(batch, axis=0)
|
||||
yield Tensor(stacked, device="NPY")
|
||||
yield Tensor.stack(batch, dim=0)
|
||||
|
||||
def batch_load_llama3(bs:int, samples:int, seqlen:int, base_dir:Path, seed:int=0, val:bool=True, small:bool=False):
|
||||
return iterate_llama3_dataset(get_llama3_dataset(samples, seqlen, base_dir, seed, val, small), bs)
|
||||
|
||||
@@ -2,7 +2,7 @@ import math
|
||||
from typing import Union
|
||||
|
||||
from tinygrad import Tensor, nn, dtypes
|
||||
from tinygrad.helpers import prod, argfix, Context, TRAINING
|
||||
from tinygrad.helpers import prod, argfix, Context
|
||||
from tinygrad.nn.state import get_parameters
|
||||
from extra.models.unet import UNetModel
|
||||
|
||||
@@ -57,7 +57,7 @@ class EmbeddingBert(nn.Embedding):
|
||||
def __call__(self, idx:Tensor) -> Tensor:
|
||||
if idx.numel() == 0: return Tensor.empty(idx.shape+(self.embed_sz,), dtype=self.weight.dtype, device=self.weight.device)
|
||||
arange_shp, weight_shp, big_shp = (1, 1, self.vocab_sz, 1), (1, 1, self.vocab_sz, self.embed_sz), idx.shape+(self.vocab_sz, self.embed_sz,)
|
||||
if not hasattr(self, 'arange'): self.arange = Tensor.arange(self.vocab_sz).reshape(arange_shp)
|
||||
if not hasattr(self, 'arange'): self.arange = Tensor.arange(self.vocab_sz, requires_grad=False, device=self.weight.device).reshape(arange_shp)
|
||||
arange, idx, vals = self.arange.expand(big_shp), idx.reshape(idx.shape+(1, 1,)).expand(big_shp), self.weight.cast(dtypes.default_float).reshape(weight_shp).expand(big_shp)
|
||||
return (arange == idx).where(vals, 0).sum(2, dtype=vals.dtype)
|
||||
|
||||
@@ -77,15 +77,15 @@ class FrozenBatchNorm2dRetinaNet(nn.BatchNorm2d):
|
||||
def __init__(self, sz:int, eps=1e-5, affine=True, track_running_stats=True, momentum=0.1):
|
||||
self.eps, self.track_running_stats, self.momentum = eps, track_running_stats, momentum
|
||||
|
||||
self.weight = Tensor.ones(sz, dtype=dtypes.float32).is_param_(False) if affine else None
|
||||
self.bias = Tensor.zeros(sz, dtype=dtypes.float32).is_param_(False) if affine else None
|
||||
self.weight = Tensor.ones(sz, dtype=dtypes.float32, requires_grad=False) if affine else None
|
||||
self.bias = Tensor.zeros(sz, dtype=dtypes.float32, requires_grad=False) if affine else None
|
||||
|
||||
if track_running_stats: self.running_mean, self.running_var = Tensor.zeros(sz, dtype=dtypes.float32).is_param_(False), Tensor.ones(sz, dtype=dtypes.float32).is_param_(False)
|
||||
self.num_batches_tracked = Tensor.zeros(1, dtype=dtypes.long).is_param_(False)
|
||||
if track_running_stats: self.running_mean, self.running_var = Tensor.zeros(sz, dtype=dtypes.float32, requires_grad=False), Tensor.ones(sz, dtype=dtypes.float32, requires_grad=False)
|
||||
self.num_batches_tracked = Tensor.zeros(1, dtype=dtypes.long, requires_grad=False)
|
||||
|
||||
def __call__(self, x:Tensor) -> Tensor:
|
||||
batch_mean, batch_var = super().calc_stats(x.cast(dtypes.float32))
|
||||
if self.track_running_stats and TRAINING:
|
||||
if self.track_running_stats and Tensor.training:
|
||||
self.running_mean.assign((1-self.momentum) * self.running_mean + self.momentum * batch_mean.detach().cast(self.running_mean.dtype))
|
||||
self.running_var.assign((1-self.momentum) * self.running_var + self.momentum * x.numel()/(x.numel()-x.shape[1]) * batch_var.detach().cast(self.running_var.dtype))
|
||||
self.num_batches_tracked += 1
|
||||
|
||||
@@ -325,18 +325,19 @@ def eval_stable_diffusion():
|
||||
# NOTE: the clip weights are the same between model.cond_stage_model and clip_encoder
|
||||
eval_timesteps = list(reversed(range(1, 1000, 20)))
|
||||
|
||||
with Context(DEV="CPU"):
|
||||
# The choice of alphas_prev[0] = alphas_cumprod[0] seems arbitrary, but it's how the mlperf ref does it:
|
||||
# alphas_prev = np.asarray([alphacums[0]] + alphacums[ddim_timesteps[:-1]].tolist())
|
||||
eval_alphas_prev = model.alphas_cumprod[0:1].cat(model.alphas_cumprod[list(range(1, 1000, 20))[:-1]]).to(GPUS).realize()
|
||||
inception = FidInceptionV3().load_from_pretrained(CKPTDIR / "inception" / "pt_inception-2015-12-05-6726825d.pth")
|
||||
vision_cfg = {'width': 1280, 'layers': 32, 'd_head': 80, 'image_size': 224, 'patch_size': 14}
|
||||
text_cfg = {'width': 1024, 'n_heads': 16, 'layers': 24, 'vocab_size': 49408, 'ctx_length': 77}
|
||||
clip.gelu = gelu_erf
|
||||
clip_encoder = OpenClipEncoder(1024, text_cfg, vision_cfg)
|
||||
loaded = torch_load(CKPTDIR / "clip" / "open_clip_pytorch_model.bin")
|
||||
loaded.update({"attn_mask": clip_encoder.attn_mask, "mean": clip_encoder.mean, "std": clip_encoder.std})
|
||||
load_state_dict(clip_encoder, loaded)
|
||||
original_device, Device.DEFAULT = Device.DEFAULT, "CPU"
|
||||
# The choice of alphas_prev[0] = alphas_cumprod[0] seems arbitrary, but it's how the mlperf ref does it:
|
||||
# alphas_prev = np.asarray([alphacums[0]] + alphacums[ddim_timesteps[:-1]].tolist())
|
||||
eval_alphas_prev = model.alphas_cumprod[0:1].cat(model.alphas_cumprod[list(range(1, 1000, 20))[:-1]]).to(GPUS).realize()
|
||||
inception = FidInceptionV3().load_from_pretrained(CKPTDIR / "inception" / "pt_inception-2015-12-05-6726825d.pth")
|
||||
vision_cfg = {'width': 1280, 'layers': 32, 'd_head': 80, 'image_size': 224, 'patch_size': 14}
|
||||
text_cfg = {'width': 1024, 'n_heads': 16, 'layers': 24, 'vocab_size': 49408, 'ctx_length': 77}
|
||||
clip.gelu = gelu_erf
|
||||
clip_encoder = OpenClipEncoder(1024, text_cfg, vision_cfg)
|
||||
loaded = torch_load(CKPTDIR / "clip" / "open_clip_pytorch_model.bin")
|
||||
loaded.update({"attn_mask": clip_encoder.attn_mask, "mean": clip_encoder.mean, "std": clip_encoder.std})
|
||||
load_state_dict(clip_encoder, loaded)
|
||||
Device.DEFAULT=original_device
|
||||
|
||||
@TinyJit
|
||||
def denoise_step(x:Tensor, x_x:Tensor, t_t:Tensor, uc_c:Tensor, sqrt_alphas_cumprod_t:Tensor, sqrt_one_minus_alphas_cumprod_t:Tensor,
|
||||
@@ -358,7 +359,7 @@ def eval_stable_diffusion():
|
||||
batch = batch.cat(batch[-1:].expand(bs - unpadded_bs, *batch[-1].shape))
|
||||
return batch, unpadded_bs
|
||||
|
||||
@Context(TRAINING=0)
|
||||
@Tensor.train(mode=False)
|
||||
def eval_unet(eval_inputs:list[dict], unet:UNetModel, cond_stage:FrozenOpenClipEmbedder, first_stage:AutoencoderKL,
|
||||
inception:FidInceptionV3, clip:OpenClipEncoder) -> tuple[float, float]:
|
||||
# Eval is divided into 5 jits, one per model
|
||||
@@ -498,10 +499,11 @@ def eval_stable_diffusion():
|
||||
|
||||
if __name__ == "__main__":
|
||||
# inference only
|
||||
Tensor.training = False
|
||||
|
||||
models = getenv("MODEL", "resnet,retinanet,unet3d,rnnt,bert").split(",")
|
||||
with Context(TRAINING=0):
|
||||
for m in models:
|
||||
nm = f"eval_{m}"
|
||||
if nm in globals():
|
||||
print(f"eval {m}")
|
||||
globals()[nm]()
|
||||
for m in models:
|
||||
nm = f"eval_{m}"
|
||||
if nm in globals():
|
||||
print(f"eval {m}")
|
||||
globals()[nm]()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# load each model here, quick benchmark
|
||||
from tinygrad import Tensor, GlobalCounters
|
||||
from tinygrad.helpers import getenv, Context
|
||||
from tinygrad.helpers import getenv
|
||||
import numpy as np
|
||||
|
||||
def test_model(model, *inputs):
|
||||
@@ -59,10 +59,11 @@ def spec_mrcnn():
|
||||
|
||||
if __name__ == "__main__":
|
||||
# inference only for now
|
||||
with Context(TRAINING=0):
|
||||
for m in getenv("MODEL", "resnet,retinanet,unet3d,rnnt,bert,mrcnn").split(","):
|
||||
nm = f"spec_{m}"
|
||||
if nm in globals():
|
||||
print(f"testing {m}")
|
||||
globals()[nm]()
|
||||
Tensor.training = False
|
||||
|
||||
for m in getenv("MODEL", "resnet,retinanet,unet3d,rnnt,bert,mrcnn").split(","):
|
||||
nm = f"spec_{m}"
|
||||
if nm in globals():
|
||||
print(f"testing {m}")
|
||||
globals()[nm]()
|
||||
|
||||
|
||||
+126
-552
@@ -2,8 +2,8 @@ import os, time, math, functools, random, contextlib
|
||||
from pathlib import Path
|
||||
import multiprocessing
|
||||
|
||||
from tinygrad import Device, GlobalCounters, Tensor, TinyJit, dtypes, Context
|
||||
from tinygrad.helpers import getenv, BEAM, WINO, round_up, diskcache_clear, Profiling, profile_marker, DEBUG
|
||||
from tinygrad import Device, GlobalCounters, Tensor, TinyJit, dtypes
|
||||
from tinygrad.helpers import getenv, BEAM, WINO, round_up, diskcache_clear, Profiling
|
||||
from tinygrad.nn.state import get_parameters, get_state_dict, load_state_dict, safe_load, safe_save
|
||||
from tinygrad.nn.optim import LAMB, LARS, SGD, OptimizerGroup, Adam, AdamW
|
||||
|
||||
@@ -157,7 +157,6 @@ def train_resnet():
|
||||
# input_std = Tensor([0.229, 0.224, 0.225], device=GPUS, dtype=dtypes.float32).reshape(1, -1, 1, 1)
|
||||
def normalize(x): return (x.permute([0, 3, 1, 2]) - input_mean).cast(dtypes.default_float)
|
||||
@TinyJit
|
||||
@Context(TRAINING=1)
|
||||
def train_step(X, Y):
|
||||
optimizer_group.zero_grad()
|
||||
X = normalize(X)
|
||||
@@ -171,7 +170,6 @@ def train_resnet():
|
||||
return loss.realize(), top_1.realize()
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=0)
|
||||
def eval_step(X, Y):
|
||||
X = normalize(X)
|
||||
out = model.forward(X)
|
||||
@@ -182,11 +180,11 @@ def train_resnet():
|
||||
def fake_data_get(batch_size):
|
||||
x = Tensor.zeros(batch_size, 224, 224, 3, dtype=dtypes.uchar).contiguous()
|
||||
y = [0] * batch_size
|
||||
return x.shard(GPUS, axis=0).realize(), Tensor(y).shard(GPUS, axis=0), y, None
|
||||
return x.shard(GPUS, axis=0).realize(), Tensor(y, requires_grad=False).shard(GPUS, axis=0), y, None
|
||||
|
||||
def data_get(it):
|
||||
x, y, cookie = next(it)
|
||||
return x.shard(GPUS, axis=0).realize(), Tensor(y).shard(GPUS, axis=0), y, cookie
|
||||
return x.shard(GPUS, axis=0).realize(), Tensor(y, requires_grad=False).shard(GPUS, axis=0), y, cookie
|
||||
|
||||
# ** epoch loop **
|
||||
step_times = []
|
||||
@@ -194,6 +192,7 @@ def train_resnet():
|
||||
# ** train loop **
|
||||
if MLLOGGER and RUNMLPERF:
|
||||
MLLOGGER.start(key=mllog_constants.EPOCH_START, value=e+1, metadata=dict(epoch_num=e+1))
|
||||
Tensor.training = True
|
||||
BEAM.value = TRAIN_BEAM
|
||||
|
||||
if INITMLPERF:
|
||||
@@ -247,7 +246,7 @@ def train_resnet():
|
||||
|
||||
if i == BENCHMARK:
|
||||
assert not math.isnan(loss)
|
||||
median_step_time = sorted(step_times)[BENCHMARK // 2] # in seconds
|
||||
median_step_time = sorted(step_times)[(BENCHMARK + 1) // 2] # in seconds
|
||||
estimated_total_minutes = int(median_step_time * steps_in_train_epoch * epochs / 60)
|
||||
print(f"Estimated training time: {estimated_total_minutes // 60}h{estimated_total_minutes % 60}m")
|
||||
print(f"epoch global_ops: {steps_in_train_epoch * GlobalCounters.global_ops:_}, "
|
||||
@@ -272,6 +271,7 @@ def train_resnet():
|
||||
eval_loss = 0.0
|
||||
eval_top_1 = 0
|
||||
eval_num_samples = 0
|
||||
Tensor.training = False
|
||||
BEAM.value = EVAL_BEAM
|
||||
|
||||
if INITMLPERF:
|
||||
@@ -413,7 +413,7 @@ def train_retinanet():
|
||||
layers_to_train = ["layer4", "layer3", "layer2", "layer1", "conv1"][:trainable_layers]
|
||||
for k, v in get_state_dict(backbone).items():
|
||||
if all([not k.startswith(layer) for layer in layers_to_train]):
|
||||
v.is_param_(False)
|
||||
v.requires_grad = False
|
||||
|
||||
def _data_get(it:Iterator[tuple[Tensor, ...]], val:bool=False):
|
||||
if val:
|
||||
@@ -593,7 +593,7 @@ def train_retinanet():
|
||||
|
||||
if i == BENCHMARK:
|
||||
assert not math.isnan(loss)
|
||||
median_step_time = sorted(step_times)[BENCHMARK // 2] # in seconds
|
||||
median_step_time = sorted(step_times)[(BENCHMARK + 1) // 2] # in seconds
|
||||
estimated_total_minutes = int(median_step_time * steps_in_train_epoch * EPOCHS / 60)
|
||||
print(f"Estimated training time: {estimated_total_minutes // 60}h{estimated_total_minutes % 60}m")
|
||||
print(f"epoch global_ops: {steps_in_train_epoch * GlobalCounters.global_ops:_}, "
|
||||
@@ -614,7 +614,7 @@ def train_retinanet():
|
||||
|
||||
if getenv("RESET_STEP", 1): _train_step.reset()
|
||||
|
||||
with Context(TRAINING=0):
|
||||
with Tensor.train(mode=False):
|
||||
if not RUNMLPERF:
|
||||
i, proc = 0, _fake_data_get(EVAL_BS, val=(val:=True))
|
||||
else:
|
||||
@@ -784,7 +784,7 @@ def train_unet3d():
|
||||
return x.shard(GPUS, axis=0).realize(), y.shard(GPUS, axis=0), cookie
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=1)
|
||||
@Tensor.train()
|
||||
def train_step(model, x, y):
|
||||
optim.zero_grad()
|
||||
|
||||
@@ -795,10 +795,10 @@ def train_unet3d():
|
||||
optim.step()
|
||||
return loss.realize()
|
||||
|
||||
@Context(TRAINING=0)
|
||||
@Tensor.train(mode=False)
|
||||
def eval_step(model, x, y):
|
||||
y_hat, y = sliding_window_inference(model, x, y, gpus=GPUS)
|
||||
y_hat, y = Tensor(y_hat), Tensor(y)
|
||||
y_hat, y = Tensor(y_hat), Tensor(y, requires_grad=False)
|
||||
loss = dice_ce_loss(y_hat, y)
|
||||
score = dice_score(y_hat, y)
|
||||
return loss.realize(), score.realize()
|
||||
@@ -868,7 +868,7 @@ def train_unet3d():
|
||||
i += 1
|
||||
|
||||
if i == BENCHMARK:
|
||||
median_step_time = sorted(step_times)[BENCHMARK // 2] # in seconds
|
||||
median_step_time = sorted(step_times)[(BENCHMARK + 1) // 2] # in seconds
|
||||
estimated_total_minutes = int(median_step_time * SAMPLES_PER_EPOCH * NUM_EPOCHS / 60)
|
||||
print(f"Estimated training time: {estimated_total_minutes // 60}h{estimated_total_minutes % 60}m")
|
||||
if (TRAIN_BEAM or EVAL_BEAM) and epoch == start_epoch: break
|
||||
@@ -919,7 +919,6 @@ def train_rnnt():
|
||||
pass
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=0)
|
||||
def eval_step_bert(model, input_ids:Tensor, segment_ids:Tensor, attention_mask:Tensor, masked_positions:Tensor, masked_lm_ids:Tensor,
|
||||
masked_lm_weights:Tensor, next_sentence_labels:Tensor, GPUS):
|
||||
for t in [input_ids, segment_ids, attention_mask, masked_positions, masked_lm_ids, masked_lm_weights, next_sentence_labels]:
|
||||
@@ -1107,7 +1106,6 @@ def train_bert():
|
||||
MLLOGGER.start(key=mllog_constants.EPOCH_START, value=i*GBS, metadata={"epoch_num": i*GBS})
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=1)
|
||||
def train_step_bert(input_ids:Tensor, segment_ids:Tensor, attention_mask:Tensor,
|
||||
masked_positions:Tensor, masked_lm_ids:Tensor, masked_lm_weights:Tensor, next_sentence_labels:Tensor):
|
||||
for t in [input_ids, segment_ids, attention_mask, masked_positions, masked_lm_ids, masked_lm_weights, next_sentence_labels]:
|
||||
@@ -1135,6 +1133,7 @@ def train_bert():
|
||||
|
||||
while train_data is not None and i < train_steps and not achieved:
|
||||
if getenv("TRAIN", 1):
|
||||
Tensor.training = True
|
||||
BEAM.value = TRAIN_BEAM
|
||||
st = time.perf_counter()
|
||||
GlobalCounters.reset()
|
||||
@@ -1168,7 +1167,7 @@ def train_bert():
|
||||
i += 1
|
||||
|
||||
if i == BENCHMARK:
|
||||
median_step_time = sorted(step_times)[BENCHMARK // 2] # in seconds
|
||||
median_step_time = sorted(step_times)[(BENCHMARK + 1) // 2] # in seconds
|
||||
estimated_total_minutes = int(median_step_time * train_steps / 60)
|
||||
print(f"Estimated training time: {estimated_total_minutes // 60}h{estimated_total_minutes % 60}m")
|
||||
print(f"epoch global_ops: {train_steps * GlobalCounters.global_ops:_}, "
|
||||
@@ -1187,6 +1186,7 @@ def train_bert():
|
||||
eval_lm_accs = []
|
||||
eval_clsf_accs = []
|
||||
eval_times = []
|
||||
Tensor.training = False
|
||||
BEAM.value = EVAL_BEAM
|
||||
|
||||
for j in tqdm(range(max_eval_steps), desc="Evaluating", total=max_eval_steps, disable=BENCHMARK):
|
||||
@@ -1282,97 +1282,40 @@ def train_bert():
|
||||
previous_step = i
|
||||
|
||||
def train_llama3():
|
||||
from examples.mlperf.models.flat_llama import FlatTransformer, apply_grad, FP8_DTYPE, MXFP8, MXFP4
|
||||
from extra.models.llama import Transformer
|
||||
from examples.llama3 import MODEL_PARAMS
|
||||
from examples.mlperf.lr_schedulers import CosineAnnealingLRWithWarmup
|
||||
from examples.mlperf.optim import GradAccClipAdamW, clip_grads
|
||||
|
||||
INITMLPERF = getenv("INITMLPERF")
|
||||
RUNMLPERF = getenv("RUNMLPERF")
|
||||
LOGMLPERF = getenv("LOGMLPERF")
|
||||
BENCHMARK = getenv("BENCHMARK")
|
||||
|
||||
config = {}
|
||||
BASEDIR = config["BASEDIR"] = Path(getenv("BASEDIR", "/raid/datasets/c4/"))
|
||||
BS = config["BS"] = getenv("BS", 16)
|
||||
grad_acc = config["GRADIENT_ACC_STEPS"] = getenv("GRADIENT_ACC_STEPS", 1)
|
||||
assert grad_acc == 1, f"{grad_acc=} is not supported"
|
||||
GBS = config["GLOBAL_BATCH_SIZE"] = BS * grad_acc
|
||||
SEED = config["SEED"] = getenv("SEED", 5760)
|
||||
DATA_SEED = config["DATA_SEED"] = getenv("DATA_SEED", SEED)
|
||||
SEQLEN = config["SEQLEN"] = getenv("SEQLEN", 8192)
|
||||
TRAIN_ON_VAL = config["TRAIN_ON_VAL"] = getenv("TRAIN_ON_VAL", 0)
|
||||
SMALL = config["SMALL"] = getenv("SMALL", 0)
|
||||
SAMPLES = config["SAMPLES"] = getenv("SAMPLES", 5_760 if TRAIN_ON_VAL else 1_200_000 * 1152)
|
||||
EVAL_SAMPLES = config["EVAL_SAMPLES"] = getenv("EVAL_SAMPLES", 5760 if not SMALL else 1024)
|
||||
MAX_STEPS = config["MAX_STEPS"] = getenv("MAX_STEPS", math.ceil(1_200_000 * 1152 / GBS))
|
||||
WARMUP_STEPS = config["WARMUP_STEPS"] = getenv("WARMUP_STEPS", math.ceil(8000 * 1152 / GBS))
|
||||
LR = config["LR"] = getenv("LR", 8e-5 * GBS / 1152)
|
||||
END_LR = config["END_LR"] = getenv("END_LR", 8e-7)
|
||||
EVAL_FREQ = config["EVAL_FREQ"] = getenv("EVAL_FREQ", 46080)
|
||||
EVAL_BS = config["EVAL_BS"] = getenv("EVAL_BS", 16)
|
||||
EVAL_TARGET = config["EVAL_TARGET"] = getenv("EVAL_TARGET", 5.6)
|
||||
|
||||
if LOGMLPERF:
|
||||
from mlperf_logging import mllog
|
||||
import mlperf_logging.mllog.constants as mllog_constants
|
||||
|
||||
mllog.config(filename=f"result_llama31_{SEED}.log")
|
||||
mllog.config(root_dir=Path(__file__).parents[3].as_posix())
|
||||
MLLOGGER = mllog.get_mllogger()
|
||||
MLLOGGER.logger.propagate = False
|
||||
|
||||
LLAMA_BENCHMARK = mllog_constants.LLAMA31_405B if getenv("LLAMA3_SIZE", "8B") == "405B" else mllog_constants.LLAMA31_8B
|
||||
|
||||
if INITMLPERF:
|
||||
assert BENCHMARK, "BENCHMARK must be set for INITMLPERF"
|
||||
MLLOGGER.event(key=mllog_constants.SUBMISSION_ORG, value="tinycorp")
|
||||
MLLOGGER.event(key=mllog_constants.SUBMISSION_PLATFORM, value=getenv("SUBMISSION_PLATFORM", "tinybox"))
|
||||
MLLOGGER.event(key=mllog_constants.SUBMISSION_DIVISION, value=mllog_constants.CLOSED)
|
||||
MLLOGGER.event(key=mllog_constants.SUBMISSION_STATUS, value=mllog_constants.ONPREM)
|
||||
|
||||
MLLOGGER.event(key=mllog_constants.SUBMISSION_BENCHMARK, value=LLAMA_BENCHMARK)
|
||||
|
||||
diskcache_clear()
|
||||
MLLOGGER.event(key=mllog_constants.CACHE_CLEAR, value=True)
|
||||
MLLOGGER.start(key=mllog_constants.INIT_START, value=None)
|
||||
|
||||
if RUNMLPERF:
|
||||
MLLOGGER.start(key=mllog_constants.RUN_START, value=None)
|
||||
MLLOGGER.event(key=mllog_constants.SEED, value=SEED)
|
||||
|
||||
MLLOGGER.event(key=mllog_constants.GLOBAL_BATCH_SIZE, value=GBS)
|
||||
MLLOGGER.event(key=mllog_constants.MAX_SEQUENCE_LENGTH, value=SEQLEN)
|
||||
MLLOGGER.event(key=mllog_constants.MAX_STEPS, value=MAX_STEPS)
|
||||
MLLOGGER.event(key=mllog_constants.GRADIENT_ACCUMULATION_STEPS, value=grad_acc)
|
||||
MLLOGGER.event(key=mllog_constants.EVAL_SAMPLES, value=EVAL_SAMPLES)
|
||||
MLLOGGER.event(key=mllog_constants.TRAIN_SAMPLES, value=SAMPLES)
|
||||
|
||||
MLLOGGER.event(key=mllog_constants.OPT_NAME, value=mllog_constants.ADAMW)
|
||||
MLLOGGER.event(key=mllog_constants.OPT_BASE_LR, value=LR)
|
||||
MLLOGGER.event(key=mllog_constants.OPT_END_LR, value=END_LR)
|
||||
MLLOGGER.event(key=mllog_constants.OPT_ADAMW_BETA_1, value=0.9)
|
||||
MLLOGGER.event(key=mllog_constants.OPT_ADAMW_BETA_2, value=0.95)
|
||||
MLLOGGER.event(key=mllog_constants.OPT_ADAMW_EPSILON, value=1e-5)
|
||||
MLLOGGER.event(key=mllog_constants.OPT_ADAMW_WEIGHT_DECAY, value=0.1)
|
||||
MLLOGGER.event(key=mllog_constants.OPT_LR_WARMUP_STEPS, value=WARMUP_STEPS)
|
||||
MLLOGGER.event(key=mllog_constants.NUM_WARMUP_STEPS, value=WARMUP_STEPS)
|
||||
MLLOGGER.event(key=mllog_constants.OPT_LR_DECAY_STEPS, value=MAX_STEPS - WARMUP_STEPS)
|
||||
MLLOGGER.event(key=mllog_constants.OPT_LR_DECAY_SCHEDULE, value="cosine with linear warmup")
|
||||
MLLOGGER.event(key=mllog_constants.OPT_GRADIENT_CLIP_NORM, value=1.0)
|
||||
else:
|
||||
MLLOGGER = None
|
||||
# LR=1e-4 TRAIN_ON_VAL=1 DEFAULT_FLOAT=bfloat16 JITBEAM=2 OPTIM_DTYPE=bfloat16 LLAMA3_SIZE=1B WARMUP_STEPS=36 DECAY_STEPS=360 SEQLEN=512 PYTHONPATH=. AMD=1 AMD_LLVM=0 MODEL=llama3 python3 examples/mlperf/model_train.py
|
||||
# trains to 7
|
||||
|
||||
opt_adamw_beta_1 = 0.9
|
||||
opt_adamw_beta_2 = 0.95
|
||||
opt_adamw_epsilon = 1e-5
|
||||
opt_adamw_weight_decay = 0.1
|
||||
|
||||
opt_learning_rate_warmup_steps = WARMUP_STEPS
|
||||
opt_learning_rate_decay_steps = MAX_STEPS - opt_learning_rate_warmup_steps
|
||||
opt_base_learning_rate = LR
|
||||
opt_end_learning_rate = END_LR
|
||||
|
||||
Tensor.manual_seed(SEED) # seed for weight initialization
|
||||
opt_gradient_clip_norm = 1.0
|
||||
opt_learning_rate_warmup_steps = getenv("WARMUP_STEPS", math.ceil(8000 * 1152 / GBS))
|
||||
opt_learning_rate_decay_steps = getenv("MAX_STEPS", math.ceil(1_200_000 * 1152 / GBS)) - opt_learning_rate_warmup_steps
|
||||
opt_base_learning_rate = getenv("LR", 8e-5 * GBS / 1152) # NOTE: cannot change for benchmark
|
||||
opt_end_learning_rate = getenv("END_LR", 8e-7)
|
||||
|
||||
# ** init wandb **
|
||||
WANDB = getenv("WANDB")
|
||||
@@ -1384,44 +1327,44 @@ def train_llama3():
|
||||
model_params = MODEL_PARAMS[getenv("LLAMA3_SIZE", "8B")]["args"]
|
||||
# vocab_size from the mixtral tokenizer
|
||||
if not SMALL: model_params |= {"vocab_size": 32000}
|
||||
real_vocab_size = model_params['vocab_size']
|
||||
if (llama_layers:=getenv("LLAMA_LAYERS")) != 0: model_params['n_layers'] = llama_layers
|
||||
print(f"model parameters: {model_params}")
|
||||
|
||||
# pad vocab
|
||||
if (MP := getenv("MP", 1)) > 1: model_params['vocab_size'] = round_up(model_params['vocab_size'], 256 * MP)
|
||||
vocab_mask:Tensor = Tensor.arange(model_params['vocab_size']).reshape(1, 1, -1) >= real_vocab_size
|
||||
|
||||
model = FlatTransformer(**model_params, max_context=SEQLEN)
|
||||
|
||||
model = Transformer(**model_params, max_context=SEQLEN, jit=False, disable_kv_cache=True)
|
||||
params = get_parameters(model)
|
||||
# weights are all bfloat16 for now
|
||||
assert params and all(p.dtype == dtypes.bfloat16 for p in params)
|
||||
|
||||
if getenv("EMPTYWEIGHT"):
|
||||
if getenv("FAKEDATA"):
|
||||
for v in get_parameters(model):
|
||||
v = v.assign(Tensor.empty(v.shape, dtype=v.dtype))
|
||||
v = v.assign(Tensor.empty(v.shape))
|
||||
|
||||
is_dp = (DP := getenv("DP", 1)) > 1
|
||||
is_mp = (MP := getenv("MP", 1)) > 1
|
||||
is_sharding = is_dp or is_mp
|
||||
device_count = max(DP, MP)
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(device_count))
|
||||
if (DP := getenv("DP", 1)) > 1:
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(DP))
|
||||
for v in get_parameters(model):
|
||||
v.shard_(device, axis=None)
|
||||
|
||||
model.shard(device, is_mp)
|
||||
|
||||
if is_dp: vocab_mask.shard_(device, axis=None).realize()
|
||||
if is_mp: vocab_mask.shard_(device, axis=2).realize()
|
||||
|
||||
is_offload_optim = bool(getenv("OFFLOAD_OPTIM"))
|
||||
is_fake_offload = Device.DEFAULT == "NULL"
|
||||
optim_device = ("CPU" if not is_fake_offload else "NULL:99") if is_offload_optim else None
|
||||
optim = GradAccClipAdamW(params, lr=0.0, b1=opt_adamw_beta_1, b2=opt_adamw_beta_2,
|
||||
eps=opt_adamw_epsilon, weight_decay=opt_adamw_weight_decay, grad_acc=grad_acc, device=optim_device)
|
||||
|
||||
for p in optim.params:
|
||||
grad_dtype = dtypes.bfloat16 if p.dtype == FP8_DTYPE else p.dtype
|
||||
p.grad = p.zeros_like(dtype=grad_dtype).contiguous()
|
||||
grads = [p.grad for p in optim.params]
|
||||
if (MP := getenv("MP", 1)) > 1:
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(MP))
|
||||
for k,v in get_state_dict(model).items():
|
||||
if 'scale' in k: v.shard_(device, axis=None) # from quantized
|
||||
elif '.attention.wq' in k: v.shard_(device, axis=0)
|
||||
elif '.attention.wk' in k: v.shard_(device, axis=0)
|
||||
elif '.attention.wv' in k: v.shard_(device, axis=0)
|
||||
elif '.attention.wo' in k: v.shard_(device, axis=1)
|
||||
elif '.feed_forward.w1.' in k: v.shard_(device, axis=0)
|
||||
elif '.feed_forward.w2.' in k: v.shard_(device, axis=1)
|
||||
elif '.feed_forward.w3.' in k: v.shard_(device, axis=0)
|
||||
elif 'tok_embeddings.weight' in k: v.shard_(device, axis=0)
|
||||
elif 'output.weight' in k: v.shard_(device, axis=0)
|
||||
else:
|
||||
# attention_norm, ffn_norm, norm
|
||||
v.shard_(device, axis=None)
|
||||
# prevents memory spike on device 0
|
||||
v.realize()
|
||||
|
||||
optim = AdamW(get_parameters(model), lr=0.0,
|
||||
b1=opt_adamw_beta_1, b2=opt_adamw_beta_2, eps=opt_adamw_epsilon, weight_decay=opt_adamw_weight_decay)
|
||||
scheduler = CosineAnnealingLRWithWarmup(optim, opt_base_learning_rate, opt_end_learning_rate, opt_learning_rate_warmup_steps, opt_learning_rate_decay_steps)
|
||||
|
||||
if resume_ckpt := getenv("RESUME_CKPT"):
|
||||
@@ -1433,172 +1376,102 @@ def train_llama3():
|
||||
print(f"loading optim checkpoint from {fn}")
|
||||
load_state_dict(scheduler, safe_load(fn), realize=False)
|
||||
|
||||
fp8_amax = [t for ts in model._fp8_amax.values() for t in ts]
|
||||
fp8_next_amax = [t for ts in model._fp8_next_amax.values() for t in ts]
|
||||
fp8_grad_amax = [t for ts in model._fp8_grad_amax.values() for t in ts]
|
||||
fp8_next_grad_amax = [t for ts in model._fp8_next_grad_amax.values() for t in ts]
|
||||
fp8_inv_scales = list(model._fp8_inv_scale.values()) + list(model._fp8_next_inv_scale.values())
|
||||
|
||||
from tinygrad.nn.state import get_state_dict
|
||||
model_state = get_state_dict(model)
|
||||
for wname in model._fp8_inv_scale:
|
||||
w = model_state[wname]
|
||||
w._inv_scale = model._fp8_inv_scale[wname]
|
||||
w._next_inv_scale = model._fp8_next_inv_scale[wname]
|
||||
if optim.master_params:
|
||||
idx = next(j for j, p in enumerate(optim.params) if p is w)
|
||||
master = optim.master_params[idx]
|
||||
inv = w._inv_scale if w._inv_scale.device == master.device else w._inv_scale.to(master.device)
|
||||
if MXFP8:
|
||||
from extra.gemm.cdna_asm_gemm import _mx_block_scale
|
||||
bs = _mx_block_scale(inv.reshape(-1, inv.shape[-1])).reshape(w.shape)
|
||||
master.assign((master * bs).contiguous())
|
||||
else:
|
||||
master.assign((master * inv.reshape(*inv.shape, *([1]*(w.ndim-inv.ndim)))).contiguous())
|
||||
|
||||
# realize everything here
|
||||
if optim.master_params: Tensor.realize(*optim.master_params)
|
||||
loss_acc = Tensor.zeros(1, dtype=dtypes.float32, device=device)
|
||||
Tensor.realize(loss_acc, *optim.params, *fp8_inv_scales, *fp8_amax, *fp8_next_amax, *fp8_grad_amax, *fp8_next_grad_amax)
|
||||
|
||||
@TinyJit
|
||||
def minibatch(tokens:Tensor):
|
||||
model.reset_amax()
|
||||
if is_dp: tokens = tokens.to(None).shard(device, 0)
|
||||
if is_mp: tokens = tokens.shard(device)
|
||||
if not is_sharding: tokens = tokens.to(None)
|
||||
logits:Tensor = model(tokens[:, :-1], save=bool(SMALL))
|
||||
if getenv("FAST_CE", 0):
|
||||
from extra.llama_kernels.fused_ce import fused_ce_loss
|
||||
loss = fused_ce_loss(logits.cast(dtypes.bfloat16), tokens[:, 1:], label_smoothing=0.0)
|
||||
else:
|
||||
loss = vocab_mask.where(-1e9, logits).sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
@Tensor.train()
|
||||
def train_step(model, tokens:Tensor):
|
||||
optim.zero_grad()
|
||||
if (DP := getenv("DP", 1)) > 1:
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(DP))
|
||||
tokens = tokens.shard(device, 0)
|
||||
if (MP := getenv("MP", 1)) > 1:
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(MP))
|
||||
tokens = tokens.shard(device)
|
||||
logits:Tensor = model(tokens[:, :-1], start_pos=0, temperature=math.nan)
|
||||
loss = logits.sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
loss.backward()
|
||||
# L2 norm grad clip
|
||||
# https://github.com/NVIDIA/NeMo/blob/3368c3fc0b4a186ab33a1d68a504315100c0b2a6/nemo/collections/nlp/modules/common/megatron/clip_grads.py#L57
|
||||
# https://docs.pytorch.org/docs/stable/generated/torch.nn.utils.clip_grad_norm_.html
|
||||
if not getenv("DISABLE_GRAD_CLIP_NORM"):
|
||||
total_norm = Tensor(0.0, dtype=dtypes.float32, device=optim.params[0].device)
|
||||
for p in optim.params:
|
||||
total_norm += p.grad.float().square().sum()
|
||||
total_norm = total_norm.sqrt().contiguous()
|
||||
for p in optim.params:
|
||||
p.grad = p.grad * (opt_gradient_clip_norm / (total_norm + 1e-6)).clamp(max_=1.0)
|
||||
|
||||
for g, new_g in zip(grads, loss.gradient(*optim.params)):
|
||||
apply_grad(g, new_g.uop)
|
||||
|
||||
loss_acc.assign(loss_acc + loss.flatten().float())
|
||||
return loss_acc.realize(*grads, *fp8_amax, *fp8_next_amax, *fp8_grad_amax, *fp8_next_grad_amax)
|
||||
|
||||
@TinyJit
|
||||
def optim_step():
|
||||
grad_norm = clip_grads(grads, grad_acc, 1.0)
|
||||
optim.fstep(grads, grad_norm)
|
||||
optim.step()
|
||||
scheduler.step()
|
||||
|
||||
for g in grads: g.assign(0)
|
||||
model.update_amax()
|
||||
|
||||
lr_cpu = optim.lr.float().to("CPU")
|
||||
grad_norm_cpu = grad_norm.float().to("CPU")
|
||||
loss_cpu = loss_acc.to("CPU")
|
||||
Tensor.realize(lr_cpu, grad_norm_cpu, loss_cpu, loss_acc.assign(0), *grads, *fp8_inv_scales, *fp8_amax, *fp8_grad_amax)
|
||||
|
||||
return lr_cpu, grad_norm_cpu, loss_cpu
|
||||
lr = optim.lr
|
||||
loss.realize(lr)
|
||||
return loss, lr
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=0)
|
||||
def eval_step(tokens:Tensor):
|
||||
if is_dp: tokens = tokens.to(None).shard(device, 0)
|
||||
if is_mp: tokens = tokens.shard(device)
|
||||
if not is_sharding: tokens = tokens.to(None)
|
||||
logits:Tensor = model(tokens[:, :-1])
|
||||
loss = vocab_mask.where(-1e9, logits).sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
return loss.flatten().float().to("CPU")
|
||||
@Tensor.train(False)
|
||||
def eval_step(model, tokens:Tensor):
|
||||
if (DP := getenv("DP", 1)) > 1:
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(DP))
|
||||
tokens = tokens.shard(device, 0)
|
||||
if (MP := getenv("MP", 1)) > 1:
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(MP))
|
||||
tokens = tokens.shard(device)
|
||||
logits:Tensor = model(tokens[:, :-1], start_pos=0, temperature=math.nan)
|
||||
loss = logits.sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
return loss.flatten().float()
|
||||
|
||||
# ** data iters **
|
||||
def fake_data(bs, samples):
|
||||
import numpy as np
|
||||
for _ in range(samples // bs):
|
||||
fake_data_np = np.random.randint(0, real_vocab_size, size=(bs, SEQLEN + 1), dtype=np.int32)
|
||||
yield Tensor(fake_data_np, device="NPY")
|
||||
yield Tensor.randint(bs, SEQLEN + 1, low=0, high=model_params["vocab_size"], dtype=dtypes.int32, device=Device.DEFAULT)
|
||||
|
||||
def get_train_iter():
|
||||
if getenv("FAKEDATA", 0):
|
||||
return fake_data(BS, SAMPLES)
|
||||
else:
|
||||
from examples.mlperf.dataloader import batch_load_llama3
|
||||
return batch_load_llama3(BS, SAMPLES, SEQLEN, BASEDIR, seed=DATA_SEED, val=bool(TRAIN_ON_VAL), small=bool(SMALL))
|
||||
return batch_load_llama3(BS, SAMPLES, SEQLEN, BASEDIR, seed=SEED, val=bool(TRAIN_ON_VAL), small=bool(SMALL))
|
||||
|
||||
if getenv("FAKEDATA", 0):
|
||||
eval_dataset = None
|
||||
else:
|
||||
from examples.mlperf.dataloader import get_llama3_dataset
|
||||
eval_dataset = get_llama3_dataset(EVAL_SAMPLES, SEQLEN, BASEDIR, val=True, small=bool(SMALL))
|
||||
eval_dataset = get_llama3_dataset(1024 if SMALL else 5760, SEQLEN, BASEDIR, val=True, small=bool(SMALL))
|
||||
|
||||
def get_eval_iter():
|
||||
if eval_dataset is None:
|
||||
return fake_data(EVAL_BS, EVAL_SAMPLES)
|
||||
return fake_data(EVAL_BS, 5760)
|
||||
from examples.mlperf.dataloader import iterate_llama3_dataset
|
||||
return iterate_llama3_dataset(eval_dataset, EVAL_BS)
|
||||
|
||||
num_params = sum(p.numel() for p in params) - model_params["vocab_size"]*model_params["dim"]
|
||||
train_iter = get_train_iter()
|
||||
iter = get_train_iter()
|
||||
i, sequences_seen = resume_ckpt, 0
|
||||
step_times = []
|
||||
|
||||
if MLLOGGER and RUNMLPERF:
|
||||
MLLOGGER.start(key=mllog_constants.EPOCH_START, metadata={mllog_constants.SAMPLES_COUNT: sequences_seen})
|
||||
MLLOGGER.start(key=mllog_constants.BLOCK_START, metadata={mllog_constants.SAMPLES_COUNT: sequences_seen})
|
||||
|
||||
while i < MAX_STEPS:
|
||||
for tokens in tqdm(iter, total=SAMPLES//GBS):
|
||||
GlobalCounters.reset()
|
||||
actual_gbs = GBS if i >= 2 else BS
|
||||
if getenv("TRAIN", 1):
|
||||
profile_marker(f"train @ {i}")
|
||||
st = time.perf_counter()
|
||||
|
||||
stopped = False
|
||||
data_time, dev_time = 0, 0
|
||||
for _ in range(accum_steps:=grad_acc if i >= 2 else 1):
|
||||
ist = time.perf_counter()
|
||||
try: tokens = next(train_iter)
|
||||
except StopIteration:
|
||||
stopped = True
|
||||
break
|
||||
mst = time.perf_counter()
|
||||
data_time += mst - ist
|
||||
minibatch(tokens)
|
||||
dev_time += time.perf_counter() - mst
|
||||
if stopped: break
|
||||
|
||||
gt = time.perf_counter()
|
||||
ret = optim_step()
|
||||
lr, grad_norm, loss = ret[0].item(), ret[1].item(), ret[2].item() / accum_steps
|
||||
et = time.perf_counter()
|
||||
|
||||
optim_time = et - gt
|
||||
dev_time += optim_time
|
||||
step_time = et - st
|
||||
gbs_time = gt - st
|
||||
if BENCHMARK: step_times.append(step_time)
|
||||
t = time.perf_counter()
|
||||
loss, lr = train_step(model, tokens)
|
||||
loss = loss.float().item()
|
||||
lr = lr.item()
|
||||
|
||||
i += 1
|
||||
sequences_seen += actual_gbs
|
||||
sequences_seen += tokens.shape[0]
|
||||
|
||||
sec = time.perf_counter()-t
|
||||
if BENCHMARK: step_times.append(sec)
|
||||
|
||||
mem_gb = GlobalCounters.mem_used / 1e9
|
||||
gflops = GlobalCounters.global_ops / 1e9 / dev_time
|
||||
mfu = ((6 * num_params * SEQLEN * GBS) / (dev_time * device_count * (9.2e15 if MXFP4 else 4.6e15))) * 100
|
||||
gflops = GlobalCounters.global_ops / 1e9 / sec
|
||||
tqdm.write(
|
||||
f"{i:5} {step_time:.3f} s step, {gbs_time:.3f} s gbs, {optim_time:.3f} s optim, {data_time:.3f} s data, {loss:.4f} loss, " \
|
||||
f"{lr:.12f} LR, {grad_norm:.6f} grad_norm, {mem_gb:.2f} GB used, {gflops:9.2f} GFLOPS, {mfu:5.2f}% MFU")
|
||||
if DEBUG >= 1: tqdm.write(" mem per device: " + ', '.join(f"{dev}: {mem/1e9:.2f} GB" for dev, mem in sorted(GlobalCounters.mem_used_per_device.items())))
|
||||
f"{i:5} {sec:.2f} s run, {loss:.4f} loss, {lr:.12f} LR, {mem_gb:.2f} GB used, {gflops:9.2f} GFLOPS")
|
||||
|
||||
if (fname:=getenv("LOSS_FILE", "")):
|
||||
with open(fname, "a") as f:
|
||||
f.write(f"{i} {loss:.4f} {lr:.12f} {mem_gb:.2f}\n")
|
||||
|
||||
if WANDB:
|
||||
wandb.log({
|
||||
"train/loss": loss,
|
||||
"train/lr": lr,
|
||||
"train/grad_norm": grad_norm,
|
||||
"train/step_time": step_time,
|
||||
"train/gbs_time": gbs_time,
|
||||
"train/optim_time": optim_time,
|
||||
"train/dev_time": dev_time,
|
||||
"train/data_time": data_time,
|
||||
"train/mem": mem_gb,
|
||||
"train/GFLOPS": gflops,
|
||||
"train/MFU": mfu,
|
||||
"train/sequences_seen": sequences_seen
|
||||
})
|
||||
wandb.log({"lr": lr, "train/loss": loss, "train/step_time": sec, "train/GFLOPS": gflops, "train/sequences_seen": sequences_seen})
|
||||
|
||||
if (ckpt_freq := getenv("CKPT")) and (i % ckpt_freq == 0 and (i != 1 or ckpt_freq == 1)):
|
||||
tqdm.write("saving checkpoint")
|
||||
@@ -1611,339 +1484,40 @@ def train_llama3():
|
||||
safe_save(get_state_dict(scheduler), fn)
|
||||
|
||||
if i == BENCHMARK:
|
||||
median_step_time = sorted(step_times)[BENCHMARK // 2]
|
||||
estimated_steps = 200_000 // GBS if getenv("LLAMA3_SIZE", "8B") == "8B" else MAX_STEPS
|
||||
estimated_total_minutes = int(median_step_time * estimated_steps / 60)
|
||||
median_step_time = sorted(step_times)[(BENCHMARK + 1) // 2]
|
||||
estimated_total_minutes = int(median_step_time * (SAMPLES // GBS) / 60)
|
||||
print(f"Estimated training time: {estimated_total_minutes // 60}h{estimated_total_minutes % 60}m")
|
||||
print(f"epoch global_ops: {GlobalCounters.global_ops:_}, "
|
||||
f"epoch global_mem: {GlobalCounters.global_mem:_}")
|
||||
|
||||
if (sequences_seen // EVAL_FREQ != (sequences_seen - actual_gbs) // EVAL_FREQ and (i != 1 or EVAL_FREQ == 1)) or (BENCHMARK and i == BENCHMARK):
|
||||
if EVAL_BS == 0: return
|
||||
if (sequences_seen % EVAL_FREQ == 0 and (i != 1 or EVAL_FREQ == 1)) or (BENCHMARK and i == BENCHMARK):
|
||||
tqdm.write(f"evaluating after {sequences_seen} sequences")
|
||||
profile_marker(f"eval @ {i}")
|
||||
|
||||
if MLLOGGER and RUNMLPERF:
|
||||
MLLOGGER.end(key=mllog_constants.BLOCK_STOP, metadata={mllog_constants.SAMPLES_COUNT: sequences_seen})
|
||||
MLLOGGER.start(key=mllog_constants.EVAL_START, metadata={mllog_constants.SAMPLES_COUNT: sequences_seen})
|
||||
|
||||
# run eval
|
||||
eval_losses = []
|
||||
eval_iter = get_eval_iter()
|
||||
tqdm.write(f"evaluating {EVAL_SAMPLES//EVAL_BS} batches of {EVAL_BS} sequences")
|
||||
tqdm.write(f"evaluating {5760//EVAL_BS} batches of {EVAL_BS} sequences")
|
||||
|
||||
for j,tokens in tqdm(enumerate(eval_iter), total=EVAL_SAMPLES//EVAL_BS):
|
||||
eval_losses += eval_step(tokens).tolist()
|
||||
|
||||
if BENCHMARK and (j+1) == min(BENCHMARK, EVAL_SAMPLES//EVAL_BS):
|
||||
if MLLOGGER and INITMLPERF:
|
||||
MLLOGGER.end(key=mllog_constants.INIT_STOP, value=None)
|
||||
for j,tokens in tqdm(enumerate(eval_iter), total=5760//EVAL_BS):
|
||||
eval_losses += eval_step(model, tokens).tolist()
|
||||
|
||||
if BENCHMARK and (j+1) == min(BENCHMARK, 5760//EVAL_BS):
|
||||
return
|
||||
|
||||
log_perplexity = sum(eval_losses) / len(eval_losses)
|
||||
log_perplexity = Tensor(eval_losses).mean().float().item()
|
||||
|
||||
tqdm.write(f"eval log perplexity: {log_perplexity:.4f}")
|
||||
|
||||
if MLLOGGER and RUNMLPERF:
|
||||
MLLOGGER.event(key=mllog_constants.EVAL_ACCURACY, value=log_perplexity, metadata={mllog_constants.SAMPLES_COUNT: sequences_seen})
|
||||
MLLOGGER.end(key=mllog_constants.EVAL_STOP, metadata={mllog_constants.SAMPLES_COUNT: sequences_seen})
|
||||
|
||||
if WANDB:
|
||||
wandb.log({"eval/log_perplexity": log_perplexity, "eval/sequences_seen": sequences_seen})
|
||||
|
||||
if log_perplexity < EVAL_TARGET:
|
||||
tqdm.write(f"target achieved after {sequences_seen} sequences")
|
||||
if MLLOGGER and RUNMLPERF:
|
||||
MLLOGGER.end(key=mllog_constants.EPOCH_STOP, metadata={mllog_constants.SAMPLES_COUNT: sequences_seen})
|
||||
MLLOGGER.end(key=mllog_constants.RUN_STOP, metadata={mllog_constants.STATUS: mllog_constants.SUCCESS})
|
||||
if getenv("CKPT"):
|
||||
if not os.path.exists(ckpt_dir := "./ckpts"): os.mkdir(ckpt_dir)
|
||||
fn = f"{ckpt_dir}/llama3.safe"
|
||||
safe_save(get_state_dict(model), fn)
|
||||
break
|
||||
if MLLOGGER and RUNMLPERF:
|
||||
MLLOGGER.start(key=mllog_constants.BLOCK_START, metadata={mllog_constants.SAMPLES_COUNT: sequences_seen})
|
||||
|
||||
def train_gptoss():
|
||||
from examples.mlperf.models.gpt_oss import GPTOSS, GPT_OSS_20B, apply_grad, FP8_DTYPE
|
||||
from examples.mlperf.lr_schedulers import CosineAnnealingLRWithWarmup
|
||||
from examples.mlperf.optim import GradAccClipAdamW, GradAccClipAdamWGroup, clip_grads
|
||||
|
||||
BENCHMARK = getenv("BENCHMARK")
|
||||
|
||||
config = {}
|
||||
BASEDIR = config["BASEDIR"] = Path(getenv("BASEDIR", "/raid/datasets/c4-8b/"))
|
||||
BS = config["BS"] = getenv("BS", 16)
|
||||
grad_acc = config["GRADIENT_ACC_STEPS"] = getenv("GRADIENT_ACC_STEPS", 1)
|
||||
GBS = config["GLOBAL_BATCH_SIZE"] = BS * grad_acc
|
||||
SEED = config["SEED"] = getenv("SEED", 5760)
|
||||
DATA_SEED = config["DATA_SEED"] = getenv("DATA_SEED", SEED)
|
||||
SEQLEN = config["SEQLEN"] = getenv("SEQLEN", 8192)
|
||||
TRAIN_ON_VAL = config["TRAIN_ON_VAL"] = getenv("TRAIN_ON_VAL", 0)
|
||||
MAX_STEPS = config["MAX_STEPS"] = getenv("MAX_STEPS", 1_200_000)
|
||||
SAMPLES = config["SAMPLES"] = getenv("SAMPLES", 5_760 if TRAIN_ON_VAL else MAX_STEPS * GBS)
|
||||
EVAL_SAMPLES = config["EVAL_SAMPLES"] = getenv("EVAL_SAMPLES", 1024)
|
||||
WARMUP_STEPS = config["WARMUP_STEPS"] = getenv("WARMUP_STEPS", 128)
|
||||
LR = config["LR"] = getenv("LR", 4e-4 * GBS / 16)
|
||||
END_LR = config["END_LR"] = getenv("END_LR", 4e-5)
|
||||
EVAL_FREQ = config["EVAL_FREQ"] = getenv("EVAL_FREQ", 12288)
|
||||
EVAL_BS = config["EVAL_BS"] = getenv("EVAL_BS", 16)
|
||||
EVAL_TARGET = config["EVAL_TARGET"] = getenv("EVAL_TARGET", 3.34)
|
||||
|
||||
opt_adamw_beta_1 = 0.9
|
||||
opt_adamw_beta_2 = 0.95
|
||||
opt_adamw_epsilon = 1e-5
|
||||
opt_adamw_weight_decay = 0.1
|
||||
|
||||
opt_learning_rate_warmup_steps = WARMUP_STEPS
|
||||
opt_learning_rate_decay_steps = MAX_STEPS - opt_learning_rate_warmup_steps
|
||||
opt_base_learning_rate = LR
|
||||
opt_end_learning_rate = END_LR
|
||||
|
||||
Tensor.manual_seed(SEED) # seed for weight initialization
|
||||
|
||||
# ** init wandb **
|
||||
WANDB = getenv("WANDB")
|
||||
if WANDB:
|
||||
import wandb
|
||||
wandb_args = {"id": wandb_id, "resume": "must"} if (wandb_id := getenv("WANDB_RESUME", "")) else {}
|
||||
wandb.init(config=config, **wandb_args, project="MLPerf-gpt-oss")
|
||||
|
||||
model_params = GPT_OSS_20B
|
||||
model_params['vocab_size'] = getenv("VOCAB_SIZE", 128256)
|
||||
real_vocab_size = model_params['vocab_size']
|
||||
if (layers:=getenv("LAYERS")) != 0: model_params['n_layers'] = layers
|
||||
if (experts:=getenv("EXPERTS")) != 0: model_params['n_experts'] = experts
|
||||
print(f"model parameters: {model_params}")
|
||||
|
||||
model = GPTOSS(**model_params, max_context=SEQLEN)
|
||||
|
||||
params = get_parameters(model)
|
||||
|
||||
if getenv("EMPTYWEIGHT"):
|
||||
for v in get_parameters(model):
|
||||
v = v.assign(Tensor.empty(v.shape, dtype=v.dtype))
|
||||
|
||||
is_dp = (DP := getenv("DP", 1)) > 1
|
||||
is_sharding = is_dp
|
||||
device_count = DP
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(device_count))
|
||||
|
||||
model.shard(device, False)
|
||||
|
||||
is_offload_optim = bool(getenv("OFFLOAD_OPTIM"))
|
||||
is_fake_offload = Device.DEFAULT == "NULL"
|
||||
optim_device = ("CPU" if not is_fake_offload else "NULL:99") if is_offload_optim else None
|
||||
params_wd = [p for p in params if p.ndim >= 3]
|
||||
params_no_wd = [p for p in params if p.ndim < 3]
|
||||
optim = GradAccClipAdamWGroup(
|
||||
GradAccClipAdamW(params_wd, lr=0.0, b1=opt_adamw_beta_1, b2=opt_adamw_beta_2, eps=opt_adamw_epsilon, weight_decay=opt_adamw_weight_decay, grad_acc=grad_acc, device=optim_device),
|
||||
GradAccClipAdamW(params_no_wd, lr=0.0, b1=opt_adamw_beta_1, b2=opt_adamw_beta_2, eps=opt_adamw_epsilon, weight_decay=0.0, grad_acc=grad_acc, device=optim_device),
|
||||
)
|
||||
|
||||
for p in optim.params:
|
||||
grad_dtype = dtypes.bfloat16 if p.dtype == FP8_DTYPE else p.dtype
|
||||
p.grad = p.zeros_like(dtype=grad_dtype).contiguous()
|
||||
grads = [p.grad for p in optim.params]
|
||||
|
||||
from extra.gemm.cdna_asm_gemm import _mx_block_scale
|
||||
model_state = get_state_dict(model)
|
||||
def _scale_key(n):
|
||||
if "." in n and (c:=f"{(b:=n.rsplit('.',1))[0]}_scale.{b[1]}") in model_state: return c
|
||||
return f"{n}_scale"
|
||||
fp8_scale_names = {n: _scale_key(n) for n, t in model_state.items() if t.dtype == FP8_DTYPE}
|
||||
fp8_inv_scales = [model_state[sname] for sname in fp8_scale_names.values()]
|
||||
for wname, sname in fp8_scale_names.items():
|
||||
w, scale = model_state[wname], model_state[sname]
|
||||
w._inv_scale = scale
|
||||
if optim.master_params:
|
||||
master = optim.master_params[next(j for j, p in enumerate(optim.params) if p is w)]
|
||||
inv = scale if scale.device == master.device else scale.to(master.device)
|
||||
bs = _mx_block_scale(inv.reshape(-1, inv.shape[-1])).reshape(w.shape)
|
||||
master.assign((master * bs).contiguous())
|
||||
|
||||
scheduler = CosineAnnealingLRWithWarmup(optim, opt_base_learning_rate, opt_end_learning_rate, opt_learning_rate_warmup_steps, opt_learning_rate_decay_steps)
|
||||
|
||||
if optim.master_params:
|
||||
for m in optim.master_params: m.realize()
|
||||
Tensor.realize(*optim.params, *fp8_inv_scales)
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=1)
|
||||
def minibatch(tokens:Tensor):
|
||||
if is_dp: tokens = tokens.to(None).shard(device, 0)
|
||||
if not is_sharding: tokens = tokens.to(None)
|
||||
logits:Tensor = model(tokens[:, :-1], save=True)
|
||||
loss = logits.sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
|
||||
for g, new_g in zip(grads, loss.gradient(*optim.params)):
|
||||
apply_grad(g, new_g.uop)
|
||||
|
||||
loss_cpu = loss.flatten().float().to("CPU")
|
||||
return loss_cpu.realize(*grads)
|
||||
|
||||
@TinyJit
|
||||
def optim_step():
|
||||
grad_norm = clip_grads(grads, grad_acc, 1.0)
|
||||
optim.fstep(grads, grad_norm)
|
||||
scheduler.step()
|
||||
|
||||
for g in grads: g.assign(0)
|
||||
|
||||
lr_cpu = optim.lr.float().to("CPU")
|
||||
grad_norm_cpu = grad_norm.float().to("CPU")
|
||||
Tensor.realize(lr_cpu, grad_norm_cpu, *grads, *fp8_inv_scales)
|
||||
|
||||
return lr_cpu, grad_norm_cpu
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=0)
|
||||
def eval_step(tokens:Tensor):
|
||||
if is_dp: tokens = tokens.to(None).shard(device, 0)
|
||||
if not is_sharding: tokens = tokens.to(None)
|
||||
logits:Tensor = model(tokens[:, :-1])
|
||||
loss = logits.sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
return loss.flatten().float().to("CPU")
|
||||
|
||||
# ** data iters **
|
||||
def fake_data(bs, samples):
|
||||
import numpy as np
|
||||
for _ in range(samples // bs):
|
||||
fake_data_np = np.random.randint(0, real_vocab_size, size=(bs, SEQLEN + 1), dtype=np.int32)
|
||||
yield Tensor(fake_data_np, device="NPY")
|
||||
|
||||
def get_train_iter():
|
||||
if getenv("FAKEDATA", 0):
|
||||
return fake_data(BS, SAMPLES)
|
||||
else:
|
||||
from examples.mlperf.dataloader import batch_load_llama3
|
||||
return batch_load_llama3(BS, SAMPLES, SEQLEN, BASEDIR, seed=DATA_SEED, val=bool(TRAIN_ON_VAL), small=True)
|
||||
|
||||
if getenv("FAKEDATA", 0):
|
||||
eval_dataset = None
|
||||
else:
|
||||
from examples.mlperf.dataloader import get_llama3_dataset
|
||||
eval_dataset = get_llama3_dataset(EVAL_SAMPLES, SEQLEN, BASEDIR, val=True, small=True)
|
||||
|
||||
def get_eval_iter():
|
||||
if eval_dataset is None:
|
||||
return fake_data(EVAL_BS, EVAL_SAMPLES)
|
||||
from examples.mlperf.dataloader import iterate_llama3_dataset
|
||||
return iterate_llama3_dataset(eval_dataset, EVAL_BS)
|
||||
|
||||
num_params = sum(p.numel() for p in params) - model_params["vocab_size"]*model_params["dim"]
|
||||
train_iter = get_train_iter()
|
||||
i, sequences_seen = 0, 0
|
||||
step_times = []
|
||||
|
||||
while i < MAX_STEPS:
|
||||
GlobalCounters.reset()
|
||||
actual_gbs = GBS if i >= 2 else BS
|
||||
if getenv("TRAIN", 1):
|
||||
profile_marker(f"train @ {i}")
|
||||
st = time.perf_counter()
|
||||
|
||||
stopped = False
|
||||
losses, data_time, dev_time = [], 0, 0
|
||||
for _ in range(grad_acc if i >= 2 else 1):
|
||||
ist = time.perf_counter()
|
||||
try: tokens = next(train_iter)
|
||||
except StopIteration:
|
||||
stopped = True
|
||||
break
|
||||
mst = time.perf_counter()
|
||||
data_time += mst - ist
|
||||
losses.append(minibatch(tokens).item())
|
||||
dev_time += time.perf_counter() - mst
|
||||
if stopped: break
|
||||
|
||||
gt = time.perf_counter()
|
||||
ret = optim_step()
|
||||
lr, grad_norm = ret[0].item(), ret[1].item()
|
||||
et = time.perf_counter()
|
||||
|
||||
loss = sum(losses) / len(losses)
|
||||
optim_time = et - gt
|
||||
dev_time += optim_time
|
||||
step_time = et - st
|
||||
gbs_time = gt - st
|
||||
if BENCHMARK: step_times.append(step_time)
|
||||
|
||||
i += 1
|
||||
sequences_seen += actual_gbs
|
||||
|
||||
mem_gb = GlobalCounters.mem_used / 1e9
|
||||
gflops = GlobalCounters.global_ops / 1e9 / dev_time
|
||||
mfu = ((6 * num_params * SEQLEN * GBS) / (dev_time * device_count * 4.6e15)) * 100
|
||||
tqdm.write(
|
||||
f"{i:5} {step_time:.3f} s step, {gbs_time:.3f} s gbs, {optim_time:.3f} s optim, {data_time:.3f} s data, {loss:.4f} loss, " \
|
||||
f"{lr:.12f} LR, {grad_norm:.6f} grad_norm, {mem_gb:.2f} GB used, {gflops:9.2f} GFLOPS, {mfu:5.2f}% MFU")
|
||||
if DEBUG >= 1: tqdm.write(" mem per device: " + ', '.join(f"{dev}: {mem/1e9:.2f} GB" for dev, mem in sorted(GlobalCounters.mem_used_per_device.items())))
|
||||
|
||||
if WANDB:
|
||||
wandb.log({
|
||||
"train/loss": loss,
|
||||
"train/lr": lr,
|
||||
"train/grad_norm": grad_norm,
|
||||
"train/step_time": step_time,
|
||||
"train/gbs_time": gbs_time,
|
||||
"train/optim_time": optim_time,
|
||||
"train/dev_time": dev_time,
|
||||
"train/data_time": data_time,
|
||||
"train/mem": mem_gb,
|
||||
"train/GFLOPS": gflops,
|
||||
"train/MFU": mfu,
|
||||
"train/sequences_seen": sequences_seen
|
||||
})
|
||||
|
||||
if (ckpt_freq := getenv("CKPT")) and (i % ckpt_freq == 0 and (i != 1 or ckpt_freq == 1)):
|
||||
tqdm.write("saving checkpoint")
|
||||
if not os.path.exists(ckpt_dir := "./ckpts"): os.mkdir(ckpt_dir)
|
||||
fn = f"{ckpt_dir}/gptoss_{i}.safe"
|
||||
safe_save(get_state_dict(model), fn)
|
||||
|
||||
tqdm.write("saving optim checkpoint")
|
||||
fn = f"{ckpt_dir}/gptoss_{i}_optim.safe"
|
||||
safe_save(get_state_dict(scheduler), fn)
|
||||
|
||||
if i == BENCHMARK:
|
||||
median_step_time = sorted(step_times)[BENCHMARK // 2]
|
||||
estimated_steps = MAX_STEPS
|
||||
estimated_total_minutes = int(median_step_time * estimated_steps / 60)
|
||||
print(f"Estimated training time: {estimated_total_minutes // 60}h{estimated_total_minutes % 60}m")
|
||||
print(f"epoch global_ops: {GlobalCounters.global_ops:_}, "
|
||||
f"epoch global_mem: {GlobalCounters.global_mem:_}")
|
||||
|
||||
if (sequences_seen // EVAL_FREQ != (sequences_seen - actual_gbs) // EVAL_FREQ and (i != 1 or EVAL_FREQ == 1)) or (BENCHMARK and i == BENCHMARK):
|
||||
if EVAL_BS == 0: return
|
||||
tqdm.write(f"evaluating after {sequences_seen} sequences")
|
||||
profile_marker(f"eval @ {i}")
|
||||
|
||||
# run eval
|
||||
eval_losses = []
|
||||
eval_iter = get_eval_iter()
|
||||
tqdm.write(f"evaluating {EVAL_SAMPLES//EVAL_BS} batches of {EVAL_BS} sequences")
|
||||
|
||||
for j,tokens in tqdm(enumerate(eval_iter), total=EVAL_SAMPLES//EVAL_BS):
|
||||
eval_losses += eval_step(tokens).tolist()
|
||||
|
||||
if BENCHMARK and (j+1) == min(BENCHMARK, EVAL_SAMPLES//EVAL_BS):
|
||||
return
|
||||
|
||||
log_perplexity = sum(eval_losses) / len(eval_losses)
|
||||
|
||||
tqdm.write(f"eval log perplexity: {log_perplexity:.4f}")
|
||||
|
||||
if WANDB:
|
||||
wandb.log({"eval/log_perplexity": log_perplexity, "eval/sequences_seen": sequences_seen})
|
||||
|
||||
if log_perplexity < EVAL_TARGET:
|
||||
tqdm.write(f"target achieved after {sequences_seen} sequences")
|
||||
if getenv("CKPT"):
|
||||
if not os.path.exists(ckpt_dir := "./ckpts"): os.mkdir(ckpt_dir)
|
||||
fn = f"{ckpt_dir}/gptoss.safe"
|
||||
safe_save(get_state_dict(model), fn)
|
||||
break
|
||||
|
||||
def train_stable_diffusion():
|
||||
from extra.models.unet import UNetModel
|
||||
@@ -2015,7 +1589,7 @@ def train_stable_diffusion():
|
||||
loss, out_lr = loss.detach().to("CPU"), optimizer.lr.to("CPU")
|
||||
Tensor.realize(loss, out_lr)
|
||||
return loss, out_lr
|
||||
|
||||
|
||||
# checkpointing takes ~9 minutes without this, and ~1 minute with this
|
||||
@TinyJit
|
||||
def ckpt_to_cpu():
|
||||
@@ -2023,7 +1597,7 @@ def train_stable_diffusion():
|
||||
# move to CPU first so more GPU bufs aren't created (can trigger OOM)
|
||||
for k,v in ckpt.items(): ckpt[k] = v.detach().to("CPU")
|
||||
Tensor.realize(*[v for v in ckpt.values()])
|
||||
for k,v in ckpt.items(): ckpt[k] = v.cast(v.dtype).contiguous()
|
||||
for k,v in ckpt.items(): ckpt[k] = v.cast(v.dtype.base).contiguous()
|
||||
Tensor.realize(*[v for v in ckpt.values()])
|
||||
return ckpt
|
||||
|
||||
@@ -2054,7 +1628,7 @@ def train_stable_diffusion():
|
||||
if i == 3:
|
||||
for _ in range(3): ckpt_to_cpu() # do this at the beginning of run to prevent OOM surprises when checkpointing
|
||||
print("BEAM COMPLETE", flush=True) # allows wrapper script to detect BEAM search completion and retry if it failed
|
||||
|
||||
|
||||
total_train_time = time.perf_counter() - train_start_time
|
||||
if WANDB:
|
||||
wandb.log({"train/loss": loss_item, "train/lr": lr_item, "train/loop_time_prev": loop_time, "train/dl_time": dl_time, "train/step": i,
|
||||
@@ -2090,7 +1664,7 @@ if __name__ == "__main__":
|
||||
elif getenv("RUNMLPERF"): bench_log_manager = WallTimeEvent(BenchEvent.MLPERF_RUN)
|
||||
else: bench_log_manager = contextlib.nullcontext()
|
||||
|
||||
with Context(TRAINING=1):
|
||||
with Tensor.train():
|
||||
for m in getenv("MODEL", "resnet,retinanet,unet3d,rnnt,bert,maskrcnn,stable_diffusion").split(","):
|
||||
nm = f"train_{m}"
|
||||
if nm in globals():
|
||||
|
||||
@@ -1,458 +0,0 @@
|
||||
import math, os
|
||||
if __name__ == "__main__":
|
||||
os.environ["DEFAULT_FLOAT"] = "bfloat16"
|
||||
os.environ["OPTIM_DTYPE"] = "bfloat16"
|
||||
if "DEV" not in os.environ: os.environ["DEV"] = "NULL::gfx950"
|
||||
# CDNA
|
||||
os.environ["DEVICE_IN_FUNCTION_BUG"] = "1"
|
||||
os.environ["ALL2ALL"] = "1"
|
||||
os.environ["USE_ATOMICS"] = "1"
|
||||
if "HK_FLASH_ATTENTION" not in os.environ:
|
||||
os.environ["HK_FLASH_ATTENTION"] = "1"
|
||||
if "ASM_GEMM" not in os.environ:
|
||||
os.environ["ASM_GEMM"] = "1"
|
||||
from tinygrad import Tensor, nn, function, getenv, dtypes, TinyJit
|
||||
from tinygrad.helpers import Timing, colored, GlobalCounters, profile_marker, round_up
|
||||
from tinygrad.uop.ops import Ops, UOp
|
||||
from extra.models.llama import apply_rotary_emb, precompute_freqs_cis
|
||||
from extra.llama_kernels.rmsnorm import rmsnorm
|
||||
from extra.llama_kernels import FP8_MAX, local_abs_max
|
||||
|
||||
ASM_GEMM = getenv("ASM_GEMM", 0)
|
||||
FUSED_INPUT_QUANTIZE = getenv("FUSED_INPUT_QUANTIZE", 0)
|
||||
FUSED_ADD_NORM_MUL_QUANTIZE = getenv("FUSED_ADD_NORM_MUL_QUANTIZE", 0)
|
||||
FUSED_SILU_W13 = getenv("FUSED_SILU_W13", 0)
|
||||
SPLIT_W13 = getenv("SPLIT_W13", 0)
|
||||
COLUMNWISE_WEIGHT_SCALE = getenv("COLUMNWISE_WEIGHT_SCALE", 0)
|
||||
MXFP8 = getenv("MXFP8", 0)
|
||||
MXFP4 = getenv("MXFP4", 0)
|
||||
|
||||
FP8_DTYPE = dtypes.fp8e4m3
|
||||
FP8_GRAD_DTYPE = dtypes.fp8e5m2
|
||||
|
||||
def quantize_fp8(x:Tensor, amax_state:Tensor|None=None):
|
||||
new_amax = (local_abs_max(x) if isinstance(x.device, tuple) else x.abs().max()).detach().cast(dtypes.float32)
|
||||
scale = FP8_MAX / ((amax_state if amax_state is not None else new_amax) + 1e-8)
|
||||
x_scaled = x * scale
|
||||
x_clamped = x_scaled + (x_scaled.detach().clamp(-FP8_MAX, FP8_MAX) - x_scaled.detach()) # STE
|
||||
return x_clamped.cast(FP8_DTYPE), scale.float().reciprocal(), new_amax
|
||||
|
||||
def matmul(x:Tensor, w:Tensor, fp8:bool=True, amax_x:Tensor|None=None, w_inv_scale:Tensor|None=None,
|
||||
x_fp8:Tensor|None=None, grad_amax_state:Tensor|None=None, next_grad_amax_state:Tensor|None=None, x_prequant_mx:tuple|None=None,
|
||||
next_amax_x:Tensor|None=None) -> tuple[Tensor,...]:
|
||||
if not fp8:
|
||||
if ASM_GEMM:
|
||||
from extra.gemm.cdna_asm_gemm import can_use_asm_gemm, asm_gemm
|
||||
if can_use_asm_gemm(x, w.T): return (asm_gemm(x, w.T),)
|
||||
return (x @ w.T,)
|
||||
if MXFP4:
|
||||
assert x is not None, "MXFP4 matmul requires an unquantized input"
|
||||
from extra.gemm.cdna_asm_gemm import asm_gemm, can_use_asm_gemm
|
||||
if can_use_asm_gemm(x, w.T): return (asm_gemm(x, w.T, mxfp4=True),)
|
||||
return (x @ w.T,)
|
||||
assert w_inv_scale is not None, "fp8 matmul requires w_inv_scale (weights must be stored in fp8 with per-tensor scale)"
|
||||
if MXFP8:
|
||||
from extra.gemm.cdna_asm_gemm import asm_gemm, quantize_mxfp8, mx_pack, can_use_asm_gemm, _mx_block_scale
|
||||
if x_prequant_mx is not None: x_q, x_e8, x_si = x_prequant_mx # fused producer already quantized (2d)
|
||||
else: x_q, x_e8, x_si = quantize_mxfp8(x.reshape(-1, x.shape[-1]))
|
||||
l_shape = x.shape[:-1] if x is not None else x_q.shape[:-1]
|
||||
if can_use_asm_gemm(x_q, w.T):
|
||||
out = asm_gemm(x_q, w.T, mx=True, mx_scales=(x_si, x_e8, mx_pack(w_inv_scale), w_inv_scale),
|
||||
mx_w_stored=True).reshape(*l_shape, w.shape[0])
|
||||
else:
|
||||
x_phys = (x_q.cast(dtypes.bfloat16) * _mx_block_scale(x_e8)).reshape(*l_shape, x_q.shape[-1])
|
||||
out = x_phys @ (w.cast(dtypes.bfloat16) * _mx_block_scale(w_inv_scale)).T
|
||||
return out, x_q
|
||||
if x_fp8 is None:
|
||||
if FUSED_INPUT_QUANTIZE:
|
||||
from extra.llama_kernels.quantize_fp8_delayed import quantize_fp8_delayed
|
||||
x_fp8, _ = quantize_fp8_delayed(x, amax_x, next_amax_x, FP8_DTYPE)
|
||||
else:
|
||||
x_fp8, _, new_amax_x = quantize_fp8(x, amax_state=amax_x)
|
||||
next_amax_x.assign(new_amax_x)
|
||||
if ASM_GEMM:
|
||||
from extra.gemm.cdna_asm_gemm import can_use_asm_gemm, asm_gemm
|
||||
if can_use_asm_gemm(x_fp8, w.T):
|
||||
assert amax_x is not None
|
||||
if COLUMNWISE_WEIGHT_SCALE:
|
||||
out = asm_gemm(x_fp8, w.T, x_scale=amax_x, grad_amax_state=grad_amax_state,
|
||||
next_grad_amax_state=next_grad_amax_state, w_post_scale=w_inv_scale)
|
||||
else:
|
||||
out = asm_gemm(x_fp8, w.T, x_scale=amax_x, w_scale=w_inv_scale, grad_amax_state=grad_amax_state,
|
||||
next_grad_amax_state=next_grad_amax_state)
|
||||
return out, x_fp8
|
||||
return (x_fp8.dot(w.T, dtype=dtypes.float) * ((amax_x.float() + 1e-8) / FP8_MAX) * w_inv_scale).cast(dtypes.bfloat16), x_fp8
|
||||
|
||||
def norm_quantize_matmul(x:Tensor, norm:Tensor, w:Tensor, w_inv_scale:Tensor, eps:float, amax_x:Tensor|None,
|
||||
next_amax_x:Tensor|None, grad_amax_state:Tensor|None, next_grad_amax_state:Tensor|None):
|
||||
if FUSED_ADD_NORM_MUL_QUANTIZE and not MXFP4:
|
||||
from extra.llama_kernels.fused_rmsnorm_mul_quantize_fp8 import fused_rmsnorm_mul_quantize_fp8
|
||||
x_fp8, x_normed, rrms = fused_rmsnorm_mul_quantize_fp8(x, norm, amax_x, eps, FP8_DTYPE, next_amax_x)
|
||||
out, *ret = matmul(None, w, w_inv_scale=w_inv_scale, x_fp8=x_fp8, amax_x=amax_x,
|
||||
grad_amax_state=grad_amax_state, next_grad_amax_state=next_grad_amax_state)
|
||||
return out, x_normed, rrms, ret
|
||||
x_normed, rrms = rmsnorm(x, eps)
|
||||
out, *ret = matmul(x_normed * norm, w, amax_x=amax_x, w_inv_scale=w_inv_scale, grad_amax_state=grad_amax_state,
|
||||
next_grad_amax_state=next_grad_amax_state, next_amax_x=next_amax_x)
|
||||
return out, x_normed, rrms, ret
|
||||
|
||||
def add_norm_quantize_matmul(x:Tensor, residual:Tensor, norm:Tensor, w:Tensor, w_inv_scale:Tensor, eps:float, amax_x:Tensor|None,
|
||||
next_amax_x:Tensor|None, grad_amax_state:Tensor|None=None, next_grad_amax_state:Tensor|None=None):
|
||||
if FUSED_ADD_NORM_MUL_QUANTIZE and not MXFP4:
|
||||
from extra.llama_kernels.fused_rmsnorm_mul_quantize_fp8 import fused_add_rmsnorm_mul_quantize_fp8
|
||||
x_fp8, h, x_normed, rrms = fused_add_rmsnorm_mul_quantize_fp8(x, residual, norm, amax_x, eps, FP8_DTYPE, next_amax_x)
|
||||
out, *ret = matmul(None, w, w_inv_scale=w_inv_scale, x_fp8=x_fp8, amax_x=amax_x,
|
||||
grad_amax_state=grad_amax_state, next_grad_amax_state=next_grad_amax_state)
|
||||
return out, h, x_normed, rrms, ret
|
||||
h = x + residual
|
||||
x_normed, rrms = rmsnorm(h, eps)
|
||||
out, *ret = matmul(x_normed * norm, w, amax_x=amax_x, w_inv_scale=w_inv_scale, grad_amax_state=grad_amax_state,
|
||||
next_grad_amax_state=next_grad_amax_state, next_amax_x=next_amax_x)
|
||||
return out, h, x_normed, rrms, ret
|
||||
|
||||
def silu_w13_quantize_matmul(x_w13:Tensor, w2:Tensor, s_2:Tensor,
|
||||
amax_x2:Tensor|None, next_amax_x2:Tensor|None,
|
||||
grad_amax_xw13:Tensor|None, next_grad_amax_xw13:Tensor|None,
|
||||
grad_amax_xout:Tensor|None, next_grad_amax_xout:Tensor|None):
|
||||
if FUSED_SILU_W13 and MXFP4:
|
||||
from extra.llama_kernels.swiglu import swiglu
|
||||
out, *ret = matmul(swiglu(x_w13), w2, amax_x=amax_x2, w_inv_scale=s_2, grad_amax_state=grad_amax_xout,
|
||||
next_grad_amax_state=next_grad_amax_xout, next_amax_x=next_amax_x2)
|
||||
return out, ret
|
||||
if FUSED_SILU_W13 and not MXFP4:
|
||||
from extra.llama_kernels.cast_amax import fused_quantize_fp8_w13
|
||||
x2_fp8 = fused_quantize_fp8_w13(x_w13, amax_x2, FP8_DTYPE, grad_amax_state=grad_amax_xw13,
|
||||
next_grad_amax_state=next_grad_amax_xw13, amax_out=next_amax_x2)
|
||||
out, *ret = matmul(None, w2, w_inv_scale=s_2, x_fp8=x2_fp8, amax_x=amax_x2,
|
||||
grad_amax_state=grad_amax_xout, next_grad_amax_state=next_grad_amax_xout)
|
||||
return out, ret
|
||||
hidden = x_w13.shape[-1] // 2
|
||||
x_w1, x_w3 = x_w13[..., :hidden], x_w13[..., hidden:]
|
||||
out, *ret = matmul(x_w1.silu() * x_w3, w2, amax_x=amax_x2, w_inv_scale=s_2, grad_amax_state=grad_amax_xout,
|
||||
next_grad_amax_state=next_grad_amax_xout, next_amax_x=next_amax_x2)
|
||||
return out, ret
|
||||
|
||||
class FlatTransformer:
|
||||
def __init__(self, dim:int, hidden_dim:int, n_heads:int, n_layers:int, norm_eps:float, vocab_size:int, n_kv_heads:int|None=None,
|
||||
rope_theta:int=10000, max_context:int=1024):
|
||||
self.vocab_size = vocab_size
|
||||
self.n_layers = n_layers
|
||||
self.n_heads = n_heads
|
||||
self.n_kv_heads = n_kv_heads if n_kv_heads is not None else n_heads # n_kv_heads != n_heads implies MQA [arxiv/2307.09288, A.2.1]
|
||||
self.head_dim = dim // n_heads
|
||||
self.n_rep = self.n_heads // self.n_kv_heads
|
||||
self.hidden_dim = hidden_dim
|
||||
|
||||
scaled_std = 0.02 / math.sqrt(2 * n_layers)
|
||||
|
||||
# Attention
|
||||
self.wqkv, s_qkv = self.lin_per_layer(dim, self.n_heads * self.head_dim + self.n_kv_heads * self.head_dim * 2)
|
||||
self.wo, s_o = self.lin_per_layer(self.n_heads * self.head_dim, dim, std=scaled_std)
|
||||
|
||||
# FeedForward
|
||||
if SPLIT_W13:
|
||||
self.w1, s_1 = self.lin_per_layer(dim, hidden_dim)
|
||||
self.w3, s_3 = self.lin_per_layer(dim, hidden_dim)
|
||||
else:
|
||||
self.w13, s_13 = self.lin_per_layer(dim, hidden_dim * 2)
|
||||
self.w2, s_2 = self.lin_per_layer(hidden_dim, dim, std=scaled_std)
|
||||
|
||||
self.norm_eps = norm_eps
|
||||
self.attention_norm = Tensor.ones(n_layers, dim).contiguous()
|
||||
self.ffn_norm = Tensor.ones(n_layers, dim).contiguous()
|
||||
|
||||
# output
|
||||
self.norm = nn.RMSNorm(dim, norm_eps)
|
||||
self.tok_embeddings = nn.Embedding(vocab_size, dim)
|
||||
self.tok_embeddings.weight = Tensor.normal(vocab_size, dim, mean=0.0, std=0.02, dtype=dtypes.bfloat16)
|
||||
self.output = Tensor.normal(1, vocab_size, dim, mean=0.0, std=0.02, dtype=dtypes.bfloat16)
|
||||
self.freqs_cis = precompute_freqs_cis(dim // n_heads, max_context * 2, rope_theta).clone().is_param_(False)
|
||||
|
||||
def _amax(): return Tensor.full((), FP8_MAX, dtype=dtypes.float32).contiguous().is_param_(False)
|
||||
n_amax = 0 if MXFP4 else n_layers
|
||||
names = ["xqkv", "xo", "x2"]
|
||||
names += ["x1", "x3"] if SPLIT_W13 else ["x13"]
|
||||
self._fp8_amax = {name: [_amax() for _ in range(n_amax)] for name in names}
|
||||
self._fp8_next_amax = {name: [_amax() for _ in range(n_amax)] for name in names}
|
||||
grad_names = ["xqkv", "xo", "xout"]
|
||||
grad_names += ["xw1", "xw3"] if SPLIT_W13 else ["xw13"]
|
||||
self._fp8_grad_amax = {name: [_amax() for _ in range(n_amax)] for name in grad_names}
|
||||
self._fp8_next_grad_amax = {name: [_amax() for _ in range(n_amax)] for name in grad_names}
|
||||
w_scales = [("wqkv", s_qkv), ("wo", s_o), ("w2", s_2)]
|
||||
w_scales += [("w1", s_1), ("w3", s_3)] if SPLIT_W13 else [("w13", s_13)]
|
||||
self._fp8_inv_scale = {name: (s if MXFP8 else s.float()).contiguous().is_param_(False) for name, s in w_scales}
|
||||
self._fp8_next_inv_scale = {name: (s if MXFP8 else s.float()).contiguous().is_param_(False) for name, s in w_scales}
|
||||
|
||||
def lin_per_layer(self, in_features:int, out_features:int, std:float=0.02, w:Tensor|None=None):
|
||||
if w is None:
|
||||
if getenv("ZEROS"): w = Tensor.zeros(self.n_layers, out_features, in_features)
|
||||
else: w = Tensor.normal(self.n_layers, out_features, in_features, mean=0.0, std=std)
|
||||
if MXFP8:
|
||||
from extra.gemm.cdna_asm_gemm import quantize_mxfp8
|
||||
w_q, w_e8, _ = quantize_mxfp8(w.reshape(self.n_layers * out_features, in_features))
|
||||
return w_q.reshape(self.n_layers, out_features, in_features), w_e8.reshape(self.n_layers, out_features, in_features // 32)
|
||||
if MXFP4:
|
||||
# FP4 is produced dynamically so optimizer updates always start from the current BF16 weight.
|
||||
return w.cast(dtypes.bfloat16), Tensor.ones(self.n_layers)
|
||||
amax = (w.abs().max(axis=2) if COLUMNWISE_WEIGHT_SCALE else w.abs().flatten(1).max(1)).detach()
|
||||
scale = FP8_MAX / (amax + 1e-8)
|
||||
inv_scale = (amax + 1e-8) / FP8_MAX
|
||||
scale_b = scale.reshape(self.n_layers, out_features, 1) if COLUMNWISE_WEIGHT_SCALE else scale.reshape(-1, 1, 1)
|
||||
return (w * scale_b).clamp(-FP8_MAX, FP8_MAX).cast(FP8_DTYPE), inv_scale
|
||||
|
||||
def attention(self, x:Tensor, freqs_cis:Tensor, *, attention_norm:Tensor, wqkv:Tensor, wo:Tensor,
|
||||
amax_xqkv:Tensor|None, amax_xo:Tensor|None, s_qkv:Tensor, s_o:Tensor,
|
||||
next_amax_xqkv:Tensor|None, next_amax_xo:Tensor|None,
|
||||
grad_amax_xqkv:Tensor|None, grad_amax_xo:Tensor|None,
|
||||
next_grad_amax_xqkv:Tensor|None, next_grad_amax_xo:Tensor|None):
|
||||
bsz, seqlen, _ = x.shape
|
||||
saves = []
|
||||
|
||||
xqkv, x_normed, rrms, s = norm_quantize_matmul(x, attention_norm, wqkv, s_qkv, self.norm_eps,
|
||||
amax_x=amax_xqkv, grad_amax_state=grad_amax_xqkv,
|
||||
next_grad_amax_state=next_grad_amax_xqkv, next_amax_x=next_amax_xqkv)
|
||||
saves.extend([x_normed, rrms, *s, xqkv])
|
||||
if getenv("HK_FLASH_ATTENTION"):
|
||||
from extra.thunder.amd.fa import flash_attention, fused_qkv_rope
|
||||
xq, xk, xv = fused_qkv_rope(xqkv, freqs_cis, self.n_heads, self.n_kv_heads, self.head_dim)
|
||||
attn, *save = flash_attention(xq, xk, xv, is_causal=True, write_flat=True)
|
||||
saves.extend(save)
|
||||
else:
|
||||
xqkv = xqkv.reshape(bsz, seqlen, self.n_kv_heads, self.n_rep + 2, self.head_dim)
|
||||
xq = xqkv[:, :, :, :self.n_rep].reshape(bsz, seqlen, self.n_heads, self.head_dim)
|
||||
xk = xqkv[:, :, :, self.n_rep].reshape(bsz, seqlen, self.n_kv_heads, self.head_dim)
|
||||
xv = xqkv[:, :, :, self.n_rep+1].reshape(bsz, seqlen, self.n_kv_heads, self.head_dim)
|
||||
xq, xk = apply_rotary_emb(xq, xk, freqs_cis)
|
||||
xq, xk, xv = xq.cast(dtypes.bfloat16), xk.cast(dtypes.bfloat16), xv.cast(dtypes.bfloat16)
|
||||
xq, xk, xv = xq.transpose(1, 2), xk.transpose(1, 2), xv.transpose(1, 2)
|
||||
attn = xq.scaled_dot_product_attention(xk, xv, is_causal=True, enable_gqa=True).transpose(1, 2)
|
||||
attn = attn.reshape(bsz, seqlen, -1)
|
||||
|
||||
out, *s = matmul(attn, wo, amax_x=amax_xo, w_inv_scale=s_o, grad_amax_state=grad_amax_xo,
|
||||
next_grad_amax_state=next_grad_amax_xo, next_amax_x=next_amax_xo)
|
||||
saves.extend([*s, out])
|
||||
return out, saves
|
||||
|
||||
def feed_forward(self, x:Tensor, residual:Tensor, **kwargs):
|
||||
saves = []
|
||||
|
||||
if SPLIT_W13:
|
||||
h = x + residual
|
||||
x_normed, rrms = rmsnorm(h, self.norm_eps)
|
||||
saves.extend([x_normed, rrms])
|
||||
inp = x_normed * kwargs["ffn_norm"]
|
||||
x_w1, *s = matmul(inp, kwargs["w1"], amax_x=kwargs["amax_x1"], w_inv_scale=kwargs["s_1"],
|
||||
grad_amax_state=kwargs["grad_amax_xw1"], next_grad_amax_state=kwargs["next_grad_amax_xw1"],
|
||||
next_amax_x=kwargs["next_amax_x1"])
|
||||
saves.extend([*s, x_w1])
|
||||
x_w3, *s = matmul(inp, kwargs["w3"], amax_x=kwargs["amax_x3"], w_inv_scale=kwargs["s_3"],
|
||||
grad_amax_state=kwargs["grad_amax_xw3"], next_grad_amax_state=kwargs["next_grad_amax_xw3"],
|
||||
next_amax_x=kwargs["next_amax_x3"])
|
||||
saves.extend([*s, x_w3])
|
||||
if FUSED_SILU_W13 and MXFP8:
|
||||
from extra.llama_kernels.fused_silu_mul_quantize_mxfp8 import fused_silu_mul_quantize_mxfp8
|
||||
aq, ae8, asi = fused_silu_mul_quantize_mxfp8(x_w1.reshape(-1, x_w1.shape[-1]), x_w3.reshape(-1, x_w3.shape[-1]))
|
||||
out, *s = matmul(None, kwargs["w2"], x_prequant_mx=(aq, ae8, asi), amax_x=kwargs["amax_x2"],
|
||||
w_inv_scale=kwargs["s_2"], grad_amax_state=kwargs["grad_amax_xout"],
|
||||
next_grad_amax_state=kwargs["next_grad_amax_xout"], next_amax_x=kwargs["next_amax_x2"])
|
||||
out = out.reshape(*x_w1.shape[:-1], kwargs["w2"].shape[0])
|
||||
else:
|
||||
out, *s = matmul(x_w1.silu() * x_w3, kwargs["w2"], amax_x=kwargs["amax_x2"], w_inv_scale=kwargs["s_2"],
|
||||
grad_amax_state=kwargs["grad_amax_xout"], next_grad_amax_state=kwargs["next_grad_amax_xout"],
|
||||
next_amax_x=kwargs["next_amax_x2"])
|
||||
saves.extend([*s, out])
|
||||
else:
|
||||
x_w13, h, x_normed, rrms, s = add_norm_quantize_matmul(x, residual, kwargs["ffn_norm"], kwargs["w13"], kwargs["s_13"],
|
||||
self.norm_eps, amax_x=kwargs["amax_x13"],
|
||||
next_amax_x=kwargs["next_amax_x13"],
|
||||
grad_amax_state=kwargs["grad_amax_xw13"],
|
||||
next_grad_amax_state=kwargs["next_grad_amax_xw13"])
|
||||
saves.extend([x_normed, rrms, *s, x_w13])
|
||||
out, s = silu_w13_quantize_matmul(x_w13, kwargs["w2"], kwargs["s_2"], amax_x2=kwargs["amax_x2"],
|
||||
next_amax_x2=kwargs["next_amax_x2"],
|
||||
grad_amax_xw13=kwargs["grad_amax_xw13"],
|
||||
next_grad_amax_xw13=kwargs["next_grad_amax_xw13"],
|
||||
grad_amax_xout=kwargs["grad_amax_xout"],
|
||||
next_grad_amax_xout=kwargs["next_grad_amax_xout"])
|
||||
saves.extend([*s, out])
|
||||
return out, h, saves
|
||||
|
||||
@function(precompile=True, precompile_backward=True)
|
||||
def run_layer(self, x:Tensor, freqs_cis:Tensor, attn_kwargs:dict, ffn_kwargs:dict, save:bool=True):
|
||||
attn, attn_saves = self.attention(x, freqs_cis, **attn_kwargs)
|
||||
ffn, h, ffn_saves = self.feed_forward(x, attn, **ffn_kwargs)
|
||||
h = h + ffn
|
||||
if save: return (h, *attn_saves, *ffn_saves)
|
||||
else: return (h,)
|
||||
|
||||
def shard(self, device:tuple[str, ...], mp:bool=False):
|
||||
from tinygrad.nn.state import get_parameters
|
||||
if not mp:
|
||||
for v in get_parameters(self): v.shard_(device, axis=None)
|
||||
else:
|
||||
# flat per-layer weights: axis 0 is n_layers, so shard axes are +1 vs per-layer Transformer
|
||||
def _shard_fp8(name:str, axis:int, std:float=0.02):
|
||||
w = getattr(self, name)
|
||||
if MXFP8:
|
||||
from extra.gemm.cdna_asm_gemm import quantize_mxfp8
|
||||
w_bf16 = Tensor.empty(self.n_layers, w.shape[1], w.shape[2], dtype=dtypes.bfloat16).shard(device, axis=axis).randn_like() * std
|
||||
w_q, w_e8, _ = quantize_mxfp8(w_bf16)
|
||||
w.replace(w_q)
|
||||
self._fp8_inv_scale[name].replace(w_e8.contiguous()).is_param_(False)
|
||||
self._fp8_next_inv_scale[name].replace(w_e8.contiguous()).is_param_(False)
|
||||
else:
|
||||
w.shard_(device, axis=axis)
|
||||
scale_axis = (1 if axis == 1 else None) if COLUMNWISE_WEIGHT_SCALE else None
|
||||
self._fp8_inv_scale[name] = self._fp8_inv_scale[name].shard(device, axis=scale_axis).contiguous().is_param_(False)
|
||||
self._fp8_next_inv_scale[name] = self._fp8_next_inv_scale[name].shard(device, axis=scale_axis).contiguous().is_param_(False)
|
||||
Tensor.realize(w, self._fp8_inv_scale[name], self._fp8_next_inv_scale[name])
|
||||
sstd = 0.02 / math.sqrt(2 * self.n_layers)
|
||||
_shard_fp8("wqkv", 1) # (n_layers, out, dim) shard out
|
||||
_shard_fp8("wo", 2, sstd) # (n_layers, dim, in) shard in
|
||||
if SPLIT_W13:
|
||||
_shard_fp8("w1", 1)
|
||||
_shard_fp8("w3", 1)
|
||||
else:
|
||||
_shard_fp8("w13", 1) # (n_layers, hidden*2, dim) shard out
|
||||
_shard_fp8("w2", 2, sstd) # (n_layers, dim, hidden) shard in
|
||||
self.attention_norm.shard_(device, axis=None).realize()
|
||||
self.ffn_norm.shard_(device, axis=None).realize()
|
||||
self.norm.weight.shard_(device, axis=None).realize()
|
||||
self.tok_embeddings.weight.shard_(device, axis=0).realize()
|
||||
self.output.shard_(device, axis=1).realize()
|
||||
self.freqs_cis.shard_(device, axis=None).realize()
|
||||
for amax_dict in (self._fp8_amax, self._fp8_next_amax, self._fp8_grad_amax, self._fp8_next_grad_amax):
|
||||
for name in amax_dict:
|
||||
for i in range(len(amax_dict[name])):
|
||||
amax_dict[name][i] = amax_dict[name][i].to(device).contiguous().is_param_(False)
|
||||
|
||||
def reset_amax(self):
|
||||
for st in (self._fp8_next_amax, self._fp8_next_grad_amax):
|
||||
for ts in st.values():
|
||||
for t in ts: t.assign(0)
|
||||
|
||||
def update_amax(self):
|
||||
for cur, nxt in ((self._fp8_amax, self._fp8_next_amax), (self._fp8_grad_amax, self._fp8_next_grad_amax)):
|
||||
for name in cur:
|
||||
for c, n in zip(cur[name], nxt[name]): c.assign(n)
|
||||
|
||||
def __call__(self, tokens:Tensor, save:bool=True):
|
||||
h = self.tok_embeddings(tokens)
|
||||
freqs_cis = self.freqs_cis.cast(h.dtype)
|
||||
if not getenv("HK_FLASH_ATTENTION"): freqs_cis = freqs_cis[:, :tokens.shape[1], :, :, :]
|
||||
a, na, ga, nga, s = self._fp8_amax, self._fp8_next_amax, self._fp8_grad_amax, self._fp8_next_grad_amax, self._fp8_inv_scale
|
||||
def amax_kwargs(i:int, act_names:tuple[str, ...], grad_names:tuple[str, ...]) -> dict[str, Tensor|None]:
|
||||
specs = (("amax_", a, act_names), ("next_amax_", na, act_names), ("grad_amax_", ga, grad_names), ("next_grad_amax_", nga, grad_names))
|
||||
if MXFP4: return dict.fromkeys(f"{prefix}{name}" for prefix, _, names in specs for name in names)
|
||||
return {f"{prefix}{name}":val[name][i] for prefix, val, names in specs for name in names}
|
||||
for i in range(self.n_layers):
|
||||
attn_kwargs = dict(attention_norm=self.attention_norm[i], wqkv=self.wqkv[i], wo=self.wo[i], s_qkv=s["wqkv"][i], s_o=s["wo"][i],
|
||||
**amax_kwargs(i, ("xqkv", "xo"), ("xqkv", "xo")))
|
||||
ffn_kwargs = dict(ffn_norm=self.ffn_norm[i], w2=self.w2[i], s_2=s["w2"][i], **amax_kwargs(i, ("x2",), ("xout",)))
|
||||
if SPLIT_W13:
|
||||
ffn_kwargs.update(w1=self.w1[i], w3=self.w3[i], s_1=s["w1"][i], s_3=s["w3"][i], **amax_kwargs(i, ("x1", "x3"), ("xw1", "xw3")))
|
||||
else:
|
||||
ffn_kwargs.update(w13=self.w13[i], s_13=s["w13"][i], **amax_kwargs(i, ("x13",), ("xw13",)))
|
||||
h, *_ = self.run_layer(h, freqs_cis, attn_kwargs, ffn_kwargs, save=save)
|
||||
|
||||
logits = matmul(self.norm(h), self.output[0], fp8=False)[0]
|
||||
return logits
|
||||
|
||||
def _get_pads(uop:UOp) -> list[UOp]:
|
||||
if uop.op == Ops.ADD: return _get_pads(uop.src[0]) + _get_pads(uop.src[1])
|
||||
return [uop]
|
||||
|
||||
def apply_grad(grad_buf:Tensor, new_grad:UOp):
|
||||
pads = _get_pads(new_grad)
|
||||
if len(pads) <= 1:
|
||||
new_grad = new_grad.cast(grad_buf.dtype)
|
||||
grad_buf.uop = grad_buf.uop.after(grad_buf.uop.store(grad_buf.uop + new_grad))
|
||||
return
|
||||
cur = grad_buf.uop
|
||||
for pad in sorted(pads, key=lambda p: p.marg[0][0] if p.op == Ops.PAD else 0, reverse=True):
|
||||
if pad.op == Ops.PAD:
|
||||
grad_shrink = tuple([(p[0], s+p[0]) for s,p in zip(pad.src[0].shape, pad.marg)])
|
||||
buf_slice = cur.shrink(grad_shrink)
|
||||
cur = cur.after(buf_slice.store(buf_slice + pad.src[0].cast(cur.dtype)))
|
||||
else:
|
||||
cur = cur.after(cur.store(cur + pad.cast(cur.dtype)))
|
||||
grad_buf.uop = cur
|
||||
|
||||
if __name__ == "__main__":
|
||||
config = {}
|
||||
BS = config["BS"] = getenv("BS", 16)
|
||||
SEQLEN = config["SEQLEN"] = getenv("SEQLEN", 8192)
|
||||
SMALL = config["SMALL"] = getenv("SMALL", 0)
|
||||
|
||||
from examples.llama3 import MODEL_PARAMS
|
||||
model_params = MODEL_PARAMS[llama_size:=getenv("LLAMA3_SIZE", "8B")]["args"]
|
||||
# vocab_size from mixtral tokenizer
|
||||
if not SMALL: model_params |= {"vocab_size": 32000}
|
||||
real_vocab_size = model_params['vocab_size']
|
||||
if (llama_layers:=getenv("LLAMA_LAYERS")) != 0: model_params["n_layers"] = llama_layers
|
||||
|
||||
# pad vocab
|
||||
if (MP := getenv("MP", 1)) > 1: model_params["vocab_size"] = round_up(model_params["vocab_size"], 256 * MP)
|
||||
vocab_mask:Tensor = Tensor.arange(model_params["vocab_size"]).reshape(1, 1, -1) >= real_vocab_size
|
||||
|
||||
model = FlatTransformer(**model_params, max_context=SEQLEN)
|
||||
|
||||
state = nn.state.get_state_dict(model)
|
||||
print("tensor count:", len(state))
|
||||
|
||||
# shard the model
|
||||
from tinygrad import Device
|
||||
is_dp = (DP := getenv("DP", 1)) > 1
|
||||
is_mp = (MP := getenv("MP", 1)) > 1
|
||||
is_sharding = is_dp or is_mp
|
||||
device_count = max(DP, MP)
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(device_count))
|
||||
|
||||
model.shard(device, is_mp)
|
||||
|
||||
if is_dp: vocab_mask.shard_(device, axis=None).realize()
|
||||
if is_mp: vocab_mask.shard_(device, axis=2).realize()
|
||||
|
||||
# preallocate all the grad buffers and zero them out
|
||||
grad_dtype = lambda x: dtypes.bfloat16 if x.dtype in dtypes.fp8s else x.dtype
|
||||
grads = {x:x.zeros_like(dtype=grad_dtype(x)).contiguous() for x in state.values() if x.is_param}
|
||||
|
||||
fp8_amax = [t for ts in model._fp8_amax.values() for t in ts]
|
||||
fp8_grad_amax = [t for ts in model._fp8_grad_amax.values() for t in ts]
|
||||
|
||||
# print model size
|
||||
sz = 0
|
||||
for k,v in state.items():
|
||||
print(f"{colored(k, 'green' if v in grads else 'white'):30s} {str(v.shape):30s} {str(v.dtype):20s} {v.device} {v.nbytes()/1e9:.2f} GB")
|
||||
sz += v.nbytes()
|
||||
print(f"total sz: {sz/1e9:.2f} GB")
|
||||
|
||||
with Timing("fake data: "): tokens = Tensor.randint(BS, SEQLEN+1, low=0, high=real_vocab_size, dtype=dtypes.int)
|
||||
with Timing("realize weights/grads/data: "): Tensor.realize(*state.values(), *grads.values(), tokens)
|
||||
print("mem per device: " + ', '.join(f"{dev}: {mem/1e9:.2f} GB" for dev, mem in sorted(GlobalCounters.mem_used_per_device.items())))
|
||||
if DP > 1: tokens = tokens.shard(tuple(f"{Device.DEFAULT}:{i}" for i in range(DP)), axis=0)
|
||||
if MP > 1: tokens = tokens.shard(tuple(f"{Device.DEFAULT}:{i}" for i in range(MP)))
|
||||
|
||||
@TinyJit
|
||||
def fwd_bwd(tokens:Tensor):
|
||||
with Timing("python forward: "):
|
||||
model.reset_amax()
|
||||
logits = model(tokens[:, :-1], save=llama_size=="8B")
|
||||
loss = vocab_mask.where(-1e9, logits).sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
with Timing("python backward: "):
|
||||
for t,g in zip(grads, loss.gradient(*grads)):
|
||||
apply_grad(grads[t], g.uop)
|
||||
with Timing("run fwd_bwd: "): loss.realize(*grads.values(), *fp8_amax, *fp8_grad_amax)
|
||||
|
||||
@TinyJit
|
||||
def optim_step():
|
||||
for g in grads.values(): g.assign(g.zeros_like())
|
||||
Tensor.realize(*grads.values())
|
||||
|
||||
for i in range(6):
|
||||
GlobalCounters.reset()
|
||||
profile_marker(f"step {i}")
|
||||
with Timing(colored(f"*** step {i}: ", "red")):
|
||||
fwd_bwd(tokens)
|
||||
optim_step()
|
||||
print("mem per device: " + ', '.join(f"{dev}: {mem/1e9:.2f} GB" for dev, mem in sorted(GlobalCounters.mem_used_per_device.items())))
|
||||
@@ -1,351 +0,0 @@
|
||||
import math, os, functools
|
||||
if __name__ == "__main__":
|
||||
os.environ["DEFAULT_FLOAT"] = "bfloat16"
|
||||
os.environ["OPTIM_DTYPE"] = "bfloat16"
|
||||
if "DEV" not in os.environ: os.environ["DEV"] = "NULL::gfx950"
|
||||
# CDNA
|
||||
os.environ["DEVICE_IN_FUNCTION_BUG"] = "1"
|
||||
os.environ["ALL2ALL"] = "1"
|
||||
os.environ["USE_ATOMICS"] = "1"
|
||||
from tinygrad import Tensor, nn, function, getenv, dtypes, TinyJit
|
||||
from tinygrad.helpers import Timing, colored, GlobalCounters, profile_marker
|
||||
from tinygrad.uop.ops import Ops, UOp
|
||||
from extra.models.llama import apply_rotary_emb
|
||||
from extra.llama_kernels.rmsnorm import rmsnorm
|
||||
from extra.gemm.cdna_asm_gemm import _mx_block_scale, _mx_block_scale_3d, quantize_mxfp8
|
||||
from extra.gemm.moe_gemm import grouped_mx_gemm
|
||||
from extra.gemm.moe_routing import route, dispatch, combine
|
||||
|
||||
FP8_DTYPE = dtypes.fp8e4m3
|
||||
FP8_MAX = 448.0
|
||||
INIT_STD = 0.02
|
||||
ASM_GEMM = getenv("ASM_GEMM", 0)
|
||||
|
||||
|
||||
def _quant_dequant_fwd(x:Tensor) -> Tensor:
|
||||
# x (2d bf16) -> bf16 value after an mxfp8 round-trip (1x32 block scaling on the last axis)
|
||||
M, K = x.shape
|
||||
scale_K = K // 32
|
||||
amax = x.float().reshape(M, scale_K, 32).abs().max(axis=-1)
|
||||
e8 = (amax.maximum(1e-38).log2().floor() + 127).clamp(0, 254).cast(dtypes.uint8)
|
||||
qscale = (127.0 - e8.cast(dtypes.float32)).exp2().reshape(M, scale_K, 1).expand(M, scale_K, 32).reshape(M, K)
|
||||
x_fp8 = (x.float() * qscale).clamp(-FP8_MAX, FP8_MAX).cast(FP8_DTYPE).cast(dtypes.float32)
|
||||
return (x_fp8 * _mx_block_scale(e8)).cast(dtypes.bfloat16)
|
||||
|
||||
@functools.cache
|
||||
def _quant_dequant_fwd_fxn(x_p, device):
|
||||
return _quant_dequant_fwd(Tensor(x_p, device=device))
|
||||
|
||||
def _quant_dequant_bwd(grad:UOp, call:UOp) -> tuple:
|
||||
return (Tensor(grad).cast(dtypes.bfloat16).uop,)
|
||||
|
||||
def quant_dequant_mx(x:Tensor) -> Tensor:
|
||||
fxn = _quant_dequant_fwd_fxn(x.as_param(0).uop, x.device)
|
||||
return Tensor(UOp.maketuple(fxn.uop).call(x.uop, grad_fxn=_quant_dequant_bwd).gettuple(0))
|
||||
|
||||
def _mx_scale(e8:Tensor) -> Tensor:
|
||||
return _mx_block_scale(e8) if e8.ndim == 2 else _mx_block_scale_3d(e8)
|
||||
|
||||
def _dequant_fwd(w_q:Tensor, w_scale:Tensor) -> Tensor:
|
||||
return w_q.cast(dtypes.bfloat16) * _mx_scale(w_scale)
|
||||
|
||||
@functools.cache
|
||||
def _dequant_fwd_fxn(wq_p, ws_p, device):
|
||||
return _dequant_fwd(Tensor(wq_p, device=device), Tensor(ws_p, device=device))
|
||||
|
||||
def _dequant_bwd(grad:UOp, call:UOp) -> tuple:
|
||||
return (Tensor(grad).cast(dtypes.bfloat16).uop, None)
|
||||
|
||||
def dequant_weight(w_q:Tensor, w_scale:Tensor) -> Tensor:
|
||||
fxn = _dequant_fwd_fxn(w_q.as_param(0).uop, w_scale.as_param(1).uop, w_q.device)
|
||||
call = UOp.maketuple(fxn.uop).call(w_q.uop, w_scale.uop, grad_fxn=_dequant_bwd)
|
||||
return Tensor(call.gettuple(0))
|
||||
|
||||
def matmul_mx(x:Tensor, w_q:Tensor, w_scale:Tensor) -> Tensor:
|
||||
l_shape = x.shape[:-1]
|
||||
if ASM_GEMM:
|
||||
from extra.gemm.cdna_asm_gemm import asm_gemm, can_use_asm_gemm, mx_pack
|
||||
x2, K, N = x.reshape(-1, x.shape[-1]), x.shape[-1], w_q.shape[0]
|
||||
wq, ws = w_q, w_scale
|
||||
if (pad := (-K) % 256):
|
||||
x2 = x2.pad(((0, 0), (0, pad)))
|
||||
wq = wq.pad(((0, 0), (0, pad)))
|
||||
ws = ws.pad(((0, 0), (0, pad // 32)), value=127).cast(dtypes.uint8)
|
||||
if (npad := (-N) % 256):
|
||||
wq = wq.pad(((0, npad), (0, 0)))
|
||||
ws = ws.pad(((0, npad), (0, 0)), value=127).cast(dtypes.uint8)
|
||||
x_q, x_e8, x_si = quantize_mxfp8(x2)
|
||||
if x_si is not None and can_use_asm_gemm(x_q, wq.T):
|
||||
out = asm_gemm(x_q, wq.T, mx=True, mx_scales=(x_si, x_e8, mx_pack(ws), ws), mx_w_stored=True)
|
||||
return (out[:, :N] if npad else out).reshape(*l_shape, N).cast(dtypes.bfloat16)
|
||||
x_phys = quant_dequant_mx(x.reshape(-1, x.shape[-1])).reshape(*l_shape, x.shape[-1])
|
||||
w_phys = dequant_weight(w_q, w_scale)
|
||||
return (x_phys @ w_phys.T).cast(dtypes.bfloat16)
|
||||
|
||||
def _pad_to_mult(t:Tensor, axis:int, mult:int=256) -> Tensor:
|
||||
if (r := (-t.shape[axis]) % mult) == 0: return t
|
||||
pads = [(0, 0)] * t.ndim
|
||||
pads[axis] = (0, r)
|
||||
return t.pad(tuple(pads))
|
||||
|
||||
def _pad_cols(t:Tensor) -> Tensor: return _pad_to_mult(t, -1)
|
||||
def _pad_rows(t:Tensor) -> Tensor: return _pad_to_mult(t, -2)
|
||||
|
||||
def swiglu(x:Tensor, limit:float=7.0, alpha:float=1.702) -> Tensor:
|
||||
x_glu, x_linear = x[..., ::2], x[..., 1::2]
|
||||
x_glu = x_glu.clamp(max_=limit)
|
||||
x_linear = x_linear.clamp(-limit, limit)
|
||||
return (x_glu * (alpha * x_glu).sigmoid()) * (x_linear + 1)
|
||||
|
||||
def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0) -> Tensor:
|
||||
freqs = 1.0 / (theta ** (Tensor.arange(0, dim, 2, dtype=dtypes.float32)[:(dim // 2)] / dim))
|
||||
freqs = Tensor.arange(end, dtype=dtypes.float32).unsqueeze(dim=1) * freqs.unsqueeze(dim=0)
|
||||
return Tensor.stack(freqs.cos(), freqs.sin(), dim=-1).cast(dtypes.default_float).reshape(1, end, 1, dim//2, 2)
|
||||
|
||||
class GPTOSS:
|
||||
def __init__(self, dim:int, n_layers:int, n_heads:int, n_kv_heads:int, head_dim:int, n_experts:int, experts_per_tok:int,
|
||||
intermediate_size:int, vocab_size:int, norm_eps:float=1e-5, rope_theta:int=150000, sliding_window:int=128,
|
||||
swiglu_limit:float=7.0, max_context:int=8192):
|
||||
self.dim, self.n_layers, self.n_heads, self.n_kv_heads, self.head_dim = dim, n_layers, n_heads, n_kv_heads, head_dim
|
||||
self.n_rep = n_heads // n_kv_heads
|
||||
self.n_experts, self.experts_per_tok, self.intermediate_size = n_experts, experts_per_tok, intermediate_size
|
||||
self.vocab_size, self.norm_eps, self.sliding_window, self.swiglu_limit = vocab_size, norm_eps, sliding_window, swiglu_limit
|
||||
self.sm_scale = 1.0 / math.sqrt(head_dim)
|
||||
|
||||
scaled_std = INIT_STD / math.sqrt(2 * n_layers)
|
||||
q_dim, qkv_dim = n_heads * head_dim, head_dim * (n_heads + 2 * n_kv_heads)
|
||||
|
||||
# attn
|
||||
self.wqkv, self.wqkv_scale = self._quant_weight(n_layers, qkv_dim, dim)
|
||||
self.wqkv_bias = Tensor.zeros(n_layers, qkv_dim, dtype=dtypes.bfloat16).contiguous()
|
||||
self.wo, self.wo_scale = self._quant_weight(n_layers, dim, q_dim, std=scaled_std)
|
||||
self.wo_bias = Tensor.zeros(n_layers, dim, dtype=dtypes.bfloat16).contiguous()
|
||||
self.sinks = Tensor.zeros(n_layers, n_heads, dtype=dtypes.bfloat16).contiguous()
|
||||
self.attention_norm = Tensor.ones(n_layers, dim).contiguous()
|
||||
|
||||
# moe ffn
|
||||
self.ffn_norm = Tensor.ones(n_layers, dim).contiguous()
|
||||
self.gate = Tensor.normal(n_layers, n_experts, dim, mean=0.0, std=INIT_STD, dtype=dtypes.bfloat16)
|
||||
self.gate_bias = Tensor.zeros(n_layers, n_experts, dtype=dtypes.bfloat16).contiguous()
|
||||
self.w_gate_up, self.w_gate_up_scale = self._quant_weight(n_layers, n_experts, intermediate_size * 2, dim, moe=True)
|
||||
self.w_gate_up_bias = Tensor.zeros(n_layers, n_experts, intermediate_size * 2, dtype=dtypes.bfloat16).contiguous()
|
||||
self.w_down, self.w_down_scale = self._quant_weight(n_layers, n_experts, dim, intermediate_size, std=scaled_std, moe=True)
|
||||
self.w_down_bias = Tensor.zeros(n_layers, n_experts, dim, dtype=dtypes.bfloat16).contiguous()
|
||||
|
||||
# output
|
||||
self.norm = nn.RMSNorm(dim, norm_eps)
|
||||
self.tok_embeddings = nn.Embedding(vocab_size, dim)
|
||||
self.tok_embeddings.weight = Tensor.normal(vocab_size, dim, mean=0.0, std=INIT_STD, dtype=dtypes.bfloat16)
|
||||
self.output = Tensor.normal(vocab_size, dim, mean=0.0, std=INIT_STD, dtype=dtypes.bfloat16)
|
||||
self.freqs_cis = precompute_freqs_cis(head_dim, max_context * 2, rope_theta).contiguous().is_param_(False)
|
||||
|
||||
def _quant_weight(self, *shape:int, std:float=INIT_STD, moe:bool=False):
|
||||
def _one(*s:int):
|
||||
w = Tensor.zeros(*s) if getenv("ZEROS") else Tensor.normal(*s, mean=0.0, std=std)
|
||||
w_q, w_e8, _ = quantize_mxfp8(_pad_cols(_pad_rows(w)) if moe else w)
|
||||
return w_q, w_e8.is_param_(False)
|
||||
if moe:
|
||||
qs = [_one(*shape[1:]) for _ in range(shape[0])]
|
||||
return [q[0] for q in qs], [q[1] for q in qs]
|
||||
return _one(*shape)
|
||||
|
||||
def _attn_mask(self, seqlen:int, dtype) -> Tensor:
|
||||
i, j = Tensor.arange(seqlen).reshape(seqlen, 1), Tensor.arange(seqlen).reshape(1, seqlen)
|
||||
return (j <= i).where(0.0, -1e30).cast(dtype).contiguous()
|
||||
|
||||
def _sliding_attention(self, xq:Tensor, xk:Tensor, xv:Tensor, sinks:Tensor) -> Tensor:
|
||||
bsz, seqlen, H, hd = xq.shape
|
||||
KV, R, W = self.n_kv_heads, self.n_rep, self.sliding_window
|
||||
assert seqlen % W == 0, f"seqlen {seqlen} must be a multiple of sliding_window {W} for banded attention"
|
||||
nb = seqlen // W
|
||||
q = xq.reshape(bsz, seqlen, KV, R, hd).permute(0, 2, 3, 1, 4).reshape(bsz, KV, R, nb, W, hd).float()
|
||||
k, v = (x.permute(0, 2, 1, 3).reshape(bsz, KV, 1, nb, W, hd).float() for x in (xk, xv))
|
||||
kk, vv = (x.pad((None, None, None, (1, 0), None, None))[:, :, :, :nb].cat(x, dim=-2) for x in (k, v))
|
||||
sc = (q @ kk.transpose(-1, -2)) * self.sm_scale # (B,KV,R,nb,W,2W)
|
||||
i, j, pv = Tensor.arange(W).reshape(W, 1), Tensor.arange(2 * W).reshape(1, 2 * W), Tensor.arange(nb).reshape(nb, 1, 1) >= 1
|
||||
sc = ((j > i) & (j <= i + W) & (pv | (j >= W))).where(sc, -float("inf"))
|
||||
sink = sinks.reshape(1, KV, R, 1, 1, 1).float()
|
||||
m = sc.max(-1, keepdim=True).maximum(sink)
|
||||
e = (sc - m).exp()
|
||||
p = (e / (e.sum(-1, keepdim=True) + (sink - m).exp())).cast(dtypes.bfloat16)
|
||||
attn = p @ vv.cast(dtypes.bfloat16)
|
||||
return attn.reshape(bsz, KV, R, seqlen, hd).permute(0, 3, 1, 2, 4).reshape(bsz, seqlen, H * hd)
|
||||
|
||||
def attention(self, x:Tensor, freqs_cis:Tensor, mask:Tensor, sliding:bool, *, attention_norm:Tensor, wqkv:Tensor,
|
||||
wqkv_scale:Tensor, wqkv_bias:Tensor, wo:Tensor, wo_scale:Tensor, wo_bias:Tensor, sinks:Tensor):
|
||||
bsz, seqlen, _ = x.shape
|
||||
x_normed, rrms = rmsnorm(x, self.norm_eps)
|
||||
qkv = matmul_mx(x_normed * attention_norm, wqkv, wqkv_scale) + wqkv_bias
|
||||
qkv = qkv.reshape(bsz, seqlen, self.n_kv_heads, self.n_rep + 2, self.head_dim)
|
||||
xq = qkv[:, :, :, :self.n_rep].reshape(bsz, seqlen, self.n_heads, self.head_dim)
|
||||
xk, xv = qkv[:, :, :, self.n_rep], qkv[:, :, :, self.n_rep + 1]
|
||||
xq, xk = apply_rotary_emb(xq, xk, freqs_cis)
|
||||
xq, xk, xv = xq.cast(dtypes.bfloat16), xk.cast(dtypes.bfloat16), xv.cast(dtypes.bfloat16) # (B,N,H,D)/(B,N,KV,D)
|
||||
|
||||
if sliding:
|
||||
attn = self._sliding_attention(xq, xk, xv, sinks)
|
||||
elif getenv("HK_FLASH_ATTENTION"):
|
||||
from extra.thunder.amd.fa import flash_attention
|
||||
attn, *_ = flash_attention(xq, xk, xv, is_causal=True, write_flat=True, sinks=sinks)
|
||||
attn = attn.reshape(bsz, seqlen, self.n_heads * self.head_dim)
|
||||
else:
|
||||
xqm = xq.reshape(bsz, seqlen, self.n_kv_heads, self.n_rep, self.head_dim).permute(0, 2, 3, 1, 4)
|
||||
xkm, xvm = xk.permute(0, 2, 1, 3).unsqueeze(2), xv.permute(0, 2, 1, 3).unsqueeze(2)
|
||||
scores = (xqm @ xkm.transpose(-2, -1)).float() * self.sm_scale + mask
|
||||
sink = sinks.reshape(1, self.n_kv_heads, self.n_rep, 1, 1).float()
|
||||
m = scores.max(-1, keepdim=True).maximum(sink)
|
||||
e = (scores - m).exp()
|
||||
w = (e / (e.sum(-1, keepdim=True) + (sink - m).exp())).cast(dtypes.bfloat16)
|
||||
attn = (w @ xvm).permute(0, 3, 1, 2, 4).reshape(bsz, seqlen, self.n_heads * self.head_dim)
|
||||
|
||||
out = matmul_mx(attn, wo, wo_scale) + wo_bias
|
||||
return out, [x_normed, rrms, attn]
|
||||
|
||||
def feed_forward(self, x:Tensor, *, ffn_norm:Tensor, gate:Tensor, gate_bias:Tensor,
|
||||
w_gate_up:Tensor, w_gate_up_scale:Tensor, w_gate_up_bias:Tensor,
|
||||
w_down:Tensor, w_down_scale:Tensor, w_down_bias:Tensor):
|
||||
x_normed, rrms = rmsnorm(x, self.norm_eps)
|
||||
inp = x_normed * ffn_norm
|
||||
logits = inp.float() @ gate.float().T + gate_bias.float()
|
||||
dim, inter = self.dim, self.intermediate_size
|
||||
|
||||
if getenv("GROUPED_MOE", 0):
|
||||
bsz, seqlen = x.shape[:2]
|
||||
inp, logits = inp.reshape(-1, dim), logits.reshape(-1, self.n_experts)
|
||||
r = route(logits, self.experts_per_tok, self.n_experts)
|
||||
onehot = r.rows_e.one_hot(self.n_experts).float()
|
||||
xg = dispatch(_pad_cols(inp.cast(dtypes.bfloat16)), r)
|
||||
h = grouped_mx_gemm(xg, (w_gate_up, w_gate_up_scale), r.off)[:, :2*inter] + (onehot @ w_gate_up_bias.float()).cast(dtypes.bfloat16)
|
||||
y = swiglu(h, self.swiglu_limit)
|
||||
z = grouped_mx_gemm(_pad_cols(y.cast(dtypes.bfloat16)), (w_down, w_down_scale), r.off)[:, :dim] \
|
||||
+ (onehot @ w_down_bias.float()).cast(dtypes.bfloat16)
|
||||
out = combine(z, r, inp.shape[0], self.experts_per_tok).reshape(bsz, seqlen, dim)
|
||||
else:
|
||||
thresh = logits.topk(self.experts_per_tok)[0][..., -1:]
|
||||
weights = (logits >= thresh).where(logits, -float("inf")).softmax(-1)
|
||||
|
||||
out = None
|
||||
for e in range(self.n_experts):
|
||||
gu_q, gu_s = w_gate_up[e][:2*inter, :dim].contiguous(), w_gate_up_scale[e][:2*inter, :dim//32].contiguous()
|
||||
dn_q, dn_s = w_down[e][:dim, :inter].contiguous(), w_down_scale[e][:dim, :inter//32].contiguous()
|
||||
gate_up = matmul_mx(inp, gu_q, gu_s) + w_gate_up_bias[e]
|
||||
y = (matmul_mx(swiglu(gate_up, self.swiglu_limit), dn_q, dn_s) + w_down_bias[e]).contiguous()
|
||||
contrib = weights[..., e:e+1].cast(y.dtype) * y
|
||||
out = contrib if out is None else out + contrib
|
||||
return out, [x_normed, rrms]
|
||||
|
||||
@function(precompile=True, precompile_backward=True)
|
||||
def run_layer(self, x:Tensor, freqs_cis:Tensor, mask:Tensor, sliding:bool, attn_kwargs:dict, ffn_kwargs:dict, save:bool=True):
|
||||
attn, attn_saves = self.attention(x, freqs_cis, mask, sliding, **attn_kwargs)
|
||||
h = x + attn
|
||||
ffn, ffn_saves = self.feed_forward(h, **ffn_kwargs)
|
||||
h = h + ffn
|
||||
if save: return (h, *attn_saves, *ffn_saves)
|
||||
return (h,)
|
||||
|
||||
def shard(self, device:tuple[str, ...], mp:bool=False):
|
||||
assert not mp, "MP not supported"
|
||||
from tinygrad.nn.state import get_parameters
|
||||
for v in get_parameters(self): v.shard_(device, axis=None)
|
||||
Tensor.realize(*get_parameters(self))
|
||||
|
||||
def __call__(self, tokens:Tensor, save:bool=True):
|
||||
h = self.tok_embeddings(tokens)
|
||||
bsz, seqlen = tokens.shape
|
||||
freqs_cis = self.freqs_cis.cast(h.dtype)[:, :seqlen, :, :, :]
|
||||
mask_full = None if getenv("HK_FLASH_ATTENTION") else self._attn_mask(seqlen, dtypes.float32)
|
||||
for i in range(self.n_layers):
|
||||
attn_kwargs = dict(attention_norm=self.attention_norm[i], wqkv=self.wqkv[i], wqkv_scale=self.wqkv_scale[i],
|
||||
wqkv_bias=self.wqkv_bias[i], wo=self.wo[i], wo_scale=self.wo_scale[i], wo_bias=self.wo_bias[i],
|
||||
sinks=self.sinks[i])
|
||||
ffn_kwargs = dict(ffn_norm=self.ffn_norm[i], gate=self.gate[i], gate_bias=self.gate_bias[i],
|
||||
w_gate_up=self.w_gate_up[i], w_gate_up_scale=self.w_gate_up_scale[i], w_gate_up_bias=self.w_gate_up_bias[i],
|
||||
w_down=self.w_down[i], w_down_scale=self.w_down_scale[i], w_down_bias=self.w_down_bias[i])
|
||||
h, *_ = self.run_layer(h, freqs_cis, mask_full, i % 2 == 0, attn_kwargs, ffn_kwargs, save=save)
|
||||
|
||||
logits = self.norm(h) @ self.output.T
|
||||
return logits
|
||||
|
||||
def _get_pads(uop:UOp) -> list[UOp]:
|
||||
if uop.op == Ops.ADD: return _get_pads(uop.src[0]) + _get_pads(uop.src[1])
|
||||
return [uop]
|
||||
|
||||
def apply_grad(grad_buf:Tensor, new_grad:UOp):
|
||||
pads = _get_pads(new_grad)
|
||||
if len(pads) <= 1:
|
||||
new_grad = new_grad.cast(grad_buf.dtype)
|
||||
grad_buf.uop = grad_buf.uop.after(grad_buf.uop.store(grad_buf.uop + new_grad))
|
||||
return
|
||||
cur = grad_buf.uop
|
||||
for pad in sorted(pads, key=lambda p: p.marg[0][0] if p.op == Ops.PAD else 0, reverse=True):
|
||||
if pad.op == Ops.PAD:
|
||||
grad_shrink = tuple([(p[0], s+p[0]) for s,p in zip(pad.src[0].shape, pad.marg)])
|
||||
buf_slice = cur.shrink(grad_shrink)
|
||||
cur = cur.after(buf_slice.store(buf_slice + pad.src[0].cast(cur.dtype)))
|
||||
else:
|
||||
cur = cur.after(cur.store(cur + pad.cast(cur.dtype)))
|
||||
grad_buf.uop = cur
|
||||
|
||||
GPT_OSS_20B = dict(dim=2880, n_layers=24, n_heads=64, n_kv_heads=8, head_dim=64, n_experts=32, experts_per_tok=4,
|
||||
intermediate_size=2880, vocab_size=128256, norm_eps=1e-5, rope_theta=150000, sliding_window=128,
|
||||
swiglu_limit=7.0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
config = {}
|
||||
BS = config["BS"] = getenv("BS", 16)
|
||||
SEQLEN = config["SEQLEN"] = getenv("SEQLEN", 8192)
|
||||
|
||||
model_params = GPT_OSS_20B
|
||||
real_vocab_size = model_params["vocab_size"]
|
||||
if (layers := getenv("LAYERS")) != 0: model_params["n_layers"] = layers
|
||||
|
||||
model = GPTOSS(**model_params, max_context=SEQLEN)
|
||||
|
||||
state = nn.state.get_state_dict(model)
|
||||
print("tensor count:", len(state))
|
||||
|
||||
from tinygrad import Device
|
||||
is_dp = (DP := getenv("DP", 1)) > 1
|
||||
device_count = DP
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(device_count))
|
||||
|
||||
if is_dp: model.shard(device)
|
||||
|
||||
# preallocate all the grad buffers and zero them out
|
||||
grad_dtype = lambda x: dtypes.bfloat16 if x.dtype in dtypes.fp8s else x.dtype
|
||||
grads = {x:x.zeros_like(dtype=grad_dtype(x)).contiguous() for x in state.values() if x.is_param}
|
||||
|
||||
# print model size
|
||||
sz = 0
|
||||
for k,v in state.items():
|
||||
print(f"{colored(k, 'green' if v in grads else 'white'):30s} {str(v.shape):30s} {str(v.dtype):20s} {v.device} {v.nbytes()/1e9:.2f} GB")
|
||||
sz += v.nbytes()
|
||||
print(f"total sz: {sz/1e9:.2f} GB")
|
||||
|
||||
with Timing("fake data: "): tokens = Tensor.randint(BS, SEQLEN+1, low=0, high=real_vocab_size, dtype=dtypes.int)
|
||||
with Timing("realize weights/grads/data: "): Tensor.realize(*state.values(), *grads.values(), tokens)
|
||||
print("mem per device: " + ', '.join(f"{dev}: {mem/1e9:.2f} GB" for dev, mem in sorted(GlobalCounters.mem_used_per_device.items())))
|
||||
if is_dp: tokens = tokens.shard(device, axis=0)
|
||||
|
||||
@TinyJit
|
||||
def fwd_bwd(tokens:Tensor):
|
||||
with Timing("python forward: "):
|
||||
logits = model(tokens[:, :-1], save=True)
|
||||
loss = logits.sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
with Timing("python backward: "):
|
||||
for t,g in zip(grads, loss.gradient(*grads)):
|
||||
apply_grad(grads[t], g.uop)
|
||||
with Timing("run fwd_bwd: "): loss.realize(*grads.values())
|
||||
|
||||
@TinyJit
|
||||
def optim_step():
|
||||
for g in grads.values(): g.assign(g.zeros_like())
|
||||
Tensor.realize(*grads.values())
|
||||
|
||||
for i in range(6):
|
||||
GlobalCounters.reset()
|
||||
profile_marker(f"step {i}")
|
||||
with Timing(colored(f"*** step {i}: ", "red")):
|
||||
fwd_bwd(tokens)
|
||||
optim_step()
|
||||
print("mem per device: " + ', '.join(f"{dev}: {mem/1e9:.2f} GB" for dev, mem in sorted(GlobalCounters.mem_used_per_device.items())))
|
||||
@@ -1,68 +0,0 @@
|
||||
import unittest
|
||||
from tinygrad import Tensor, TinyJit
|
||||
from tinygrad.nn.state import get_parameters
|
||||
from examples.mlperf.models.flat_llama import apply_grad
|
||||
|
||||
class FlatModel:
|
||||
def __init__(self, n_layers:int, dim:int, hidden:int):
|
||||
self.n_layers = n_layers
|
||||
self.w1 = Tensor.uniform(n_layers, dim, hidden, low=-0.1, high=0.1)
|
||||
self.w2 = Tensor.uniform(n_layers, hidden, dim, low=-0.1, high=0.1)
|
||||
self.scale = Tensor.uniform(dim, low=0.9, high=1.1)
|
||||
self.bias = Tensor.zeros(dim).contiguous()
|
||||
|
||||
def __call__(self, x:Tensor) -> Tensor:
|
||||
h = x
|
||||
for i in range(self.n_layers):
|
||||
h = (h @ self.w1[i]).relu() @ self.w2[i] + h
|
||||
return (h * self.scale + self.bias).sum()
|
||||
|
||||
class TestApplyGradE2E(unittest.TestCase):
|
||||
def _run_with_apply_grad(self, model, xs):
|
||||
grads = {p: Tensor.zeros(p.shape, dtype=p.dtype).contiguous().realize() for p in get_parameters(model)}
|
||||
for x in xs:
|
||||
loss = model(x)
|
||||
for p, g in zip(grads, loss.gradient(*grads)):
|
||||
apply_grad(grads[p], g.uop)
|
||||
Tensor.realize(loss, *grads.values())
|
||||
return [grads[p] for p in get_parameters(model)]
|
||||
|
||||
def _run_reference(self, model, xs):
|
||||
for x in xs: model(x).backward()
|
||||
return [p.grad for p in get_parameters(model)]
|
||||
|
||||
def _assert_close(self, got, expected, atol, rtol):
|
||||
for g, e in zip(got, expected):
|
||||
self.assertTrue(g.allclose(e, atol=atol, rtol=rtol).item(), f"grad mismatch (max abs diff {(g - e).abs().max().item()})")
|
||||
|
||||
def _assert_match(self, model, xs, atol, rtol):
|
||||
self._assert_close(self._run_with_apply_grad(model, xs), self._run_reference(model, xs), atol, rtol)
|
||||
|
||||
def test_e2e_single_step(self):
|
||||
model = FlatModel(n_layers=3, dim=8, hidden=16)
|
||||
Tensor.realize(*get_parameters(model))
|
||||
self._assert_match(model, [Tensor.randn(2, 8).realize()], atol=1e-4, rtol=1e-4)
|
||||
|
||||
def test_e2e_multi_step_accumulation(self):
|
||||
model = FlatModel(n_layers=4, dim=8, hidden=16)
|
||||
Tensor.realize(*get_parameters(model))
|
||||
self._assert_match(model, [Tensor.randn(2, 8).realize() for _ in range(3)], atol=1e-4, rtol=1e-4)
|
||||
|
||||
def test_e2e_jit(self):
|
||||
model = FlatModel(n_layers=3, dim=8, hidden=16)
|
||||
Tensor.realize(*get_parameters(model))
|
||||
grads = {p: Tensor.zeros(p.shape, dtype=p.dtype).contiguous().realize() for p in get_parameters(model)}
|
||||
|
||||
@TinyJit
|
||||
def fwd_bwd(x:Tensor):
|
||||
loss = model(x)
|
||||
for p, g in zip(grads, loss.gradient(*grads)): apply_grad(grads[p], g.uop)
|
||||
Tensor.realize(loss, *grads.values())
|
||||
|
||||
xs = [Tensor.randn(2, 8).realize() for _ in range(3)]
|
||||
for x in xs: fwd_bwd(x)
|
||||
self._assert_close([grads[p] for p in get_parameters(model)], self._run_reference(model, xs), atol=1e-3, rtol=1e-3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,137 +0,0 @@
|
||||
import os
|
||||
os.environ["WQKV"] = "1"
|
||||
import unittest
|
||||
import numpy as np
|
||||
from tinygrad import Tensor, nn, dtypes
|
||||
from tinygrad.device import Device
|
||||
from examples.mlperf.models.llama import Transformer
|
||||
from examples.mlperf.models.flat_llama import FlatTransformer
|
||||
|
||||
def copy_weights(flat:FlatTransformer, ref:Transformer):
|
||||
n_layers = flat.n_layers
|
||||
Tensor.realize(*nn.state.get_state_dict(ref).values())
|
||||
flat.wqkv.assign(Tensor(np.stack([ref.layers[i].attention.wqkv.weight.numpy() for i in range(n_layers)])))
|
||||
flat.wo.assign(Tensor(np.stack([ref.layers[i].attention.wo.weight.numpy() for i in range(n_layers)])))
|
||||
flat.w1.assign(Tensor(np.stack([ref.layers[i].feed_forward.w1.weight.numpy() for i in range(n_layers)])))
|
||||
flat.w2.assign(Tensor(np.stack([ref.layers[i].feed_forward.w2.weight.numpy() for i in range(n_layers)])))
|
||||
flat.w3.assign(Tensor(np.stack([ref.layers[i].feed_forward.w3.weight.numpy() for i in range(n_layers)])))
|
||||
flat.attention_norm.assign(Tensor(np.stack([ref.layers[i].attention_norm.weight.numpy() for i in range(n_layers)])))
|
||||
flat.ffn_norm.assign(Tensor(np.stack([ref.layers[i].ffn_norm.weight.numpy() for i in range(n_layers)])))
|
||||
flat.norm.weight.assign(Tensor(ref.norm.weight.numpy()))
|
||||
flat.tok_embeddings.weight.assign(Tensor(ref.tok_embeddings.weight.numpy()))
|
||||
flat.output.weight.assign(Tensor(ref.output.weight.numpy()))
|
||||
|
||||
class TestFlatLlama(unittest.TestCase):
|
||||
def test_forward_match(self):
|
||||
Tensor.manual_seed(42)
|
||||
params = dict(dim=128, hidden_dim=256, n_heads=4, n_kv_heads=2, n_layers=2, norm_eps=1e-5, vocab_size=1024, rope_theta=10000, max_context=64)
|
||||
ref = Transformer(**params)
|
||||
flat = FlatTransformer(**params)
|
||||
copy_weights(flat, ref)
|
||||
Tensor.realize(*nn.state.get_state_dict(flat).values())
|
||||
|
||||
tokens = Tensor([[1, 50, 100, 999, 2]])
|
||||
ref_logits = ref(tokens).realize()
|
||||
flat_logits = flat(tokens).realize()
|
||||
self.assertEqual(ref_logits.shape, flat_logits.shape)
|
||||
diff = (ref_logits - flat_logits).abs().max().item()
|
||||
self.assertLess(diff, 1e-5, f"forward mismatch: max abs diff {diff}")
|
||||
|
||||
def test_backward_match(self):
|
||||
Tensor.manual_seed(42)
|
||||
params = dict(dim=128, hidden_dim=256, n_heads=4, n_kv_heads=2, n_layers=2, norm_eps=1e-5, vocab_size=1024, rope_theta=10000, max_context=64)
|
||||
ref = Transformer(**params)
|
||||
flat = FlatTransformer(**params)
|
||||
copy_weights(flat, ref)
|
||||
|
||||
Tensor.realize(*nn.state.get_state_dict(flat).values())
|
||||
|
||||
tokens = Tensor([[1, 50, 100, 999, 2, 10]])
|
||||
|
||||
ref_loss = ref(tokens[:, :-1]).sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
ref_loss.backward()
|
||||
ref_grads = {k: v.grad.numpy() for k, v in nn.state.get_state_dict(ref).items() if v.grad is not None}
|
||||
|
||||
flat_loss = flat(tokens[:, :-1]).sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
flat_loss.backward()
|
||||
flat_grads = {k: v.grad.numpy() for k, v in nn.state.get_state_dict(flat).items() if v.grad is not None}
|
||||
|
||||
# check loss matches
|
||||
self.assertAlmostEqual(ref_loss.item(), flat_loss.item(), places=4)
|
||||
|
||||
# check output weight grad matches
|
||||
diff = abs(ref_grads["output.weight"] - flat_grads["output.weight"]).max()
|
||||
self.assertLess(diff, 1e-4, f"output.weight grad mismatch: max abs diff {diff}")
|
||||
|
||||
# check per-layer weight grads match
|
||||
for i in range(params["n_layers"]):
|
||||
for flat_key, ref_key in [
|
||||
("wqkv", f"layers.{i}.attention.wqkv.weight"),
|
||||
("wo", f"layers.{i}.attention.wo.weight"),
|
||||
("w1", f"layers.{i}.feed_forward.w1.weight"),
|
||||
("w2", f"layers.{i}.feed_forward.w2.weight"),
|
||||
("w3", f"layers.{i}.feed_forward.w3.weight"),
|
||||
]:
|
||||
diff = abs(ref_grads[ref_key] - flat_grads[flat_key][i]).max()
|
||||
self.assertLess(diff, 1e-4, f"layer {i} {flat_key} grad mismatch: max abs diff {diff}")
|
||||
|
||||
@unittest.skipUnless(Device.DEFAULT == "CPU", "multi-device CPU test")
|
||||
def test_forward_match_mp(self):
|
||||
Tensor.manual_seed(42)
|
||||
params = dict(dim=128, hidden_dim=256, n_heads=4, n_kv_heads=2, n_layers=2, norm_eps=1e-5, vocab_size=1024, rope_theta=10000, max_context=64)
|
||||
from tinygrad import Device
|
||||
devices = (f"{Device.DEFAULT}:0", f"{Device.DEFAULT}:1")
|
||||
ref = Transformer(**params)
|
||||
flat = FlatTransformer(**params)
|
||||
copy_weights(flat, ref)
|
||||
Tensor.realize(*nn.state.get_state_dict(flat).values())
|
||||
flat.shard(devices, mp=True)
|
||||
|
||||
tokens = Tensor([[1, 50, 100, 999, 2]], device=devices[0])
|
||||
ref_logits = ref(tokens.to(devices[0])).numpy()
|
||||
flat_logits = flat(tokens.shard(devices)).numpy()
|
||||
self.assertEqual(ref_logits.shape, flat_logits.shape)
|
||||
np.testing.assert_allclose(flat_logits, ref_logits, atol=1e-4, rtol=1e-4)
|
||||
|
||||
@unittest.skipUnless(Device.DEFAULT == "CPU", "multi-device CPU test")
|
||||
def test_forward_match_dp(self):
|
||||
Tensor.manual_seed(42)
|
||||
params = dict(dim=128, hidden_dim=256, n_heads=4, n_kv_heads=2, n_layers=2, norm_eps=1e-5, vocab_size=1024, rope_theta=10000, max_context=64)
|
||||
from tinygrad import Device
|
||||
devices = (f"{Device.DEFAULT}:0", f"{Device.DEFAULT}:1")
|
||||
ref = Transformer(**params)
|
||||
flat = FlatTransformer(**params)
|
||||
copy_weights(flat, ref)
|
||||
Tensor.realize(*nn.state.get_state_dict(flat).values())
|
||||
flat.shard(devices)
|
||||
|
||||
tokens = Tensor([[1, 50, 100, 999, 2], [2, 100, 50, 1, 999]], device=devices[0])
|
||||
ref_logits = ref(tokens.to(devices[0])).numpy()
|
||||
flat_logits = flat(tokens.shard(devices, axis=0)).numpy()
|
||||
self.assertEqual(ref_logits.shape, flat_logits.shape)
|
||||
np.testing.assert_allclose(flat_logits, ref_logits, atol=1e-4, rtol=1e-4)
|
||||
|
||||
@unittest.skipUnless(dtypes.fp8e4m3 in Device[Device.DEFAULT].renderer.supported_dtypes(), "fp8 not supported on this device")
|
||||
def test_forward_fp8(self):
|
||||
import examples.mlperf.models.flat_llama as flat_llama_mod
|
||||
old_fp8 = flat_llama_mod.FP8
|
||||
try:
|
||||
flat_llama_mod.FP8 = 1
|
||||
Tensor.manual_seed(42)
|
||||
params = dict(dim=128, hidden_dim=256, n_heads=4, n_kv_heads=2, n_layers=2, norm_eps=1e-5, vocab_size=1024, rope_theta=10000, max_context=64)
|
||||
ref = Transformer(**params)
|
||||
flat = FlatTransformer(**params)
|
||||
copy_weights(flat, ref)
|
||||
Tensor.realize(*nn.state.get_state_dict(flat).values())
|
||||
|
||||
tokens = Tensor([[1, 50, 100, 999, 2]])
|
||||
ref_logits = ref(tokens).numpy()
|
||||
flat_logits = flat(tokens).numpy()
|
||||
self.assertEqual(ref_logits.shape, flat_logits.shape)
|
||||
# FP8 has lower precision, allow larger tolerance
|
||||
np.testing.assert_allclose(flat_logits, ref_logits, atol=1.0, rtol=0.1)
|
||||
finally:
|
||||
flat_llama_mod.FP8 = old_fp8
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,144 +0,0 @@
|
||||
from tinygrad.tensor import Tensor
|
||||
from tinygrad.dtype import dtypes
|
||||
from tinygrad.nn.optim import Optimizer, OptimizerGroup
|
||||
from tinygrad.helpers import FUSE_OPTIM, getenv
|
||||
from tinygrad.uop.ops import UOp, Ops, AxisType
|
||||
|
||||
STOCHASTIC_ROUND = getenv("STOCHASTIC_ROUND", 0)
|
||||
MASTER_WEIGHTS = getenv("MASTER_WEIGHTS", 0)
|
||||
ZERO_OPTIM = getenv("ZERO_OPTIM", 0)
|
||||
FP8_AMAX_MARGIN = getenv("FP8_AMAX_MARGIN", 1.1)
|
||||
IMMEDIATE_SCALE = getenv("IMMEDIATE_SCALE", 0)
|
||||
MXFP8 = getenv("MXFP8", 0)
|
||||
|
||||
def stochastic_round_bf16(x:Tensor) -> Tensor:
|
||||
bits = x.bitcast(dtypes.uint32)
|
||||
if isinstance(x.device, tuple):
|
||||
shape = x.uop.shard_shape if x.uop.axis is not None else x.shape
|
||||
noise = Tensor(UOp(Ops.MSTACK, dtypes.default_float, tuple(Tensor.rand(*shape, device=d).uop for d in x.device)))
|
||||
else:
|
||||
noise = x.rand_like()
|
||||
noise = (noise * 0xFFFF).cast(dtypes.uint32)
|
||||
return ((bits + noise) & 0xFFFF0000).bitcast(dtypes.float32).cast(dtypes.bfloat16)
|
||||
|
||||
def clip_grads(grads:list[Tensor], grad_acc, clip_norm) -> Tensor:
|
||||
for g in grads: g.assign(g / grad_acc)
|
||||
total_norm = Tensor.stack(*[g.float().square().sum() for g in grads]).sum().sqrt().contiguous()
|
||||
for g in grads: g.assign((g * (clip_norm / (total_norm + 1e-6)).clamp(max_=1.0)).cast(g.dtype))
|
||||
return total_norm
|
||||
|
||||
class GradAccClipAdamW(Optimizer):
|
||||
def __init__(self, params:list[Tensor], lr=0.001, b1=0.9, b2=0.999, eps=1e-6, weight_decay=0.0, grad_acc=1, clip_norm=1.0, device=None, fused=FUSE_OPTIM):
|
||||
super().__init__(params, lr, device, fused)
|
||||
self.b1, self.b2, self.eps, self.wd = b1, b2, eps, weight_decay
|
||||
self.b1_t, self.b2_t = (Tensor.ones((1,), dtype=dtypes.float32, device=self.device) for _ in [b1, b2])
|
||||
self.zero = bool(ZERO_OPTIM) and isinstance(self.device, tuple) and not self.fused
|
||||
self.m = [self._zero_shard(x) for x in self._new_optim_param()]
|
||||
self.v = [self._zero_shard(x) for x in self._new_optim_param()]
|
||||
self.grad_acc, self.clip_norm = grad_acc, clip_norm
|
||||
if MASTER_WEIGHTS and self.params[0].dtype != dtypes.float32:
|
||||
self.master_params:list[Tensor]|None = [self._zero_shard(p.to(self.device).float().contiguous()) for p in self.params]
|
||||
else:
|
||||
self.master_params = None
|
||||
|
||||
def _zero_shard(self, t:Tensor) -> Tensor:
|
||||
if not self.zero or t.ndim < 2 or (t.shape[0] % len(self.device)) != 0: return t
|
||||
return Tensor(t.uop._shard(0, UOp.range(len(self.device), -1, AxisType.DEVICE)).unshard(0)).clone()
|
||||
|
||||
def _zero_gather(self, t:Tensor) -> Tensor:
|
||||
if not isinstance(t.device, tuple) or t.uop.axis != 0: return t
|
||||
n, sz = len(t.device), t.shape[0] // len(t.device)
|
||||
return Tensor.cat(*[t[p*sz:(p+1)*sz] for p in range(n)], dim=0)
|
||||
|
||||
def fschedule_step(self, grads:list[Tensor]) -> list[Tensor]:
|
||||
updates, extra = self._step([], grads)
|
||||
for i, tt in enumerate(self.params): tt.assign(self._apply_update(tt, updates[i], self.master_params[i] if self.master_params else None))
|
||||
fp8_inv_scales = [tt._inv_scale for tt in self.params if hasattr(tt, '_inv_scale')]
|
||||
fp8_next_inv_scales = [tt._next_inv_scale for tt in self.params if hasattr(tt, '_next_inv_scale')]
|
||||
return extra + self.params + self.buffers + (self.master_params or []) + fp8_inv_scales + fp8_next_inv_scales
|
||||
|
||||
def fstep(self, grads:list[Tensor], grad_norm:Tensor|None=None):
|
||||
Tensor.realize(*([grad_norm] if grad_norm is not None else []), *self.fschedule_step(grads))
|
||||
|
||||
def _step(self, params:list[Tensor], grads:list[Tensor]) -> tuple[list[Tensor], list[Tensor]]:
|
||||
grads = list(grads)
|
||||
|
||||
for i in range(len(grads)):
|
||||
if grads[i].device != self.m[i].device: grads[i] = grads[i].to(self.m[i].device)
|
||||
ret = []
|
||||
self.b1_t *= self.b1
|
||||
self.b2_t *= self.b2
|
||||
for i, g in enumerate(grads):
|
||||
m_new = self.b1 * self.m[i].float() + (1.0 - self.b1) * g.float()
|
||||
v_new = self.b2 * self.v[i].float() + (1.0 - self.b2) * (g.float() * g.float())
|
||||
self.m[i].assign(m_new.cast(self.m[i].dtype))
|
||||
self.v[i].assign(v_new.cast(self.v[i].dtype))
|
||||
m_hat = m_new / (1.0 - self.b1_t)
|
||||
v_hat = v_new / (1.0 - self.b2_t)
|
||||
up = m_hat / (v_hat.sqrt() + self.eps)
|
||||
ret.append(self.lr * up)
|
||||
return ret, [self.b1_t, self.b2_t] + self.m + self.v
|
||||
|
||||
def _apply_update(self, t:Tensor, up:Tensor, master:Tensor|None=None) -> Tensor:
|
||||
w = master if master is not None else t
|
||||
wd = self.wd if t.ndim >= 3 else 0.0
|
||||
up = up.float().shard_like(w) + self.lr.to(w.device) * wd * w.detach()
|
||||
new_w = w.detach() - up
|
||||
if master is not None: master.assign(new_w)
|
||||
if self.zero and not (MXFP8 and t.dtype in dtypes.fp8s): new_w = self._zero_gather(new_w)
|
||||
# when master is offloaded to a different device than the param, results are resharded back onto the param's (sharded) device
|
||||
offloaded = master is not None and master.device != t.device
|
||||
if STOCHASTIC_ROUND and t.dtype == dtypes.bfloat16:
|
||||
out = stochastic_round_bf16(new_w)
|
||||
return out.shard_like(t) if offloaded else out
|
||||
if t.dtype in dtypes.fp8s:
|
||||
if MXFP8:
|
||||
from extra.gemm.cdna_asm_gemm import quantize_mxfp8
|
||||
w_q, w_e8, _ = quantize_mxfp8(new_w.reshape(-1, new_w.shape[-1]))
|
||||
if self.zero: w_q, w_e8 = self._zero_gather(w_q), self._zero_gather(w_e8)
|
||||
new_e8 = w_e8.reshape(t._inv_scale.shape)
|
||||
t._inv_scale.assign(new_e8.shard_like(t._inv_scale) if offloaded else new_e8)
|
||||
ret = w_q.reshape(t.shape)
|
||||
return ret.shard_like(t) if offloaded else ret
|
||||
from examples.mlperf.models.flat_llama import FP8_MAX
|
||||
if IMMEDIATE_SCALE:
|
||||
amax_axis = tuple(range(t._inv_scale.ndim, new_w.ndim))
|
||||
new_inv = ((new_w.float().abs().max(axis=amax_axis).detach() + 1e-8) / FP8_MAX).cast(t._inv_scale.dtype)
|
||||
t._inv_scale.assign(new_inv.shard_like(t._inv_scale) if offloaded else new_inv)
|
||||
scale = new_inv.reciprocal().reshape(*new_inv.shape, *([1]*(new_w.ndim-new_inv.ndim)))
|
||||
ret = (new_w * scale).clamp(-FP8_MAX, FP8_MAX).cast(t.dtype)
|
||||
return ret.shard_like(t) if offloaded else ret
|
||||
# delayed scaling: reuse previous step's inv_scale
|
||||
t._inv_scale.assign(t._next_inv_scale)
|
||||
inv_scale = t._inv_scale.to(new_w.device) if offloaded else t._inv_scale
|
||||
scale = inv_scale.reciprocal().reshape(*inv_scale.shape, *([1]*(new_w.ndim-inv_scale.ndim)))
|
||||
scaled = (new_w * scale).clamp(-FP8_MAX, FP8_MAX)
|
||||
ret = scaled.cast(t.dtype)
|
||||
# update inv_scale for next step from quantized result
|
||||
new_amax = (ret.float().abs().max(axis=tuple(range(inv_scale.ndim, ret.ndim))) * inv_scale * FP8_AMAX_MARGIN).detach()
|
||||
new_inv = ((new_amax + 1e-8) / FP8_MAX).cast(t._inv_scale.dtype)
|
||||
t._next_inv_scale.assign(new_inv.shard_like(t._next_inv_scale) if offloaded else new_inv)
|
||||
return ret.shard_like(t) if offloaded else ret
|
||||
out = new_w.cast(t.dtype)
|
||||
return out.shard_like(t) if offloaded else out
|
||||
|
||||
class GradAccClipAdamWGroup(OptimizerGroup):
|
||||
def __init__(self, *optimizers:GradAccClipAdamW):
|
||||
super().__init__(*optimizers)
|
||||
for o in self.optimizers[1:]: o.lr = self.optimizers[0].lr
|
||||
def fstep(self, grads:list[Tensor], grad_norm:Tensor|None=None):
|
||||
offset = 0
|
||||
to_realize = []
|
||||
for o in self.optimizers:
|
||||
n = len(o.params)
|
||||
to_realize += o.fschedule_step(grads[offset:offset+n])
|
||||
offset += n
|
||||
Tensor.realize(*to_realize, *([grad_norm] if grad_norm is not None else []))
|
||||
@property
|
||||
def lr(self): return self.optimizers[0].lr
|
||||
@property
|
||||
def device(self): return self.optimizers[0].device
|
||||
@property
|
||||
def master_params(self):
|
||||
mp = [mp for o in self.optimizers for mp in (o.master_params or [])]
|
||||
return mp if mp else None
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=1 BS=128 EVAL_BS=128
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=8 BS=1024 EVAL_BS=1024
|
||||
export OPT_BASE_LEARNING_RATE=0.0011 OPT_LAMB_BETA_1=0.60466 OPT_LAMB_BETA_2=0.85437 DECAY=0.1
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=8 BS=1024 EVAL_BS=1024
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e # Exit on any error
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export SUBMISSION_PLATFORM="tinybox_8xMI300X"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=8 BS=1024 EVAL_BS=1024
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e # Exit on any error
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="bert"
|
||||
export SUBMISSION_PLATFORM="tinybox_green"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e # Exit on any error
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export SUBMISSION_PLATFORM="tinybox_red"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="resnet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="resnet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e # Exit on any error
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="resnet"
|
||||
export SUBMISSION_PLATFORM="tinybox_green"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="resnet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="resnet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e # Exit on any error
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="resnet"
|
||||
export SUBMISSION_PLATFORM="tinybox_red"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="retinanet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
export BASEDIR="/raid/datasets/openimages"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="retinanet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
export BASEDIR="/raid/datasets/openimages"
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e # Exit on any error
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="retinanet"
|
||||
export SUBMISSION_PLATFORM="tinybox_green"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="retinanet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
export BASEDIR="/raid/datasets/openimages"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="retinanet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
export BASEDIR="/raid/datasets/openimages"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=1 BS=128 EVAL_BS=128
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=8 BS=1024 EVAL_BS=1024
|
||||
export OPT_BASE_LEARNING_RATE=0.0011 OPT_LAMB_BETA_1=0.60466 OPT_LAMB_BETA_2=0.85437 DECAY=0.1
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=8 BS=1024 EVAL_BS=1024
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
set -e # Exit on any error
|
||||
set -o pipefail # Make pipeline fail if any command fails
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export SUBMISSION_PLATFORM="tinybox_8xMI300X"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=8 BS=1024 EVAL_BS=1024
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=90 EVAL_BS=90
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=90 EVAL_BS=90
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
set -e # Exit on any error
|
||||
set -o pipefail # Make pipeline fail if any command fails
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="bert"
|
||||
export SUBMISSION_PLATFORM="tinybox_green"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=90 EVAL_BS=90
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=90 EVAL_BS=90
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=90 EVAL_BS=90
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
set -e # Exit on any error
|
||||
set -o pipefail # Make pipeline fail if any command fails
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export SUBMISSION_PLATFORM="tinybox_red"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=90 EVAL_BS=90
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="resnet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="resnet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
set -e # Exit on any error
|
||||
set -o pipefail # Make pipeline fail if any command fails
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="resnet"
|
||||
export SUBMISSION_PLATFORM="tinybox_green"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="resnet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="resnet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
set -e # Exit on any error
|
||||
set -o pipefail # Make pipeline fail if any command fails
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="resnet"
|
||||
export SUBMISSION_PLATFORM="tinybox_red"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=1536 EVAL_BS=192
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="retinanet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
export BASEDIR="/raid/datasets/openimages"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="retinanet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
export BASEDIR="/raid/datasets/openimages"
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
set -e # Exit on any error
|
||||
set -o pipefail # Make pipeline fail if any command fails
|
||||
|
||||
export PYTHONPATH="." DEV=NV
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="retinanet"
|
||||
export SUBMISSION_PLATFORM="tinybox_green"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export PYTHONPATH="." DEV=AMD
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="retinanet"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
export BASEDIR="/raid/datasets/openimages"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user