bump tinygrad (#39085)

This commit is contained in:
Daniel Koepping
2026-10-20 03:55:45 +00:00
committed by firestar5683
parent 926ba1b700
commit 68984ff07b
444 changed files with 15637 additions and 12588 deletions
+2
View File
@@ -231,6 +231,8 @@ class ModelState:
self.input_device.copy_from(self.input_host)
self.input_queues['new_img'] = self.run_warp(**self.warp_inputs)
self.run_model(output_buffers=self.outputs, **self.input_queues)
if self.chestnut and (err := Device[self.outputs['outputs'].device].error_state.host.view(fmt='q')[0]):
raise RuntimeError(f"chestnut failed with {err}")
if after_enqueue is not None:
after_enqueue()
model_output = self.outputs['outputs'].numpy()[0]
+12 -8
View File
@@ -63,7 +63,10 @@ runs:
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" == "Linux" ]]; then
if [[ "$RUNNER_OS" != "Windows" ]]; then
if [[ "$RUNNER_OS" == "macOS" ]]; then
sudo install -d -o "$USER" /opt/venv /opt/python
fi
echo "VIRTUAL_ENV=/opt/venv/${{ inputs.python-version }}" >> "$GITHUB_ENV"
echo "UV_PYTHON_INSTALL_DIR=/opt/python" >> "$GITHUB_ENV"
else
@@ -78,7 +81,7 @@ runs:
# **** Caching packages ****
- name: Cache Python packages (PR)
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && env.RUNNER_ENVIRONMENT == 'github-hosted'
id: restore-venv-pr
uses: actions/cache/restore@v5
with:
@@ -95,7 +98,7 @@ runs:
# **** Caching downloads ****
- name: Cache downloads (PR)
if: inputs.key != '' && github.event_name == 'pull_request'
if: inputs.key != '' && github.event_name == 'pull_request' && env.RUNNER_ENVIRONMENT == 'github-hosted'
uses: actions/cache/restore@v5
with:
path: ${{ runner.os == 'Linux' && '~/.cache/tinygrad/downloads/' || '~/Library/Caches/tinygrad/downloads/' }}
@@ -115,7 +118,7 @@ runs:
run: |
uv venv --allow-existing --python ${{ inputs.python-version }} "$VIRTUAL_ENV"
DEPS="${{ inputs.deps }}"
uv pip install --python "$VIRTUAL_ENV" -e ".[${DEPS// /,}]" ${{ inputs.pydeps }} --torch-backend cpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/Triton-Nightly/pypi/simple/
uv pip install --python "$VIRTUAL_ENV" -e ".[${DEPS// /,}]" ${{ inputs.pydeps }} --torch-backend cpu
- name: Install dependencies in venv (without extra)
if: inputs.deps == ''
shell: bash
@@ -211,7 +214,7 @@ runs:
echo "installed=$installed" >> "$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' || inputs.ninja == 'true' || inputs.autogen == 'true') && github.event_name == 'pull_request' && steps.apt-pkgs.outputs.installed == 'false'
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true' || inputs.ninja == 'true' || inputs.autogen == 'true') && github.event_name == 'pull_request' && steps.apt-pkgs.outputs.installed == 'false' && env.RUNNER_ENVIRONMENT == 'github-hosted'
uses: actions/cache/restore@v5
with:
path: /var/cache/apt/archives/
@@ -267,10 +270,11 @@ runs:
if: inputs.cuda == 'true'
shell: bash
run: |
sudo mkdir -p /usr/local/cuda/targets/x86_64-linux
sudo mkdir -p /usr/local/cuda-11.5/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 tar -xJ -C /usr/local/cuda-11.5/targets/x86_64-linux --strip-components=1
sudo ln -sfnT /usr/local/cuda-11.5 /usr/local/cuda
echo /usr/local/cuda-11.5/targets/x86_64-linux/lib | sudo tee /etc/ld.so.conf.d/cuda-11.5.conf
sudo ldconfig
# **** gpuocelot ****
+1 -1
View File
@@ -1,7 +1,7 @@
name: Autogen
env:
# increment this when downloads substantially change to avoid the internet
CACHE_VERSION: '13'
CACHE_VERSION: '14'
CAPTURE_PROCESS_REPLAY: 1
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PYTHONPATH: ${{ github.workspace }}
+25 -40
View File
@@ -95,7 +95,6 @@ jobs:
shell: bash -e -o pipefail {0}
env:
DEV: ${{ matrix.dev }}
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
if: github.repository_owner == 'tinygrad'
steps:
- name: Checkout Code
@@ -107,7 +106,7 @@ jobs:
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
- name: Setup (NV)
if: ${{ matrix.dev == 'NV' }}
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
run: ./extra/hcq/hcq_smi.py nv kill_pids --sudoless
- name: setup staging db
if: github.ref == 'refs/heads/update_benchmark_staging'
run: |
@@ -135,7 +134,6 @@ jobs:
shell: bash -e -o pipefail {0}
env:
DEV: ${{ matrix.dev }}
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
if: github.repository_owner == 'tinygrad'
steps:
- name: Checkout Code
@@ -147,7 +145,7 @@ jobs:
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
- name: Setup (NV)
if: ${{ matrix.dev == 'NV' }}
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
run: ./extra/hcq/hcq_smi.py nv kill_pids --sudoless
- name: setup staging db
if: github.ref == 'refs/heads/update_benchmark_staging'
run: |
@@ -183,7 +181,6 @@ jobs:
shell: bash -e -o pipefail {0}
env:
DEV: ${{ matrix.dev }}
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
if: github.repository_owner == 'tinygrad'
steps:
- name: Checkout Code
@@ -195,7 +192,7 @@ jobs:
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
- name: Setup (NV)
if: ${{ matrix.dev == 'NV' }}
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
run: ./extra/hcq/hcq_smi.py nv kill_pids --sudoless
- name: Symlink models and datasets
run: |
mkdir -p extra/datasets
@@ -225,7 +222,6 @@ jobs:
shell: bash -e -o pipefail {0}
env:
DEV: ${{ matrix.dev }}
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
if: github.repository_owner == 'tinygrad'
steps:
- name: Checkout Code
@@ -237,7 +233,7 @@ jobs:
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
- name: Setup (NV)
if: ${{ matrix.dev == 'NV' }}
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
run: ./extra/hcq/hcq_smi.py nv kill_pids --sudoless
- name: setup staging db
if: github.ref == 'refs/heads/update_benchmark_staging'
run: |
@@ -270,7 +266,6 @@ jobs:
shell: bash -e -o pipefail {0}
env:
DEV: ${{ matrix.dev }}
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
if: github.repository_owner == 'tinygrad'
steps:
- name: Checkout Code
@@ -282,7 +277,7 @@ jobs:
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
- name: Setup (NV)
if: ${{ matrix.dev == 'NV' }}
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
run: ./extra/hcq/hcq_smi.py nv kill_pids --sudoless
- name: Symlink models and datasets
run: |
mkdir -p weights
@@ -334,7 +329,7 @@ jobs:
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
- name: Setup (NV)
if: ${{ matrix.dev == 'NV' }}
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
run: ./extra/hcq/hcq_smi.py nv kill_pids --sudoless
- name: setup staging db
if: github.ref == 'refs/heads/update_benchmark_staging'
run: |
@@ -351,14 +346,14 @@ jobs:
- name: Test tensor cores
run: |
if [[ "${{ matrix.dev }}" == "METAL" ]]; then
python3 test/opt/test_tensor_cores.py
python3 test/runtime/test_tensor_cores.py
DEBUG=2 SHOULD_USE_TC=1 python3 extra/gemm/simple_matmul.py
DEBUG=2 SHOULD_USE_TC=1 HALF=1 python3 extra/gemm/simple_matmul.py
DEBUG=2 SHOULD_USE_TC=1 BFLOAT16=1 python3 extra/gemm/simple_matmul.py
M_START=6 M_STOP=10 M_STEP=1 N_START=6 N_STOP=10 N_STEP=1 K_START=6 K_STOP=24 K_STEP=1 TC_OPT=2 DEBUG=2 python3 ./extra/gemm/fuzz_matmul.py
elif [[ "${{ matrix.dev }}" == "NV" ]]; then
ALLOW_TF32=1 python3 test/opt/test_tensor_cores.py
DEV=NV:PTX ALLOW_TF32=1 python3 test/opt/test_tensor_cores.py
ALLOW_TF32=1 python3 test/runtime/test_tensor_cores.py
DEV=NV:PTX ALLOW_TF32=1 python3 test/runtime/test_tensor_cores.py
DEV=CUDA SHOULD_USE_TC=1 HALF=1 DEBUG=2 python3 extra/gemm/simple_matmul.py
DEV=CUDA SHOULD_USE_TC=1 BFLOAT16=1 DEBUG=2 python3 extra/gemm/simple_matmul.py
DEV=CUDA SHOULD_USE_TC=1 ALLOW_TF32=1 DEBUG=2 ATOL=2e-2 python3 extra/gemm/simple_matmul.py
@@ -369,9 +364,9 @@ jobs:
# M_START=12 M_STOP=20 M_STEP=1 N_START=6 N_STOP=10 N_STEP=1 K_START=28 K_STOP=36 K_STEP=1 HALF=1 TC_OPT=2 python3 ./extra/gemm/fuzz_matmul.py
# DEV=NV:PTX M_START=12 M_STOP=20 M_STEP=1 N_START=6 N_STOP=10 N_STEP=1 K_START=28 K_STOP=36 K_STEP=1 HALF=1 TC_OPT=2 python3 ./extra/gemm/fuzz_matmul.py
else
python3 test/opt/test_tensor_cores.py
python3 test/runtime/test_tensor_cores.py
# TODO: this is flaky
# DEV=AMD:LLVM python3 test/opt/test_tensor_cores.py
# DEV=AMD:LLVM python3 test/runtime/test_tensor_cores.py
SHOULD_USE_TC=1 BFLOAT16=1 DEBUG=2 python3 extra/gemm/simple_matmul.py
SHOULD_USE_TC=1 HALF=1 DEBUG=2 ATOL=2e-2 python3 extra/gemm/simple_matmul.py
# TODO: AMD compiler bug causes this to fail
@@ -548,9 +543,9 @@ jobs:
- name: Test copy speeds
run: SIZE=64000000 GMMU=0 DEV=USB+AMD taskset -c 4-7 python3 test/external/external_test_usb_asm24.py
driverbenchmarks:
name: PCI Driver Benchmark (DEV=${{ matrix.dev }})
runs-on: [self-hosted, Linux, tinyboxrandom]
drivertests:
name: PCI Driver Tests (DEV=${{ matrix.dev }})
runs-on: ${{ matrix.dev == 'AMD' && '9060xt' || '3080' }}
strategy:
fail-fast: false
matrix:
@@ -565,12 +560,6 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v6
- name: Setup
run: |
./extra/hcq/hcq_smi.py ${{ matrix.dev }} rmmod --expect
./extra/hcq/hcq_smi.py ${{ matrix.dev }} kill_pids --sudoless
mkdir -p extra/datasets
ln -s /raid/datasets/imagenet extra/datasets/imagenet
- name: setup staging db
if: github.ref == 'refs/heads/update_benchmark_staging'
run: |
@@ -581,24 +570,26 @@ jobs:
- name: Test driver cold start time
run: time DEBUG=3 AM_RESET=1 python3 test/test_tiny.py TestTiny.test_plus
- name: Test driver warm start time
if: ${{ matrix.dev == 'AMD' }}
if: ${{ matrix.dev != 'NV' }}
run: time DEBUG=3 python3 test/test_tiny.py TestTiny.test_plus
- name: Test GPU crash recovery
if: ${{ matrix.dev == 'AMD' }}
if: ${{ matrix.dev != 'NV' }}
run: python3 -m pytest -rA test/external/external_test_gpu_crash.py
- name: Test tensor cores
run: |
if [[ "${{ matrix.dev }}" == "AMD" ]]; then
if [[ "${{ matrix.dev }}" == "NV" ]]; then
ALLOW_TF32=1 python3 test/runtime/test_tensor_cores.py
else
# Fails on 9070
# python3 test/test_linearizer.py test/opt/test_tensor_cores.py
# DEV=AMD:LLVM python3 test/test_linearizer.py test/opt/test_tensor_cores.py
# python3 test/test_linearizer.py test/runtime/test_tensor_cores.py
# DEV=AMD:LLVM python3 test/test_linearizer.py test/runtime/test_tensor_cores.py
# SHOULD_USE_TC=1 BFLOAT16=1 DEBUG=2 python3 extra/gemm/simple_matmul.py
SHOULD_USE_TC=1 HALF=1 DEBUG=2 ATOL=2e-2 python3 extra/gemm/simple_matmul.py
else
ALLOW_TF32=1 python3 test/opt/test_tensor_cores.py
fi
- name: Test DISK copy time
run: TESTFILE=/raid/downloads/llama3-8b-sfr/model-00001-of-00004.safetensors python3 test/external/external_benchmark_disk_raw.py
run: |
truncate -s 4G disk-benchmark
TESTFILE=disk-benchmark python3 test/external/external_benchmark_disk_raw.py
- name: Test CPU copy time
run: |
GRAPH_ONE_KERNEL=1 NSZ=8192 python3 test/speed/external_test_copy_speed.py TestCopySpeed.testCopyDefaulttoCPUJit
@@ -607,14 +598,8 @@ jobs:
# - name: HEVC Decode Benchmark
# if: ${{ matrix.dev == 'NV' }}
# run: IGNORE_BEAM_CACHE=1 VALIDATE=1 MAX_FRAMES=100 ASSERT_FPS=1400 JITBEAM=1 PYTHONPATH=. python3 extra/hevc/decode.py
- name: Run 10 MLPerf ResNet50 training steps (1 gpu)
if: ${{ matrix.dev == 'NV' }}
run: BENCHMARK_LOG=resnet_10steps MNISTMOCK=1 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=256 GPUS=1 MODEL=resnet python3 examples/mlperf/model_train.py
- name: Run 10 MLPerf Bert training steps (1 gpu)
# TODO: remove BERT_LAYERS once scheduler is fast
run: BENCHMARK_LOG=bert_10steps CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=66 GPUS=1 BERT_LAYERS=2 MODEL=bert python3 examples/mlperf/model_train.py
- name: Remote
if: ${{ matrix.dev == 'AMD' }}
if: ${{ matrix.dev != 'NV' }}
env:
DEV: PCI+AMD
run: |
+17 -20
View File
@@ -1,7 +1,7 @@
name: Platform Tests
env:
# increment this when downloads substantially change to avoid the internet
CACHE_VERSION: '19'
CACHE_VERSION: '20'
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 }}
@@ -23,7 +23,7 @@ jobs:
# ****** OSX Tests ******
unittestmacos:
name: MacOS (unit)
name: MacOS (opt, device)
runs-on: macos-26
timeout-minutes: 20
steps:
@@ -34,18 +34,15 @@ jobs:
with:
key: unittest-macos
deps: testing_unit
- name: Run unit tests
run: DEV=METAL python -m pytest -n=auto test/unit/ --durations=20
- name: Run opt tests
run: DEV=METAL python -m pytest -n=auto test/opt --durations=20
llvm: 'true'
- name: Test tensor core ops (fake)
run: DEV=METAL DEBUG=3 TC=2 python test/backend/test_ops.py TestOps.test_gemm
run: DEV=METAL DEBUG=3 TC=2 python test/runtime/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
run: DEV=METAL DEBUG=3 python test/runtime/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: Run opt and device tests
run: DEV=METAL python3 -m pytest -n=auto test/runtime/test_kernel_opts.py test/runtime/test_tensor_cores.py test/runtime/test_search.py test/device/metal --durations=20
#- 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
@@ -63,6 +60,7 @@ jobs:
with:
key: unittest-macos-mock
deps: testing_unit
llvm: 'true'
amd: 'true'
ocelot: 'true'
- name: Run NULL backend tests
@@ -70,19 +68,17 @@ jobs:
- name: Run pytest (amd)
env:
DEV: MOCKKFD+AMD
HCQ_RUNTIME_DEV: PYTHON
FORWARD_ONLY: 1
run: |
python3 -m pytest -n=auto test/device/test_hcq2.py test/test_tiny.py --durations=20
python3 -m pytest -n=auto test/null/test_hcq2.py test/runtime/test_hcq2.py test/test_tiny.py test/device/amd --durations=20
- name: Run pytest (ptx)
env:
DEV: "MOCK+NV:PTX"
HCQ_RUNTIME_DEV: PYTHON
FORWARD_ONLY: 1
# TODO: failing due to library loading error
CAPTURE_PROCESS_REPLAY: 0
run: |
python3 -m pytest -n=auto test/device/test_hcq2.py test/test_tiny.py \
python3 -m pytest -n=auto test/null/test_hcq2.py test/runtime/test_hcq2.py test/test_tiny.py test/device/nv \
test/testextra/test_hevc.py::TestHevc::test_hevc_decode_compile --durations=20
- name: Run process replay tests
uses: ./.github/actions/process-replay
@@ -105,12 +101,13 @@ jobs:
with:
key: macos-metal
deps: testing_unit
llvm: 'true'
- 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 runtime tests
run: python -m pytest -n=auto test/runtime test/device/metal --durations=20 --splits 2 --group ${{ matrix.group }}
- name: Run process replay tests
uses: ./.github/actions/process-replay
@@ -143,8 +140,8 @@ jobs:
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 test_tiny and device tests
run: python -m pytest -n=auto test/test_tiny.py test/device/$(python -c 'from tinygrad import Device; print(Device.DEFAULT.lower())') --durations=20
- name: Run process replay tests
uses: ./.github/actions/process-replay
@@ -180,6 +177,6 @@ jobs:
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
- name: Run test_tiny and device tests
shell: bash
run: python -m pytest -n=auto test/test_tiny.py --durations=20
run: python -m pytest -n=auto test/test_tiny.py test/device/$(python -c 'from tinygrad import Device; print(Device.DEFAULT.lower())') --durations=20
+97 -78
View File
@@ -1,11 +1,12 @@
name: Unit Tests
env:
# increment this when downloads substantially change to avoid the internet
CACHE_VERSION: '19'
CACHE_VERSION: '20'
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
PYTHONFAULTHANDLER: 1
on:
push:
@@ -21,10 +22,11 @@ concurrency:
jobs:
docs:
name: Docs
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: &linux ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'turin' || 'ubuntu-24.04' }}
timeout-minutes: 10
env:
CHECK_OOB: 0
DEV: CPU
steps:
- name: Checkout Code
uses: actions/checkout@v6
@@ -60,7 +62,7 @@ jobs:
torchbackend:
name: Torch Backend Tests
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 15
steps:
- name: Checkout Code
@@ -74,17 +76,17 @@ jobs:
llvm: 'true'
ninja: 'true'
- name: Test ResNet-18
run: DEBUG=2 python3 extra/torch_backend/example.py
run: DEV=CPU DEBUG=2 python3 extra/torch_backend/example.py
- name: Test one op in torch tests
run: DEBUG=2 python3 extra/torch_backend/torch_tests.py TestTinyBackendPRIVATEUSE1.test_unary_log_tiny_float32
run: DEV=CPU DEBUG=2 python3 extra/torch_backend/torch_tests.py TestTinyBackendPRIVATEUSE1.test_unary_log_tiny_float32
- name: Test Ops with TINY_BACKEND
run: DEV=CPU:LLVM LLVMOPT=0 TINY_BACKEND=1 python3 -m pytest -n auto test/backend/test_ops.py --durations=20
run: DEV=CPU:LLVM LLVMOPT=0 TINY_BACKEND=1 python3 -m pytest -n auto test/runtime/test_ops.py --durations=20
- name: Custom tests
run: DEV=CPU:LLVM GPUS=4 TINY_BACKEND=1 python3 -m pytest -nauto extra/torch_backend/test.py extra/torch_backend/test_inplace.py extra/torch_backend/test_multigpu.py extra/torch_backend/test_kernel_fusion.py --durations=20
torchbackendtrain:
name: Torch Backend Training
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 15
steps:
- name: Checkout Code
@@ -101,7 +103,7 @@ jobs:
bepython:
name: Python Backend
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 15
steps:
- name: Checkout Code
@@ -111,10 +113,10 @@ jobs:
with:
key: be-minimal
deps: testing_unit
- name: Run backend tests
run: SKIP_SLOW_TEST=1 DEV=PYTHON python3 -m pytest -n=auto test/backend/test_dtype.py test/backend/test_dtype_alu.py test/backend/test_ops.py test/backend/test_uops.py test/backend/test_symbolic_ops.py test/backend/test_renderer_failures.py::TestRendererFailures --durations=20
- name: Run runtime tests
run: SKIP_SLOW_TEST=1 DEV=PYTHON python3 -m pytest -n=auto test/runtime/test_dtype.py test/runtime/test_dtype_alu.py test/runtime/test_ops.py test/runtime/test_uops.py test/runtime/test_symbolic_ops.py test/runtime/test_renderer_failures.py::TestRendererFailures test/device/python --durations=20
- name: Test IMAGE support
run: IMAGE=1 DEV=PYTHON python3 test/backend/test_ops.py TestOps.test_gemm TestOps.test_simple_conv2d
run: IMAGE=1 DEV=PYTHON python3 test/runtime/test_ops.py TestOps.test_gemm TestOps.test_simple_conv2d
- name: Test emulated tensor cores
env:
DEBUG: 2
@@ -130,16 +132,17 @@ jobs:
'DEV=PYTHON::sm_75 HALF=1' 'DEV=PYTHON::sm_80 HALF=1' 'DEV=PYTHON::sm_80 ALLOW_TF32=1'
- name: Run additional tensor core tests
run: |
DEV=PYTHON::METAL python3 -m pytest -nauto test/opt/test_tensor_cores.py test/null/test_uops_stats.py::TestUOpsStatsMatmulHalf
DEV=PYTHON::gfx1100 python3 -m pytest -nauto test/opt/test_tensor_cores.py test/null/test_uops_stats.py::TestUOpsStatsMatmulHalf
DEV=PYTHON::gfx950 python3 -m pytest -nauto test/opt/test_tensor_cores.py
DEV=PYTHON::gfx1201 python3 -m pytest -nauto test/opt/test_tensor_cores.py
ALLOW_TF32=1 DEV=PYTHON::sm_89 python3 -m pytest -nauto test/opt/test_tensor_cores.py
DEV=PYTHON::METAL python3 -m pytest -nauto test/runtime/test_tensor_cores.py test/null/test_uops_stats.py::TestUOpsStatsMatmulHalf
DEV=PYTHON::gfx1100 python3 -m pytest -nauto test/runtime/test_tensor_cores.py test/null/test_uops_stats.py::TestUOpsStatsMatmulHalf
DEV=PYTHON::gfx942 python3 -m pytest -nauto test/runtime/test_tensor_cores.py
DEV=PYTHON::gfx950 python3 -m pytest -nauto test/runtime/test_tensor_cores.py
DEV=PYTHON::gfx1201 python3 -m pytest -nauto test/runtime/test_tensor_cores.py
ALLOW_TF32=1 DEV=PYTHON::sm_89 python3 -m pytest -nauto test/runtime/test_tensor_cores.py
DEBUG=2 DEV=PYTHON::sm_80 python3 ./test/null/test_uops_stats.py TestUOpsStatsMatmulHalf
linter:
name: Linters
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 10
steps:
@@ -170,7 +173,7 @@ jobs:
nulltest:
name: Null Tests
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 15
steps:
@@ -188,7 +191,7 @@ jobs:
run: SPEC=2 DEV=NULL python -m pytest -n=auto test/null/ --durations=20
- name: Run targeted tests on NULL backend
run: |
DEV=NULL python3 -m unittest test.backend.test_multitensor.TestMultiTensor.test_data_parallel_resnet_train_step
DEV=NULL python3 -m unittest test.runtime.test_multitensor.TestMultiTensor.test_data_parallel_resnet_train_step
DEV=NULL VIZ=1 python3 -m pytest -n=auto test/null/test_viz.py
DEBUG=7 python -m tinygrad.viz.cli --json | jq empty
# TODO: too slow
@@ -203,8 +206,8 @@ jobs:
# run: DEV=NULL python3 examples/llama.py --gen 1 --size 7B --shard 4 --prompt "Hello." --count 3 --temperature 0 --timing
unittest:
name: Unit Tests
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
name: Backend Tests (CPU)
runs-on: *linux
timeout-minutes: 15
steps:
@@ -222,10 +225,8 @@ jobs:
run: SKIP=ruff,mypy,tests pre-commit run --all-files
- name: Check Device.DEFAULT
run: python -c "from tinygrad import Device; assert Device.DEFAULT == 'CPU', Device.DEFAULT"
- name: Run unit tests
run: |
DEV=CPU python test/null/test_device.py TestRunAsModule.test_module_runs
DEV=CPU python -m pytest -n=auto test/unit/ --durations=20
- name: Run test as module
run: DEV=CPU python test/null/test_device.py TestRunAsModule.test_module_runs
- name: Run GC tests
run: python test/external/external_uop_gc.py
- name: External Benchmark Schedule
@@ -241,7 +242,7 @@ jobs:
matrix:
group: [1, 2]
name: SPEC=2 (${{ matrix.group }})
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 15
steps:
- name: Checkout Code
@@ -253,12 +254,14 @@ jobs:
deps: testing_unit
llvm: 'true'
- name: Test SPEC=2
run: SPEC=2 pytest --maxfail=10 -n auto --durations=30 test/unit test/backend test/opt --ignore test/backend/test_custom_kernel.py --ignore test/unit/test_hashing.py --splits 2 --group ${{ matrix.group }}
run: DEV=CPU SPEC=2 pytest --maxfail=10 -n auto --durations=30 test/runtime test/device/cpu --splits 2 --group ${{ matrix.group }}
fuzzing:
name: Fuzzing
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 10
env:
DEV: CPU
steps:
- name: Checkout Code
uses: actions/checkout@v6
@@ -273,7 +276,7 @@ jobs:
testopenclimage:
name: CL IMAGE Tests
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 15
steps:
- name: Checkout Code
@@ -286,7 +289,7 @@ jobs:
opencl: 'true'
- name: Test CL IMAGE=1 ops
run: |
DEV=CL IMAGE=1 python -m pytest -n=auto test/backend/test_ops.py --durations=20
DEV=CL IMAGE=1 python -m pytest -n=auto test/runtime/test_ops.py --durations=20
DEV=CL IMAGE=1 python test/models/test_end2end.py TestEnd2End.test_linear_mnist
- name: Run process replay tests
uses: ./.github/actions/process-replay
@@ -295,7 +298,7 @@ jobs:
name: openpilot compile tests
env:
LFS_SHA: "51b12d20a25b76fa8a5717c2e5af01f148e469e2"
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 15
steps:
- name: Checkout Code
@@ -330,7 +333,7 @@ jobs:
DEV: CL::IMAGE_PITCH_ALIGNMENT=64
run: |
VIZ=1 IMAGE=1 FLOAT16=1 python examples/openpilot/compile_onnx.py "https://huggingface.co/commaai/openpilot-lfs/resolve/$LFS_SHA/openpilot/selfdrive/modeld/models/driving_supercombo.onnx" /tmp/openpilot_viz.pkl --benchmark-runs 1
ALLOWED_KERNEL_COUNT=162 ALLOWED_READ_IMAGE=1530 ALLOWED_GATED_READ_IMAGE=47 python examples/openpilot/check_image_reads.py
ALLOWED_KERNEL_COUNT=162 ALLOWED_READ_IMAGE=1530 ALLOWED_GATED_READ_IMAGE=44 python examples/openpilot/check_image_reads.py
- name: Run process replay tests
uses: ./.github/actions/process-replay
@@ -338,7 +341,7 @@ jobs:
testonnxcpu:
name: ONNX (CPU) Tests
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 20
steps:
@@ -351,13 +354,13 @@ jobs:
deps: testing
llvm: 'true'
- name: Test ONNX (CPU)
run: DEV=CPU python -m pytest -n=auto test/external/external_test_onnx_backend.py test/external/external_test_onnx_runner.py test/external/external_test_onnx_ops.py test/backend/test_quantize_onnx.py --durations=20
run: DEV=CPU python -m pytest -n=auto test/external/external_test_onnx_backend.py test/external/external_test_onnx_runner.py test/external/external_test_onnx_ops.py test/device/cpu/test_quantize_onnx.py --durations=20
- name: Run process replay tests
uses: ./.github/actions/process-replay
testoptim:
name: Optimization Tests
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 20
steps:
- name: Checkout Code
@@ -389,10 +392,11 @@ jobs:
testllm:
name: Test LLM
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 15
env:
CHECK_OOB: 0
DEV: CPU
steps:
- name: Checkout Code
uses: actions/checkout@v6
@@ -406,17 +410,17 @@ jobs:
run: |
parallel --link --tagstring '[{1}]' '{2}' \
::: llama 'llama q4' qwen3.5 qwen \
::: $'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model llama3.2:1b | tee /dev/stderr | grep -i rooster' \
$'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model llama3.2:1b-q4 | tee /dev/stderr | grep -i rooster' \
$'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model qwen3.5:0.8b | tee /dev/stderr | grep -i rooster' \
$'echo "What\'s a female chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model qwen3:0.6b | tee /dev/stderr | grep -i hen'
::: $'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model llama3.2:1b | tee -a /dev/stderr | grep -i rooster' \
$'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model llama3.2:1b-q4 | tee -a /dev/stderr | grep -i rooster' \
$'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model qwen3.5:0.8b | tee -a /dev/stderr | grep -i rooster' \
$'echo "What\'s a female chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model qwen3:0.6b | tee -a /dev/stderr | grep -i hen'
# NOTE: qwen is dumb and only knows about female chickens
# ****** Models Tests ******
testmodels:
name: Models
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 15
steps:
- name: Checkout Code
@@ -436,7 +440,7 @@ jobs:
testdsp:
name: Linux (DSP)
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 15
steps:
- name: Checkout Code
@@ -449,7 +453,7 @@ jobs:
llvm: "true"
qemu: "true"
- name: Run tests
run: MOCKDSP=1 DEV=DSP python -m pytest -n=auto test/test_tiny.py test/backend/test_transcendental.py::TestTranscendentalVectorized test/backend/test_quantize_onnx.py
run: MOCKDSP=1 DEV=DSP python -m pytest -n=auto test/test_tiny.py test/runtime/test_transcendental.py::TestTranscendentalVectorized test/device/dsp
testlinux:
strategy:
@@ -464,7 +468,7 @@ jobs:
- 'WEBGPU'
name: Linux (DEV=${{ matrix.dev }})
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 20
steps:
- name: Checkout Code
@@ -483,18 +487,17 @@ jobs:
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 backend tests
run: python -m pytest -n=auto test/backend --durations=20
- name: Run runtime tests
run: python -m pytest -n=auto test/runtime test/device/$(python -c 'from tinygrad import Device; print(Device.DEFAULT.lower())') --durations=20
- name: Run process replay tests
uses: ./.github/actions/process-replay
testamdasm:
name: AMD ASM IDE
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 20
env:
DEV: MOCKKFD+AMD
HCQ_RUNTIME_DEV: PYTHON
steps:
- name: Checkout Code
uses: actions/checkout@v6
@@ -533,16 +536,14 @@ jobs:
PYTHONPATH="." DEV=MOCKKFD+AMD N=256 python3 extra/gemm/amd_asm_matmul.py
PYTHONPATH="." DEV=MOCKKFD+AMD N=256 python3 extra/gemm/amd_copy_matmul.py
- name: Run LLVM test
run: DEV=MOCKKFD+AMD:LLVM python test/device/test_amd_llvm.py
run: DEV=MOCKKFD+AMD:LLVM python test/device/amd/test_llvm.py
testmockam:
name: Linux (am)
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 15
env:
DEV: MOCKPCI+AMD
HCQ2: 1
HCQ_RUNTIME_DEV: PYTHON
PYTHONPATH: .
steps:
- name: Checkout Code
@@ -554,13 +555,13 @@ jobs:
deps: testing_unit
amd: 'true'
- name: Run tests on MOCKAM
run: python -m pytest test/test_tiny.py test/unit/test_jit.py
run: python -m pytest test/test_tiny.py test/runtime/test_jit.py
- name: Run test_tiny on MOCKUSB
run: HCQ2=0 GMMU=0 DEV=MOCKUSB+AMD python test/test_tiny.py
run: CHECK_OOB=0 GMMU=0 DEV=MOCKUSB+AMD python -m pytest test/test_tiny.py test/runtime/test_hcq2.py
- name: Run test_hcq2 on MOCKPCI
run: python -m pytest test/device/test_hcq2.py
run: python -m pytest test/null/test_hcq2.py test/runtime/test_hcq2.py
- name: Run disk copy tests on MOCKPCI
run: python -m pytest test/unit/test_disk_tensor.py -k test_copy_from_disk
run: python -m pytest test/runtime/test_disk_tensor.py -k test_copy_from_disk
testamd:
strategy:
@@ -570,13 +571,11 @@ jobs:
arch: [gfx1100, gfx1201, gfx950]
name: Linux (${{ matrix.backend }} ${{ matrix.arch }})
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
timeout-minutes: 15
runs-on: *linux
timeout-minutes: 20
env:
DEV: MOCKKFD+AMD:${{ matrix.backend == 'amdllvm' && 'LLVM' || '' }}:${{ matrix.arch }}
SKIP_SLOW_TEST: 1
HCQ2: 1
HCQ_RUNTIME_DEV: PYTHON
PYTHONPATH: .
steps:
- name: Checkout Code
@@ -592,20 +591,36 @@ jobs:
run: |
python3 -c "from tinygrad import Device; assert Device.DEFAULT in ['AMD'], Device.DEFAULT"
DEBUG=5 FORWARD_ONLY=1 python3 test/test_tiny.py TestTiny.test_plus
- name: Run MXFP4 Llama training on NULL backend
if: ${{ matrix.backend == 'amd' && matrix.arch == 'gfx950' }}
run: PYTHONPATH=. DEV=NULL:HIP:gfx950 MXFP4=1 LLAMA_LAYERS=2 BENCHMARK=3 NULL_ALLOW_COPYOUT=1 NO_HIPCC=1 ROCM_PATH=/opt/rocm JITBEAM=0 examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/profile.sh
- name: Run pytest (amd)
run: python -m pytest -n=auto test/backend/test_ops.py test/backend/test_dtype.py test/backend/test_dtype_alu.py test/backend/test_linearizer.py test/backend/test_randomness.py test/backend/test_jit.py test/backend/test_graph.py test/backend/test_multitensor.py test/device/test_hcq2.py test/external/external_test_am.py test/backend/test_asm_gemm.py::TestAsmGEMM --durations=20
- name: Run opt tests
run: python -m pytest -n=auto test/opt --durations=20
- name: Run disk copy tests
run: python -m pytest test/unit/test_disk_tensor.py -k test_copy_from_disk
- name: Run TRANSCENDENTAL math
run: TRANSCENDENTAL=2 python -m pytest -n=auto test/backend/test_ops.py::TestOps::test_sin test/backend/test_ops.py::TestOps::test_cos test/backend/test_ops.py::TestOps::test_tan test/backend/test_ops.py::TestOps::test_exp test/backend/test_ops.py::TestOps::test_log --durations=20
run: >-
python -m pytest -n=auto test/runtime/test_ops.py test/runtime/test_dtype.py test/runtime/test_dtype_alu.py
test/runtime/test_kernel_opts.py test/runtime/test_linearizer.py test/runtime/test_randomness.py test/runtime/test_jit.py
test/runtime/test_multitensor.py test/runtime/test_search.py test/runtime/test_tensor_cores.py test/null/test_hcq2.py
test/runtime/test_hcq2.py test/external/external_test_am.py test/device/amd
test/runtime/test_disk_tensor.py::TestDiskTensor::test_copy_from_disk
test/runtime/test_disk_tensor.py::TestDiskTensor::test_copy_from_disk_offset
test/runtime/test_disk_tensor.py::TestDiskTensor::test_copy_from_disk_huge --durations=20
- name: Run process replay tests
uses: ./.github/actions/process-replay
testamdmxfp4:
name: AMD MXFP4 Llama
runs-on: *linux
timeout-minutes: 15
env:
DEV: NULL:HIP:gfx950
steps:
- name: Checkout Code
uses: actions/checkout@v6
- name: Setup Environment
uses: ./.github/actions/setup-tinygrad
with:
key: gfx950-mxfp4
deps: testing_unit
amd: 'true'
- name: Run MXFP4 Llama training on NULL backend
run: MXFP4=1 LLAMA_LAYERS=2 BENCHMARK=3 NULL_ALLOW_COPYOUT=1 NO_HIPCC=1 ROCM_PATH=/opt/rocm JITBEAM=0 examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/profile.sh
testnvidia:
strategy:
fail-fast: false
@@ -613,7 +628,7 @@ jobs:
backend: [ptx, nv]
name: Linux (${{ matrix.backend }})
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 20
env:
FORWARD_ONLY: 1
@@ -628,16 +643,20 @@ jobs:
cuda: 'true'
ocelot: 'true'
- name: Set env
run: printf "${{ matrix.backend == 'ptx' && 'DEV=MOCK+CUDA:PTX' || matrix.backend == 'nv' && 'DEV=MOCK+NV\nSKIP_SLOW_TEST=1\nHCQ_RUNTIME_DEV=PYTHON' }}" >> $GITHUB_ENV
run: printf "${{ matrix.backend == 'ptx' && 'DEV=MOCK+CUDA:PTX' || matrix.backend == 'nv' && 'DEV=MOCK+NV\nSKIP_SLOW_TEST=1' }}" >> $GITHUB_ENV
- name: Check Device.DEFAULT and print some source
run: |
python3 -c "from tinygrad import Device; assert Device.DEFAULT in ['CUDA','NV'], Device.DEFAULT"
DEBUG=5 FORWARD_ONLY=1 python3 test/test_tiny.py TestTiny.test_plus
- name: Run pytest (cuda)
# skip multitensor because it's slow
run: python -m pytest -n=auto test/backend --ignore test/backend/test_multitensor.py --durations=20
# NV and CUDA share the NVIDIA device tests.
run: python -m pytest -n=auto test/runtime test/device/nv --ignore test/runtime/test_multitensor.py --durations=20
- name: Run NV PTX device tests
if: matrix.backend == 'nv'
run: DEV=MOCK+NV:PTX python -m pytest -n=auto test/device/nv --durations=20
- name: Run TestOps.test_add with PMA
run: VIZ=-1 PMA=1 PMA_BUFFER_SIZE=8 DEBUG=5 python3 test/backend/test_ops.py TestOps.test_add
run: VIZ=-1 PMA=1 PMA_BUFFER_SIZE=8 DEBUG=5 python3 test/runtime/test_ops.py TestOps.test_add
- name: Run process replay tests
uses: ./.github/actions/process-replay
@@ -652,7 +671,7 @@ jobs:
- 'NULL:QCOMCL:a630'
- 'NULL:NAK:sm_120'
name: Compile-only (DEV=${{ matrix.dev }})
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
runs-on: *linux
timeout-minutes: 15
env:
NULL_ALLOW_COPYOUT: 1
@@ -670,11 +689,11 @@ jobs:
- name: Test IMAGE
shell: bash
if: contains(matrix.dev, 'a630')
run: DEBUG=7 python3 test/backend/test_ops.py TestOps.test_gemm | grep isam
run: DEBUG=7 python3 test/runtime/test_ops.py TestOps.test_gemm | grep isam
- 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
DEBUG=4 python3 test/runtime/test_ops.py TestOps.test_add
# QCOMCL compiles in qemu, too slow for parallel workers
${{ contains(matrix.dev, 'QCOMCL') && 'PARALLEL=0' || '' }} python -m pytest -n=auto test/backend/test_ops.py --durations=20
${{ contains(matrix.dev, 'QCOMCL') && 'PARALLEL=0' || '' }} python -m pytest -n=auto test/runtime/test_ops.py --durations=20
+1 -1
View File
@@ -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/backend/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/runtime/test_ops.py test/runtime/test_schedule.py test/runtime/test_assign.py test/runtime/test_tensor.py test/runtime/test_jit.py test/runtime/test_schedule_cache.py test/null/test_pattern_matcher.py test/null/test_uop_symbolic.py test/runtime/test_helpers.py
language: system
always_run: true
pass_filenames: false
+1 -1
View File
@@ -194,7 +194,7 @@ 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/runtime/test_ops.py # just the ops tests
python3 -m pytest test/ # whole test suite
```
+7 -1
View File
@@ -8,6 +8,12 @@ def pytest_configure(config):
@pytest.hookimpl(wrapper=True)
def pytest_runtest_call(item):
faulthandler.dump_traceback_later(int(os.getenv("TEST_TIMEOUT", 120)), file=item.config.stash[stderr_fd_key], exit=True)
faulthandler.dump_traceback_later(int(os.getenv("TEST_TIMEOUT", 180)), file=item.config.stash[stderr_fd_key], exit=True)
try: yield
finally: faulthandler.cancel_dump_traceback_later()
@pytest.fixture(autouse=True)
def clear_runtime_cache():
yield
from tinygrad.engine.realize import runtime_cache
runtime_cache.clear()
+2 -2
View File
@@ -81,7 +81,7 @@ def example_3_custom_uop(a:Tensor, correct):
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)
reduce_loop = UOp.range(buf.shape[1], 2, AxisType.LOOP)
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))
@@ -91,7 +91,7 @@ def example_3_custom_uop(a:Tensor, correct):
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)
late_reduce_loop = UOp.range(LCLS, 3, AxisType.LOOP)
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]
+2
View File
@@ -16,6 +16,8 @@ Optional requirements:
|----------|------------------|-------------|
| AM_RESET | [1] | Performs a full GPU reset (reloading all firmware and IP blocks) |
| AM_DEBUG | [0-4] | Sets the level of additional debugging information |
| AM_POWER_LIMIT | [watts] | Sets the GPU power limit |
| AM_PERF_LEVEL | [auto, high, low] | Sets the clock policy (default: auto with AM_POWER_LIMIT, high without) |
## AM Driver Details
+1 -1
View File
@@ -33,7 +33,7 @@ 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).
`run_linear` walks the `LINEAR` UOp, dispatching each `CALL` to a runner (kernel, hcq batch, copy, encdec, or validate).
## Runtime
+1 -1
View File
@@ -36,7 +36,7 @@ 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
FLOAT16 | [1] | use float16 for images instead of float32
JIT | [0-2] | 0=disabled, 1=[jit enabled](quickstart.md#jit) (default), 2=jit enabled, but graphs are disabled
JIT | [0-2] | 0=disabled, 1=[jit enabled](quickstart.md#jit) (default), 2=same as 1 (kept for compatibility)
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...)
+1
View File
@@ -52,6 +52,7 @@
::: tinygrad.Tensor.linear
::: tinygrad.Tensor.sequential
::: tinygrad.Tensor.layernorm
::: tinygrad.Tensor.rmsnorm
::: tinygrad.Tensor.batchnorm
::: tinygrad.Tensor.dropout
::: tinygrad.Tensor.one_hot
+1 -1
View File
@@ -99,6 +99,6 @@ if __name__ == "__main__":
#print(srcs[si.ast][1])
main.append("}")
mallocs = [f" {b.dtype.name}* {n} = ({b.dtype.name}*)malloc({b.nbytes});" for n,b in dedup(all_bufs)]
mallocs = [f" {(dt:=Device['CPU'].renderer.render_dtype(b.dtype))}* {n} = ({dt}*)malloc({b.nbytes});" for n,b in dedup(all_bufs)]
with open("out.c", "w") as f: f.write('\n'.join(c_code+premain+mallocs+main))
+5 -32
View File
@@ -1282,7 +1282,7 @@ def train_bert():
previous_step = i
def train_llama3():
from examples.mlperf.models.flat_llama import FlatTransformer, apply_grad, FP8_DTYPE, MXFP8, MXFP4
from examples.mlperf.models.flat_llama import FlatTransformer, apply_grad, MXFP4
from examples.llama3 import MODEL_PARAMS
from examples.mlperf.lr_schedulers import CosineAnnealingLRWithWarmup
from examples.mlperf.optim import GradAccClipAdamW, clip_grads
@@ -1418,8 +1418,7 @@ def train_llama3():
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()
p.grad = p.zeros_like().contiguous()
grads = [p.grad for p in optim.params]
scheduler = CosineAnnealingLRWithWarmup(optim, opt_base_learning_rate, opt_end_learning_rate, opt_learning_rate_warmup_steps, opt_learning_rate_decay_steps)
@@ -1433,39 +1432,15 @@ 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)
Tensor.realize(loss_acc, *optim.params)
mxfp4_weights = model.create_mxfp4_weight_cache() if MXFP4 else None
if mxfp4_weights is not None: Tensor.realize(*[x for layers in mxfp4_weights.values() for outputs in layers for x in outputs])
@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)
@@ -1480,7 +1455,7 @@ def train_llama3():
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)
return loss_acc.realize(*grads)
@TinyJit
def optim_step():
@@ -1489,14 +1464,12 @@ def train_llama3():
scheduler.step()
for g in grads: g.assign(0)
model.update_amax()
new_mxfp4_w = model.update_mxfp4_weight_cache(mxfp4_weights) if mxfp4_weights is not None else []
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,
*new_mxfp4_w)
Tensor.realize(lr_cpu, grad_norm_cpu, loss_cpu, loss_acc.assign(0), *grads, *new_mxfp4_w)
return lr_cpu, grad_norm_cpu, loss_cpu
@@ -16,123 +16,39 @@ from tinygrad.helpers import Timing, colored, GlobalCounters, profile_marker, ro
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, mxfp4_w:tuple[Tensor, Tensor, Tensor, Tensor]|None=None,
def matmul(x:Tensor, w:Tensor, mxfp4:bool=bool(MXFP4), mxfp4_w:tuple[Tensor, Tensor, Tensor, Tensor]|None=None,
x_prequant_mxfp4:tuple[Tensor|None, Tensor|None, Tensor|None, Tensor|None]|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"
if mxfp4 or ASM_GEMM:
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, mxfp4_w=mxfp4_w, mxfp4_x=x_prequant_mxfp4),)
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
if can_use_asm_gemm(x, w.T): return (asm_gemm(x, w.T, mxfp4=mxfp4, mxfp4_w=mxfp4_w, mxfp4_x=x_prequant_mxfp4),)
return (x @ w.T,)
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, mxfp4_w=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
def norm_quantize_matmul(x:Tensor, norm:Tensor, w:Tensor, eps:float, mxfp4_w=None):
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, mxfp4_w=mxfp4_w)
out, *ret = matmul(x_normed * norm, w, mxfp4_w=mxfp4_w)
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, mxfp4_w=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
def add_norm_quantize_matmul(x:Tensor, residual:Tensor, norm:Tensor, w:Tensor, eps:float, mxfp4_w=None):
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, mxfp4_w=mxfp4_w)
out, *ret = matmul(x_normed * norm, w, mxfp4_w=mxfp4_w)
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, mxfp4_w=None):
def silu_w13_quantize_matmul(x_w13:Tensor, w2:Tensor, mxfp4_w=None):
if FUSED_SILU_W13 and MXFP4:
from extra.llama_kernels.swiglu import swiglu_mxfp4
x2, x2_mxfp4 = swiglu_mxfp4(x_w13)
out, *ret = matmul(x2, 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, mxfp4_w=mxfp4_w,
x_prequant_mxfp4=x2_mxfp4)
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)
out, *ret = matmul(x2, w2, mxfp4_w=mxfp4_w, x_prequant_mxfp4=x2_mxfp4)
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, mxfp4_w=mxfp4_w)
out, *ret = matmul(x_w1.silu() * x_w3, w2, mxfp4_w=mxfp4_w)
return out, ret
class FlatTransformer:
@@ -149,16 +65,16 @@ class FlatTransformer:
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)
self.wqkv = self.lin_per_layer(dim, self.n_heads * self.head_dim + self.n_kv_heads * self.head_dim * 2)
self.wo = 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)
self.w1 = self.lin_per_layer(dim, hidden_dim)
self.w3 = 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.w13 = self.lin_per_layer(dim, hidden_dim * 2)
self.w2 = self.lin_per_layer(hidden_dim, dim, std=scaled_std)
self.norm_eps = norm_eps
self.attention_norm = Tensor.ones(n_layers, dim).contiguous()
@@ -171,50 +87,17 @@ class FlatTransformer:
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
return w.cast(dtypes.bfloat16)
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, mxfp4_wqkv=None, mxfp4_wo=None):
def attention(self, x:Tensor, freqs_cis:Tensor, *, attention_norm:Tensor, wqkv:Tensor, wo:Tensor, mxfp4_wqkv=None, mxfp4_wo=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,
mxfp4_w=mxfp4_wqkv)
xqkv, x_normed, rrms, s = norm_quantize_matmul(x, attention_norm, wqkv, self.norm_eps, mxfp4_w=mxfp4_wqkv)
saves.extend([x_normed, rrms, *s, xqkv])
if getenv("HK_FLASH_ATTENTION"):
from extra.thunder.amd.fa import flash_attention, fused_qkv_rope
@@ -232,8 +115,7 @@ class FlatTransformer:
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, mxfp4_w=mxfp4_wo)
out, *s = matmul(attn, wo, mxfp4_w=mxfp4_wo)
saves.extend([*s, out])
return out, saves
@@ -245,41 +127,17 @@ class FlatTransformer:
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"], mxfp4_w=kwargs.get("mxfp4_w1"))
x_w1, *s = matmul(inp, kwargs["w1"], mxfp4_w=kwargs.get("mxfp4_w1"))
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"], mxfp4_w=kwargs.get("mxfp4_w3"))
x_w3, *s = matmul(inp, kwargs["w3"], mxfp4_w=kwargs.get("mxfp4_w3"))
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"], mxfp4_w=kwargs.get("mxfp4_w2"))
out, *s = matmul(x_w1.silu() * x_w3, kwargs["w2"], mxfp4_w=kwargs.get("mxfp4_w2"))
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"],
mxfp4_w=kwargs.get("mxfp4_w13"))
x_w13, h, x_normed, rrms, s = add_norm_quantize_matmul(x, residual, kwargs["ffn_norm"], kwargs["w13"],
self.norm_eps, mxfp4_w=kwargs.get("mxfp4_w13"))
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"],
mxfp4_w=kwargs.get("mxfp4_w2"))
out, s = silu_w13_quantize_matmul(x_w13, kwargs["w2"], mxfp4_w=kwargs.get("mxfp4_w2"))
saves.extend([*s, out])
return out, h, saves
@@ -297,40 +155,20 @@ class FlatTransformer:
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
self.wqkv.shard_(device, axis=1).realize() # (n_layers, out, dim) shard out
self.wo.shard_(device, axis=2).realize() # (n_layers, dim, in) shard in
if SPLIT_W13:
_shard_fp8("w1", 1)
_shard_fp8("w3", 1)
self.w1.shard_(device, axis=1).realize()
self.w3.shard_(device, axis=1).realize()
else:
_shard_fp8("w13", 1) # (n_layers, hidden*2, dim) shard out
_shard_fp8("w2", 2, sstd) # (n_layers, dim, hidden) shard in
self.w13.shard_(device, axis=1).realize() # (n_layers, hidden*2, dim) shard out
self.w2.shard_(device, axis=2).realize() # (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 create_mxfp4_weight_cache(self) -> dict[str, list[tuple[Tensor, Tensor, Tensor, Tensor]]]:
assert MXFP4
@@ -343,42 +181,26 @@ class FlatTransformer:
return [x for name, layers in cache.items() for weight, outputs in zip(getattr(self, name), layers)
for x in quantize_mxfp4(weight, shuffle_row=True, shuffle_col=True, out=outputs)]
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,
mxfp4_weights:dict[str, list[tuple[Tensor, Tensor, Tensor, Tensor]]]|None=None):
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",)))
attn_kwargs = dict(attention_norm=self.attention_norm[i], wqkv=self.wqkv[i], wo=self.wo[i])
ffn_kwargs = dict(ffn_norm=self.ffn_norm[i], w2=self.w2[i])
if mxfp4_weights is not None:
attn_kwargs.update(mxfp4_wqkv=mxfp4_weights["wqkv"][i], mxfp4_wo=mxfp4_weights["wo"][i])
ffn_kwargs.update(mxfp4_w2=mxfp4_weights["w2"][i])
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")))
ffn_kwargs.update(w1=self.w1[i], w3=self.w3[i])
if mxfp4_weights is not None: ffn_kwargs.update(mxfp4_w1=mxfp4_weights["w1"][i], mxfp4_w3=mxfp4_weights["w3"][i])
else:
ffn_kwargs.update(w13=self.w13[i], s_13=s["w13"][i], **amax_kwargs(i, ("x13",), ("xw13",)))
ffn_kwargs.update(w13=self.w13[i])
if mxfp4_weights is not None: ffn_kwargs.update(mxfp4_w13=mxfp4_weights["w13"][i])
h, *_ = self.run_layer(h, freqs_cis, attn_kwargs, ffn_kwargs, save=save)
logits = matmul(self.norm(h), self.output[0], fp8=False)[0]
logits = matmul(self.norm(h), self.output[0], mxfp4=False)[0]
return logits
def _get_pads(uop:UOp) -> list[UOp]:
@@ -437,11 +259,7 @@ if __name__ == "__main__":
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]
grads = {x:x.zeros_like().contiguous() for x in state.values() if x.is_param}
# print model size
sz = 0
@@ -459,13 +277,12 @@ if __name__ == "__main__":
@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)
with Timing("run fwd_bwd: "): loss.realize(*grads.values())
@TinyJit
def optim_step():
+64 -21
View File
@@ -14,7 +14,7 @@ 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, asm_gemm, can_use_asm_gemm, mx_pack
from extra.gemm.moe_gemm import grouped_mx_gemm
from extra.gemm.moe_routing import route, dispatch, combine, router_mfma, Routing, BLOCK_ROW
from extra.gemm.moe_routing import route, dispatch, dispatch_fp8, combine, router_mfma, Routing, BLOCK_ROW
from extra.gptoss_kernels.embedding import GPTOSSEmbedding
FP8_DTYPE = dtypes.fp8e4m3
@@ -61,7 +61,7 @@ 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)
return Tensor(fxn.uop.call_with_output(w_q.uop, w_scale.uop, grad_fxn=_dequant_bwd))
def matmul_mx(x:Tensor|tuple[Tensor, Tensor], w_q:Tensor, w_scale:Tensor) -> Tensor:
def matmul_mx(x:Tensor|tuple[Tensor, Tensor], w_q:Tensor, w_scale:Tensor, *, padded_output:bool=False) -> Tensor:
if isinstance(x, tuple):
assert ASM_GEMM, "pre-quantized MXFP8 input requires ASM_GEMM"
from extra.gemm.cdna_asm_gemm import asm_gemm, can_use_asm_gemm, mx_pack
@@ -79,6 +79,7 @@ def matmul_mx(x:Tensor|tuple[Tensor, Tensor], w_q:Tensor, w_scale:Tensor) -> Ten
ws = ws.pad(((0, npad), (0, 0)), value=127).cast(dtypes.uint8)
assert can_use_asm_gemm(x_q, wq.T)
out = asm_gemm(x_q, wq.T, mx=True, mx_scales=(mx_pack(x_e8), x_e8, mx_pack(ws), ws), mx_w_stored=True)
if padded_output: return out
return (out[:, :N] if npad else out).reshape(*l_shape, N).cast(dtypes.bfloat16)
l_shape = x.shape[:-1]
if ASM_GEMM:
@@ -100,7 +101,9 @@ def matmul_mx(x:Tensor|tuple[Tensor, Tensor], w_q:Tensor, w_scale:Tensor) -> Ten
else: 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)
if padded_output: return out
return (out[:, :N] if npad else out).reshape(*l_shape, N).cast(dtypes.bfloat16)
assert not padded_output, "padded output requires an ASM GEMM"
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)
@@ -223,7 +226,7 @@ class GPTOSS:
x_q, x_e8, rrms = rmsnorm_mul_quantize_mxfp8(x, attention_norm, self.norm_eps)
qkv = matmul_mx((x_q, x_e8), wqkv, wqkv_scale) + wqkv_bias
norm_saves = [x_q, x_e8, rrms]
if getenv("FUSED_RMSNORM_MUL", 0):
elif getenv("FUSED_RMSNORM_MUL", 0):
from extra.gptoss_kernels.rmsnorm import rmsnorm_mul
x_normed, rrms = rmsnorm_mul(x, attention_norm, self.norm_eps)
qkv = matmul_mx(x_normed, wqkv, wqkv_scale) + wqkv_bias
@@ -233,11 +236,15 @@ class GPTOSS:
qkv = matmul_mx(x_normed * attention_norm, wqkv, wqkv_scale) + wqkv_bias
norm_saves = [x_normed, rrms]
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 getenv("FUSED_QKV_ROPE", 0):
from extra.gptoss_kernels.qkv_rope import fused_qkv_rope
xq, xk, xv = fused_qkv_rope(qkv, freqs_cis)
else:
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)
fa_saves = []
if getenv("HK_FLASH_ATTENTION"):
@@ -257,8 +264,13 @@ class GPTOSS:
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, [attn] + norm_saves + fa_saves
proj = matmul_mx(attn, wo, wo_scale, padded_output=bool(getenv("GPTOSS_RESIDUAL_HIP", 0)))
if getenv("GPTOSS_RESIDUAL_HIP", 0):
from extra.gptoss_kernels.residual import wo_bias_add
out = wo_bias_add(proj[:, :self.dim].reshape(x.shape), wo_bias)
else:
out = proj + wo_bias
return out, [attn] + norm_saves + fa_saves, proj
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,
@@ -271,21 +283,47 @@ class GPTOSS:
x_normed, rrms = rmsnorm(x, self.norm_eps)
inp = x_normed * ffn_norm
logits = router_mfma(inp, gate, gate_bias) if getenv("ROUTER_MFMA", 0) else 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)
xg = dispatch(_pad_cols(inp.cast(dtypes.bfloat16)), r)
quantized = None
if getenv("FUSED_ROUTER_DGRAD", 0): assert getenv("FUSED_ROUTER_TOPK", 0) and getenv("FP8_DISPATCH", 0)
if getenv("FUSED_ROUTER_TOPK", 0):
from extra.gptoss_kernels.router_topk import fused_router, fused_router_quantize
from extra.gemm.moe_routing import route_topk
if getenv("FUSED_ROUTER_DGRAD", 0):
weights, topi, q, scales = fused_router_quantize(inp, gate, gate_bias)
quantized = (q, scales)
else:
weights, topi = fused_router(inp, gate, gate_bias)
r = route_topk(weights, topi, self.n_experts)
else:
logits = router_mfma(inp, gate, gate_bias) if getenv("ROUTER_MFMA", 0) else inp.float() @ gate.float().T + gate_bias.float()
r = route(logits.reshape(-1, self.n_experts), self.experts_per_tok, self.n_experts)
inp = inp.reshape(-1, dim)
xg = dispatch_fp8(quantized, r) if quantized is not None else \
(dispatch_fp8 if getenv("FP8_DISPATCH", 0) else dispatch)(_pad_cols(inp.cast(dtypes.bfloat16)), r)
h = grouped_mx_gemm(xg, (w_gate_up, w_gate_up_scale), r.off)[:, :2*inter] + _moe_bias_tile(w_gate_up_bias, r).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] \
+ _moe_bias_tile(w_down_bias, r).cast(dtypes.bfloat16)
out = combine(z, r, inp.shape[0], self.experts_per_tok).reshape(bsz, seqlen, dim)
return out, [x_normed, rrms, xg, h, y, z, r.weights, r.dest_row, r.off]
if getenv("FUSED_SWIGLU", 0):
from extra.gptoss_kernels.swiglu import fused_swiglu_quantize
y = fused_swiglu_quantize(h)
else:
y = swiglu(h, self.swiglu_limit)
if getenv("FUSED_COMBINE", 0):
from extra.gptoss_kernels.combine import down_combine
yq, ye8 = y if isinstance(y, tuple) else quantize_mxfp8(_pad_cols(y.cast(dtypes.bfloat16)))[:2]
out, z = down_combine(yq, ye8, w_down, w_down_scale, r.off, w_down_bias, r.dest_row, r.weights)
out = out.reshape(bsz, seqlen, dim)
else:
z = grouped_mx_gemm(y if isinstance(y, tuple) else _pad_cols(y.cast(dtypes.bfloat16)), (w_down, w_down_scale), r.off)[:, :dim] \
+ _moe_bias_tile(w_down_bias, r).cast(dtypes.bfloat16)
out = combine(z, r, inp.shape[0], self.experts_per_tok).reshape(bsz, seqlen, dim)
return out, [x_normed, rrms, *(xg if isinstance(xg, tuple) else (xg,)), h, *(y if isinstance(y, tuple) else (y,)), z,
r.weights, r.topi, r.dest_row, r.off,
*([quantized[1]] if quantized is not None else [])]
else:
logits = router_mfma(inp, gate, gate_bias) if getenv("ROUTER_MFMA", 0) else inp.float() @ gate.float().T + gate_bias.float()
thresh = logits.topk(self.experts_per_tok)[0][..., -1:]
weights = (logits >= thresh).where(logits, -float("inf")).softmax(-1)
@@ -301,10 +339,15 @@ class GPTOSS:
@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)
attn, attn_saves, proj = 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: ffn_saves.append(h)
if getenv("GPTOSS_RESIDUAL_HIP", 0):
from extra.gptoss_kernels.residual import residual_join
h = residual_join(h, x, proj, attn_kwargs["wo_bias"], ffn)
else:
h = h + ffn
if save: return (h, *attn_saves, *ffn_saves)
return (h,)
@@ -2,7 +2,7 @@ import os
os.environ["WQKV"] = "1"
import unittest
import numpy as np
from tinygrad import Tensor, nn, dtypes
from tinygrad import Tensor, nn
from tinygrad.device import Device
from examples.mlperf.models.llama import Transformer
from examples.mlperf.models.flat_llama import FlatTransformer
@@ -111,27 +111,5 @@ class TestFlatLlama(unittest.TestCase):
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()
+14 -37
View File
@@ -7,8 +7,6 @@ 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)
PRESTORE_WT = getenv("PRESTORE_WT", 0)
@@ -65,8 +63,7 @@ class GradAccClipAdamW(Optimizer):
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
return extra + self.params + self.buffers + (self.master_params or []) + fp8_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))
@@ -102,39 +99,19 @@ class GradAccClipAdamW(Optimizer):
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)
if PRESTORE_WT and hasattr(t, '_wT_q'):
from extra.gemm.cdna_asm_gemm import _mx_block_scale_3d
w_phys = ret.cast(dtypes.bfloat16) * _mx_block_scale_3d(new_e8).cast(dtypes.bfloat16)
wT_q, wT_e8, _ = quantize_mxfp8(w_phys.transpose(1, 2))
t._wT_q.assign(wT_q.shard_like(t._wT_q) if offloaded else wT_q)
t._wT_e8.assign(wT_e8.shard_like(t._wT_e8) if offloaded else wT_e8)
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)
if MXFP8 and t.dtype in dtypes.fp8s:
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)
if PRESTORE_WT and hasattr(t, '_wT_q'):
from extra.gemm.cdna_asm_gemm import _mx_block_scale_3d
w_phys = ret.cast(dtypes.bfloat16) * _mx_block_scale_3d(new_e8).cast(dtypes.bfloat16)
wT_q, wT_e8, _ = quantize_mxfp8(w_phys.transpose(1, 2))
t._wT_q.assign(wT_q.shard_like(t._wT_q) if offloaded else wT_q)
t._wT_e8.assign(wT_e8.shard_like(t._wT_e8) if offloaded else wT_e8)
return ret.shard_like(t) if offloaded else ret
out = new_w.cast(t.dtype)
return out.shard_like(t) if offloaded else out
@@ -16,12 +16,8 @@ export USE_ATOMICS=${USE_ATOMICS:-1}
export ASM_GEMM=${ASM_GEMM:-1}
export WQKV=${WQKV:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export FP8=${FP8:-1}
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
export FAST_CE=${FAST_CE:-0}
export FUSED_INPUT_QUANTIZE=${FUSED_INPUT_QUANTIZE:-0}
export FUSED_GRAD_QUANTIZE=${FUSED_GRAD_QUANTIZE:-0}
export FUSED_ADD_NORM_MUL_QUANTIZE=${FUSED_ADD_NORM_MUL_QUANTIZE:-0}
export FUSED_SILU_W13=${FUSED_SILU_W13:-0}
export SPLIT_W13=${SPLIT_W13:-1}
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-1}
@@ -16,12 +16,8 @@ export USE_ATOMICS=${USE_ATOMICS:-1}
export ASM_GEMM=${ASM_GEMM:-1}
export WQKV=${WQKV:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export FP8=${FP8:-1}
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
export FAST_CE=${FAST_CE:-0}
export FUSED_INPUT_QUANTIZE=${FUSED_INPUT_QUANTIZE:-0}
export FUSED_GRAD_QUANTIZE=${FUSED_GRAD_QUANTIZE:-0}
export FUSED_ADD_NORM_MUL_QUANTIZE=${FUSED_ADD_NORM_MUL_QUANTIZE:-0}
export FUSED_SILU_W13=${FUSED_SILU_W13:-0}
export SPLIT_W13=${SPLIT_W13:-1}
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-1}
@@ -19,9 +19,6 @@ export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export MXFP4=${MXFP4:-1}
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
export FAST_CE=${FAST_CE:-1}
export FUSED_INPUT_QUANTIZE=${FUSED_INPUT_QUANTIZE:-1}
export FUSED_GRAD_QUANTIZE=${FUSED_GRAD_QUANTIZE:-1}
export FUSED_ADD_NORM_MUL_QUANTIZE=${FUSED_ADD_NORM_MUL_QUANTIZE:-1}
export FUSED_SILU_W13=${FUSED_SILU_W13:-1}
export SPLIT_W13=${SPLIT_W13:-0}
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-0}
@@ -16,12 +16,8 @@ export USE_ATOMICS=${USE_ATOMICS:-1}
export ASM_GEMM=${ASM_GEMM:-1}
export WQKV=${WQKV:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export FP8=${FP8:-1}
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
export FAST_CE=${FAST_CE:-0}
export FUSED_INPUT_QUANTIZE=${FUSED_INPUT_QUANTIZE:-0}
export FUSED_GRAD_QUANTIZE=${FUSED_GRAD_QUANTIZE:-0}
export FUSED_ADD_NORM_MUL_QUANTIZE=${FUSED_ADD_NORM_MUL_QUANTIZE:-0}
export FUSED_SILU_W13=${FUSED_SILU_W13:-0}
export SPLIT_W13=${SPLIT_W13:-1}
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-1}
@@ -19,9 +19,6 @@ export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export MXFP4=${MXFP4:-1}
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
export FAST_CE=${FAST_CE:-1}
export FUSED_INPUT_QUANTIZE=${FUSED_INPUT_QUANTIZE:-1}
export FUSED_GRAD_QUANTIZE=${FUSED_GRAD_QUANTIZE:-1}
export FUSED_ADD_NORM_MUL_QUANTIZE=${FUSED_ADD_NORM_MUL_QUANTIZE:-1}
export FUSED_SILU_W13=${FUSED_SILU_W13:-1}
export SPLIT_W13=${SPLIT_W13:-0}
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-0}
@@ -16,12 +16,8 @@ export USE_ATOMICS=${USE_ATOMICS:-1}
export ASM_GEMM=${ASM_GEMM:-1}
export WQKV=${WQKV:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export FP8=${FP8:-1}
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
export FAST_CE=${FAST_CE:-0}
export FUSED_INPUT_QUANTIZE=${FUSED_INPUT_QUANTIZE:-0}
export FUSED_GRAD_QUANTIZE=${FUSED_GRAD_QUANTIZE:-0}
export FUSED_ADD_NORM_MUL_QUANTIZE=${FUSED_ADD_NORM_MUL_QUANTIZE:-0}
export FUSED_SILU_W13=${FUSED_SILU_W13:-0}
export SPLIT_W13=${SPLIT_W13:-1}
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-1}
@@ -20,9 +20,6 @@ export MASTER_WEIGHTS=1
export MXFP4=1
export ALLREDUCE_CAST=1
export FAST_CE=1
export FUSED_INPUT_QUANTIZE=1
export FUSED_GRAD_QUANTIZE=1
export FUSED_ADD_NORM_MUL_QUANTIZE=1
export FUSED_SILU_W13=1
export SPLIT_W13=0
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
export NODES=${NODES:-"tinyamd3 tinyamd4"}
export REMOTE=${REMOTE:-"local,$(echo $NODES | tr ' ' '\n' | grep -vx "$(hostname)" | head -1):6667"}
export DEV=PCI+AMD REMOTE_TIMEOUT=600 ALLREDUCE_NODE_NDEVS=8 DP=16 BS=32 EVAL_BS=16
exec bash "$(dirname "$0")/../tinybox_8xMI350X/dev_beam.sh"
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
export NODES=${NODES:-"tinyamd3 tinyamd4"}
export REMOTE=${REMOTE:-"local,$(echo $NODES | tr ' ' '\n' | grep -vx "$(hostname)" | head -1):6667"}
export DEV=PCI+AMD REMOTE_TIMEOUT=600 ALLREDUCE_NODE_NDEVS=8 DP=16 BS=32 EVAL_BS=16
exec bash "$(dirname "$0")/../tinybox_8xMI350X/dev_run.sh"
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
for p in $(lspci -D -d ::0604 | cut -d" " -f1); do sudo -n setpci -s $p ECAP_ACS+0x6.w=0000:000c 2>/dev/null || true; done
@@ -16,12 +16,8 @@ export USE_ATOMICS=${USE_ATOMICS:-1}
export ASM_GEMM=${ASM_GEMM:-1}
export WQKV=${WQKV:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export FP8=${FP8:-1}
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
export FAST_CE=${FAST_CE:-1}
export FUSED_INPUT_QUANTIZE=${FUSED_INPUT_QUANTIZE:-1}
export FUSED_GRAD_QUANTIZE=${FUSED_GRAD_QUANTIZE:-1}
export FUSED_ADD_NORM_MUL_QUANTIZE=${FUSED_ADD_NORM_MUL_QUANTIZE:-1}
export FUSED_SILU_W13=${FUSED_SILU_W13:-1}
export SPLIT_W13=${SPLIT_W13:-0}
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-0}
@@ -16,12 +16,8 @@ export USE_ATOMICS=${USE_ATOMICS:-1}
export ASM_GEMM=${ASM_GEMM:-1}
export WQKV=${WQKV:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export FP8=${FP8:-1}
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
export FAST_CE=${FAST_CE:-0}
export FUSED_INPUT_QUANTIZE=${FUSED_INPUT_QUANTIZE:-0}
export FUSED_GRAD_QUANTIZE=${FUSED_GRAD_QUANTIZE:-0}
export FUSED_ADD_NORM_MUL_QUANTIZE=${FUSED_ADD_NORM_MUL_QUANTIZE:-0}
export FUSED_SILU_W13=${FUSED_SILU_W13:-0}
export SPLIT_W13=${SPLIT_W13:-1}
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-1}
@@ -16,12 +16,8 @@ export USE_ATOMICS=${USE_ATOMICS:-1}
export ASM_GEMM=${ASM_GEMM:-1}
export WQKV=${WQKV:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export FP8=${FP8:-1}
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
export FAST_CE=${FAST_CE:-1}
export FUSED_INPUT_QUANTIZE=${FUSED_INPUT_QUANTIZE:-1}
export FUSED_GRAD_QUANTIZE=${FUSED_GRAD_QUANTIZE:-1}
export FUSED_ADD_NORM_MUL_QUANTIZE=${FUSED_ADD_NORM_MUL_QUANTIZE:-1}
export FUSED_SILU_W13=${FUSED_SILU_W13:-1}
export SPLIT_W13=${SPLIT_W13:-0}
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-0}
@@ -16,12 +16,8 @@ export USE_ATOMICS=${USE_ATOMICS:-1}
export ASM_GEMM=${ASM_GEMM:-1}
export WQKV=${WQKV:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export FP8=${FP8:-1}
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
export FAST_CE=${FAST_CE:-0}
export FUSED_INPUT_QUANTIZE=${FUSED_INPUT_QUANTIZE:-0}
export FUSED_GRAD_QUANTIZE=${FUSED_GRAD_QUANTIZE:-0}
export FUSED_ADD_NORM_MUL_QUANTIZE=${FUSED_ADD_NORM_MUL_QUANTIZE:-0}
export FUSED_SILU_W13=${FUSED_SILU_W13:-0}
export SPLIT_W13=${SPLIT_W13:-1}
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-1}
@@ -17,12 +17,8 @@ export USE_ATOMICS=1
export ASM_GEMM=1
export WQKV=1
export MASTER_WEIGHTS=1
export FP8=1
export ALLREDUCE_CAST=1
export FAST_CE=1
export FUSED_INPUT_QUANTIZE=1
export FUSED_GRAD_QUANTIZE=1
export FUSED_ADD_NORM_MUL_QUANTIZE=1
export FUSED_SILU_W13=1
export SPLIT_W13=0
@@ -1,16 +1,16 @@
import pickle, sys
from tinygrad.helpers import getenv, is_image_shape, temp
from tinygrad.uop.ops import Ops
from tinygrad.viz.serve import VizData, _reconstruct
from tinygrad.viz.serve import VizData, _reconstruct, canonicalize_ast
with open(sys.argv[1] if len(sys.argv) > 1 else temp("rewrites.pkl", append_user=True), "rb") as f: data = VizData(pickle.load(f))
steps = [s for key, ctx in zip(data.trace.keys, data.trace.rewrites) if key.display_name.startswith("JIT ")
for s in ctx if s.name == "View captured linear"]
assert len(steps) == 1, f"expected one JIT compilation, found {len(steps)}"
asts = [call.without_after.src[0] for call in _reconstruct(data, steps[0].sink).src]
by_ast = {key.keys[1]: _reconstruct(data, s.sink) for key, ctx in zip(data.trace.keys, data.trace.rewrites)
for s in ctx if s.name == "View Program" and key.keys[1] in asts}
programs = [by_ast[ast] if ast.op is Ops.SINK else ast for ast in asts if ast.op in {Ops.SINK, Ops.PROGRAM}]
by_ast = {canonicalize_ast(_reconstruct(data, b.sink)): _reconstruct(data, p.sink) for ctx in data.trace.rewrites
for b in ctx if b.name == "View Base AST" for p in ctx if p.name == "View Program"}
programs = [by_ast[canonicalize_ast(ast)] if ast.op is Ops.SINK else ast for ast in asts if ast.op in {Ops.SINK, Ops.PROGRAM}]
read_image, gated_read_image = 0, 0
for prg in programs:
uops = prg.src[1].src
+12 -22
View File
@@ -2,7 +2,7 @@
import io, pathlib, pickle, shutil, struct, time, tempfile
import numpy as np
from typing import Callable
from tinygrad import Tensor, Device, Context, dtypes
from tinygrad import Tensor, Device, Context
from tinygrad.device import Buffer
from tinygrad.uop.ops import PatternMatcher, UPat, Ops, graph_rewrite
from tinygrad.nn.state import get_parameters
@@ -22,7 +22,7 @@ def dump_pickle(obj, path):
if not isinstance(b, Buffer) or b.device != Device.DEFAULT: return None
offset += buffers.write(bytes((-offset) % 256))
offset = (off:=offset) + buffers.write(b.as_memoryview())
return b.size, b.dtype, off
return b.nbytes, off
p = pickle.Pickler(opcodes)
p.persistent_id = persistent_id
@@ -36,33 +36,23 @@ def dump_pickle(obj, path):
def load_pickle(path, *, out_of_band=False):
with open(path, "rb") as f:
if not out_of_band: return pickle.load(f)
opcodes = f.read(struct.unpack('<q', f.read(8))[0])
if f.tell() == pathlib.Path(path).stat().st_size:
return pickle.loads(opcodes)
buffers = Tensor(pathlib.Path(path))[f.tell():].uop.buffer.ensure_allocated()
opcodes, buffers = f.read(struct.unpack('<q', f.read(8))[0]), Tensor(pathlib.Path(path))[f.tell():].to(Device.DEFAULT).realize().uop.buffer
# FIXME: we load in chunks here because hcq_submit for one large copy is very slow to compile
arena, CHUNK_SIZE = Buffer(Device.DEFAULT, buffers.nbytes, dtypes.uchar, preallocate=True), 32 << 20
for off in range(0, buffers.nbytes, CHUNK_SIZE):
size = min(CHUNK_SIZE, buffers.nbytes-off)
arena.view(size, dtypes.uchar, off).ensure_allocated().copy_from(buffers.view(size, dtypes.uchar, off).ensure_allocated())
def persistent_load(pid): return arena.view(*pid)
def persistent_load(pid): return buffers.view(*pid)
u = pickle.Unpickler(io.BytesIO(opcodes))
u.persistent_load = persistent_load
return u.load()
@Context(OPENPILOT_HACKS=1, **{'AMD': {'TC_OPT': 2, 'TC_MIN_GLOBALS': 32}}.get(Device.DEFAULT, {}))
def benchmark(fxn:Callable, cb=None, **kwargs):
# Select benchmark options when called, without probing devices on import.
with Context(OPENPILOT_HACKS=1, **{'AMD': {'TC_OPT': 2, 'TC_MIN_GLOBALS': 32}}.get(Device.DEFAULT, {})):
Device.default.synchronize()
start = time.perf_counter()
if (output := fxn(**kwargs)) is not None: output.realize()
Device.default.synchronize()
end = time.perf_counter()
if cb: cb(end-start)
return [t.numpy().copy() for t in get_parameters(kwargs.get('output_buffers', output))]
Device.default.synchronize()
start = time.perf_counter()
if (output := fxn(**kwargs)) is not None: output.realize()
Device.default.synchronize()
end = time.perf_counter()
if cb: cb(end-start)
return [t.numpy().copy() for t in get_parameters(kwargs.get('output_buffers', output))]
pm_retargetable = PatternMatcher([
(UPat(Ops.PROGRAM, src=(UPat(), UPat(), UPat(), UPat()), name="p"), lambda p: p.replace(src=p.src[:-1]) if p.arg.target.device == "CPU" else None)
@@ -114,7 +114,7 @@ if __name__ == "__main__":
linear, output_bufs = jit_model(step, *step.input)
functions, statements, bufs, _ = compile_net(linear, output_bufs)
state = get_state_dict(model)
weights = {(id(b), b.offset, b.size, b.dtype): name for name, x in state.items() if (b:=x.uop.base.realized) is not None}
weights = {(id(b), b.offset, b.nbytes, x.dtype): name for name, x in state.items() if (b:=x.uop.base.realized) is not None}
kernel_code = '\n\n'.join([f"const {key} = `{fixup_code(code, key)}`;" for key, code in functions.items()])
kernel_names = ', '.join([name for (name, _, _, _) in statements])
input_names = [f"input{i}" for i in range(len(step.input))]
+5 -2
View File
@@ -4,7 +4,7 @@ import time, mmap, sys, shutil, os, glob, subprocess, argparse, collections
from tinygrad.helpers import DEBUG, NO_COLOR, colored, ansilen
from tinygrad.runtime.autogen import libc
from tinygrad.runtime.autogen.am import am
from tinygrad.runtime.support.hcq import MMIOInterface
from tinygrad.runtime.support.memory import MMIOInterface
from tinygrad.runtime.support.am.amdev import AMDev, AMMemoryManager, AMPageTableEntry
from tinygrad.runtime.support.am.ip import AM_SOC, AM_GMC, AM_IH, AM_PSP, AM_SMU, AM_GFX, AM_SDMA
@@ -118,6 +118,9 @@ class SMICtx:
def _open_am_device(self, pcibus):
if pcibus not in self.opened_pci_resources:
if not os.path.exists(f"/sys/bus/pci/devices/{pcibus}"):
if DEBUG >= 2: print(f"/sys/bus/pci/devices/{pcibus} not found")
return
bar_fds = {bar: os.open(f"/sys/bus/pci/devices/{pcibus}/resource{bar}", os.O_RDWR | os.O_SYNC) for bar in [0, 2, 5]}
bar_size = {0: get_bar0_size(pcibus), 2: os.fstat(bar_fds[2]).st_size, 5: os.fstat(bar_fds[5]).st_size}
@@ -161,7 +164,7 @@ class SMICtx:
case (13,0,6): table_t = dev.smu.smu_mod.MetricsTableV0_t
case (13,0,12): table_t = dev.smu.smu_mod.MetricsTable_t
case _: table_t = dev.smu.smu_mod.SmuMetricsExternal_t
tables[dev] = dev.smu.read_table(table_t, dev.smu.smu_mod.SMU_TABLE_SMU_METRICS) if dev.pci_state == "D0" else None
tables[dev] = dev.smu.read_table(table_t, getattr(dev.smu.smu_mod, "TABLE_SMU_METRICS", dev.smu.smu_mod.SMU_TABLE_SMU_METRICS)) if dev.pci_state == "D0" else None
return tables
def _pick_nonzero_avg(self, vals) -> int:
+1 -1
View File
@@ -3,7 +3,7 @@
import os
from tinygrad.helpers import Context
from tinygrad.runtime.support.system import System, PCIDevice
from tinygrad.runtime.support.hcq import FileIOInterface
from tinygrad.runtime.support.system import FileIOInterface
from tinygrad.runtime.support.am.amdev import AMDev
if __name__ == "__main__":
+2 -1
View File
@@ -8,10 +8,11 @@ if __name__ == "__main__":
parser.add_argument("--prompt-tokens", type=int, default=1024, help="number of prompt tokens (default: %(default)s)")
parser.add_argument("--decode-tokens", type=int, default=16, help="number of tokens to decode (default: %(default)s)")
parser.add_argument("--chunk-size", type=int, default=32, help="chunk size for prefill (default: %(default)s)")
parser.add_argument("--shard", type=int, default=1, help="tensor parallel device count (default: %(default)s)")
args = parser.parse_args()
st = time.perf_counter()
model, _ = Transformer.from_gguf(args.model, args.max_context)
model, _ = Transformer.from_gguf(args.model, args.max_context, shard=args.shard)
print(f"load {time.perf_counter()-st:.3f}s", flush=True)
st = time.perf_counter()
+2 -2
View File
@@ -26,7 +26,7 @@ def compile_net(linear:UOp, output_bufs:List[Buffer]) -> Tuple[Dict[str,str], Li
if bu.op is Ops.PARAM: key, name, size = ("in", bu.arg.slot), f"input{bu.arg.slot}", prod(bu.shape)*bu.dtype.itemsize
else:
b = bu.buffer
key, size = (id(b.base), b.offset, b.size, b.dtype), b.size*b.dtype.itemsize
key, size = (id(b.base), b.offset, b.nbytes, bu.dtype), b.nbytes
if key in bufs: return bufs[key][0]
if (name:=output_name.get(id(b))) is None:
name, n = f"buf_{n}", n+1
@@ -244,7 +244,7 @@ def export_model(model, target:str, *inputs, model_name: Optional[str] = "model"
with Context(JIT=2): linear, output_bufs = jit_model(model, *inputs)
functions, statements, bufs, bufs_to_save = compile_net(linear, output_bufs)
state = get_state_dict(model)
weight_names = {(id(b), b.offset, b.size, b.dtype): name for name, x in state.items() if (b:=x.uop.base.realized) is not None}
weight_names = {(id(b), b.offset, b.nbytes, x.dtype): name for name, x in state.items() if (b:=x.uop.base.realized) is not None}
input_names = [f"input{i}" for i in range(len(inputs))]
output_names = [f"output{i}" for i in range(len(output_bufs))]
+2 -2
View File
@@ -1,6 +1,6 @@
from typing import Callable, Any
from tinygrad import Tensor, dtypes, nn, UOp
from tinygrad.uop.ops import KernelInfo, AxisType, Ops
from tinygrad.uop.ops import KernelInfo, Ops
def quantize_to_fp8(x: Tensor, dtype=dtypes.fp8e4m3):
fp8_min = -448.0 if dtype == dtypes.fp8e4m3 else -57344.0
@@ -21,7 +21,7 @@ def custom_matmul(output: UOp, inp: UOp, weight: UOp) -> UOp:
seq_idx = UOp.range(SEQ, 2)
out_idx = UOp.range(OUT, 3)
batch_idx = UOp.range(output.size//SEQ//OUT, 1)
reduce_idx = UOp.range(IN, 0, AxisType.REDUCE)
reduce_idx = UOp.range(IN, 0)
product = (inp.index((seq_idx*IN+reduce_idx+batch_idx*IN*SEQ)) * weight.index((out_idx*IN+reduce_idx))).cast(dtypes.float)
reduced = product.reduce(reduce_idx, arg=Ops.ADD)
store_op = output.index((seq_idx*OUT+out_idx+batch_idx*OUT*SEQ)).store(reduced).end(batch_idx, seq_idx, out_idx)
+3 -3
View File
@@ -9,7 +9,7 @@ def call(*ranges):
def wrapper(*args):
ret = fxn(*ranges, *args).end(*ranges)
# closing the GLOBAL ranges finishes the kernel
if any(r.arg[-1] is AxisType.GLOBAL for r in ranges): ret = ret.sink(arg=KernelInfo(opts_to_apply=()))
if any(r.axis_type is AxisType.GLOBAL for r in ranges): ret = ret.sink(arg=KernelInfo(opts_to_apply=()))
return ret
return wrapper
return decorator
@@ -48,7 +48,7 @@ def block_128x128_gemm(lane:UOp, wave_m:UOp, wave_n:UOp, c:UOp, a:UOp, b:UOp) ->
acc = UOp.placeholder((TM, TN), dtypes.float, slot=2, addrspace=AddrSpace.REG)
acc = acc.after(acc.store(acc.zeros_like(buffer=False)))
@call(UOp.range(K // BLOCK_K, 100, AxisType.REDUCE))
@call(UOp.range(K // BLOCK_K, 100, AxisType.LOOP))
def tile_reduce(k_tile:UOp):
A_local = UOp.placeholder((BLOCK_K, BLOCK_M), a.dtype, slot=0, addrspace=AddrSpace.LOCAL)
B_local = UOp.placeholder((BLOCK_K, BLOCK_N), b.dtype, slot=1, addrspace=AddrSpace.LOCAL)
@@ -60,7 +60,7 @@ def block_128x128_gemm(lane:UOp, wave_m:UOp, wave_n:UOp, c:UOp, a:UOp, b:UOp) ->
# NOTE: no explicit barrier needed, the AFTER on the LOCAL buffers implies it in late codegen
A_local, B_local = A_local.after(A_store, B_store), B_local.after(A_store, B_store)
@call(UOp.range(BLOCK_K, 101, AxisType.REDUCE))
@call(UOp.range(BLOCK_K, 101, AxisType.LOOP))
def inner_reduce(k:UOp):
# registers for LOCAL -> REG
a_frag = UOp.placeholder((TM//UNROLL_M, UNROLL_M), dtypes.float, slot=0, addrspace=AddrSpace.REG)
+3 -3
View File
@@ -51,7 +51,7 @@ def block_128x128_gemm(c:UOp, a:UOp, b:UOp) -> UOp:
a = a.reshape(K // BLOCK_K, BLOCK_K, BLOCK_M)
b = b.reshape(K // BLOCK_K, BLOCK_K, BLOCK_N)
k_tile = UOp.range(K // BLOCK_K, 100, AxisType.REDUCE)
k_tile = UOp.range(K // BLOCK_K, 100, AxisType.LOOP)
# copy with transpose for wmma (input is k×spatial, LDS is spatial×k)
A_copy = A_local.permute((1,0)) if use_wmma else A_local
@@ -69,7 +69,7 @@ def block_128x128_gemm(c:UOp, a:UOp, b:UOp) -> UOp:
acc = acc.after(acc.store(acc.zeros_like(buffer=False)))
if use_wmma:
k = UOp.range(BLOCK_K // WMMA_K, 101, AxisType.REDUCE)
k = UOp.range(BLOCK_K // WMMA_K, 101, AxisType.LOOP)
tile_m = UOp.range(TM // WMMA_ACC, 200)
tile_n = UOp.range(TN, 201)
@@ -87,7 +87,7 @@ def block_128x128_gemm(c:UOp, a:UOp, b:UOp) -> UOp:
a_frag = UOp.placeholder((TM//UNROLL_M, UNROLL_M), dtypes.float, slot=0, addrspace=AddrSpace.REG)
b_frag = UOp.placeholder((TN//UNROLL_N, UNROLL_N), dtypes.float, slot=1, addrspace=AddrSpace.REG)
k = UOp.range(BLOCK_K, 101, AxisType.REDUCE)
k = UOp.range(BLOCK_K, 101, AxisType.LOOP)
a_frag = a_frag.after(a_frag.store(A_local[k].reshape(WAVES_M, TM//UNROLL_M, LANES_PER_WAVE_M, UNROLL_M)[wave_m, :, lane_m, :]))
b_frag = b_frag.after(b_frag.store(B_local[k].reshape(WAVES_N, TN//UNROLL_N, LANES_PER_WAVE_N, UNROLL_N)[wave_n, :, lane_n, :]))
+2 -2
View File
@@ -45,7 +45,7 @@ def hand_spec_kernel3(c:UOp, a:UOp, b:UOp) -> UOp:
c = c.reshape(M // BLOCK_M, BLOCK_M, N // BLOCK_N, BLOCK_N)[block_id_m, :, block_id_n, :]
# open the main reduction range
k_tile_range = UOp.range(K // BLOCK_K, 0, AxisType.REDUCE)
k_tile_range = UOp.range(K // BLOCK_K, 0, AxisType.LOOP)
a = a.reshape(M // BLOCK_M, BLOCK_M, K // BLOCK_K, BLOCK_K)[block_id_m, :, k_tile_range, :]
b = b.reshape(K // BLOCK_K, BLOCK_K, N // BLOCK_N, BLOCK_N)[k_tile_range, :, block_id_n, :]
@@ -70,7 +70,7 @@ def hand_spec_kernel3(c:UOp, a:UOp, b:UOp) -> UOp:
A_local, B_local = A_local.after(A_local_store, B_local_store), B_local.after(A_local_store, B_local_store)
# open inner k range
k = UOp.range(BLOCK_K, 3, AxisType.REDUCE)
k = UOp.range(BLOCK_K, 3, AxisType.LOOP)
# ---------------------------
# LOCAL -> REG (per-wave tiles)
+26 -161
View File
@@ -5,87 +5,12 @@ from tinygrad.uop.ops import UOp, Ops, KernelInfo, AxisType
from tinygrad.renderer import Estimates
from tinygrad.helpers import getenv, all_same, DEBUG, ceildiv
from tinygrad.runtime.support.compiler_amd import HIPCCCompiler
from examples.mlperf.models.flat_llama import FP8_DTYPE, quantize_fp8
from extra.llama_kernels.quantize_mxfp4 import quantize_mxfp4
FP8_DTYPE = dtypes.fp8e4m3
TILE_M, TILE_N, TILE_K = 256, 256, 64
# ** FP8 GEMM custom kernel
@functools.cache
def custom_hk_fp8_gemm(C:UOp, A:UOp, B:UOp, *args:UOp, dname:str, scale_mode:int=3) -> UOp:
# scale_mode: 0=no scale, 1=x only, 2=w only, 3=both
n_scales = (1 if scale_mode & 1 else 0) + (1 if scale_mode & 2 else 0) + (1 if scale_mode & 4 else 0)
scales, extra = args[:n_scales], args[n_scales:]
M, K = A.shape[0]*A.shape[1], A.shape[2]
N, K2 = B.shape[(1 if B.ndim == 3 else 0):]
assert K == K2, f"{A.shape} {B.shape}"
block_size = 256
threads = UOp.special(64 * 8, "lidx0")
workgroups = UOp.special((M // block_size) * (N // block_size), "gidx0")
sink_inputs = (C.base, A.base, B.base) + tuple(s.base for s in scales) + (threads, workgroups)
sink = UOp.sink(*sink_inputs,
arg=KernelInfo(f"hk_fp8_gemm_{M}_{N}_{K}", estimates=Estimates(ops=2*M*N*K, mem=(M*K+N*K)*A.dtype.itemsize+M*N*C.dtype.itemsize)))
kittens_path = pathlib.Path(__file__).parent.parent/"thunder"/"amd"
src = (kittens_path/"gemm_fp8.cpp").read_text()
lib = HIPCCCompiler("gfx950", [f"-I{(kittens_path/'include').as_posix()}", "-std=c++20", "-DKITTENS_CDNA4", "-ffast-math",
"-DHIP_ENABLE_WARP_SYNC_BUILTINS", f"-DGEMM_M={M}", f"-DGEMM_N={N}", f"-DGEMM_K={K}",
f"-DSCALE_MODE={scale_mode}"]).compile_cached(src)
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src),
UOp(Ops.BINARY, arg=lib)))
# ** FP8 AtB GEMM custom kernel
@functools.cache
def custom_hk_fp8_atb_gemm(C:UOp, A:UOp, B:UOp, *args:UOp, dname:str, scale_mode:int=5) -> UOp:
# C = A.T @ B, A and B are physically [K, M] and [K, N].
n_scales = (1 if scale_mode & 1 else 0) + (1 if scale_mode & 2 else 0) + (1 if scale_mode & 4 else 0)
scales = args[:n_scales]
K, M = A.shape[0]*A.shape[1], A.shape[2]
K2, N = B.shape[0]*B.shape[1], B.shape[2]
assert K == K2, f"{A.shape} {B.shape}"
block_m, block_n, block_k, num_warps = 256, 256, 128, 8
assert M % block_m == 0 and N % block_n == 0 and K % block_k == 0, f"invalid fp8 atb tile {(block_m, block_n, block_k)} for {(M, N, K)}"
threads = UOp.special(64 * num_warps, "lidx0")
workgroups = UOp.special((M // block_m) * (N // block_n), "gidx0")
sink_inputs = (C.base, A.base, B.base) + tuple(s.base for s in scales) + (threads, workgroups)
sink = UOp.sink(*sink_inputs,
arg=KernelInfo(f"hk_fp8_atb_gemm_{M}_{N}_{K}", estimates=Estimates(ops=2*M*N*K, mem=(M*K+N*K)*A.dtype.itemsize+M*N*C.dtype.itemsize)))
kittens_path = pathlib.Path(__file__).parent.parent/"thunder"/"amd"
src = (kittens_path/"gemm_fp8_atb.cpp").read_text()
lib = HIPCCCompiler("gfx950", [f"-I{(kittens_path/'include').as_posix()}", "-std=c++20", "-DKITTENS_CDNA4", "-ffast-math",
"-DHIP_ENABLE_WARP_SYNC_BUILTINS", f"-DGEMM_M={M}", f"-DGEMM_N={N}", f"-DGEMM_K={K}",
f"-DSCALE_MODE={scale_mode}"]).compile_cached(src)
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src),
UOp(Ops.BINARY, arg=lib)))
def hk_fp8_atb_gemm(a:Tensor, b:Tensor, x_scale:Tensor|None=None, g_amax:Tensor|None=None) -> Tensor:
assert a.dtype == b.dtype == FP8_DTYPE, f"expected fp8, got {a.dtype} {b.dtype}"
assert a.ndim == b.ndim == 3 and a.shape[:2] == b.shape[:2], f"{a.shape} {b.shape}"
batch, rows, M = a.shape
N = b.shape[2]
assert M % TILE_M == 0 and N % TILE_N == 0 and (batch * rows) % 128 == 0, \
f"fp8 atb shape {a.shape} {b.shape} must produce (M,N,K) multiples of ({TILE_M},{TILE_N},128)"
is_multi = isinstance(a.device, tuple)
reduce_out = False
if is_multi:
ndev = len(a.device)
if a.uop.axis in (0, 1) or b.uop.axis in (0, 1): inv, out_axis, reduce_out = Tensor.invalids(1, M, N, dtype=dtypes.bfloat16, device=a.device), 0, True
elif b.uop.axis == 2: inv, out_axis = Tensor.invalids(1, M, N // ndev, dtype=dtypes.bfloat16, device=a.device), 2
elif a.uop.axis == 2: inv, out_axis = Tensor.invalids(1, M // ndev, N, dtype=dtypes.bfloat16, device=a.device), 1
else: inv, out_axis, reduce_out = Tensor.invalids(1, M, N, dtype=dtypes.bfloat16, device=a.device), 0, True
out = Tensor(inv.uop.unshard(out_axis), device=a.device)
dname = a.device[0]
else:
out = Tensor.invalids(1, M, N, dtype=dtypes.bfloat16, device=a.device)
dname = a.device
dname = dname.split(":")[0]
scales = tuple(s for s in (x_scale, g_amax) if s is not None)
scale_mode = (1 if x_scale is not None else 0) | (4 if g_amax is not None else 0)
out = Tensor.custom_kernel(out, a, b, *scales, fxn=functools.partial(custom_hk_fp8_atb_gemm, dname=dname, scale_mode=scale_mode))[0]
if reduce_out: out = out.sum(0)
return out.squeeze(0) if out.ndim == 3 else out
# ** MXFP8 GEMM custom kernel
@functools.cache
@@ -221,7 +146,7 @@ def custom_uop_gemm(C:UOp, A:UOp, B:UOp) -> UOp:
assert K == K2
m = UOp.range(M, 1)
n = UOp.range(N, 2)
k = UOp.range(K, 0, AxisType.REDUCE)
k = UOp.range(K, 0, AxisType.LOOP)
mul = (A.flatten().index((m*UOp.const(K)+k))*
B.flatten().index((k*UOp.const(N)+n))).cast(dtypes.float32)
red = mul.reduce(k, arg=Ops.ADD).cast(C.dtype)
@@ -294,79 +219,29 @@ def hk_bf16_atb_gemm(a:Tensor, b:Tensor) -> Tensor:
# ** backward gemm, might use the asm gemm
def custom_gemm_bw(gradient:UOp, kernel:UOp, n_scales:int=2, has_grad_amax:bool=False, has_w_post:bool=False):
def custom_gemm_bw(gradient:UOp, kernel:UOp):
inputs = kernel.src[1:]
if inputs[1].dtype == FP8_DTYPE:
out, a, b = inputs[:3]
i = 3
s_x = inputs[i]; i += 1
has_w = n_scales >= 2
s_w = inputs[i] if has_w else None; i += has_w
s_g_amax = inputs[i] if n_scales == 3 else None; i += (n_scales == 3)
grad_amax_state = inputs[i] if has_grad_amax else None; i += has_grad_amax
next_grad_amax_state = inputs[i] if has_grad_amax else None; i += has_grad_amax
w_post = inputs[i] if has_w_post else None
a_t, b_t, g_t = Tensor(a, device=a.device), Tensor(b, device=a.device), Tensor(gradient, device=a.device)
s_x_t = Tensor(s_x, device=a.device)
s_w_t = Tensor(s_w, device=a.device) if has_w else None
s_g_amax_t = Tensor(s_g_amax, device=a.device) if s_g_amax is not None else None
w_post_t = Tensor(w_post, device=a.device) if has_w_post else None
g_t = g_t[:a.shape[0]]
from extra.llama_kernels.cast_amax import _grad_fp8_mailbox
from extra.llama_kernels.quantize_fp8_delayed import quantize_fp8_delayed
gbase = gradient.base if hasattr(gradient, "base") else gradient
mailbox_entry = _grad_fp8_mailbox.pop(gbase, None) or _grad_fp8_mailbox.pop(gradient, None)
if mailbox_entry is not None:
g_fp8_u, grad_amax_u = mailbox_entry
g_fp8 = Tensor(g_fp8_u, device=a.device)[:a.shape[0]]
g_amax = Tensor(grad_amax_u, device=a.device)
else:
assert grad_amax_state is not None, "fp8 matmul bwd needs either a mailbox entry or a grad_amax_state"
if getenv("CURRENT_GRAD_SCALE", 0):
g_fp8, _, g_amax = quantize_fp8(g_t, amax_state=None)
elif getenv("FUSED_GRAD_QUANTIZE", 0):
grad_amax_t = Tensor(grad_amax_state, device=a.device)
g_amax = grad_amax_t
g_fp8, _ = quantize_fp8_delayed(g_t, g_amax, Tensor(next_grad_amax_state, device=a.device))
else:
grad_amax_t = Tensor(grad_amax_state, device=a.device)
g_amax = grad_amax_t
g_fp8, _, new_grad_amax = quantize_fp8(g_t, amax_state=g_amax)
store_effect = next_grad_amax_state.store(new_grad_amax.uop)
g_fp8 = Tensor(g_fp8.contiguous().uop.after(store_effect), device=a.device)
# dgrad: applies grad/activation amax scales in the GEMM epilogue; w_scale is already inverse.
assert s_g_amax_t is None, "fp8 GEMM bwd through g_amax scaling is unsupported"
grad_a = asm_gemm(g_fp8, b_t, x_scale=s_x_t, w_scale=s_w_t, g_amax=g_amax) if has_w else asm_gemm(g_fp8, b_t, x_scale=s_x_t, g_amax=g_amax)
# wgrad: no w_scale
grad_b = hk_fp8_atb_gemm(g_fp8, a_t, x_scale=s_x_t, g_amax=g_amax)
# wgrad: rescale if not scalar
if w_post_t is not None:
grad_b = grad_b / w_post_t.reshape(*w_post_t.shape, *([1]*(grad_b.ndim - w_post_t.ndim)))
# one None per input: (out, a, b, x_scale[, w_scale][, grad_amax][, w_post_scale])
ret = (None, grad_a.uop, grad_b.uop) + tuple(None for _ in inputs[3:])
return ret
hk_bf16 = len(inputs) == 4 and inputs[1].dtype == dtypes.bfloat16
if hk_bf16:
out, a, b_t, b = inputs
assert all_same([gradient.device, a.device, b_t.device, b.device, out.device])
else:
hk_bf16 = len(inputs) == 4 and inputs[1].dtype == dtypes.bfloat16
if hk_bf16:
out, a, b_t, b = inputs
assert all_same([gradient.device, a.device, b_t.device, b.device, out.device])
else:
assert len(inputs) == 3, f"regular gemm must have exactly 3 sources, got: {len(inputs)}"
out, a, b = inputs
assert all_same([gradient.device, a.device, b.device, out.device])
a_t, b_t, g_t = Tensor(a, device=a.device), Tensor(b, device=a.device), Tensor(gradient, device=a.device)
g_t = g_t[:a.shape[0]]
if hk_bf16 and g_t.dtype != b_t.dtype: g_t = g_t.cast(b_t.dtype)
if can_use_asm_gemm(g_t, b_t.T): grad_a = asm_gemm(g_t, b_t.T).uop
else: grad_a = (g_t @ b_t.T).uop
if hk_bf16:
grad_b = hk_bf16_atb_gemm(a_t, g_t).uop
else:
a_t_flat, g_t_flat = a_t.permute(2, 0, 1).reshape(a_t.shape[2], -1), g_t.reshape(-1, g_t.shape[-1])
if can_use_asm_gemm(a_t_flat, g_t_flat): grad_b = asm_gemm(a_t_flat, g_t_flat).uop
else: grad_b = (a_t_flat @ g_t_flat).uop
# hk_bf16 uses b.T, writes gradients only for a and b
return (None, grad_a, None, grad_b) if hk_bf16 else (None, grad_a, grad_b)
assert len(inputs) == 3, f"regular gemm must have exactly 3 sources, got: {len(inputs)}"
out, a, b = inputs
assert all_same([gradient.device, a.device, b.device, out.device])
a_t, b_t, g_t = Tensor(a, device=a.device), Tensor(b, device=a.device), Tensor(gradient, device=a.device)
g_t = g_t[:a.shape[0]]
if hk_bf16 and g_t.dtype != b_t.dtype: g_t = g_t.cast(b_t.dtype)
if can_use_asm_gemm(g_t, b_t.T): grad_a = asm_gemm(g_t, b_t.T).uop
else: grad_a = (g_t @ b_t.T).uop
if hk_bf16:
grad_b = hk_bf16_atb_gemm(a_t, g_t).uop
else:
a_t_flat, g_t_flat = a_t.permute(2, 0, 1).reshape(a_t.shape[2], -1), g_t.reshape(-1, g_t.shape[-1])
if can_use_asm_gemm(a_t_flat, g_t_flat): grad_b = asm_gemm(a_t_flat, g_t_flat).uop
else: grad_b = (a_t_flat @ g_t_flat).uop
# hk_bf16 uses b.T, writes gradients only for a and b
return (None, grad_a, None, grad_b) if hk_bf16 else (None, grad_a, grad_b)
# ** mxfp8 gemm backward
@@ -418,12 +293,11 @@ def custom_mxfp4_gemm_bw(gradient:UOp, kernel:UOp):
# ** main gemm function
def asm_gemm(a:Tensor, b:Tensor, x_scale:Tensor|None=None, w_scale:Tensor|None=None, grad_amax_state:Tensor|None=None,
next_grad_amax_state:Tensor|None=None,
w_post_scale:Tensor|None=None, mx:bool=False, mx_scales:tuple|None=None, mx_w_stored:bool=False, g_amax:Tensor|None=None,
def asm_gemm(a:Tensor, b:Tensor, w_post_scale:Tensor|None=None, mx:bool=False, mx_scales:tuple|None=None, mx_w_stored:bool=False,
a_pretranspose:Tensor|None=None, mxfp4:bool=False, mxfp4_w:tuple[Tensor, Tensor, Tensor, Tensor]|None=None,
mxfp4_x:tuple[Tensor|None, Tensor|None, Tensor|None, Tensor|None]|None=None) -> Tensor:
assert can_use_asm_gemm(a, b), f"{counters['todos'][-1]}"
assert mx or a.dtype != FP8_DTYPE, "FP8 GEMM requires MXFP8 block scaling"
if mxfp4:
assert not mx and mx_scales is None, "mxfp4 owns quantization; mx/mx_scales are for mxfp8"
assert a.dtype == dtypes.bfloat16, f"cannot quantize {a.dtype} to mxfp4"
@@ -490,15 +364,6 @@ def asm_gemm(a:Tensor, b:Tensor, x_scale:Tensor|None=None, w_scale:Tensor|None=N
grad_fxn = functools.partial(custom_mx_gemm_bw, has_w_post=has_w_post, w_stored=mx_w_stored)
extra = [w_post_scale] if w_post_scale is not None else []
out = Tensor.custom_kernel(out, a_q.reshape(a.shape), b_q, a_si, b_si, a_e8, b_e8, *extra, fxn=fxn, grad_fxn=grad_fxn)[0]
# fp8 gemm computes a@b.T, kernel multiplies output by x_scale * w_scale before bf16 store
elif a.dtype == FP8_DTYPE:
scales = tuple(s for s in (x_scale, w_scale, g_amax) if s is not None)
scale_mode = (1 if x_scale is not None else 0) | (2 if w_scale is not None else 0) | (4 if g_amax is not None else 0)
assert (grad_amax_state is None) == (next_grad_amax_state is None)
extra = ([grad_amax_state, next_grad_amax_state] if grad_amax_state is not None else []) + ([w_post_scale] if w_post_scale is not None else [])
fxn = functools.partial(custom_hk_fp8_gemm, dname=dname, scale_mode=scale_mode)
bw = functools.partial(custom_gemm_bw, n_scales=len(scales), has_grad_amax=grad_amax_state is not None, has_w_post=w_post_scale is not None)
out = Tensor.custom_kernel(out, a, b.T, *scales, *extra, fxn=fxn, grad_fxn=bw)[0]
elif a.dtype == dtypes.bfloat16:
out = Tensor.custom_kernel(out, a, b.T, b, fxn=functools.partial(custom_hk_bf16_gemm, dname=dname), grad_fxn=custom_gemm_bw)[0]
else:
+119 -116
View File
@@ -8,6 +8,10 @@ class Kernel:
self.instructions.append(inst)
inst._target, inst._pos = target, self.pos
self.pos += inst.size()
def waitcnt(self, lgkm=None, vm=None, exp=None):
vmcnt, lgkmcnt, expcnt = vm if vm is not None else 63, lgkm if lgkm is not None else 15, exp if exp is not None else 7
waitcnt = (vmcnt & 0xf) | ((expcnt & 0x7) << 4) | ((lgkmcnt & 0xf) << 8) | ((vmcnt & 0x30) << 10)
self.emit(s_waitcnt(simm16=waitcnt))
def finalize(self):
for inst in self.instructions:
if inst._target is not None: inst.simm16 = (self.labels[inst._target] - inst._pos - inst.size()) // 4
@@ -50,7 +54,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_mov_b32(s[49], s[2]))
k.emit(s_mov_b32(s[47], s[3]))
k.emit(v_readfirstlane_b32_e32(v[46], v[3]))
k.emit(s_waitcnt(49279))
k.waitcnt(lgkm=0)
if (tile_m, tile_n) == (128, 512):
for i in range(2):
@@ -271,7 +275,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(buffer_load_dwordx4(v[0:3], v[214 + i * 1], s[12:15], 0, 0, 1, 0, 0, 0, 0, 1))
for i in range(24):
k.emit(v_accvgpr_write(v[232 + i * 1], 0))
k.emit(s_waitcnt(20347))
k.waitcnt(vm=27)
k.emit(s_barrier())
k.emit(ds_read_b128(v[8:11], v[216]))
k.emit(ds_read_b128(v[24:27], v[216], v[0], v[0], 0, 64))
@@ -316,11 +320,10 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_mul_i32(s[62], s[36], 64))
k.emit(v_add_u32_e32(v[242], s[62], v[240]))
k.emit(v_add_u32_e32(v[243], s[62], v[241]))
k.label('L0_0FAC')
k.emit(s_cmp_lt_i32(s[46], 2))
k.emit(s_cbranch_scc0(1283), target='L0_23C0')
k.label('L0_0FB4')
k.emit(s_waitcnt(127))
k.emit(s_cbranch_scc0(), target='T128_LOOP_LDS1')
k.label('T128_LOOP_LDS0')
k.waitcnt(lgkm=0, vm=15)
k.emit(v_mfma_fp4(v[0:3], v[72:75], v[8:11], 0, 0, v[204], v[200]))
k.emit(s_barrier())
for i in range(2):
@@ -351,7 +354,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(buffer_load_dwordx4(v[164:167], v[227], s[16:19], 0, 0, 1))
k.emit(v_mfma_fp4(v[56:59], v[100:103], v[32:35], 1, 3, v[205], v[201]))
k.emit(v_mfma_fp4(v[60:63], v[100:103], v[36:39], 3, 3, v[205], v[201]))
k.emit(s_waitcnt(3965))
k.waitcnt(vm=13)
k.emit(v_mfma_fp4(v[64:67], v[104:107], v[8:11], 0, 0, v[206], v[200]))
k.emit(s_add_u32(s[62], LIT, s[60], 512))
k.emit(v_mfma_fp4(v[68:71], v[104:107], v[12:15], 2, 0, v[206], v[200]))
@@ -398,7 +401,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(buffer_load_dwordx4(v[188:191], v[233], s[16:19], 0, 0, 1))
k.emit(v_mfma_fp4(v[120:123], v[132:135], v[32:35], 1, 3, v[207], v[201]))
k.emit(v_mfma_fp4(v[124:127], v[132:135], v[36:39], 3, 3, v[207], v[201]))
k.emit(s_waitcnt(16498))
k.waitcnt(lgkm=0, vm=18)
k.emit(v_mfma_fp4(v[128:131], v[72:75], v[40:43], 0, 0, v[204], v[202]))
k.emit(s_barrier())
k.emit(v_mfma_fp4(v[132:135], v[72:75], v[44:47], 2, 0, v[204], v[202]))
@@ -481,8 +484,8 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[248:251], v[132:135], v[64:67], 1, 3, v[207], v[203]))
k.emit(s_cmp_lt_i32(s[60], s[61]))
k.emit(v_mfma_fp4(v[252:255], v[132:135], v[68:71], 3, 3, v[207], v[203]))
k.emit(s_cbranch_scc0(1926), target='L0_37CC')
k.emit(s_waitcnt(127))
k.emit(s_cbranch_scc0(), target='T128_EPILOGUE')
k.waitcnt(lgkm=0, vm=15)
k.emit(v_mfma_fp4(v[0:3], v[136:139], v[8:11], 0, 0, v[208], v[200]))
k.emit(s_barrier())
for i in range(2):
@@ -513,7 +516,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(buffer_load_dwordx4(v[100:103], v[227], s[16:19], 0, 0, 1))
k.emit(v_mfma_fp4(v[56:59], v[164:167], v[32:35], 1, 3, v[209], v[201]))
k.emit(v_mfma_fp4(v[60:63], v[164:167], v[36:39], 3, 3, v[209], v[201]))
k.emit(s_waitcnt(3965))
k.waitcnt(vm=13)
k.emit(v_mfma_fp4(v[64:67], v[168:171], v[8:11], 0, 0, v[210], v[200]))
k.emit(s_add_u32(s[62], LIT, s[60], 512))
k.emit(v_mfma_fp4(v[68:71], v[168:171], v[12:15], 2, 0, v[210], v[200]))
@@ -560,7 +563,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(buffer_load_dwordx4(v[124:127], v[233], s[16:19], 0, 0, 1))
k.emit(v_mfma_fp4(v[120:123], v[196:199], v[32:35], 1, 3, v[211], v[201]))
k.emit(v_mfma_fp4(v[124:127], v[196:199], v[36:39], 3, 3, v[211], v[201]))
k.emit(s_waitcnt(16498))
k.waitcnt(lgkm=0, vm=18)
k.emit(v_mfma_fp4(v[128:131], v[136:139], v[40:43], 0, 0, v[208], v[202]))
k.emit(s_barrier())
k.emit(v_mfma_fp4(v[132:135], v[136:139], v[44:47], 2, 0, v[208], v[202]))
@@ -643,10 +646,10 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[248:251], v[196:199], v[64:67], 1, 3, v[211], v[203]))
k.emit(s_cmp_lt_i32(s[60], s[61]))
k.emit(v_mfma_fp4(v[252:255], v[196:199], v[68:71], 3, 3, v[211], v[203]))
k.emit(s_cbranch_scc0(1284), target='L0_37CC')
k.emit(s_branch(64253), target='L0_0FB4')
k.label('L0_23C0')
k.emit(s_waitcnt(127))
k.emit(s_cbranch_scc0(), target='T128_EPILOGUE')
k.emit(s_branch(), target='T128_LOOP_LDS0')
k.label('T128_LOOP_LDS1')
k.waitcnt(lgkm=0, vm=15)
k.emit(v_mfma_fp4(v[0:3], v[72:75], v[8:11], 0, 0, v[204], v[200]))
k.emit(s_barrier())
for i in range(2):
@@ -679,7 +682,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[56:59], v[100:103], v[32:35], 1, 3, v[205], v[201]))
k.emit(buffer_load_dwordx4(v[164:167], v[227], s[16:19], 0, 0, 1))
k.emit(v_mfma_fp4(v[60:63], v[100:103], v[36:39], 3, 3, v[205], v[201]))
k.emit(s_waitcnt(3965))
k.waitcnt(vm=13)
k.emit(v_mfma_fp4(v[64:67], v[104:107], v[8:11], 0, 0, v[206], v[200]))
k.emit(s_add_u32(s[62], LIT, s[60], 512))
k.emit(v_mfma_fp4(v[68:71], v[104:107], v[12:15], 2, 0, v[206], v[200]))
@@ -724,7 +727,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[120:123], v[132:135], v[32:35], 1, 3, v[207], v[201]))
k.emit(v_mfma_fp4(v[124:127], v[132:135], v[36:39], 3, 3, v[207], v[201]))
k.emit(buffer_load_dwordx4(v[188:191], v[233], s[16:19], 0, 0, 1))
k.emit(s_waitcnt(16498))
k.waitcnt(lgkm=0, vm=18)
k.emit(v_mfma_fp4(v[128:131], v[72:75], v[40:43], 0, 0, v[204], v[202]))
k.emit(s_barrier())
k.emit(v_mfma_fp4(v[132:135], v[72:75], v[44:47], 2, 0, v[204], v[202]))
@@ -803,8 +806,8 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[248:251], v[132:135], v[64:67], 1, 3, v[207], v[203]))
k.emit(s_cmp_lt_i32(s[60], s[61]))
k.emit(v_mfma_fp4(v[252:255], v[132:135], v[68:71], 3, 3, v[207], v[203]))
k.emit(s_cbranch_scc0(643), target='L0_37CC')
k.emit(s_waitcnt(127))
k.emit(s_cbranch_scc0(), target='T128_EPILOGUE')
k.waitcnt(lgkm=0, vm=15)
k.emit(v_mfma_fp4(v[0:3], v[136:139], v[8:11], 0, 0, v[208], v[200]))
k.emit(s_barrier())
for i in range(2):
@@ -837,7 +840,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[56:59], v[164:167], v[32:35], 1, 3, v[209], v[201]))
k.emit(buffer_load_dwordx4(v[100:103], v[227], s[16:19], 0, 0, 1))
k.emit(v_mfma_fp4(v[60:63], v[164:167], v[36:39], 3, 3, v[209], v[201]))
k.emit(s_waitcnt(3965))
k.waitcnt(vm=13)
k.emit(v_mfma_fp4(v[64:67], v[168:171], v[8:11], 0, 0, v[210], v[200]))
k.emit(s_add_u32(s[62], LIT, s[60], 512))
k.emit(v_mfma_fp4(v[68:71], v[168:171], v[12:15], 2, 0, v[210], v[200]))
@@ -882,7 +885,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[120:123], v[196:199], v[32:35], 1, 3, v[211], v[201]))
k.emit(v_mfma_fp4(v[124:127], v[196:199], v[36:39], 3, 3, v[211], v[201]))
k.emit(buffer_load_dwordx4(v[124:127], v[233], s[16:19], 0, 0, 1))
k.emit(s_waitcnt(16498))
k.waitcnt(lgkm=0, vm=18)
k.emit(v_mfma_fp4(v[128:131], v[136:139], v[40:43], 0, 0, v[208], v[202]))
k.emit(s_barrier())
k.emit(v_mfma_fp4(v[132:135], v[136:139], v[44:47], 2, 0, v[208], v[202]))
@@ -961,10 +964,10 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[248:251], v[196:199], v[64:67], 1, 3, v[211], v[203]))
k.emit(s_cmp_lt_i32(s[60], s[61]))
k.emit(v_mfma_fp4(v[252:255], v[196:199], v[68:71], 3, 3, v[211], v[203]))
k.emit(s_cbranch_scc0(1), target='L0_37CC')
k.emit(s_branch(64253), target='L0_23C0')
k.label('L0_37CC')
k.emit(s_waitcnt())
k.emit(s_cbranch_scc0(), target='T128_EPILOGUE')
k.emit(s_branch(), target='T128_LOOP_LDS1')
k.label('T128_EPILOGUE')
k.waitcnt(lgkm=0, vm=0, exp=0)
k.emit(s_barrier())
k.emit(s_mul_i32(s[62], s[36], 0))
k.emit(v_add_u32_e32(v[244], s[62], v[240]))
@@ -1215,7 +1218,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_nop(1))
k.emit(buffer_store_dwordx4(v[16:19], v[244], s[4:7], 0, 0, 1))
k.emit(v_add_i32(v[244], v[244], 64))
k.emit(s_waitcnt())
k.waitcnt(lgkm=0, vm=0, exp=0)
k.emit(s_endpgm())
elif (tile_m, tile_n) == (192, 256):
k.emit(s_mul_i32(s[63], LIT, 8, 192))
@@ -1246,7 +1249,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_nop(3))
k.emit(s_lshl_b32(s[62], s[62], 3))
k.emit(s_cmp_lt_i32(s[3], s[62]))
k.emit(s_cbranch_scc0(33), target='L1_01C0')
k.emit(s_cbranch_scc0(), target='T192_REMAP_IDENTITY')
k.emit(s_add_u32(s[49], s[44], LIT, 255))
k.emit(s_lshr_b32(s[48], s[49], 8))
k.emit(s_mul_i32(s[49], s[48], s[3]))
@@ -1255,38 +1258,38 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_lshl_b32(s[47], s[63], 5))
k.emit(s_mul_i32(s[62], s[62], s[47]))
k.emit(s_cmp_lt_i32(s[49], s[62]))
k.emit(s_cbranch_scc0(13), target='L1_0198')
k.emit(s_cbranch_scc0(), target='T192_REMAP_NARROW')
k.emit(s_and_b32(s[62], s[49], LIT, 255))
k.emit(s_and_b32(s[47], s[62], 31))
k.emit(s_lshr_b32(s[48], s[62], 5))
k.emit(s_lshr_b32(s[49], s[49], 8))
k.label('L1_0178')
k.label('T192_REMAP_WIDE_LOOP')
k.emit(s_cmp_lt_i32(s[49], s[63]))
k.emit(s_cbranch_scc1(3), target='L1_018C')
k.emit(s_cbranch_scc1(), target='T192_REMAP_WIDE_DONE')
k.emit(s_sub_i32(s[49], s[49], s[63]))
k.emit(s_add_i32(s[48], s[48], 8))
k.emit(s_branch(65531), target='L1_0178')
k.label('L1_018C')
k.emit(s_branch(), target='T192_REMAP_WIDE_LOOP')
k.label('T192_REMAP_WIDE_DONE')
k.emit(s_mul_i32(s[49], s[49], 32))
k.emit(s_add_i32(s[47], s[47], s[49]))
k.emit(s_branch(12), target='L1_01C8')
k.label('L1_0198')
k.emit(s_branch(), target='T192_REMAP_DONE')
k.label('T192_REMAP_NARROW')
k.emit(s_sub_i32(s[49], s[49], s[62]))
k.emit(s_sub_i32(s[63], s[48], s[47]))
k.emit(s_mov_b32(s[48], 0))
k.label('L1_01A4')
k.label('T192_REMAP_NARROW_LOOP')
k.emit(s_cmp_lt_i32(s[49], s[63]))
k.emit(s_cbranch_scc1(3), target='L1_01B8')
k.emit(s_cbranch_scc1(), target='T192_REMAP_NARROW_DONE')
k.emit(s_sub_i32(s[49], s[49], s[63]))
k.emit(s_add_i32(s[48], s[48], 1))
k.emit(s_branch(65531), target='L1_01A4')
k.label('L1_01B8')
k.emit(s_branch(), target='T192_REMAP_NARROW_LOOP')
k.label('T192_REMAP_NARROW_DONE')
k.emit(s_add_i32(s[47], s[47], s[49]))
k.emit(s_branch(2), target='L1_01C8')
k.label('L1_01C0')
k.emit(s_branch(), target='T192_REMAP_DONE')
k.label('T192_REMAP_IDENTITY')
k.emit(s_mov_b32(s[47], s[2]))
k.emit(s_mov_b32(s[48], s[3]))
k.label('L1_01C8')
k.label('T192_REMAP_DONE')
k.emit(s_lshr_b32(s[37], s[37], 1))
k.emit(s_mul_i32(s[62], s[48], LIT, 192))
k.emit(s_mul_hi_u32(s[63], s[37], s[62]))
@@ -1493,7 +1496,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(buffer_load_dwordx4(v[144:147], v[189], s[16:19], 0, 1024, 1))
k.emit(buffer_load_dwordx4(v[148:151], v[190], s[16:19], 0, 1024, 1))
k.emit(buffer_load_dword(v[176], v[193], s[24:27], 0, 0, 1))
k.emit(s_waitcnt(20347))
k.waitcnt(vm=27)
k.emit(s_barrier())
k.emit(ds_read_b128(v[8:11], v[184]))
k.emit(ds_read_b128(v[16:19], v[184], v[0], v[0], 0, 64))
@@ -1539,12 +1542,12 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
for i in range(23):
k.emit(v_add_u32_e32(v[196 + i * 1], s[62], v[195 + i * 1]))
k.emit(s_cmp_lt_i32(s[46], 2))
k.emit(s_cbranch_scc0(1063), target='L1_1E2C')
k.label('L1_0D90')
k.emit(s_waitcnt(20338))
k.emit(s_cbranch_scc0(), target='T192_LOOP_LDS1')
k.label('T192_LOOP_LDS0')
k.waitcnt(vm=18)
k.emit(s_barrier())
for i in range(2):
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
for j54 in range(2):
k.emit(v_mfma_fp4(v[0 + i * 32:3 + i * 32], v[104 + j54 * 8:107 + j54 * 8], v[8 + j54 * 8 + i * 16:11 + j54 * 8 + i * 16], 0, 0 + j54 * 3, v[174], v[168 + i * 1]))
k.emit(ds_read_b128(v[40 + j54 * 4 + i * 16:43 + j54 * 4 + i * 16], v[184], v[0], v[0], 0, 0 + i * 128, 33 + j54 * 2 + i * 16))
@@ -1554,7 +1557,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(ds_read_b128(v[48 + j54 * 4 + i * 16:51 + j54 * 4 + i * 16], v[184], v[0], v[0], 0, 64 + i * 128, 33 + j54 * 2 + i * 16))
k.emit(v_mfma_fp4(v[12 + i * 32:15 + i * 32], v[108 + j54 * 8:111 + j54 * 8], v[12 + j54 * 8 + i * 16:15 + j54 * 8 + i * 16], 3, 0 + j54 * 3, v[174], v[168 + i * 1]))
k.emit(ds_read_b32(v[170 + i * 1], v[188], v[0], v[0], 0, 0, 2 + i * 1))
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
k.emit(v_mfma_fp4(v[64:67], v[104:107], v[40:43], 0, 0, v[174], v[170]))
k.emit(ds_read_b128(v[72:75], v[184], v[0], v[0], 0, 0, 66))
k.emit(v_mfma_fp4(v[68:71], v[104:107], v[44:47], 2, 0, v[174], v[170]))
@@ -1576,7 +1579,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_addc_u32(s[17], 0, s[17]))
k.emit(s_sub_u32(s[18], s[18], s[67]))
k.emit(ds_read_b32(v[172], v[188], v[0], v[0], 0, 0, 4))
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
k.emit(v_mfma_fp4(v[96:99], v[104:107], v[56:59], 0, 0, v[174], v[171]))
k.emit(s_add_u32(s[24], s[24], s[69]))
k.emit(ds_read_b128(v[88:91], v[184], v[0], v[0], 0, 128, 82))
@@ -1592,7 +1595,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[108:111], v[116:119], v[68:71], 3, 3, v[174], v[171]))
k.emit(ds_read_b32(v[173], v[188], v[0], v[0], 0, 0, 5))
k.emit(s_barrier())
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
k.emit(v_mfma_fp4(v[128:131], v[104:107], v[72:75], 0, 0, v[174], v[172]))
k.emit(v_mfma_fp4(v[132:135], v[104:107], v[76:79], 2, 0, v[174], v[172]))
k.emit(s_add_u32(NULL, 0, s[65]))
@@ -1605,7 +1608,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(buffer_load_dword(v[0], v[187], s[20:23], 0, 0, 1, 0, 0, 0, 0, 1))
k.emit(v_mfma_fp4(v[136:139], v[116:119], v[80:83], 1, 3, v[174], v[172]))
k.emit(v_mfma_fp4(v[140:143], v[116:119], v[84:87], 3, 3, v[174], v[172]))
k.emit(s_waitcnt(49279))
k.waitcnt(lgkm=0)
k.emit(v_mfma_fp4(v[160:163], v[104:107], v[88:91], 0, 0, v[174], v[173]))
k.emit(v_mfma_fp4(v[164:167], v[104:107], v[92:95], 2, 0, v[174], v[173]))
k.emit(s_add_u32(NULL, 0, s[64]))
@@ -1618,7 +1621,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(buffer_load_dwordx4(v[0:3], v[179], s[12:15], 0, 0, 1, 0, 0, 0, 0, 1))
k.emit(v_mfma_fp4(v[168:171], v[116:119], v[96:99], 1, 3, v[174], v[173]))
k.emit(v_mfma_fp4(v[172:175], v[116:119], v[100:103], 3, 3, v[174], v[173]))
k.emit(s_waitcnt(20338))
k.waitcnt(vm=18)
k.emit(s_barrier())
k.emit(v_mfma_fp4(v[16:19], v[120:123], v[8:11], 0, 0, v[175], v[168]))
k.emit(v_mfma_fp4(v[20:23], v[120:123], v[12:15], 2, 0, v[175], v[168]))
@@ -1689,11 +1692,11 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(ds_read_b128(v[24 + j55 * 8 + i * 4:27 + j55 * 8 + i * 4], v[185], v[0], v[0], 0, 128 + j55 * 64, 16 + i * 2))
k.emit(v_mfma_fp4(v[180 + j55 * 8:183 + j55 * 8], v[120 + j55 * 4 + i * 8:123 + j55 * 4 + i * 8], v[92 + i * 8:95 + i * 8], 2 + j55 * 1, 0 + i * 3, v[175], v[173]))
k.emit(ds_read_b32(v[169], v[188], v[0], v[0], 0, 0, 9))
k.emit(s_cbranch_scc0(1596), target='L1_2EC8')
k.emit(s_waitcnt(20338))
k.emit(s_cbranch_scc0(), target='T192_EPILOGUE')
k.waitcnt(vm=18)
k.emit(s_barrier())
for i in range(2):
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
for j56 in range(2):
k.emit(v_mfma_fp4(v[0 + i * 32:3 + i * 32], v[136 + j56 * 8:139 + j56 * 8], v[8 + j56 * 8 + i * 16:11 + j56 * 8 + i * 16], 0, 0 + j56 * 3, v[176], v[168 + i * 1]))
k.emit(ds_read_b128(v[40 + j56 * 4 + i * 16:43 + j56 * 4 + i * 16], v[185], v[0], v[0], 0, 0 + i * 128, 33 + j56 * 2 + i * 16))
@@ -1703,7 +1706,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(ds_read_b128(v[48 + j56 * 4 + i * 16:51 + j56 * 4 + i * 16], v[185], v[0], v[0], 0, 64 + i * 128, 33 + j56 * 2 + i * 16))
k.emit(v_mfma_fp4(v[12 + i * 32:15 + i * 32], v[140 + j56 * 8:143 + j56 * 8], v[12 + j56 * 8 + i * 16:15 + j56 * 8 + i * 16], 3, 0 + j56 * 3, v[176], v[168 + i * 1]))
k.emit(ds_read_b32(v[170 + i * 1], v[188], v[0], v[0], 0, 0, 10 + i * 1))
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
k.emit(v_mfma_fp4(v[64:67], v[136:139], v[40:43], 0, 0, v[176], v[170]))
k.emit(ds_read_b128(v[72:75], v[185], v[0], v[0], 0, 0, 66))
k.emit(v_mfma_fp4(v[68:71], v[136:139], v[44:47], 2, 0, v[176], v[170]))
@@ -1725,7 +1728,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_addc_u32(s[17], 0, s[17]))
k.emit(s_sub_u32(s[18], s[18], s[67]))
k.emit(ds_read_b32(v[172], v[188], v[0], v[0], 0, 0, 12))
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
k.emit(v_mfma_fp4(v[96:99], v[136:139], v[56:59], 0, 0, v[176], v[171]))
k.emit(s_add_u32(s[24], s[24], s[69]))
k.emit(ds_read_b128(v[88:91], v[185], v[0], v[0], 0, 128, 82))
@@ -1741,7 +1744,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[108:111], v[148:151], v[68:71], 3, 3, v[176], v[171]))
k.emit(ds_read_b32(v[173], v[188], v[0], v[0], 0, 0, 13))
k.emit(s_barrier())
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
for i in range(2):
k.emit(v_mfma_fp4(v[128:131], v[136 + i * 8:139 + i * 8], v[72 + i * 8:75 + i * 8], 0, 0 + i * 3, v[176], v[172]))
k.emit(v_mfma_fp4(v[132:135], v[136 + i * 8:139 + i * 8], v[76 + i * 8:79 + i * 8], 2, 0 + i * 3, v[176], v[172]))
@@ -1749,7 +1752,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(buffer_load_dword(v[0], v[186 + i * 1], s[20:23], 0, 0, 1, 0, 0, 0, 0, 1))
k.emit(v_mfma_fp4(v[136:139], v[140 + i * 8:143 + i * 8], v[72 + i * 8:75 + i * 8], 1, 0 + i * 3, v[176], v[172]))
k.emit(v_mfma_fp4(v[140:143], v[140 + i * 8:143 + i * 8], v[76 + i * 8:79 + i * 8], 3, 0 + i * 3, v[176], v[172]))
k.emit(s_waitcnt(49279))
k.waitcnt(lgkm=0)
for i in range(2):
k.emit(v_mfma_fp4(v[160:163], v[136 + i * 8:139 + i * 8], v[88 + i * 8:91 + i * 8], 0, 0 + i * 3, v[176], v[173]))
k.emit(v_mfma_fp4(v[164:167], v[136 + i * 8:139 + i * 8], v[92 + i * 8:95 + i * 8], 2, 0 + i * 3, v[176], v[173]))
@@ -1757,7 +1760,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(buffer_load_dwordx4(v[0:3], v[178 + i * 1], s[12:15], 0, 0, 1, 0, 0, 0, 0, 1))
k.emit(v_mfma_fp4(v[168:171], v[140 + i * 8:143 + i * 8], v[88 + i * 8:91 + i * 8], 1, 0 + i * 3, v[176], v[173]))
k.emit(v_mfma_fp4(v[172:175], v[140 + i * 8:143 + i * 8], v[92 + i * 8:95 + i * 8], 3, 0 + i * 3, v[176], v[173]))
k.emit(s_waitcnt(20338))
k.waitcnt(vm=18)
k.emit(s_barrier())
k.emit(v_mfma_fp4(v[16:19], v[152:155], v[8:11], 0, 0, v[177], v[168]))
k.emit(v_mfma_fp4(v[20:23], v[152:155], v[12:15], 2, 0, v[177], v[168]))
@@ -1828,13 +1831,13 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(ds_read_b128(v[24 + j57 * 8 + i * 4:27 + j57 * 8 + i * 4], v[184], v[0], v[0], 0, 128 + j57 * 64, 16 + i * 2))
k.emit(v_mfma_fp4(v[180 + j57 * 8:183 + j57 * 8], v[152 + j57 * 4 + i * 8:155 + j57 * 4 + i * 8], v[92 + i * 8:95 + i * 8], 2 + j57 * 1, 0 + i * 3, v[177], v[173]))
k.emit(ds_read_b32(v[169], v[188], v[0], v[0], 0, 0, 1))
k.emit(s_cbranch_scc0(1064), target='L1_2EC8')
k.emit(s_branch(64473), target='L1_0D90')
k.label('L1_1E2C')
k.emit(s_waitcnt(20338))
k.emit(s_cbranch_scc0(), target='T192_EPILOGUE')
k.emit(s_branch(), target='T192_LOOP_LDS0')
k.label('T192_LOOP_LDS1')
k.waitcnt(vm=18)
k.emit(s_barrier())
for i in range(2):
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
for j58 in range(2):
k.emit(v_mfma_fp4(v[0 + i * 32:3 + i * 32], v[104 + j58 * 8:107 + j58 * 8], v[8 + j58 * 8 + i * 16:11 + j58 * 8 + i * 16], 0, 0 + j58 * 3, v[174], v[168 + i * 1]))
k.emit(buffer_load_dwordx4(v[152 + j58 * 4 + i * 8:155 + j58 * 4 + i * 8], v[191 + j58 * 1], s[16:19], 0, 0 + i * 1024, 1))
@@ -1844,7 +1847,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[12 + i * 32:15 + i * 32], v[108 + j58 * 8:111 + j58 * 8], v[12 + j58 * 8 + i * 16:15 + j58 * 8 + i * 16], 3, 0 + j58 * 3, v[174], v[168 + i * 1]))
k.emit(ds_read_b128(v[48 + j58 * 4 + i * 16:51 + j58 * 4 + i * 16], v[184], v[0], v[0], 0, 64 + i * 128, 33 + j58 * 2 + i * 16))
k.emit(ds_read_b32(v[170 + i * 1], v[188], v[0], v[0], 0, 0, 2 + i * 1))
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
k.emit(v_mfma_fp4(v[64:67], v[104:107], v[40:43], 0, 0, v[174], v[170]))
k.emit(buffer_load_dword(v[177], v[194], s[24:27], 0, 0, 1))
k.emit(v_mfma_fp4(v[68:71], v[104:107], v[44:47], 2, 0, v[174], v[170]))
@@ -1866,7 +1869,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_sub_u32(s[18], s[18], s[67]))
k.emit(ds_read_b128(v[84:87], v[184], v[0], v[0], 0, 64, 68))
k.emit(ds_read_b32(v[172], v[188], v[0], v[0], 0, 0, 4))
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
k.emit(v_mfma_fp4(v[96:99], v[104:107], v[56:59], 0, 0, v[174], v[171]))
k.emit(s_add_u32(s[24], s[24], s[69]))
k.emit(v_mfma_fp4(v[100:103], v[104:107], v[60:63], 2, 0, v[174], v[171]))
@@ -1882,7 +1885,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(ds_read_b128(v[100:103], v[184], v[0], v[0], 0, 192, 84))
k.emit(ds_read_b32(v[173], v[188], v[0], v[0], 0, 0, 5))
k.emit(s_barrier())
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
k.emit(v_mfma_fp4(v[128:131], v[104:107], v[72:75], 0, 0, v[174], v[172]))
k.emit(s_add_u32(NULL, 0, s[65]))
k.emit(buffer_load_dword(v[0], v[186], s[20:23], 0, 0, 1, 0, 0, 0, 0, 1))
@@ -1895,7 +1898,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[132:135], v[112:115], v[84:87], 2, 3, v[174], v[172]))
k.emit(v_mfma_fp4(v[136:139], v[116:119], v[80:83], 1, 3, v[174], v[172]))
k.emit(v_mfma_fp4(v[140:143], v[116:119], v[84:87], 3, 3, v[174], v[172]))
k.emit(s_waitcnt(49279))
k.waitcnt(lgkm=0)
k.emit(v_mfma_fp4(v[160:163], v[104:107], v[88:91], 0, 0, v[174], v[173]))
k.emit(s_add_u32(NULL, 0, s[64]))
k.emit(buffer_load_dwordx4(v[0:3], v[178], s[12:15], 0, 0, 1, 0, 0, 0, 0, 1))
@@ -1908,7 +1911,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[164:167], v[112:115], v[100:103], 2, 3, v[174], v[173]))
k.emit(v_mfma_fp4(v[168:171], v[116:119], v[96:99], 1, 3, v[174], v[173]))
k.emit(v_mfma_fp4(v[172:175], v[116:119], v[100:103], 3, 3, v[174], v[173]))
k.emit(s_waitcnt(20338))
k.waitcnt(vm=18)
k.emit(s_barrier())
k.emit(v_mfma_fp4(v[16:19], v[120:123], v[8:11], 0, 0, v[175], v[168]))
k.emit(s_add_u32(NULL, LIT, s[64], 8448))
@@ -1975,11 +1978,11 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[180 + j59 * 8:183 + j59 * 8], v[120 + j59 * 4 + i * 8:123 + j59 * 4 + i * 8], v[92 + i * 8:95 + i * 8], 2 + j59 * 1, 0 + i * 3, v[175], v[173]))
k.emit(ds_read_b128(v[24 + j59 * 8 + i * 4:27 + j59 * 8 + i * 4], v[185], v[0], v[0], 0, 128 + j59 * 64, 16 + i * 2))
k.emit(ds_read_b32(v[169], v[188], v[0], v[0], 0, 0, 9))
k.emit(s_cbranch_scc0(533), target='L1_2EC8')
k.emit(s_waitcnt(20338))
k.emit(s_cbranch_scc0(), target='T192_EPILOGUE')
k.waitcnt(vm=18)
k.emit(s_barrier())
for i in range(2):
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
for j60 in range(2):
k.emit(v_mfma_fp4(v[0 + i * 32:3 + i * 32], v[136 + j60 * 8:139 + j60 * 8], v[8 + j60 * 8 + i * 16:11 + j60 * 8 + i * 16], 0, 0 + j60 * 3, v[176], v[168 + i * 1]))
k.emit(buffer_load_dwordx4(v[120 + j60 * 4 + i * 8:123 + j60 * 4 + i * 8], v[191 + j60 * 1], s[16:19], 0, 0 + i * 1024, 1))
@@ -1989,7 +1992,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[12 + i * 32:15 + i * 32], v[140 + j60 * 8:143 + j60 * 8], v[12 + j60 * 8 + i * 16:15 + j60 * 8 + i * 16], 3, 0 + j60 * 3, v[176], v[168 + i * 1]))
k.emit(ds_read_b128(v[48 + j60 * 4 + i * 16:51 + j60 * 4 + i * 16], v[185], v[0], v[0], 0, 64 + i * 128, 33 + j60 * 2 + i * 16))
k.emit(ds_read_b32(v[170 + i * 1], v[188], v[0], v[0], 0, 0, 10 + i * 1))
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
k.emit(v_mfma_fp4(v[64:67], v[136:139], v[40:43], 0, 0, v[176], v[170]))
k.emit(buffer_load_dword(v[175], v[194], s[24:27], 0, 0, 1))
k.emit(v_mfma_fp4(v[68:71], v[136:139], v[44:47], 2, 0, v[176], v[170]))
@@ -2011,7 +2014,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_sub_u32(s[18], s[18], s[67]))
k.emit(ds_read_b128(v[84:87], v[185], v[0], v[0], 0, 64, 68))
k.emit(ds_read_b32(v[172], v[188], v[0], v[0], 0, 0, 12))
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
k.emit(v_mfma_fp4(v[96:99], v[136:139], v[56:59], 0, 0, v[176], v[171]))
k.emit(s_add_u32(s[24], s[24], s[69]))
k.emit(v_mfma_fp4(v[100:103], v[136:139], v[60:63], 2, 0, v[176], v[171]))
@@ -2027,7 +2030,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(ds_read_b128(v[100:103], v[185], v[0], v[0], 0, 192, 84))
k.emit(ds_read_b32(v[173], v[188], v[0], v[0], 0, 0, 13))
k.emit(s_barrier())
k.emit(s_waitcnt(50559))
k.waitcnt(lgkm=5)
for i in range(2):
k.emit(v_mfma_fp4(v[128:131], v[136 + i * 8:139 + i * 8], v[72 + i * 8:75 + i * 8], 0, 0 + i * 3, v[176], v[172]))
k.emit(s_add_u32(NULL, LIT, s[65], 2048 + i * 1024))
@@ -2035,7 +2038,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[132:135], v[136 + i * 8:139 + i * 8], v[76 + i * 8:79 + i * 8], 2, 0 + i * 3, v[176], v[172]))
k.emit(v_mfma_fp4(v[136:139], v[140 + i * 8:143 + i * 8], v[72 + i * 8:75 + i * 8], 1, 0 + i * 3, v[176], v[172]))
k.emit(v_mfma_fp4(v[140:143], v[140 + i * 8:143 + i * 8], v[76 + i * 8:79 + i * 8], 3, 0 + i * 3, v[176], v[172]))
k.emit(s_waitcnt(49279))
k.waitcnt(lgkm=0)
for i in range(2):
k.emit(v_mfma_fp4(v[160:163], v[136 + i * 8:139 + i * 8], v[88 + i * 8:91 + i * 8], 0, 0 + i * 3, v[176], v[173]))
k.emit(s_add_u32(NULL, LIT, s[64], 25344 + i * 4224))
@@ -2043,7 +2046,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[164:167], v[136 + i * 8:139 + i * 8], v[92 + i * 8:95 + i * 8], 2, 0 + i * 3, v[176], v[173]))
k.emit(v_mfma_fp4(v[168:171], v[140 + i * 8:143 + i * 8], v[88 + i * 8:91 + i * 8], 1, 0 + i * 3, v[176], v[173]))
k.emit(v_mfma_fp4(v[172:175], v[140 + i * 8:143 + i * 8], v[92 + i * 8:95 + i * 8], 3, 0 + i * 3, v[176], v[173]))
k.emit(s_waitcnt(20338))
k.waitcnt(vm=18)
k.emit(s_barrier())
k.emit(v_mfma_fp4(v[16:19], v[152:155], v[8:11], 0, 0, v[177], v[168]))
k.emit(s_add_u32(NULL, LIT, s[64], 33792))
@@ -2110,10 +2113,10 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[180 + j61 * 8:183 + j61 * 8], v[152 + j61 * 4 + i * 8:155 + j61 * 4 + i * 8], v[92 + i * 8:95 + i * 8], 2 + j61 * 1, 0 + i * 3, v[177], v[173]))
k.emit(ds_read_b128(v[24 + j61 * 8 + i * 4:27 + j61 * 8 + i * 4], v[184], v[0], v[0], 0, 128 + j61 * 64, 16 + i * 2))
k.emit(ds_read_b32(v[169], v[188], v[0], v[0], 0, 0, 1))
k.emit(s_cbranch_scc0(1), target='L1_2EC8')
k.emit(s_branch(64473), target='L1_1E2C')
k.label('L1_2EC8')
k.emit(s_waitcnt())
k.emit(s_cbranch_scc0(), target='T192_EPILOGUE')
k.emit(s_branch(), target='T192_LOOP_LDS1')
k.label('T192_EPILOGUE')
k.waitcnt(lgkm=0, vm=0, exp=0)
k.emit(s_barrier())
k.emit(v_lshrrev_b32_e32(v[4], 5))
k.emit(v_mul_i32_i24_e32(v[4], 16, v[4]))
@@ -2201,17 +2204,17 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_permlane16_swap_b32_e32(v[17], v[19]))
k.emit(s_nop(1))
k.emit(ds_write_b128(v[0], v[4], v[16:19], v[0], 0, 0 + i * 64, 88))
k.emit(s_waitcnt(49279))
k.waitcnt(lgkm=0)
k.emit(v_mul_i32_i24_e64(v[4], v[0], 16))
k.emit(v_add_i32(v[4], v[4], s[62]))
k.emit(ds_read_b128(v[16:19], v[4]))
for i in range(23):
k.emit(s_waitcnt(49279))
k.waitcnt(lgkm=0)
k.emit(buffer_store_dwordx4(v[16:19], v[195 + i * 1], s[4:7], 0, 0, 1))
k.emit(ds_read_b128(v[16:19], v[4], v[0], v[0], 0, 0, 4 + i * 4))
k.emit(s_waitcnt(49279))
k.waitcnt(lgkm=0)
k.emit(buffer_store_dwordx4(v[16:19], v[218], s[4:7], 0, 0, 1))
k.emit(s_waitcnt())
k.waitcnt(lgkm=0, vm=0, exp=0)
k.emit(s_endpgm())
elif (tile_m, tile_n) == (256, 256):
k.emit(s_add_u32(s[55], s[44], LIT, 255))
@@ -2222,20 +2225,20 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_lshr_b32(s[52], s[55], 8))
k.emit(s_lshl_b32(s[52], s[52], 5))
k.emit(s_mov_b32(s[49], 0))
k.label('L2_00E8')
k.label('T256_REMAP_STRIP_LOOP')
k.emit(s_cmp_lt_i32(s[48], s[52]))
k.emit(s_cbranch_scc1(3), target='L2_00FC')
k.emit(s_cbranch_scc1(), target='T256_REMAP_STRIP_DONE')
k.emit(s_sub_i32(s[48], s[48], s[52]))
k.emit(s_add_i32(s[49], s[49], 32))
k.emit(s_branch(65531), target='L2_00E8')
k.label('L2_00FC')
k.emit(s_branch(), target='T256_REMAP_STRIP_LOOP')
k.label('T256_REMAP_STRIP_DONE')
k.emit(s_sub_i32(s[54], s[54], s[49]))
k.emit(s_cmp_lt_i32(s[54], 32))
k.emit(s_cbranch_scc1(3), target='L2_0114')
k.emit(s_cbranch_scc1(), target='T256_REMAP_SMALL_STRIP')
k.emit(s_lshr_b32(s[47], s[48], 5))
k.emit(s_and_b32(s[52], s[48], 31))
k.emit(s_branch(32), target='L2_0194')
k.label('L2_0114')
k.emit(s_branch(), target='T256_REMAP_DONE')
k.label('T256_REMAP_SMALL_STRIP')
k.emit(v_cvt_f32_u32_e32(v[4], s[54]))
k.emit(s_sub_i32(s[47], 0, s[54]))
k.emit(v_rcp_iflag_f32_e32(v[4], v[4]))
@@ -2263,7 +2266,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_nop(3))
k.emit(s_mul_i32(s[52], s[54], s[47]))
k.emit(s_sub_i32(s[52], s[48], s[52]))
k.label('L2_0194')
k.label('T256_REMAP_DONE')
k.emit(s_add_i32(s[49], s[52], s[49]))
k.emit(s_mov_b32(s[6], -16))
k.emit(s_mov_b32(s[10], -16))
@@ -2447,7 +2450,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_sub_u32(s[14 + i * 8], s[14 + i * 8], s[61 + i * 2]))
for i in range(8):
k.emit(v_accvgpr_write(v[248 + i * 1], 0))
k.emit(s_waitcnt(20345))
k.waitcnt(vm=25)
k.emit(s_barrier())
k.emit(ds_read_b128(v[8:11], v[220]))
k.emit(ds_read_b128(v[40:43], v[220], v[0], v[0], 0, 64))
@@ -2497,9 +2500,9 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_cselect_b32(s[61 + i * 1], s[61 + i * 1], 0))
k.emit(s_cselect_b32(s[63 + i * 1], s[63 + i * 1], 0))
k.emit(s_cmp_lt_i32(s[46], 2))
k.emit(s_cbranch_scc0(1367), target='L2_25B8')
k.label('L2_105C')
k.emit(s_waitcnt(122))
k.emit(s_cbranch_scc0(), target='T256_LOOP_LDS1_INIT')
k.label('T256_LOOP_LDS0')
k.waitcnt(lgkm=0, vm=10)
k.emit(v_mfma_fp4(v[0:3], v[136:139], v[8:11], 0, 0, v[208], v[200]))
k.emit(s_barrier())
k.emit(s_nop())
@@ -2571,7 +2574,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[92:95], v[160:163], v[68:71], 2, 3, v[209], v[203]))
k.emit(v_mfma_fp4(v[120:123], v[164:167], v[64:67], 1, 3, v[209], v[203]))
k.emit(v_mfma_fp4(v[124:127], v[164:167], v[68:71], 3, 3, v[209], v[203]))
k.emit(s_waitcnt(127))
k.waitcnt(lgkm=0, vm=15)
k.emit(v_mfma_fp4(v[128:131], v[136:139], v[72:75], 0, 0, v[208], v[204]))
k.emit(s_barrier())
k.emit(s_nop())
@@ -2666,8 +2669,8 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_cmp_lt_i32(s[50], s[51]))
k.emit(v_mfma_fp4(v[248:251], v[164:167], v[128:131], 1, 3, v[209], v[207]))
k.emit(v_mfma_fp4(v[252:255], v[164:167], v[132:135], 3, 3, v[209], v[207]))
k.emit(s_cbranch_scc0(2051), target='L2_3B10')
k.emit(s_waitcnt(122))
k.emit(s_cbranch_scc0(), target='T256_EPILOGUE')
k.waitcnt(lgkm=0, vm=10)
k.emit(v_mfma_fp4(v[0:3], v[168:171], v[8:11], 0, 0, v[210], v[200]))
k.emit(s_barrier())
k.emit(s_nop())
@@ -2739,7 +2742,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[92:95], v[192:195], v[68:71], 2, 3, v[211], v[203]))
k.emit(v_mfma_fp4(v[120:123], v[196:199], v[64:67], 1, 3, v[211], v[203]))
k.emit(v_mfma_fp4(v[124:127], v[196:199], v[68:71], 3, 3, v[211], v[203]))
k.emit(s_waitcnt(127))
k.waitcnt(lgkm=0, vm=15)
k.emit(v_mfma_fp4(v[128:131], v[168:171], v[72:75], 0, 0, v[210], v[204]))
k.emit(s_barrier())
k.emit(s_nop())
@@ -2834,12 +2837,12 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_cmp_lt_i32(s[50], s[51]))
k.emit(v_mfma_fp4(v[248:251], v[196:199], v[128:131], 1, 3, v[211], v[207]))
k.emit(v_mfma_fp4(v[252:255], v[196:199], v[132:135], 3, 3, v[211], v[207]))
k.emit(s_cbranch_scc0(1367), target='L2_3B10')
k.emit(s_branch(64169), target='L2_105C')
k.label('L2_25B8')
k.emit(s_cbranch_scc0(), target='T256_EPILOGUE')
k.emit(s_branch(), target='T256_LOOP_LDS0')
k.label('T256_LOOP_LDS1_INIT')
k.emit(s_nop())
k.label('L2_25BC')
k.emit(s_waitcnt(122))
k.label('T256_LOOP_LDS1')
k.waitcnt(lgkm=0, vm=10)
k.emit(v_mfma_fp4(v[0:3], v[136:139], v[8:11], 0, 0, v[208], v[200]))
k.emit(s_barrier())
k.emit(s_nop())
@@ -2911,7 +2914,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[92:95], v[160:163], v[68:71], 2, 3, v[209], v[203]))
k.emit(v_mfma_fp4(v[120:123], v[164:167], v[64:67], 1, 3, v[209], v[203]))
k.emit(v_mfma_fp4(v[124:127], v[164:167], v[68:71], 3, 3, v[209], v[203]))
k.emit(s_waitcnt(127))
k.waitcnt(lgkm=0, vm=15)
k.emit(v_mfma_fp4(v[128:131], v[136:139], v[72:75], 0, 0, v[208], v[204]))
k.emit(s_barrier())
k.emit(s_nop())
@@ -3006,8 +3009,8 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_cmp_lt_i32(s[50], s[51]))
k.emit(v_mfma_fp4(v[248:251], v[164:167], v[128:131], 1, 3, v[209], v[207]))
k.emit(v_mfma_fp4(v[252:255], v[164:167], v[132:135], 3, 3, v[209], v[207]))
k.emit(s_cbranch_scc0(684), target='L2_3B10')
k.emit(s_waitcnt(122))
k.emit(s_cbranch_scc0(), target='T256_EPILOGUE')
k.waitcnt(lgkm=0, vm=10)
k.emit(v_mfma_fp4(v[0:3], v[168:171], v[8:11], 0, 0, v[210], v[200]))
k.emit(s_barrier())
k.emit(s_nop())
@@ -3079,7 +3082,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(v_mfma_fp4(v[92:95], v[192:195], v[68:71], 2, 3, v[211], v[203]))
k.emit(v_mfma_fp4(v[120:123], v[196:199], v[64:67], 1, 3, v[211], v[203]))
k.emit(v_mfma_fp4(v[124:127], v[196:199], v[68:71], 3, 3, v[211], v[203]))
k.emit(s_waitcnt(127))
k.waitcnt(lgkm=0, vm=15)
k.emit(v_mfma_fp4(v[128:131], v[168:171], v[72:75], 0, 0, v[210], v[204]))
k.emit(s_barrier())
k.emit(s_nop())
@@ -3173,10 +3176,10 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_cmp_lt_i32(s[50], s[51]))
k.emit(v_mfma_fp4(v[248:251], v[196:199], v[128:131], 1, 3, v[211], v[207]))
k.emit(v_mfma_fp4(v[252:255], v[196:199], v[132:135], 3, 3, v[211], v[207]))
k.emit(s_cbranch_scc0(1), target='L2_3B10')
k.emit(s_branch(64171), target='L2_25BC')
k.label('L2_3B10')
k.emit(s_waitcnt(112))
k.emit(s_cbranch_scc0(), target='T256_EPILOGUE')
k.emit(s_branch(), target='T256_LOOP_LDS1')
k.label('T256_EPILOGUE')
k.waitcnt(lgkm=0, vm=0)
k.emit(s_barrier())
for i in range(2):
for j94 in range(4):
@@ -3391,7 +3394,7 @@ def build_kernel(M: int, N: int, K: int, tile_m: int, tile_n: int):
k.emit(s_nop(1))
k.emit(buffer_store_dwordx4(v[16:19], v[250], s[4:7], 0, 0, 1))
k.emit(v_add_i32(v[250], v[250], 64))
k.emit(s_waitcnt())
k.waitcnt(lgkm=0, vm=0, exp=0)
k.emit(s_endpgm())
else:
raise AssertionError(f'unsupported tile {(tile_m, tile_n)}')
+1 -1
View File
@@ -18,7 +18,7 @@ def hand_spec_tc_cores():
a = UOp.placeholder((N, N), dtypes.float, slot=1).reshape((N//8, 8, N//8, 8))
b = UOp.placeholder((N, N), dtypes.float, slot=2).reshape((N//8, 8, N//8, 8))
gk = UOp.range(N // 8, 0, AxisType.REDUCE)
gk = UOp.range(N // 8, 0, AxisType.LOOP)
a_tc = UOp.stack(*[mat_idx(a, gx, gk, warp, i) for i in range(2)])
b_tc = UOp.stack(*[mat_idx(b, gk, gy, warp, i) for i in range(2)])
@@ -111,7 +111,7 @@ def custom_gemm(C:UOp, A:UOp, B:UOp) -> UOp:
return Asl.after(barrier), Bsl.after(barrier)
def compute_on_locals(acc:UOp, Asl:UOp, Bsl:UOp, rng:int, afters:tuple[UOp, ...]=()) -> UOp:
K_inner_loop = UOp.range(BLOCK_K//TC_K, rng, AxisType.REDUCE)
K_inner_loop = UOp.range(BLOCK_K//TC_K, rng, AxisType.LOOP)
# load from locals into registers
Ar = UOp.placeholder((BLOCK_M//TC_M//WARPGROUP_SIZE,), dtypes.half, slot=1, addrspace=AddrSpace.REG)
@@ -150,7 +150,7 @@ def custom_gemm(C:UOp, A:UOp, B:UOp) -> UOp:
if not getenv("PIPELINE"):
As, Bs = make_locals(slot=0)
K_outer_loop = UOp.range(K//BLOCK_K, 0, AxisType.REDUCE)
K_outer_loop = UOp.range(K//BLOCK_K, 0, AxisType.LOOP)
As, Bs = load_to_locals(K_outer_loop, As, Bs, 1000, barrier=True)
acc_store = compute_on_locals(acc, As, Bs, 1500, afters=(K_outer_loop,))
acc = acc.after(acc_store.barrier().end(K_outer_loop))
@@ -160,7 +160,7 @@ def custom_gemm(C:UOp, A:UOp, B:UOp) -> UOp:
As1, Bs1 = make_locals(slot=2)
As0, Bs0 = load_to_locals(0, As0, Bs0, 1000)
K_outer_loop = UOp.range((K//BLOCK_K-2)//2, 0, AxisType.REDUCE)
K_outer_loop = UOp.range((K//BLOCK_K-2)//2, 0, AxisType.LOOP)
As1, Bs1 = load_to_locals(K_outer_loop+1, As1, Bs1, 2000, barrier=False)
acc_store = compute_on_locals(acc, As0, Bs0, 1500, afters=(K_outer_loop,))
As0, Bs0 = load_to_locals(K_outer_loop+2, As0, Bs0, 3000, barrier=False)
@@ -34,7 +34,7 @@ def copy(dest:UOp, src:UOp, rng:int, set=False, upcast=()):
return dest.after(copy) if set else copy
def compute_on_locals(acc:UOp, Asl:UOp, Bsl:UOp, rng:int, afters:tuple[UOp, ...], warpgroup, warp) -> UOp:
K_inner_loop = UOp.range(BLOCK_K//TC_K, rng, AxisType.REDUCE)
K_inner_loop = UOp.range(BLOCK_K//TC_K, rng, AxisType.LOOP)
# load from locals into registers
Ar = UOp.placeholder((BLOCK_M//TC_M//WARPGROUP_SIZE,), dtypes.half, slot=1, addrspace=AddrSpace.REG)
@@ -68,7 +68,7 @@ def compute_on_locals(acc:UOp, Asl:UOp, Bsl:UOp, rng:int, afters:tuple[UOp, ...]
def custom_gemm(C:UOp, A:UOp, B:UOp) -> UOp:
gx, gy = UOp.special(M//BLOCK_M, "gidx0"), UOp.special(N//BLOCK_N, "gidx1")
K_outer_loop = UOp.range(K//BLOCK_K, 0, AxisType.REDUCE)
K_outer_loop = UOp.range(K//BLOCK_K, 0, AxisType.LOOP)
# split out the globals into blocks
C = C.src[0].cast(dtypes.float).reshape((M//BLOCK_M, BLOCK_M, N//BLOCK_N, BLOCK_N))
@@ -114,7 +114,7 @@ def custom_gemm(C:UOp, A:UOp, B:UOp) -> UOp:
C_view = C[gx, :, gy, :].reshape(BLOCK_M//TC_M//WARPGROUP_SIZE, WARPGROUP_SIZE, TC_M, BLOCK_N//TC_N, TC_N)[:, warpgroup, warp%16, :, (warp//16)*4]
sink = copy(C_view, acc.after(sink), rng=300)
else:
sink = C.after(barrier.end(K_outer_loop))[0,0,0,0].store(As[0,0]+Bs[0,0])
sink = C.after(barrier.end(K_outer_loop))[0,0,0,0].store((As[0,0]+Bs[0,0]).float())
return sink.sink(arg=KernelInfo(name="custom_gemm", opts_to_apply=())).simplify()
+20 -11
View File
@@ -4,7 +4,7 @@ from tinygrad.uop.ops import UOp, Ops, KernelInfo, AxisType
from tinygrad.helpers import getenv
from tinygrad.renderer import Estimates
from tinygrad.runtime.support.compiler_amd import HIPCCCompiler
from extra.gemm.cdna_asm_gemm import quantize_mxfp8, _mx_block_scale, _mx_block_scale_3d
from extra.gemm.cdna_asm_gemm import quantize_mxfp8, mx_pack, _mx_block_scale, _mx_block_scale_3d
ZERO_OPTIM = getenv("ZERO_OPTIM", 0)
@@ -95,31 +95,40 @@ def custom_grouped_mx_gemm_bw(gradient:UOp, kernel:UOp, w_stored:bool=False) ->
bq = Tensor(inputs[2], device=inputs[2].device)
ae8 = Tensor(inputs[5], device=inputs[5].device)
be8 = Tensor(inputs[6], device=inputs[6].device)
E, N = bq.shape[0], bq.shape[1]
M, K = aq.shape
g = Tensor(gradient, device=aq.device).reshape(M, N).cast(dtypes.bfloat16)
expert_off = Tensor(inputs[7], device=inputs[7].device)
grad_xq, grad_wq = grouped_mx_gemm_backward(Tensor(gradient), aq, ae8, bq, be8, expert_off, w_stored=w_stored)
return (None, grad_xq.uop, grad_wq.uop) + tuple(None for _ in inputs[3:])
def grouped_mx_gemm_backward(g:Tensor, aq:Tensor, ae8:Tensor, bq:Tensor, be8:Tensor, expert_off:Tensor, *,
w_stored:bool=False, quantized_gradient:tuple[Tensor, Tensor]|None=None) -> tuple[Tensor, Tensor]:
E, N = bq.shape[:2]
g = g.reshape(aq.shape[0], N).cast(dtypes.bfloat16)
x_phys = (aq.cast(dtypes.bfloat16) * _mx_block_scale(ae8).cast(dtypes.bfloat16))
w_phys = (bq.cast(dtypes.bfloat16) * _mx_block_scale_3d(be8).cast(dtypes.bfloat16))
expert_off = Tensor(inputs[7], device=inputs[7].device)
grad_x = grouped_mx_gemm(g, w_phys.transpose(1, 2), expert_off)
grad_x = grouped_mx_gemm(g if quantized_gradient is None else quantized_gradient, w_phys.transpose(1, 2), expert_off)
grad_w = grouped_mx_wgrad(g, x_phys, expert_off, E)
grad_xq = grad_x * _mx_block_scale(ae8).cast(dtypes.bfloat16)
grad_wq = grad_w.contiguous() if w_stored else (grad_w * _mx_block_scale_3d(be8).cast(dtypes.bfloat16)).contiguous()
return (None, grad_xq.uop, grad_wq.uop) + tuple(None for _ in inputs[3:])
return grad_xq, grad_wq
_grouped_bw_stored = functools.partial(custom_grouped_mx_gemm_bw, w_stored=True)
def grouped_mx_gemm(x:Tensor, w:Tensor|tuple[Tensor, Tensor], expert_off:Tensor) -> Tensor:
def grouped_mx_gemm(x:Tensor|tuple[Tensor, Tensor], w:Tensor|tuple[Tensor, Tensor], expert_off:Tensor) -> Tensor:
if isinstance(x, tuple):
x_q, x_e8 = x
x_si, x = mx_pack(x_e8), x_q
else:
x_q, x_e8, x_si = quantize_mxfp8(x)
if (pre_quantized := isinstance(w, tuple)):
w_q, w_e8 = w
E, N, K2 = w_q.shape
else:
E, N, K2 = w.shape
M, K = x.shape
assert K == K2, f"shape mismatch {x.shape} {w.shape}"
assert M % 256 == 0 and N % 256 == 0 and K % 128 == 0, f"grouped mxfp8 needs M%256,N%256,K%128, got {x.shape} {w.shape}"
assert K == K2, f"shape mismatch K {K} != {K2}"
assert x_e8.shape == (M, K // 32)
assert M % 256 == 0 and N % 256 == 0 and K % 128 == 0, f"grouped mxfp8 needs M%256,N%256,K%128, got {M,K,N}"
dname = (x.device[0] if isinstance(x.device, tuple) else x.device).split(":")[0]
x_q, x_e8, x_si = quantize_mxfp8(x)
if not pre_quantized: w_q, w_e8, _ = quantize_mxfp8(w)
w_si = mx_pack_3d(w_e8)
xe_in, out_shape = x_e8.reshape(M, K // 32), (M, N)
+40 -7
View File
@@ -123,8 +123,14 @@ def _gscatter_bwd(gradient:UOp, kernel:UOp) -> tuple:
dev = src_u.device
G, T_l, D = src_u.shape
k = idx_u.shape[1] // T_l
if getenv("GGATHER_SUM_HIP", 0):
from extra.gptoss_kernels.gather_sum import gather_sum
assert k == 4
return None, gather_sum(Tensor(gradient), Tensor(idx_u)).uop, None
sel = grouped_gather_rows(Tensor(gradient, device=dev), Tensor(idx_u, device=dev), G)
return (None, sel.reshape(G, T_l, k, D).sum(2).cast(src_u.dtype).uop, None)
# FP8 dispatch carries BF16 gradients back to the quantizer, not FP8-rounded gradients.
grad_dtype = dtypes.bfloat16 if src_u.dtype == dtypes.fp8e4m3 else src_u.dtype
return (None, sel.reshape(G, T_l, k, D).sum(2).cast(grad_dtype).uop, None)
def grouped_scatter_rows(src:Tensor, idx:Tensor, m_l:int) -> Tensor:
G, T_l, D = src.shape
@@ -135,9 +141,12 @@ def m_max_for(t_local:int, experts_per_tok:int, n_experts:int) -> int:
return (-(-t_local * experts_per_tok // BLOCK_ROW) + n_experts) * BLOCK_ROW
class Routing:
def __init__(self, weights:Tensor, dest_row:Tensor, off:Tensor, m_l:int, n_groups:int, t_local:int):
def __init__(self, weights:Tensor, dest_row:Tensor, off:Tensor, m_l:int, n_groups:int, t_local:int,
topi:Tensor|None=None, counts:Tensor|None=None):
self.weights, self.dest_row = weights, dest_row
self.off = off
self.topi = topi
self.counts = counts
self.m_l, self.n_groups, self.t_local = m_l, n_groups, t_local
@property
@@ -158,22 +167,46 @@ def route(logits:Tensor, experts_per_tok:int, n_experts:int) -> Routing:
T, E = logits.shape
k, G = experts_per_tok, n_groups_of(logits)
assert T % G == 0, f"tokens {T} must split across {G} devices"
T_l, m_l = T // G, m_max_for(T // G, k, n_experts)
T_l = T // G
topv, topi = logits.reshape(G, T_l, E).topk(k)
weights = topv.softmax(-1)
if getenv("FUSED_ROUTER_TOPK", 0):
from extra.gptoss_kernels.router_topk import fused_router_topk
weights, topi = fused_router_topk(logits.reshape(G, T_l, E))
else:
topv, topi = logits.reshape(G, T_l, E).topk(k)
weights = topv.softmax(-1)
return route_topk(weights, topi, n_experts)
def route_topk(weights:Tensor, topi:Tensor, n_experts:int) -> Routing:
G, T_l, k = weights.shape
E, m_l = n_experts, m_max_for(T_l, k, n_experts)
m = topi.reshape(G, T_l * k).cast(dtypes.int32).one_hot(E).cast(dtypes.int32)
pad = ((m.sum(1) + (BLOCK_ROW - 1)) // BLOCK_ROW) * BLOCK_ROW
counts = m.sum(1)
pad = ((counts + (BLOCK_ROW - 1)) // BLOCK_ROW) * BLOCK_ROW
off = pad.cumsum(1).pad(((0, 0), (1, 0)))
dest_row = ((m.cumsum(1) + off[:, :E].reshape(G, 1, E)) * m).sum(-1).sub(1).cast(dtypes.int32)
return Routing(weights, dest_row, off, m_l, G, T_l)
return Routing(weights, dest_row, off, m_l, G, T_l, topi=topi, counts=counts)
def dispatch(x:Tensor, r:Routing) -> Tensor:
G, D = r.n_groups, x.shape[-1]
return grouped_scatter_rows(x.reshape(G, r.t_local, D), r.dest_row, r.m_l).reshape(G * r.m_l, D)
def dispatch_fp8(x:Tensor|tuple[Tensor, Tensor], r:Routing) -> tuple[Tensor, Tensor]:
from extra.gptoss_kernels.quantize_mxfp8 import quantize_mxfp8_fused_qe8
q, e8 = x if isinstance(x, tuple) else quantize_mxfp8_fused_qe8(x)
if getenv("DISPATCH_GATHER", 0):
from extra.gptoss_kernels.dispatch import inverse_rows, dispatch_gather
assert r.counts is not None
src_row = inverse_rows(r.dest_row, r.counts, r.off, r.m_l)
return tuple(dispatch_gather(t.reshape(r.n_groups, r.t_local, -1), r.dest_row, src_row) for t in (q, e8))
return dispatch(q, r), dispatch(e8, r)
def combine(y:Tensor, r:Routing, n_tokens:int, experts_per_tok:int) -> Tensor:
G, D, k = r.n_groups, y.shape[-1], experts_per_tok
if getenv("FUSED_COMBINE", 0):
from extra.gptoss_kernels.combine import fused_combine
assert k == 4
return fused_combine(y.reshape(G, r.m_l, D), r.dest_row, r.weights.reshape(G, r.t_local, k)).reshape(n_tokens, D)
sel = grouped_gather_rows(y.reshape(G, r.m_l, D), r.dest_row, G).reshape(G, r.t_local, k, D)
return (sel * r.weights.reshape(G, r.t_local, k, 1).cast(sel.dtype)).sum(2).reshape(n_tokens, D).cast(y.dtype)
@@ -14,8 +14,8 @@ if __name__ == "__main__":
C = A.matmul(B)
if getenv("GEMV"):
opts = [
Opt(op=OptOps.SPLIT, axis=1, arg=(8, AxisType.UNROLL)),
Opt(op=OptOps.SPLIT, axis=1, arg=(32, AxisType.GROUP_REDUCE)),
Opt(op=OptOps.SPLIT, axis=1, arg=(8, AxisType.UPCAST)),
Opt(op=OptOps.SPLIT, axis=1, arg=(32, AxisType.LOCAL)),
]
else:
opts = [
+1 -1
View File
@@ -38,7 +38,7 @@ def alloc_shared(shape:tuple[int, ...], dtype:DType, slot:int) -> UOp:
def alloc_fragment(shape:tuple[int, ...], dtype:DType, slot:int, axes:tuple[int, ...], rngs:tuple[UOp, ...]) -> UOp:
"""T.alloc_fragment: per-thread REG fragment + UNSHARD over the LOCAL thread grid."""
assert len(axes) == len(rngs)
assert all(tnum.op is Ops.RANGE and tnum.arg[-1] is AxisType.LOCAL for tnum in rngs), "fragments shard over LOCAL ranges"
assert all(tnum.op is Ops.RANGE and tnum.axis_type is AxisType.LOCAL for tnum in rngs), "fragments shard over LOCAL ranges"
by_axis = dict(zip(axes, rngs))
shard_shape = tuple(s // (int(by_axis[i].vmax)+1) if i in by_axis else s for i, s in enumerate(shape))
fragment = UOp.placeholder(shard_shape, dtype, slot, AddrSpace.REG)
@@ -0,0 +1,116 @@
import functools, pathlib
from tinygrad import Tensor, dtypes, function
from tinygrad.uop.ops import UOp, Ops, KernelInfo
from extra.llama_kernels import alloc_like, compile_hip
@functools.cache
def _combine_forward(out:UOp, z:UOp, dest_row:UOp, weights:UOp) -> UOp:
groups, tokens, hidden = out.shape
rows = z.shape[1]
assert hidden == 2880 and z.shape == (groups, rows, hidden)
assert dest_row.shape == (groups, tokens*4) and weights.shape == (groups, tokens, 4)
assert out.dtype == z.dtype == dtypes.bfloat16 and dest_row.dtype == dtypes.int32 and weights.dtype == dtypes.float32
sink = UOp.sink(out.base, z.base, dest_row.base, weights.base, UOp.special(256, "lidx0"),
UOp.special(groups*tokens, "gidx0"), arg=KernelInfo("gptoss_combine_forward"))
src = (pathlib.Path(__file__).parent/"forward.cpp").read_text()
lib = compile_hip(src, [f"-DTOKENS={tokens}", f"-DROWS={rows}", "-fno-fast-math"])
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=lib)))
@functools.cache
def _combine_weights_backward(out:UOp, z:UOp, gradient:UOp, dest_row:UOp) -> UOp:
groups, tokens, k = out.shape
rows = z.shape[1]
assert k == 4 and z.shape == (groups, rows, 2880) and gradient.shape == (groups, tokens, 2880)
assert dest_row.shape == (groups, tokens*4) and dest_row.dtype == dtypes.int32
assert out.dtype == dtypes.float32 and z.dtype == gradient.dtype == dtypes.bfloat16
sink = UOp.sink(out.base, z.base, gradient.base, dest_row.base, UOp.special(256, "lidx0"),
UOp.special(groups*tokens, "gidx0"), arg=KernelInfo("gptoss_combine_weights_backward"))
src = (pathlib.Path(__file__).parent/"weights_backward.cpp").read_text()
lib = compile_hip(src, [f"-DTOKENS={tokens}", f"-DROWS={rows}", "-fno-fast-math"])
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=lib)))
@functools.cache
def _inverse_rows(out:UOp, dest_row:UOp) -> UOp:
groups, entries = dest_row.shape
g, i = UOp.range(groups, 0), UOp.range(entries, 1)
row = dest_row.index(g, i).load().cast(dtypes.weakint)
return out.index(g, row).store(i.cast(dtypes.int32)).end(g, i).sink(arg=KernelInfo("gptoss_combine_inverse", opts_to_apply=()))
@functools.cache
def _combine_input_backward(out:UOp, gradient:UOp, src_row:UOp, weights:UOp) -> UOp:
groups, rows, hidden = out.shape
tokens = gradient.shape[1]
assert hidden == 2880 and gradient.shape == (groups, tokens, hidden)
assert src_row.shape == (groups, rows) and weights.shape == (groups, tokens, 4)
assert out.dtype == gradient.dtype == dtypes.bfloat16 and src_row.dtype == dtypes.int32 and weights.dtype == dtypes.float32
sink = UOp.sink(out.base, gradient.base, src_row.base, weights.base, UOp.special(256, "lidx0"),
UOp.special(groups*rows, "gidx0"), arg=KernelInfo("gptoss_combine_input_backward"))
src = (pathlib.Path(__file__).parent/"input_backward.cpp").read_text()
lib = compile_hip(src, [f"-DTOKENS={tokens}", f"-DROWS={rows}", "-fno-fast-math"])
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=lib)))
def _combine_backward(gradient:UOp, kernel:UOp) -> tuple:
z, dest_row, weights = (Tensor(u) for u in kernel.src[2:5])
src_row = z[:, :, 0].full_like(-1, dtype=dtypes.int32)
src_row = Tensor.custom_kernel(src_row, dest_row, fxn=_inverse_rows)[0]
dz = alloc_like(z.shape, z.dtype, z.device, z.uop.axis)
dz = Tensor.custom_kernel(dz, Tensor(gradient), src_row, weights, fxn=_combine_input_backward)[0]
dw = alloc_like(weights.shape, weights.dtype, weights.device, weights.uop.axis)
dw = Tensor.custom_kernel(dw, z, Tensor(gradient), dest_row, fxn=_combine_weights_backward)[0]
return None, dz.uop, None, dw.uop
def fused_combine(z:Tensor, dest_row:Tensor, weights:Tensor) -> Tensor:
out = alloc_like((*weights.shape[:2], z.shape[-1]), z.dtype, z.device, z.uop.axis)
return Tensor.custom_kernel(out, z, dest_row, weights, fxn=_combine_forward, grad_fxn=_combine_backward)[0]
@functools.cache
def _combine_input_quantize_backward(out:UOp, q:UOp, scales:UOp, gradient:UOp, src_row:UOp, weights:UOp) -> UOp:
groups, rows = src_row.shape
tokens = gradient.shape[1]
assert out.shape == q.shape == (groups*rows, 3072) and scales.shape == (groups*rows, 96)
assert gradient.shape == (groups, tokens, 2880) and weights.shape == (groups, tokens, 4)
assert out.dtype == gradient.dtype == dtypes.bfloat16 and q.dtype == dtypes.fp8e4m3 and scales.dtype == dtypes.uint8
assert src_row.dtype == dtypes.int32 and weights.dtype == dtypes.float32
sink = UOp.sink(out.base, q.base, scales.base, gradient.base, src_row.base, weights.base,
UOp.special(256, "lidx0"), UOp.special(groups*rows, "gidx0"), arg=KernelInfo("gptoss_combine_input_quantize_backward"))
src = (pathlib.Path(__file__).parent/"input_quantize_backward.cpp").read_text()
lib = compile_hip(src, [f"-DTOKENS={tokens}", f"-DROWS={rows}", "-fno-fast-math"])
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=lib)))
def combine_input_quantize_backward(gradient:Tensor, dest_row:Tensor, weights:Tensor, rows:int) -> tuple[Tensor, Tensor, Tensor]:
groups = weights.shape[0]
src_row = alloc_like((groups, rows), dtypes.int32, gradient.device, gradient.uop.axis).full_like(-1)
src_row = Tensor.custom_kernel(src_row, dest_row, fxn=_inverse_rows)[0]
out = alloc_like((groups*rows, 3072), dtypes.bfloat16, gradient.device, gradient.uop.axis)
q = alloc_like(out.shape, dtypes.fp8e4m3, gradient.device, gradient.uop.axis)
scales = alloc_like((groups*rows, 96), dtypes.uint8, gradient.device, gradient.uop.axis)
return tuple(Tensor.custom_kernel(out, q, scales, gradient, src_row, weights, fxn=_combine_input_quantize_backward)[:3])
def _down_bias(bias:Tensor, off:Tensor, rows:int) -> Tensor:
from extra.gemm.moe_routing import Routing, BLOCK_ROW
r = Routing(None, None, off, rows, off.shape[0], 0)
tile_bias = r.tile_e.one_hot(bias.shape[0]).float() @ bias.float()
return tile_bias.reshape(-1, 1, 2880).expand(-1, BLOCK_ROW, -1).reshape(-1, 2880).cast(dtypes.bfloat16)
def _down_combine_backward(gradient:UOp, call:UOp) -> tuple:
from extra.gemm.moe_gemm import grouped_mx_gemm_backward
aq, ae8, bq, be8, off, bias, dest_row, weights = (Tensor(u) for u in call.src[1:9])
_, z = (Tensor(u) for u in call.unbound_outputs)
groups, tokens, _ = weights.shape
rows = aq.shape[0] // groups
g = Tensor(gradient).reshape(groups, tokens, 2880)
dz, q, scales = combine_input_quantize_backward(g, dest_row, weights, rows)
dx, db = grouped_mx_gemm_backward(dz, aq, ae8, bq, be8, off, w_stored=True, quantized_gradient=(q, scales))
dbias, = _down_bias(bias, off, rows).gradient(bias, gradient=dz[:, :2880])
dw = alloc_like(weights.shape, weights.dtype, weights.device, weights.uop.axis)
dw = Tensor.custom_kernel(dw, z.reshape(groups, rows, 2880), g, dest_row, fxn=_combine_weights_backward)[0]
return dx.uop, None, db.uop, None, None, dbias.uop, None, dw.uop
@function(grad_fxn=_down_combine_backward)
def down_combine(aq:Tensor, ae8:Tensor, bq:Tensor, be8:Tensor, off:Tensor, bias:Tensor,
dest_row:Tensor, weights:Tensor) -> tuple[Tensor, Tensor]:
from extra.gemm.moe_gemm import grouped_mx_gemm
groups = weights.shape[0]
rows = aq.shape[0] // groups
z = grouped_mx_gemm((aq, ae8), (bq, be8), off)[:, :2880] + _down_bias(bias, off, rows)
return fused_combine(z.reshape(groups, rows, 2880), dest_row, weights), z
@@ -0,0 +1,25 @@
#include <hip/hip_runtime.h>
#include <hip/hip_bf16.h>
// One workgroup gathers and combines the four expert rows for one token.
extern "C" __global__ __launch_bounds__(256) void gptoss_combine_forward(
__hip_bfloat16 *__restrict__ out, const __hip_bfloat16 *__restrict__ z,
const int *__restrict__ dest_row, const float *__restrict__ weights) {
const int token = blockIdx.x, group = token / TOKENS, tid = threadIdx.x;
__shared__ int rows[4];
__shared__ float ws[4];
if (tid < 4) {
rows[tid] = group * ROWS + dest_row[token * 4 + tid];
ws[tid] = weights[token * 4 + tid];
}
__syncthreads();
for (int d = tid; d < 2880; d += 256) {
float acc = 0.0f;
#pragma unroll
for (int j = 0; j < 4; j++) {
acc = fmaf(ws[j], (float)z[(long long)rows[j] * 2880 + d], acc);
}
out[(long long)token * 2880 + d] = (__hip_bfloat16)acc;
}
}
@@ -0,0 +1,20 @@
#include <hip/hip_runtime.h>
#include <hip/hip_bf16.h>
extern "C" __global__ __launch_bounds__(256) void gptoss_combine_input_backward(
__hip_bfloat16 *__restrict__ out, const __hip_bfloat16 *__restrict__ dout,
const int *__restrict__ src_row, const float *__restrict__ weights) {
const int row = blockIdx.x, group = row / ROWS, tid = threadIdx.x;
const int src = src_row[row];
// Return before forming input addresses for unused grouped rows.
if (src < 0) {
for (int d = tid; d < 2880; d += 256) out[(long long)row * 2880 + d] = (__hip_bfloat16)0.0f;
return;
}
const int token = group * TOKENS + src / 4;
const float weight = weights[token * 4 + src % 4];
for (int d = tid; d < 2880; d += 256) {
out[(long long)row * 2880 + d] = (__hip_bfloat16)((float)dout[(long long)token * 2880 + d] * weight);
}
}
@@ -0,0 +1,56 @@
#include <hip/hip_runtime.h>
#include <hip/hip_bf16.h>
#include <hip/hip_fp8.h>
typedef short short2v __attribute__((ext_vector_type(2)));
typedef unsigned uint4v __attribute__((ext_vector_type(4)));
typedef unsigned short ushort4v __attribute__((ext_vector_type(4)));
extern "C" __global__ __launch_bounds__(256) void gptoss_combine_input_quantize_backward(
__hip_bfloat16 *__restrict__ out, unsigned char *__restrict__ q, unsigned char *__restrict__ scales,
const __hip_bfloat16 *__restrict__ dout, const int *__restrict__ src_row, const float *__restrict__ weights) {
const int row = blockIdx.x, group = row / ROWS, tid = threadIdx.x;
// Four lanes own one MX block, with aligned eight-value loads and stores per lane.
const int src = src_row[row], lane = tid % 4;
for (int block = tid / 4; block < 96; block += 64) {
const int d = block * 32 + lane * 8;
const long long index = (long long)row * 3072 + d;
// Neither padding nor unused rows may read dout or weights.
if (src < 0 || d >= 2880) {
*reinterpret_cast<uint4v*>(&out[index]) = uint4v{};
*reinterpret_cast<ushort4v*>(&q[index]) = ushort4v{};
if (lane == 0) scales[(long long)row * 96 + block] = 0;
continue;
}
const int token = group * TOKENS + src / 4;
const float weight = weights[token * 4 + src % 4];
const uint4v input = *reinterpret_cast<const uint4v*>(&dout[(long long)token * 2880 + d]);
uint4v output;
float2 values[4];
float amax = 0.0f;
#pragma unroll
for (int i = 0; i < 4; i++) {
const unsigned bits = input[i];
const float2 dy = make_float2(__builtin_bit_cast(float, bits << 16), __builtin_bit_cast(float, bits & 0xffff0000u));
const float2 value = __bfloat1622float2(__float22bfloat162_rn(make_float2(dy.x * weight, dy.y * weight)));
values[i] = value;
const __hip_bfloat162 rounded_pair = __float22bfloat162_rn(value);
output[i] = (unsigned)__bfloat16_as_ushort(rounded_pair.x) | ((unsigned)__bfloat16_as_ushort(rounded_pair.y) << 16);
amax = fmaxf(amax, fmaxf(fabsf(value.x), fabsf(value.y)));
}
*reinterpret_cast<uint4v*>(&out[index]) = output;
#pragma unroll
for (int offset = 2; offset > 0; offset >>= 1) amax = fmaxf(amax, __shfl_down(amax, offset, 4));
amax = __shfl(amax, 0, 4);
const unsigned e8 = min((__builtin_bit_cast(unsigned, amax) >> 23) & 255u, 254u);
if (lane == 0) scales[(long long)row * 96 + block] = e8;
ushort4v quantized;
#pragma unroll
for (int i = 0; i < 4; i++) {
short2v packed = {0, 0};
packed = __builtin_amdgcn_cvt_scalef32_pk_fp8_f32(packed, values[i].x, values[i].y, __builtin_bit_cast(float, e8 << 23), false);
quantized[i] = __builtin_bit_cast(unsigned short, packed[0]);
}
*reinterpret_cast<ushort4v*>(&q[index]) = quantized;
}
}
@@ -0,0 +1,44 @@
#include <hip/hip_runtime.h>
#include <hip/hip_bf16.h>
// One workgroup computes all four routing-weight gradients, reusing dout across expert slots.
extern "C" __global__ __launch_bounds__(256) void gptoss_combine_weights_backward(
float *__restrict__ dw, const __hip_bfloat16 *__restrict__ z,
const __hip_bfloat16 *__restrict__ dout, const int *__restrict__ dest_row) {
const int token = blockIdx.x, group = token / TOKENS, tid = threadIdx.x;
float sums[4] = {};
int rows[4];
#pragma unroll
for (int j = 0; j < 4; j++) rows[j] = group * ROWS + dest_row[token * 4 + j];
for (int d = tid; d < 2880; d += 256) {
const float dy = (float)dout[(long long)token * 2880 + d];
#pragma unroll
for (int j = 0; j < 4; j++)
sums[j] = fmaf(dy, (float)z[(long long)rows[j] * 2880 + d], sums[j]);
}
// Reduce each slot across the four wavefronts.
#pragma unroll
for (int offset = 32; offset > 0; offset >>= 1)
#pragma unroll
for (int j = 0; j < 4; j++) sums[j] += __shfl_down(sums[j], offset, 64);
__shared__ float warp_sums[4][4];
const int lane = tid % 64, warp = tid / 64;
#pragma unroll
for (int j = 0; j < 4; j++) if (lane == 0) warp_sums[warp][j] = sums[j];
__syncthreads();
if (warp == 0) {
#pragma unroll
for (int j = 0; j < 4; j++) sums[j] = lane < 4 ? warp_sums[lane][j] : 0.0f;
#pragma unroll
for (int offset = 32; offset > 0; offset >>= 1)
#pragma unroll
for (int j = 0; j < 4; j++) sums[j] += __shfl_down(sums[j], offset, 64);
if (lane == 0)
#pragma unroll
for (int j = 0; j < 4; j++) dw[token * 4 + j] = sums[j];
}
}
@@ -0,0 +1,48 @@
import functools, pathlib
from tinygrad import Tensor, dtypes
from tinygrad.uop.ops import UOp, Ops, KernelInfo, AxisType
from extra.llama_kernels import alloc_like, compile_hip
@functools.cache
def _inverse_rows(out:UOp, dest_row:UOp, counts:UOp, off:UOp) -> UOp:
groups, rows = out.shape
entries, experts = dest_row.shape[1], counts.shape[1]
assert rows >= entries and counts.shape == (groups, experts) and off.shape == (groups, experts+1)
sink = UOp.sink(out.base, dest_row.base, counts.base, off.base, UOp.special(256, "lidx0"),
UOp.special(groups*((rows+255)//256), "gidx0"), arg=KernelInfo(f"dispatch_inverse_{groups}_{rows}_{entries}_{experts}"))
src = (pathlib.Path(__file__).parent/"inverse.cpp").read_text()
lib = compile_hip(src, [f"-DROWS={rows}", f"-DENTRIES={entries}", f"-DEXPERTS={experts}"])
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=lib)))
def inverse_rows(dest_row:Tensor, counts:Tensor, off:Tensor, rows:int) -> Tensor:
out = alloc_like((dest_row.shape[0], rows), dtypes.int32, dest_row.device, dest_row.uop.axis)
return Tensor.custom_kernel(out, dest_row, counts, off, fxn=_inverse_rows)[0]
@functools.cache
def _dispatch_gather(out:UOp, x:UOp, dest_row:UOp, src_row:UOp) -> UOp:
from extra.gemm.moe_routing import _blk_for
groups, tokens, hidden = x.shape
rows = src_row.shape[1]
assert out.shape == (groups*rows, hidden) and dest_row.shape == (groups, tokens*4)
assert x.dtype == out.dtype and x.dtype.itemsize == 1
block = _blk_for(hidden)
g, row = UOp.range(groups, 0, AxisType.GLOBAL), UOp.range(rows, 1, AxisType.GLOBAL)
outer, inner = UOp.range(hidden//block, 2), UOp.range(block, 3, AxisType.LOCAL)
col = outer*block + inner
source = src_row.index(g, row).load().cast(dtypes.weakint)
anchor = dest_row.index(g, 0).load() >= 0
valid = (source >= 0) & anchor
token = valid.where(source, source.const_like(0)) // 4
value = valid.where(x.index(g, token, col).load(), UOp.const(0, x.dtype))
return out.reshape(groups, rows, hidden).index(g, row, col).store(value).end(g, row, outer, inner).sink(
arg=KernelInfo(f"dispatch_gather_{groups}_{rows}_{tokens}_{hidden}", opts_to_apply=()))
def _dispatch_gather_bwd(gradient:UOp, kernel:UOp) -> tuple:
from extra.gemm.moe_routing import _gscatter_bwd
# The first three arguments match scatter's backward; src_row is forward-only.
x = kernel.src[2]
return (*_gscatter_bwd(gradient.reshape(x.shape[0], -1, x.shape[-1]), kernel), None)
def dispatch_gather(x:Tensor, dest_row:Tensor, src_row:Tensor) -> Tensor:
out = alloc_like((x.shape[0]*src_row.shape[1], x.shape[-1]), x.dtype, x.device, x.uop.axis)
return Tensor.custom_kernel(out, x, dest_row, src_row, fxn=_dispatch_gather, grad_fxn=_dispatch_gather_bwd)[0]
@@ -0,0 +1,13 @@
#include <hip/hip_runtime.h>
// Copy FP8/scale bytes unchanged; every output is written, including padded rows.
// dest is retained as a saved backward input, but forward uses only src_row.
extern "C" __global__ __launch_bounds__(64) void dispatch_gather(
unsigned char* __restrict__ out, const unsigned char* __restrict__ x,
const int* __restrict__ dest, const int* __restrict__ src_row) {
const int row = blockIdx.x;
const int source = src_row[row];
const long long token = (long long)(row / ROWS) * TOKENS + (source >= 0 ? source / 4 : 0);
for (int d = threadIdx.x; d < HIDDEN; d += 64)
out[(long long)row * HIDDEN + d] = source >= 0 ? x[token * HIDDEN + d] : 0;
}
@@ -0,0 +1,16 @@
#include <hip/hip_runtime.h>
// Valid inverse writes and padding writes are disjoint, so this needs no initialization pass or grid barrier.
extern "C" __global__ __launch_bounds__(256) void dispatch_inverse(
int* __restrict__ out, const int* __restrict__ dest, const int* __restrict__ counts, const int* __restrict__ off) {
const int group = blockIdx.x / ((ROWS + 255) / 256);
const int i = (blockIdx.x % ((ROWS + 255) / 256)) * 256 + threadIdx.x;
if (i < ENTRIES) out[(long long)group * ROWS + dest[(long long)group * ENTRIES + i]] = i;
if (i < ROWS) {
int expert = 0;
#pragma unroll
for (int e = 1; e < EXPERTS; e++) expert += i >= off[group * (EXPERTS + 1) + e];
if (i >= off[group * (EXPERTS + 1) + expert] + counts[group * EXPERTS + expert])
out[(long long)group * ROWS + i] = -1;
}
}
@@ -0,0 +1,24 @@
import functools, pathlib
from tinygrad import Tensor, dtypes
from tinygrad.uop.ops import UOp, Ops, KernelInfo
from extra.llama_kernels import alloc_like, compile_hip
@functools.cache
def _gather_sum(out:UOp, table:UOp, indices:UOp) -> UOp:
groups, tokens, hidden = out.shape
rows = table.shape[1]
assert table.shape == (groups, rows, hidden) and indices.shape == (groups, tokens*4)
assert out.dtype == table.dtype == dtypes.bfloat16 and indices.dtype == dtypes.int32
assert hidden % 64 == 0
sink = UOp.sink(out.base, table.base, indices.base, UOp.special(64, "lidx0"), UOp.special(groups*tokens, "gidx0"),
arg=KernelInfo(f"gather_sum_{groups}_{tokens}_{rows}_{hidden}"))
src = (pathlib.Path(__file__).parent/"gather_sum.cpp").read_text()
lib = compile_hip(src, [f"-DTOKENS={tokens}", f"-DROWS={rows}", f"-DHIDDEN={hidden}", "-fno-fast-math"])
return UOp(Ops.PROGRAM,
src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=lib)))
def gather_sum(table:Tensor, indices:Tensor) -> Tensor:
groups, _, hidden = table.shape
assert indices.shape[0] == groups and indices.shape[1] % 4 == 0
out = alloc_like((groups, indices.shape[1]//4, hidden), table.dtype, table.device, table.uop.axis)
return Tensor.custom_kernel(out, table, indices, fxn=_gather_sum)[0]
@@ -0,0 +1,23 @@
#include <hip/hip_runtime.h>
#include <hip/hip_bf16.h>
// One wave per token: gather its four expert gradients and write only their sum.
extern "C" __global__ __launch_bounds__(64) void gather_sum(
__hip_bfloat16* __restrict__ out, const __hip_bfloat16* __restrict__ table, const int* __restrict__ indices) {
#pragma clang fp reassociate(off)
const int token = blockIdx.x;
const long long group_base = (long long)(token / TOKENS) * ROWS * HIDDEN;
const int4 rows = *reinterpret_cast<const int4*>(&indices[(long long)token * 4]);
const long long row0 = group_base + (long long)rows.x * HIDDEN;
const long long row1 = group_base + (long long)rows.y * HIDDEN;
const long long row2 = group_base + (long long)rows.z * HIDDEN;
const long long row3 = group_base + (long long)rows.w * HIDDEN;
#pragma unroll 2
for (int d = threadIdx.x; d < HIDDEN; d += 64) {
float sum = (float)table[row0 + d];
sum = sum + (float)table[row1 + d];
sum = sum + (float)table[row2 + d];
sum = sum + (float)table[row3 + d];
out[(long long)token * HIDDEN + d] = (__hip_bfloat16)sum;
}
}
@@ -0,0 +1,42 @@
import functools, pathlib
from tinygrad import Tensor, dtypes
from tinygrad.uop.ops import UOp, Ops, KernelInfo
from extra.llama_kernels import alloc_like, compile_hip
@functools.cache
def _forward(q:UOp, k:UOp, v:UOp, xqkv:UOp, freqs:UOp) -> UOp:
assert xqkv.shape == (2, 8192, 5120) and q.shape == (2, 8192, 64, 64) and k.shape == v.shape == (2, 8192, 8, 64)
sink = UOp.sink(q.base, k.base, v.base, xqkv.base, freqs.base,
UOp.special(256, "lidx0"), UOp.special(2, "gidx0"), UOp.special(8192, "gidx1"),
arg=KernelInfo("fused_qkv_rope_forward_gptoss_packed"))
src = (pathlib.Path(__file__).parent/"forward.cpp").read_text()
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src),
UOp(Ops.BINARY, arg=compile_hip(src, []))))
@functools.cache
def _backward_kernel(out:UOp, dq:UOp, dk:UOp, dv:UOp, freqs:UOp) -> UOp:
assert out.shape == (2, 8192, 5120) and dq.shape == (2, 64, 8192, 64) and dk.shape == dv.shape == (16, 8192, 8, 64)
sink = UOp.sink(out.base, dq.base, dk.base, dv.base, freqs.base,
UOp.special(256, "lidx0"), UOp.special(2, "gidx0"), UOp.special(128, "gidx1"), UOp.special(80, "gidx2"),
arg=KernelInfo("fused_qkv_rope_backward_gptoss"))
src = (pathlib.Path(__file__).parent/"backward.cpp").read_text()
include = pathlib.Path(__file__).parents[2]/"thunder"/"amd"/"include"
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src),
UOp(Ops.BINARY, arg=compile_hip(src, [f"-I{include}", "-DKITTENS_CDNA4", "-DHIP_ENABLE_WARP_SYNC_BUILTINS"]))))
def _backward(dq:UOp, dk:UOp, dv:UOp, call:UOp) -> tuple:
from extra.thunder.amd.fa import _fa_native_grads
# Consume FA's shuffled dQ and unreduced dK/dV directly, without materializing their logical views.
native = _fa_native_grads(dq, dk, dv)
assert native is not None, "GPT-OSS QKV/RoPE backward requires native Flash Attention gradients"
xqkv, freqs = (Tensor(u) for u in call.src[4:6])
out = alloc_like(xqkv.shape, xqkv.dtype, xqkv.device, xqkv.uop.axis)
out, *_ = Tensor.custom_kernel(out, *(Tensor(u) for u in native), freqs, fxn=_backward_kernel)
return None, None, None, out.uop, None
def fused_qkv_rope(xqkv:Tensor, freqs:Tensor) -> tuple[Tensor, Tensor, Tensor]:
batch, seq, hidden = xqkv.shape
assert hidden == 5120 and freqs.shape == (1, seq, 1, 32, 2) and xqkv.dtype == freqs.dtype == dtypes.bfloat16
q, k, v = (alloc_like((batch, seq, heads, 64), dtypes.bfloat16, xqkv.device, xqkv.uop.axis) for heads in (64, 8, 8))
q, k, v, *_ = Tensor.custom_kernel(q, k, v, xqkv, freqs, fxn=_forward, grad_fxn=_backward)
return q, k, v
@@ -0,0 +1,168 @@
#include "kittens.cuh"
using namespace kittens;
#ifndef ATTN_B
#define ATTN_B 2
#endif
#ifndef ATTN_N
#define ATTN_N 8192
#endif
#ifndef ATTN_H
#define ATTN_H 64
#endif
#ifndef ATTN_H_KV
#define ATTN_H_KV 8
#endif
#ifndef ATTN_D
#define ATTN_D 64
#endif
#ifndef THREADS_PER_BLOCK
#define THREADS_PER_BLOCK 256
#endif
constexpr int GROUP_SIZE = ATTN_H / ATTN_H_KV;
constexpr int HALF_D = ATTN_D / 2;
constexpr int PACKED_H = ATTN_H_KV * (GROUP_SIZE + 2);
// The current D=64 FA emits one dK/dV partial per query head in each GQA group.
constexpr int KV_PARTIALS = GROUP_SIZE;
constexpr int Q_HEADS_PER_WG = 1;
constexpr int Q_FIELDS = ATTN_H / Q_HEADS_PER_WG;
constexpr int NUM_WARPS = 4;
constexpr int TILE_N = 16;
template<typename T> using grad_tile = rt<T, TILE_N, ATTN_D, row_l, rt_16x32_s>;
template<int axis, ducks::rt::row_layout RT, ducks::gl::all GL, ducks::coord::tile COORD=coord<RT>>
__device__ __forceinline__ void load_fa_shuffled(RT &dst, const GL &src, const COORD &idx) {
using U = typename GL::dtype;
using U2 = base_types::packing<U>::packed_type;
U *src_ptr = (U*)&src[(idx.template unit_coord<axis, 3>())];
const int row_stride = src.template stride<axis>();
const int lane = kittens::laneid();
const int tile_row_stride = row_stride * dst.base_tile_rows;
const int tile_stride = dst.base_tile_rows * dst.base_tile_cols;
const uint32_t buffer_size = src.batch() * src.depth() * src.rows() * src.cols() * sizeof(U);
const buffer_resource br = make_buffer_resource(reinterpret_cast<uintptr_t>(src_ptr), buffer_size, 0x00020000);
#pragma unroll
for (int i = 0; i < dst.height; i++) {
#pragma unroll
for (int j = 0; j < dst.width; j++) {
const float4 loaded = std::bit_cast<float4>(llvm_amdgcn_raw_buffer_load_b128(
std::bit_cast<i32x4>(br), (i * tile_row_stride + j * tile_stride + lane * 8) * sizeof(U), 0, 0));
const U2 *packed = reinterpret_cast<const U2*>(&loaded);
#pragma unroll
for (int k = 0; k < dst.packed_per_base_tile; k++) dst.tiles[i][j].data[k] = packed[k];
}
}
}
template<int axis, ducks::rt::row_layout RT, ducks::gl::all GL, ducks::coord::tile COORD=coord<RT>>
__device__ __forceinline__ void store_fa_shuffled(const GL &dst, const RT &src, const COORD &idx) {
using U = typename GL::dtype;
U *dst_ptr = (U*)&dst[(idx.template unit_coord<axis, 3>())];
const int row_stride = dst.template stride<axis>();
const int lane = kittens::laneid();
const int row_offset = (lane % 4) * 4;
const int col_offset = ((lane / 32) * 16) + (((lane % 32) / 16) * 2) + (((lane % 16) / 4) * 4);
const uint32_t buffer_size = dst.batch() * dst.depth() * dst.rows() * dst.cols() * sizeof(U);
const buffer_resource br = make_buffer_resource(reinterpret_cast<uintptr_t>(dst_ptr), buffer_size, 0x00020000);
#pragma unroll
for (int i = 0; i < src.height; i++) {
const int row = src.base_tile_rows * i + row_offset;
#pragma unroll
for (int j = 0; j < src.width; j++) {
const int col = src.base_tile_cols * j + col_offset;
#pragma unroll
for (int k = 0; k < src.packed_per_base_tile; k++) llvm_amdgcn_raw_buffer_store_b32(
*reinterpret_cast<const uint32_t*>(&src.tiles[i][j].data[k]), std::bit_cast<i32x4>(br),
((row + k) * row_stride + col) * sizeof(U), 0, 0);
}
}
}
template<ducks::rt::row_layout RT>
__device__ __forceinline__ void inverse_rope_fa(RT &tile, const bf16_2 *freqs, const int n_base) {
const int lane = kittens::laneid();
const int row_offset = (lane % 4) * 4;
const int col_offset = ((lane / 32) * 16) + (((lane % 32) / 16) * 2) + (((lane % 16) / 4) * 4);
#pragma unroll
for (int i = 0; i < tile.height; i++) {
#pragma unroll
for (int j = 0; j < tile.width; j++) {
const int col = tile.base_tile_cols * j + col_offset;
#pragma unroll
for (int k = 0; k < tile.packed_per_base_tile; k++) {
const int row = tile.base_tile_rows * i + row_offset + k;
const float2 cs = __bfloat1622float2(freqs[(n_base + row) * HALF_D + col / 2]);
const float2 g = __bfloat1622float2(tile.tiles[i][j].data[k]);
tile.tiles[i][j].data[k] = __float22bfloat162_rn(make_float2(g.x * cs.x + g.y * cs.y, -g.x * cs.y + g.y * cs.x));
}
}
}
}
template<ducks::rt::row_layout RT>
__device__ __forceinline__ void inverse_rope(RT &tile, const bf16_2 *freqs, const int n_base) {
const int lane = kittens::laneid();
#pragma unroll
for (int i = 0; i < tile.height; i++) {
const int row = tile.base_tile_rows * i + lane % tile.base_tile_rows;
#pragma unroll
for (int j = 0; j < tile.width; j++) {
#pragma unroll
for (int k = 0; k < tile.packed_per_base_tile; k++) {
const int col = tile.base_tile_cols * j + tile.base_tile_stride * (lane / tile.base_tile_rows) + 2 * k;
const float2 cs = __bfloat1622float2(freqs[(n_base + row) * HALF_D + col / 2]);
const float2 g = __bfloat1622float2(tile.tiles[i][j].data[k]);
tile.tiles[i][j].data[k] = __float22bfloat162_rn(make_float2(g.x * cs.x + g.y * cs.y, -g.x * cs.y + g.y * cs.x));
}
}
}
}
extern "C" __global__ __launch_bounds__(THREADS_PER_BLOCK) void
fused_qkv_rope_backward(
bf16* __restrict__ dxqkv,
const bf16* __restrict__ dq,
const bf16* __restrict__ dk,
const bf16* __restrict__ dv,
const bf16* __restrict__ freqs_cis) {
gl<bf16, -1, -1, -1, -1> out{dxqkv, ATTN_B, ATTN_N, PACKED_H, ATTN_D};
gl<bf16, -1, -1, -1, -1> dqg{const_cast<bf16*>(dq), ATTN_B, ATTN_H, ATTN_N, ATTN_D};
gl<bf16, -1, -1, -1, -1> dkg{const_cast<bf16*>(dk), ATTN_B * KV_PARTIALS, ATTN_N, ATTN_H_KV, ATTN_D};
gl<bf16, -1, -1, -1, -1> dvg{const_cast<bf16*>(dv), ATTN_B * KV_PARTIALS, ATTN_N, ATTN_H_KV, ATTN_D};
const int b = blockIdx.x, n_tile = blockIdx.y * NUM_WARPS + kittens::warpid(), n_base = n_tile * TILE_N;
const int field = blockIdx.z;
if (field < Q_FIELDS) {
#pragma unroll
for (int qj = 0; qj < Q_HEADS_PER_WG; qj++) {
const int qh = field * Q_HEADS_PER_WG + qj;
grad_tile<bf16> tile;
load_fa_shuffled<2>(tile, dqg, {b, qh, n_tile, 0});
inverse_rope_fa(tile, reinterpret_cast<const bf16_2*>(freqs_cis), n_base);
const int out_head = (qh / GROUP_SIZE) * (GROUP_SIZE + 2) + qh % GROUP_SIZE;
store_fa_shuffled<1>(out, tile, {b, n_tile, out_head, 0});
}
} else {
const int kv_field = field - Q_FIELDS;
const bool is_k = kv_field < ATTN_H_KV;
const int kvh = kv_field - (is_k ? 0 : ATTN_H_KV);
const auto &src = is_k ? dkg : dvg;
grad_tile<bf16> partial, tile;
grad_tile<float> partial_f, sum;
zero(sum);
#pragma unroll
for (int p = 0; p < KV_PARTIALS; p++) {
load<1>(partial, src, {b * KV_PARTIALS + p, n_tile, kvh, 0});
copy(partial_f, partial);
add(sum, sum, partial_f);
}
copy(tile, sum);
if (is_k) inverse_rope(tile, reinterpret_cast<const bf16_2*>(freqs_cis), n_base);
const int out_head = kvh * (GROUP_SIZE + 2) + GROUP_SIZE + !is_k;
store<1>(out, tile, {b, n_tile, out_head, 0});
}
}
@@ -0,0 +1,89 @@
#include <hip/hip_runtime.h>
#include <hip/hip_bf16.h>
#ifndef ATTN_B
#define ATTN_B 2
#endif
#ifndef ATTN_N
#define ATTN_N 8192
#endif
#ifndef ATTN_H
#define ATTN_H 64
#endif
#ifndef ATTN_H_KV
#define ATTN_H_KV 8
#endif
#ifndef ATTN_D
#define ATTN_D 64
#endif
#ifndef THREADS_PER_BLOCK
#define THREADS_PER_BLOCK 256
#endif
constexpr int GROUP_SIZE = ATTN_H / ATTN_H_KV;
constexpr int HALF_D = ATTN_D / 2;
constexpr int PACKED_D = (GROUP_SIZE + 2) * ATTN_D;
using float2v = float __attribute__((ext_vector_type(2)));
static_assert(ATTN_B == 2 && ATTN_N == 8192 && ATTN_H == 64 && ATTN_H_KV == 8 && ATTN_D == 64,
"this kernel is the exact per-device GPTOSS QKV/RoPE shape");
static_assert(THREADS_PER_BLOCK == ATTN_H_KV * HALF_D, "one thread must own each KV-head/RoPE pair");
extern "C" __global__ __launch_bounds__(THREADS_PER_BLOCK) void
fused_qkv_rope_forward(
__hip_bfloat16* __restrict__ q,
__hip_bfloat16* __restrict__ k,
__hip_bfloat16* __restrict__ v,
const __hip_bfloat16* __restrict__ xqkv,
const __hip_bfloat16* __restrict__ freqs_cis) {
const int b = blockIdx.x;
const int n = blockIdx.y;
const int bn = b * ATTN_N + n;
const int kvh = threadIdx.x / HALF_D;
const int pair = threadIdx.x % HALF_D;
const int even = pair * 2;
const int packed_head = bn * ATTN_H_KV * PACKED_D + kvh * PACKED_D;
const int q_head = bn * ATTN_H * ATTN_D + kvh * GROUP_SIZE * ATTN_D;
const int kv_head = (bn * ATTN_H_KV + kvh) * ATTN_D;
// Cos/sin is shared by every Q/K slot owned by this (KV head, pair) thread.
const __hip_bfloat16 *freq_pair = freqs_cis + (n * HALF_D + pair) * 2;
const uint32_t cs_raw = *reinterpret_cast<const uint32_t*>(freq_pair);
const float2 cs = __bfloat1622float2(*reinterpret_cast<const __hip_bfloat162*>(freq_pair));
uint32_t ab_raw[GROUP_SIZE + 1];
bool exceptional = (cs_raw & 0x7f80u) == 0x7f80u || (cs_raw & 0x7f800000u) == 0x7f800000u;
#pragma unroll
for (int slot = 0; slot < GROUP_SIZE + 1; slot++) {
ab_raw[slot] = *reinterpret_cast<const uint32_t*>(xqkv + packed_head + slot * ATTN_D + even);
exceptional |= (ab_raw[slot] & 0x7f80u) == 0x7f80u || (ab_raw[slot] & 0x7f800000u) == 0x7f800000u;
}
if (__builtin_expect(exceptional, 0)) {
#pragma unroll
for (int slot = 0; slot < GROUP_SIZE + 1; slot++) {
const float2 ab = __bfloat1622float2(*reinterpret_cast<const __hip_bfloat162*>(&ab_raw[slot]));
__hip_bfloat16 *dst = slot < GROUP_SIZE ? q : k;
const int out = slot < GROUP_SIZE ? q_head + slot * ATTN_D + even : kv_head + even;
// Match the original kernel's packed instruction association exactly for NaN/Inf values. The low-lane
// instruction modifier, rather than pre-negating an operand, preserves its NaN sign/payload behavior.
const float2v base = float2v{ab.x, ab.y} * float2v{cs.x, cs.x};
const float2v cross = {ab.y, ab.x}, sine = {cs.y, cs.y};
float2v rotated;
asm volatile("v_pk_fma_f32 %0, %1, %2, %3 neg_lo:[1,0,0]"
: "=v"(rotated) : "v"(sine), "v"(cross), "v"(base));
*reinterpret_cast<__hip_bfloat162*>(dst + out) =
__float22bfloat162_rn(make_float2(rotated[0], rotated[1]));
}
} else {
#pragma unroll
for (int slot = 0; slot < GROUP_SIZE + 1; slot++) {
const float2 ab = __bfloat1622float2(*reinterpret_cast<const __hip_bfloat162*>(&ab_raw[slot]));
__hip_bfloat16 *dst = slot < GROUP_SIZE ? q : k;
const int out = slot < GROUP_SIZE ? q_head + slot * ATTN_D + even : kv_head + even;
*reinterpret_cast<__hip_bfloat162*>(dst + out) = __float22bfloat162_rn(
make_float2(ab.x * cs.x - ab.y * cs.y, ab.x * cs.y + ab.y * cs.x));
}
}
// V is already in output order; copy the aligned BF16 pair without conversion.
*reinterpret_cast<__hip_bfloat162*>(v + kv_head + even) =
*reinterpret_cast<const __hip_bfloat162*>(xqkv + packed_head + (GROUP_SIZE + 1) * ATTN_D + even);
}
@@ -15,14 +15,14 @@ def _custom_quantize_mxfp8_qe8(fp8_out:UOp, e8_out:UOp, x:UOp) -> UOp:
x, fp8_out, e8_out = x.reshape(n_elems), fp8_out.reshape(n_elems), e8_out.reshape(n_blocks)
wg = UOp.range(n_blocks // THREADS_PER_WG, 0, AxisType.GLOBAL)
tid = UOp.range(THREADS_PER_WG, 1, AxisType.LOCAL)
lane = UOp.range(BLK, 3, AxisType.UNROLL)
lane = UOp.range(BLK, 3, AxisType.UPCAST)
block = wg * THREADS_PER_WG + tid
idx = block * BLK + lane
x_f = x[idx].cast(dtypes.float)
abs_x = (x_f < 0.0).where(-x_f, x_f)
blk_max = abs_x.reduce(lane, arg=Ops.MAX)
e8f = (blk_max.maximum(1e-38).log2().floor() + 127.0).maximum(0.0).minimum(254.0)
qscale = (127.0 - e8f).exp2()
e8f = ((blk_max.bitcast(dtypes.uint32) >> 23) & 0xFF).minimum(254)
qscale = ((e8f.const_like(254) - e8f) << 23).bitcast(dtypes.float32)
scaled = (x_f * qscale).maximum(-FP8_MAX).minimum(FP8_MAX)
fp8_store = fp8_out[idx].store(scaled.cast(fp8_out.dtype)).end(lane)
e8_store = e8_out.after(fp8_store)[block].store(e8f.cast(dtypes.uint8))
@@ -0,0 +1,54 @@
import functools, math, pathlib
from tinygrad import Tensor, dtypes, function
from tinygrad.helpers import ALLREDUCE_CAST
from tinygrad.runtime.support.compiler_amd import HIPCompiler
from tinygrad.uop.ops import UOp, Ops, KernelInfo
from extra.llama_kernels import alloc_like, compile_hip
@functools.cache
def _residual_kernel(out:UOp, x:UOp, proj:UOp, bias:UOp, moe:UOp) -> UOp:
rows = math.prod(x.shape[:-1])
assert out.shape == x.shape == moe.shape and x.shape[-1] == 2880
assert proj.shape == (rows, 3072) and bias.shape == (2880,)
assert all(t.dtype == dtypes.bfloat16 for t in (out, x, proj, bias, moe))
sink = UOp.sink(out.base, x.base, proj.base, bias.base, moe.base,
UOp.special(256, "lidx0"), UOp.special(rows, "gidx0"), arg=KernelInfo("gptoss_residual_join_vec"))
src = (pathlib.Path(__file__).parent/"residual.cpp").read_text()
lib = compile_hip(src, [f"-DROWS={rows}", "-DREAL_D=2880", "-DPAD_D=3072", "-fno-fast-math"])
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=lib)))
def _residual_backward(gradient:UOp, call:UOp) -> tuple:
return gradient, None, None, None, gradient
@function(grad_fxn=_residual_backward)
def residual_join(h:Tensor, x:Tensor, proj:Tensor, bias:Tensor, moe:Tensor) -> Tensor:
out = alloc_like(x.shape, x.dtype, x.device, x.uop.axis)
return Tensor.custom_kernel(out, x, proj, bias, moe, fxn=_residual_kernel)[0]
@functools.cache
def _wo_bias_kernel(out:UOp, inp:UOp, *, stage:str) -> UOp:
assert stage in ("partial", "final")
partial = stage == "partial"
assert out.shape == ((16, 2880) if partial else (2880,)) and inp.shape == ((16384, 2880) if partial else (16, 2880))
assert out.dtype == (dtypes.float32 if partial else dtypes.bfloat16) and inp.dtype == (dtypes.bfloat16 if partial else dtypes.float32)
sink = UOp.sink(out.base, inp.base, UOp.special(64, "lidx0"), UOp.special(45, "gidx0"),
*((UOp.special(16, "gidx1"),) if partial else ()), arg=KernelInfo(f"gptoss_wo_bias_{stage}"))
src = (pathlib.Path(__file__).parent/f"wo_bias_{stage}.cpp").read_text()
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src),
UOp(Ops.BINARY, arg=HIPCompiler("gfx950").compile_cached(src))))
def wo_bias_gradient(g:Tensor) -> Tensor:
groups = len(g.device) if isinstance(g.device, tuple) else 1
assert groups == 1 or (g.uop.axis == 0 and ALLREDUCE_CAST), "WO bias sum requires the baseline BF16 aggregation"
partial = alloc_like((groups*16, 2880), dtypes.float32, g.device, g.uop.axis)
local = alloc_like((2880,), dtypes.bfloat16, g.device)
partial = Tensor.custom_kernel(partial, g.reshape(-1, 2880), fxn=functools.partial(_wo_bias_kernel, stage="partial"))[0]
local = Tensor.custom_kernel(local, partial, fxn=functools.partial(_wo_bias_kernel, stage="final"))[0]
return Tensor(local.uop.allreduce(Ops.ADD, g.device)) if groups > 1 else local
def _wo_bias_backward(gradient:UOp, call:UOp) -> tuple:
return gradient, wo_bias_gradient(Tensor(gradient)).uop
@function(grad_fxn=_wo_bias_backward)
def wo_bias_add(x:Tensor, bias:Tensor) -> Tensor:
return x + bias
@@ -0,0 +1,46 @@
#include <hip/hip_runtime.h>
#include <stdint.h>
using bf16 = __bf16;
using uint4v = uint32_t __attribute__((ext_vector_type(4)));
#if !defined(ROWS) || !defined(REAL_D) || !defined(PAD_D)
#error "ROWS, REAL_D and PAD_D are required"
#endif
// Exact GPT-OSS final residual: bf16(bf16(x + bf16(proj + bias)) + moe).
// proj has the physical GEMM pitch PAD_D while every other matrix is packed REAL_D.
extern "C" __global__ __launch_bounds__(256) void gptoss_residual_join_vec(
bf16 *__restrict__ out, const bf16 *__restrict__ x, const bf16 *__restrict__ proj,
const bf16 *__restrict__ bias, const bf16 *__restrict__ moe) {
const int row = blockIdx.x;
const int tid = threadIdx.x;
#pragma unroll
for (int pass = 0; pass < 2; pass++) {
const int col = (tid + pass * 256) * 8;
if (col >= REAL_D) continue;
const long long live_off = (long long)row * REAL_D + col;
const long long pad_off = (long long)row * PAD_D + col;
const uint4v xv = *reinterpret_cast<const uint4v *>(x + live_off);
const uint4v pv = *reinterpret_cast<const uint4v *>(proj + pad_off);
const uint4v bv = *reinterpret_cast<const uint4v *>(bias + col);
const uint4v mv = *reinterpret_cast<const uint4v *>(moe + live_off);
uint4v ov;
const uint16_t *xh = reinterpret_cast<const uint16_t *>(&xv);
const uint16_t *ph = reinterpret_cast<const uint16_t *>(&pv);
const uint16_t *bh = reinterpret_cast<const uint16_t *>(&bv);
const uint16_t *mh = reinterpret_cast<const uint16_t *>(&mv);
uint16_t *oh = reinterpret_cast<uint16_t *>(&ov);
#pragma unroll
for (int i = 0; i < 8; i++) {
const bf16 xb = __builtin_bit_cast(bf16, xh[i]);
const bf16 pb = __builtin_bit_cast(bf16, ph[i]);
const bf16 bb = __builtin_bit_cast(bf16, bh[i]);
const bf16 mb = __builtin_bit_cast(bf16, mh[i]);
const bf16 attn = (bf16)((float)pb + (float)bb);
const bf16 h = (bf16)((float)xb + (float)attn);
oh[i] = __builtin_bit_cast(uint16_t, (bf16)((float)h + (float)mb));
}
*reinterpret_cast<uint4v *>(out + live_off) = ov;
}
}
@@ -0,0 +1,14 @@
typedef unsigned long size_t;
typedef __bf16 bf16;
extern "C" __attribute__((device, const)) size_t __ockl_get_local_id(unsigned int);
extern "C" __attribute__((device, const)) size_t __ockl_get_group_id(unsigned int);
// The local BF16 boundary must precede the existing data-parallel aggregation.
extern "C" __attribute__((global, amdgpu_flat_work_group_size(64,64)))
void gptoss_wo_bias_final(bf16* out, const float* in) {
int d = __ockl_get_group_id(0)*64 + __ockl_get_local_id(0);
float acc=0;
#pragma unroll
for (int p=0;p<16;p++) acc=acc+in[p*2880+d];
out[d]=(bf16)acc;
}
@@ -0,0 +1,21 @@
typedef unsigned long size_t;
typedef __bf16 bf16;
extern "C" __attribute__((device, const)) size_t __ockl_get_local_id(unsigned int);
extern "C" __attribute__((device, const)) size_t __ockl_get_group_id(unsigned int);
// Keep the generated GPTOSS reduction's 16 ordered 1024-token partials.
// Prefetching changes only load scheduling; all FP32 additions stay in order.
extern "C" __attribute__((global, amdgpu_flat_work_group_size(64,64)))
void gptoss_wo_bias_partial(float* out, const bf16* in) {
int d = __ockl_get_group_id(0)*64 + __ockl_get_local_id(0);
int p = __ockl_get_group_id(1);
float acc = 0;
for (int r=0; r<1024; r+=64) {
float x[64];
#pragma unroll
for (int j=0; j<64; j++) x[j] = (float)in[(p*1024+r+j)*2880+d];
#pragma unroll
for (int j=0; j<64; j++) acc = acc+x[j];
}
out[p*2880+d] = acc;
}
@@ -170,8 +170,7 @@ def fast_final_rmsnorm(x:Tensor, weight:Tensor, eps:float) -> Tensor:
axis = x.uop.axis if isinstance(x.device, tuple) else None
local_shape = x.uop.shard_shape if axis is not None else x.shape
if axis not in (None, 0) or math.prod(local_shape[:-1]) != 16384:
xf = x.float()
return (xf * (xf.square().mean(-1, keepdim=True) + eps).rsqrt()).cast(x.dtype) * weight
return x.float().rmsnorm(eps=eps).cast(x.dtype) * weight
fxn = _fast_final_rmsnorm_fwd_fxn(x.as_param(0).uop, weight.as_param(1).uop, eps, x.device)
outputs = UOp.call_with_outputs((fxn[0].uop, fxn[1].uop, fxn[2].uop),
x.uop, weight.uop, grad_fxn=functools.partial(_fast_final_rmsnorm_bwd, eps=eps))
@@ -0,0 +1,112 @@
import functools, math, pathlib
from tinygrad import Tensor, dtypes, function
from tinygrad.helpers import getenv
from tinygrad.runtime.support.compiler_amd import HIPCompiler, HIPCCCompiler
from tinygrad.uop.ops import UOp, Ops, KernelInfo
from extra.llama_kernels import alloc_like, compile_hip
@functools.cache
def _router_topk_fwd(weights:UOp, indices:UOp, logits:UOp) -> UOp:
tokens = math.prod(logits.shape[:-1])
sink = UOp.sink(weights.base, indices.base, logits.base,
UOp.special(256, "lidx0"), UOp.special((tokens+255)//256, "gidx0"),
arg=KernelInfo(f"moe_router_topk_{tokens}_32_4"))
src = (pathlib.Path(__file__).parent/"forward.cpp").read_text()
return UOp(Ops.PROGRAM,
src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src),
UOp(Ops.BINARY, arg=compile_hip(src, [f"-DTOKENS={tokens}"]))))
@functools.cache
def _router_topk_bwd_kernel(grad_logits:UOp, bias_partials:UOp, grad_weights:UOp, weights:UOp, indices:UOp) -> UOp:
tokens = math.prod(grad_logits.shape[:-1])
sink = UOp.sink(grad_logits.base, bias_partials.base, grad_weights.base, weights.base, indices.base,
UOp.special(256, "lidx0"), UOp.special((tokens+255)//256, "gidx0"),
arg=KernelInfo(f"moe_router_topk_bwd_{tokens}_32_4"))
src = (pathlib.Path(__file__).parent/"backward.cpp").read_text()
return UOp(Ops.PROGRAM,
src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src),
UOp(Ops.BINARY, arg=compile_hip(src, [f"-DTOKENS={tokens}"]))))
def router_topk_backward(gradient:Tensor, weights:Tensor, indices:Tensor) -> tuple[Tensor, Tensor]:
groups, tokens, _ = weights.shape
grad_logits = alloc_like((groups, tokens, 32), dtypes.float32, weights.device, weights.uop.axis)
bias_partials = alloc_like((groups, (tokens+255)//256, 32), dtypes.float32, weights.device, weights.uop.axis)
grad_logits, bias_partials, *_ = Tensor.custom_kernel(grad_logits, bias_partials, gradient.contiguous(), weights, indices,
fxn=_router_topk_bwd_kernel)
return grad_logits, bias_partials
def _router_topk_bwd(gradient:UOp, kernel:UOp) -> tuple:
weights_u, indices_u = kernel.src[1:3]
grad_logits, _ = router_topk_backward(Tensor(gradient), Tensor(weights_u.after(kernel)), Tensor(indices_u.after(kernel)))
return None, None, grad_logits.uop
@functools.cache
def _router_weight_bwd_kernel(out:UOp, x:UOp, gradient:UOp) -> UOp:
rows, hidden = math.prod(x.shape[:-1]), x.shape[-1]
assert rows % 512 == 0 and hidden % 16 == 0
sink = UOp.sink(out.base, x.base, gradient.base, UOp.special(1024, "lidx0"), UOp.special(hidden//16, "gidx0"),
arg=KernelInfo(f"moe_router_fp32_wgrad_{rows}_{hidden}_32"))
src = (pathlib.Path(__file__).parent/"weight_backward.cpp").read_text()
include = pathlib.Path(__file__).parents[2]/"thunder"/"amd"/"include"
lib = HIPCCCompiler("gfx950", [f"-I{include}", "-std=c++20", "-DKITTENS_CDNA4", "-DHIP_ENABLE_WARP_SYNC_BUILTINS",
f"-DROUTER_M={rows}", f"-DROUTER_K={hidden}"]).compile_cached(src)
return UOp(Ops.PROGRAM,
src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=lib)))
def router_weight_gradient(x:Tensor, gradient:Tensor) -> Tensor:
assert x.dtype == dtypes.bfloat16 and gradient.dtype == dtypes.float32
groups = len(x.device) if isinstance(x.device, tuple) and x.uop.axis is not None else 1
partial = alloc_like((groups, 32, x.shape[-1]), dtypes.bfloat16, x.device, x.uop.axis)
partial, *_ = Tensor.custom_kernel(partial, x, gradient, fxn=_router_weight_bwd_kernel)
return partial.sum(0, dtype=x.dtype)
def _router_bwd(gradient:UOp, call:UOp) -> tuple:
x, weight, bias = (Tensor(u) for u in call.src[1:4])
weights, indices = (Tensor(u) for u in call.unbound_outputs)
grad_logits, bias_partials = router_topk_backward(Tensor(gradient), weights, indices)
grad_x, = (x.float() @ weight.float().T).gradient(x, gradient=grad_logits.reshape(*x.shape[:-1], 32))
grad_weight = router_weight_gradient(x, grad_logits)
return grad_x.uop, grad_weight.uop, bias_partials.sum(1).cast(bias.dtype).sum(0, dtype=bias.dtype).uop
@function(grad_fxn=_router_bwd)
def fused_router(x:Tensor, weight:Tensor, bias:Tensor) -> tuple[Tensor, Tensor]:
from extra.gemm.moe_routing import router_mfma
logits = router_mfma(x, weight, bias) if getenv("ROUTER_MFMA", 0) else x.float() @ weight.float().T + bias.float()
groups = len(x.device) if isinstance(x.device, tuple) else 1
return fused_router_topk(logits.reshape(groups, -1, 32))
@functools.cache
def _router_input_bwd_kernel(out:UOp, weight:UOp, gradient:UOp, residual:UOp, scales:UOp) -> UOp:
assert math.prod(out.shape[:-1]) == 16384 and out.shape[-1] == 2880 and weight.shape == (32, 2880)
assert math.prod(gradient.shape[:-1]) == 16384 and gradient.shape[-1] == 32
assert residual.shape == (16384, 3072) and scales.shape == (16384, 96)
sink = UOp.sink(out.base, weight.base, gradient.base, residual.base, scales.base,
UOp.special(16, "lidx0"), UOp.special(4, "lidx1"), UOp.special(9, "gidx0"), UOp.special(2048, "gidx1"),
arg=KernelInfo("moe_router_dgrad_fused"))
src = (pathlib.Path(__file__).parent/"input_backward.cpp").read_text()
return UOp(Ops.PROGRAM,
src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src),
UOp(Ops.BINARY, arg=HIPCompiler("gfx950").compile_cached(src))))
def _router_quantize_bwd(gradient:UOp, quantized_gradient:UOp, *, call:UOp) -> tuple:
x, weight, bias = (Tensor(u) for u in call.src[1:4])
weights, indices, _, scales = (Tensor(u) for u in call.unbound_outputs)
grad_logits, bias_partials = router_topk_backward(Tensor(gradient), weights, indices)
grad_x = alloc_like(x.shape, x.dtype, x.device, x.uop.axis)
grad_x, *_ = Tensor.custom_kernel(grad_x, weight, grad_logits, Tensor(quantized_gradient), scales, fxn=_router_input_bwd_kernel)
return grad_x.uop, router_weight_gradient(x, grad_logits).uop, bias_partials.sum(1).cast(bias.dtype).sum(0, dtype=bias.dtype).uop
@function(grad_fxn=_router_quantize_bwd)
def fused_router_quantize(x:Tensor, weight:Tensor, bias:Tensor) -> tuple[Tensor, Tensor, Tensor, Tensor]:
from extra.gptoss_kernels.quantize_mxfp8 import quantize_mxfp8_fused_qe8
weights, indices = fused_router(x, weight, bias)
quantized, scales = quantize_mxfp8_fused_qe8(x.reshape(-1, 2880).pad(((0, 0), (0, 192))))
return weights, indices, quantized, scales
def fused_router_topk(logits:Tensor) -> tuple[Tensor, Tensor]:
assert logits.ndim == 3 and logits.shape[-1] == 32 and logits.dtype == dtypes.float32
axis = logits.uop.axis
weights = alloc_like((*logits.shape[:-1], 4), dtypes.float32, logits.device, axis)
indices = alloc_like(weights.shape, dtypes.int32, logits.device, axis)
weights, indices, *_ = Tensor.custom_kernel(weights, indices, logits, fxn=_router_topk_fwd, grad_fxn=_router_topk_bwd)
return weights, indices
@@ -0,0 +1,33 @@
#include <hip/hip_runtime.h>
extern "C" __global__ __launch_bounds__(256) void moe_router_topk_bwd(
float *__restrict__ grad_logits, float *__restrict__ bias_partials, const float *__restrict__ grad_weights,
const float *__restrict__ weights, const int *__restrict__ indices) {
__shared__ float partial[4][32];
if (threadIdx.x < 128) partial[threadIdx.x / 32][threadIdx.x % 32] = 0.0f;
__syncthreads();
const int token = blockIdx.x * 256 + threadIdx.x;
if (token < TOKENS) {
const long long in = (long long)token * 4;
float dot = 0.0f;
#pragma unroll
for (int i = 0; i < 4; i++) dot += grad_weights[in + i] * weights[in + i];
const long long out = (long long)token * 32;
#pragma unroll
for (int e = 0; e < 32; e++) grad_logits[out + e] = 0.0f;
#pragma unroll
for (int i = 0; i < 4; i++) {
const int e = indices[in + i];
const float dg = weights[in + i] * (grad_weights[in + i] - dot);
grad_logits[out + e] = dg;
atomicAdd(&partial[threadIdx.x / 64][e], dg);
}
}
__syncthreads();
if (threadIdx.x < 32) {
float sum = 0.0f;
#pragma unroll
for (int w = 0; w < 4; w++) sum += partial[w][threadIdx.x];
bias_partials[(long long)blockIdx.x * 32 + threadIdx.x] = sum;
}
}
@@ -0,0 +1,37 @@
#include <hip/hip_runtime.h>
extern "C" __global__ __launch_bounds__(256) void moe_router_topk(
float *__restrict__ weights, int *__restrict__ indices, const float *__restrict__ logits) {
const int token = blockIdx.x * 256 + threadIdx.x;
if (token >= TOKENS) return;
float topv[4];
int topi[4];
#pragma unroll
for (int i = 0; i < 4; i++) { topv[i] = -3.402823466e+38f; topi[i] = -1; }
const long long base = (long long)token * 32;
#pragma unroll
for (int e = 0; e < 32; e++) {
float v = logits[base + e];
int pos = 4;
#pragma unroll
for (int i = 0; i < 4; i++) if (pos == 4 && v > topv[i]) pos = i;
if (pos < 4) {
#pragma unroll
for (int i = 3; i > 0; i--) if (i > pos) { topv[i] = topv[i-1]; topi[i] = topi[i-1]; }
topv[pos] = v;
topi[pos] = e;
}
}
float denom = 0.0f;
float ex[4];
#pragma unroll
for (int i = 0; i < 4; i++) { ex[i] = expf(topv[i] - topv[0]); denom += ex[i]; }
const long long out = (long long)token * 4;
#pragma unroll
for (int i = 0; i < 4; i++) {
weights[out + i] = ex[i] / denom;
indices[out + i] = topi[i];
}
}
@@ -0,0 +1,68 @@
typedef long unsigned int size_t;
typedef __bf16 bf16;
typedef float float4_t __attribute__((ext_vector_type(4)));
extern "C" __attribute__((device, const)) size_t __ockl_get_local_id(unsigned int);
extern "C" __attribute__((device, const)) size_t __ockl_get_group_id(unsigned int);
// GPT-OSS router dgrad join, specialized to the production local shard:
// bf16(bf16(sum_e bf16(weight[e,d]) * fp32(grad_logits[t,e]))
// + bf16(bf16(residual[t,d]) * 2**(127-e8[t,d/32])))
extern "C" __attribute__((global)) void __attribute__((amdgpu_flat_work_group_size(64, 64)))
moe_router_dgrad_fused(bf16 *__restrict__ out, const bf16 *__restrict__ weight,
const float *__restrict__ grad_logits, const bf16 *__restrict__ residual,
const unsigned char *__restrict__ e8) {
constexpr int K = 2880, KP = 3072, E = 32;
constexpr int DIM_TILE = 320, DIMS_PER_THREAD = 5;
const int lane_x = __ockl_get_local_id(0);
const int lane_y = __ockl_get_local_id(1);
const int dim_tile = __ockl_get_group_id(0);
const int token8 = __ockl_get_group_id(1);
const int dim0 = dim_tile * DIM_TILE + lane_y * 16 + lane_x;
const int token0 = token8 * 8;
float acc[8][DIMS_PER_THREAD] = {};
#pragma unroll 1
for (int ec = 0; ec < E / 4; ec++) {
const int expert = ec * 4;
const float4_t g0 = *reinterpret_cast<const float4_t *>(grad_logits + (long long)(token0 + 0) * E + expert);
const float4_t g1 = *reinterpret_cast<const float4_t *>(grad_logits + (long long)(token0 + 1) * E + expert);
const float4_t g2 = *reinterpret_cast<const float4_t *>(grad_logits + (long long)(token0 + 2) * E + expert);
const float4_t g3 = *reinterpret_cast<const float4_t *>(grad_logits + (long long)(token0 + 3) * E + expert);
const float4_t g4 = *reinterpret_cast<const float4_t *>(grad_logits + (long long)(token0 + 4) * E + expert);
const float4_t g5 = *reinterpret_cast<const float4_t *>(grad_logits + (long long)(token0 + 5) * E + expert);
const float4_t g6 = *reinterpret_cast<const float4_t *>(grad_logits + (long long)(token0 + 6) * E + expert);
const float4_t g7 = *reinterpret_cast<const float4_t *>(grad_logits + (long long)(token0 + 7) * E + expert);
#pragma unroll
for (int j = 0; j < DIMS_PER_THREAD; j++) {
const int d = dim0 + j * 64;
const float w0 = (float)weight[(long long)(expert + 0) * K + d];
const float w1 = (float)weight[(long long)(expert + 1) * K + d];
const float w2 = (float)weight[(long long)(expert + 2) * K + d];
const float w3 = (float)weight[(long long)(expert + 3) * K + d];
acc[0][j] = acc[0][j] + w0*g0.x + w1*g0.y + w2*g0.z + w3*g0.w;
acc[1][j] = acc[1][j] + w0*g1.x + w1*g1.y + w2*g1.z + w3*g1.w;
acc[2][j] = acc[2][j] + w0*g2.x + w1*g2.y + w2*g2.z + w3*g2.w;
acc[3][j] = acc[3][j] + w0*g3.x + w1*g3.y + w2*g3.z + w3*g3.w;
acc[4][j] = acc[4][j] + w0*g4.x + w1*g4.y + w2*g4.z + w3*g4.w;
acc[5][j] = acc[5][j] + w0*g5.x + w1*g5.y + w2*g5.z + w3*g5.w;
acc[6][j] = acc[6][j] + w0*g6.x + w1*g6.y + w2*g6.z + w3*g6.w;
acc[7][j] = acc[7][j] + w0*g7.x + w1*g7.y + w2*g7.z + w3*g7.w;
}
}
#pragma unroll
for (int t = 0; t < 8; t++) {
#pragma unroll
for (int j = 0; j < DIMS_PER_THREAD; j++) {
const int token = token0 + t, d = dim0 + j * 64;
const unsigned char scale_e = e8[(long long)token * (KP / 32) + d / 32];
// Integer E8M0 exponents produce an exact power of two. e8=254 is 2^-127, a float subnormal.
const unsigned scale_bits = scale_e >= 254 ? (0x00400000u >> ((unsigned)scale_e - 254u)) : (254u - (unsigned)scale_e) << 23;
const float qscale = __builtin_bit_cast(float, scale_bits);
const bf16 router = (bf16)acc[t][j];
const bf16 dequant = (bf16)((float)residual[(long long)token * KP + d] * qscale);
out[(long long)token * K + d] = (bf16)((float)router + (float)dequant);
}
}
}
@@ -0,0 +1,69 @@
#include "kittens.cuh"
using namespace kittens;
constexpr int NUM_WARPS = 16;
constexpr int THREADS = NUM_WARPS * 64;
constexpr int TILE = 16;
constexpr int PREFETCH = 8;
using float4_t = float __attribute__((ext_vector_type(4)));
static_assert(ROUTER_K % TILE == 0);
static_assert(ROUTER_M % (4 * NUM_WARPS * PREFETCH) == 0);
// Each block computes 16 features for all 32 experts, retaining FP32 logit gradients and accumulation.
extern "C" __global__ __launch_bounds__(THREADS, 2) void moe_router_fp32_wgrad(
bf16 *__restrict__ out, const bf16 *__restrict__ x, const float *__restrict__ gradient) {
const int lane = laneid();
const int warp = threadIdx.x / 64;
const int feature_base = blockIdx.x * TILE;
const int k_lane = lane / TILE;
const int mn_lane = lane % TILE;
float4_t accum[2] = {};
#pragma unroll 1
for (int mt = warp; mt < ROUTER_M / 4; mt += NUM_WARPS * PREFETCH) {
float av[PREFETCH], bv0[PREFETCH], bv1[PREFETCH];
#pragma unroll
for (int p = 0; p < PREFETCH; p++) {
const int token = (mt + p * NUM_WARPS) * 4 + k_lane;
av[p] = (float)x[(long long)token * ROUTER_K + feature_base + mn_lane];
bv0[p] = gradient[(long long)token * 32 + mn_lane];
bv1[p] = gradient[(long long)token * 32 + TILE + mn_lane];
}
// Keep the prefetched operands live together instead of serializing load/MFMA pairs.
asm volatile("" :: "v"(av[0]), "v"(bv0[0]), "v"(bv1[0]), "v"(av[1]), "v"(bv0[1]), "v"(bv1[1]),
"v"(av[2]), "v"(bv0[2]), "v"(bv1[2]), "v"(av[3]), "v"(bv0[3]), "v"(bv1[3]),
"v"(av[4]), "v"(bv0[4]), "v"(bv1[4]), "v"(av[5]), "v"(bv0[5]), "v"(bv1[5]),
"v"(av[6]), "v"(bv0[6]), "v"(bv1[6]), "v"(av[7]), "v"(bv0[7]), "v"(bv1[7]) : "memory");
#pragma unroll
for (int p = 0; p < PREFETCH; p++) {
accum[0] = __builtin_amdgcn_mfma_f32_16x16x4f32(av[p], bv0[p], accum[0], 0, 0, 0);
accum[1] = __builtin_amdgcn_mfma_f32_16x16x4f32(av[p], bv1[p], accum[1], 0, 0, 0);
}
}
__shared__ float partial[2][NUM_WARPS][64][4];
#pragma unroll
for (int et = 0; et < 2; et++) {
#pragma unroll
for (int i = 0; i < 4; i++) partial[et][warp][lane][i] = accum[et][i];
}
__syncthreads();
if (warp == 0) {
#pragma unroll
for (int et = 0; et < 2; et++) {
float4_t total = {};
#pragma unroll
for (int w = 0; w < NUM_WARPS; w++) {
#pragma unroll
for (int i = 0; i < 4; i++) total[i] += partial[et][w][lane][i];
}
const int expert = et * TILE + lane % 16;
const int feature0 = feature_base + 4 * (lane / 16);
#pragma unroll
for (int i = 0; i < 4; i++) out[(long long)expert * ROUTER_K + feature0 + i] = (bf16)total[i];
}
}
}
@@ -0,0 +1,68 @@
import functools
from tinygrad import Tensor, dtypes
from tinygrad.uop.ops import UOp, Ops, KernelInfo, AxisType
from extra.llama_kernels import alloc_like
ALPHA, LIMIT, LOG2E = 1.702, 7.0, 1.4426950408889634
@functools.cache
def _swiglu_quantize(q:UOp, e8:UOp, h:UOp) -> UOp:
rows = h.shape[0]
wg = UOp.range((rows*24 + 255)//256, 0, AxisType.GLOBAL)
tid = UOp.range(256, 1, AxisType.LOCAL)
sb = UOp.range(4, 2, AxisType.UPCAST)
lane = UOp.range(32, 3, AxisType.UPCAST)
block = (wg*256 + tid)*4 + sb
row, col = block//96, (block%96)*32 + lane
valid = (row < rows) & (col < 2880)
h = h.reshape(rows*5760)
gate = h.index((row*5760 + col*2).valid(valid)).load().cast(dtypes.float32)
up = h.index((row*5760 + col*2 + 1).valid(valid)).load().cast(dtypes.float32)
glu, linear = gate.minimum(LIMIT), up.maximum(-LIMIT).minimum(LIMIT)
sig = (1.0 + (glu * (ALPHA * -LOG2E)).exp2()).reciprocal()
act = glu * sig * (linear + 1.0)
amax = (act < 0.0).where(-act, act).reduce(lane, arg=Ops.MAX)
exponent = (amax.maximum(1e-38).log2().floor() + 127.0).maximum(0.0).minimum(254.0)
scaled = (act * (127.0 - exponent).exp2()).maximum(-448.0).minimum(448.0)
store = q.reshape(rows*3072).index((block*32 + lane).valid(row < rows)).store(scaled.cast(q.dtype)).end(lane)
return e8.reshape(rows*96).after(store).index(block.valid(row < rows)).store(exponent.cast(dtypes.uint8)).end(sb, tid, wg).sink(
arg=KernelInfo("gptoss_swiglu_quantize", opts_to_apply=()))
@functools.cache
def _swiglu_backward(out:UOp, h:UOp, gradient:UOp, e8:UOp) -> UOp:
rows = h.shape[0]
wg = UOp.range((rows*2880 + 256*8 - 1)//(256*8), 0, AxisType.GLOBAL)
tid = UOp.range(256, 1, AxisType.LOCAL)
lane = UOp.range(8, 2, AxisType.UPCAST)
idx = (wg*256 + tid)*8 + lane
valid = idx < rows*2880
row, col = idx//2880, idx%2880
h, out = h.reshape(rows*5760), out.reshape(rows*5760)
gate = h.index((idx*2).valid(valid)).load().cast(dtypes.float32)
up = h.index((idx*2 + 1).valid(valid)).load().cast(dtypes.float32)
scale = (127.0 - e8.reshape(rows*96).index((row*96 + col//32).valid(valid)).load().cast(dtypes.float32)).exp2()
dy = gradient.reshape(rows*3072).index((row*3072 + col).valid(valid)).load().cast(dtypes.float32) * scale
glu, linear = gate.minimum(LIMIT), up.maximum(-LIMIT).minimum(LIMIT)
sig = (1.0 + (glu * (ALPHA * -LOG2E)).exp2()).reciprocal()
sprime = sig * (1.0 + ALPHA * glu * (1.0 - sig))
dgate, dup = dy * sprime * (linear + 1.0), dy * (glu * sig)
gate_ok = (gate < LIMIT).where(1.0, 0.0)
up_ok = (up > -LIMIT).where(1.0, 0.0) * (up < LIMIT).where(1.0, 0.0)
store = out.index((idx*2).valid(valid)).store((dgate * gate_ok).cast(out.dtype))
return out.after(store).index((idx*2 + 1).valid(valid)).store((dup * up_ok).cast(out.dtype)).end(lane, tid, wg).sink(
arg=KernelInfo("gptoss_swiglu_backward", opts_to_apply=()))
def _swiglu_quantize_backward(gradient:UOp, kernel:UOp) -> tuple:
_, e8, h = kernel.src[1:4]
out = alloc_like(h.shape, h.dtype, h.device, h.axis)
out = Tensor.custom_kernel(out, Tensor(h), Tensor(gradient).cast(dtypes.bfloat16), Tensor(e8.after(kernel)), fxn=_swiglu_backward)[0]
return None, None, out.uop
def fused_swiglu_quantize(h:Tensor) -> tuple[Tensor, Tensor]:
assert h.ndim == 2 and h.shape[1] == 5760 and h.dtype == dtypes.bfloat16
assert h.uop.shard_shape[0] % 2 == 0
rows = h.shape[0]
q = alloc_like((rows, 3072), dtypes.fp8e4m3, h.device, h.uop.axis)
e8 = alloc_like((rows, 96), dtypes.uint8, h.device, h.uop.axis)
q, e8, _ = Tensor.custom_kernel(q, e8, h, fxn=_swiglu_quantize, grad_fxn=_swiglu_quantize_backward)
return q, e8
+7
View File
@@ -65,6 +65,13 @@ def cmd_kill_pids(args):
devs = scan_devs_based_on_lock(prefix:={"amd":"am", "nv":"nv"}[args.backend], args)
use_sudo = not getattr(args, "sudoless", False)
# with the kernel driver there are no locks and /proc/<pid>/fd is root-only, so lsof misses them. ask the driver.
for _ in range(128 if args.backend == "nv" and _is_module_loaded("nvidia") else 0):
if not (pids := set(subprocess.check_output(['nvidia-smi', '--query-compute-apps=pid', '--format=csv,noheader']).decode().split())): break
print(f"Killing processes {pids} (which use nvidia)")
subprocess.run((['sudo'] if use_sudo else []) + ['kill', '-9', *pids])
time.sleep(0.2)
for dev in devs:
for i in range(128):
if i > 0: time.sleep(0.2)
+70 -6
View File
@@ -2,13 +2,77 @@ import collections, time
from typing import Any, cast
from tinygrad.helpers import round_up, PROFILE, ALL2ALL, merge_dicts, getenv, suppress_finalizing, TracingKey, unwrap
from extra.hcq1.hcq import HCQBuffer, HCQCompiled, HCQAllocator, HCQSignal, HWQueue, HCQArgsState
from tinygrad.runtime.support.hcq import BumpAllocator, MMIOInterface
from tinygrad.runtime.support.hcq2 import DepsTracker
from tinygrad.runtime.support.memory import BumpAllocator, MMIOInterface
from tinygrad.device import BufferStorage, Buffer, BufferSpec, Compiled, Device, MultiBuffer, ProfileGraphEntry, ProfileGraphEvent
from tinygrad.dtype import dtypes
from tinygrad.uop.ops import UOp, Ops, Variable
from tinygrad.engine.jit import GraphRunner, MultiGraphRunner
from tinygrad.engine.realize import estimate_uop, get_runtime, unwrap_multi, resolve_params, get_call_arg_uops
from tinygrad.renderer import Estimates
class GraphRunner:
def __init__(self, linear:UOp, input_uops:tuple[UOp, ...]=()):
self.linear = linear.src[0]
self.calls: list[tuple[int, UOp, list[Buffer], dict[str, int]]] = []
self.runtimes: list[Any|None] = []
self.uop_replace: list[list[tuple[int, int]]] = []
for call in self.linear.src:
replace = [(p, b.arg.slot) for p, b in enumerate(get_call_arg_uops(call)) if b.op is Ops.PARAM]
for dev_idx, (bufs, device_vars) in enumerate(unwrap_multi(call, resolve_params(call, input_uops))):
self.calls.append((dev_idx, call.body, [b.ensure_allocated() for b in bufs], device_vars))
self.runtimes.append(get_runtime(bufs[0].device, call.body) if call.body.op is Ops.PROGRAM else None)
self.uop_replace.append(replace)
self.var_vals_replace:dict[int, list[tuple[int, int]]] = {}
self.launch_dims_replace:dict[int, tuple[int|None, int|None]] = {}
self.launch_dims_base:dict[int, tuple[tuple[int|float, ...], tuple[int, ...]]] = {}
def is_sym_dim(dim) -> bool: return not all(isinstance(d, (int, float)) for d in dim)
crs = [(j, self.calls[j][1].arg, self.calls[j][3]) for j in range(len(self.calls)) if self.calls[j][1].op is Ops.PROGRAM]
self.vars = sorted({v.expr for _,p,dv in crs for v in p.vars if v.expr not in dv})
self.symbolic_dims = dedup(tuple(d) for _,p,_ in crs for d in (p.local_size, p.global_size) if is_sym_dim(d))
def find_symbolic_dim(dim:tuple[int,int,int]): return self.symbolic_dims.index(tuple(dim)) if tuple(dim) in self.symbolic_dims else None
for j,p,dv in crs:
if (replace:=[(i, self.vars.index(v.expr)) for i, v in enumerate(p.vars) if v.expr not in dv]):
self.var_vals_replace[j] = replace
global_dim_idx, local_dim_idx = find_symbolic_dim(p.global_size), find_symbolic_dim(p.local_size)
if global_dim_idx is not None or local_dim_idx is not None:
self.launch_dims_replace[j] = (global_dim_idx, local_dim_idx)
self.launch_dims_base[j] = (tuple(p.global_size), tuple(p.local_size))
estimates = sum((estimate_uop(call) for call in self.linear.src), Estimates())
self.device, self.estimates = self.calls[0][2][0].device.split(":")[0], estimates.simplify()
def __call__(self, input_uops:tuple[UOp, ...], var_vals:dict[str, int], wait=False) -> float|None: raise NotImplementedError("override this")
def updated_vars(self, var_vals: dict[str, int]):
vals = [var_vals[v] for v in self.vars]
for j, vidxs in self.var_vals_replace.items():
for i, v in vidxs: yield j, i, vals[v]
def updated_launch_dims(self, var_vals: dict[str, int]):
dims = [tuple(sym_infer(s, var_vals) for s in dim) for dim in self.symbolic_dims]
for j, (gl, lc) in self.launch_dims_replace.items():
yield j, (dims[gl] if gl is not None else self.launch_dims_base[j][0]), (dims[lc] if lc is not None else self.launch_dims_base[j][1])
@staticmethod
def _all_devs(batch_devs:list[Compiled], new_call:UOp) -> list[Compiled]:
return dedup(batch_devs + [Device[x] for b in get_call_arg_uops(new_call)
for x in (b.device if isinstance(b.device, tuple) else (b.device,))])
@staticmethod
def supports_uop(batch_devs:list[Compiled], new_call:UOp) -> bool:
return new_call.op is Ops.CALL and new_call.body.op is Ops.PROGRAM and len(GraphRunner._all_devs(batch_devs, new_call)) == 1
class HCQGraph(GraphRunner):
def _access_resources(self, bufs:list[Buffer], write:list[int], new_dependency:Any):
if not hasattr(self, "deps"): self.deps = DepsTracker()
return self.deps.access_resources([UOp.from_buffer(b.base, dtypes.uint8)[b.offset:b.offset + b.nbytes] for b in bufs], write, new_dependency)
class HCQGraph(MultiGraphRunner):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.devices = list({cast(HCQCompiled, Device[b.device]) for (_,_,bufs,_) in self.calls for b in bufs})
@@ -84,7 +148,7 @@ class HCQGraph(MultiGraphRunner):
self.device_vars: dict[HCQCompiled, dict[str, int]] = {}
for j, ((_, ast, bufs, device_vars), runtime) in enumerate(zip(self.calls, self.runtimes)):
is_xfer = ast.op is Ops.COPY and hasattr(alc:=Device[bufs[0].device].allocator, '_transfer') and alc.supports_transfer \
is_xfer = ast.op is Ops.STORE and hasattr(alc:=Device[bufs[0].device].allocator, '_transfer') and alc.supports_transfer \
and bufs[0].device.split(":")[0] == bufs[1].device.split(":")[0]
ji_devs = [cast(HCQCompiled, Device[b.device]) for b in bufs] if is_xfer else []
is_rdma = len(ji_devs) > 0 and not any(d._is_cpu() for d in ji_devs) and len(set(d.peer_group for d in ji_devs)) > 1
@@ -196,7 +260,7 @@ class HCQGraph(MultiGraphRunner):
dest_queue.signal(dest_out_signal, dest_out_val)
self.num_rdma_ops[(dest_rdma, src_rdma)] += 1
elif ast.op is Ops.COPY:
elif ast.op is Ops.STORE:
dest, src = bufs[0], bufs[1]
uop_replace_j = dict(self.uop_replace[j])
for bufid in range(len(bufs)):
@@ -330,7 +394,7 @@ class HCQGraph(MultiGraphRunner):
try: [d.rdma_dev() for d in all_devs if not d._is_cpu()]
except RuntimeError: return False
if new_call.src[0].op is Ops.COPY:
if new_call.src[0].op is Ops.STORE:
# MOCKGPU is not supported, since it can't execute commands in parallel
is_xfer = len(set(type(d) for d in all_devs)) == 1 and hasattr(alc:=all_devs[0].allocator, '_transfer') and alc.supports_transfer
return is_xfer or (all_devs[0].hw_copy_queue_t is not None and not getattr(all_devs[0], 'iface', None).__class__.__name__.startswith("MOCK"))
+5 -21
View File
@@ -361,7 +361,8 @@ class HCQCompiled(Compiled, Generic[SignalType]):
signal_t:Type[SignalType]|None=None, comp_queue_t:Callable[..., HWQueue]|None=None, copy_queue_t:Callable[..., HWQueue]|None=None,
kernargs_size=(16 << 20), sigalloc_size=0x1000, can_recover:bool=False, arch=None):
from extra.hcq1.graph import HCQGraph
super().__init__(device, allocator, compilers, runtime, HCQGraph, arch=arch)
super().__init__(device, allocator, compilers, runtime, arch=arch)
self.graph = HCQGraph
self.peer_group = getattr(getattr(self, 'iface', None), 'peer_group', device.split(":")[0])
HCQCompiled.peer_groups[self.peer_group].append(self)
@@ -475,8 +476,9 @@ class HCQAllocatorBase(Allocator[HCQDeviceType], Generic[HCQDeviceType]):
This class implements basic copy operations following the HCQ API, utilizing both types of `HWQueue`.
"""
def __init__(self, dev:HCQDeviceType, batch_size:int=(2 << 20), batch_cnt:int=32, copy_bufs=None, **kwargs):
super().__init__(dev, **kwargs)
def __init__(self, dev:HCQDeviceType, batch_size:int=(2 << 20), batch_cnt:int=32, copy_bufs=None, supports_transfer:bool=True):
super().__init__(dev)
self.supports_transfer = supports_transfer
self.b = copy_bufs or [self._alloc(batch_size, BufferSpec(host=True)).buf for _ in range(batch_cnt)]
self.b_timeline, self.b_next = [0] * len(self.b), 0
@@ -521,24 +523,6 @@ class HCQAllocator(HCQAllocatorBase, Generic[HCQDeviceType]):
.signal(self.dev.timeline_signal, self.dev.next_timeline()).submit(self.dev)
self.b_timeline[self.b_next] = self.dev.timeline_value - 1
def copy_from_disk(self, dest:HCQBuffer, src, size):
def _get_temp_buf():
# Check if the next buffer is safe to be used (its signal has passed) and reserve it.
if self.b_timeline[(self.b_next + 1) % len(self.b)] <= self.dev.timeline_signal.value:
self.b_timeline[(self.b_next + 1) % len(self.b)], self.b_next = (1 << 64), (self.b_next + 1) % len(self.b)
return (self.b[self.b_next].cpu_view(), self.b_next)
return None
assert self.dev.hw_copy_queue_t is not None
with hcq_profile(self.dev, queue_type=self.dev.hw_copy_queue_t, desc=TracingKey(f"DISK -> {self.dev.device}", ret=size), enabled=PROFILE,
dev_suff="SDMA:0"):
for (batch_info, dst_off, src_off, copy_size) in src.device.allocator._copyout_sharded(src, size, _get_temp_buf, seg_len=self.b[0].size,
use_ioring=type(self.b[0].cpu_view()) is MMIOInterface):
self.dev.hw_copy_queue_t().wait(self.dev.timeline_signal, self.dev.timeline_value - 1) \
.copy(dest.offset(dst_off), self.b[batch_info[1]].offset(src_off), copy_size) \
.signal(self.dev.timeline_signal, self.dev.next_timeline()).submit(self.dev)
self.b_timeline[batch_info[1]] = self.dev.timeline_value - 1
def _copyout(self, dest:memoryview, src:HCQBuffer):
self.dev.synchronize()
if self.dev.hw_copy_queue_t is None:
+6 -5
View File
@@ -4,8 +4,9 @@ import os, ctypes, struct, hashlib, functools, importlib, mmap, errno, array, co
assert sys.platform != 'win32'
from dataclasses import dataclass
from extra.hcq1.hcq import HCQBuffer, HCQCompiled, HCQAllocator, HWQueue, CLikeArgsState, HCQSignal, HCQProgram, hcq_profile
from tinygrad.runtime.support.hcq import FileIOInterface
from tinygrad.runtime.support.hcq import MMIOInterface, BumpAllocator, hcq_filter_visible_devices
from tinygrad.runtime.support.system import FileIOInterface
from tinygrad.runtime.support.memory import MMIOInterface, BumpAllocator
from tinygrad.runtime.support.system import filter_visible_devices
from tinygrad.uop.ops import sint
from tinygrad.device import BufferStorage, Compiled, BufferSpec, TinyELF
from tinygrad.helpers import getenv, round_up, data64_le, DEBUG, PROFILE, ProfileEvent, lo32, hi32, colored, prod, TracingKey
@@ -641,7 +642,7 @@ class AMDProgram(HCQProgram['AMDDevice']):
class AMDAllocator(HCQAllocator['AMDDevice']):
def __init__(self, dev:AMDDevice):
super().__init__(dev, copy_bufs=getattr(dev.iface, 'copy_bufs', None),
supports_copy_from_disk=dev.has_sdma_queue, supports_transfer=dev.has_sdma_queue and not dev.is_usb())
supports_transfer=dev.has_sdma_queue and not dev.is_usb())
def _alloc(self, size:int, options:BufferSpec) -> BufferStorage:
opaque = self.dev.iface.alloc(size, host=options.host, uncached=options.uncached, cpu_access=options.cpu_access or not self.dev.has_sdma_queue)
@@ -780,7 +781,7 @@ class KFDIface:
if KFDIface.kfd is None:
KFDIface.kfd = FileIOInterface("/dev/kfd", os.O_RDWR)
gpus = [g for g in FileIOInterface(kfd_topo_path).listdir() if self._is_usable_gpu(FileIOInterface(f"{kfd_topo_path}/{g}/gpu_id"))]
KFDIface.gpus = hcq_filter_visible_devices(sorted(gpus, key=lambda x: int(x.split('/')[-1])), "AMD")
KFDIface.gpus = filter_visible_devices(sorted(gpus, key=lambda x: int(x.split('/')[-1])), "AMD")
KFDIface.count = len(KFDIface.gpus)
if device_id >= len(KFDIface.gpus): raise RuntimeError(f"No device found for {device_id}. Requesting more devices than the system has?")
@@ -996,7 +997,7 @@ class PCIIface(PCIIfaceBase):
class USBIface(PCIIface):
def __init__(self, dev, dev_id): # pylint: disable=super-init-not-called
if dev_id >= len(visible:=hcq_filter_visible_devices(USB3.list_devices(0xADD1, 0x0001) + USB3.list_devices(0x3801, 0x0001), "AMD")):
if dev_id >= len(visible:=filter_visible_devices(USB3.list_devices(0xADD1, 0x0001) + USB3.list_devices(0x3801, 0x0001), "AMD")):
raise RuntimeError(f"AMD:{dev_id} does not exist ({pluralize('device', len(visible))} available)")
self.dev, self.pci_dev, self.vram_bar, self.count = dev, USBPCIDevice("AM", *visible[dev_id]), 0, len(visible)
self.dev_impl = AMDev(self.pci_dev)
+1 -1
View File
@@ -3,7 +3,7 @@ import mmap, struct, functools, atexit
from typing import cast
from tinygrad.uop.ops import sint
from extra.hcq1.hcq import HCQCompiled, HCQAllocatorBase, HCQAllocator, HWQueue, HCQBuffer
from tinygrad.runtime.support.hcq import FileIOInterface
from tinygrad.runtime.support.system import FileIOInterface
from tinygrad.runtime.support.system import System, PCIIfaceBase, PCIAllocationMeta
from tinygrad.runtime.support.memory import VirtMapping, AddrSpace
from extra.mlx_driver.mlxdev import MLXDev, MLXQP
+2 -1
View File
@@ -1,7 +1,8 @@
from __future__ import annotations
import os, mmap, array, functools, contextlib, itertools, struct, socket, subprocess, time, enum, atexit
from tinygrad.helpers import getenv, temp, ceildiv, unwrap, fetch, system, _ensure_downloads_dir, DEBUG, flatten
from tinygrad.runtime.support.hcq import FileIOInterface, MMIOInterface
from tinygrad.runtime.support.memory import MMIOInterface
from tinygrad.runtime.support.system import FileIOInterface
from tinygrad.runtime.support.system import PCIDevice, System
class RemoteCmd(enum.IntEnum):
+36 -36
View File
@@ -32,8 +32,8 @@ class TestHCQ(unittest.TestCase):
def setUp(self):
TestHCQ.d0.synchronize()
TestHCQ.a.uop.buffer.copy_from(Buffer("PYTHON", 2, dtypes.float, opaque=memoryview(bytearray(struct.pack("ff", 0, 1)))))
TestHCQ.b.uop.buffer.copy_from(Buffer("PYTHON", 2, dtypes.float, opaque=memoryview(bytearray(struct.pack("ff", 0, 0)))))
TestHCQ.a.uop.buffer.copy_from(Buffer("PYTHON", 8, initial_value=bytes(struct.pack("ff", 0, 1))))
TestHCQ.b.uop.buffer.copy_from(Buffer("PYTHON", 8, initial_value=bytes(struct.pack("ff", 0, 0))))
TestHCQ.d0.synchronize() # wait for copyins to complete
# Test signals
@@ -171,8 +171,8 @@ class TestHCQ(unittest.TestCase):
prg = to_program(replace_opts(si.src[0], [Opt(op=OptOps.SPLIT, axis=0, arg=(3, AxisType.LOCAL)) for _ in range(3)]), TestHCQ.d0.renderer)
runtime = get_runtime(Device.DEFAULT, prg)
zb = Buffer(Device.DEFAULT, 3 * 3 * 3, dtypes.int, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
zt = Buffer(Device.DEFAULT, 3 * 3 * 3, dtypes.int, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
zb = Buffer(Device.DEFAULT, 3 * 3 * 3 * 4, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
zt = Buffer(Device.DEFAULT, 3 * 3 * 3 * 4, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
ctypes.memset(zb._buf.va_addr, 0, zb.nbytes)
kernargs = runtime.fill_kernargs([zt._buf, zb._buf])
@@ -211,8 +211,8 @@ class TestHCQ(unittest.TestCase):
if TestHCQ.d0.hw_copy_queue_t is None: self.skipTest("device does not support copy queue")
sz = 64 << 20
buf1 = Buffer(Device.DEFAULT, sz, dtypes.int8, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, sz, dtypes.int8, options=BufferSpec(host=True, nolru=True)).ensure_allocated()
buf1 = Buffer(Device.DEFAULT, sz, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, sz, options=BufferSpec(host=True, nolru=True)).ensure_allocated()
ctypes.memset(buf2._buf.va_addr, 1, sz)
TestHCQ.d0.hw_copy_queue_t().wait(TestHCQ.d0.timeline_signal, TestHCQ.d0.timeline_value - 1) \
@@ -231,8 +231,8 @@ class TestHCQ(unittest.TestCase):
# NOTE: these must be a multiple of 8 for .view(fmt='Q') to work
for sz in [(1 << 32) - 8, (1 << 32), (1 << 32) + 8, (5 << 30), (6 << 30) - 0x4642ee0]:
buf1 = Buffer(Device.DEFAULT, sz, dtypes.int8, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, sz, dtypes.int8, options=BufferSpec(host=True, nolru=True)).ensure_allocated()
buf1 = Buffer(Device.DEFAULT, sz, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, sz, options=BufferSpec(host=True, nolru=True)).ensure_allocated()
ctypes.memset(buf2._buf.va_addr, 0x3e, sz)
buf2_q_view = buf2.host.view(fmt='Q')
@@ -274,8 +274,8 @@ class TestHCQ(unittest.TestCase):
virt_dest_addr = Variable("virt_dest_addr", 0, 0xffffffffffffffff, dtypes.uint64)
sz = 64 << 20
buf1 = Buffer(Device.DEFAULT, sz, dtypes.int8, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, sz, dtypes.int8, options=BufferSpec(host=True, nolru=True)).ensure_allocated()
buf1 = Buffer(Device.DEFAULT, sz, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, sz, options=BufferSpec(host=True, nolru=True)).ensure_allocated()
ctypes.memset(buf2._buf.va_addr, 1, sz)
q = TestHCQ.d0.hw_copy_queue_t().wait(TestHCQ.d0.timeline_signal, TestHCQ.d0.timeline_value - 1) \
@@ -352,8 +352,8 @@ class TestHCQ(unittest.TestCase):
# THEORY: the bandwidth is low here because it's only using one SDMA queue. I suspect it's more stable like this at least.
SZ = 200_000_000
a = Buffer(Device.DEFAULT, SZ, dtypes.uint8, options=BufferSpec(nolru=True)).allocate()
b = Buffer(Device.DEFAULT, SZ, dtypes.uint8, options=BufferSpec(nolru=True)).allocate()
a = Buffer(Device.DEFAULT, SZ, options=BufferSpec(nolru=True)).allocate()
b = Buffer(Device.DEFAULT, SZ, options=BufferSpec(nolru=True)).allocate()
sig_st, sig_en = TestHCQ.d0.new_signal(), TestHCQ.d0.new_signal()
st = time.perf_counter()
@@ -380,8 +380,8 @@ class TestHCQ(unittest.TestCase):
except Exception: self.skipTest("no multidevice, test skipped")
SZ = 200_000_000
b = Buffer(f"{Device.DEFAULT}:1", SZ, dtypes.uint8, options=BufferSpec(nolru=True)).allocate()
a = Buffer(Device.DEFAULT, SZ, dtypes.uint8, options=BufferSpec(nolru=True)).allocate()
b = Buffer(f"{Device.DEFAULT}:1", SZ, options=BufferSpec(nolru=True)).allocate()
a = Buffer(Device.DEFAULT, SZ, options=BufferSpec(nolru=True)).allocate()
TestHCQ.d0.allocator._map(b._buf)
sig_st, sig_en = TestHCQ.d0.new_signal(), TestHCQ.d0.new_signal()
@@ -420,8 +420,8 @@ class TestHCQ(unittest.TestCase):
def test_small_copies_from_host_buf(self):
if TestHCQ.d0.hw_copy_queue_t is None: self.skipTest("device does not support copy queue")
buf1 = Buffer(Device.DEFAULT, 1, dtypes.int8, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, 1, dtypes.int8, options=BufferSpec(host=True, nolru=True)).ensure_allocated()
buf1 = Buffer(Device.DEFAULT, 1, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, 1, options=BufferSpec(host=True, nolru=True)).ensure_allocated()
for i in range(256):
ctypes.memset(buf2._buf.va_addr, i, 1)
@@ -437,9 +437,9 @@ class TestHCQ(unittest.TestCase):
def test_small_copies_from_host_buf_intercopy(self):
if TestHCQ.d0.hw_copy_queue_t is None: self.skipTest("device does not support copy queue")
buf1 = Buffer(Device.DEFAULT, 1, dtypes.int8, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, 1, dtypes.int8, options=BufferSpec(nolru=True)).ensure_allocated()
buf3 = Buffer(Device.DEFAULT, 1, dtypes.int8, options=BufferSpec(host=True, nolru=True)).ensure_allocated()
buf1 = Buffer(Device.DEFAULT, 1, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, 1, options=BufferSpec(nolru=True)).ensure_allocated()
buf3 = Buffer(Device.DEFAULT, 1, options=BufferSpec(host=True, nolru=True)).ensure_allocated()
for i in range(256):
ctypes.memset(buf3._buf.va_addr, i, 1)
@@ -459,9 +459,9 @@ class TestHCQ(unittest.TestCase):
try: _ = Device[f"{Device.DEFAULT}:1"]
except Exception: self.skipTest("no multidevice, test skipped")
buf1 = Buffer(Device.DEFAULT, 1, dtypes.int8, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(f"{Device.DEFAULT}:1", 1, dtypes.int8, options=BufferSpec(nolru=True)).ensure_allocated()
buf3 = Buffer(Device.DEFAULT, 1, dtypes.int8, options=BufferSpec(host=True, nolru=True)).ensure_allocated()
buf1 = Buffer(Device.DEFAULT, 1, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(f"{Device.DEFAULT}:1", 1, options=BufferSpec(nolru=True)).ensure_allocated()
buf3 = Buffer(Device.DEFAULT, 1, options=BufferSpec(host=True, nolru=True)).ensure_allocated()
TestHCQ.d0.allocator._map(buf2._buf)
for i in range(256):
@@ -482,8 +482,8 @@ class TestHCQ(unittest.TestCase):
prg = to_program(b.schedule_linear().src[-1].src[0], TestHCQ.d0.renderer)
runtime = get_runtime(TestHCQ.d0.device, prg)
buf1 = Buffer(Device.DEFAULT, 2, dtypes.int8, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, 2, dtypes.int8, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
buf1 = Buffer(Device.DEFAULT, 2, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, 2, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
kernargs_ptr = runtime.fill_kernargs([buf1._buf, buf2._buf])
@@ -503,9 +503,9 @@ class TestHCQ(unittest.TestCase):
def test_memory_barrier_before_copy(self):
if TestHCQ.d0.hw_copy_queue_t is None: self.skipTest("device does not support copy queue")
buf1 = Buffer(Device.DEFAULT, 1, dtypes.int8, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, 1, dtypes.int8, options=BufferSpec(nolru=True)).ensure_allocated()
buf3 = Buffer(Device.DEFAULT, 1, dtypes.int8, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
buf1 = Buffer(Device.DEFAULT, 1, options=BufferSpec(nolru=True)).ensure_allocated()
buf2 = Buffer(Device.DEFAULT, 1, options=BufferSpec(nolru=True)).ensure_allocated()
buf3 = Buffer(Device.DEFAULT, 1, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
for i in range(256):
ctypes.memset(buf3._buf.va_addr, i, 1)
@@ -526,7 +526,7 @@ class TestHCQ(unittest.TestCase):
assert buf2.as_memoryview()[0] == i
def test_write(self):
buf = Buffer(Device.DEFAULT, 4, dtypes.uint32, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
buf = Buffer(Device.DEFAULT, 16, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
try:
TestHCQ.d0.hw_compute_queue_t().write(buf._buf, 0x42) \
@@ -539,7 +539,7 @@ class TestHCQ(unittest.TestCase):
assert buf.as_memoryview().cast("I")[0] == 0x42
def test_poll_bit_set(self):
buf = Buffer(Device.DEFAULT, 4, dtypes.uint32, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
buf = Buffer(Device.DEFAULT, 16, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
try:
TestHCQ.d0.hw_compute_queue_t().write(buf._buf, 0x01000000, b64=False) \
@@ -551,7 +551,7 @@ class TestHCQ(unittest.TestCase):
TestHCQ.d0.timeline_value += 1
def test_poll_bit_clear(self):
buf = Buffer(Device.DEFAULT, 4, dtypes.uint32, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
buf = Buffer(Device.DEFAULT, 16, options=BufferSpec(cpu_access=True, nolru=True)).ensure_allocated()
try:
TestHCQ.d0.hw_compute_queue_t().write(buf._buf, 0xFE000000, b64=False) \
@@ -566,7 +566,7 @@ class TestHCQ(unittest.TestCase):
if Device[Device.DEFAULT].hw_copy_queue_t is None: self.skipTest("skip device without copy queue")
sz = 0x2000
cpu_buffer = Buffer("CPU", sz, dtypes.uint8, options=BufferSpec(cpu_access=True)).ensure_allocated()
cpu_buffer = Buffer("CPU", sz, options=BufferSpec(cpu_access=True)).ensure_allocated()
cpu_buffer.host.view(fmt='B')[:] = bytes([x & 0xff for x in range(sz)])
for devid in range(6):
@@ -575,7 +575,7 @@ class TestHCQ(unittest.TestCase):
try: d = Device[f"{Device.DEFAULT}:{devid}"]
except Exception: break
local_buf = Buffer(f"{Device.DEFAULT}:{devid}", sz, dtypes.uint8, options=BufferSpec(cpu_access=True)).ensure_allocated()
local_buf = Buffer(f"{Device.DEFAULT}:{devid}", sz, options=BufferSpec(cpu_access=True)).ensure_allocated()
d.allocator._map(cpu_buffer._buf)
@@ -585,7 +585,7 @@ class TestHCQ(unittest.TestCase):
d.timeline_signal.wait(d.timeline_value)
d.timeline_value += 1
np.testing.assert_equal(cpu_buffer.numpy(), local_buf.numpy(), "failed")
np.testing.assert_equal(np.frombuffer(cpu_buffer.as_memoryview(), np.uint8), np.frombuffer(local_buf.as_memoryview(), np.uint8), "failed")
@unittest.skipUnless(MOCKGPU and not (DEV.device == "AMD" and DEV.interface == "MOCKPCI"), "Emulate this on MOCKGPU to check the path in CI")
def test_on_device_hang(self):
@@ -637,8 +637,8 @@ class TestHCQ(unittest.TestCase):
if TestHCQ.d0.peer_group == d1.peer_group: self.skipTest("devices in same peer group, no RDMA path")
SZ = 200_000_000
a = Buffer(Device.DEFAULT, SZ, dtypes.uint8, options=BufferSpec(nolru=True)).allocate()
b = Buffer(f"{Device.DEFAULT}:7", SZ, dtypes.uint8, options=BufferSpec(nolru=True)).allocate()
a = Buffer(Device.DEFAULT, SZ, options=BufferSpec(nolru=True)).allocate()
b = Buffer(f"{Device.DEFAULT}:7", SZ, options=BufferSpec(nolru=True)).allocate()
# warmup
TestHCQ.d0.allocator._transfer(a._buf, b._buf, SZ, src_dev=d1, dest_dev=TestHCQ.d0)
@@ -655,7 +655,7 @@ class TestHCQ(unittest.TestCase):
print(f"cross device rdma copy: {et_ms:.2f} ms, {gb_s:.2f} GB/s")
assert 1 <= gb_s <= 100
np.testing.assert_equal(a.numpy(), b.numpy(), "failed")
np.testing.assert_equal(np.frombuffer(a.as_memoryview(), np.uint8), np.frombuffer(b.as_memoryview(), np.uint8), "failed")
if __name__ == "__main__":
unittest.main()
+3 -3
View File
@@ -12,14 +12,14 @@ extern "C" __attribute__((global)) void __attribute__((amdgpu_flat_work_group_si
}
""")
dev.compiler.disassemble(mbin)
buf0 = Buffer(Device.DEFAULT, 1*65537, dtypes.uint8).ensure_allocated()
buf0 = Buffer(Device.DEFAULT, 1*65537).ensure_allocated()
prg = dev.runtime("write_ones", mbin)
prg(buf0._buf, global_size=(1,65537,1), local_size=(1,1,1), wait=True)
import numpy as np
def to_np(buf): return np.frombuffer(buf.as_memoryview().cast(buf.dtype.fmt), dtype=_to_np_dtype(buf.dtype))
def to_np(buf, dtype): return np.frombuffer(buf.as_memoryview(), dtype=_to_np_dtype(dtype))
big = to_np(buf0)
big = to_np(buf0, dtypes.uint8)
print(big)
print((big-1).nonzero())
@@ -1,73 +0,0 @@
from __future__ import annotations
import functools, pathlib
from tinygrad import Tensor, dtypes
from tinygrad.uop.ops import UOp, Ops, KernelInfo
from tinygrad.renderer import Estimates
from extra.llama_kernels import NUM_WG, THREADS_PER_WG, compile_cpp, alloc_like, dname_of
# module-level mailbox: grad_xw13 UOp -> (grad_xw13_fp8 UOp, delayed amax UOp)
# lets cdna_asm_gemm's bwd reuse the fp8 companion produced by the fused silu_mul bwd kernel
# instead of doing a redundant bf16 -> fp8 quantize.
_grad_fp8_mailbox:dict[UOp, tuple[UOp, UOp]] = {}
@functools.cache
def _custom_fused_bwd_w13(grad_xw13_fp8:UOp, grad_amax_next:UOp, grad_amax:UOp,
xw13:UOp, grad_x2:UOp, amax_state:UOp, grad_amax_state:UOp, dname:str) -> UOp:
hidden = xw13.shape[2] // 2
n_elems = xw13.shape[0] * xw13.shape[1] * hidden
threads, workgroups = UOp.special(THREADS_PER_WG, "lidx0"), UOp.special(NUM_WG, "gidx0")
mem = n_elems * 2 * 3 + n_elems * 2 + 4 + 4
sink = UOp.sink(grad_xw13_fp8.base, grad_amax_next.base, grad_amax.base,
xw13.base, grad_x2.base, amax_state.base, grad_amax_state.base, threads, workgroups,
arg=KernelInfo(f"fused_silu_mul_bwd_w13_{n_elems}", estimates=Estimates(ops=10*n_elems, mem=mem)))
src, lib = compile_cpp(pathlib.Path(__file__).parent, "cast_amax_bwd_w13.cpp", n_elems, hidden)
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)),
UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=lib)))
@functools.cache
def _custom_fused_cast_amax_w13(fp8_out:UOp, amax_out:UOp, xw13:UOp, amax_state:UOp, grad_amax_state:UOp,
next_grad_amax_state:UOp, dname:str) -> UOp:
# NOTE: grad_amax_state is plumbed through as an unused fwd input so the bwd kernel can read it via kernel.src
hidden = xw13.shape[2] // 2
n_elems = xw13.shape[0] * xw13.shape[1] * hidden
threads, workgroups = UOp.special(THREADS_PER_WG, "lidx0"), UOp.special(NUM_WG, "gidx0")
mem = n_elems * 2 * 2 + n_elems + 4
sink = UOp.sink(fp8_out.base, amax_out.base, xw13.base, amax_state.base, threads, workgroups,
arg=KernelInfo(f"fused_silu_mul_cast_amax_w13_{n_elems}", estimates=Estimates(ops=5*n_elems, mem=mem)))
src, lib = compile_cpp(pathlib.Path(__file__).parent, "cast_amax_fwd_w13.cpp", n_elems, hidden)
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)),
UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=lib)))
def _fused_quantize_bwd_w13(gradient:UOp, kernel:UOp):
_, _, xw13, amax_state, grad_amax_state, next_grad_amax_state = kernel.src[1:]
device = xw13.device
axis = xw13.axis if isinstance(device, tuple) else None
grad_xw13_fp8 = alloc_like(xw13.shape, dtypes.fp8e4m3, device, axis)
grad_amax_next = Tensor(next_grad_amax_state, device=device)
grad_amax_state_t = Tensor(grad_amax_state, device=device)
fxn = functools.partial(_custom_fused_bwd_w13, dname=dname_of(device))
grad_amax = grad_amax_state_t.empty_like()
grad_xw13_fp8, grad_amax_next, grad_amax, *_ = Tensor.custom_kernel(
grad_xw13_fp8, grad_amax_next, grad_amax,
Tensor(xw13, device=device), Tensor(gradient, device=device).cast(dtypes.bfloat16),
Tensor(amax_state, device=device), grad_amax_state_t, fxn=fxn)
grad_xw13_uop = grad_xw13_fp8.uop.cast(dtypes.bfloat16)
assert grad_xw13_fp8.uop.op is Ops.AFTER, f"expected AFTER, got {grad_xw13_fp8.uop.op}"
# Stash fp8 companion for cdna_asm_gemm's bwd to attach to grad_a.
_grad_fp8_mailbox[grad_xw13_uop] = (grad_xw13_fp8.uop, grad_amax_state_t.uop)
return (None, None, grad_xw13_uop, None, None, None)
def fused_quantize_fp8_w13(xw13:Tensor, amax_state:Tensor, fp8_dtype, grad_amax_state:Tensor,
next_grad_amax_state:Tensor, amax_out:Tensor) -> Tensor:
# NOTE: silu(xw1)*xw3 -> fp8 + amax over fused xw13 layout. Returns fp8.
# grad_amax_state: delayed amax for grad_xw13 fp8 quantization in the backward.
assert xw13.dtype == dtypes.bfloat16, f"expected bf16, got {xw13.dtype}"
MBS, SEQ, H2 = xw13.shape
assert H2 % 2 == 0, f"w13 last-axis must be even, got {H2}"
HIDDEN = H2 // 2
axis = xw13.uop.axis if isinstance(xw13.device, tuple) else None
fp8_out = alloc_like((MBS, SEQ, HIDDEN), fp8_dtype, xw13.device, axis)
fxn = functools.partial(_custom_fused_cast_amax_w13, dname=dname_of(xw13.device))
fp8_out, amax_out, *_ = Tensor.custom_kernel(fp8_out, amax_out, xw13, amax_state, grad_amax_state, next_grad_amax_state,
fxn=fxn, grad_fxn=_fused_quantize_bwd_w13)
return fp8_out
@@ -1,97 +0,0 @@
#include <hip/hip_runtime.h>
#include <hip/hip_bf16.h>
#include <hip/hip_fp8.h>
#ifndef N_ELEMS
#define N_ELEMS 234881024
#endif
#ifndef HIDDEN
#define HIDDEN 14336
#endif
#ifndef NUM_WG
#define NUM_WG 1024
#endif
#ifndef THREADS_PER_WG
#define THREADS_PER_WG 256
#endif
constexpr int VEC = 8;
constexpr float FP8_MAX = 448.0f;
static_assert(N_ELEMS % VEC == 0, "N_ELEMS must be divisible by VEC");
static_assert(HIDDEN % VEC == 0, "HIDDEN must be divisible by VEC");
// fused silu*mul backward, three outputs in a single HBM pass:
// 1) fp8 grad_xw13_fp8 — delayed-scale quantize using grad_amax_state (mailbox to matmul bwd)
// 2) fp32 grad_amax_next — scalar |grad_xw13| via global atomic max
// 3) fp32 grad_amax_out — delayed grad amax used for quantize/GEMM epilogue scale
// grad_amax_state is read for the fp8 scale. The store of new_grad_amax into grad_amax_state's
// buffer is built in Python as a separate effect and threaded into grad_a via .after(store).
extern "C" __global__ __launch_bounds__(THREADS_PER_WG) void
fused_silu_mul_bwd_w13(
__hip_fp8_storage_t* __restrict__ grad_xw13_fp8_out, // fp8, 2*N_ELEMS
float* __restrict__ grad_amax_next, // fp32 scalar, initialized to 0 before launch
float* __restrict__ grad_amax_out, // fp32 scalar delayed grad amax
const __hip_bfloat16* __restrict__ xw13, // bf16, 2*N_ELEMS
const __hip_bfloat16* __restrict__ grad_x2, // bf16, N_ELEMS
const float* __restrict__ amax_state, // fp32 scalar (fwd x2 amax)
const float* __restrict__ grad_amax_state) // fp32 scalar (delayed grad amax)
{
__shared__ float sdata[THREADS_PER_WG];
const int tid = threadIdx.x;
const int wg = blockIdx.x;
const int gid = wg * THREADS_PER_WG + tid;
const int stride_elems = NUM_WG * THREADS_PER_WG * VEC;
const float scale = FP8_MAX / (static_cast<float>(*amax_state) + 1e-8f);
const float grad_amax = static_cast<float>(*grad_amax_state);
const float g_scale = FP8_MAX / (grad_amax + 1e-8f);
float local_max = 0.0f;
if (wg == 0 && tid == 0) *grad_amax_out = grad_amax;
for (int base = gid * VEC; base < N_ELEMS; base += stride_elems) {
const int outer = base / HIDDEN;
const int inner = base % HIDDEN;
const int xw1_off = outer * 2 * HIDDEN + inner;
const int xw3_off = xw1_off + HIDDEN;
float4 x1_raw = *reinterpret_cast<const float4*>(&xw13[xw1_off]);
float4 x3_raw = *reinterpret_cast<const float4*>(&xw13[xw3_off]);
float4 g_raw = *reinterpret_cast<const float4*>(&grad_x2[base]);
const __hip_bfloat16 *x1 = reinterpret_cast<const __hip_bfloat16*>(&x1_raw);
const __hip_bfloat16 *x3 = reinterpret_cast<const __hip_bfloat16*>(&x3_raw);
const __hip_bfloat16 *gv = reinterpret_cast<const __hip_bfloat16*>(&g_raw);
__hip_fp8_storage_t fp8_1[VEC], fp8_3[VEC];
#pragma unroll
for (int i = 0; i < VEC; i++) {
const float f1 = static_cast<float>(x1[i]);
const float f3 = static_cast<float>(x3[i]);
const float fg = static_cast<float>(gv[i]);
const float sig = 1.0f / (1.0f + __expf(-f1));
const float silu = f1 * sig;
const float silu_prime = sig + silu * (1.0f - sig);
const float gs = fg * scale;
const float g1 = gs * silu_prime * f3;
const float g3 = gs * silu;
local_max = fmaxf(local_max, fmaxf(fabsf(g1), fabsf(g3)));
fp8_1[i] = __hip_cvt_float_to_fp8(fmaxf(-FP8_MAX, fminf(FP8_MAX, g1 * g_scale)), __HIP_SATFINITE, __HIP_E4M3);
fp8_3[i] = __hip_cvt_float_to_fp8(fmaxf(-FP8_MAX, fminf(FP8_MAX, g3 * g_scale)), __HIP_SATFINITE, __HIP_E4M3);
}
*reinterpret_cast<uint64_t*>(&grad_xw13_fp8_out[xw1_off]) = *reinterpret_cast<uint64_t*>(fp8_1);
*reinterpret_cast<uint64_t*>(&grad_xw13_fp8_out[xw3_off]) = *reinterpret_cast<uint64_t*>(fp8_3);
}
sdata[tid] = local_max;
__syncthreads();
for (int s = THREADS_PER_WG / 2; s > 0; s >>= 1) {
if (tid < s) sdata[tid] = fmaxf(sdata[tid], sdata[tid + s]);
__syncthreads();
}
if (tid == 0 && sdata[0] > *grad_amax_next)
atomicMax(reinterpret_cast<int32_t*>(grad_amax_next), __float_as_int(sdata[0]));
}
@@ -1,79 +0,0 @@
#include <hip/hip_runtime.h>
#include <hip/hip_bf16.h>
#include <hip/hip_fp8.h>
#ifndef N_ELEMS
#define N_ELEMS 234881024
#endif
#ifndef HIDDEN
#define HIDDEN 14336
#endif
#ifndef NUM_WG
#define NUM_WG 1024
#endif
#ifndef THREADS_PER_WG
#define THREADS_PER_WG 256
#endif
constexpr int VEC = 8;
constexpr float FP8_MAX = 448.0f;
static_assert(N_ELEMS % VEC == 0, "N_ELEMS must be divisible by VEC");
static_assert(HIDDEN % VEC == 0, "HIDDEN must be divisible by VEC (so VEC loads don't straddle block boundary)");
extern "C" __global__ __launch_bounds__(THREADS_PER_WG) void
fused_silu_mul_cast_amax_w13(
__hip_fp8_storage_t* __restrict__ fp8_out, // fp8, N_ELEMS
float* __restrict__ amax_out, // fp32 scalar, initialized to 0 before launch
const __hip_bfloat16* __restrict__ xw13, // bf16, 2*N_ELEMS
const float* __restrict__ amax_state) // fp32 scalar
{
__shared__ float sdata[THREADS_PER_WG];
const int tid = threadIdx.x;
const int wg = blockIdx.x;
const int gid = wg * THREADS_PER_WG + tid;
const int stride_elems = NUM_WG * THREADS_PER_WG * VEC;
const float scale = FP8_MAX / (static_cast<float>(*amax_state) + 1e-8f);
float local_max = 0.0f;
// grid-stride over 8-element groups
for (int base = gid * VEC; base < N_ELEMS; base += stride_elems) {
// interleaved xw13 layout: xw1 and xw3 are not contiguous halves
const int outer = base / HIDDEN;
const int inner = base % HIDDEN;
const int xw1_off = outer * 2 * HIDDEN + inner;
const int xw3_off = xw1_off + HIDDEN;
float4 x1_raw = *reinterpret_cast<const float4*>(&xw13[xw1_off]);
float4 x3_raw = *reinterpret_cast<const float4*>(&xw13[xw3_off]);
const __hip_bfloat16 *x1 = reinterpret_cast<const __hip_bfloat16*>(&x1_raw);
const __hip_bfloat16 *x3 = reinterpret_cast<const __hip_bfloat16*>(&x3_raw);
__hip_fp8_storage_t out[VEC];
#pragma unroll
for (int i = 0; i < VEC; i++) {
const float f1 = static_cast<float>(x1[i]);
const float f3 = static_cast<float>(x3[i]);
const float silu = f1 / (1.0f + __expf(-f1));
const float x2 = silu * f3;
local_max = fmaxf(local_max, fabsf(x2));
const float x_scaled = fmaxf(-FP8_MAX, fminf(FP8_MAX, x2 * scale));
out[i] = __hip_cvt_float_to_fp8(x_scaled, __HIP_SATFINITE, __HIP_E4M3);
}
*reinterpret_cast<uint64_t*>(&fp8_out[base]) = *reinterpret_cast<uint64_t*>(out);
}
// LDS tree reduction: per-workgroup amax, then global atomic into the scalar.
sdata[tid] = local_max;
__syncthreads();
for (int s = THREADS_PER_WG / 2; s > 0; s >>= 1) {
if (tid < s) sdata[tid] = fmaxf(sdata[tid], sdata[tid + s]);
__syncthreads();
}
if (tid == 0 && sdata[0] > *amax_out) atomicMax(reinterpret_cast<int32_t*>(amax_out), __float_as_int(sdata[0]));
}
@@ -1,6 +1,6 @@
import functools
from tinygrad import Tensor, dtypes
from tinygrad.uop.ops import UOp, Ops, KernelInfo, AxisType
from tinygrad.uop.ops import UOp, Ops, KernelInfo
@functools.cache
def _custom_fused_ce_loss_fwd(loss_out:UOp, max_out:UOp, lse_out:UOp, logits:UOp, targets:UOp,
@@ -9,13 +9,13 @@ def _custom_fused_ce_loss_fwd(loss_out:UOp, max_out:UOp, lse_out:UOp, logits:UOp
b = row // seq
s = row % seq
v_max = UOp.range(vocab, 1, axis_type=AxisType.REDUCE)
v_max = UOp.range(vocab, 1)
row_max = logits[b, s, v_max].cast(dtypes.float).reduce(v_max, arg=Ops.MAX)
v_lse = UOp.range(vocab, 2, axis_type=AxisType.REDUCE)
v_lse = UOp.range(vocab, 2)
row_lse = (logits[b, s, v_lse].cast(dtypes.float) - row_max).exp().reduce(v_lse, arg=Ops.ADD).log() + row_max
v_smooth = UOp.range(vocab, 3, axis_type=AxisType.REDUCE)
v_smooth = UOp.range(vocab, 3)
target = logits[b, s, targets[row].cast(dtypes.weakint)].cast(dtypes.float)
mean_logits = logits[b, s, v_smooth].cast(dtypes.float).reduce(v_smooth, arg=Ops.ADD) / vocab
loss = row_lse - (1.0 - label_smoothing) * target - label_smoothing * mean_logits
@@ -1,150 +0,0 @@
from __future__ import annotations
import functools, pathlib
from tinygrad import Tensor, dtypes
from tinygrad.uop.ops import UOp, Ops, KernelInfo
from tinygrad.renderer import Estimates
from extra.llama_kernels import NUM_WG, THREADS_PER_WG, alloc_like, alloc_local, dname_of, compile_hip
def _src() -> str: return (pathlib.Path(__file__).parent/"fused_rmsnorm_mul_quantize_fp8.cpp").read_text()
def _src_bwd() -> str: return (pathlib.Path(__file__).parent/"fused_rmsnorm_mul_quantize_fp8_bwd.cpp").read_text()
@functools.cache
def _custom_fwd(fp8_out:UOp, x_normed_out:UOp, rrms_out:UOp, amax_out:UOp,
x:UOp, weight:UOp, amax_state:UOp, dname:str, eps_val:float) -> UOp:
MBS, SEQ, HIDDEN = x.shape
n_elems = MBS * SEQ * HIDDEN
threads, workgroups = UOp.special(THREADS_PER_WG, "lidx0"), UOp.special(NUM_WG, "gidx0")
mem = n_elems * 2 + n_elems + MBS * SEQ * 4 + n_elems + HIDDEN * 2 + 4 + 4
sink = UOp.sink(fp8_out.base, x_normed_out.base, rrms_out.base, amax_out.base,
x.base, weight.base, amax_state.base, threads, workgroups,
arg=KernelInfo(f"fused_rmsnorm_mul_quantize_fp8_{n_elems}_h{HIDDEN}_eps{eps_val:.0e}",
estimates=Estimates(ops=6*n_elems, mem=mem)))
defines = [f"-DN_ELEMS={n_elems}", f"-DHIDDEN={HIDDEN}", f"-DNUM_WG={NUM_WG}", f"-DTHREADS_PER_WG={THREADS_PER_WG}",
f"-DEPS_LITERAL={eps_val}f"]
src = _src()
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)),
UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=compile_hip(src, defines))))
@functools.cache
def _custom_fwd_add(fp8_out:UOp, h_out:UOp, x_normed_out:UOp, rrms_out:UOp, amax_out:UOp,
x:UOp, residual:UOp, weight:UOp, amax_state:UOp, dname:str, eps_val:float) -> UOp:
MBS, SEQ, HIDDEN = x.shape
n_elems = MBS * SEQ * HIDDEN
threads, workgroups = UOp.special(THREADS_PER_WG, "lidx0"), UOp.special(NUM_WG, "gidx0")
mem = n_elems * 2 * 4 + MBS * SEQ * 4 + HIDDEN * 2 + 4 + 4
sink = UOp.sink(fp8_out.base, h_out.base, x_normed_out.base, rrms_out.base, amax_out.base,
x.base, residual.base, weight.base, amax_state.base, threads, workgroups,
arg=KernelInfo(f"fused_add_rmsnorm_mul_quantize_fp8_{n_elems}_h{HIDDEN}_eps{eps_val:.0e}",
estimates=Estimates(ops=7*n_elems, mem=mem)))
defines = [f"-DN_ELEMS={n_elems}", f"-DHIDDEN={HIDDEN}", f"-DNUM_WG={NUM_WG}", f"-DTHREADS_PER_WG={THREADS_PER_WG}",
f"-DEPS_LITERAL={eps_val}f", f"-DHAS_RESIDUAL=1"]
src = _src()
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)),
UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=compile_hip(src, defines))))
@functools.cache
def _custom_bwd(grad_x:UOp, grad_weight_partial:UOp,
grad_fp8:UOp, x_normed:UOp, rrms:UOp, weight:UOp, amax_state:UOp, dname:str) -> UOp:
MBS, SEQ, HIDDEN = x_normed.shape
n_elems = MBS * SEQ * HIDDEN
threads, workgroups = UOp.special(THREADS_PER_WG, "lidx0"), UOp.special(NUM_WG, "gidx0")
mem = n_elems * 2 * 3 + NUM_WG * HIDDEN * 4 + MBS * SEQ * 4 + HIDDEN * 2 + 4
sink = UOp.sink(grad_x.base, grad_weight_partial.base,
grad_fp8.base, x_normed.base, rrms.base, weight.base, amax_state.base, threads, workgroups,
arg=KernelInfo(f"fused_rmsnorm_mul_quantize_fp8_bwd_{n_elems}_h{HIDDEN}",
estimates=Estimates(ops=8*n_elems, mem=mem)))
defines = [f"-DN_ELEMS={n_elems}", f"-DHIDDEN={HIDDEN}", f"-DNUM_WG={NUM_WG}", f"-DTHREADS_PER_WG={THREADS_PER_WG}"]
src = _src_bwd()
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)),
UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=compile_hip(src, defines))))
def _bwd_common(fp8_grad_u, h_grad_u, x_u, x_normed_u, rrms_u, weight_u, amax_state_u, kernel:UOp):
device = x_u.device
MBS, SEQ, HIDDEN = x_normed_u.shape
axis = x_normed_u.axis if isinstance(device, tuple) else None
grad_x = alloc_like((MBS, SEQ, HIDDEN), dtypes.bfloat16, device, axis)
grad_weight_partial = alloc_local((NUM_WG, HIDDEN), dtypes.float32, device, axis)
grad_h_from_fp8 = None
grad_weight_uop = None
if fp8_grad_u is not None:
fxn = functools.partial(_custom_bwd, dname=dname_of(device))
grad_x_t, grad_weight_partial_t, *_ = Tensor.custom_kernel(
grad_x, grad_weight_partial,
Tensor(fp8_grad_u, device=device).cast(dtypes.bfloat16),
Tensor(x_normed_u.after(kernel), device=device),
Tensor(rrms_u.after(kernel), device=device),
Tensor(weight_u, device=device),
Tensor(amax_state_u, device=device), fxn=fxn)
grad_h_from_fp8 = grad_x_t
grad_weight_uop = grad_weight_partial_t.sum(axis=0).cast(dtypes.bfloat16).uop
if h_grad_u is not None:
h_grad_t = Tensor(h_grad_u, device=device).cast(dtypes.bfloat16)
grad_total = (grad_h_from_fp8 + h_grad_t) if grad_h_from_fp8 is not None else h_grad_t
else:
grad_total = grad_h_from_fp8
return grad_total.uop, grad_weight_uop
def _fused_bwd(gradient:UOp, kernel:UOp):
# NOTE: fwd inputs (fp8_out, x_normed_out, rrms_out, amax_out, x, weight, amax_state)
_, x_normed_u, rrms_u, _, x_u, weight_u, amax_state_u = kernel.src[1:]
grad_x, grad_w = _bwd_common(gradient, None, x_u, x_normed_u, rrms_u, weight_u, amax_state_u, kernel)
return (None, None, None, None, grad_x, grad_w, None)
def _fused_add_bwd(*args, **kwargs):
# Two invocation modes: 1 grad => positional; >1 grads => kwarg `call=`.
# Outputs: (fp8_out, h_out, x_normed_out, rrms_out, amax_buf). Both fp8 and h may be consumed
# downstream — TUPLE order in gradient.py preserves kernel-output slot order.
# Don't dispatch by dtype: matmul's bwd emits fp8 grad as bf16 (no explicit cast), so
# dtype-detection collapses both into h_grad and silently drops the rmsnorm-bwd path.
if 'call' in kwargs:
kernel, all_grads = kwargs['call'], list(args)
else:
gradient, kernel = args
all_grads = [gradient]
fp8_grad_u = h_grad_u = None
if len(all_grads) >= 2:
fp8_grad_u, h_grad_u = all_grads[0], all_grads[1]
elif len(all_grads) == 1:
g = all_grads[0]
if g.dtype == dtypes.bfloat16: h_grad_u = g
else: fp8_grad_u = g
_, _, x_normed_u, rrms_u, _, x_u, _, weight_u, amax_state_u = kernel.src[1:]
grad_h, grad_w = _bwd_common(fp8_grad_u, h_grad_u, x_u, x_normed_u, rrms_u, weight_u, amax_state_u, kernel)
return (None, None, None, None, None, grad_h, grad_h, grad_w, None)
def fused_rmsnorm_mul_quantize_fp8(x:Tensor, weight:Tensor, amax_state:Tensor, eps:float, fp8_dtype,
amax_out:Tensor) -> tuple[Tensor, Tensor, Tensor]:
# NOTE: rmsnorm(x) * weight -> fp8 + amax. Returns (fp8, x_normed, rrms).
# x_normed + rrms are saved for the rmsnorm backward (also recomputed here from x regs).
assert x.dtype == dtypes.bfloat16 and weight.dtype == dtypes.bfloat16
assert x.shape[-1] == weight.shape[-1], f"HIDDEN mismatch: x={x.shape}, weight={weight.shape}"
MBS, SEQ, HIDDEN = x.shape
axis = x.uop.axis if isinstance(x.device, tuple) else None
if isinstance(x.device, tuple): assert axis in (None, 0, 1), f"unsupported sharding axis={axis}"
fp8_out = alloc_like((MBS, SEQ, HIDDEN), fp8_dtype, x.device, axis)
x_normed_out = alloc_like((MBS, SEQ, HIDDEN), dtypes.bfloat16, x.device, axis)
rrms_out = alloc_like((MBS, SEQ), dtypes.float32, x.device, axis)
fxn = functools.partial(_custom_fwd, dname=dname_of(x.device), eps_val=eps)
fp8_out, x_normed_out, rrms_out, amax_out, *_ = Tensor.custom_kernel(
fp8_out, x_normed_out, rrms_out, amax_out, x, weight, amax_state, fxn=fxn, grad_fxn=_fused_bwd)
return fp8_out, x_normed_out, rrms_out
def fused_add_rmsnorm_mul_quantize_fp8(x:Tensor, residual:Tensor, weight:Tensor, amax_state:Tensor,
eps:float, fp8_dtype, amax_out:Tensor) -> tuple[Tensor, Tensor, Tensor, Tensor]:
# NOTE: h = x + residual; y_normed = rmsnorm(h); fp8 = quantize(y_normed * weight).
# Returns (fp8, h, x_normed, rrms). h is also written so downstream can
# reuse it without recomputing x+residual — eliminates the separate residual-add kernel.
assert x.dtype == dtypes.bfloat16 and residual.dtype == dtypes.bfloat16 and weight.dtype == dtypes.bfloat16
assert x.shape == residual.shape
MBS, SEQ, HIDDEN = x.shape
axis = x.uop.axis if isinstance(x.device, tuple) else None
if isinstance(x.device, tuple): assert axis in (None, 0, 1), f"unsupported sharding axis={axis}"
fp8_out = alloc_like((MBS, SEQ, HIDDEN), fp8_dtype, x.device, axis)
h_out = alloc_like((MBS, SEQ, HIDDEN), dtypes.bfloat16, x.device, axis)
x_normed_out = alloc_like((MBS, SEQ, HIDDEN), dtypes.bfloat16, x.device, axis)
rrms_out = alloc_like((MBS, SEQ), dtypes.float32, x.device, axis)
fxn = functools.partial(_custom_fwd_add, dname=dname_of(x.device), eps_val=eps)
fp8_out, h_out, x_normed_out, rrms_out, amax_out, *_ = Tensor.custom_kernel(
fp8_out, h_out, x_normed_out, rrms_out, amax_out, x, residual, weight, amax_state,
fxn=fxn, grad_fxn=_fused_add_bwd)
return fp8_out, h_out, x_normed_out, rrms_out
@@ -1,155 +0,0 @@
#include <hip/hip_runtime.h>
#include <hip/hip_bf16.h>
#include <hip/hip_fp8.h>
// Fuses the full pre-matmul preparation for a layer into a single HBM pass:
// y = rmsnorm(x) * weight (reduce-mean-square + rsqrt + per-elem mul)
// fp8 = fp8_sat(y * (FP8_MAX / amax_state))
// Also writes:
// rrms[row] — saved for the rmsnorm backward
// amax_out — scalar |y| via global atomic max
//
// Layout: one WG per row, ROWS_PER_WG rows per WG via grid-stride (ROWS = N_ELEMS / HIDDEN).
// Each thread handles HIDDEN / THREADS_PER_WG elements per row.
#ifndef N_ELEMS
#define N_ELEMS 67108864
#endif
#ifndef HIDDEN
#define HIDDEN 4096
#endif
#ifndef NUM_WG
#define NUM_WG 1024
#endif
#ifndef THREADS_PER_WG
#define THREADS_PER_WG 256
#endif
#ifndef EPS_LITERAL
#define EPS_LITERAL 1e-5f
#endif
#ifndef HAS_RESIDUAL
#define HAS_RESIDUAL 0
#endif
constexpr int VEC = 8;
constexpr float FP8_MAX = 448.0f;
static_assert(N_ELEMS % HIDDEN == 0, "N_ELEMS must be a multiple of HIDDEN");
static_assert(HIDDEN % (THREADS_PER_WG * VEC) == 0, "HIDDEN must be divisible by THREADS_PER_WG*VEC");
constexpr int ROWS = N_ELEMS / HIDDEN;
constexpr int ELEMS_PER_THREAD = HIDDEN / THREADS_PER_WG; // each thread sees this many elems per row
constexpr int VECS_PER_THREAD = ELEMS_PER_THREAD / VEC; // number of 8-wide vec loads
#if HAS_RESIDUAL
extern "C" __global__ __launch_bounds__(THREADS_PER_WG) void
fused_add_rmsnorm_mul_quantize_fp8(
__hip_fp8_storage_t* __restrict__ fp8_out, // fp8, ROWS*HIDDEN
__hip_bfloat16* __restrict__ h_out, // bf16, ROWS*HIDDEN — x + residual (saved for downstream)
__hip_bfloat16* __restrict__ x_normed_out, // bf16, ROWS*HIDDEN
float* __restrict__ rrms_out, // fp32, ROWS
float* __restrict__ amax_out, // fp32 scalar, initialized to 0 before launch
const __hip_bfloat16* __restrict__ x, // bf16, ROWS*HIDDEN
const __hip_bfloat16* __restrict__ residual, // bf16, ROWS*HIDDEN — added into x before rmsnorm
const __hip_bfloat16* __restrict__ weight, // bf16, HIDDEN
const float* __restrict__ amax_state) // fp32 scalar
{
#else
extern "C" __global__ __launch_bounds__(THREADS_PER_WG) void
fused_rmsnorm_mul_quantize_fp8(
__hip_fp8_storage_t* __restrict__ fp8_out, // fp8, ROWS*HIDDEN
__hip_bfloat16* __restrict__ x_normed_out, // bf16, ROWS*HIDDEN (saved for rmsnorm bwd)
float* __restrict__ rrms_out, // fp32, ROWS (fp32 to match rmsnorm_bwd.cpp expectation)
float* __restrict__ amax_out, // fp32 scalar, initialized to 0 before launch
const __hip_bfloat16* __restrict__ x, // bf16, ROWS*HIDDEN
const __hip_bfloat16* __restrict__ weight, // bf16, HIDDEN (per-hidden scale)
const float* __restrict__ amax_state) // fp32 scalar
{
#endif
__shared__ float sdata[THREADS_PER_WG];
const int tid = threadIdx.x;
const int wg = blockIdx.x;
const float scale = FP8_MAX / (static_cast<float>(*amax_state) + 1e-8f);
const float inv_hidden = 1.0f / static_cast<float>(HIDDEN);
float local_max = 0.0f;
// Grid-stride over rows. Each WG processes rows (wg, wg+NUM_WG, wg+2*NUM_WG, ...).
for (int row = wg; row < ROWS; row += NUM_WG) {
const int row_off = row * HIDDEN;
// Load row (+ residual if present) into registers.
float regs[ELEMS_PER_THREAD];
float sum_sq = 0.0f;
#pragma unroll
for (int v = 0; v < VECS_PER_THREAD; v++) {
const int h_base = tid * VEC + v * THREADS_PER_WG * VEC;
float4 raw = *reinterpret_cast<const float4*>(&x[row_off + h_base]);
const __hip_bfloat16 *xi = reinterpret_cast<const __hip_bfloat16*>(&raw);
#if HAS_RESIDUAL
float4 res_raw = *reinterpret_cast<const float4*>(&residual[row_off + h_base]);
const __hip_bfloat16 *ri = reinterpret_cast<const __hip_bfloat16*>(&res_raw);
__hip_bfloat16 h_buf[VEC];
#endif
#pragma unroll
for (int i = 0; i < VEC; i++) {
#if HAS_RESIDUAL
const float f = static_cast<float>(xi[i]) + static_cast<float>(ri[i]);
h_buf[i] = static_cast<__hip_bfloat16>(f);
#else
const float f = static_cast<float>(xi[i]);
#endif
regs[v * VEC + i] = f;
sum_sq += f * f;
}
#if HAS_RESIDUAL
*reinterpret_cast<float4*>(&h_out[row_off + h_base]) = *reinterpret_cast<float4*>(h_buf);
#endif
}
// LDS tree-reduce sum_sq across the WG.
sdata[tid] = sum_sq;
__syncthreads();
for (int s = THREADS_PER_WG / 2; s > 0; s >>= 1) {
if (tid < s) sdata[tid] = sdata[tid] + sdata[tid + s];
__syncthreads();
}
const float mean_sq = sdata[0] * inv_hidden;
const float rrms = 1.0f / sqrtf(mean_sq + EPS_LITERAL);
if (tid == 0) rrms_out[row] = rrms;
// Normalize, multiply by weight, quantize. Also write x_normed (for rmsnorm bwd).
#pragma unroll
for (int v = 0; v < VECS_PER_THREAD; v++) {
const int h_base = tid * VEC + v * THREADS_PER_WG * VEC;
float4 w_raw = *reinterpret_cast<const float4*>(&weight[h_base]);
const __hip_bfloat16 *wi = reinterpret_cast<const __hip_bfloat16*>(&w_raw);
__hip_fp8_storage_t out[VEC];
__hip_bfloat16 xn[VEC];
#pragma unroll
for (int i = 0; i < VEC; i++) {
const float x_normed = regs[v * VEC + i] * rrms;
xn[i] = static_cast<__hip_bfloat16>(x_normed);
const float y = x_normed * static_cast<float>(wi[i]);
local_max = fmaxf(local_max, fabsf(y));
const float scaled = fmaxf(-FP8_MAX, fminf(FP8_MAX, y * scale));
out[i] = __hip_cvt_float_to_fp8(scaled, __HIP_SATFINITE, __HIP_E4M3);
}
*reinterpret_cast<uint64_t*>(&fp8_out[row_off + h_base]) = *reinterpret_cast<uint64_t*>(out);
*reinterpret_cast<float4*>(&x_normed_out[row_off + h_base]) = *reinterpret_cast<float4*>(xn);
}
__syncthreads(); // before next row's sum_sq reduce reuses sdata
}
// Final per-WG amax reduce, then global atomic into the scalar.
sdata[tid] = local_max;
__syncthreads();
for (int s = THREADS_PER_WG / 2; s > 0; s >>= 1) {
if (tid < s) sdata[tid] = fmaxf(sdata[tid], sdata[tid + s]);
__syncthreads();
}
if (tid == 0 && sdata[0] > *amax_out) atomicMax(reinterpret_cast<int32_t*>(amax_out), __float_as_int(sdata[0]));
}
@@ -1,147 +0,0 @@
#include <hip/hip_runtime.h>
#include <hip/hip_bf16.h>
// Full backward for fused_rmsnorm_mul_quantize_fp8.cpp. One HBM pass per row produces:
// grad_x (bf16) — gradient w.r.t. pre-rmsnorm x
// grad_weight_partial (fp32) — per-WG partial of the weight gradient, reduced later
//
// Input (all read):
// grad_fp8 (bf16) — upstream grad w.r.t. fp8_out (bf16-typed gradient value)
// x_normed (bf16) — saved from the fwd kernel, shape (ROWS, HIDDEN)
// rrms (fp32) — saved rrms per row
// weight (bf16) — per-HIDDEN rmsnorm weight
// amax_state (bf16) — delayed amax used to compute the fp8 scale in fwd
//
// Chain: y = x_normed * weight; fp8 = sat(y * scale). Through STE: grad_y = grad_fp8 * scale.
// grad_x_normed = grad_y * weight.
// grad_weight = sum_rows(grad_y * x_normed).
// grad_x = rrms * (grad_x_normed - x_normed * mean(grad_x_normed * x_normed, last_dim)).
#ifndef N_ELEMS
#define N_ELEMS 67108864
#endif
#ifndef HIDDEN
#define HIDDEN 4096
#endif
#ifndef NUM_WG
#define NUM_WG 1024
#endif
#ifndef THREADS_PER_WG
#define THREADS_PER_WG 256
#endif
constexpr int VEC = 8;
constexpr float FP8_MAX = 448.0f;
static_assert(N_ELEMS % HIDDEN == 0, "N_ELEMS must be a multiple of HIDDEN");
static_assert(HIDDEN % (THREADS_PER_WG * VEC) == 0, "HIDDEN must be divisible by THREADS_PER_WG*VEC");
constexpr int ROWS = N_ELEMS / HIDDEN;
constexpr int ELEMS_PER_THREAD = HIDDEN / THREADS_PER_WG;
constexpr int VECS_PER_THREAD = ELEMS_PER_THREAD / VEC;
extern "C" __global__ __launch_bounds__(THREADS_PER_WG) void
fused_rmsnorm_mul_quantize_fp8_bwd(
__hip_bfloat16* __restrict__ grad_x, // out: bf16, ROWS*HIDDEN
float* __restrict__ grad_weight_partial, // out: fp32, NUM_WG*HIDDEN
const __hip_bfloat16* __restrict__ grad_fp8, // in: bf16, ROWS*HIDDEN (grad of fp8_out)
const __hip_bfloat16* __restrict__ x_normed, // in: bf16, ROWS*HIDDEN
const float* __restrict__ rrms, // in: fp32, ROWS
const __hip_bfloat16* __restrict__ weight, // in: bf16, HIDDEN
const float* __restrict__ amax_state) // in: fp32 scalar
{
__shared__ float sdata[THREADS_PER_WG];
const int tid = threadIdx.x;
const int wg = blockIdx.x;
const float scale = FP8_MAX / (static_cast<float>(*amax_state) + 1e-8f);
const float inv_hidden = 1.0f / static_cast<float>(HIDDEN);
// Per-thread accumulator for grad_weight (across all rows this WG touches).
float gw_accum[ELEMS_PER_THREAD];
#pragma unroll
for (int i = 0; i < ELEMS_PER_THREAD; i++) gw_accum[i] = 0.0f;
// Preload weight into registers (same across rows). Use ELEMS_PER_THREAD entries.
float w_regs[ELEMS_PER_THREAD];
#pragma unroll
for (int v = 0; v < VECS_PER_THREAD; v++) {
const int h_base = tid * VEC + v * THREADS_PER_WG * VEC;
float4 w_raw = *reinterpret_cast<const float4*>(&weight[h_base]);
const __hip_bfloat16 *wi = reinterpret_cast<const __hip_bfloat16*>(&w_raw);
#pragma unroll
for (int i = 0; i < VEC; i++) w_regs[v * VEC + i] = static_cast<float>(wi[i]);
}
for (int row = wg; row < ROWS; row += NUM_WG) {
const int row_off = row * HIDDEN;
const float rrms_v = rrms[row];
// Load grad_fp8 and x_normed rows into registers, compute grad_y and grad_x_normed.
float g_y_regs[ELEMS_PER_THREAD];
float xn_regs[ELEMS_PER_THREAD];
float g_xn_regs[ELEMS_PER_THREAD]; // grad_x_normed
float local_dot = 0.0f; // sum(grad_x_normed * x_normed) for mean
#pragma unroll
for (int v = 0; v < VECS_PER_THREAD; v++) {
const int h_base = tid * VEC + v * THREADS_PER_WG * VEC;
float4 g_raw = *reinterpret_cast<const float4*>(&grad_fp8[row_off + h_base]);
float4 xn_raw = *reinterpret_cast<const float4*>(&x_normed[row_off + h_base]);
const __hip_bfloat16 *gi = reinterpret_cast<const __hip_bfloat16*>(&g_raw);
const __hip_bfloat16 *xni = reinterpret_cast<const __hip_bfloat16*>(&xn_raw);
#pragma unroll
for (int i = 0; i < VEC; i++) {
const int idx = v * VEC + i;
const float g_y = static_cast<float>(gi[i]) * scale;
const float xn = static_cast<float>(xni[i]);
g_y_regs[idx] = g_y;
xn_regs[idx] = xn;
g_xn_regs[idx] = g_y * w_regs[idx]; // grad_x_normed = grad_y * weight
gw_accum[idx] += g_y * xn; // grad_weight contrib
local_dot += g_xn_regs[idx] * xn; // for mean
}
}
// LDS reduce local_dot to sdata[0].
sdata[tid] = local_dot;
__syncthreads();
for (int s = THREADS_PER_WG / 2; s > 0; s >>= 1) {
if (tid < s) sdata[tid] = sdata[tid] + sdata[tid + s];
__syncthreads();
}
const float mean_term = sdata[0] * inv_hidden;
// Compute grad_x = rrms * (grad_x_normed - x_normed * mean_term) and write.
#pragma unroll
for (int v = 0; v < VECS_PER_THREAD; v++) {
const int h_base = tid * VEC + v * THREADS_PER_WG * VEC;
__hip_bfloat16 out[VEC];
#pragma unroll
for (int i = 0; i < VEC; i++) {
const int idx = v * VEC + i;
const float dx = rrms_v * (g_xn_regs[idx] - xn_regs[idx] * mean_term);
out[i] = static_cast<__hip_bfloat16>(dx);
}
*reinterpret_cast<float4*>(&grad_x[row_off + h_base]) = *reinterpret_cast<float4*>(out);
}
__syncthreads();
}
// Write this WG's grad_weight partial to HBM (fp32, NUM_WG x HIDDEN layout).
const int gw_row_off = wg * HIDDEN;
#pragma unroll
for (int v = 0; v < VECS_PER_THREAD; v++) {
const int h_base = tid * VEC + v * THREADS_PER_WG * VEC;
// Write 8 fp32 values with two float4 stores.
float4 out_lo, out_hi;
out_lo.x = gw_accum[v * VEC + 0]; out_lo.y = gw_accum[v * VEC + 1];
out_lo.z = gw_accum[v * VEC + 2]; out_lo.w = gw_accum[v * VEC + 3];
out_hi.x = gw_accum[v * VEC + 4]; out_hi.y = gw_accum[v * VEC + 5];
out_hi.z = gw_accum[v * VEC + 6]; out_hi.w = gw_accum[v * VEC + 7];
*reinterpret_cast<float4*>(&grad_weight_partial[gw_row_off + h_base + 0]) = out_lo;
*reinterpret_cast<float4*>(&grad_weight_partial[gw_row_off + h_base + 4]) = out_hi;
}
}
@@ -1,104 +0,0 @@
import functools
from tinygrad import Tensor, dtypes
from tinygrad.uop.ops import UOp, Ops, KernelInfo, AxisType
from extra.llama_kernels import FP8_MAX, THREADS_PER_WG, alloc_like
BLK = 32
PACK = 4
LOG2E = 1.4426950408889634
@functools.cache
def _custom_silu_mul_quantize_mxfp8(fp8_out:UOp, e8_out:UOp, si_out:UOp, x_w1:UOp, x_w3:UOp) -> UOp:
rows, K = x_w1.shape
scale_K = K // BLK
n_elems = rows * K
n_super = n_elems // (BLK * PACK)
sk4 = scale_K // PACK
assert n_super % THREADS_PER_WG == 0, f"{n_super=} must divide over {THREADS_PER_WG=}"
nwg = n_super // THREADS_PER_WG
x_w1, x_w3 = x_w1.reshape(n_elems), x_w3.reshape(n_elems)
fp8_out = fp8_out.reshape(n_elems)
e8_out = e8_out.reshape(rows * scale_K)
si_out = si_out.reshape(sk4 * rows)
wg = UOp.range(nwg, 0, AxisType.GLOBAL)
tid = UOp.range(THREADS_PER_WG, 1, AxisType.LOCAL)
sb = UOp.range(PACK, 2, AxisType.UNROLL)
lane = UOp.range(BLK, 3, AxisType.UNROLL)
super_idx = wg * THREADS_PER_WG + tid
idx = super_idx * (BLK * PACK) + sb * BLK + lane
w1 = x_w1[idx].cast(dtypes.float)
w3 = x_w3[idx].cast(dtypes.float)
sig = (1.0 + (w1 * -LOG2E).exp2()).reciprocal()
act = w1 * sig * w3
abs_a = (act < 0.0).where(-act, act)
blk_max = abs_a.reduce(lane, arg=Ops.MAX)
e8f = (blk_max.maximum(1e-38).log2().floor() + 127.0).maximum(0.0).minimum(254.0)
qscale = (127.0 - e8f).exp2()
scaled = (act * qscale).maximum(-FP8_MAX).minimum(FP8_MAX)
e8u8 = e8f.cast(dtypes.uint8)
fp8_store = fp8_out[idx].store(scaled.cast(fp8_out.dtype)).end(lane)
e8_store = e8_out.after(fp8_store)[super_idx * PACK + sb].store(e8u8)
packed = (e8u8.cast(dtypes.uint32) << (sb.cast(dtypes.uint32) * 8)).reduce(sb, arg=Ops.ADD)
row, col4 = super_idx // sk4, super_idx % sk4
si_store = si_out.after(e8_store.end(sb))[col4 * rows + row].store(packed)
return si_store.end(tid, wg).sink(arg=KernelInfo(f"silu_mul_quantize_mxfp8_{n_elems}", opts_to_apply=()))
@functools.cache
def _custom_silu_mul_bwd_mxfp8(gx1_out:UOp, gx3_out:UOp, x_w1:UOp, x_w3:UOp, grad_aq:UOp, e8:UOp) -> UOp:
rows, K = x_w1.shape
scale_K = K // BLK
n_elems = rows * K
VEC = 8
assert n_elems % (THREADS_PER_WG * VEC) == 0, f"{n_elems=} must divide {THREADS_PER_WG*VEC=}"
nwg = n_elems // (THREADS_PER_WG * VEC)
x_w1, x_w3, grad_aq = x_w1.reshape(n_elems), x_w3.reshape(n_elems), grad_aq.reshape(n_elems)
gx1_out, gx3_out, e8 = gx1_out.reshape(n_elems), gx3_out.reshape(n_elems), e8.reshape(rows * scale_K)
wg = UOp.range(nwg, 0, AxisType.GLOBAL)
tid = UOp.range(THREADS_PER_WG, 1, AxisType.LOCAL)
lane = UOp.range(VEC, 2, AxisType.UNROLL)
idx = (wg * THREADS_PER_WG + tid) * VEC + lane
e8v = e8[idx // BLK].cast(dtypes.float)
qscale = (127.0 - e8v).exp2()
ga = grad_aq[idx].cast(dtypes.float) * qscale
w1 = x_w1[idx].cast(dtypes.float)
w3 = x_w3[idx].cast(dtypes.float)
sig = (1.0 + (w1 * -LOG2E).exp2()).reciprocal()
s = w1 * sig
sprime = sig * (1.0 + w1 * (1.0 - sig))
gx1 = gx1_out[idx].store((ga * sprime * w3).cast(gx1_out.dtype))
gx3 = gx3_out.after(gx1)[idx].store((ga * s).cast(gx3_out.dtype))
return gx3.end(lane, tid, wg).sink(arg=KernelInfo(f"silu_mul_bwd_mxfp8_{n_elems}", opts_to_apply=()))
def _silu_mul_quantize_mxfp8_bwd(gradient:UOp, kernel:UOp):
_, e8_out, _, x_w1, x_w3 = kernel.src[1:]
device = x_w1.device
rows, K = x_w1.shape
axis = x_w1.axis if isinstance(device, tuple) else None
gx1 = alloc_like((rows, K), dtypes.bfloat16, device, axis)
gx3 = alloc_like((rows, K), dtypes.bfloat16, device, axis)
gx1, gx3, *_ = Tensor.custom_kernel(gx1, gx3, Tensor(x_w1, device=device), Tensor(x_w3, device=device),
Tensor(gradient, device=device).cast(dtypes.bfloat16), Tensor(e8_out.after(kernel), device=device),
fxn=_custom_silu_mul_bwd_mxfp8)
return (None, None, None, gx1.uop, gx3.uop)
def fused_silu_mul_quantize_mxfp8(x_w1:Tensor, x_w3:Tensor) -> tuple[Tensor, Tensor, Tensor]:
assert x_w1.shape == x_w3.shape, f"{x_w1.shape} != {x_w3.shape}"
assert x_w1.dtype == dtypes.bfloat16 and x_w3.dtype == dtypes.bfloat16
assert x_w1.ndim == 2, f"expected 2d, got {x_w1.shape}"
from extra.gemm.cdna_asm_gemm import FP8_DTYPE
rows, K = x_w1.shape
scale_K = K // BLK
axis = x_w1.uop.axis if isinstance(x_w1.device, tuple) else None
fp8_out = alloc_like((rows, K), FP8_DTYPE, x_w1.device, axis)
e8_out = alloc_like((rows, scale_K), dtypes.uint8, x_w1.device, axis)
si_out = alloc_like((scale_K // PACK, rows), dtypes.uint32, x_w1.device, None if axis is None else (1 if axis == 0 else 0))
fp8_out, e8_out, si_out, *_ = Tensor.custom_kernel(fp8_out, e8_out, si_out, x_w1, x_w3,
fxn=_custom_silu_mul_quantize_mxfp8, grad_fxn=_silu_mul_quantize_mxfp8_bwd)
return fp8_out, e8_out, si_out
@@ -1,96 +0,0 @@
import functools
from tinygrad import Tensor, dtypes
from tinygrad.dtype import AddrSpace
from tinygrad.helpers import prod
from tinygrad.uop.ops import UOp, Ops, KernelInfo, AxisType
from extra.llama_kernels import FP8_MAX, NUM_WG, THREADS_PER_WG, alloc_like
@functools.cache
def _custom_quantize_fp8_with_amax(fp8_out:UOp, amax_out:UOp, x:UOp, amax_state:UOp, device=None) -> UOp:
VEC = 8
n_elems = prod(x.shape)
assert n_elems % (NUM_WG * THREADS_PER_WG * VEC) == 0
x = x.reshape(n_elems)
fp8_out = fp8_out.reshape(n_elems)
wg = UOp.range(NUM_WG, 0, AxisType.GLOBAL)
tid = UOp.range(THREADS_PER_WG, 1, AxisType.LOCAL)
it = UOp.range((n_elems // VEC) // (NUM_WG * THREADS_PER_WG), 2, AxisType.WEAK)
lane = UOp.range(VEC, 3, AxisType.UNROLL)
idx = (((it * NUM_WG + wg) * THREADS_PER_WG + tid) * VEC) + lane
scale = FP8_MAX / (amax_state[0].cast(dtypes.float) + 1e-8)
x_f = x[idx].cast(dtypes.float)
abs_x = (x_f < 0.0).where(-x_f, x_f)
scaled = (x_f * scale).maximum(-FP8_MAX).minimum(FP8_MAX)
fp8_store = fp8_out[idx].store(scaled.cast(fp8_out.dtype)).end(lane)
lane_max = abs_x.reduce(lane, arg=Ops.MAX)
lmax = UOp.placeholder((1,), dtypes.float, slot=1, addrspace=AddrSpace.REG)
lmax_init = lmax.after(wg, tid)[0].store(0.0)
lmax_prev = lmax.after(lmax_init, it)[0]
lmax_store = lmax.after(fp8_store)[0].store(lmax_prev.maximum(lane_max))
lmax_val = lmax.after(lmax_store.end(it))[0]
lds = UOp.placeholder((THREADS_PER_WG,), dtypes.float, slot=0, addrspace=AddrSpace.LOCAL)
lds = lds.after(lds[tid].store(lmax_val))
step = THREADS_PER_WG // 2
while step:
active = tid < step
other = lds[(tid + step).valid(active)].load()
lds = lds.after(lds[tid.valid(active)].store(lds[tid].maximum(other)))
step //= 2
device = device[0].split(":")[0] if isinstance(device, tuple) else device.split(":")[0]
if device in {"AMD", "NULL"}: atomic_arg = "if ({2} > {3}) __hip_atomic_fetch_max((int*){0}, {1}, __ATOMIC_RELAXED, __HIP_MEMORY_SCOPE_AGENT);"
else: raise NotImplementedError(f"no atomic max for device {device}")
amax_idx = amax_out.reshape((1,)).index(UOp.const(0))
max_val = lds[0].load()
atomic = UOp(Ops.CUSTOM, src=(amax_idx, max_val.bitcast(dtypes.int32), max_val, amax_idx.load()), arg=(atomic_arg, dtypes.void))
return atomic.end(tid, wg).sink(arg=KernelInfo(f"quantize_fp8_with_amax_{n_elems}", opts_to_apply=()))
@functools.cache
def _custom_quantize_fp8_scalar(fp8_out:UOp, x:UOp, amax_state:UOp) -> UOp:
n_elems = prod(x.shape)
i = UOp.range(n_elems, 0)
x_f = x.reshape(n_elems)[i].cast(dtypes.float)
scale = FP8_MAX / (amax_state[0].cast(dtypes.float) + 1e-8)
store = fp8_out.reshape(n_elems)[i].store((x_f * scale).cast(fp8_out.dtype))
return store.end(i).sink(arg=KernelInfo(f"quantize_fp8_scalar_{n_elems}"))
def _quantize_fp8_delayed_bwd(gradient:UOp, kernel:UOp):
# NOTE: STE-equivalent backward — grad_x = grad_fp8 * scale, scale = FP8_MAX / amax_state.
# `gradient` is bf16 grad w.r.t. fp8 output (asm_gemm bwd already applied x_scale).
_, _, x, amax_state = kernel.src[1:]
device = x.device
scale = FP8_MAX / (Tensor(amax_state, device=device).float() + 1e-8)
grad_x = (Tensor(gradient, device=device).float() * scale).cast(dtypes.bfloat16)
return (None, None, grad_x.uop, None)
def quantize_fp8_delayed(x:Tensor, amax_state:Tensor, amax_out:Tensor, fp8_dtype=dtypes.fp8e4m3) -> tuple[Tensor, Tensor]:
# NOTE: one-pass bf16 -> fp8 quantize with delayed scaling.
# Fused kernel reads x once and writes fp8 + scalar amax via global atomic max.
assert x.dtype == dtypes.bfloat16, f"expected bf16, got {x.dtype}"
axis = x.uop.axis if isinstance(x.device, tuple) else None
fp8_out = alloc_like(x.shape, fp8_dtype, x.device, axis)
n_elems = prod(x.uop.shard_shape)
assert n_elems % NUM_WG == 0, f"{n_elems=} must divide over {NUM_WG=}"
fxn = functools.partial(_custom_quantize_fp8_with_amax, device=x.device)
fp8_out, amax_out, *_ = Tensor.custom_kernel(fp8_out, amax_out, x, amax_state,
fxn=fxn, grad_fxn=_quantize_fp8_delayed_bwd)
inv_scale = (amax_state.float() + 1e-8) / FP8_MAX
return fp8_out, inv_scale
def quantize_fp8_scalar(x:Tensor, amax_state:Tensor, fp8_dtype=dtypes.fp8e4m3) -> Tensor:
# NOTE: pure one-pass bf16 -> fp8 quantize with delayed scalar scale. No amax computation.
axis = x.uop.axis if isinstance(x.device, tuple) else None
fp8_out = alloc_like(x.shape, fp8_dtype, x.device, axis)
fxn = _custom_quantize_fp8_scalar
fp8_out, *_ = Tensor.custom_kernel(fp8_out, x, amax_state, fxn=fxn)
return fp8_out
@@ -1,71 +0,0 @@
import functools
from tinygrad import Tensor, dtypes
from tinygrad.helpers import prod
from tinygrad.uop.ops import UOp, Ops, KernelInfo, AxisType
from extra.llama_kernels import FP8_MAX, THREADS_PER_WG, alloc_like
BLK = 32
PACK = 4
@functools.cache
def _custom_quantize_mxfp8(fp8_out:UOp, e8_out:UOp, si_out:UOp, x:UOp) -> UOp:
rows, K = x.shape
scale_K = K // BLK
n_elems = rows * K
n_super = n_elems // (BLK * PACK)
sk4 = scale_K // PACK
assert n_super % THREADS_PER_WG == 0, f"{n_super=} must divide over {THREADS_PER_WG=}"
nwg = n_super // THREADS_PER_WG
x = x.reshape(n_elems)
fp8_out = fp8_out.reshape(n_elems)
e8_out = e8_out.reshape(rows * scale_K)
si_out = si_out.reshape(sk4 * rows)
wg = UOp.range(nwg, 0, AxisType.GLOBAL)
tid = UOp.range(THREADS_PER_WG, 1, AxisType.LOCAL)
sb = UOp.range(PACK, 2, AxisType.UNROLL)
lane = UOp.range(BLK, 3, AxisType.UNROLL)
super_idx = wg * THREADS_PER_WG + tid
idx = super_idx * (BLK * PACK) + sb * BLK + lane
x_f = x[idx].cast(dtypes.float)
abs_x = (x_f < 0.0).where(-x_f, x_f)
blk_max = abs_x.reduce(lane, arg=Ops.MAX)
e8f = (blk_max.maximum(1e-38).log2().floor() + 127.0).maximum(0.0).minimum(254.0)
qscale = (127.0 - e8f).exp2()
scaled = (x_f * qscale).maximum(-FP8_MAX).minimum(FP8_MAX)
e8u8 = e8f.cast(dtypes.uint8)
fp8_store = fp8_out[idx].store(scaled.cast(fp8_out.dtype)).end(lane)
e8_store = e8_out.after(fp8_store)[super_idx * PACK + sb].store(e8u8)
# pack the 4 e8 of this super-block into one uint32 (little-endian: byte sb), write transposed (sk4, row)
packed = (e8u8.cast(dtypes.uint32) << (sb.cast(dtypes.uint32) * 8)).reduce(sb, arg=Ops.ADD)
row, col4 = super_idx // sk4, super_idx % sk4
si_store = si_out.after(e8_store.end(sb))[col4 * rows + row].store(packed)
return si_store.end(tid, wg).sink(arg=KernelInfo(f"quantize_mxfp8_{n_elems}", opts_to_apply=()))
def _quantize_mxfp8_fused_bwd(gradient:UOp, kernel:UOp):
_, e8_out, _, x = kernel.src[1:]
device = x.device
rows, K = x.shape
scale_K = K // BLK
e8 = Tensor(e8_out, device=device).reshape(rows, scale_K)
qscale = (127.0 - e8.cast(dtypes.float32)).exp2().reshape(rows, scale_K, 1).expand(rows, scale_K, BLK).reshape(rows, K)
grad_x = (Tensor(gradient, device=device).float() * qscale).cast(dtypes.bfloat16)
return (None, None, None, grad_x.uop)
def quantize_mxfp8_fused(x:Tensor) -> tuple[Tensor, Tensor, Tensor]:
assert x.dtype == dtypes.bfloat16, f"expected bf16, got {x.dtype}"
assert x.ndim == 2, f"expected 2d (rows, K), got {x.shape}"
from extra.gemm.cdna_asm_gemm import FP8_DTYPE
rows, K = x.shape
scale_K = K // BLK
axis = x.uop.axis if isinstance(x.device, tuple) else None
fp8_out = alloc_like((rows, K), FP8_DTYPE, x.device, axis)
e8_out = alloc_like((rows, scale_K), dtypes.uint8, x.device, axis)
si_out = alloc_like((scale_K // PACK, rows), dtypes.uint32, x.device, None if axis is None else (1 if axis == 0 else 0))
fp8_out, e8_out, si_out, *_ = Tensor.custom_kernel(fp8_out, e8_out, si_out, x, fxn=_custom_quantize_mxfp8, grad_fxn=_quantize_mxfp8_fused_bwd)
return fp8_out, e8_out, si_out
+4 -5
View File
@@ -3,7 +3,6 @@
import struct
from tinygrad.helpers import getenv, round_up
from tinygrad.device import Device, BufferSpec, Buffer
from tinygrad.dtype import dtypes
from tinygrad.runtime.support.system import PCIDevice
from tinygrad.runtime.support.memory import AddrSpace
from tinygrad.runtime.ops_amd import AMDComputeQueue
@@ -31,8 +30,8 @@ print(f"[init] loopback connect QP 0x{qp.qp_info['qpn']:x}")
qp.connect(qp.qp_info['qpn'], dev.mac, int.from_bytes(dev.local_gid, 'big'))
# allocate src/dst via AMD GPU allocator
buf_src = Buffer(gpu.device, BUF_SIZE, dtypes.uint8, options=BufferSpec(nolru=True), preallocate=True)
buf_dst = Buffer(gpu.device, BUF_SIZE, dtypes.uint8, options=BufferSpec(nolru=True), preallocate=True)
buf_src = Buffer(gpu.device, BUF_SIZE, options=BufferSpec(nolru=True), preallocate=True)
buf_dst = Buffer(gpu.device, BUF_SIZE, options=BufferSpec(nolru=True), preallocate=True)
bar_base = gpu.iface.pci_dev.bar_info(gpu.iface.vram_bar)[0]
src_paddr = buf_src.meta.mapping.paddrs[0][0] + bar_base
@@ -41,8 +40,8 @@ print(f"src paddr=0x{src_paddr:x} dst paddr=0x{dst_paddr:x}")
# fill src, zero dst
test_msg = b"Hello from loopback send/recv!"
buf_src.copy_from(Buffer("PYTHON", BUF_SIZE, dtypes.uint8, opaque=memoryview(bytearray(test_msg.ljust(BUF_SIZE, b'\x00')))))
buf_dst.copy_from(Buffer("PYTHON", BUF_SIZE, dtypes.uint8, opaque=memoryview(bytearray(BUF_SIZE))))
buf_src.copy_from(Buffer("PYTHON", BUF_SIZE, initial_value=bytes(test_msg.ljust(BUF_SIZE, b'\x00'))))
buf_dst.copy_from(Buffer("PYTHON", BUF_SIZE, initial_value=bytes(BUF_SIZE)))
gpu.synchronize()
# post recv WQE on RQ from CPU (scatter entry: byte_count, lkey, addr)
+1 -2
View File
@@ -62,8 +62,7 @@ class T5LayerNorm:
# w/o mean and there is no bias. Additionally we want to make sure that the accumulation for
# half-precision inputs is done in fp32
variance = hidden_states.cast(dtypes.float32).pow(2).mean(-1, keepdim=True)
hidden_states = hidden_states * Tensor.rsqrt(variance + self.variance_epsilon)
hidden_states = hidden_states.cast(dtypes.float32).rmsnorm(eps=self.variance_epsilon)
# convert into half-precision if necessary
if self.weight.dtype in [dtypes.float16, dtypes.bfloat16]:

Some files were not shown because too many files have changed in this diff Show More