Compare commits

..
Author SHA1 Message Date
geohot 4218cc9257 fix spec 2026-05-27 17:35:46 -07:00
geohot 17419edc4a fix slice store to remove the index 2026-05-27 17:21:49 -07:00
78 changed files with 856 additions and 892 deletions
@@ -5,7 +5,6 @@ runs:
steps: steps:
- name: Run process replay tests - name: Run process replay tests
shell: bash shell: bash
if: env.CAPTURE_PROCESS_REPLAY == '1'
run: | run: |
export PR_TITLE=$(jq -r .pull_request.title "$GITHUB_EVENT_PATH") export PR_TITLE=$(jq -r .pull_request.title "$GITHUB_EVENT_PATH")
export CURRENT_SHA=${{ github.event.pull_request && github.event.pull_request.head.sha || github.sha }} export CURRENT_SHA=${{ github.event.pull_request && github.event.pull_request.head.sha || github.sha }}
@@ -228,11 +228,6 @@ runs:
sudo chown -R $USER:$USER /var/cache/apt/archives/ sudo chown -R $USER:$USER /var/cache/apt/archives/
- name: Add clang to PATH (Linux)
if: inputs.llvm == 'true' && runner.os == 'Linux'
shell: bash
run: echo "/usr/lib/llvm-20/bin" >> "$GITHUB_PATH"
# **** AMD **** # **** AMD ****
- name: Setup AMD (Linux) - name: Setup AMD (Linux)
if: inputs.amd == 'true' && runner.os == 'Linux' if: inputs.amd == 'true' && runner.os == 'Linux'
-13
View File
@@ -806,16 +806,3 @@ jobs:
pkill -f 'extra/remote/serve.py' || true pkill -f 'extra/remote/serve.py' || true
- name: Run process replay tests - name: Run process replay tests
uses: ./.github/actions/process-replay uses: ./.github/actions/process-replay
llvmspeed:
name: LLVM Speed
runs-on: [self-hosted, Linux, tinyboxrandom]
timeout-minutes: 20
if: github.repository_owner == 'tinygrad'
steps:
- name: Checkout Code
uses: actions/checkout@v6
- name: Speed Test
run: DEV=CPU:LLVM THREADS=0 python3 test/speed/external_test_speed_v_torch.py
- name: Speed Test (BEAM=2)
run: BEAM=2 DEV=CPU:LLVM THREADS=0 python3 test/speed/external_test_speed_v_torch.py
+172 -105
View File
@@ -2,7 +2,7 @@ name: Unit Tests
env: env:
# increment this when downloads substantially change to avoid the internet # increment this when downloads substantially change to avoid the internet
CACHE_VERSION: '19' CACHE_VERSION: '19'
CAPTURE_PROCESS_REPLAY: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.title, '[pr]') && '1' || '0' }} CAPTURE_PROCESS_REPLAY: 1
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PYTHONPATH: ${{ github.workspace }} PYTHONPATH: ${{ github.workspace }}
CHECK_OOB: 1 CHECK_OOB: 1
@@ -14,14 +14,28 @@ on:
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
concurrency:
group: test-${{ github.event_name }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs: jobs:
llvmspeed:
name: LLVM Speed
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout Code
uses: actions/checkout@v6
- name: Setup Environment
uses: ./.github/actions/setup-tinygrad
with:
key: llvm-speed
deps: testing_unit
llvm: 'true'
- name: Speed Test
run: DEV=CPU:LLVM THREADS=0 python3 test/speed/external_test_speed_v_torch.py
- name: Speed Test (BEAM=2)
run: BEAM=2 DEV=CPU:LLVM THREADS=0 python3 test/speed/external_test_speed_v_torch.py
docs: docs:
name: Docs name: Docs
runs-on: &linux ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }} runs-on: ubuntu-24.04
timeout-minutes: 10 timeout-minutes: 10
env: env:
CHECK_OOB: 0 CHECK_OOB: 0
@@ -75,7 +89,7 @@ jobs:
torchbackend: torchbackend:
name: Torch Backend Tests name: Torch Backend Tests
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout Code - name: Checkout Code
@@ -111,7 +125,7 @@ jobs:
torchbackendmore: torchbackendmore:
name: Torch Backend Tests More name: Torch Backend Tests More
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout Code - name: Checkout Code
@@ -133,7 +147,7 @@ jobs:
bepython: bepython:
name: Python Backend name: Python Backend
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout Code - name: Checkout Code
@@ -201,7 +215,7 @@ jobs:
linter: linter:
name: Linters name: Linters
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
@@ -232,7 +246,7 @@ jobs:
nulltest: nulltest:
name: Null Tests name: Null Tests
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
@@ -263,7 +277,7 @@ jobs:
unittest: unittest:
name: Unit Tests name: Unit Tests
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
@@ -306,7 +320,7 @@ jobs:
matrix: matrix:
group: [1, 2] group: [1, 2]
name: SPEC=2 (${{ matrix.group }}) name: SPEC=2 (${{ matrix.group }})
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout Code - name: Checkout Code
@@ -317,13 +331,12 @@ jobs:
key: spec-unit key: spec-unit
deps: testing_unit deps: testing_unit
python-version: '3.14' python-version: '3.14'
llvm: 'true'
- name: Test SPEC=2 - 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 --timeout 60 -k "not test_setitem_big" -k "not test_conv2d_ceildiv_edge_case" --splits 2 --group ${{ matrix.group }} 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 --timeout 60 -k "not test_setitem_big" -k "not test_conv2d_ceildiv_edge_case" --splits 2 --group ${{ matrix.group }}
fuzzing: fuzzing:
name: Fuzzing name: Fuzzing
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- name: Checkout Code - name: Checkout Code
@@ -344,7 +357,7 @@ jobs:
testopenclimage: testopenclimage:
name: CL IMAGE Tests name: CL IMAGE Tests
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout Code - name: Checkout Code
@@ -364,7 +377,7 @@ jobs:
testgpumisc: testgpumisc:
name: CL Misc tests name: CL Misc tests
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- name: Checkout Code - name: Checkout Code
@@ -389,7 +402,7 @@ jobs:
testopenpilot: testopenpilot:
name: openpilot Compile Tests name: openpilot Compile Tests
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout Code - name: Checkout Code
@@ -404,6 +417,8 @@ jobs:
- name: Test openpilot model kernel count and gate usage - name: Test openpilot model kernel count and gate usage
run: | run: |
ALLOWED_KERNEL_COUNT=123 ALLOWED_READ_IMAGE=1468 ALLOWED_GATED_READ_IMAGE=18 FLOAT16=1 DEV=CL IMAGE=1 python examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/cf6376aa9a090f0da26c280ef69eabf9bbdd51d1faac9ed392919c3db69be916 ALLOWED_KERNEL_COUNT=123 ALLOWED_READ_IMAGE=1468 ALLOWED_GATED_READ_IMAGE=18 FLOAT16=1 DEV=CL IMAGE=1 python examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/cf6376aa9a090f0da26c280ef69eabf9bbdd51d1faac9ed392919c3db69be916
- name: Test openpilot CL compile fp16
run: FLOAT16=1 DEV=CL IMAGE=1 python examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/cf6376aa9a090f0da26c280ef69eabf9bbdd51d1faac9ed392919c3db69be916
- name: Test openpilot CL compile fp32 (test correctness) - name: Test openpilot CL compile fp32 (test correctness)
run: | run: |
DEV=CL IMAGE=1 SELFTEST=1 python examples/openpilot/compile3.py https://github.com/haraschax/filedump/raw/refs/heads/master/driving_vision_fp32.onnx DEV=CL IMAGE=1 SELFTEST=1 python examples/openpilot/compile3.py https://github.com/haraschax/filedump/raw/refs/heads/master/driving_vision_fp32.onnx
@@ -417,7 +432,7 @@ jobs:
testonnxcpu: testonnxcpu:
name: ONNX (CPU) Tests name: ONNX (CPU) Tests
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
@@ -445,7 +460,7 @@ jobs:
testopencl: testopencl:
name: ONNX (CL)+Optimization Tests name: ONNX (CL)+Optimization Tests
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
- name: Checkout Code - name: Checkout Code
@@ -479,7 +494,7 @@ jobs:
testllm: testllm:
name: Test LLM name: Test LLM
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
env: env:
CHECK_OOB: 0 CHECK_OOB: 0
@@ -504,7 +519,7 @@ jobs:
testmodels: testmodels:
name: Models (llvm+cpu+gpu) name: Models (llvm+cpu+gpu)
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout Code - name: Checkout Code
@@ -547,7 +562,7 @@ jobs:
testdsp: testdsp:
name: Linux (DSP) name: Linux (DSP)
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout Code - name: Checkout Code
@@ -556,7 +571,8 @@ jobs:
uses: ./.github/actions/setup-tinygrad uses: ./.github/actions/setup-tinygrad
with: with:
key: dsp-minimal key: dsp-minimal
deps: testing deps: testing_unit
pydeps: "onnx==1.18.0 onnxruntime ml_dtypes"
llvm: "true" llvm: "true"
qemu: "true" qemu: "true"
- name: Set MOCKDSP env - name: Set MOCKDSP env
@@ -564,24 +580,13 @@ jobs:
- name: Run test_tiny on DSP - name: Run test_tiny on DSP
run: DEBUG=2 DEV=DSP python test/test_tiny.py run: DEBUG=2 DEV=DSP python test/test_tiny.py
- name: Test transcendentals - name: Test transcendentals
run: DEBUG=2 DEV=DSP python test/backend/test_transcendental.py TestTranscendentalVectorized run: CC=clang-20 DEBUG=2 DEV=DSP python test/backend/test_transcendental.py TestTranscendentalVectorized
- name: Test quantize onnx - name: Test quantize onnx
run: DEBUG=2 DEV=DSP python3 test/backend/test_quantize_onnx.py run: DEBUG=2 DEV=DSP python3 test/backend/test_quantize_onnx.py
testlinux: testwebgpu:
strategy: name: Linux (WebGPU)
fail-fast: false runs-on: ubuntu-24.04
matrix:
dev:
- 'CPU:CLANG'
- 'CPU:LLVM'
- 'CPU:LVP'
- 'CPU:X86'
- 'CL'
- 'WEBGPU'
name: Linux (DEV=${{ matrix.dev }})
runs-on: *linux
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
- name: Checkout Code - name: Checkout Code
@@ -589,27 +594,23 @@ jobs:
- name: Setup Environment - name: Setup Environment
uses: ./.github/actions/setup-tinygrad uses: ./.github/actions/setup-tinygrad
with: with:
key: linux-${{ matrix.dev }} key: webgpu-minimal
deps: testing_unit deps: testing_unit
python-version: '3.12' python-version: '3.12'
llvm: ${{ contains(matrix.dev, 'LLVM') || contains(matrix.dev, 'LVP') || contains(matrix.dev, 'CLANG') }} webgpu: 'true'
mesa: ${{ contains(matrix.dev, 'LVP') && 'cpu' || 'false' }} - name: Check Device.DEFAULT (WEBGPU) and print some source
webgpu: ${{ matrix.dev == 'WEBGPU' }}
opencl: ${{ matrix.dev == 'CL' }}
- name: Set env
run: printf "DEV=${{ matrix.dev }}${{ matrix.dev == 'CPU:CLANG' && '\nCPU_COUNT=2' || '' }}" >> $GITHUB_ENV
- name: Check Device.DEFAULT and print some source
run: | run: |
python -c "from tinygrad import Device; from tinygrad.helpers import Target; assert Device.DEFAULT == Target.parse('${{ matrix.dev }}').device" DEV=WEBGPU python -c "from tinygrad import Device; assert Device.DEFAULT == 'WEBGPU', Device.DEFAULT"
DEBUG=4 python test/test_tiny.py TestTiny.test_plus DEV=WEBGPU DEBUG=4 FORWARD_ONLY=1 python3 test/test_tiny.py TestTiny.test_plus
- name: Run backend tests - name: Run selected webgpu tests
run: python -m pytest -n=auto test/backend --durations=20 run: |
DEV=WEBGPU WEBGPU_BACKEND="WGPUBackendType_Vulkan" python3 -m pytest -n=auto test/backend --durations=20
- name: Run process replay tests - name: Run process replay tests
uses: ./.github/actions/process-replay uses: ./.github/actions/process-replay
testamdasm: testamdasm:
name: AMD ASM IDE name: AMD ASM IDE
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 20 timeout-minutes: 20
env: env:
DEV: MOCKKFD+AMD DEV: MOCKKFD+AMD
@@ -656,7 +657,7 @@ jobs:
testmockam: testmockam:
name: Linux (am) name: Linux (am)
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
env: env:
DEV: MOCKPCI+AMD DEV: MOCKPCI+AMD
@@ -692,7 +693,7 @@ jobs:
arch: [gfx1100, gfx1201, gfx950] arch: [gfx1100, gfx1201, gfx950]
name: Linux (${{ matrix.backend }} ${{ matrix.arch }}) name: Linux (${{ matrix.backend }} ${{ matrix.arch }})
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
env: env:
DEV: MOCKKFD+AMD:${{ matrix.backend == 'amdllvm' && 'LLVM' || '' }}:${{ matrix.arch }} DEV: MOCKKFD+AMD:${{ matrix.backend == 'amdllvm' && 'LLVM' || '' }}:${{ matrix.arch }}
@@ -727,7 +728,7 @@ jobs:
backend: [ptx, nv] backend: [ptx, nv]
name: Linux (${{ matrix.backend }}) name: Linux (${{ matrix.backend }})
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 20 timeout-minutes: 20
env: env:
FORWARD_ONLY: 1 FORWARD_ONLY: 1
@@ -755,6 +756,39 @@ jobs:
- name: Run process replay tests - name: Run process replay tests
uses: ./.github/actions/process-replay uses: ./.github/actions/process-replay
testcpuopencl:
strategy:
fail-fast: false
matrix:
backend: [llvm, cpu, opencl, lvp, x86]
name: Linux (${{ matrix.backend }})
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout Code
uses: actions/checkout@v6
- name: Setup Environment
uses: ./.github/actions/setup-tinygrad
with:
key: ${{ matrix.backend }}-minimal
deps: testing_unit
opencl: ${{ matrix.backend == 'opencl' && 'true' }}
llvm: ${{ matrix.backend == 'llvm' || matrix.backend == 'cpu' || matrix.backend == 'lvp' }}
mesa: ${{ matrix.backend == 'lvp' && 'cpu' }}
- name: Set env
run: printf "${{ matrix.backend == 'llvm' && 'DEV=CPU:LLVM' || matrix.backend == 'cpu' && 'CC=clang-20\nDEV=CPU\nCPU_COUNT=2' || matrix.backend == 'opencl' && 'DEV=CL' || matrix.backend == 'lvp' && 'DEV=CPU:LVP' || matrix.backend == 'x86' && 'DEV=CPU:X86' }}" >> $GITHUB_ENV
- name: Check Device.DEFAULT and print some source
run: |
python3 -c "from tinygrad import Device; assert Device.DEFAULT in ['CPU','CL'], Device.DEFAULT"
DEBUG=5 FORWARD_ONLY=1 python3 test/test_tiny.py TestTiny.test_plus
- name: Run pytest (${{ matrix.backend }})
run: python -m pytest -n=auto test/backend --durations=20
- 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
- name: Run process replay tests
uses: ./.github/actions/process-replay
# ****** OSX Tests ****** # ****** OSX Tests ******
testmetal: testmetal:
@@ -814,56 +848,84 @@ jobs:
- name: Run process replay tests - name: Run process replay tests
uses: ./.github/actions/process-replay uses: ./.github/actions/process-replay
testmacos: osxwebgpu:
strategy: name: MacOS (WebGPU)
fail-fast: false runs-on: macos-14
matrix: timeout-minutes: 10
dev:
- 'CPU:CLANG'
- 'CPU:LLVM'
- 'CPU:LVP'
- 'METAL'
- 'WEBGPU'
name: MacOS (DEV=${{ matrix.dev }})
runs-on: macos-15
timeout-minutes: 20
steps: steps:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Setup Environment - name: Setup Environment
uses: ./.github/actions/setup-tinygrad uses: ./.github/actions/setup-tinygrad
with: with:
key: macos-${{ matrix.dev }} key: osx-webgpu
deps: testing_unit deps: testing
python-version: '3.12' webgpu: 'true'
llvm: ${{ contains(matrix.dev, 'LLVM') || contains(matrix.dev, 'LVP') }} - name: Build WEBGPU Efficientnet
mesa: ${{ contains(matrix.dev, 'LVP') && 'cpu' || 'false' }} run: DEV=WEBGPU WEBGPU_BACKEND="WGPUBackendType_Metal" python3 -m examples.compile_efficientnet
webgpu: ${{ matrix.dev == 'WEBGPU' }} - name: Run selected webgpu tests
- name: Set env run: DEV=WEBGPU WEBGPU_BACKEND="WGPUBackendType_Metal" python3 -m pytest -n=auto test/backend --durations=20
run: printf "DEV=${{ matrix.dev }}${{ matrix.dev == 'CPU:CLANG' && '\nCPU_COUNT=2' || '' }}" >> $GITHUB_ENV #- name: Clean npm cache
- name: Check Device.DEFAULT and print some source # run: npm cache clean --force
run: | #- name: Install Puppeteer
python -c "from tinygrad import Device; from tinygrad.helpers import Target; assert Device.DEFAULT == Target.parse('${{ matrix.dev }}').device" # run: npm install puppeteer
DEBUG=4 python test/test_tiny.py TestTiny.test_plus # this is also flaky
- name: Run backend tests #- name: Run WEBGPU Efficientnet
run: python -m pytest -n=auto test/backend --durations=20 # run: node test/web/test_webgpu.js
- name: Run process replay tests # this is flaky
uses: ./.github/actions/process-replay #- name: Run VIZ tests as external package
# run: |
# mkdir $GITHUB_WORKSPACE/test_dir
# cd $GITHUB_WORKSPACE/test_dir
# python -m venv venv
# source venv/bin/activate
# pip install $GITHUB_WORKSPACE
# cp $GITHUB_WORKSPACE/test/web/test_viz.js .
# node test_viz.js
- name: Test ONNX Runner (WEBGPU)
run: DEV=WEBGPU python3 test/external/external_test_onnx_runner.py
# ****** Windows Tests ****** osxtests:
testwindows:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
dev: backend: [metal, llvm, cpu, lvp]
- 'CPU:CLANG' name: MacOS (${{ matrix.backend }})
- 'CPU:LLVM' runs-on: macos-15
- 'CPU:X86' timeout-minutes: 20
- 'WEBGPU' steps:
- name: Checkout Code
uses: actions/checkout@v6
- name: Setup Environment
uses: ./.github/actions/setup-tinygrad
with:
key: macos-${{ matrix.backend }}-minimal
deps: testing_unit
llvm: ${{ matrix.backend == 'llvm' || matrix.backend == 'lvp' }}
mesa: ${{ matrix.backend == 'lvp' && 'cpu' }}
- name: Set env
run: printf "${{ matrix.backend == 'llvm' && 'DEV=CPU:LLVM' || matrix.backend == 'cpu' && 'DEV=CPU\nCPU_COUNT=2' || matrix.backend == 'metal' && 'DEV=METAL' || matrix.backend == 'lvp' && 'DEV=CPU:LVP' }}" >> $GITHUB_ENV
- name: Check Device.DEFAULT and print some source
run: |
python -c "from tinygrad import Device; assert Device.DEFAULT == {'LLVM':'CPU','LVP':'CPU'}.get(x:='${{ matrix.backend }}'.upper(), x), Device.DEFAULT"
DEBUG=4 python3 test/test_tiny.py TestTiny.test_plus
- name: Run pytest (${{ matrix.backend }})
run: python3 -m pytest -n=auto test/backend --durations=20
- name: Run process replay tests
uses: ./.github/actions/process-replay
- name: Run macOS-specific unit test
if: matrix.backend == 'llvm'
run: python3 -m pytest test/unit/test_disk_tensor.py::TestDiskTensor::test_copy_to_cpu_not_truncated test/unit/test_cpu.py
name: Windows (DEV=${{ matrix.dev }}) # ****** Windows Tests ******
wintests:
strategy:
fail-fast: false
matrix:
backend: [llvm, cpu, webgpu, x86]
name: Windows (${{ matrix.backend }})
runs-on: windows-latest runs-on: windows-latest
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
@@ -872,20 +934,25 @@ jobs:
- name: Setup Environment - name: Setup Environment
uses: ./.github/actions/setup-tinygrad uses: ./.github/actions/setup-tinygrad
with: with:
key: windows-${{ matrix.dev }}-minimal key: windows-${{ matrix.backend }}-minimal
deps: testing_unit deps: testing_unit
pydeps: ${{ matrix.dev == 'WEBGPU' && 'dawn-python' || '' }} pydeps: ${{ matrix.backend == 'webgpu' && 'dawn-python' || '' }}
- name: Set env - name: Set env
shell: bash shell: bash
run: printf "DEV=${{ matrix.dev }}${{ matrix.dev == 'CPU:CLANG' && '\nCPU_COUNT=2' || '' }}" >> $GITHUB_ENV run: printf "${{ matrix.backend == 'llvm' && 'DEV=CPU:LLVM' || matrix.backend == 'cpu' && 'DEV=CPU\nCPU_COUNT=2' || matrix.backend == 'webgpu' && 'DEV=WEBGPU' || matrix.backend == 'x86' && 'DEV=CPU:X86' }}" >> $GITHUB_ENV
- name: Check Device.DEFAULT and print some source - name: Run unit tests
if: matrix.backend=='llvm'
# test_newton_schulz hits RecursionError
run: python -m pytest -n=auto test/unit/ --ignore=test/unit/test_disk_tensor.py --ignore=test/unit/test_tar.py --ignore=test/unit/test_linalg.py --durations=20
- name: Run NULL backend tests
if: matrix.backend=='llvm'
shell: bash
run: DEV=NULL python -m pytest -n=auto test/null/ --ignore=test/null/test_elf.py --durations=20
- name: Run pytest (${{ matrix.backend }})
shell: bash shell: bash
run: | run: |
python -c "from tinygrad import Device; from tinygrad.helpers import Target; assert Device.DEFAULT == Target.parse('${{ matrix.dev }}').device" python -c "from tinygrad import Device; assert Device.DEFAULT == {'LLVM':'CPU', 'X86':'CPU'}.get(x:='${{ matrix.backend }}'.upper(), x), Device.DEFAULT"
DEBUG=4 python test/test_tiny.py TestTiny.test_plus python -m pytest -n=auto test/test_tiny.py test/backend/test_ops.py --durations=20
- name: Run test_tiny
shell: bash
run: python -m pytest -n=auto test/test_tiny.py --durations=20
# ****** Compile-only Tests ****** # ****** Compile-only Tests ******
@@ -895,7 +962,7 @@ jobs:
matrix: matrix:
backend: [ir3, nak] backend: [ir3, nak]
name: Compile-only (${{ matrix.backend }}) name: Compile-only (${{ matrix.backend }})
runs-on: *linux runs-on: ubuntu-24.04
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout Code - name: Checkout Code
@@ -927,7 +994,7 @@ jobs:
python -m pytest -n=auto test/backend/test_ops.py --durations=20 python -m pytest -n=auto test/backend/test_ops.py --durations=20
qcomclcompiletests: qcomclcompiletests:
name: Compile-only (QCOM CL) name: Compile-only (QCOM CL)
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'MEMBER' && 'namespace-profile-tinygrad-arm64' || 'ubuntu-24.04-arm' }} runs-on: ubuntu-24.04-arm
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout Code - name: Checkout Code
+5 -3
View File
@@ -1419,7 +1419,10 @@ def train_llama3():
for p in optim.params: for p in optim.params:
grad_dtype = dtypes.bfloat16 if p.dtype == FP8_DTYPE else p.dtype grad_dtype = dtypes.bfloat16 if p.dtype == FP8_DTYPE else p.dtype
p.grad = p.zeros_like(dtype=grad_dtype).contiguous() if isinstance(p.device, tuple) and p.uop.axis is not None:
p.grad = Tensor.zeros(p.shape, dtype=grad_dtype, device=p.device[0]).shard_(p.device, axis=p.uop.axis).contiguous()
else:
p.grad = Tensor.zeros(p.shape, dtype=grad_dtype, device=p.device).contiguous()
grads = [p.grad for p in optim.params] 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) scheduler = CosineAnnealingLRWithWarmup(optim, opt_base_learning_rate, opt_end_learning_rate, opt_learning_rate_warmup_steps, opt_learning_rate_decay_steps)
@@ -1435,14 +1438,13 @@ def train_llama3():
fp8_amax = [t for ts in model._fp8_amax.values() for t in ts] 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] if hasattr(model, "_fp8_grad_amax") else [] fp8_grad_amax = [t for ts in model._fp8_grad_amax.values() for t in ts] if hasattr(model, "_fp8_grad_amax") else []
fp8_inv_scales = list(model._fp8_inv_scale.values()) + list(model._fp8_next_inv_scale.values()) fp8_inv_scales = list(model._fp8_inv_scale.values())
from tinygrad.nn.state import get_state_dict from tinygrad.nn.state import get_state_dict
model_state = get_state_dict(model) model_state = get_state_dict(model)
for wname in model._fp8_inv_scale: for wname in model._fp8_inv_scale:
w = model_state[wname] w = model_state[wname]
w._inv_scale = model._fp8_inv_scale[wname] w._inv_scale = model._fp8_inv_scale[wname]
w._next_inv_scale = model._fp8_next_inv_scale[wname]
if optim.master_params: if optim.master_params:
idx = next(j for j, p in enumerate(optim.params) if p is w) idx = next(j for j, p in enumerate(optim.params) if p is w)
master = optim.master_params[idx] master = optim.master_params[idx]
+15 -15
View File
@@ -136,7 +136,6 @@ class FlatTransformer:
w_scales = [("wqkv", s_qkv), ("wo", s_o), ("w2", s_2)] 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)] w_scales += [("w1", s_1), ("w3", s_3)] if SPLIT_W13 else [("w13", s_13)]
self._fp8_inv_scale = {name: s.float().contiguous().is_param_(False) for name, s in w_scales} self._fp8_inv_scale = {name: s.float().contiguous().is_param_(False) for name, s in w_scales}
self._fp8_next_inv_scale = {name: 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): def lin_per_layer(self, in_features:int, out_features:int, std:float=0.02):
if getenv("ZEROS"): w = Tensor.zeros(self.n_layers, out_features, in_features) if getenv("ZEROS"): w = Tensor.zeros(self.n_layers, out_features, in_features)
@@ -222,19 +221,14 @@ class FlatTransformer:
for v in get_parameters(self): v.shard_(device, axis=None) for v in get_parameters(self): v.shard_(device, axis=None)
else: else:
# flat per-layer weights: axis 0 is n_layers, so shard axes are +1 vs per-layer Transformer # 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): self.wqkv.shard_(device, axis=1).realize() # (n_layers, out, dim) shard out
getattr(self, name).shard_(device, axis=axis) self.wo.shard_(device, axis=2).realize() # (n_layers, dim, in) shard in
self._fp8_inv_scale[name] = self._fp8_inv_scale[name].to(device).contiguous().is_param_(False)
self._fp8_next_inv_scale[name] = self._fp8_next_inv_scale[name].to(device).contiguous().is_param_(False)
Tensor.realize(getattr(self, name), self._fp8_inv_scale[name], self._fp8_next_inv_scale[name])
_shard_fp8("wqkv", 1) # (n_layers, out, dim) shard out
_shard_fp8("wo", 2) # (n_layers, dim, in) shard in
if SPLIT_W13: if SPLIT_W13:
_shard_fp8("w1", 1) self.w1.shard_(device, axis=1).realize()
_shard_fp8("w3", 1) self.w3.shard_(device, axis=1).realize()
else: else:
_shard_fp8("w13", 1) # (n_layers, hidden*2, dim) shard out self.w13.shard_(device, axis=1).realize() # (n_layers, hidden*2, dim) shard out
_shard_fp8("w2", 2) # (n_layers, dim, hidden) shard in self.w2.shard_(device, axis=2).realize() # (n_layers, dim, hidden) shard in
self.attention_norm.shard_(device, axis=None).realize() self.attention_norm.shard_(device, axis=None).realize()
self.ffn_norm.shard_(device, axis=None).realize() self.ffn_norm.shard_(device, axis=None).realize()
self.norm.weight.shard_(device, axis=None).realize() self.norm.weight.shard_(device, axis=None).realize()
@@ -245,6 +239,8 @@ class FlatTransformer:
for name in amax_dict: for name in amax_dict:
for i in range(len(amax_dict[name])): for i in range(len(amax_dict[name])):
amax_dict[name][i] = amax_dict[name][i].to(device).contiguous().is_param_(False) amax_dict[name][i] = amax_dict[name][i].to(device).contiguous().is_param_(False)
for name in self._fp8_inv_scale:
self._fp8_inv_scale[name] = self._fp8_inv_scale[name].to(device).contiguous().is_param_(False)
def __call__(self, tokens:Tensor, save:bool=True): def __call__(self, tokens:Tensor, save:bool=True):
h = self.tok_embeddings(tokens) h = self.tok_embeddings(tokens)
@@ -280,9 +276,9 @@ def apply_grad(grad_buf:Tensor, new_grad:UOp):
grad_buf.uop = grad_buf.uop.after(grad_buf.uop.store(grad_buf.uop + new_grad)) grad_buf.uop = grad_buf.uop.after(grad_buf.uop.store(grad_buf.uop + new_grad))
return return
cur = grad_buf.uop cur = grad_buf.uop
for pad in sorted(pads, key=lambda p: p.marg[0][0] if p.op == Ops.PAD else 0, reverse=True): for pad in sorted(pads, key=lambda p: p.marg[0][1] if p.op == Ops.PAD else 0, reverse=True):
if pad.op == Ops.PAD: if pad.op == Ops.PAD:
grad_shrink = tuple([(p[0], s+p[0]) for s,p in zip(pad.src[0].shape, pad.marg)]) grad_shrink = tuple([(p[1], s+p[1]) for s,p in zip(pad.src[0].shape, pad.marg)])
buf_slice = cur.shrink(grad_shrink) buf_slice = cur.shrink(grad_shrink)
cur = cur.after(buf_slice.store(buf_slice + pad.src[0].cast(cur.dtype))) cur = cur.after(buf_slice.store(buf_slice + pad.src[0].cast(cur.dtype)))
else: else:
@@ -326,7 +322,11 @@ if __name__ == "__main__":
# preallocate all the grad buffers and zero them out # preallocate all the grad buffers and zero them out
grad_dtype = lambda x: dtypes.bfloat16 if x.dtype in dtypes.fp8s else x.dtype 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} def _make_grad(x):
if isinstance(x.device, tuple) and x.uop.axis is not None:
return Tensor.zeros(x.shape, dtype=grad_dtype(x), device=x.device[0]).shard_(x.device, axis=x.uop.axis).contiguous()
return Tensor.zeros(x.shape, dtype=grad_dtype(x), device=x.device).contiguous()
grads = {x:_make_grad(x) for x in state.values() if x.is_param}
fp8_amax = [t for ts in model._fp8_amax.values() for t in ts] 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] fp8_grad_amax = [t for ts in model._fp8_grad_amax.values() for t in ts]
+5 -9
View File
@@ -6,7 +6,6 @@ from tinygrad.uop.ops import UOp, Ops
STOCHASTIC_ROUND = getenv("STOCHASTIC_ROUND", 0) STOCHASTIC_ROUND = getenv("STOCHASTIC_ROUND", 0)
MASTER_WEIGHTS = getenv("MASTER_WEIGHTS", 0) MASTER_WEIGHTS = getenv("MASTER_WEIGHTS", 0)
FP8_AMAX_MARGIN = getenv("FP8_AMAX_MARGIN", 1.1)
def stochastic_round_bf16(x:Tensor) -> Tensor: def stochastic_round_bf16(x:Tensor) -> Tensor:
bits = x.bitcast(dtypes.uint32) bits = x.bitcast(dtypes.uint32)
@@ -40,8 +39,7 @@ class GradAccClipAdamW(Optimizer):
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)) 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))
# collect inv_scale tensors attached to fp8 params (set by _apply_update) # collect inv_scale tensors attached to fp8 params (set by _apply_update)
fp8_inv_scales = [tt._inv_scale for tt in self.params if hasattr(tt, '_inv_scale')] 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')] to_realize = extra+self.params+self.buffers+(self.master_params or [])+fp8_inv_scales
to_realize = extra+self.params+self.buffers+(self.master_params or [])+fp8_inv_scales+fp8_next_inv_scales
Tensor.realize(*to_realize) Tensor.realize(*to_realize)
return extra[-1] return extra[-1]
@@ -91,15 +89,13 @@ class GradAccClipAdamW(Optimizer):
if t.dtype in dtypes.fp8s: if t.dtype in dtypes.fp8s:
from examples.mlperf.models.flat_llama import FP8_MAX from examples.mlperf.models.flat_llama import FP8_MAX
# delayed scaling: reuse previous step's inv_scale # delayed scaling: reuse previous step's inv_scale
t._inv_scale.assign(t._next_inv_scale) scale = t._inv_scale.reciprocal().reshape(-1, *([1]*(new_w.ndim-1)))
inv_scale = t._inv_scale.to(new_w.device) if offloaded else t._inv_scale
scale = inv_scale.reciprocal().reshape(-1, *([1]*(new_w.ndim-1)))
scaled = (new_w * scale).clamp(-FP8_MAX, FP8_MAX) scaled = (new_w * scale).clamp(-FP8_MAX, FP8_MAX)
ret = scaled.cast(t.dtype) ret = scaled.cast(t.dtype)
# update inv_scale for next step from quantized result # update inv_scale for next step from quantized result
new_amax = (ret.float().abs().max(axis=tuple(range(1, ret.ndim))) * inv_scale * FP8_AMAX_MARGIN).detach() new_amax = (ret.float().abs().max(axis=tuple(range(1, ret.ndim))) * t._inv_scale).detach()
new_inv = ((new_amax + 1e-8) / FP8_MAX).cast(t._inv_scale.dtype) 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) t._inv_scale.assign(inv.shard_like(t._inv_scale) if offloaded else inv)
return ret.shard_like(t) if offloaded else ret return ret.shard_like(t) if offloaded else ret
out = new_w.cast(t.dtype) out = new_w.cast(t.dtype)
return out.shard_like(t) if offloaded else out return out.shard_like(t) if offloaded else out
+1 -1
View File
@@ -23,7 +23,7 @@ def compile_net(linear:UOp, output_bufs:List[Buffer]) -> Tuple[Dict[str,str], Li
def name_of(bu:UOp, is_out:bool) -> str: def name_of(bu:UOp, is_out:bool) -> str:
nonlocal n nonlocal n
if bu.op is Ops.PARAM: key, name, size = ("in", bu.arg.slot), f"input{bu.arg.slot}", prod(bu.shape)*bu.dtype.itemsize if bu.op is Ops.PARAM: key, name, size = ("in", bu.arg), f"input{bu.arg}", prod(bu.shape)*bu.dtype.itemsize
else: else:
b = bu.buffer 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.size, b.dtype), b.size*b.dtype.itemsize
+46 -65
View File
@@ -5,7 +5,7 @@ from dataclasses import replace
if TYPE_CHECKING: from tinygrad.engine.realize import ExecContext if TYPE_CHECKING: from tinygrad.engine.realize import ExecContext
from tinygrad.helpers import DEV, getenv, select_first_inited, select_by_name, suppress_finalizing, mv_address, round_up, DEBUG, dedup, pluralize from tinygrad.helpers import DEV, getenv, select_first_inited, select_by_name, suppress_finalizing, mv_address, round_up, DEBUG, dedup, pluralize
from tinygrad.device import Device, Buffer, BufferSpec, Compiled, LRUAllocator, MultiBuffer from tinygrad.device import Device, Buffer, BufferSpec, Compiled, LRUAllocator, MultiBuffer
from tinygrad.uop.ops import Ops, sint, UOp, UPat, PatternMatcher, KernelInfo, graph_rewrite, track_rewrites, GroupOp from tinygrad.uop.ops import Ops, sint, UOp, UPat, PatternMatcher, KernelInfo, graph_rewrite, track_rewrites
from tinygrad.uop.symbolic import symbolic_simple, symbolic from tinygrad.uop.symbolic import symbolic_simple, symbolic
from tinygrad.dtype import dtypes, DType from tinygrad.dtype import dtypes, DType
from dataclasses import dataclass, field from dataclasses import dataclass, field
@@ -17,18 +17,14 @@ from tinygrad.engine.realize import to_program, track_stats, get_call_arg_uops,
HCQDeviceType = TypeVar('HCQDeviceType', bound='HCQ2Compiled') HCQDeviceType = TypeVar('HCQDeviceType', bound='HCQ2Compiled')
class HCQ2Compiled(Compiled): class HCQ2Compiled(Compiled):
timestamp_divider: float = 1000.0 # GPU timestamp counter ticks per microsecond; override per device """
A base class for devices compatible with the HCQ (Hardware Command Queue) API.
"""
timestamp_divider: float = 1000.0 # GPU timestamp counter ticks per microsecond; override per device
def __init__(self, device:str, allocator:'HCQAllocator', compilers:list[type[Renderer]], runtime, can_recover:bool=False, arch=None): def __init__(self, device:str, allocator:'HCQAllocator', compilers:list[type[Renderer]], runtime, can_recover:bool=False, arch=None):
self.device_id:int = int(device.split(":")[1]) if ":" in device else 0 self.device_id:int = int(device.split(":")[1]) if ":" in device else 0
# default pm bufferize
self.pm_bufferize = PatternMatcher([
(UPat(Ops.BUFFER, tag="timeline_signal"), lambda ctx: ctx.timeline_signal),
(UPat(Ops.BUFFER, tag="timeline_value"), lambda ctx: ctx.timeline_value),
(UPat(Ops.BUFFER, name="b"), lambda ctx, b: Buffer(ctx.device, b.arg, b.dtype, options=BufferSpec(host=True, uncached=True, cpu_access=True))),
])
super().__init__(device, allocator, compilers, lambda *a, **kw: None, None, arch=arch) super().__init__(device, allocator, compilers, lambda *a, **kw: None, None, arch=arch)
@functools.cached_property @functools.cached_property
@@ -45,6 +41,14 @@ class HCQ2Compiled(Compiled):
buf.as_memoryview(force_zero_copy=True).cast('Q')[0] = 1 buf.as_memoryview(force_zero_copy=True).cast('Q')[0] = 1
return buf return buf
@functools.cached_property
def pm_bufferize(self) -> PatternMatcher:
return PatternMatcher([
(UPat(Ops.BUFFER, tag="timeline_signal"), lambda ctx: ctx.timeline_signal),
(UPat(Ops.BUFFER, tag="timeline_value"), lambda ctx: ctx.timeline_value),
(UPat(Ops.BUFFER, name="b"), lambda ctx, b: Buffer(ctx.device, b.arg, b.dtype, options=BufferSpec(host=True, uncached=True, cpu_access=True))),
])
def synchronize(self, timeout:int|None=None): def synchronize(self, timeout:int|None=None):
if not hasattr(self, 'iface'): return if not hasattr(self, 'iface'): return
sig = self.timeline_signal._buf.cpu_view().mv.cast('Q') sig = self.timeline_signal._buf.cpu_view().mv.cast('Q')
@@ -55,6 +59,14 @@ class HCQ2Compiled(Compiled):
def device_props(self) -> dict[str,Any]: return {} # to be overridden if needed. dict keys are backend dependent. def device_props(self) -> dict[str,Any]: return {} # to be overridden if needed. dict keys are backend dependent.
def _realloc(self, oldbuf:HCQ2Buffer|None, new_size:int, options:BufferSpec|None=None, force=False) -> tuple[HCQ2Buffer, bool]:
if oldbuf is not None: self.allocator.free(oldbuf, oldbuf.size, options=options)
try: buf, realloced = self.allocator.alloc(new_size, options=options), True
except MemoryError:
if force: raise
buf, realloced = self.allocator.alloc(oldbuf.size if oldbuf is not None else new_size, options=options), False
return buf, realloced
def count(self) -> int: return self.iface.count if hasattr(self, 'iface') else 1 def count(self) -> int: return self.iface.count if hasattr(self, 'iface') else 1
def _select_iface(self): def _select_iface(self):
@@ -155,24 +167,15 @@ class HCQEncoder:
stores = [buf.index(UOp.const(dtypes.int, off), dtype=buf.dtype.ptr()).cast(dt.ptr()).store(val.cast(dt)) for off, val, dt in self.patches] stores = [buf.index(UOp.const(dtypes.int, off), dtype=buf.dtype.ptr()).cast(dt.ptr()).store(val.cast(dt)) for off, val, dt in self.patches]
return buf.after(buf.store(blob_uop), *stores) return buf.after(buf.store(blob_uop), *stores)
# *****************
# 0. helpers
HCQ_DEVS = frozenset(("AMD",))
HCQ_P2P_DEVS = HCQ_DEVS | frozenset(("CPU",))
def to_tuple(d): return d if isinstance(d, tuple) else (d,)
def all_devices_in(d:Any, c:frozenset[str]) -> bool: return {x.split(":")[0] for x in to_tuple(d)} <= c
# ***************** # *****************
# 1.1. prep runtimes: staging copies # 1.1. prep runtimes: staging copies
def _need_staging(a, b): return all_devices_in(a.device, HCQ_DEVS) and not all_devices_in(b.device, HCQ_P2P_DEVS) HCQ_DEVS = ("AMD",)
HCQ_P2P_DEVS = HCQ_DEVS + ("CPU",)
def stage_copy(dst:UOp, src:UOp) -> UOp|None: def stage_copy(dst:UOp, src:UOp) -> UOp|None:
if not (_need_staging(src, dst) or _need_staging(dst, src)): return None dst_dev, src_dev = dst.device.split(":")[0], src.device.split(":")[0]
if not ((src_dev in HCQ_DEVS and dst_dev not in HCQ_P2P_DEVS) or (dst_dev in HCQ_DEVS and src_dev not in HCQ_P2P_DEVS)): return None
stage = UOp.new_buffer("CPU", src.buffer.nbytes, dtypes.uint8) stage = UOp.new_buffer("CPU", src.buffer.nbytes, dtypes.uint8)
return UOp(Ops.LINEAR, dtypes.void, (src.copy_to_device("CPU").call(stage, src), stage.copy_to_device(dst.device).call(dst, stage))) return UOp(Ops.LINEAR, dtypes.void, (src.copy_to_device("CPU").call(stage, src), stage.copy_to_device(dst.device).call(dst, stage)))
pm_insert_copy_staging = PatternMatcher([(UPat(Ops.CALL, src=(UPat(Ops.COPY), UPat(name="dst"), UPat(name="src"))), stage_copy)]) pm_insert_copy_staging = PatternMatcher([(UPat(Ops.CALL, src=(UPat(Ops.COPY), UPat(name="dst"), UPat(name="src"))), stage_copy)])
@@ -189,8 +192,7 @@ def get_pm_prep_program(name:str) -> PatternMatcher|None:
def prep_program(call:UOp, prg:UOp) -> UOp|None: def prep_program(call:UOp, prg:UOp) -> UOp|None:
dev = call.src[1].device dev = call.src[1].device
if (pm:=get_pm_prep_program(to_tuple(dev)[0].split(":")[0])) is None or (lowered:=pm.rewrite(prg)) is None: return None if (pm:=get_pm_prep_program(dev.split(":")[0])) is None or (lowered:=pm.rewrite(prg)) is None: return None
data, image_bytes = lowered data, image_bytes = lowered
buf = UOp.new_buffer(dev, len(image_bytes), dtypes.uint8).rtag("program") buf = UOp.new_buffer(dev, len(image_bytes), dtypes.uint8).rtag("program")
blob = UOp(Ops.BINARY, dtypes.void, src=(), arg=image_bytes) blob = UOp(Ops.BINARY, dtypes.void, src=(), arg=image_bytes)
@@ -274,7 +276,7 @@ def get_pm_lower(name:str) -> PatternMatcher|None:
except ImportError: return None except ImportError: return None
def encode_cmdbuf(call:UOp, q:UOp) -> UOp|None: def encode_cmdbuf(call:UOp, q:UOp) -> UOp|None:
if (pm:=get_pm_lower(to_tuple(q.arg[0])[0].split(":")[0])) is None or (encoded:=pm.rewrite(q)) is None: return None if (pm:=get_pm_lower(call.src[1].device.split(":")[0])) is None or (encoded:=pm.rewrite(q)) is None: return None
return call.replace(src=(encoded,) + call.src[1:]) return call.replace(src=(encoded,) + call.src[1:])
pm_encode_cmdbufs = PatternMatcher([(UPat(Ops.CALL, src=(UPat(Ops.LINEAR, name="q"),), name="call", allow_any_len=True), encode_cmdbuf)]) pm_encode_cmdbufs = PatternMatcher([(UPat(Ops.CALL, src=(UPat(Ops.LINEAR, name="q"),), name="call", allow_any_len=True), encode_cmdbuf)])
@@ -302,52 +304,40 @@ pm_lift_patches_to_cmdbuf = PatternMatcher([
def bufferize_buf(buf:UOp) -> UOp|None: def bufferize_buf(buf:UOp) -> UOp|None:
if buf.tag is None: return None if buf.tag is None: return None
uops = tuple(UOp.from_buffer((dv:=Device[dev]).pm_bufferize.rewrite(buf, ctx=dv), dev) for dev in to_tuple(buf.src[1].arg)) d = buf.src[1].arg
uops = tuple(UOp.from_buffer((d:=Device[dev]).pm_bufferize.rewrite(buf, ctx=d), dev) for dev in (d if isinstance(d, tuple) else (d,)))
return uops[0] if len(uops) == 1 else UOp(Ops.MSTACK, uops[0].dtype, uops) return uops[0] if len(uops) == 1 else UOp(Ops.MSTACK, uops[0].dtype, uops)
pm_bufferize = PatternMatcher([(UPat(Ops.BUFFER, name="buf"), bufferize_buf)]) pm_bufferize = PatternMatcher([(UPat(Ops.BUFFER, name="buf"), bufferize_buf)])
# ***************** # *****************
# 5.1. capture buffers reachable from each hcq call as BIND, so we don't drop their refs # 5.1. capture buffers reachable from each hcq call as BIND, so resolve_patches doesn't drop their refs
def hold_call_buffers(call:UOp) -> UOp|None: def hold_call_buffers(call:UOp) -> UOp|None:
if not (bufs:=tuple(dedup(u for u in call.src[0].toposort() if u.op is Ops.BUFFER and u not in call.src))): return None if not (bufs:=tuple(dedup(u for u in call.src[0].toposort() if u.op is Ops.BUFFER))): return None
return call.replace(src=call.src + (UOp(Ops.BIND, dtypes.void, src=bufs),)) return call.replace(src=call.src + (UOp(Ops.BIND, dtypes.void, src=bufs),))
pm_hold_call_buffers = PatternMatcher([(UPat(Ops.CALL, tag="hcq", name="call"), hold_call_buffers)]) pm_hold_call_buffers = PatternMatcher([(UPat(Ops.CALL, tag="hcq", name="call"), hold_call_buffers)])
# ***************** # *****************
# 5.2. resolve patches # 5.2. resolve patches
def push_stack(op, s): return UOp(Ops.STACK, op.dtype.scalar().vec(len(s.src)),
tuple(op.replace(dtype=op.dtype.scalar(), src=tuple(x if y is s else y for y in op.src)) for x in s.src))
def fold_blob_store(buf:UOp, blob:UOp) -> UOp: def fold_blob_store(buf:UOp, blob:UOp) -> UOp:
for b in (buf.src if buf.op is Ops.MSTACK else (buf,)): b.buffer.ensure_allocated()._buf.cpu_view().mv.cast('B')[:len(blob.arg)] = blob.arg buf.buffer.ensure_allocated()._buf.cpu_view().mv.cast('B')[:len(blob.arg)] = blob.arg
return UOp(Ops.NOOP) return UOp(Ops.NOOP)
def fold_const_store(buf:UOp, off:UOp, val:UOp) -> UOp: def fold_const_store(buf:UOp, off:UOp, val:UOp) -> UOp:
for b, v in zip((buf.src if buf.op is Ops.MSTACK else (buf,)), (val.src if val.op is Ops.STACK else (val,))): struct.pack_into(f'<{val.dtype.fmt}', buf.buffer.ensure_allocated()._buf.cpu_view().mv.cast('B'), off.arg * buf.dtype.base.itemsize, val.arg)
struct.pack_into(f'<{v.dtype.fmt}', b.buffer.ensure_allocated()._buf.cpu_view().mv.cast('B'), off.arg * b.dtype.base.itemsize, v.arg)
return UOp(Ops.NOOP) return UOp(Ops.NOOP)
def resolve_getaddr(buf:UOp, g:UOp) -> UOp: pm_resolve_patches = symbolic + PatternMatcher([
if isinstance(b:=buf.buffer, Buffer): return UOp.const(dtypes.uint64, b.get_buf(g.src[1].arg).va_addr)
return UOp(Ops.STACK, dtypes.uint64.vec(len(b.bufs)), tuple(UOp.const(dtypes.uint64, x.ensure_allocated()._buf.va_addr) for x in b.bufs))
pm_resolve_patches = PatternMatcher([
# multi
(UPat(GroupOp.ALU, src=[UPat(Ops.STACK, name="s"), UPat(Ops.CONST)], name="op"), push_stack),
(UPat(Ops.CAST, src=(UPat(Ops.STACK, name="s"),), name="op"), push_stack),
# getaddr
(UPat(Ops.GETADDR, src=(UPat(Ops.SLICE, name="bv"), UPat(Ops.DEVICE, name="dev"))), # getaddr(slice(x)) -> offset+getaddr(x) (UPat(Ops.GETADDR, src=(UPat(Ops.SLICE, name="bv"), UPat(Ops.DEVICE, name="dev"))), # getaddr(slice(x)) -> offset+getaddr(x)
lambda bv, dev: UOp(Ops.GETADDR, dtypes.uint64, src=(bv.src[0], dev)) + UOp.const(dtypes.uint64, bv.src[1].arg * bv.src[0].dtype.itemsize)), lambda bv, dev: UOp(Ops.GETADDR, dtypes.uint64, src=(bv.src[0], dev)) + UOp.const(dtypes.uint64, bv.src[1].arg * bv.src[0].dtype.itemsize)),
(UPat(Ops.GETADDR, src=(UPat({Ops.BUFFER, Ops.MSTACK, Ops.MSELECT}, name="buf"), UPat(Ops.DEVICE)), name="g"), resolve_getaddr), (UPat(Ops.GETADDR, src=(UPat(Ops.BUFFER, name="buf"), UPat(Ops.DEVICE)), name="g"),
lambda buf, g: UOp.const(dtypes.uint64, buf.buffer.get_buf(g.src[1].arg).va_addr)),
(UPat(Ops.GETADDR, src=(UPat.cvar("const"), UPat())), lambda const: const),
# folders (UPat(Ops.BUFFER, name="buf").store(UPat(Ops.BINARY, name="blob")), fold_blob_store),
(UPat({Ops.BUFFER, Ops.MSTACK}, name="buf").store(UPat(Ops.BINARY, name="blob")), fold_blob_store), (UPat(Ops.BUFFER, name="buf").index(UPat.cvar("off")).or_casted().store(UPat.cvar("val")), fold_const_store),
(UPat({Ops.BUFFER, Ops.MSTACK}, name="buf").index(UPat.cvar("off")).or_casted().store(UPat.any(UPat.cvar("val"), UPat(Ops.STACK, name="val"))), ])
fold_const_store),
]) + symbolic_simple
# ***************** # *****************
# 6. callify hcq programs # 6. callify hcq programs
@@ -356,20 +346,12 @@ pm_fixup = PatternMatcher([ # TODO: this should gone?
(UPat(Ops.CONST, name="c"), lambda c: c.replace(src=()) if len(c.src) else None), (UPat(Ops.CONST, name="c"), lambda c: c.replace(src=()) if len(c.src) else None),
]) ])
def to_param(bufs:list[UOp], ref:UOp) -> UOp:
bufs.append(ref)
return UOp.placeholder((ref.buffer.size,), ref.dtype, len(bufs)-1)
pm_to_param = PatternMatcher([(UPat({Ops.MSELECT, Ops.MSTACK, Ops.BUFFER}, name="r"), lambda ctx, r: to_param(ctx, r))])
def parametrize_host_buffers(call:UOp) -> UOp:
body = graph_rewrite(call.src[0], pm_to_param, ctx=(bufs:=[]), bottom_up=True, name="parametrize host buffers")
return call.replace(src=(body, *bufs) + call.src[1:], tag="hcq_param")
pm_parametrize_host_buffers = PatternMatcher([(UPat(Ops.CALL, tag="hcq", name="call"), parametrize_host_buffers)])
def callify_hcq(call:UOp) -> UOp: def callify_hcq(call:UOp) -> UOp:
sink = UOp.sink(call.src[0], arg=KernelInfo(name="hcq_submit", estimates=Estimates()), tag=1) bufs = dedup([u for u in call.src[0].toposort() if u.op is Ops.BUFFER])
return to_program(sink, Device["CPU"].renderer).call(*call.src[1:]) subs = {b: UOp.placeholder((b.arg,), b.dtype, i) for i, b in enumerate(bufs)}
pm_callify_hcq = PatternMatcher([(UPat(Ops.CALL, tag="hcq_param", name="call"), callify_hcq)]) sink = UOp.sink(call.src[0].substitute(subs), arg=KernelInfo(name="hcq_submit", estimates=Estimates()), tag=1)
return to_program(sink, Device["CPU"].renderer).call(*bufs, UOp(Ops.BIND, dtypes.void, src=call.src[1:]))
pm_callify_hcq = PatternMatcher([(UPat(Ops.CALL, tag="hcq", name="call"), callify_hcq)])
@track_rewrites(lambda _,ret: f"HCQ Schedule {pluralize('Kernel', len(ret.src))}") @track_rewrites(lambda _,ret: f"HCQ Schedule {pluralize('Kernel', len(ret.src))}")
def hcq_schedule(linear:UOp) -> UOp: def hcq_schedule(linear:UOp) -> UOp:
@@ -389,7 +371,6 @@ def hcq_schedule(linear:UOp) -> UOp:
linear = graph_rewrite(linear, pm_hold_call_buffers, walk=True, name="hold call buffers") linear = graph_rewrite(linear, pm_hold_call_buffers, walk=True, name="hold call buffers")
linear = graph_rewrite(linear, pm_resolve_patches, bottom_up=False, name="simplify patches", enter_calls=True) linear = graph_rewrite(linear, pm_resolve_patches, bottom_up=False, name="simplify patches", enter_calls=True)
linear = graph_rewrite(linear, pm_fixup, bottom_up=False, name="fixup", enter_calls=True) linear = graph_rewrite(linear, pm_fixup, bottom_up=False, name="fixup", enter_calls=True)
linear = graph_rewrite(linear, pm_parametrize_host_buffers, name="parametrize host buffers") linear = graph_rewrite(linear, pm_callify_hcq, name="parametrize host buffers")
linear = graph_rewrite(linear, pm_callify_hcq, name="callify hcq")
return linear return linear
+62 -69
View File
@@ -105,9 +105,10 @@ class AMDComputeQueue(HCQEncoder):
self.acquire_mem(gli=0, gl2=0) self.acquire_mem(gli=0, gl2=0)
scratch_addr = self.get_dev_addr(UOp.new_buffer(self.devs, data.private_segment_size, dtypes.uint8).rtag("scratch")) scratch_buf = UOp.new_buffer(self.devs if len(self.devs) > 1 else self.devs[0], self.dev.scratch.size, dtypes.uint8).rtag("scratch")
args_addr = self.get_dev_addr(args) scratch_addr = self.get_dev_addr(scratch_buf)
args_addr = self.get_dev_addr(args)
user_regs = [] user_regs = []
if data.enable_private_segment_sgpr: if data.enable_private_segment_sgpr:
scratch_hilo = data64_le(scratch_addr) scratch_hilo = data64_le(scratch_addr)
@@ -118,10 +119,10 @@ class AMDComputeQueue(HCQEncoder):
self.wreg(self.gc.regCOMPUTE_PGM_LO, *data64_le(prog_addr >> 8)) self.wreg(self.gc.regCOMPUTE_PGM_LO, *data64_le(prog_addr >> 8))
self.wreg(self.gc.regCOMPUTE_PGM_RSRC1, data.rsrc1, data.rsrc2) self.wreg(self.gc.regCOMPUTE_PGM_RSRC1, data.rsrc1, data.rsrc2)
self.wreg(self.gc.regCOMPUTE_PGM_RSRC3, data.rsrc3) self.wreg(self.gc.regCOMPUTE_PGM_RSRC3, data.rsrc3)
self.wreg(self.gc.regCOMPUTE_TMPRING_SIZE, self.dev.tmpring_size(data.private_segment_size)) self.wreg(self.gc.regCOMPUTE_TMPRING_SIZE, self.dev.tmpring_size)
for xcc_id in range(self.dev.xccs): for xcc_id in range(self.dev.xccs):
scratch_base = scratch_addr + (data.private_segment_size // self.dev.xccs * xcc_id) scratch_base = scratch_addr + (self.dev.scratch.size // self.dev.xccs * xcc_id)
self.wreg(self.gc.regCOMPUTE_DISPATCH_SCRATCH_BASE_LO, *data64_le(scratch_base >> 8)) self.wreg(self.gc.regCOMPUTE_DISPATCH_SCRATCH_BASE_LO, *data64_le(scratch_base >> 8))
self.wreg(self.gc.regCOMPUTE_RESTART_X, 0, 0, 0) self.wreg(self.gc.regCOMPUTE_RESTART_X, 0, 0, 0)
@@ -152,7 +153,7 @@ def amd_submit_pm4(cmdbuf, devs):
# the compute queue's ring and its host-side ring/write/put pointers (placeholders, resolved in pm_bufferize) # the compute queue's ring and its host-side ring/write/put pointers (placeholders, resolved in pm_bufferize)
q = Device['AMD'].compute_queue q = Device['AMD'].compute_queue
ring, wptr, doorbell, put_ptr = (UOp.new_buffer(devs, b.size, b.dtype).rtag(("COMPUTE:0", name)) ring, wptr, doorbell, put_ptr = (UOp.new_buffer(devs, b.size, b.dtype).rtag(("compute_queue", name))
for name, b in (("ring", q.ring), ("write_ptr", q.write_ptr), ("doorbell", q.doorbell), ("put_value", q.put_value))) for name, b in (("ring", q.ring), ("write_ptr", q.write_ptr), ("doorbell", q.doorbell), ("put_value", q.put_value)))
# place the cmdbuf at the ring's write offset, wrapping the ring # place the cmdbuf at the ring's write offset, wrapping the ring
@@ -219,7 +220,7 @@ def amd_submit_sdma(cmdbuf, devs):
# the sdma queue's ring and its host-side ring/write/put pointers # the sdma queue's ring and its host-side ring/write/put pointers
q = Device['AMD'].sdma_queue(0) q = Device['AMD'].sdma_queue(0)
ring, wptr, doorbell, put_ptr = (UOp.new_buffer(devs, b.size, b.dtype).rtag(("SDMA:0", name)) ring, wptr, doorbell, put_ptr = (UOp.new_buffer(devs, b.size, b.dtype).rtag(("sdma_queue", name))
for name, b in (("ring", q.ring), ("write_ptr", q.write_ptr), ("doorbell", q.doorbell), ("put_value", q.put_value))) for name, b in (("ring", q.ring), ("write_ptr", q.write_ptr), ("doorbell", q.doorbell), ("put_value", q.put_value)))
# sdma needs the cmdbuf contiguous: if it won't fit before the ring end, restart at 0 and zero the tail # sdma needs the cmdbuf contiguous: if it won't fit before the ring end, restart at 0 and zero the tail
@@ -248,7 +249,7 @@ def amd_submit_sdma(cmdbuf, devs):
@dataclass(frozen=True) @dataclass(frozen=True)
class AMDProgramData: class AMDProgramData:
entry_point_offset:int; rsrc1:int; rsrc2:int; rsrc3:int; wave32:bool entry_point_offset:int; rsrc1:int; rsrc2:int; rsrc3:int; wave32:bool
private_segment_size:int; kernargs_segment_size:int; kernargs_alloc_size:int kernargs_segment_size:int; kernargs_alloc_size:int
enable_dispatch_ptr:int; enable_private_segment_sgpr:int enable_dispatch_ptr:int; enable_private_segment_sgpr:int
_amd_program_cache:dict[tuple[bytes,str], tuple[AMDProgramData,bytes]] = {} _amd_program_cache:dict[tuple[bytes,str], tuple[AMDProgramData,bytes]] = {}
@@ -270,7 +271,6 @@ def amd_build_program(prg:UOp) -> UOp:
rsrc1=desc.compute_pgm_rsrc1 | ((1<<20) if dev.target[0]==11 else 0), # priv=1 on gfx11 for cwsr rsrc1=desc.compute_pgm_rsrc1 | ((1<<20) if dev.target[0]==11 else 0), # priv=1 on gfx11 for cwsr
rsrc2=desc.compute_pgm_rsrc2 | (lds<<15), rsrc3=desc.compute_pgm_rsrc3, rsrc2=desc.compute_pgm_rsrc2 | (lds<<15), rsrc3=desc.compute_pgm_rsrc3,
wave32=bool(desc.kernel_code_properties & 0x400), wave32=bool(desc.kernel_code_properties & 0x400),
private_segment_size=desc.private_segment_fixed_size,
kernargs_segment_size=desc.kernarg_size, kernargs_segment_size=desc.kernarg_size,
kernargs_alloc_size=desc.kernarg_size + (ctypes.sizeof(hsa.hsa_kernel_dispatch_packet_t) if edp else 0), kernargs_alloc_size=desc.kernarg_size + (ctypes.sizeof(hsa.hsa_kernel_dispatch_packet_t) if edp else 0),
enable_dispatch_ptr=edp, enable_dispatch_ptr=edp,
@@ -423,13 +423,13 @@ class AMDDevice(HCQ2Compiled):
self.max_copy_size = 0x40000000 if self.iface.ip_versions[am.SDMA0_HWIP][0] >= 5 else 0x400000 self.max_copy_size = 0x40000000 if self.iface.ip_versions[am.SDMA0_HWIP][0] >= 5 else 0x400000
self.sdma_queues:dict = {} self.sdma_queues:dict = {}
self.has_sdma_queue = True # self.sdma_queue(0) is not None, TODO: think of this self.has_sdma_queue = self.sdma_queue(0) is not None
super().__init__(device, AMDAllocator(self), [HIPRenderer, AMDLLVMRenderer, HIPCCRenderer], None, can_recover=self.is_am(), arch=self.arch) super().__init__(device, AMDAllocator(self), [HIPRenderer, AMDLLVMRenderer, HIPCCRenderer], None, can_recover=self.is_am(), arch=self.arch)
# Scratch setup # Scratch setup
self.max_private_segment_size = 0 self.max_private_segment_size = 0
self.pm_bufferize = PatternMatcher([(UPat(Ops.BUFFER, tag="scratch", name="b"), lambda ctx, b: ctx.scratch_buffer(b.arg))]) + self.pm_bufferize self._ensure_has_local_memory(4096) # set default scratch size to 128 bytes per thread
self.pmc_enabled:bool = PROFILE > 0 and PMC > 0 self.pmc_enabled:bool = PROFILE > 0 and PMC > 0
if self.pmc_enabled: if self.pmc_enabled:
@@ -456,6 +456,19 @@ class AMDDevice(HCQ2Compiled):
self.sqtt_wptrs = self.allocator.alloc(round_up(self.se_cnt * self.xccs * 4, 0x1000), BufferSpec(cpu_access=True, nolru=True)) self.sqtt_wptrs = self.allocator.alloc(round_up(self.se_cnt * self.xccs * 4, 0x1000), BufferSpec(cpu_access=True, nolru=True))
self.sqtt_next_cmd_id = itertools.count(0) self.sqtt_next_cmd_id = itertools.count(0)
@functools.cached_property
def compute_queue(self) -> AMDQueueDesc:
# https://gitlab.freedesktop.org/agd5f/linux/-/blob/a1fc9f584c4aaf8bc1ebfa459fc57a3f26a290d8/drivers/gpu/drm/amd/amdkfd/kfd_queue.c#L391
sgrp_size_per_cu, hwreg_size_per_cu = 0x4000, 0x1000
lds_size_per_cu = self.iface.props["lds_size_in_kb"] << 10 if self.target[:2] == (9,5) else 0x10000
vgpr_size_per_cu = 0x60000 if self.target in {(11,0,0), (11,0,1), (11,5,1), (12,0,0), (12,0,1)} else 0x80000 if self.target[0] == 9 else 0x40000
wg_data_size = round_up((vgpr_size_per_cu + sgrp_size_per_cu + lds_size_per_cu + hwreg_size_per_cu) * self.cu_cnt, mmap.PAGESIZE)
ctl_stack_size = round_up((12 if self.target[0] != 9 else 8) * self.wave_cnt + 8 + 40, mmap.PAGESIZE)
return self.create_queue(kfd.KFD_IOC_QUEUE_TYPE_COMPUTE_AQL if self.is_aql else kfd.KFD_IOC_QUEUE_TYPE_COMPUTE,
0x2000 if self.is_usb() else (16 << 20), eop_buffer_size=0x1000,
ctx_save_restore_size=0 if self.is_am() else wg_data_size + ctl_stack_size, ctl_stack_size=ctl_stack_size,
debug_memory_size=round_up(self.wave_cnt * 32, 64))
def create_queue(self, queue_type, ring_size, ctx_save_restore_size=0, eop_buffer_size=0, ctl_stack_size=0, debug_memory_size=0, idx=0): def create_queue(self, queue_type, ring_size, ctx_save_restore_size=0, eop_buffer_size=0, ctl_stack_size=0, debug_memory_size=0, idx=0):
ring = self.iface.alloc(ring_size, uncached=True, cpu_access=True) ring = self.iface.alloc(ring_size, uncached=True, cpu_access=True)
gart = self.iface.alloc(0x100, uncached=True, cpu_access=True) gart = self.iface.alloc(0x100, uncached=True, cpu_access=True)
@@ -471,29 +484,9 @@ class AMDDevice(HCQ2Compiled):
cwsr_buffer = self.iface.alloc(cwsr_buffer_size) if ctx_save_restore_size else None cwsr_buffer = self.iface.alloc(cwsr_buffer_size) if ctx_save_restore_size else None
eop_buffer = self.iface.alloc(eop_buffer_size) if eop_buffer_size else None eop_buffer = self.iface.alloc(eop_buffer_size) if eop_buffer_size else None
queue = (self.iface.create_queue(queue_type, ring, gart, rptr=getattr(hsa.amd_queue_t, 'read_dispatch_id').offset, return (self.iface.create_queue(queue_type, ring, gart, rptr=getattr(hsa.amd_queue_t, 'read_dispatch_id').offset,
wptr=getattr(hsa.amd_queue_t, 'write_dispatch_id').offset, eop_buffer=eop_buffer, cwsr_buffer=cwsr_buffer, wptr=getattr(hsa.amd_queue_t, 'write_dispatch_id').offset, eop_buffer=eop_buffer, cwsr_buffer=cwsr_buffer,
ctx_save_restore_size=ctx_save_restore_size, ctl_stack_size=ctl_stack_size, idx=idx)) ctx_save_restore_size=ctx_save_restore_size, ctl_stack_size=ctl_stack_size, idx=idx))
qname = f"{'SDMA' if queue_type == kfd.KFD_IOC_QUEUE_TYPE_SDMA else 'COMPUTE'}:{idx}"
self.pm_bufferize = PatternMatcher([
(UPat(Ops.BUFFER, tag={(qname, name)}), lambda ctx, b=getattr(queue, name): b) for name in ["ring", "write_ptr", "doorbell", "put_value"]
]) + self.pm_bufferize
return queue
@functools.cached_property
def compute_queue(self) -> AMDQueueDesc:
# https://gitlab.freedesktop.org/agd5f/linux/-/blob/a1fc9f584c4aaf8bc1ebfa459fc57a3f26a290d8/drivers/gpu/drm/amd/amdkfd/kfd_queue.c#L391
sgrp_size_per_cu, hwreg_size_per_cu = 0x4000, 0x1000
lds_size_per_cu = self.iface.props["lds_size_in_kb"] << 10 if self.target[:2] == (9,5) else 0x10000
vgpr_size_per_cu = 0x60000 if self.target in {(11,0,0), (11,0,1), (11,5,1), (12,0,0), (12,0,1)} else 0x80000 if self.target[0] == 9 else 0x40000
wg_data_size = round_up((vgpr_size_per_cu + sgrp_size_per_cu + lds_size_per_cu + hwreg_size_per_cu) * self.cu_cnt, mmap.PAGESIZE)
ctl_stack_size = round_up((12 if self.target[0] != 9 else 8) * self.wave_cnt + 8 + 40, mmap.PAGESIZE)
return self.create_queue(kfd.KFD_IOC_QUEUE_TYPE_COMPUTE_AQL if self.is_aql else kfd.KFD_IOC_QUEUE_TYPE_COMPUTE,
0x2000 if self.is_usb() else (16 << 20), eop_buffer_size=0x1000,
ctx_save_restore_size=0 if self.is_am() else wg_data_size + ctl_stack_size, ctl_stack_size=ctl_stack_size,
debug_memory_size=round_up(self.wave_cnt * 32, 64))
def sdma_queue(self, idx:int): def sdma_queue(self, idx:int):
if getenv("AMD_DISABLE_SDMA"): return None if getenv("AMD_DISABLE_SDMA"): return None
@@ -502,50 +495,50 @@ class AMDDevice(HCQ2Compiled):
self.sdma_queues[idx] = self.create_queue(kfd.KFD_IOC_QUEUE_TYPE_SDMA, 0x200 if self.is_usb() else (16 << 20), idx=idx) self.sdma_queues[idx] = self.create_queue(kfd.KFD_IOC_QUEUE_TYPE_SDMA, 0x200 if self.is_usb() else (16 << 20), idx=idx)
return self.sdma_queues.get(idx, None) return self.sdma_queues.get(idx, None)
def tmpring_size(self, private_segment_size): def _ensure_has_local_memory(self, private_segment_size):
private_segment_size = max(private_segment_size, 128) if self.max_private_segment_size >= private_segment_size: return
lanes_per_wave = 64 # wave64 lanes_per_wave = 64 # wave64
mem_alignment_size = 256 if self.target[0] != 9 else 1024 mem_alignment_size = 256 if self.target[0] != 9 else 1024
size_per_thread = round_up(private_segment_size, mem_alignment_size // lanes_per_wave) size_per_thread = round_up(private_segment_size, mem_alignment_size // lanes_per_wave)
size_per_xcc = size_per_thread * lanes_per_wave * self.iface.props['max_slots_scratch_cu'] * self.cu_cnt size_per_xcc = size_per_thread * lanes_per_wave * self.iface.props['max_slots_scratch_cu'] * self.cu_cnt
self.scratch, ok = self._realloc(getattr(self, 'scratch', None), size_per_xcc * self.xccs)
if ok:
# NOTE: xcc logic is correct only for GFX9.
max_scratch_waves = self.cu_cnt * self.iface.props['max_slots_scratch_cu'] * self.xccs
wave_scratch = ceildiv(lanes_per_wave * size_per_thread, mem_alignment_size)
num_waves = (size_per_xcc // (wave_scratch * mem_alignment_size)) // (self.se_cnt if self.target[0] != 9 else 1)
# NOTE: xcc logic is correct only for GFX9. tmpring_t = getattr(hsa, f'union_COMPUTE_TMPRING_SIZE{"_GFX"+str(self.target[0]) if self.target[0] != 9 else ""}_bitfields')
max_scratch_waves = self.cu_cnt * self.iface.props['max_slots_scratch_cu'] * self.xccs self.tmpring_size = int.from_bytes(tmpring_t(WAVES=min(num_waves, max_scratch_waves), WAVESIZE=wave_scratch), 'little')
wave_scratch = ceildiv(lanes_per_wave * size_per_thread, mem_alignment_size)
num_waves = (size_per_xcc // (wave_scratch * mem_alignment_size)) // (self.se_cnt if self.target[0] != 9 else 1)
tmpring_t = getattr(hsa, f'union_COMPUTE_TMPRING_SIZE{"_GFX"+str(self.target[0]) if self.target[0] != 9 else ""}_bitfields')
tmpring = int.from_bytes(tmpring_t(WAVES=min(num_waves, max_scratch_waves), WAVESIZE=wave_scratch), 'little')
if hasattr(self, 'aql_desc'):
gfx9_rsrc = {'NUM_FORMAT':hsa.BUF_NUM_FORMAT_UINT, 'DATA_FORMAT':hsa.BUF_DATA_FORMAT_32, 'ELEMENT_SIZE':1, 'INDEX_STRIDE':3}
rsrc = {'DST_SEL_X':hsa.SQ_SEL_X, 'DST_SEL_Y':hsa.SQ_SEL_Y, 'DST_SEL_Z':hsa.SQ_SEL_Z, 'DST_SEL_W':hsa.SQ_SEL_W, 'ADD_TID_ENABLE':1,
'TYPE':hsa.SQ_RSRC_BUF, **(gfx9_rsrc if self.target[0] == 9 else {'FORMAT':hsa.BUF_FORMAT_32_UINT, 'OOB_SELECT':2})}
rsrc1_t = getattr(hsa, f'union_SQ_BUF_RSRC_WORD1{"_GFX11" if self.target[0] != 9 else ""}_bitfields')
rsrc3_t = getattr(hsa, f'union_SQ_BUF_RSRC_WORD3{"_GFX"+str(self.target[0]) if self.target[0] != 9 else ""}_bitfields')
self.aql_desc.scratch_backing_memory_location = int(self.scratch.get_buf().va_addr)
self.aql_desc.scratch_wave64_lane_byte_size = self.max_private_segment_size * lanes_per_wave // 64
self.aql_desc.scratch_resource_descriptor[:] = [lo32(self.scratch.get_buf().va_addr),
int.from_bytes(rsrc1_t(BASE_ADDRESS_HI=hi32(self.scratch.get_buf().va_addr), SWIZZLE_ENABLE=1), 'little'),
lo32(size_per_xcc), int.from_bytes(bytes(rsrc3_t(**rsrc)), 'little')]
self.aql_desc.compute_tmpring_size = tmpring
self.aql_gart.cpu_view()[:ctypes.sizeof(self.aql_desc)] = bytes(self.aql_desc)
return tmpring
def scratch_buffer(self, private_segment_size):
private_segment_size = max(private_segment_size, 128)
if self.max_private_segment_size < private_segment_size:
lanes_per_wave = 64 # wave64
mem_alignment_size = 256 if self.target[0] != 9 else 1024
size_per_thread = round_up(private_segment_size, mem_alignment_size // lanes_per_wave)
size_per_xcc = size_per_thread * lanes_per_wave * self.iface.props['max_slots_scratch_cu'] * self.cu_cnt
self.scratch = Buffer(self.device, size_per_xcc * self.xccs, dtypes.uint8, options=BufferSpec(nolru=True), preallocate=True)
self.max_private_segment_size = private_segment_size self.max_private_segment_size = private_segment_size
return self.scratch
if hasattr(self, 'aql_desc'):
gfx9_rsrc = {'NUM_FORMAT':hsa.BUF_NUM_FORMAT_UINT, 'DATA_FORMAT':hsa.BUF_DATA_FORMAT_32, 'ELEMENT_SIZE':1, 'INDEX_STRIDE':3}
rsrc = {'DST_SEL_X':hsa.SQ_SEL_X, 'DST_SEL_Y':hsa.SQ_SEL_Y, 'DST_SEL_Z':hsa.SQ_SEL_Z, 'DST_SEL_W':hsa.SQ_SEL_W, 'ADD_TID_ENABLE':1,
'TYPE':hsa.SQ_RSRC_BUF, **(gfx9_rsrc if self.target[0] == 9 else {'FORMAT':hsa.BUF_FORMAT_32_UINT, 'OOB_SELECT':2})}
rsrc1_t = getattr(hsa, f'union_SQ_BUF_RSRC_WORD1{"_GFX11" if self.target[0] != 9 else ""}_bitfields')
rsrc3_t = getattr(hsa, f'union_SQ_BUF_RSRC_WORD3{"_GFX"+str(self.target[0]) if self.target[0] != 9 else ""}_bitfields')
self.aql_desc.scratch_backing_memory_location = int(self.scratch.va_addr)
self.aql_desc.scratch_wave64_lane_byte_size = self.max_private_segment_size * lanes_per_wave // 64
self.aql_desc.scratch_resource_descriptor[:] = [lo32(self.scratch.va_addr),
int.from_bytes(rsrc1_t(BASE_ADDRESS_HI=hi32(self.scratch.va_addr), SWIZZLE_ENABLE=1), 'little'),
lo32(size_per_xcc), int.from_bytes(bytes(rsrc3_t(**rsrc)), 'little')]
self.aql_desc.compute_tmpring_size = self.tmpring_size
self.aql_gart.cpu_view()[:ctypes.sizeof(self.aql_desc)] = bytes(self.aql_desc)
def on_device_hang(self): self.iface.on_device_hang() def on_device_hang(self): self.iface.on_device_hang()
@functools.cached_property
def pm_bufferize(self) -> PatternMatcher:
return PatternMatcher([
(UPat(Ops.BUFFER, tag="scratch"),
lambda ctx: Buffer(ctx.device, ctx.scratch.size, dtypes.uint8, opaque=ctx.scratch, options=BufferSpec(external_ptr=1))),
(UPat(Ops.BUFFER, tag={("compute_queue", n) for n in ("ring", "write_ptr", "doorbell", "put_value")}, name="b"),
lambda ctx, b: getattr(ctx.compute_queue, b.tag[1])),
(UPat(Ops.BUFFER, tag={("sdma_queue", n) for n in ("ring", "write_ptr", "doorbell", "put_value")}, name="b"),
lambda ctx, b: getattr(ctx.sdma_queue(0), b.tag[1])),
]) + super().pm_bufferize
def device_props(self): return self.iface.props def device_props(self): return self.iface.props
+1 -1
View File
@@ -23,7 +23,7 @@ def calculate_storage_offset(x: Tensor) -> int:
for u in x.uop.toposort(): for u in x.uop.toposort():
if u.op == Ops.SHRINK: if u.op == Ops.SHRINK:
u_strides = strides_for_shape(u.src[0].shape) u_strides = strides_for_shape(u.src[0].shape)
for i, (start, _) in enumerate(u.marg): offset += start * u_strides[i] for i, (_, start) in enumerate(u.marg): offset += start * u_strides[i]
return offset return offset
def wrap(x: Tensor) -> torch.Tensor: def wrap(x: Tensor) -> torch.Tensor:
x._strides = strides_for_shape(x.shape) # always recalculate x._strides = strides_for_shape(x.shape) # always recalculate
+2 -2
View File
@@ -240,9 +240,9 @@ class TestTorchBackend(unittest.TestCase):
np.testing.assert_equal(result.cpu().numpy(), [3., 3., 2.]) np.testing.assert_equal(result.cpu().numpy(), [3., 3., 2.])
def test_mnist_index(self): def test_mnist_index(self):
# from tinygrad.nn.datasets import mnist
X_train, Y_train = Tensor.randint(60000, 1, 28, 28, dtype='uchar').realize(), Tensor.randint(60000, dtype='uchar').realize()
GlobalCounters.reset() GlobalCounters.reset()
from tinygrad.nn.datasets import mnist
X_train, Y_train, _, _ = mnist()
X_train = torch.tensor(X_train.float().numpy(), device=device) X_train = torch.tensor(X_train.float().numpy(), device=device)
Y_train = torch.tensor(Y_train.cast('int64').numpy(), device=device) Y_train = torch.tensor(Y_train.cast('int64').numpy(), device=device)
samples = torch.randint(0, X_train.shape[0], (32,)) samples = torch.randint(0, X_train.shape[0], (32,))
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -74,8 +74,8 @@ A \op{Buffer}'s \textbf{addrspace} is \texttt{GLOBAL}, \texttt{LOCAL}, or \textt
\op{Flip} & $(T,)$ & bools $\mathbf{f}$ & Reverse along flagged axes. \\ \op{Flip} & $(T,)$ & bools $\mathbf{f}$ & Reverse along flagged axes. \\
\op{Reshape} & $(T, \mathbf{s'})$ & --- & Reinterpret in row-major order. $\prod s_k = \prod s'_k$. \\ \op{Reshape} & $(T, \mathbf{s'})$ & --- & Reinterpret in row-major order. $\prod s_k = \prod s'_k$. \\
\op{Expand} & $(T, \mathbf{s'})$ & --- & Broadcast size-1 axes. $s_k \in \{1, s'_k\}$. \\ \op{Expand} & $(T, \mathbf{s'})$ & --- & Broadcast size-1 axes. $s_k \in \{1, s'_k\}$. \\
\op{Pad} & $(T, \mathbf{o}, \mathbf{s'})$ & --- & Place $T$ at offset $o_k$ in a zero-filled output of shape $s'_k$. \\ \op{Pad} & $(T, \mathbf{s'}, \mathbf{o})$ & --- & Place $T$ at offset $o_k$ in a zero-filled output of shape $s'_k$. \\
\op{Shrink} & $(T, \mathbf{o}, \mathbf{s'})$ & --- & Keep $s'_k$ elements starting at offset $o_k$ per axis. Inverse of \op{Pad}. \\ \op{Shrink} & $(T, \mathbf{s'}, \mathbf{o})$ & --- & Keep $s'_k$ elements starting at offset $o_k$ per axis. Inverse of \op{Pad}. \\
\op{Index} & $(T, i_0, i_1, \ldots)$ & --- & Index from left. $()$-shaped $i$ removes dim; $(k,)$-shaped makes it $k$. \\ \op{Index} & $(T, i_0, i_1, \ldots)$ & --- & Index from left. $()$-shaped $i$ removes dim; $(k,)$-shaped makes it $k$. \\
\op{Stack} & $(T_0, T_1, \ldots)$ & --- & Join along a newly created leading axis. All shapes must match. \\ \op{Stack} & $(T_0, T_1, \ldots)$ & --- & Join along a newly created leading axis. All shapes must match. \\
\op{Replicated} & $(T,)$ & axes & Mark $T$ as replicated along axes. Collapse axes to $1$. \\ \op{Replicated} & $(T,)$ & axes & Mark $T$ as replicated along axes. Collapse axes to $1$. \\
+5 -5
View File
@@ -167,7 +167,7 @@ class TestDSPcodePatterns(unittest.TestCase):
def test_global_atomic_add_f32_parsing(self): def test_global_atomic_add_f32_parsing(self):
"""Test GLOBAL_ATOMIC_ADD_F32 keeps memory values in float dtype.""" """Test GLOBAL_ATOMIC_ADD_F32 keeps memory values in float dtype."""
vmem = UOp.param(2, dtypes.uint32.ptr(1024)) vmem = UOp(Ops.PARAM, dtypes.uint32.ptr(1024), arg=2)
srcs = { srcs = {
'ADDR': UOp.const(dtypes.uint64, 0), 'ADDR': UOp.const(dtypes.uint64, 0),
'DATA': UOp.const(dtypes.uint32, 0x3f800000), 'DATA': UOp.const(dtypes.uint32, 0x3f800000),
@@ -198,7 +198,7 @@ class TestDSPcodePatterns(unittest.TestCase):
def test_mem_read_parsing(self): def test_mem_read_parsing(self):
"""Test MEM[addr].type read expression parsing.""" """Test MEM[addr].type read expression parsing."""
# Create a mock LDS buffer # Create a mock LDS buffer
lds = UOp.param(3, dtypes.uint32.ptr(16384)) lds = UOp(Ops.PARAM, dtypes.uint32.ptr(16384), arg=3)
addr = UOp.const(dtypes.uint32, 0) addr = UOp.const(dtypes.uint32, 0)
vrs = {'_lds': lds, 'ADDR': addr, 'OFFSET': UOp.const(dtypes.uint32, 0)} vrs = {'_lds': lds, 'ADDR': addr, 'OFFSET': UOp.const(dtypes.uint32, 0)}
@@ -233,7 +233,7 @@ class TestDSPcodePatterns(unittest.TestCase):
pcode = PCODE.get(DSOp.DS_LOAD_2ADDR_B32) pcode = PCODE.get(DSOp.DS_LOAD_2ADDR_B32)
self.assertIsNotNone(pcode) self.assertIsNotNone(pcode)
assert pcode is not None assert pcode is not None
lds = UOp.param(3, dtypes.uint32.ptr(16384)) lds = UOp(Ops.PARAM, dtypes.uint32.ptr(16384), arg=3)
srcs = { srcs = {
'ADDR': UOp.const(dtypes.uint32, 0), 'ADDR': UOp.const(dtypes.uint32, 0),
'OFFSET0': UOp.const(dtypes.uint32, 0), 'OFFSET0': UOp.const(dtypes.uint32, 0),
@@ -314,7 +314,7 @@ class TestConcatWidthParsing(unittest.TestCase):
self.assertEqual(parsed.simplify().arg, expected) self.assertEqual(parsed.simplify().arg, expected)
def test_permlane64_wave64_pcode_indices(self): def test_permlane64_wave64_pcode_indices(self):
vgpr = UOp.param(0, dtypes.uint32.ptr(256)) vgpr = UOp(Ops.PARAM, dtypes.uint32.ptr(256), arg=0)
srcs = { srcs = {
'SRC0': UOp.const(dtypes.uint32, 0), 'SRC0': UOp.const(dtypes.uint32, 0),
'VDST': UOp.const(dtypes.uint32, 1), 'VDST': UOp.const(dtypes.uint32, 1),
@@ -347,7 +347,7 @@ class TestAllPcode(unittest.TestCase):
def _make_srcs(self): def _make_srcs(self):
"""Create dummy source variables for pcode parsing.""" """Create dummy source variables for pcode parsing."""
u32, u64 = lambda v=0: UOp.const(dtypes.uint32, v), lambda v=0: UOp.const(dtypes.uint64, v) u32, u64 = lambda v=0: UOp.const(dtypes.uint32, v), lambda v=0: UOp.const(dtypes.uint64, v)
lds = UOp.param(3, dtypes.uint32.ptr(16384)) lds = UOp(Ops.PARAM, dtypes.uint32.ptr(16384), arg=3)
return {'laneId': u32(), 'laneID': u32(), 'S0': u32(), 'S1': u32(), 'S2': u32(), 'S3': u32(), 'SRC0': u32(), return {'laneId': u32(), 'laneID': u32(), 'S0': u32(), 'S1': u32(), 'S2': u32(), 'S3': u32(), 'SRC0': u32(),
'D0': u32(), 'D1': u32(), 'DST': u32(), 'VDST': u32(), 'SDST': u32(), 'D0': u32(), 'D1': u32(), 'DST': u32(), 'VDST': u32(), 'SDST': u32(),
'VCC': u64(), 'VCCZ': u32(), 'EXEC': u64(), 'EXEC_LO': u32(), 'EXECZ': u32(), 'SCC': u32(), 'VCC': u64(), 'VCCZ': u32(), 'EXEC': u64(), 'EXEC_LO': u32(), 'EXECZ': u32(), 'SCC': u32(),
+2 -2
View File
@@ -125,8 +125,8 @@ class TestIndexing(unittest.TestCase):
def test_index_mnist(self, noopt=1, op_limit=512*784*13, split_reduceop=0): def test_index_mnist(self, noopt=1, op_limit=512*784*13, split_reduceop=0):
# WEBGPU generates more ops due to bitpacking of < 4-byte dtypes # WEBGPU generates more ops due to bitpacking of < 4-byte dtypes
if Device.DEFAULT == "WEBGPU": op_limit *= 15 if Device.DEFAULT == "WEBGPU": op_limit *= 15
# from tinygrad.nn.datasets import mnist from tinygrad.nn.datasets import mnist
X_train, Y_train = Tensor.randint(DSET, 1, 28, 28, dtype='uchar').realize(), Tensor.randint(DSET, dtype='uchar').realize() X_train, Y_train, _, _ = mnist()
with Context(NOOPT=noopt, SPLIT_REDUCEOP=split_reduceop): with Context(NOOPT=noopt, SPLIT_REDUCEOP=split_reduceop):
samples = Tensor.randint(getenv("BS", 512), high=X_train.shape[0]).realize() samples = Tensor.randint(getenv("BS", 512), high=X_train.shape[0]).realize()
GlobalCounters.reset() GlobalCounters.reset()
+6 -6
View File
@@ -57,7 +57,7 @@ class TestIselX86(unittest.TestCase):
# need to move src from gpr to xmm before broadcasting # need to move src from gpr to xmm before broadcasting
self.assertTrue(n.arg is X86Ops.VPBROADCASTD and n.src[0].arg is X86Ops.VMOVD) self.assertTrue(n.arg is X86Ops.VPBROADCASTD and n.src[0].arg is X86Ops.VMOVD)
# if we can fuse a load we can skip the move and access memory directly # if we can fuse a load we can skip the move and access memory directly
load = UOp.param(0, dtypes.int32.ptr()).index(UOp.const(dtypes.int32, 0), ptr=True).load() load = UOp(Ops.PARAM, dtypes.int32.ptr(), arg=0).index(UOp.const(dtypes.int32, 0), ptr=True).load()
n = self.isel_rewrite(load.broadcast(4)) n = self.isel_rewrite(load.broadcast(4))
self.assertTrue(n.arg is X86Ops.VPBROADCASTD and len(n.src) == 3) self.assertTrue(n.arg is X86Ops.VPBROADCASTD and len(n.src) == 3)
@@ -122,20 +122,20 @@ class TestIselX86(unittest.TestCase):
# complex address is [base + index*scale + displacement] # complex address is [base + index*scale + displacement]
def test_complex_address(self): def test_complex_address(self):
a = UOp.variable("a", 0, 0, dtypes.int32) a = UOp.variable("a", 0, 0, dtypes.int32)
load = UOp.param(0, dtypes.int32.ptr()).index(a + 1, ptr=True).load() load = UOp(Ops.PARAM, dtypes.int32.ptr(), arg=0).index(a + 1, ptr=True).load()
n = self.isel_rewrite(load) n = self.isel_rewrite(load)
# displacement is the constant in "a" scaled to the buffer element size, dtype is int8 when the value fits otherwise int32 # displacement is the constant in "a" scaled to the buffer element size, dtype is int8 when the value fits otherwise int32
self.assertTrue(n.src[2].op is Ops.CONST and n.src[2].dtype is dtypes.int8 and n.src[2].arg == 4) self.assertTrue(n.src[2].op is Ops.CONST and n.src[2].dtype is dtypes.int8 and n.src[2].arg == 4)
def test_fold_load(self): def test_fold_load(self):
load1 = UOp.param(0, dtypes.int32.ptr()).index(UOp.const(dtypes.int32, 0), ptr=True).load() load1 = UOp(Ops.PARAM, dtypes.int32.ptr(), arg=0).index(UOp.const(dtypes.int32, 0), ptr=True).load()
load2 = UOp.param(0, dtypes.int32.ptr()).index(UOp.const(dtypes.int32, 1), ptr=True).load() load2 = UOp(Ops.PARAM, dtypes.int32.ptr(), arg=0).index(UOp.const(dtypes.int32, 1), ptr=True).load()
n = self.isel_rewrite(load1 + load2) n = self.isel_rewrite(load1 + load2)
self.assertTrue(len(n.src) == 4) self.assertTrue(len(n.src) == 4)
# don't fold when used multiple times # don't fold when used multiple times
def test_dont_fold_load(self): def test_dont_fold_load(self):
load = UOp.param(0, dtypes.int32.ptr()).index(UOp.const(dtypes.int32, 0), ptr=True).load() load = UOp(Ops.PARAM, dtypes.int32.ptr(), arg=0).index(UOp.const(dtypes.int32, 0), ptr=True).load()
# used by multiple users # used by multiple users
n = self.isel_rewrite(load + 1 + load) n = self.isel_rewrite(load + 1 + load)
self.assertTrue(len(n.src) == 2) self.assertTrue(len(n.src) == 2)
@@ -144,4 +144,4 @@ class TestIselX86(unittest.TestCase):
self.assertTrue(len(n.src) == 2) self.assertTrue(len(n.src) == 2)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+3 -3
View File
@@ -11,16 +11,16 @@ from tinygrad.codegen import to_program
class TestLinearizerFailure(unittest.TestCase): class TestLinearizerFailure(unittest.TestCase):
@unittest.skipUnless(Device.DEFAULT == "METAL", "only tested on METAL") @unittest.skipUnless(Device.DEFAULT == "METAL", "only tested on METAL")
def test_failure_beam_mnist(self): def test_failure_beam_mnist(self):
c0 = UOp.param(0, dtypes.uchar.ptr(4014080)) c0 = UOp(Ops.PARAM, dtypes.uchar.ptr(4014080), arg=0, src=())
c1 = UOp.range(UOp.const(dtypes.weakint, 512), 0, AxisType.GLOBAL) c1 = UOp.range(UOp.const(dtypes.weakint, 512), 0, AxisType.GLOBAL)
c2 = UOp.range(UOp.const(dtypes.weakint, 784), 1, AxisType.GLOBAL) c2 = UOp.range(UOp.const(dtypes.weakint, 784), 1, AxisType.GLOBAL)
c3 = UOp.range(UOp.const(dtypes.weakint, 10), 3, AxisType.GLOBAL) c3 = UOp.range(UOp.const(dtypes.weakint, 10), 3, AxisType.GLOBAL)
c4 = UOp.param(1, dtypes.int.ptr(512)) c4 = UOp(Ops.PARAM, dtypes.int.ptr(512), arg=1, src=())
c5 = c4.index(c1.valid(UOp.const(dtypes.bool, True))) c5 = c4.index(c1.valid(UOp.const(dtypes.bool, True)))
c6 = UOp.range(UOp.const(dtypes.weakint, 6000), 1004, AxisType.REDUCE) c6 = UOp.range(UOp.const(dtypes.weakint, 6000), 1004, AxisType.REDUCE)
c7 = UOp.range(UOp.const(dtypes.weakint, 3750), 2006, AxisType.REDUCE) c7 = UOp.range(UOp.const(dtypes.weakint, 3750), 2006, AxisType.REDUCE)
c8 = UOp.range(UOp.const(dtypes.weakint, 16), 2007, AxisType.GROUP_REDUCE) c8 = UOp.range(UOp.const(dtypes.weakint, 16), 2007, AxisType.GROUP_REDUCE)
c9 = UOp.param(2, dtypes.uchar.ptr(47040000)) c9 = UOp(Ops.PARAM, dtypes.uchar.ptr(47040000), arg=2, src=())
c10 = c9.index((((c3*UOp.const(dtypes.weakint, 4704000))+c2)+(c6*UOp.const(dtypes.weakint, 784))).valid(UOp.const(dtypes.bool, True))) c10 = c9.index((((c3*UOp.const(dtypes.weakint, 4704000))+c2)+(c6*UOp.const(dtypes.weakint, 784))).valid(UOp.const(dtypes.bool, True)))
c11 = c5.alu(Ops.CMPNE, ((((c3*UOp.const(dtypes.weakint, 6000))+c6)+((c7*UOp.const(dtypes.weakint, 16))+c8)).alu(Ops.CMPLT, UOp.const(dtypes.weakint, 59999)).where(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 1)).reduce(c7, c8, arg=Ops.ADD)+UOp.const(dtypes.int, -1))).where(UOp.const(dtypes.uchar, 0), c10).reduce(c6, arg=Ops.ADD) c11 = c5.alu(Ops.CMPNE, ((((c3*UOp.const(dtypes.weakint, 6000))+c6)+((c7*UOp.const(dtypes.weakint, 16))+c8)).alu(Ops.CMPLT, UOp.const(dtypes.weakint, 59999)).where(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 1)).reduce(c7, c8, arg=Ops.ADD)+UOp.const(dtypes.int, -1))).where(UOp.const(dtypes.uchar, 0), c10).reduce(c6, arg=Ops.ADD)
c12 = c0.index((((c1*UOp.const(dtypes.weakint, 7840))+(c2*UOp.const(dtypes.weakint, 10)))+c3).valid(UOp.const(dtypes.bool, True))).store(c11).end(c1, c2, c3) c12 = c0.index((((c1*UOp.const(dtypes.weakint, 7840))+(c2*UOp.const(dtypes.weakint, 10)))+c3).valid(UOp.const(dtypes.bool, True))).store(c11).end(c1, c2, c3)
+5 -5
View File
@@ -22,8 +22,8 @@ def _test_uop_result(inputs:list[Tensor], sink:UOp, local_size=None):
def _setup_and_test_alu(alu_op:Ops, input_val:ConstType, *alu_src_uops:UOp): def _setup_and_test_alu(alu_op:Ops, input_val:ConstType, *alu_src_uops:UOp):
dtype = alu_src_uops[0].dtype dtype = alu_src_uops[0].dtype
a = UOp.param(0, dtype.ptr()) a = UOp(Ops.PARAM, dtype.ptr(), (), 0)
b = UOp.param(1, dtype.ptr()) b = UOp(Ops.PARAM, dtype.ptr(), (), 1)
idx = UOp.const(dtypes.int, 0) idx = UOp.const(dtypes.int, 0)
ld = b.index(idx) ld = b.index(idx)
alu = ld.alu(alu_op, *alu_src_uops) alu = ld.alu(alu_op, *alu_src_uops)
@@ -33,7 +33,7 @@ def _setup_and_test_alu(alu_op:Ops, input_val:ConstType, *alu_src_uops:UOp):
class TestRendererFailures(unittest.TestCase): class TestRendererFailures(unittest.TestCase):
@unittest.skipIf(not isinstance(Device[Device.DEFAULT].renderer, (PTXRenderer, PythonRenderer)), "test is for ptx or python renderer") @unittest.skipIf(not isinstance(Device[Device.DEFAULT].renderer, (PTXRenderer, PythonRenderer)), "test is for ptx or python renderer")
def test_gated_store_with_alu(self): def test_gated_store_with_alu(self):
a = UOp.param(0, dtypes.int.ptr()) a = UOp(Ops.PARAM, dtypes.int.ptr(), (), 0)
gate_alu = (lidx0:=UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'lidx0')).ne(0) gate_alu = (lidx0:=UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'lidx0')).ne(0)
gated_alu_store = UOp(Ops.STORE, dtypes.void, (a.index(lidx0.valid(gate_alu)), UOp.const(dtypes.int, 1))) gated_alu_store = UOp(Ops.STORE, dtypes.void, (a.index(lidx0.valid(gate_alu)), UOp.const(dtypes.int, 1)))
sink = UOp(Ops.SINK, dtypes.void, (gated_alu_store,), arg=KernelInfo()) sink = UOp(Ops.SINK, dtypes.void, (gated_alu_store,), arg=KernelInfo())
@@ -42,7 +42,7 @@ class TestRendererFailures(unittest.TestCase):
@unittest.skipIf(not isinstance(Device[Device.DEFAULT].renderer, (PTXRenderer, PythonRenderer)), "test is for ptx or python renderer") @unittest.skipIf(not isinstance(Device[Device.DEFAULT].renderer, (PTXRenderer, PythonRenderer)), "test is for ptx or python renderer")
def test_gated_store_with_alu_2d(self): def test_gated_store_with_alu_2d(self):
a = UOp.param(0, dtypes.int.ptr()) a = UOp(Ops.PARAM, dtypes.int.ptr(), (), 0)
gate_alu_0 = (lidx0:=UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'lidx0')).ne(0) gate_alu_0 = (lidx0:=UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'lidx0')).ne(0)
gate_alu_1 = (lidx1:=UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 2),), 'lidx1')).ne(0) gate_alu_1 = (lidx1:=UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 2),), 'lidx1')).ne(0)
gated_alu_store = UOp(Ops.STORE, dtypes.void, (a.index((lidx0+lidx1*4).valid(gate_alu_0&gate_alu_1)), UOp.const(dtypes.int, 1))) gated_alu_store = UOp(Ops.STORE, dtypes.void, (a.index((lidx0+lidx1*4).valid(gate_alu_0&gate_alu_1)), UOp.const(dtypes.int, 1)))
@@ -87,7 +87,7 @@ class TestWGSLFailures(unittest.TestCase):
class TestPTXFailures(unittest.TestCase): class TestPTXFailures(unittest.TestCase):
@unittest.skip("INDEX can only have a gate ALU parent, not an IF") @unittest.skip("INDEX can only have a gate ALU parent, not an IF")
def test_gated_store_with_if(self): def test_gated_store_with_if(self):
a = UOp.param(0, dtypes.int.ptr()) a = UOp(Ops.PARAM, dtypes.int.ptr(), (), 0)
gate_alu = (lidx0:=UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'lidx0')).ne(0) gate_alu = (lidx0:=UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'lidx0')).ne(0)
val = UOp.const(dtypes.int, 1) val = UOp.const(dtypes.int, 1)
if_uop = UOp(Ops.IF, dtypes.void, (gate_alu,)) if_uop = UOp(Ops.IF, dtypes.void, (gate_alu,))
+2 -2
View File
@@ -1059,9 +1059,9 @@ class TestSchedule(unittest.TestCase):
self.assertEqual(b.tolist(), [False, False]) self.assertEqual(b.tolist(), [False, False])
def test_mnist_val(self): def test_mnist_val(self):
# from tinygrad.nn.datasets import mnist from tinygrad.nn.datasets import mnist
import torch import torch
Y_train = Tensor.randint(60000, dtype='uchar').realize() _, Y_train, _, _ = mnist()
samples = Tensor.randint(BS:=getenv("BS", 512), high=cast(int,Y_train.shape[-1])).realize() samples = Tensor.randint(BS:=getenv("BS", 512), high=cast(int,Y_train.shape[-1])).realize()
yt = Tensor.randn(BS, 10).realize() yt = Tensor.randn(BS, 10).realize()
loss = yt.sparse_categorical_crossentropy(Y_train[samples]) loss = yt.sparse_categorical_crossentropy(Y_train[samples])
+1 -1
View File
@@ -323,7 +323,7 @@ class TestWithGrad(unittest.TestCase):
def test_set_overlapping_backward(self): def test_set_overlapping_backward(self):
z = Tensor.zeros(6) z = Tensor.zeros(6)
x = Tensor.ones(4).contiguous() x = Tensor.ones(4)
y = Tensor.ones(4) * 2 y = Tensor.ones(4) * 2
z[:4] = x z[:4] = x
z[2:] = y z[2:] = y
+4 -5
View File
@@ -20,7 +20,6 @@ def run_uops(uops_list:list[UOp], bufs:list[Buffer]):
def uop(uops:list[UOp], op:Ops, dtype:Optional[DType], src:tuple[UOp, ...], arg:Any=None) -> UOp: def uop(uops:list[UOp], op:Ops, dtype:Optional[DType], src:tuple[UOp, ...], arg:Any=None) -> UOp:
if op is Ops.CONST: uops.append(UOp.const(dtype, arg)) if op is Ops.CONST: uops.append(UOp.const(dtype, arg))
elif op is Ops.PARAM: uops.append(UOp.param(arg, dtype).replace(src=()))
else: uops.append(UOp(op, dtype, tuple(src), arg)) else: uops.append(UOp(op, dtype, tuple(src), arg))
return uops[-1] return uops[-1]
@@ -221,8 +220,8 @@ class TestLocalAccess(unittest.TestCase):
@unittest.skipUnless(isinstance(Device[Device.DEFAULT].renderer, PTXRenderer), "This only tests assembly backends") @unittest.skipUnless(isinstance(Device[Device.DEFAULT].renderer, PTXRenderer), "This only tests assembly backends")
class TestAssembly(unittest.TestCase): class TestAssembly(unittest.TestCase):
def test_bitshift_left(self): def test_bitshift_left(self):
g1 = UOp.param(0, dtypes.int32.ptr()) g1 = UOp(Ops.PARAM, dtypes.int32.ptr(), (), 0)
out = UOp.param(1, dtypes.int32.ptr()) out = UOp(Ops.PARAM, dtypes.int32.ptr(), (), 1)
c1 = UOp.const(dtypes.int, 2) c1 = UOp.const(dtypes.int, 2)
c2 = UOp.const(dtypes.int, 3) c2 = UOp.const(dtypes.int, 3)
l1 = g1.index(c1) l1 = g1.index(c1)
@@ -249,7 +248,7 @@ class TestAssembly(unittest.TestCase):
self.assertGreaterEqual(len([x.op for x in uops if x.op is Ops.MULACC]), 4) self.assertGreaterEqual(len([x.op for x in uops if x.op is Ops.MULACC]), 4)
def test_mulacc_shl(self): def test_mulacc_shl(self):
g1 = UOp.param(0, dtypes.int32.ptr()) g1 = UOp(Ops.PARAM, dtypes.int32.ptr(), (), 0)
c1 = UOp.const(dtypes.int, 0) c1 = UOp.const(dtypes.int, 0)
c2 = UOp.const(dtypes.int, 1) c2 = UOp.const(dtypes.int, 1)
expr = g1.index(c1) * UOp.const(dtypes.int, 4096) + g1.index(c2) expr = g1.index(c1) * UOp.const(dtypes.int, 4096) + g1.index(c2)
@@ -258,7 +257,7 @@ class TestAssembly(unittest.TestCase):
self.assertIn(Ops.MULACC, [x.op for x in uops]) self.assertIn(Ops.MULACC, [x.op for x in uops])
def test_use_cmpeq(self): def test_use_cmpeq(self):
g = UOp.param(0, dtypes.uint32.ptr()) g = UOp(Ops.PARAM, dtypes.uint32.ptr(), (), 0)
c = UOp.const(dtypes.uint, 7) c = UOp.const(dtypes.uint, 7)
comp = g.index(c).ne(c).ne(True) comp = g.index(c).ne(c).ne(True)
uops = to_uops_list([comp], ren=Device[Device.DEFAULT].renderer) uops = to_uops_list([comp], ren=Device[Device.DEFAULT].renderer)
+13 -13
View File
@@ -12,7 +12,7 @@ from tinygrad.dtype import ImageDType, Invalid
# PYTHONPATH="." DEV=QCOM FLOAT16=1 IMAGE=2 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/720392c9a5b986981fdbed1bb8c47a6c5573a50e/selfdrive/modeld/models/driving_vision.onnx # PYTHONPATH="." DEV=QCOM FLOAT16=1 IMAGE=2 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/720392c9a5b986981fdbed1bb8c47a6c5573a50e/selfdrive/modeld/models/driving_vision.onnx
def vision_conv_143(): def vision_conv_143():
c0 = UOp.param(0, dtypes.imageh((16, 1024, 4))) c0 = UOp(Ops.PARAM, dtypes.imageh((16, 1024, 4)), (), 0)
c2 = UOp.range(32, 3, AxisType.LOOP) c2 = UOp.range(32, 3, AxisType.LOOP)
c5 = UOp.range(128, 4, AxisType.LOOP) c5 = UOp.range(128, 4, AxisType.LOOP)
c8 = UOp.range(16, 2, AxisType.LOOP) c8 = UOp.range(16, 2, AxisType.LOOP)
@@ -22,13 +22,13 @@ def vision_conv_143():
c26 = UOp.range(7, 1, AxisType.REDUCE) c26 = UOp.range(7, 1, AxisType.REDUCE)
c27 = c2*2+c26 c27 = c2*2+c26
c32 = ((c27<3)!=True)&(c27<67) c32 = ((c27<3)!=True)&(c27<67)
c34 = UOp.param(1, dtypes.imageh((32, 1024, 4))) c34 = UOp(Ops.PARAM, dtypes.imageh((32, 1024, 4)), (), 1)
c38 = c5//2 c38 = c5//2
c45 = (c32&c24).where((c27*64+c38+c17*4096+-12480), UOp.const(dtypes.weakint, Invalid)) c45 = (c32&c24).where((c27*64+c38+c17*4096+-12480), UOp.const(dtypes.weakint, Invalid))
c48 = (c24&c32).where(c34.index(c45), UOp.const(dtypes.float, 0.0)) c48 = (c24&c32).where(c34.index(c45), UOp.const(dtypes.float, 0.0))
c49 = UOp.param(2, dtypes.imageh((64, 49, 4))) c49 = UOp(Ops.PARAM, dtypes.imageh((64, 49, 4)), (), 2)
c61 = c48*c49.index((c26*4+c5%2+c16*28+c38*196)) c61 = c48*c49.index((c26*4+c5%2+c16*28+c38*196))
c63 = UOp.param(3, dtypes.float.ptr(128)) c63 = UOp(Ops.PARAM, dtypes.float.ptr(128), (), 3)
c65 = c61.reduce(c16, c26, arg=Ops.ADD)+c63.index(c5) c65 = c61.reduce(c16, c26, arg=Ops.ADD)+c63.index(c5)
c67 = c0.index((c2*128+c5+c8*4096), ptr=True).store(c65).end(c8, c2, c5) c67 = c0.index((c2*128+c5+c8*4096), ptr=True).store(c65).end(c8, c2, c5)
@@ -38,7 +38,7 @@ def vision_conv_143():
return c67.sink(arg=KernelInfo(name="conv", opts_to_apply=opts)) return c67.sink(arg=KernelInfo(name="conv", opts_to_apply=opts))
def vision_conv_153(): def vision_conv_153():
c0 = UOp.param(0, dtypes.imageh((8, 1024, 4))) c0 = UOp(Ops.PARAM, dtypes.imageh((8, 1024, 4)), (), 0)
c2 = UOp.range(16, 3, AxisType.LOOP) c2 = UOp.range(16, 3, AxisType.LOOP)
c5 = UOp.range(256, 4, AxisType.LOOP) c5 = UOp.range(256, 4, AxisType.LOOP)
c8 = UOp.range(8, 2, AxisType.LOOP) c8 = UOp.range(8, 2, AxisType.LOOP)
@@ -48,13 +48,13 @@ def vision_conv_153():
c26 = UOp.range(7, 1, AxisType.REDUCE) c26 = UOp.range(7, 1, AxisType.REDUCE)
c27 = c2*2+c26 c27 = c2*2+c26
c32 = ((c27<3)!=True)&(c27<35) c32 = ((c27<3)!=True)&(c27<35)
c34 = UOp.param(1, dtypes.imageh((16, 1024, 4))) c34 = UOp(Ops.PARAM, dtypes.imageh((16, 1024, 4)), (), 1)
c38 = c5//2 c38 = c5//2
c45 = (c32&c24).where((c27*128+c38+c17*4096+-12672), UOp.const(dtypes.weakint, Invalid)) c45 = (c32&c24).where((c27*128+c38+c17*4096+-12672), UOp.const(dtypes.weakint, Invalid))
c48 = (c24&c32).where(c34.index(c45), UOp.const(dtypes.float, 0.0)) c48 = (c24&c32).where(c34.index(c45), UOp.const(dtypes.float, 0.0))
c49 = UOp.param(2, dtypes.imageh((128, 49, 4))) c49 = UOp(Ops.PARAM, dtypes.imageh((128, 49, 4)), (), 2)
c61 = c48*c49.index((c26*4+c5%2+c16*28+c38*196)) c61 = c48*c49.index((c26*4+c5%2+c16*28+c38*196))
c63 = UOp.param(3, dtypes.float.ptr(256)) c63 = UOp(Ops.PARAM, dtypes.float.ptr(256), (), 3)
c65 = c61.reduce(c16, c26, arg=Ops.ADD)+c63.index(c5) c65 = c61.reduce(c16, c26, arg=Ops.ADD)+c63.index(c5)
c67 = c0.index((c2*256+c5+c8*4096), ptr=True).store(c65).end(c8, c2, c5) c67 = c0.index((c2*256+c5+c8*4096), ptr=True).store(c65).end(c8, c2, c5)
@@ -64,16 +64,16 @@ def vision_conv_153():
return c67.sink(arg=KernelInfo(name="conv", opts_to_apply=opts)) return c67.sink(arg=KernelInfo(name="conv", opts_to_apply=opts))
def dm_conv_172(): def dm_conv_172():
c0 = UOp.param(0, dtypes.imageh((1, 240, 4))) c0 = UOp(Ops.PARAM, dtypes.imageh((1, 240, 4)), (), 0)
c2 = UOp.range(960, 4, AxisType.LOOP) c2 = UOp.range(960, 4, AxisType.LOOP)
c5 = UOp.param(1, dtypes.imageh((8, 384, 4))) c5 = UOp(Ops.PARAM, dtypes.imageh((8, 384, 4)), (), 1)
c7 = UOp.range(32, 0, AxisType.REDUCE) c7 = UOp.range(32, 0, AxisType.REDUCE)
c10 = UOp.range(4, 1, AxisType.REDUCE) c10 = UOp.range(4, 1, AxisType.REDUCE)
c13 = UOp.range(12, 3, AxisType.REDUCE) c13 = UOp.range(12, 3, AxisType.REDUCE)
c18 = UOp.range(8, 2, AxisType.REDUCE) c18 = UOp.range(8, 2, AxisType.REDUCE)
c23 = UOp.param(2, dtypes.imageh((240, 128, 4))) c23 = UOp(Ops.PARAM, dtypes.imageh((240, 128, 4)), (), 2)
c35 = c5.index((c7*4+c10+c13*128+c18*1536))*c23.index((c10*4+c2%4+c7*16+c2//4*512)) c35 = c5.index((c7*4+c10+c13*128+c18*1536))*c23.index((c10*4+c2%4+c7*16+c2//4*512))
c37 = UOp.param(3, dtypes.float.ptr(960)) c37 = UOp(Ops.PARAM, dtypes.float.ptr(960), (), 3)
c39 = c35.reduce(c7, c10, arg=Ops.ADD)+c37.index(c2) c39 = c35.reduce(c7, c10, arg=Ops.ADD)+c37.index(c2)
c50 = (1.0+((c39+0.044708251953125*(c39*(c39*c39)))*-2.3021129851685216).exp2()).reciprocal()*c39 c50 = (1.0+((c39+0.044708251953125*(c39*(c39*c39)))*-2.3021129851685216).exp2()).reciprocal()*c39
c53 = c50.reduce(c18, c13, arg=Ops.ADD)*0.010416666666666666 c53 = c50.reduce(c18, c13, arg=Ops.ADD)*0.010416666666666666
@@ -99,4 +99,4 @@ bufs = [Buffer(ps.arg.device, g.size, g.dtype if isinstance(g.dtype, ImageDType)
gsize, lsize = ps.arg.launch_dims({}) gsize, lsize = ps.arg.launch_dims({})
t = rt(*[b._buf for b in bufs], global_size=gsize, local_size=lsize, vals=ps.arg.vals({}), wait=True) t = rt(*[b._buf for b in bufs], global_size=gsize, local_size=lsize, vals=ps.arg.vals({}), wait=True)
print(f"{t*1e6:.2f} us") print(f"{t*1e6:.2f} us")
+1 -1
View File
@@ -4,7 +4,7 @@ import os, multiprocessing, logging, pickle, sqlite3, difflib, warnings, functoo
from dataclasses import replace from dataclasses import replace
from typing import Callable, Any from typing import Callable, Any
ASSERT_DIFF = int((flag:="[PR]") in os.getenv("COMMIT_MESSAGE", flag) or flag in os.getenv("PR_TITLE", flag)) ASSERT_DIFF = int((flag:="[pr]") in os.getenv("COMMIT_MESSAGE", flag) or flag in os.getenv("PR_TITLE", flag))
if not int(os.getenv("ASSERT_PROCESS_REPLAY", "1")): ASSERT_DIFF = 0 if not int(os.getenv("ASSERT_PROCESS_REPLAY", "1")): ASSERT_DIFF = 0
try: try:
+1 -1
View File
@@ -82,7 +82,7 @@ def eval_uop(uop:UOp, inputs:list[tuple[DType, list[Any]]]|None=None, vals:tuple
for buf_dt, data in inputs or []: for buf_dt, data in inputs or []:
bufs.append(buf:=allocator.alloc(len(data) * buf_dt.itemsize)) bufs.append(buf:=allocator.alloc(len(data) * buf_dt.itemsize))
allocator._copyin(buf, memoryview(struct.pack(str(len(data)) + (buf_dt.fmt or ""), *data))) allocator._copyin(buf, memoryview(struct.pack(str(len(data)) + (buf_dt.fmt or ""), *data)))
g = UOp.param(0, uop.dtype.ptr()) g = UOp(Ops.PARAM, uop.dtype.ptr(), arg=0, src=())
prg = to_program(UOp.store(g.index(UOp.const(dtypes.int, 0)), uop).sink(arg=KernelInfo()), PythonRenderer(Target("PYTHON"))) prg = to_program(UOp.store(g.index(UOp.const(dtypes.int, 0)), uop).sink(arg=KernelInfo()), PythonRenderer(Target("PYTHON")))
prog = PythonProgram("run", PythonCompiler().compile(prg.src[3].arg)) prog = PythonProgram("run", PythonCompiler().compile(prg.src[3].arg))
prog(out_buf:=allocator.alloc(uop.dtype.itemsize), *bufs, vals=vals) prog(out_buf:=allocator.alloc(uop.dtype.itemsize), *bufs, vals=vals)
+6 -6
View File
@@ -423,10 +423,10 @@ def _collect_data_slices(assigns: list[tuple[str, UOp]], data_prefix: str, pcode
class _Ctx: class _Ctx:
"""Context for instruction compilation - holds buffers and helpers.""" """Context for instruction compilation - holds buffers and helpers."""
__slots__ = ('inst_size', 'dyn_fields', '_axis_id', 'wave_size', 'vgpr', 'accvgpr') __slots__ = ('inst_size', 'dyn_fields', '_axis_id', 'wave_size', 'vgpr', 'accvgpr')
sgpr = UOp.param(0, dtypes.uint32.ptr(SGPR_COUNT)) sgpr = UOp(Ops.PARAM, dtypes.uint32.ptr(SGPR_COUNT), arg=0)
vmem = UOp.param(2, dtypes.uint32.ptr(1 << 46)) vmem = UOp(Ops.PARAM, dtypes.uint32.ptr(1 << 46), arg=2)
lds = UOp.param(3, dtypes.uint32.ptr(16384)) lds = UOp(Ops.PARAM, dtypes.uint32.ptr(16384), arg=3)
scratch = UOp.param(4, dtypes.uint8.ptr(1 << 30)) scratch = UOp(Ops.PARAM, dtypes.uint8.ptr(1 << 30), arg=4)
# Cache PARAM UOps by wave_size so all _Ctx instances with same wave_size share identical UOp references # Cache PARAM UOps by wave_size so all _Ctx instances with same wave_size share identical UOp references
_vgpr_cache: dict[int, UOp] = {} _vgpr_cache: dict[int, UOp] = {}
_accvgpr_cache: dict[int, UOp] = {} _accvgpr_cache: dict[int, UOp] = {}
@@ -434,10 +434,10 @@ class _Ctx:
def __init__(self, inst_size: int, wave_size: int = 32): def __init__(self, inst_size: int, wave_size: int = 32):
self.inst_size, self._axis_id, self.wave_size = inst_size, 0, wave_size self.inst_size, self._axis_id, self.wave_size = inst_size, 0, wave_size
self.dyn_fields: list[tuple[int, int]] = [] # (lo, hi) of fields read dynamically self.dyn_fields: list[tuple[int, int]] = [] # (lo, hi) of fields read dynamically
if wave_size not in _Ctx._vgpr_cache: _Ctx._vgpr_cache[wave_size] = UOp.param(1, dtypes.uint32.ptr(256 * wave_size)) if wave_size not in _Ctx._vgpr_cache: _Ctx._vgpr_cache[wave_size] = UOp(Ops.PARAM, dtypes.uint32.ptr(256 * wave_size), arg=1)
self.vgpr = _Ctx._vgpr_cache[wave_size] self.vgpr = _Ctx._vgpr_cache[wave_size]
if wave_size == 64: if wave_size == 64:
if wave_size not in _Ctx._accvgpr_cache: _Ctx._accvgpr_cache[wave_size] = UOp.param(5, dtypes.uint32.ptr(256 * wave_size)) if wave_size not in _Ctx._accvgpr_cache: _Ctx._accvgpr_cache[wave_size] = UOp(Ops.PARAM, dtypes.uint32.ptr(256 * wave_size), arg=5)
self.accvgpr = _Ctx._accvgpr_cache[wave_size] self.accvgpr = _Ctx._accvgpr_cache[wave_size]
else: else:
self.accvgpr = self.vgpr self.accvgpr = self.vgpr
+12 -12
View File
@@ -28,8 +28,8 @@ class TestDevice(unittest.TestCase):
def test_nonexistent_renderer(self): def test_nonexistent_renderer(self):
with self.assertRaisesRegex(RuntimeError, "has no renderer"): with self.assertRaisesRegex(RuntimeError, "has no renderer"):
with Context(DEV="CPU:TYPO"): Device[Device.DEFAULT].renderer with Context(DEV="CPU:TYPO"): Device[Device.DEFAULT].renderer
with self.assertRaisesRegex(RuntimeError, "did you mean: 'CLANG'"): with self.assertRaisesRegex(RuntimeError, "did you mean: 'CLANGJIT'"):
with Context(DEV="CPU:CLANGJIT"): Device[Device.DEFAULT].renderer with Context(DEV="CPU:CLANG"): Device[Device.DEFAULT].renderer
@unittest.skipIf(Device.DEFAULT != "AMD", "only run on AMD") @unittest.skipIf(Device.DEFAULT != "AMD", "only run on AMD")
def test_nonexistent_iface(self): def test_nonexistent_iface(self):
@@ -69,17 +69,17 @@ class TestDevice(unittest.TestCase):
@unittest.skipIf(WIN, "skipping windows test") # TODO: subprocess causes memory violation? @unittest.skipIf(WIN, "skipping windows test") # TODO: subprocess causes memory violation?
def test_env_overwrite_default_compiler(self): def test_env_overwrite_default_compiler(self):
if Device.DEFAULT == "CPU": if Device.DEFAULT == "CPU":
from tinygrad.runtime.support.compiler_cpu import CPULLVMCompiler, ClangCompiler from tinygrad.runtime.support.compiler_cpu import CPULLVMCompiler, ClangJITCompiler
try: _, _ = CPULLVMCompiler(), ClangCompiler() try: _, _ = CPULLVMCompiler(), ClangJITCompiler()
except Exception as e: self.skipTest(f"skipping compiler test: not all compilers: {e}") except Exception as e: self.skipTest(f"skipping compiler test: not all compilers: {e}")
imports = "from tinygrad import Device; from tinygrad.runtime.support.compiler_cpu import CPULLVMCompiler, ClangCompiler" imports = "from tinygrad import Device; from tinygrad.runtime.support.compiler_cpu import CPULLVMCompiler, ClangJITCompiler"
subprocess.run([f'python3 -c "{imports}; assert isinstance(Device[Device.DEFAULT].compiler, CPULLVMCompiler)"'], subprocess.run([f'python3 -c "{imports}; assert isinstance(Device[Device.DEFAULT].compiler, CPULLVMCompiler)"'],
shell=True, check=True, env={**os.environ, "DEV": "CPU:LLVM"}) shell=True, check=True, env={**os.environ, "DEV": "CPU:LLVM"})
subprocess.run([f'python3 -c "{imports}; assert isinstance(Device[Device.DEFAULT].compiler, ClangCompiler)"'], subprocess.run([f'python3 -c "{imports}; assert isinstance(Device[Device.DEFAULT].compiler, ClangJITCompiler)"'],
shell=True, check=True, env={**os.environ, "DEV": "CPU"}) shell=True, check=True, env={**os.environ, "DEV": "CPU"})
subprocess.run([f'python3 -c "{imports}; assert isinstance(Device[Device.DEFAULT].compiler, ClangCompiler)"'], subprocess.run([f'python3 -c "{imports}; assert isinstance(Device[Device.DEFAULT].compiler, ClangJITCompiler)"'],
shell=True, check=True, env={**os.environ, "DEV": "CPU:CLANG"}) shell=True, check=True, env={**os.environ, "DEV": "CPU:CLANGJIT"})
elif Device.DEFAULT == "AMD": elif Device.DEFAULT == "AMD":
from tinygrad.runtime.support.compiler_amd import HIPCompiler, AMDLLVMCompiler from tinygrad.runtime.support.compiler_amd import HIPCompiler, AMDLLVMCompiler
try: _, _ = HIPCompiler(Device[Device.DEFAULT].arch), AMDLLVMCompiler(Device[Device.DEFAULT].arch) try: _, _ = HIPCompiler(Device[Device.DEFAULT].arch), AMDLLVMCompiler(Device[Device.DEFAULT].arch)
@@ -96,15 +96,15 @@ class TestDevice(unittest.TestCase):
@unittest.skipIf(WIN, "skipping windows test") @unittest.skipIf(WIN, "skipping windows test")
def test_env_online(self): def test_env_online(self):
from tinygrad.runtime.support.compiler_cpu import CPULLVMCompiler, ClangCompiler from tinygrad.runtime.support.compiler_cpu import CPULLVMCompiler, ClangJITCompiler
try: _, _ = CPULLVMCompiler(), ClangCompiler() try: _, _ = CPULLVMCompiler(), ClangJITCompiler()
except Exception as e: self.skipTest(f"skipping compiler test: not all compilers: {e}") except Exception as e: self.skipTest(f"skipping compiler test: not all compilers: {e}")
with Context(DEV="CPU:LLVM"): with Context(DEV="CPU:LLVM"):
inst = Device["CPU"].compiler inst = Device["CPU"].compiler
self.assertIsInstance(Device["CPU"].compiler, CPULLVMCompiler) self.assertIsInstance(Device["CPU"].compiler, CPULLVMCompiler)
with Context(DEV="CPU"): with Context(DEV="CPU"):
self.assertIsInstance(Device["CPU"].compiler, ClangCompiler) self.assertIsInstance(Device["CPU"].compiler, ClangJITCompiler)
with Context(DEV="CPU:LLVM"): with Context(DEV="CPU:LLVM"):
self.assertIsInstance(Device["CPU"].compiler, CPULLVMCompiler) self.assertIsInstance(Device["CPU"].compiler, CPULLVMCompiler)
assert inst is Device["CPU"].compiler # cached assert inst is Device["CPU"].compiler # cached
@@ -118,7 +118,7 @@ class TestDevice(unittest.TestCase):
dev = Device["CPU"] dev = Device["CPU"]
dev.cached_renderer.clear() dev.cached_renderer.clear()
with patch("tinygrad.renderer.cstyle.ClangRenderer.__init__", side_effect=RuntimeError("broken")): with patch("tinygrad.renderer.cstyle.ClangJITRenderer.__init__", side_effect=RuntimeError("broken")):
self.assertIsInstance(dev.renderer.compiler, CPULLVMCompiler) self.assertIsInstance(dev.renderer.compiler, CPULLVMCompiler)
def test_dev_contextvar(self): def test_dev_contextvar(self):
+2 -2
View File
@@ -1,5 +1,5 @@
import unittest, subprocess, platform import unittest, subprocess, platform
from tinygrad.runtime.support.compiler_cpu import ClangCompiler from tinygrad.runtime.support.compiler_cpu import ClangJITCompiler
from tinygrad.runtime.support.elf import elf_loader from tinygrad.runtime.support.elf import elf_loader
class TestElfLoader(unittest.TestCase): class TestElfLoader(unittest.TestCase):
@@ -23,7 +23,7 @@ class TestElfLoader(unittest.TestCase):
} }
''' '''
with self.assertRaisesRegex(RuntimeError, 'evil_external_function'): with self.assertRaisesRegex(RuntimeError, 'evil_external_function'):
ClangCompiler([{'AMD64':'x86_64', 'aarch64':'arm64'}.get(m:=platform.machine(), m), "native"]).compile(src) ClangJITCompiler([{'AMD64':'x86_64', 'aarch64':'arm64'}.get(m:=platform.machine(), m), "native"]).compile(src)
def test_link(self): def test_link(self):
src = ''' src = '''
float powf(float, float); // from libm float powf(float, float); // from libm
+1 -1
View File
@@ -96,7 +96,7 @@ class TestGroupedDims(unittest.TestCase):
def test_global_prod_max(self): def test_global_prod_max(self):
g, l = UOp.range(256, 0, AxisType.GLOBAL), UOp.range(256, 1, AxisType.LOCAL) g, l = UOp.range(256, 0, AxisType.GLOBAL), UOp.range(256, 1, AxisType.LOCAL)
sink = UOp.param(0, dtypes.float.ptr()).index(g + l).store(UOp.const(dtypes.float, 1.0)).end(g, l).sink(arg=KernelInfo()) sink = UOp(Ops.PARAM, dtypes.float.ptr(), (), 0).index(g + l).store(UOp.const(dtypes.float, 1.0)).end(g, l).sink(arg=KernelInfo())
class R(Renderer): global_max, local_max, global_prod_max = (256, 256, 256), (128, 128, 128), (128, 128, 128) class R(Renderer): global_max, local_max, global_prod_max = (256, 256, 256), (128, 128, 128), (128, 128, 128)
specials = [u for u in add_gpudims(R(Target()), sink).toposort() if u.op is Ops.SPECIAL] specials = [u for u in add_gpudims(R(Target()), sink).toposort() if u.op is Ops.SPECIAL]
self.assertGreater(len([s for s in specials if "lidx" in s.arg]), 1) self.assertGreater(len([s for s in specials if "lidx" in s.arg]), 1)
+3 -3
View File
@@ -7,14 +7,14 @@ from tinygrad.codegen import to_program
class TestLinearizerFailures(unittest.TestCase): class TestLinearizerFailures(unittest.TestCase):
def test_fail_1(self): def test_fail_1(self):
c0 = UOp.param(0, dtypes.float.ptr(64)) c0 = UOp(Ops.PARAM, dtypes.float.ptr(64), arg=0, src=())
c1 = UOp.range(UOp.const(dtypes.weakint, 2), 1, AxisType.LOOP) c1 = UOp.range(UOp.const(dtypes.weakint, 2), 1, AxisType.LOOP)
c2 = UOp.range(UOp.const(dtypes.weakint, 32), 2, AxisType.LOOP) c2 = UOp.range(UOp.const(dtypes.weakint, 32), 2, AxisType.LOOP)
c3 = ((c1*UOp.const(dtypes.weakint, 32))+c2) c3 = ((c1*UOp.const(dtypes.weakint, 32))+c2)
c4 = UOp.param(1, dtypes.float.ptr(163840)) c4 = UOp(Ops.PARAM, dtypes.float.ptr(163840), arg=1, src=())
c5 = UOp.range(UOp.const(dtypes.weakint, 2560), 0, AxisType.REDUCE) c5 = UOp.range(UOp.const(dtypes.weakint, 2560), 0, AxisType.REDUCE)
c6 = c4.index(((((((c5//UOp.const(dtypes.weakint, 8))%UOp.const(dtypes.weakint, 8))*UOp.const(dtypes.weakint, 8))+(c5%UOp.const(dtypes.weakint, 8)))+(((c2*UOp.const(dtypes.weakint, 40))+(c5//UOp.const(dtypes.weakint, 64)))*UOp.const(dtypes.weakint, 64)))+(c1*UOp.const(dtypes.weakint, 81920)))) c6 = c4.index(((((((c5//UOp.const(dtypes.weakint, 8))%UOp.const(dtypes.weakint, 8))*UOp.const(dtypes.weakint, 8))+(c5%UOp.const(dtypes.weakint, 8)))+(((c2*UOp.const(dtypes.weakint, 40))+(c5//UOp.const(dtypes.weakint, 64)))*UOp.const(dtypes.weakint, 64)))+(c1*UOp.const(dtypes.weakint, 81920))))
c7 = UOp.param(2, dtypes.float.ptr(64)) c7 = UOp(Ops.PARAM, dtypes.float.ptr(64), arg=2, src=())
c8 = c7.index(c3) c8 = c7.index(c3)
c9 = ((((c6+(c8*UOp.const(dtypes.float, -1.0)))*(c6+(c8*UOp.const(dtypes.float, -1.0)))).reduce(c5, arg=Ops.ADD)*UOp.const(dtypes.float, 0.000390625))+UOp.const(dtypes.float, 1e-05)).sqrt().reciprocal() c9 = ((((c6+(c8*UOp.const(dtypes.float, -1.0)))*(c6+(c8*UOp.const(dtypes.float, -1.0)))).reduce(c5, arg=Ops.ADD)*UOp.const(dtypes.float, 0.000390625))+UOp.const(dtypes.float, 1e-05)).sqrt().reciprocal()
c10 = c0.index(c3).store(c9).end(c1, c2) c10 = c0.index(c3).store(c9).end(c1, c2)
+3 -3
View File
@@ -456,14 +456,14 @@ class TestSchedule(unittest.TestCase):
check_schedule(out, 4, [c1.weight, c1.bias, *nn.state.get_parameters(bn)]) check_schedule(out, 4, [c1.weight, c1.bias, *nn.state.get_parameters(bn)])
def test_fold_conv_batchnorm_optim(self, adam=False): def test_fold_conv_batchnorm_optim(self, adam=False):
optim, cnt = (nn.optim.Adam, 29) if adam else (nn.optim.SGD, 15) # 2 is too low?
optim, cnt = (nn.optim.Adam, 16) if adam else (nn.optim.SGD, 2)
with Tensor.train(): with Tensor.train():
img = Tensor.ones(1,3,4,4) img = Tensor.ones(1,3,4,4)
c1 = nn.Conv2d(3,32,3) c1 = nn.Conv2d(3,32,3)
bn = nn.BatchNorm2d(32, track_running_stats=False) bn = nn.BatchNorm2d(32, track_running_stats=False)
_realize_weights([c1, bn]) _realize_weights([c1, bn])
opt = optim(nn.state.get_parameters([c1, bn])) opt = optim(nn.state.get_parameters([c1, bn]))
Tensor.realize(img, *nn.state.get_parameters(opt))
img_bn = bn(c1(img)).elu().sum() img_bn = bn(c1(img)).elu().sum()
opt.zero_grad() opt.zero_grad()
img_bn.backward() img_bn.backward()
@@ -477,7 +477,7 @@ class TestSchedule(unittest.TestCase):
fw = bn(x).contiguous_backward().relu().contiguous() fw = bn(x).contiguous_backward().relu().contiguous()
fw.sum().backward() fw.sum().backward()
# TODO: this is too many # TODO: this is too many
check_schedule([x.grad, bn.weight.grad, bn.bias.grad, fw], 10, nn.state.get_parameters(bn)) check_schedule([x.grad, bn.weight.grad, bn.bias.grad, fw], 9)
def test_fold_conv_relu(self): def test_fold_conv_relu(self):
c1 = nn.Conv2d(3,16,3) c1 = nn.Conv2d(3,16,3)
+6 -6
View File
@@ -15,13 +15,13 @@ def simplify_image_idx(sink: UOp) -> UOp: return graph_rewrite(sink, sym+pm_move
def get_gated_load_uop(valid:UOp, idx:UOp): def get_gated_load_uop(valid:UOp, idx:UOp):
return UOp(Ops.LOAD, dtypes.float, ( return UOp(Ops.LOAD, dtypes.float, (
UOp.param(0, dtypes.float.ptr()).index(idx.valid(valid), ptr=True), UOp(Ops.PARAM, dtypes.float.ptr(), arg=0).index(idx.valid(valid), ptr=True),
UOp.const(dtypes.float, 0.0) UOp.const(dtypes.float, 0.0)
)) ))
def get_load_image_uop(image_shape:tuple[int, ...], valid:UOp, idx:tuple[UOp, UOp]): def get_load_image_uop(image_shape:tuple[int, ...], valid:UOp, idx:tuple[UOp, UOp]):
return UOp(Ops.LOAD, dtypes.float.vec(4), ( return UOp(Ops.LOAD, dtypes.float.vec(4), (
UOp.param(0, dtypes.imagef(image_shape)).index(idx[1].valid(valid), idx[0].valid(valid), ptr=True), UOp(Ops.PARAM, dtypes.imagef(image_shape), arg=0).index(idx[1].valid(valid), idx[0].valid(valid), ptr=True),
UOp(Ops.STACK, dtypes.float.vec(4), src=(UOp.const(dtypes.float, 0.0),) * 4) UOp(Ops.STACK, dtypes.float.vec(4), src=(UOp.const(dtypes.float, 0.0),) * 4)
)) ))
@@ -513,7 +513,7 @@ class TestDropTrueGate(unittest.TestCase):
from tinygrad.codegen.late.devectorizer import load_store_indexing from tinygrad.codegen.late.devectorizer import load_store_indexing
from tinygrad.uop.ops import graph_rewrite from tinygrad.uop.ops import graph_rewrite
from tinygrad.uop.symbolic import sym from tinygrad.uop.symbolic import sym
buf = UOp.param(0, dtypes.int.ptr()) buf = UOp(Ops.PARAM, dtypes.int.ptr(), arg=0)
idx = UOp.const(dtypes.weakint, 0) idx = UOp.const(dtypes.weakint, 0)
true_gate = UOp.const(dtypes.bool, True) true_gate = UOp.const(dtypes.bool, True)
index_with_gate = UOp(Ops.INDEX, dtypes.int.ptr(), (buf, idx.valid(true_gate))) index_with_gate = UOp(Ops.INDEX, dtypes.int.ptr(), (buf, idx.valid(true_gate)))
@@ -557,7 +557,7 @@ class TestRangeShrink(unittest.TestCase):
# one load guards r < 4, but another load uses r without a gate -> no shrink # one load guards r < 4, but another load uses r without a gate -> no shrink
r = Range(0, 204) r = Range(0, 204)
load1 = get_gated_load_uop(r < UOp.const(dtypes.weakint, 4), r) load1 = get_gated_load_uop(r < UOp.const(dtypes.weakint, 4), r)
load2 = UOp(Ops.LOAD, dtypes.float, (UOp.param(1, dtypes.float.ptr()).index(r, ptr=True),)) load2 = UOp(Ops.LOAD, dtypes.float, (UOp(Ops.PARAM, dtypes.float.ptr(), arg=1).index(r, ptr=True),))
ranges = self.get_ranges(UOp.sink(load1, load2)) ranges = self.get_ranges(UOp.sink(load1, load2))
self.assertEqual(len(ranges), 1) self.assertEqual(len(ranges), 1)
self.assertEqual(ranges[0].src[0].arg, 204) self.assertEqual(ranges[0].src[0].arg, 204)
@@ -583,7 +583,7 @@ class TestRangeShrink(unittest.TestCase):
from tinygrad.dtype import Invalid from tinygrad.dtype import Invalid
r = Range(0, 204) r = Range(0, 204)
x = (r < 4).where(UOp.const(dtypes.float, 1), Invalid) x = (r < 4).where(UOp.const(dtypes.float, 1), Invalid)
ranges = self.get_ranges(UOp.param(0, dtypes.float.ptr()).index(r).store((r < 4).where(x, 0)).sink()) ranges = self.get_ranges(UOp(Ops.PARAM, dtypes.float.ptr(), arg=0).index(r).store((r < 4).where(x, 0)).sink())
self.assertEqual(len(ranges), 1) self.assertEqual(len(ranges), 1)
self.assertEqual(ranges[0].src[0].arg, 4) self.assertEqual(ranges[0].src[0].arg, 4)
@@ -592,7 +592,7 @@ class TestRangeShrink(unittest.TestCase):
from tinygrad.dtype import Invalid from tinygrad.dtype import Invalid
r = Range(0, 204) r = Range(0, 204)
x = (r < 4).where(UOp.const(dtypes.float, 1), Invalid) x = (r < 4).where(UOp.const(dtypes.float, 1), Invalid)
ranges = self.get_ranges(UOp.param(0, dtypes.float.ptr()).index(r).store((r < 4).where(0, x)).sink()) ranges = self.get_ranges(UOp(Ops.PARAM, dtypes.float.ptr(), arg=0).index(r).store((r < 4).where(0, x)).sink())
self.assertEqual(len(ranges), 1) self.assertEqual(len(ranges), 1)
self.assertEqual(ranges[0].src[0].arg, 4) self.assertEqual(ranges[0].src[0].arg, 4)
+2 -2
View File
@@ -1,7 +1,7 @@
import unittest, math import unittest, math
import numpy as np import numpy as np
from tinygrad import dtypes from tinygrad import dtypes
from tinygrad.uop.ops import UOp from tinygrad.uop.ops import UOp, Ops
from tinygrad.uop.decompositions import TRANSCENDENTAL_DTYPES, payne_hanek_reduction, cody_waite_reduction from tinygrad.uop.decompositions import TRANSCENDENTAL_DTYPES, payne_hanek_reduction, cody_waite_reduction
from tinygrad.uop.decompositions import frexp, rintk, xpow, xexp2, xlog2, trig_poly, pow2if from tinygrad.uop.decompositions import frexp, rintk, xpow, xexp2, xlog2, trig_poly, pow2if
from test.helpers import eval_uop from test.helpers import eval_uop
@@ -10,7 +10,7 @@ class TestTranscendentalFunctions(unittest.TestCase):
def test_payne_hanek_reduction(self): def test_payne_hanek_reduction(self):
# TODO: Test constant input when constant folding is fixed (or maybe test both variants) # TODO: Test constant input when constant folding is fixed (or maybe test both variants)
# Load input value from a buffer to prevent constant folding # Load input value from a buffer to prevent constant folding
input_buf = UOp.param(1, dtypes.double.ptr()) input_buf = UOp(Ops.PARAM, dtypes.double.ptr(), arg=1, src=())
loaded_value = input_buf.index(UOp.const(dtypes.int, 0)) loaded_value = input_buf.index(UOp.const(dtypes.int, 0))
def eval_payne_hanek_reduction(v:float) -> tuple[float, int]: def eval_payne_hanek_reduction(v:float) -> tuple[float, int]:
return tuple(eval_uop(u, [(dtypes.float64, [v])]) for u in payne_hanek_reduction(loaded_value)) return tuple(eval_uop(u, [(dtypes.float64, [v])]) for u in payne_hanek_reduction(loaded_value))
+35 -35
View File
@@ -260,7 +260,7 @@ class TestUOpGraph(unittest.TestCase):
@unittest.skip("this test isn't valid uops") @unittest.skip("this test isn't valid uops")
def test_noop_vectorize_fold(self): def test_noop_vectorize_fold(self):
d0 = UOp.param(0, dtypes.float.ptr()) d0 = UOp(Ops.PARAM, dtypes.float.ptr(), arg=0)
idx = UOp.const(dtypes.int, 0) idx = UOp.const(dtypes.int, 0)
ld = UOp(Ops.LOAD, dtypes.float.vec(2), (d0, idx)) ld = UOp(Ops.LOAD, dtypes.float.vec(2), (d0, idx))
vec = UOp(Ops.STACK, dtypes.float.vec(2), (ld,)) vec = UOp(Ops.STACK, dtypes.float.vec(2), (ld,))
@@ -272,9 +272,9 @@ class TestUOpGraph(unittest.TestCase):
@unittest.skip("this test isn't valid uops") @unittest.skip("this test isn't valid uops")
def test_gep_vec_fold(self): def test_gep_vec_fold(self):
d0 = UOp.param(0, dtypes.float.ptr()) d0 = UOp(Ops.PARAM, dtypes.float.ptr(), (), 0)
d1 = UOp.param(1, dtypes.float.ptr()) d1 = UOp(Ops.PARAM, dtypes.float.ptr(), (), 1)
d2 = UOp.param(2, dtypes.float.ptr()) d2 = UOp(Ops.PARAM, dtypes.float.ptr(), (), 2)
idx = UOp.const(dtypes.int, 0) idx = UOp.const(dtypes.int, 0)
def _test_vec(geps, count=4): def _test_vec(geps, count=4):
vec = UOp(Ops.STACK, dtypes.float.vec(count), geps) vec = UOp(Ops.STACK, dtypes.float.vec(count), geps)
@@ -380,8 +380,8 @@ class TestUOpGraph(unittest.TestCase):
self.assertEqual(uops[-2], wmma) # -2 to skip SINK self.assertEqual(uops[-2], wmma) # -2 to skip SINK
def test_cast_alu_fold(self): def test_cast_alu_fold(self):
d0 = UOp.param(0, dtypes.bool.ptr()) d0 = UOp(Ops.PARAM, dtypes.bool.ptr(), arg=0)
d1 = UOp.param(1, dtypes.int.ptr()) d1 = UOp(Ops.PARAM, dtypes.int.ptr(), arg=1)
idx = UOp.const(dtypes.int, 0) idx = UOp.const(dtypes.int, 0)
ld = d1.index(idx) ld = d1.index(idx)
alu = (ld<1).cast(dtypes.bool) alu = (ld<1).cast(dtypes.bool)
@@ -390,8 +390,8 @@ class TestUOpGraph(unittest.TestCase):
self.assertEqual(len([x for x in uops if x.op is Ops.CAST]), 0) self.assertEqual(len([x for x in uops if x.op is Ops.CAST]), 0)
def test_double_cast_fold(self): def test_double_cast_fold(self):
d0 = UOp.param(0, dtypes.float.ptr()) d0 = UOp(Ops.PARAM, dtypes.float.ptr(), arg=0)
d1 = UOp.param(1, dtypes.int.ptr()) d1 = UOp(Ops.PARAM, dtypes.int.ptr(), arg=1)
idx = UOp.const(dtypes.int, 0) idx = UOp.const(dtypes.int, 0)
ld = d1.index(idx) ld = d1.index(idx)
alu = ld.cast(dtypes.float).cast(dtypes.float) alu = ld.cast(dtypes.float).cast(dtypes.float)
@@ -414,7 +414,7 @@ class TestUOpGraph(unittest.TestCase):
def test_bitcast_to_same_dtype_fold(self): def test_bitcast_to_same_dtype_fold(self):
for dt in dtypes.ints + dtypes.floats + (dtypes.bool,): for dt in dtypes.ints + dtypes.floats + (dtypes.bool,):
d0 = UOp.param(0, dt.ptr()) d0 = UOp(Ops.PARAM, dt.ptr(), arg=0)
v = d0.index(UOp.const(dtypes.int, 0)) v = d0.index(UOp.const(dtypes.int, 0))
uops = to_uops_list([v.bitcast(dt)]) uops = to_uops_list([v.bitcast(dt)])
self.assertEqual(len([x for x in uops if x.op is Ops.BITCAST and x.dtype is dt]), 0, f"dtype = {dt}") self.assertEqual(len([x for x in uops if x.op is Ops.BITCAST and x.dtype is dt]), 0, f"dtype = {dt}")
@@ -427,10 +427,10 @@ class TestUOpGraph(unittest.TestCase):
def test_where_on_gated_load_fold(self): def test_where_on_gated_load_fold(self):
ridx0 = UOp.range(100, 0) ridx0 = UOp.range(100, 0)
d0 = UOp.param(0, dtypes.long.ptr()) d0 = UOp(Ops.PARAM, dtypes.long.ptr(), (), 0)
ld = d0.index(ridx0.valid(ridx0<50)) ld = d0.index(ridx0.valid(ridx0<50))
w = (ridx0<50).where(ld, 5) w = (ridx0<50).where(ld, 5)
out = UOp.param(1, dtypes.long.ptr()) out = UOp(Ops.PARAM, dtypes.long.ptr(), (), 1)
uops = to_uops_list([out.index(ridx0).store(w)]) uops = to_uops_list([out.index(ridx0).store(w)])
for u in uops: for u in uops:
assert u.op is not Ops.WHERE assert u.op is not Ops.WHERE
@@ -438,7 +438,7 @@ class TestUOpGraph(unittest.TestCase):
def test_where_on_gated_load_folds_swapped_branches(self): def test_where_on_gated_load_folds_swapped_branches(self):
ridx0 = UOp.range(100, 0) ridx0 = UOp.range(100, 0)
d0 = UOp.param(0, dtypes.long.ptr()) d0 = UOp(Ops.PARAM, dtypes.long.ptr(), (), 0)
ld = d0.index(ridx0.valid((ridx0<50).logical_not())) ld = d0.index(ridx0.valid((ridx0<50).logical_not()))
w = (ridx0<50).where(5, ld) w = (ridx0<50).where(5, ld)
uops = to_uops_list([w]) uops = to_uops_list([w])
@@ -448,11 +448,11 @@ class TestUOpGraph(unittest.TestCase):
def test_where_on_gated_load_with_cast(self): def test_where_on_gated_load_with_cast(self):
ridx0 = UOp.range(100, 0) ridx0 = UOp.range(100, 0)
d0 = UOp.param(0, dtypes.int.ptr()) d0 = UOp(Ops.PARAM, dtypes.int.ptr(), (), 0)
gate_idx = ridx0.valid((ridx0<50)) gate_idx = ridx0.valid((ridx0<50))
ld = d0.index(gate_idx).cast(dtypes.float) ld = d0.index(gate_idx).cast(dtypes.float)
w = (ridx0<50).where(ld, 5.0) w = (ridx0<50).where(ld, 5.0)
out = UOp.param(1, dtypes.float.ptr()) out = UOp(Ops.PARAM, dtypes.float.ptr(), (), 1)
uops = to_uops_list([out.index(ridx0).store(w)]) uops = to_uops_list([out.index(ridx0).store(w)])
for u in uops: for u in uops:
assert u.op is not Ops.WHERE assert u.op is not Ops.WHERE
@@ -460,27 +460,27 @@ class TestUOpGraph(unittest.TestCase):
def test_where_on_casted_gated_load_extra_cond(self): def test_where_on_casted_gated_load_extra_cond(self):
ridx0 = UOp.range(100, 0) ridx0 = UOp.range(100, 0)
d0 = UOp.param(0, dtypes.float.ptr()) d0 = UOp(Ops.PARAM, dtypes.float.ptr(), (), 0)
ld = d0.index(ridx0.valid(ridx0<50)) ld = d0.index(ridx0.valid(ridx0<50))
w = ((ridx0<50) & (ridx0>30)).where(ld, UOp.const(dtypes.float, 0)).cast(dtypes.half) w = ((ridx0<50) & (ridx0>30)).where(ld, UOp.const(dtypes.float, 0)).cast(dtypes.half)
out = UOp.param(1, dtypes.half.ptr()) out = UOp(Ops.PARAM, dtypes.half.ptr(), (), 1)
uops = to_uops_list([out.index(ridx0).store(w)]) uops = to_uops_list([out.index(ridx0).store(w)])
for u in uops: for u in uops:
assert u.op is not Ops.WHERE assert u.op is not Ops.WHERE
def test_where_on_casted_gated_load_extra_cond_swapped(self): def test_where_on_casted_gated_load_extra_cond_swapped(self):
ridx0 = UOp.range(100, 0) ridx0 = UOp.range(100, 0)
d0 = UOp.param(0, dtypes.float.ptr()) d0 = UOp(Ops.PARAM, dtypes.float.ptr(), (), 0)
ld = d0.index(ridx0.valid(ridx0<50)) ld = d0.index(ridx0.valid(ridx0<50))
w = ((ridx0<50) & (ridx0>30)).where(UOp.const(dtypes.float, 0), ld).cast(dtypes.half) w = ((ridx0<50) & (ridx0>30)).where(UOp.const(dtypes.float, 0), ld).cast(dtypes.half)
out = UOp.param(1, dtypes.half.ptr()) out = UOp(Ops.PARAM, dtypes.half.ptr(), (), 1)
uops = to_uops_list([out.index(ridx0).store(w)]) uops = to_uops_list([out.index(ridx0).store(w)])
for u in uops: for u in uops:
assert u.op is not Ops.WHERE assert u.op is not Ops.WHERE
def test_where_in_store_becomes_gate(self): def test_where_in_store_becomes_gate(self):
ridx0 = UOp.range(100, 0) ridx0 = UOp.range(100, 0)
d0 = UOp.param(0, dtypes.long.ptr()) d0 = UOp(Ops.PARAM, dtypes.long.ptr(), (), 0)
idx = d0.index(ridx0) idx = d0.index(ridx0)
ld = idx.load() ld = idx.load()
val = (ridx0<50).where(5, ld) val = (ridx0<50).where(5, ld)
@@ -493,14 +493,14 @@ class TestUOpGraph(unittest.TestCase):
def test_load_idx_becomes_int(self): def test_load_idx_becomes_int(self):
# mnist indexing with split reduceop # mnist indexing with split reduceop
# Make sure we are not doign math on the loaded index, which would promote it to long # Make sure we are not doign math on the loaded index, which would promote it to long
c0 = UOp.param(0, dtypes.uchar.ptr(128000)) c0 = UOp(Ops.PARAM, dtypes.uchar.ptr(128000), arg=0, src=())
c1 = UOp.range(UOp.const(dtypes.weakint, 512), 1, AxisType.LOOP) c1 = UOp.range(UOp.const(dtypes.weakint, 512), 1, AxisType.LOOP)
c2 = UOp.range(UOp.const(dtypes.weakint, 250), 2, AxisType.LOOP) c2 = UOp.range(UOp.const(dtypes.weakint, 250), 2, AxisType.LOOP)
c3 = UOp.param(1, dtypes.int.ptr(512)) c3 = UOp(Ops.PARAM, dtypes.int.ptr(512), arg=1, src=())
c4 = c3.index(c1) c4 = c3.index(c1)
c5 = UOp.range(UOp.const(dtypes.weakint, 240), 0, AxisType.REDUCE) c5 = UOp.range(UOp.const(dtypes.weakint, 240), 0, AxisType.REDUCE)
c6 = ((c2*UOp.const(dtypes.weakint, 240))+c5) c6 = ((c2*UOp.const(dtypes.weakint, 240))+c5)
c7 = UOp.param(2, dtypes.uchar.ptr(60000)) c7 = UOp(Ops.PARAM, dtypes.uchar.ptr(60000), arg=2, src=())
c8 = c7.index(c6) c8 = c7.index(c6)
c9 = ((c4<0).where((c4+60000), c4)!=c6.cast(dtypes.int)).where(0, c8.cast(dtypes.uint).cast(dtypes.uchar)).reduce(c5, arg=Ops.ADD) c9 = ((c4<0).where((c4+60000), c4)!=c6.cast(dtypes.int)).where(0, c8.cast(dtypes.uint).cast(dtypes.uchar)).reduce(c5, arg=Ops.ADD)
c10 = c0.index(((c1*UOp.const(dtypes.weakint, 250))+c2)).store(c9).end(c1, c2) c10 = c0.index(((c1*UOp.const(dtypes.weakint, 250))+c2)).store(c9).end(c1, c2)
@@ -510,14 +510,14 @@ class TestUOpGraph(unittest.TestCase):
def test_load_idx_no_math_on_loaded(self): def test_load_idx_no_math_on_loaded(self):
# test the (x+y)<c pattern where x has loads - we shouldn't do math on loaded indices # test the (x+y)<c pattern where x has loads - we shouldn't do math on loaded indices
c0 = UOp.param(0, dtypes.uchar.ptr(128000)) c0 = UOp(Ops.PARAM, dtypes.uchar.ptr(128000), arg=0, src=())
c1 = UOp.range(UOp.const(dtypes.weakint, 512), 1, AxisType.LOOP) c1 = UOp.range(UOp.const(dtypes.weakint, 512), 1, AxisType.LOOP)
c2 = UOp.range(UOp.const(dtypes.weakint, 250), 2, AxisType.LOOP) c2 = UOp.range(UOp.const(dtypes.weakint, 250), 2, AxisType.LOOP)
c3 = UOp.param(1, dtypes.int.ptr(512)) c3 = UOp(Ops.PARAM, dtypes.int.ptr(512), arg=1, src=())
c4 = c3.index(c1) # c4 is a load c4 = c3.index(c1) # c4 is a load
c5 = UOp.range(UOp.const(dtypes.weakint, 240), 0, AxisType.REDUCE) c5 = UOp.range(UOp.const(dtypes.weakint, 240), 0, AxisType.REDUCE)
c6 = ((c2*UOp.const(dtypes.weakint, 240))+c5) c6 = ((c2*UOp.const(dtypes.weakint, 240))+c5)
c7 = UOp.param(2, dtypes.uchar.ptr(60000)) c7 = UOp(Ops.PARAM, dtypes.uchar.ptr(60000), arg=2, src=())
c8 = c7.index(c6) c8 = c7.index(c6)
# (loaded + range) < const pattern - loaded value shouldn't be promoted to long # (loaded + range) < const pattern - loaded value shouldn't be promoted to long
loaded_idx = c4.cast(dtypes.weakint) loaded_idx = c4.cast(dtypes.weakint)
@@ -529,9 +529,9 @@ class TestUOpGraph(unittest.TestCase):
self.assertNotEqual(u.dtype, dtypes.long) self.assertNotEqual(u.dtype, dtypes.long)
def test_fold_gated_load(self): def test_fold_gated_load(self):
glbl0 = UOp.param(0, dtypes.int.ptr()) glbl0 = UOp(Ops.PARAM, dtypes.int.ptr(), (), 0)
glbl1 = UOp.param(1, dtypes.int.ptr()) glbl1 = UOp(Ops.PARAM, dtypes.int.ptr(), (), 1)
glbl2 = UOp.param(2, dtypes.int.ptr()) glbl2 = UOp(Ops.PARAM, dtypes.int.ptr(), (), 2)
idx = UOp.const(dtypes.int, 0) idx = UOp.const(dtypes.int, 0)
ld0 = glbl1.index(UOp.invalid()) ld0 = glbl1.index(UOp.invalid())
ld1 = glbl2.index(idx.valid(UOp.const(dtypes.bool, True))) ld1 = glbl2.index(idx.valid(UOp.const(dtypes.bool, True)))
@@ -541,7 +541,7 @@ class TestUOpGraph(unittest.TestCase):
self.assertEqual(ld0, UOp.load(glbl2.index(idx, ptr=True), dtype=dtypes.int)) self.assertEqual(ld0, UOp.load(glbl2.index(idx, ptr=True), dtype=dtypes.int))
def test_fold_gated_load_local(self): def test_fold_gated_load_local(self):
glbl0 = UOp.param(0, dtypes.int.ptr()) glbl0 = UOp(Ops.PARAM, dtypes.int.ptr(), (), 0)
smem = UOp(Ops.DEFINE_LOCAL, dtypes.int.ptr(size=18, addrspace=AddrSpace.LOCAL), (), "temp") smem = UOp(Ops.DEFINE_LOCAL, dtypes.int.ptr(size=18, addrspace=AddrSpace.LOCAL), (), "temp")
lidx = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 16),), "lidx0") lidx = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 16),), "lidx0")
st = UOp(Ops.STORE, dtypes.void, (smem.index(lidx, ptr=True), glbl0.index(lidx, ptr=True).load())) st = UOp(Ops.STORE, dtypes.void, (smem.index(lidx, ptr=True), glbl0.index(lidx, ptr=True).load()))
@@ -555,7 +555,7 @@ class TestUOpGraph(unittest.TestCase):
self.assertEqual(ld0.src[0], smem.after(barrier).index(lidx+2, ptr=True)) self.assertEqual(ld0.src[0], smem.after(barrier).index(lidx+2, ptr=True))
def test_fold_gated_store(self): def test_fold_gated_store(self):
glbl = UOp.param(0, dtypes.int.ptr()) glbl = UOp(Ops.PARAM, dtypes.int.ptr(), (), 0)
idx0 = UOp.const(dtypes.int, 0) idx0 = UOp.const(dtypes.int, 0)
idx1 = UOp.const(dtypes.int, 0) idx1 = UOp.const(dtypes.int, 0)
val = UOp.const(dtypes.int, 42) val = UOp.const(dtypes.int, 42)
@@ -563,12 +563,12 @@ class TestUOpGraph(unittest.TestCase):
st1 = glbl.index(idx0.valid(UOp.const(dtypes.bool, True)), ptr=True).store(val) st1 = glbl.index(idx0.valid(UOp.const(dtypes.bool, True)), ptr=True).store(val)
uops = to_uops_list([st0, st1]) uops = to_uops_list([st0, st1])
# only the second store happens # only the second store happens
self.assertEqual(len(uops), 7) # +1 for SINK, +1 for PARAM shape sentinel self.assertEqual(len(uops), 6) # +1 for SINK
self.assertEqual(uops[-2], glbl.index(idx1, ptr=True).store(val)) # -2 to skip SINK self.assertEqual(uops[-2], glbl.index(idx1, ptr=True).store(val)) # -2 to skip SINK
@unittest.skip("this is a uop type error") @unittest.skip("this is a uop type error")
def test_asserts_bad_gate(self): def test_asserts_bad_gate(self):
glbl0 = UOp.param(0, dtypes.int.ptr()) glbl0 = UOp(Ops.PARAM, dtypes.int.ptr(), (), 0)
idx = UOp.const(dtypes.int, 0) idx = UOp.const(dtypes.int, 0)
bad_gate = UOp.const(dtypes.int, 1) bad_gate = UOp.const(dtypes.int, 1)
with self.assertRaises(AssertionError): to_uops_list([UOp(Ops.STORE, dtypes.void, (glbl0, idx, UOp.const(dtypes.int, 42), bad_gate))]) with self.assertRaises(AssertionError): to_uops_list([UOp(Ops.STORE, dtypes.void, (glbl0, idx, UOp.const(dtypes.int, 42), bad_gate))])
@@ -779,7 +779,7 @@ class TestLoadStoreFolding(unittest.TestCase):
def test_gated_load_gep_preserves_alt(self): def test_gated_load_gep_preserves_alt(self):
"""Test that LOAD(GEP, alt) preserves alt value after rewrite""" """Test that LOAD(GEP, alt) preserves alt value after rewrite"""
from tinygrad.codegen.late.devectorizer import load_store_folding from tinygrad.codegen.late.devectorizer import load_store_folding
buf = UOp.param(0, dtypes.float.vec(4).ptr()) buf = UOp(Ops.PARAM, dtypes.float.vec(4).ptr(), (), 0)
idx = UOp.const(dtypes.int, 0) idx = UOp.const(dtypes.int, 0)
gate = UOp.const(dtypes.bool, True) gate = UOp.const(dtypes.bool, True)
gated_index = buf.index(idx.valid(gate)) gated_index = buf.index(idx.valid(gate))
@@ -797,8 +797,8 @@ class TestLoadStoreFolding(unittest.TestCase):
def test_gated_load_ptrcat_preserves_alt(self): def test_gated_load_ptrcat_preserves_alt(self):
"""Test that LOAD(PTRCAT, alt) preserves alt value after rewrite""" """Test that LOAD(PTRCAT, alt) preserves alt value after rewrite"""
from tinygrad.codegen.late.devectorizer import load_store_folding from tinygrad.codegen.late.devectorizer import load_store_folding
buf1 = UOp.param(0, dtypes.float.ptr()) buf1 = UOp(Ops.PARAM, dtypes.float.ptr(), (), 0)
buf2 = UOp.param(1, dtypes.float.ptr()) buf2 = UOp(Ops.PARAM, dtypes.float.ptr(), (), 1)
idx = UOp.const(dtypes.int, 0) idx = UOp.const(dtypes.int, 0)
idx1 = buf1.index(idx) idx1 = buf1.index(idx)
idx2 = buf2.index(idx) idx2 = buf2.index(idx)
+3 -3
View File
@@ -951,7 +951,7 @@ class TestSymbolic(unittest.TestCase):
expr = cond.where(a, b).cast(dtypes.half) expr = cond.where(a, b).cast(dtypes.half)
# TODO: copied from render, render does not support cast # TODO: copied from render, render does not support cast
glbl = UOp.param(0, dtypes.int.ptr()) glbl = UOp(Ops.PARAM, dtypes.int.ptr(), arg=0)
uops = get_uops(UOp(Ops.STORE, dtypes.void, (glbl.index(UOp.const(dtypes.int, 0)), expr)).sink()) uops = get_uops(UOp(Ops.STORE, dtypes.void, (glbl.index(UOp.const(dtypes.int, 0)), expr)).sink())
rewritten_uop = [uop for uop in uops if uop.op is Ops.STORE][0].src[1] rewritten_uop = [uop for uop in uops if uop.op is Ops.STORE][0].src[1]
@@ -1270,7 +1270,7 @@ class TestStoreLoadFolding(unittest.TestCase):
"""Tests for store(index, load(index)) -> NOOP rule. This rule matches patterns that EMERGE during simplification.""" """Tests for store(index, load(index)) -> NOOP rule. This rule matches patterns that EMERGE during simplification."""
def test_store_load_folding(self): def test_store_load_folding(self):
# store(idx, load(idx)) -> NOOP, including emergent patterns like store(idx, load(idx) + 0) # store(idx, load(idx)) -> NOOP, including emergent patterns like store(idx, load(idx) + 0)
buf = UOp.param(0, dtypes.int.ptr()) buf = UOp(Ops.PARAM, dtypes.int.ptr(), arg=0)
index = buf.index(UOp.const(dtypes.weakint, 0)) index = buf.index(UOp.const(dtypes.weakint, 0))
# Direct: store(idx, load(idx)) -> NOOP # Direct: store(idx, load(idx)) -> NOOP
self.assertEqual(graph_rewrite(index.store(index.load()), sym).op, Ops.NOOP) self.assertEqual(graph_rewrite(index.store(index.load()), sym).op, Ops.NOOP)
@@ -1340,7 +1340,7 @@ class TestRangeSplitting(unittest.TestCase):
from tinygrad.codegen.simplify import pm_split_ranges, pm_flatten_range from tinygrad.codegen.simplify import pm_split_ranges, pm_flatten_range
r0 = UOp.range(uconst(8), 0) r0 = UOp.range(uconst(8), 0)
# create a simple expression using the range with mod: store range%2 to a buffer # create a simple expression using the range with mod: store range%2 to a buffer
buf = UOp.param(0, dtypes.int.ptr()) buf = UOp(Ops.PARAM, dtypes.int.ptr(), arg=0)
val = (r0 % uconst(2)).cast(dtypes.int) val = (r0 % uconst(2)).cast(dtypes.int)
store = UOp(Ops.STORE, dtypes.void, (buf.index(uconst(0)), val)) store = UOp(Ops.STORE, dtypes.void, (buf.index(uconst(0)), val))
sink = UOp(Ops.SINK, dtypes.void, (UOp(Ops.END, dtypes.void, (store, r0)),)) sink = UOp(Ops.SINK, dtypes.void, (UOp(Ops.END, dtypes.void, (store, r0)),))
+2 -2
View File
@@ -82,7 +82,7 @@ class TestVminVmaxProperties(unittest.TestCase):
def test_vmin_vmax_multiplication_0_inf(self): def test_vmin_vmax_multiplication_0_inf(self):
# vmin and vmax for multiplication with a variable # vmin and vmax for multiplication with a variable
x = UOp.const(dtypes.float, 0.0) x = UOp.const(dtypes.float, 0.0)
y = UOp.load(UOp.param(0, dtypes.float.ptr(1)), UOp.const(dtypes.int, 0), dtype=dtypes.float) y = UOp.load(UOp(Ops.PARAM, dtypes.float.ptr(1), (), 0), UOp.const(dtypes.int, 0), dtype=dtypes.float)
uop = x * y uop = x * y
# TODO: these should be 0, but definitely should not be nan # TODO: these should be 0, but definitely should not be nan
self.assertEqual(uop.vmin, -math.inf) self.assertEqual(uop.vmin, -math.inf)
@@ -316,7 +316,7 @@ class TestVminVmaxVConst(unittest.TestCase):
def test_vmin_vmax_vector_with_gep(self): def test_vmin_vmax_vector_with_gep(self):
# vmin and vmax for a vector constant of bool values # vmin and vmax for a vector constant of bool values
d1 = UOp.param(1, dtypes.int.ptr()) d1 = UOp(Ops.PARAM, dtypes.int.ptr(), (), 1)
idx = UOp.const(dtypes.int, 0) idx = UOp.const(dtypes.int, 0)
val = UOp(Ops.LOAD, dtypes.int.vec(2), (d1.index(idx).cast(dtypes.int.vec(2).ptr()),)) val = UOp(Ops.LOAD, dtypes.int.vec(2), (d1.index(idx).cast(dtypes.int.vec(2).ptr()),))
uop = (val // 32).gep(0) uop = (val // 32).gep(0)
+13 -13
View File
@@ -110,7 +110,7 @@ class TestExecALU(unittest.TestCase):
class TestGatedStoreRewrite(unittest.TestCase): class TestGatedStoreRewrite(unittest.TestCase):
def test_tiny_gate_store(self): def test_tiny_gate_store(self):
gmem = UOp.param(0, dtypes.float.ptr()) gmem = UOp(Ops.PARAM, dtypes.float.ptr(), (), 0)
gidx0 = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'gidx0') gidx0 = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'gidx0')
gate = gidx0<UOp.const(dtypes.int, 1) gate = gidx0<UOp.const(dtypes.int, 1)
idx = UOp(Ops.INDEX, dtypes.float.ptr(), (gmem, (gidx0 * UOp.const(dtypes.int, 2)).valid(gate))) idx = UOp(Ops.INDEX, dtypes.float.ptr(), (gmem, (gidx0 * UOp.const(dtypes.int, 2)).valid(gate)))
@@ -126,8 +126,8 @@ class TestGatedStoreRewrite(unittest.TestCase):
self.assertEqual(len(gated_uops[-1].src), 2) self.assertEqual(len(gated_uops[-1].src), 2)
def test_gate_some_stores(self): def test_gate_some_stores(self):
gmem0 = UOp.param(0, dtypes.float.ptr()) gmem0 = UOp(Ops.PARAM, dtypes.float.ptr(), (), 0)
gmem1 = UOp.param(1, dtypes.float.ptr()) gmem1 = UOp(Ops.PARAM, dtypes.float.ptr(), (), 1)
gidx0 = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'gidx0') gidx0 = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'gidx0')
idx = gidx0 * UOp.const(dtypes.int, 2) idx = gidx0 * UOp.const(dtypes.int, 2)
idx0 = UOp(Ops.INDEX, dtypes.float.ptr(), (gmem0, idx.valid(gidx0<UOp.const(dtypes.int, 1)))) idx0 = UOp(Ops.INDEX, dtypes.float.ptr(), (gmem0, idx.valid(gidx0<UOp.const(dtypes.int, 1))))
@@ -146,8 +146,8 @@ class TestGatedStoreRewrite(unittest.TestCase):
# scaled down version of TestLinearizerDumb.test_unmerged_ifs # scaled down version of TestLinearizerDumb.test_unmerged_ifs
@unittest.skip("we don't merge ifs anymore") @unittest.skip("we don't merge ifs anymore")
def test_merge_ifs_alt(self): def test_merge_ifs_alt(self):
gmem0 = UOp.param(0, dtypes.float.ptr()) gmem0 = UOp(Ops.PARAM, dtypes.float.ptr(), (), 0)
gmem1 = UOp.param(1, dtypes.float.ptr()) gmem1 = UOp(Ops.PARAM, dtypes.float.ptr(), (), 1)
gidx0 = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'gidx0') gidx0 = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'gidx0')
idx = gidx0*UOp.const(dtypes.int, 2) idx = gidx0*UOp.const(dtypes.int, 2)
gate = gidx0<UOp.const(dtypes.int, 1) gate = gidx0<UOp.const(dtypes.int, 1)
@@ -170,7 +170,7 @@ class TestGatedStoreRewrite(unittest.TestCase):
class TestFastIdiv(unittest.TestCase): class TestFastIdiv(unittest.TestCase):
def test_division_power_of_two(self): def test_division_power_of_two(self):
for dt in (dtypes.int32, dtypes.uint32): for dt in (dtypes.int32, dtypes.uint32):
g = UOp.param(0, dt.ptr()) g = UOp(Ops.PARAM, dt.ptr(), (), 0)
c = UOp.const(dt, 2) c = UOp.const(dt, 2)
l = g.index(c) l = g.index(c)
a = UOp(Ops.CDIV, dt, (l, c)) a = UOp(Ops.CDIV, dt, (l, c))
@@ -183,7 +183,7 @@ class TestFastIdiv(unittest.TestCase):
def test_floormod_power_of_two(self): def test_floormod_power_of_two(self):
# FLOORMOD by a power of two lowers to AND (correct floor mod for any sign in two's complement) # FLOORMOD by a power of two lowers to AND (correct floor mod for any sign in two's complement)
for dt in (dtypes.int32, dtypes.uint32): for dt in (dtypes.int32, dtypes.uint32):
g = UOp.param(0, dt.ptr()) g = UOp(Ops.PARAM, dt.ptr(), (), 0)
c = UOp.const(dt, 8) c = UOp.const(dt, 8)
a = UOp(Ops.FLOORMOD, dt, (g.index(c), c)) a = UOp(Ops.FLOORMOD, dt, (g.index(c), c))
uops = to_uops_list([a], ren=Device[Device.DEFAULT].renderer) uops = to_uops_list([a], ren=Device[Device.DEFAULT].renderer)
@@ -195,7 +195,7 @@ class TestFastIdiv(unittest.TestCase):
def test_floordiv_power_of_two_uint(self): def test_floordiv_power_of_two_uint(self):
# uint FLOORDIV by a power of two lowers to a shift, leaving no IDIV/FLOORDIV in the kernel # uint FLOORDIV by a power of two lowers to a shift, leaving no IDIV/FLOORDIV in the kernel
for dt in (dtypes.uint32, dtypes.uint64): for dt in (dtypes.uint32, dtypes.uint64):
g = UOp.param(0, dt.ptr()) g = UOp(Ops.PARAM, dt.ptr(), (), 0)
c = UOp.const(dt, 2) c = UOp.const(dt, 2)
a = UOp(Ops.FLOORDIV, dt, (g.index(c), c)) a = UOp(Ops.FLOORDIV, dt, (g.index(c), c))
uops = to_uops_list([a], ren=Device[Device.DEFAULT].renderer) uops = to_uops_list([a], ren=Device[Device.DEFAULT].renderer)
@@ -207,7 +207,7 @@ class TestFastIdiv(unittest.TestCase):
@Context(DISABLE_FAST_IDIV=0) @Context(DISABLE_FAST_IDIV=0)
@unittest.skipIf(Device.DEFAULT == "WEBGPU", "WEBGPU doesn't support long") @unittest.skipIf(Device.DEFAULT == "WEBGPU", "WEBGPU doesn't support long")
def test_fast_idiv_and_mod(self): def test_fast_idiv_and_mod(self):
g = UOp.param(0, dtypes.uint32.ptr()) g = UOp(Ops.PARAM, dtypes.uint32.ptr(), (), 0)
c = UOp.const(dtypes.uint, 3) c = UOp.const(dtypes.uint, 3)
l = g.index(c) l = g.index(c)
a = UOp(Ops.CDIV, dtypes.uint, (l, c)) a = UOp(Ops.CDIV, dtypes.uint, (l, c))
@@ -242,7 +242,7 @@ class TestFastIdiv(unittest.TestCase):
@unittest.expectedFailure @unittest.expectedFailure
def test_fast_idiv_overflow(self): def test_fast_idiv_overflow(self):
# This will be possible with a slightly different method for fast_idiv # This will be possible with a slightly different method for fast_idiv
g = UOp.param(0, dtypes.uint32.ptr()) g = UOp(Ops.PARAM, dtypes.uint32.ptr(), (), 0)
c = UOp.const(dtypes.uint, 7) c = UOp.const(dtypes.uint, 7)
l = UOp(Ops.LOAD, dtypes.uint, (g.index(c),)) l = UOp(Ops.LOAD, dtypes.uint, (g.index(c),))
a = UOp(Ops.CDIV, dtypes.uint, (l, c)) a = UOp(Ops.CDIV, dtypes.uint, (l, c))
@@ -253,7 +253,7 @@ class TestFastIdiv(unittest.TestCase):
self.assertNotIn(Ops.CDIV, ops) self.assertNotIn(Ops.CDIV, ops)
def test_disable_fast_idiv(self): def test_disable_fast_idiv(self):
g = UOp.param(0, dtypes.uint32.ptr()) g = UOp(Ops.PARAM, dtypes.uint32.ptr(), (), 0)
c = UOp.const(dtypes.uint, 3) c = UOp.const(dtypes.uint, 3)
l = g.index(c) l = g.index(c)
a = UOp(Ops.CDIV, dtypes.uint, (l, c)) a = UOp(Ops.CDIV, dtypes.uint, (l, c))
@@ -290,8 +290,8 @@ class TestUOpMethod(unittest.TestCase):
self.assertEqual((gidx0*3+1).const_factor(), 1) self.assertEqual((gidx0*3+1).const_factor(), 1)
def test_replace(self): def test_replace(self):
x = UOp.param(0, dtypes.int.ptr()) x = UOp(Ops.PARAM, dtypes.int.ptr(), (), 0)
self.assertEqual(x.replace(arg=UOp.param(1, dtypes.int.ptr()).arg).arg.slot, 1) self.assertIs(x.replace(arg=None).arg, None)
with self.assertRaises(AssertionError): x.replace(field="a") with self.assertRaises(AssertionError): x.replace(field="a")
def test_const_zero_neg_zero_different(self): def test_const_zero_neg_zero_different(self):
+2 -2
View File
@@ -139,7 +139,7 @@ class TestUOpsStats(unittest.TestCase):
#MULACC should have the same stats as MUL + ADD #MULACC should have the same stats as MUL + ADD
def test_mulacc(self): def test_mulacc(self):
globl = UOp.param(0, dtypes.int.ptr()) globl = UOp(Ops.PARAM, dtypes.int.ptr(), tuple())
o1 = UOp(Ops.CONST, dtypes.int, tuple(), 1) o1 = UOp(Ops.CONST, dtypes.int, tuple(), 1)
o2 = UOp(Ops.CONST, dtypes.int, tuple(), 2) o2 = UOp(Ops.CONST, dtypes.int, tuple(), 2)
u1 = globl.index(o1) u1 = globl.index(o1)
@@ -149,7 +149,7 @@ class TestUOpsStats(unittest.TestCase):
u5 = UOp(Ops.ADD, dtypes.int, (u4,u3)) u5 = UOp(Ops.ADD, dtypes.int, (u4,u3))
uops = tuple(u5.toposort()) uops = tuple(u5.toposort())
globl = UOp.param(0, dtypes.int.ptr()) globl = UOp(Ops.PARAM, dtypes.int.ptr(), tuple())
o1 = UOp(Ops.CONST, dtypes.int, tuple(), 1) o1 = UOp(Ops.CONST, dtypes.int, tuple(), 1)
o2 = UOp(Ops.CONST, dtypes.int, tuple(), 2) o2 = UOp(Ops.CONST, dtypes.int, tuple(), 2)
u1 = globl.index(o1) u1 = globl.index(o1)
+21 -21
View File
@@ -11,7 +11,7 @@ class TestValidateOOB(unittest.TestCase):
# basic index patterns # basic index patterns
def test_const_index(self): def test_const_index(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16)) buf = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
to_uops_list([buf.index(UOp.const(dtypes.int, 0), ptr=True).load(dtype=dtypes.int)]) # valid to_uops_list([buf.index(UOp.const(dtypes.int, 0), ptr=True).load(dtype=dtypes.int)]) # valid
to_uops_list([buf.index(UOp.const(dtypes.int, 15), ptr=True).load(dtype=dtypes.int)]) # valid (last element) to_uops_list([buf.index(UOp.const(dtypes.int, 15), ptr=True).load(dtype=dtypes.int)]) # valid (last element)
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
@@ -21,7 +21,7 @@ class TestValidateOOB(unittest.TestCase):
def test_variable_index(self): def test_variable_index(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16)) buf = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
to_uops_list([buf.index(Variable("i", 0, 15), ptr=True).load(dtype=dtypes.int)]) # valid to_uops_list([buf.index(Variable("i", 0, 15), ptr=True).load(dtype=dtypes.int)]) # valid
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
to_uops_list([buf.index(Variable("i", 0, 20), ptr=True).load(dtype=dtypes.int)]) # oob to_uops_list([buf.index(Variable("i", 0, 20), ptr=True).load(dtype=dtypes.int)]) # oob
@@ -30,7 +30,7 @@ class TestValidateOOB(unittest.TestCase):
def test_range_with_mask(self): def test_range_with_mask(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16)) buf = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
r = UOp.range(42, 0, AxisType.GLOBAL) r = UOp.range(42, 0, AxisType.GLOBAL)
to_uops_list([buf.index(r.valid(r < 16), ptr=True).load(dtype=dtypes.int)]) # valid to_uops_list([buf.index(r.valid(r < 16), ptr=True).load(dtype=dtypes.int)]) # valid
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
@@ -38,7 +38,7 @@ class TestValidateOOB(unittest.TestCase):
def test_variable_with_mask(self): def test_variable_with_mask(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16)) buf = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
v = Variable("v", -5, 80) v = Variable("v", -5, 80)
to_uops_list([buf.index(v.valid((v >= 0) & (v < 16)), ptr=True).load(dtype=dtypes.int)]) # valid to_uops_list([buf.index(v.valid((v >= 0) & (v < 16)), ptr=True).load(dtype=dtypes.int)]) # valid
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
@@ -46,7 +46,7 @@ class TestValidateOOB(unittest.TestCase):
def test_gated_store(self): def test_gated_store(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16)) buf = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
v = Variable("v", 0, 20) v = Variable("v", 0, 20)
to_uops_list([buf.index(v.valid(v < 16), ptr=True).store(0)]) # valid to_uops_list([buf.index(v.valid(v < 16), ptr=True).store(0)]) # valid
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
@@ -55,14 +55,14 @@ class TestValidateOOB(unittest.TestCase):
# ALU ops in index # ALU ops in index
def test_floordiv(self): def test_floordiv(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16)) buf = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
to_uops_list([buf.index(UOp.range(32, 0, AxisType.GLOBAL) // 2, ptr=True).load(dtype=dtypes.int)]) # 0..15 valid to_uops_list([buf.index(UOp.range(32, 0, AxisType.GLOBAL) // 2, ptr=True).load(dtype=dtypes.int)]) # 0..15 valid
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
to_uops_list([buf.index(UOp.range(34, 0, AxisType.GLOBAL) // 2, ptr=True).load(dtype=dtypes.int)]) # 0..16 oob to_uops_list([buf.index(UOp.range(34, 0, AxisType.GLOBAL) // 2, ptr=True).load(dtype=dtypes.int)]) # 0..16 oob
def test_mod(self): def test_mod(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16)) buf = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
r = UOp.range(100, 0, AxisType.GLOBAL) r = UOp.range(100, 0, AxisType.GLOBAL)
to_uops_list([buf.index(r % 16, ptr=True).load(dtype=dtypes.int)]) # 0..15 valid to_uops_list([buf.index(r % 16, ptr=True).load(dtype=dtypes.int)]) # 0..15 valid
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
@@ -70,14 +70,14 @@ class TestValidateOOB(unittest.TestCase):
def test_shr(self): def test_shr(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16)) buf = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
to_uops_list([buf.index(UOp.range(64, 0, AxisType.GLOBAL) >> 2, ptr=True).load(dtype=dtypes.int)]) # 0..15 valid to_uops_list([buf.index(UOp.range(64, 0, AxisType.GLOBAL) >> 2, ptr=True).load(dtype=dtypes.int)]) # 0..15 valid
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
to_uops_list([buf.index(UOp.range(128, 0, AxisType.GLOBAL) >> 2, ptr=True).load(dtype=dtypes.int)]) # 0..31 oob to_uops_list([buf.index(UOp.range(128, 0, AxisType.GLOBAL) >> 2, ptr=True).load(dtype=dtypes.int)]) # 0..31 oob
def test_shl(self): def test_shl(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(64)) buf = UOp(Ops.PARAM, dtypes.int.ptr(64), (), 0)
r = UOp.range(8, 0, AxisType.GLOBAL) r = UOp.range(8, 0, AxisType.GLOBAL)
to_uops_list([buf.index(r << 2, ptr=True).load(dtype=dtypes.int)]) # 0..28 valid to_uops_list([buf.index(r << 2, ptr=True).load(dtype=dtypes.int)]) # 0..28 valid
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
@@ -85,7 +85,7 @@ class TestValidateOOB(unittest.TestCase):
def test_and(self): def test_and(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16)) buf = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
r = UOp.range(100, 0, AxisType.GLOBAL) r = UOp.range(100, 0, AxisType.GLOBAL)
to_uops_list([buf.index(r & 15, ptr=True).load(dtype=dtypes.int)]) # 0..15 valid to_uops_list([buf.index(r & 15, ptr=True).load(dtype=dtypes.int)]) # 0..15 valid
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
@@ -93,14 +93,14 @@ class TestValidateOOB(unittest.TestCase):
def test_max(self): def test_max(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16)) buf = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
to_uops_list([buf.index(Variable("v", -10, 15).maximum(0), ptr=True).load(dtype=dtypes.int)]) # 0..15 valid to_uops_list([buf.index(Variable("v", -10, 15).maximum(0), ptr=True).load(dtype=dtypes.int)]) # 0..15 valid
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
to_uops_list([buf.index(Variable("v2", -10, 20).maximum(0), ptr=True).load(dtype=dtypes.int)]) # 0..20 oob to_uops_list([buf.index(Variable("v2", -10, 20).maximum(0), ptr=True).load(dtype=dtypes.int)]) # 0..20 oob
def test_xor_in_mask(self): def test_xor_in_mask(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16)) buf = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
r = UOp.range(32, 0, AxisType.GLOBAL) r = UOp.range(32, 0, AxisType.GLOBAL)
to_uops_list([buf.index(r.valid((r < 8) ^ ((r >= 8) & (r < 16))), ptr=True).load(dtype=dtypes.int)]) # 0..15 valid to_uops_list([buf.index(r.valid((r < 8) ^ ((r >= 8) & (r < 16))), ptr=True).load(dtype=dtypes.int)]) # 0..15 valid
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
@@ -109,22 +109,22 @@ class TestValidateOOB(unittest.TestCase):
# cast patterns # cast patterns
def test_float_cast_in_index(self): def test_float_cast_in_index(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16)) buf = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
r = UOp.range(20, 0) r = UOp.range(20, 0)
i = (r.cast(dtypes.float) * 0.68).trunc().cast(dtypes.int) i = (r.cast(dtypes.float) * 0.68).trunc().cast(dtypes.int)
to_uops_list([buf.index(i.valid((i >= 0) & (i < 16)), ptr=True).load(dtype=dtypes.int)]) to_uops_list([buf.index(i.valid((i >= 0) & (i < 16)), ptr=True).load(dtype=dtypes.int)])
def test_bool_cast_in_mask(self): def test_bool_cast_in_mask(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(1)) buf = UOp(Ops.PARAM, dtypes.int.ptr(1), (), 0)
r = UOp.range(20, 0) r = UOp.range(20, 0)
to_uops_list([buf.index(r.valid(r.cast(dtypes.bool).logical_not()), ptr=True).load(dtype=dtypes.int)]) # only r=0 valid to_uops_list([buf.index(r.valid(r.cast(dtypes.bool).logical_not()), ptr=True).load(dtype=dtypes.int)]) # only r=0 valid
# load result as index/mask # load result as index/mask
def test_load_as_index(self): def test_load_as_index(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf0 = UOp.param(0, dtypes.int.ptr(16)) buf0 = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
buf1 = UOp.param(1, dtypes.int.ptr(64)) buf1 = UOp(Ops.PARAM, dtypes.int.ptr(64), (), 1)
r = UOp.range(42, 0, AxisType.GLOBAL) r = UOp.range(42, 0, AxisType.GLOBAL)
ld0 = buf0.index(r.valid(r < 8), ptr=True).load(dtype=dtypes.int).cast(dtypes.weakint) ld0 = buf0.index(r.valid(r < 8), ptr=True).load(dtype=dtypes.int).cast(dtypes.weakint)
to_uops_list([buf1.index((ld0 * 2).valid((ld0 >= 0) & (ld0 < 32)), ptr=True).load(dtype=dtypes.int)]) # valid to_uops_list([buf1.index((ld0 * 2).valid((ld0 >= 0) & (ld0 < 32)), ptr=True).load(dtype=dtypes.int)]) # valid
@@ -133,8 +133,8 @@ class TestValidateOOB(unittest.TestCase):
def test_load_bool_as_mask(self): def test_load_bool_as_mask(self):
with Context(CHECK_OOB=1, SPEC=2): with Context(CHECK_OOB=1, SPEC=2):
buf_bool = UOp.param(0, dtypes.bool.ptr(16)) buf_bool = UOp(Ops.PARAM, dtypes.bool.ptr(16), (), 0)
buf_int = UOp.param(1, dtypes.int.ptr(8)) buf_int = UOp(Ops.PARAM, dtypes.int.ptr(8), (), 1)
gidx = UOp(Ops.SPECIAL, dtypes.weakint, (UOp.const(dtypes.weakint, 16),), "gidx0") gidx = UOp(Ops.SPECIAL, dtypes.weakint, (UOp.const(dtypes.weakint, 16),), "gidx0")
ld_bool = buf_bool.index(gidx, ptr=True).load() ld_bool = buf_bool.index(gidx, ptr=True).load()
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
@@ -145,7 +145,7 @@ class TestValidateOOB(unittest.TestCase):
def test_in_bounds_access_gated_local(self): def test_in_bounds_access_gated_local(self):
with Context(CHECK_OOB=1): with Context(CHECK_OOB=1):
# Define buffers # Define buffers
gbuf = UOp.param(0, dtypes.uint.ptr(400)) gbuf = UOp(Ops.PARAM, dtypes.uint.ptr(400), (), 0)
sbuf = UOp(Ops.DEFINE_LOCAL, dtypes.uint.ptr(8, addrspace=AddrSpace.LOCAL), (), "temp0") sbuf = UOp(Ops.DEFINE_LOCAL, dtypes.uint.ptr(8, addrspace=AddrSpace.LOCAL), (), "temp0")
# Define indices, valids and barrier # Define indices, valids and barrier
@@ -169,8 +169,8 @@ class TestValidateOOB(unittest.TestCase):
@unittest.skip("Bool load is not supported yet") @unittest.skip("Bool load is not supported yet")
def test_load_mask(self): def test_load_mask(self):
with Context(CHECK_OOB=1): with Context(CHECK_OOB=1):
glbl0 = UOp.param(0, dtypes.int.ptr(16)) glbl0 = UOp(Ops.PARAM, dtypes.int.ptr(16), (), 0)
mask = UOp.param(0, dtypes.bool.ptr(16)) mask = UOp(Ops.PARAM, dtypes.bool.ptr(16), (), 0)
ridx = UOp.range(20, 0) ridx = UOp.range(20, 0)
ld0 = UOp(Ops.LOAD, dtypes.int, (glbl0.index(UOp.const(ridx, ridx<16&mask), ptr=True))) ld0 = UOp(Ops.LOAD, dtypes.int, (glbl0.index(UOp.const(ridx, ridx<16&mask), ptr=True)))
to_uops_list([ld0]) to_uops_list([ld0])
+10 -25
View File
@@ -5,14 +5,15 @@ from typing import Generator
from tinygrad.uop.ops import UOp, UPat, Ops, PatternMatcher, TrackedPatternMatcher, graph_rewrite, track_rewrites, profile_matches from tinygrad.uop.ops import UOp, UPat, Ops, PatternMatcher, TrackedPatternMatcher, graph_rewrite, track_rewrites, profile_matches
from tinygrad.uop.symbolic import sym from tinygrad.uop.symbolic import sym
from tinygrad.dtype import dtypes, AddrSpace from tinygrad.dtype import dtypes
from tinygrad.helpers import colored, ansistrip, flatten, TracingKey, ProfileRangeEvent, ProfileEvent, Context, cpu_events, profile_marker from tinygrad.helpers import colored, ansistrip, flatten, TracingKey, ProfileRangeEvent, ProfileEvent, Context, cpu_events, profile_marker
from tinygrad.helpers import cpu_profile, ProfilePointEvent, unwrap from tinygrad.helpers import cpu_profile, ProfilePointEvent, unwrap
from tinygrad.device import Buffer from tinygrad.device import Buffer
from tinygrad.uop.ops import tracked_keys, tracked_ctxs, uop_fields, active_rewrites, active_group, _name_cnt, RewriteTrace from tinygrad.uop.ops import tracked_keys, tracked_ctxs, uop_fields, active_rewrites, active_group, _name_cnt, RewriteTrace
from tinygrad.viz.serve import load_rewrites, get_full_rewrite, uop_to_json, VizData, get_render, addrspace_colors from tinygrad.viz.serve import load_rewrites, get_full_rewrite, uop_to_json, VizData, get_render
from tinygrad.codegen import do_to_program from tinygrad.codegen import to_program_cache
from tinygrad.codegen import to_program
@track_rewrites(name=True) @track_rewrites(name=True)
def exec_rewrite(sink:UOp, pm_lst:list[PatternMatcher], names:None|list[str]=None) -> UOp: def exec_rewrite(sink:UOp, pm_lst:list[PatternMatcher], names:None|list[str]=None) -> UOp:
@@ -40,6 +41,7 @@ class VizTrace:
@contextlib.contextmanager @contextlib.contextmanager
def save_viz(): def save_viz():
for lst in [tracked_keys, tracked_ctxs, active_rewrites, active_group, _name_cnt]: lst.clear() for lst in [tracked_keys, tracked_ctxs, active_rewrites, active_group, _name_cnt]: lst.clear()
to_program_cache.clear()
Buffer.profile_events.clear() Buffer.profile_events.clear()
cpu_events.clear() cpu_events.clear()
viz = VizTrace() viz = VizTrace()
@@ -246,19 +248,6 @@ class TestViz(unittest.TestCase):
self.assertIn("EXPAND", excluded_nodes) self.assertIn("EXPAND", excluded_nodes)
self.assertIn("CONST1 1 Ops.DEVICE", graph[id(alu)]["label"]) self.assertIn("CONST1 1 Ops.DEVICE", graph[id(alu)]["label"])
def test_stack_movement_not_folded_unless_all_const(self):
a = UOp.variable("a", 0, 10, dtype=dtypes.int)
c = UOp.const(dtypes.int, 1)
stack = a.vectorize(c)
reshaped = stack.reshape((1, 2))
graph = uop_to_json(VizData(), reshaped)
self.assertFalse(graph[id(stack)]["exclude"])
const_stack = c.vectorize(UOp.const(dtypes.int, 2))
const_reshaped = const_stack.reshape((1, 2))
const_graph = uop_to_json(VizData(), const_reshaped)
self.assertTrue(const_graph[id(const_stack)]["exclude"])
# VIZ displays nested graph_rewrites in a tree view # VIZ displays nested graph_rewrites in a tree view
def leaf_rewrite(x:UOp): return x.rtag(1) if x.tag is None else None def leaf_rewrite(x:UOp): return x.rtag(1) if x.tag is None else None
@@ -340,15 +329,12 @@ class TestVizIntegration(unittest.TestCase):
def test_codegen_tracing(self): def test_codegen_tracing(self):
with save_viz() as viz: with save_viz() as viz:
ast = (Tensor.empty(4)+Tensor.empty(4)).schedule_linear().src[0].src[0] ast = (Tensor.empty(4)+Tensor.empty(4)).schedule_linear().src[0].src[0]
prg = do_to_program(ast, Device[Device.DEFAULT].renderer) prg = to_program(ast, Device[Device.DEFAULT].renderer)
lst = viz.list_items() lst = viz.list_items()
self.assertEqual(len(lst), 3) self.assertEqual(len(lst), 3)
self.assertEqual(lst[0]["name"], "Callify 1 Buffer n1") self.assertEqual(lst[0]["name"], "Callify 1 Buffer n1")
self.assertEqual(lst[1]["name"], "Schedule 1 Kernel n1") self.assertEqual(lst[1]["name"], "Schedule 1 Kernel n1")
self.assertEqual(lst[2]["name"], prg.arg.name) self.assertEqual(lst[2]["name"], prg.arg.name)
input_ast = next(viz.get_details(2, 0))["graph"].values()
for u in input_ast:
if u["label"].startswith("PARAM\n"): self.assertEqual(u["addrspace"], addrspace_colors[AddrSpace.GLOBAL])
# schedule graph CALL nodes have a link to jump to codegen # schedule graph CALL nodes have a link to jump to codegen
def test_link_sched_codegen(self): def test_link_sched_codegen(self):
@@ -360,7 +346,7 @@ class TestVizIntegration(unittest.TestCase):
from tinygrad.engine.realize import compile_linear from tinygrad.engine.realize import compile_linear
sched = compile_linear(sched) sched = compile_linear(sched)
with Context(NO_COLOR=0): with Context(NO_COLOR=0):
prgs = [do_to_program(si.src[0], Device[c1.device].renderer).arg.name for si in sched.src] prgs = [to_program(si.src[0], Device[c1.device].renderer).arg.name for si in sched.src]
lst = viz.list_items() lst = viz.list_items()
sched_idx = next(i for i,l in enumerate(lst) if l["name"].startswith("Schedule")) sched_idx = next(i for i,l in enumerate(lst) if l["name"].startswith("Schedule"))
viz_kernel = next(i for i,s in enumerate(lst[sched_idx]["steps"]) if s["name"] == "View Kernel Graph") viz_kernel = next(i for i,s in enumerate(lst[sched_idx]["steps"]) if s["name"] == "View Kernel Graph")
@@ -767,7 +753,7 @@ class TestCfg(unittest.TestCase):
with save_viz() as viz: with save_viz() as viz:
with Context(DEV=f"NULL::{self.arch}"): with Context(DEV=f"NULL::{self.arch}"):
out = Tensor.custom_kernel(Tensor.empty(1), fxn=fxn)[0] out = Tensor.custom_kernel(Tensor.empty(1), fxn=fxn)[0]
_ = do_to_program(out.schedule_linear().src[-1].src[0], Device[out.device].renderer) _ = to_program(out.schedule_linear().src[-1].src[0], Device[out.device].renderer)
codegen_rewrites = next(s for s in viz.list_items() if s["name"] == name) codegen_rewrites = next(s for s in viz.list_items() if s["name"] == name)
disasm = next(s for s in codegen_rewrites["steps"] if s["name"] == "View Disassembly") disasm = next(s for s in codegen_rewrites["steps"] if s["name"] == "View Disassembly")
return get_render(viz.data, disasm["query"]) return get_render(viz.data, disasm["query"])
@@ -1004,9 +990,8 @@ class TestCLI(unittest.TestCase):
def test_dedup(self): def test_dedup(self):
with save_viz() as viz: with save_viz() as viz:
for _ in range(CNT:=4): for _ in range(CNT:=4):
# use kernel names unique to this test Tensor.empty(4, device="NULL").add(1).realize()
Tensor.custom_kernel(Tensor.empty(4, device="NULL"), fxn=lambda _: UOp.sink(arg=KernelInfo("k1_test_viz_dedup")))[0].realize() Tensor.empty(8, device="NULL").add(1).realize()
Tensor.custom_kernel(Tensor.empty(8, device="NULL"), fxn=lambda _: UOp.sink(arg=KernelInfo("k2_test_viz_dedup")))[0].realize()
with write_files(viz) as files, Context(NO_COLOR=1): with write_files(viz) as files, Context(NO_COLOR=1):
name = run_cli(*files, "-s", "NULL")[0]["name"] name = run_cli(*files, "-s", "NULL")[0]["name"]
with Context(DEBUG=3): with Context(DEBUG=3):
+4 -4
View File
@@ -15,10 +15,6 @@ class TestRealizeIsRealized(unittest.TestCase):
t = Tensor.zeros(10).contiguous().realize() t = Tensor.zeros(10).contiguous().realize()
assert t.uop.is_realized assert t.uop.is_realized
def test_ones(self):
t = Tensor.ones(4, 4).realize()
assert t.uop.is_realized
def test_bytes(self): def test_bytes(self):
t = Tensor(b'\x01\x02\x03').realize() t = Tensor(b'\x01\x02\x03').realize()
assert t.uop.is_realized assert t.uop.is_realized
@@ -55,6 +51,10 @@ class TestRealizeIsRealized(unittest.TestCase):
t = Tensor(3.14).realize() t = Tensor(3.14).realize()
assert not t.uop.is_realized assert not t.uop.is_realized
def test_ones_not_realized(self):
t = Tensor.ones(4, 4).realize()
assert not t.uop.is_realized
def test_none_not_realized(self): def test_none_not_realized(self):
t = Tensor(None).realize() t = Tensor(None).realize()
assert not t.uop.is_realized assert not t.uop.is_realized
-4
View File
@@ -65,10 +65,6 @@ class TestTensorData(unittest.TestCase):
assert dat.tolist() == 3 assert dat.tolist() == 3
assert dat.shape == () assert dat.shape == ()
def test_const_dtype_for_uop(self):
self.assertEqual(Tensor.const(dtypes.int8, UOp.const(dtypes.float32, 1.0)).dtype, dtypes.int8)
self.assertEqual(Tensor.const(dtypes.int32, UOp.variable("x", 1, 10).bind(5)).item(), 5)
def test_data_float32(self): def test_data_float32(self):
a = Tensor([[1,2.5],[3,4]], dtype=dtypes.float32) a = Tensor([[1,2.5],[3,4]], dtype=dtypes.float32)
dat = a.data() dat = a.data()
+2 -3
View File
@@ -1,5 +1,5 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from tinygrad.dtype import dtypes, AddrSpace, PtrDType, ImageDType from tinygrad.dtype import dtypes
from tinygrad.uop.ops import UOp, UPat, PatternMatcher, Ops, GroupOp, graph_rewrite, track_rewrites from tinygrad.uop.ops import UOp, UPat, PatternMatcher, Ops, GroupOp, graph_rewrite, track_rewrites
from tinygrad.helpers import VIZ, pluralize, all_int from tinygrad.helpers import VIZ, pluralize, all_int
@@ -176,8 +176,7 @@ def finalize_after(ctx:AllocCtx, x:UOp):
def replace_input_buffer(ctx:AllocCtx, b:UOp): def replace_input_buffer(ctx:AllocCtx, b:UOp):
ctx.replacements.append(b) ctx.replacements.append(b)
return UOp.param(len(ctx.replacements)-1, b.dtype, b.shape, b.device, return UOp.param(len(ctx.replacements)-1, b.dtype, b.shape, b.device,
b._min_max if b.op is Ops.BIND else None, b.src[0].arg[0] if b.op is Ops.BIND else None, b._min_max if b.op is Ops.BIND else None, b.src[0].arg[0] if b.op is Ops.BIND else None)
b.addrspace if isinstance(b.dtype, (PtrDType, ImageDType)) else AddrSpace.GLOBAL)
pm_finalize_call = PatternMatcher([ pm_finalize_call = PatternMatcher([
(UPat(Ops.AFTER, name="x"), finalize_after), (UPat(Ops.AFTER, name="x"), finalize_after),
+1 -1
View File
@@ -91,7 +91,7 @@ def add_gpudims(ctx:Renderer, s:UOp):
subs = {} subs = {}
for r in s_topo: for r in s_topo:
# look for local INDEXes that are not used in the GLOBAL store, then add them as an INVALID # look for local INDEXes that are not used in the GLOBAL store, then add them as an INVALID
if r.op is Ops.STORE and (idx := r.src[0]).src[0].addrspace == AddrSpace.GLOBAL: if r.op is Ops.STORE and (idx := r.src[0]).src[0].ptrdtype.addrspace == AddrSpace.GLOBAL:
missing_locals = [all_ranges[rng] for rng in local_dims if all_ranges[rng] not in idx.ranges] missing_locals = [all_ranges[rng] for rng in local_dims if all_ranges[rng] not in idx.ranges]
if len(missing_locals): if len(missing_locals):
assert len(idx.src) == 2, "index has 2 sources" assert len(idx.src) == 2, "index has 2 sources"
+5 -7
View File
@@ -104,7 +104,7 @@ def fold_expanded_index(midx:UOp):
for grp in grouped_offsets: for grp in grouped_offsets:
# get the index offset for this element. using [0] is okay, because they are the same # get the index offset for this element. using [0] is okay, because they are the same
lidx = midx.src[offsets[grp[0]][0]] lidx = midx.src[offsets[grp[0]][0]]
if len(grp) > 1: lidx = lidx.cast(buf.ptrdtype.base.vec(len(grp)).ptr(size=buf.max_numel(), addrspace=buf.addrspace)) if len(grp) > 1: lidx = lidx.cast(buf.ptrdtype.base.vec(len(grp)).ptr(size=buf.ptrdtype.size, addrspace=buf.ptrdtype.addrspace))
# set the idxs of the output # set the idxs of the output
for i,g in enumerate(grp): for i,g in enumerate(grp):
for oo in offsets[g]: idxs[oo] = global_offset+i for oo in offsets[g]: idxs[oo] = global_offset+i
@@ -113,7 +113,7 @@ def fold_expanded_index(midx:UOp):
global_offset += len(grp) global_offset += len(grp)
assert None not in idxs, f"some idxs are missing {idxs}" assert None not in idxs, f"some idxs are missing {idxs}"
# this base thing is for image, we want the CAT to be a normal pointer # this base thing is for image, we want the CAT to be a normal pointer
post_cat = UOp(Ops.PTRCAT, buf.ptrdtype.base.ptr(size=buf.max_numel(), addrspace=buf.addrspace).vec(global_offset), tuple(ret)) post_cat = UOp(Ops.PTRCAT, buf.ptrdtype.base.ptr(size=buf.ptrdtype.size, addrspace=buf.ptrdtype.addrspace).vec(global_offset), tuple(ret))
return post_cat.gep(tuple(cast(list[int], idxs))) return post_cat.gep(tuple(cast(list[int], idxs)))
def cat_after_store(cat:UOp, data:UOp): def cat_after_store(cat:UOp, data:UOp):
@@ -165,7 +165,7 @@ def split_load_store(ctx:Renderer|None, ls:UOp, idx:UOp):
must_divide = False must_divide = False
elif buf.dtype.base not in (dtypes.float, dtypes.half, *dtypes.fp8s) and not isinstance(buf.dtype, ImageDType): elif buf.dtype.base not in (dtypes.float, dtypes.half, *dtypes.fp8s) and not isinstance(buf.dtype, ImageDType):
pass pass
elif buf.addrspace == AddrSpace.REG: elif buf.ptrdtype.addrspace == AddrSpace.REG:
pass pass
elif isinstance(buf.dtype, ImageDType): elif isinstance(buf.dtype, ImageDType):
lengths = [4] lengths = [4]
@@ -186,7 +186,7 @@ def split_load_store(ctx:Renderer|None, ls:UOp, idx:UOp):
for fold_length in lengths: for fold_length in lengths:
if global_offset+fold_length > sz: continue if global_offset+fold_length > sz: continue
lidx = buf.index((offset + global_offset).valid(mask), ptr=True) lidx = buf.index((offset + global_offset).valid(mask), ptr=True)
if fold_length > 1: lidx = lidx.cast(buf.ptrdtype.base.vec(fold_length).ptr(size=buf.max_numel(), addrspace=buf.addrspace)) if fold_length > 1: lidx = lidx.cast(buf.ptrdtype.base.vec(fold_length).ptr(size=buf.ptrdtype.size, addrspace=buf.ptrdtype.addrspace))
if ls.op is Ops.STORE: ret.append(ls.replace(src=(lidx,ls.src[1].gep(tuple(range(global_offset, global_offset+fold_length)))))) if ls.op is Ops.STORE: ret.append(ls.replace(src=(lidx,ls.src[1].gep(tuple(range(global_offset, global_offset+fold_length))))))
else: ret.append(ls.replace(src=(lidx,)+ls.src[1:], dtype=ls.dtype.scalar().vec(fold_length))) else: ret.append(ls.replace(src=(lidx,)+ls.src[1:], dtype=ls.dtype.scalar().vec(fold_length)))
global_offset += fold_length global_offset += fold_length
@@ -243,9 +243,7 @@ def no_vectorized_alu(alu:UOp):
return UOp(Ops.STACK, alu.dtype, alus) return UOp(Ops.STACK, alu.dtype, alus)
def no_vectorized_buf(buf:UOp): def no_vectorized_buf(buf:UOp):
# TODO: this fails on regs return buf.replace(dtype=buf.ptrdtype.base.scalar().ptr(buf.ptrdtype.size*buf.ptrdtype.count, buf.ptrdtype.addrspace)).cast(buf.dtype)
#assert buf.max_numel() == buf.ptrdtype.size
return buf.replace(dtype=buf.ptrdtype.base.scalar().ptr(buf.ptrdtype.size*buf.ptrdtype.count, buf.addrspace)).cast(buf.dtype)
def no_vectorized_index(buf:UOp, cast:UOp, idx:UOp, bcast:UOp|None=None): def no_vectorized_index(buf:UOp, cast:UOp, idx:UOp, bcast:UOp|None=None):
cnt = cast.dtype.count cnt = cast.dtype.count
+2 -2
View File
@@ -22,7 +22,7 @@ def linearize(sink:UOp) -> list[UOp]:
extra = None extra = None
match u.op: match u.op:
# the order and placement of these defines is important # the order and placement of these defines is important
case Ops.PARAM: priority, extra = -20, u.arg.slot case Ops.PARAM: priority, extra = -20, u.arg
case Ops.DEFINE_VAR: priority, extra = -19, u.arg case Ops.DEFINE_VAR: priority, extra = -19, u.arg
case Ops.DEFINE_REG: priority = -18 case Ops.DEFINE_REG: priority = -18
case Ops.DEFINE_LOCAL: priority = -17 case Ops.DEFINE_LOCAL: priority = -17
@@ -93,4 +93,4 @@ def do_split_ends(e:UOp):
pm_split_ends = PatternMatcher([ pm_split_ends = PatternMatcher([
# split the ends # split the ends
(UPat(Ops.END, name="e"), do_split_ends), (UPat(Ops.END, name="e"), do_split_ends),
]) ])
+3 -3
View File
@@ -95,7 +95,7 @@ class Scheduler:
if (old_sz:=rng.src[0].divides(amount)) is None: if (old_sz:=rng.src[0].divides(amount)) is None:
raise KernelOptError(f"{amount} can't divide {rng.src[0]} in {self.colored_shape()}") raise KernelOptError(f"{amount} can't divide {rng.src[0]} in {self.colored_shape()}")
new_rng = UOp.range(amount, next(self.opt_range), new_type) if input_new_rng is None else input_new_rng new_rng = UOp.range(amount, next(self.opt_range), new_type) if input_new_rng is None else input_new_rng
replaced_rng = rng.replace(src=(old_sz,)) replaced_rng = rng.replace(src=(UOp.const(dtypes.int, old_sz),))
sub_axis = (new_rng * old_sz + replaced_rng) if top else (replaced_rng * amount + new_rng) sub_axis = (new_rng * old_sz + replaced_rng) if top else (replaced_rng * amount + new_rng)
self.ast = self.ast.substitute({rng:sub_axis}, name=f"shift {rng.arg[:-1]} {amount} {str(new_type).split('.')[1].lower()}") self.ast = self.ast.substitute({rng:sub_axis}, name=f"shift {rng.arg[:-1]} {amount} {str(new_type).split('.')[1].lower()}")
return replaced_rng, new_rng return replaced_rng, new_rng
@@ -329,8 +329,8 @@ class Scheduler:
def group_for_reduces(self) -> int: return len(self.axes_of(AxisType.GROUP_REDUCE)) def group_for_reduces(self) -> int: return len(self.axes_of(AxisType.GROUP_REDUCE))
def bufs_from_ast(ast:UOp, dname:str) -> list[Buffer]: def bufs_from_ast(ast:UOp, dname:str) -> list[Buffer]:
glbls = sorted([x for x in ast.backward_slice if x.op is Ops.PARAM], key=lambda x: x.arg.slot) glbls = sorted([x for x in ast.backward_slice if x.op is Ops.PARAM], key=lambda x: x.arg)
return [Buffer(dname, x.max_numel(), x.dtype.base) for x in glbls] return [Buffer(dname, x.ptrdtype.size, x.dtype.base) for x in glbls]
def apply_opts(ast:UOp, ren:Renderer, beam:int=0) -> UOp: def apply_opts(ast:UOp, ren:Renderer, beam:int=0) -> UOp:
if ast.tag is not None: return ast if ast.tag is not None: return ast
+1 -1
View File
@@ -94,7 +94,7 @@ class GraphRunner:
self.runtimes: list[Any|None] = [] self.runtimes: list[Any|None] = []
self.uop_replace: list[list[tuple[int, int]]] = [] self.uop_replace: list[list[tuple[int, int]]] = []
for call in self.linear.src: 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] replace = [(p, b.arg) 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))): for dev_idx, (bufs, device_vars) in enumerate(unwrap_multi(call, resolve_params(call, input_uops))):
self.calls.append((dev_idx, call.src[0], [b.ensure_allocated() for b in bufs], device_vars)) self.calls.append((dev_idx, call.src[0], [b.ensure_allocated() for b in bufs], device_vars))
self.runtimes.append(get_runtime(bufs[0].device, call.src[0]) if call.src[0].op is Ops.PROGRAM else None) self.runtimes.append(get_runtime(bufs[0].device, call.src[0]) if call.src[0].op is Ops.PROGRAM else None)
+2 -2
View File
@@ -137,8 +137,8 @@ class ExecContext:
cache: bool = True cache: bool = True
def _resolve(b:UOp, inputs:tuple[UOp, ...]) -> UOp: def _resolve(b:UOp, inputs:tuple[UOp, ...]) -> UOp:
if b.op in (Ops.SLICE, Ops.MSELECT) and b.src[0].op is Ops.PARAM: return b.replace(src=(inputs[b.src[0].arg.slot], *b.src[1:])) if b.op in (Ops.SLICE, Ops.MSELECT) and b.src[0].op is Ops.PARAM: return b.replace(src=(inputs[b.src[0].arg], *b.src[1:]))
return inputs[b.arg.slot] if b.op is Ops.PARAM else b return inputs[b.arg] if b.op is Ops.PARAM else b
def resolve_params(call:UOp, inputs:tuple[UOp, ...]) -> list[UOp]: return [_resolve(b, inputs) for b in get_call_arg_uops(call)] def resolve_params(call:UOp, inputs:tuple[UOp, ...]) -> list[UOp]: return [_resolve(b, inputs) for b in get_call_arg_uops(call)]
def unwrap_multi(call:UOp, resolved:list[UOp]) -> Iterator[tuple[list[Buffer], dict[str, int]]]: def unwrap_multi(call:UOp, resolved:list[UOp]) -> Iterator[tuple[list[Buffer], dict[str, int]]]:
+5 -6
View File
@@ -16,9 +16,8 @@ def reduce_gradient(ctx:UOp, ret:UOp, op:Ops):
def _compact_params(body:UOp, all_args:tuple[UOp, ...]) -> tuple[UOp, tuple[UOp, ...]]: def _compact_params(body:UOp, all_args:tuple[UOp, ...]) -> tuple[UOp, tuple[UOp, ...]]:
"""Remove unused PARAMs from body and return compacted (body, args).""" """Remove unused PARAMs from body and return compacted (body, args)."""
used = sorted({p.arg.slot: p for p in body.toposort() if p.op is Ops.PARAM}.items()) used = sorted({p.arg: p for p in body.toposort() if p.op is Ops.PARAM}.items())
body = body.substitute({p: p.replace(arg=dataclasses.replace(p.arg, slot=j)) for j,(_, p) in enumerate(used)}, walk=True) return body.substitute({p: p.replace(arg=j) for j,(_, p) in enumerate(used)}, walk=True), tuple(all_args[i] for i,_ in used)
return body, tuple(all_args[i] for i,_ in used)
def call_gradient(ctx:UOp, k:UOp, needed:set[int]) -> tuple[UOp|None, ...]: def call_gradient(ctx:UOp, k:UOp, needed:set[int]) -> tuple[UOp|None, ...]:
fxn, args = k.src[0], k.src[1:] fxn, args = k.src[0], k.src[1:]
@@ -30,7 +29,7 @@ def call_gradient(ctx:UOp, k:UOp, needed:set[int]) -> tuple[UOp|None, ...]:
return (None,) + (k.arg.grad_fxn(*real, call=k) if len(real) > 1 else k.arg.grad_fxn(real[0], k)) return (None,) + (k.arg.grad_fxn(*real, call=k) if len(real) > 1 else k.arg.grad_fxn(real[0], k))
return (None,) + k.arg.grad_fxn(on_dev(ctx, 0), k) return (None,) + k.arg.grad_fxn(on_dev(ctx, 0), k)
assert fxn.op is Ops.TUPLE, f"expected TUPLE body for gradient, got {fxn.op}" assert fxn.op is Ops.TUPLE, f"expected TUPLE body for gradient, got {fxn.op}"
params = {x.arg.slot:x for x in fxn.toposort(enter_calls=False) if x.op == Ops.PARAM} params = {x.arg:x for x in fxn.toposort(enter_calls=False) if x.op == Ops.PARAM}
grad_args = ctx.src grad_args = ctx.src
root_grad = UOp(Ops.TUPLE, src=tuple(UOp(Ops.NOOP) if g.op is Ops.NOOP else root_grad = UOp(Ops.TUPLE, src=tuple(UOp(Ops.NOOP) if g.op is Ops.NOOP else
g if g.base.op is Ops.CONST and g.device is None else g.param_like(len(args)+i) for i,g in enumerate(grad_args))) g if g.base.op is Ops.CONST and g.device is None else g.param_like(len(args)+i) for i,g in enumerate(grad_args)))
@@ -73,8 +72,8 @@ pm_gradient = PatternMatcher([
(UPat(Ops.EXPAND, name="ret"), lambda ctx, ret: (UPat(Ops.EXPAND, name="ret"), lambda ctx, ret:
(ctx.cast(sum_acc_dtype(ctx.dtype))._rop(Ops.ADD, tuple(i for i,(s,n) in enumerate(zip(ret.src[0].shape, ret.shape)) if s!=n)) (ctx.cast(sum_acc_dtype(ctx.dtype))._rop(Ops.ADD, tuple(i for i,(s,n) in enumerate(zip(ret.src[0].shape, ret.shape)) if s!=n))
.cast(ctx.dtype), None)), .cast(ctx.dtype), None)),
(UPat(Ops.PAD, name="ret"), lambda ctx, ret: (ctx.shrink(tuple([(p[0], s+p[0]) for s,p in zip(ret.src[0].shape, ret.marg)])), None, None)), (UPat(Ops.PAD, name="ret"), lambda ctx, ret: (ctx.shrink(tuple([(p[1], s+p[1]) for s,p in zip(ret.src[0].shape, ret.marg)])), None, None)),
(UPat(Ops.SHRINK, name="ret"), lambda ctx, ret: (ctx.pad(tuple([(p[0], s-p[0]-p[1]) for s,p in zip(ret.src[0].shape, ret.marg)])), None, None)), (UPat(Ops.SHRINK, name="ret"), lambda ctx, ret: (ctx.pad(tuple([(p[1], s-p[0]-p[1]) for s,p in zip(ret.src[0].shape, ret.marg)])), None, None)),
(UPat(Ops.PERMUTE, name="ret"), lambda ctx, ret: (ctx.permute(argsort(ret.marg)),)), (UPat(Ops.PERMUTE, name="ret"), lambda ctx, ret: (ctx.permute(argsort(ret.marg)),)),
(UPat(Ops.FLIP, name="ret"), lambda ctx, ret: (ctx.flip([i for i,x in enumerate(ret.marg) if x]),)), (UPat(Ops.FLIP, name="ret"), lambda ctx, ret: (ctx.flip([i for i,x in enumerate(ret.marg) if x]),)),
(UPat(Ops.COPY, name="ret"), lambda ctx, ret: (ctx.copy_to_device(ret.src[0].device), None)), (UPat(Ops.COPY, name="ret"), lambda ctx, ret: (ctx.copy_to_device(ret.src[0].device), None)),
+8 -12
View File
@@ -11,7 +11,7 @@ from tinygrad.dtype import ConstType, DType, DTypeLike, Invalid, InvalidType, Pt
from tinygrad.helpers import all_int, argfix, ceildiv, flatten, flat_to_grouped, make_tuple, prod, resolve_pool_pads, round_up from tinygrad.helpers import all_int, argfix, ceildiv, flatten, flat_to_grouped, make_tuple, prod, resolve_pool_pads, round_up
if TYPE_CHECKING: if TYPE_CHECKING:
from tinygrad.uop.ops import sint, UOp from tinygrad.uop.ops import sint
ReductionStr = Literal["mean", "sum", "none"] ReductionStr = Literal["mean", "sum", "none"]
@@ -23,13 +23,12 @@ class OpMixin(ElementwiseMixin, ReduceMixin):
def const(dtype, b, device=None): raise NotImplementedError("creation helpers are only supported on Tensor and UOp") def const(dtype, b, device=None): raise NotImplementedError("creation helpers are only supported on Tensor and UOp")
@classmethod @classmethod
def full(cls, shape:tuple[sint, ...], fill_value:ConstType|UOp, dtype:DTypeLike|None=None, def full(cls, shape:tuple[sint, ...], fill_value:ConstType, **kwargs) -> Self:
device:str|tuple[str, ...]|None=None, buffer=True) -> Self:
""" """
Creates a tensor with the given shape, filled with the given value. Creates a tensor with the given shape, filled with the given value.
You can pass in `dtype` and `device` keyword arguments to control the data type and device of the tensor. You can pass in `dtype` and `device` keyword arguments to control the data type and device of the tensor.
Pass `buffer=False` to get a broadcast const value instead of a materialized buffer. Additionally, all other keyword arguments are passed to the constructor of the tensor.
```python exec="true" source="above" session="tensor" result="python" ```python exec="true" source="above" session="tensor" result="python"
print(Tensor.full((2, 3), 42).numpy()) print(Tensor.full((2, 3), 42).numpy())
@@ -38,13 +37,11 @@ class OpMixin(ElementwiseMixin, ReduceMixin):
print(Tensor.full((2, 3), False).numpy()) print(Tensor.full((2, 3), False).numpy())
``` ```
""" """
from tinygrad.uop.ops import UOp
new_shape = argfix(shape) new_shape = argfix(shape)
dt = to_dtype(dtype) if dtype is not None else None if not kwargs.pop("buffer", True):
if isinstance(fill_value, UOp): val = cls.const(dt or fill_value.dtype, fill_value) dt = to_dtype(kwargs.pop("dtype", None) or dtypes.from_py(fill_value))
else: val = cls.const(dt or dtypes.from_py(fill_value), fill_value, None if buffer else canonicalize_device(device)) return cls.const(dt, fill_value, canonicalize_device(kwargs.pop("device", None))).reshape((1,)*len(new_shape)).expand(new_shape)
val = val.reshape((1,)*len(new_shape)).expand(new_shape) return cls.unique_const(fill_value, **kwargs).reshape((1,)*len(new_shape)).expand(new_shape)
return val.clone(device=device) if buffer else val
@classmethod @classmethod
def invalids(cls, *shape, **kwargs) -> Self: def invalids(cls, *shape, **kwargs) -> Self:
@@ -55,8 +52,7 @@ class OpMixin(ElementwiseMixin, ReduceMixin):
Eventually Tensor.empty will be replaced by this. Eventually Tensor.empty will be replaced by this.
""" """
new_shape = argfix(*shape) return cls.full(argfix(*shape), Invalid, **kwargs)
return cls.unique_const(Invalid, **kwargs).reshape((1,)*len(new_shape)).expand(new_shape)
@classmethod @classmethod
def zeros(cls, *shape, **kwargs) -> Self: def zeros(cls, *shape, **kwargs) -> Self:
+3 -3
View File
@@ -178,7 +178,7 @@ class MovementMixin:
def pad(self, arg:tuple[tuple[sint, sint] | None, ...]) -> Self: def pad(self, arg:tuple[tuple[sint, sint] | None, ...]) -> Self:
if self.ndim != len(arg): if self.ndim != len(arg):
raise ValueError(f"{self.ndim=} != {len(arg)=}") raise ValueError(f"{self.ndim=} != {len(arg)=}")
ret = self._mop(Ops.PAD, tuple((x[0], s+x[0]+x[1]) if x is not None else (0, s) for x, s in zip(arg, self.shape))) ret = self._mop(Ops.PAD, tuple((s+x[0]+x[1], x[0]) if x is not None else (s, 0) for x, s in zip(arg, self.shape)))
return self if ret.shape == self.shape else ret return self if ret.shape == self.shape else ret
def shrink(self, arg: tuple[tuple[sint, sint] | None, ...]) -> Self: def shrink(self, arg: tuple[tuple[sint, sint] | None, ...]) -> Self:
@@ -200,7 +200,7 @@ class MovementMixin:
""" """
if self.ndim != len(arg): if self.ndim != len(arg):
raise ValueError(f"{self.ndim=} != {len(arg)=}") raise ValueError(f"{self.ndim=} != {len(arg)=}")
ret = self._mop(Ops.SHRINK, arg=[(x[0], x[1]-x[0]) if x is not None else (0, s) for x, s in zip(arg, self.shape)]) ret = self._mop(Ops.SHRINK, arg=[(x[1]-x[0], x[0]) if x is not None else (s, 0) for x, s in zip(arg, self.shape)])
return self if ret.shape == self.shape else ret return self if ret.shape == self.shape else ret
def permute(self, order, *args) -> Self: def permute(self, order, *args) -> Self:
@@ -251,7 +251,7 @@ class MovementMixin:
return self.shrink(tuple([None if ns is None else (0, ns) for ns in argfix(shape, *args)])) return self.shrink(tuple([None if ns is None else (0, ns) for ns in argfix(shape, *args)]))
def pad_to(self, shape, *args) -> Self: def pad_to(self, shape, *args) -> Self:
return self._mop(Ops.PAD, tuple((0, s if ns is None else ns) for s,ns in zip(self.shape, argfix(shape, *args), strict=True))) return self._mop(Ops.PAD, tuple((s if ns is None else ns, 0) for s,ns in zip(self.shape, argfix(shape, *args), strict=True)))
def view(self, shape, *args) -> Self: def view(self, shape, *args) -> Self:
"""`.view` is an alias for `.reshape`.""" """`.view` is an alias for `.reshape`."""
+1 -1
View File
@@ -1000,7 +1000,7 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
if align_corners: return Tensor.linspace(-1, 1, steps, device=theta.device) if align_corners: return Tensor.linspace(-1, 1, steps, device=theta.device)
return Tensor.linspace(-1+1/steps, 1-1/steps, steps, device=theta.device) return Tensor.linspace(-1+1/steps, 1-1/steps, steps, device=theta.device)
grids = Tensor.meshgrid(*(generate_grid(d) for d in spatial_dims)) grids = Tensor.meshgrid(*(generate_grid(d) for d in spatial_dims))
base_grid = Tensor.stack(*reversed(grids), grids[0].const_like(1), dim=-1) base_grid = Tensor.stack(*reversed(grids), Tensor.ones_like(grids[0], device=theta.device), dim=-1)
base_grid = base_grid.reshape(1, prod(spatial_dims), len(grids)+1).expand(N, -1, -1) base_grid = base_grid.reshape(1, prod(spatial_dims), len(grids)+1).expand(N, -1, -1)
return (base_grid @ theta.transpose(1, 2)).reshape(N, *spatial_dims, -1) return (base_grid @ theta.transpose(1, 2)).reshape(N, *spatial_dims, -1)
+8 -7
View File
@@ -178,8 +178,8 @@ class CStyleLanguage(Renderer):
continue continue
if u.op in (Ops.PARAM, Ops.DEFINE_VAR): if u.op in (Ops.PARAM, Ops.DEFINE_VAR):
if u.op is not Ops.PARAM: r[u] = u.arg[0] if u.op is not Ops.PARAM: r[u] = u.arg[0]
elif isinstance(u.dtype, ImageDType): r[u] = f"data{u.arg.slot}_{u.dtype.shape[0]}x{u.dtype.shape[1]}" elif isinstance(u.dtype, ImageDType): r[u] = f"data{u.arg}_{u.dtype.shape[0]}x{u.dtype.shape[1]}"
else: r[u] = f"data{u.arg.slot}_{sz}" if (sz:=u.max_numel()) > 0 else f"data{u.arg.slot}" else: r[u] = f"data{u.arg}_{sz}" if (sz:=u.ptrdtype.size) > 0 else f"data{u.arg}"
bufs[u] = (r[u], (u.dtype, u in writable_params)) bufs[u] = (r[u], (u.dtype, u in writable_params))
continue continue
@@ -198,7 +198,7 @@ class CStyleLanguage(Renderer):
if u.op in {Ops.ENDIF, Ops.END}: depth -= 1 if u.op in {Ops.ENDIF, Ops.END}: depth -= 1
if (u.op is not Ops.CAST or u.dtype.vcount == 1) and (u.op in {Ops.CONST, Ops.GEP, Ops.INDEX, Ops.CUSTOMI} or \ if (u.op is not Ops.CAST or u.dtype.vcount == 1) and (u.op in {Ops.CONST, Ops.GEP, Ops.INDEX, Ops.CUSTOMI} or \
(u.op is Ops.LOAD and u.src[0].addrspace == AddrSpace.REG) or \ (u.op is Ops.LOAD and u.src[0].ptrdtype.addrspace == AddrSpace.REG) or \
(u.op is Ops.CAST and isinstance(u.dtype, PtrDType)) or \ (u.op is Ops.CAST and isinstance(u.dtype, PtrDType)) or \
(u.op in {Ops.STACK, *(GroupOp.ALU-{Ops.WHERE}), Ops.CAST, Ops.BITCAST} and child_count[u] == 1 and not getenv("EXPAND_SSA"))): (u.op in {Ops.STACK, *(GroupOp.ALU-{Ops.WHERE}), Ops.CAST, Ops.BITCAST} and child_count[u] == 1 and not getenv("EXPAND_SSA"))):
r[u] = l r[u] = l
@@ -275,11 +275,12 @@ class ClangRenderer(CStyleLanguage):
def supported_dtypes(self): def supported_dtypes(self):
return {d for d in super().supported_dtypes() if (d != dtypes.bfloat16 or self.target.arch.startswith(("x86", "arm"))) and d not in dtypes.fp8s} return {d for d in super().supported_dtypes() if (d != dtypes.bfloat16 or self.target.arch.startswith(("x86", "arm"))) and d not in dtypes.fp8s}
class ClangJITRenderer(ClangRenderer):
def __init__(self, target:Target): def __init__(self, target:Target):
super().__init__(target) super().__init__(target)
from tinygrad.runtime.support.compiler_cpu import ClangCompiler from tinygrad.runtime.support.compiler_cpu import ClangJITCompiler
if "AMX" in target.arch: self.tensor_cores = tc.amx if "AMX" in target.arch: self.tensor_cores = tc.amx
self.compiler = ClangCompiler([x for x in target.arch.split(",") if x != "AMX"]) self.compiler = ClangJITCompiler([x for x in target.arch.split(",") if x != "AMX"])
class OpenCLRenderer(CStyleLanguage): class OpenCLRenderer(CStyleLanguage):
has_aux = True has_aux = True
@@ -319,8 +320,8 @@ class OpenCLRenderer(CStyleLanguage):
def aux(self, uops:list[UOp]): def aux(self, uops:list[UOp]):
arg_dtypes:list[list[tuple[int, DType]]] = [] arg_dtypes:list[list[tuple[int, DType]]] = []
for i,u in enumerate(u for u in uops if u.op is Ops.PARAM): for i,u in enumerate(u for u in uops if u.op is Ops.PARAM):
while len(arg_dtypes) <= u.arg.slot: arg_dtypes.append([]) if len(arg_dtypes) >= u.arg: arg_dtypes.append([])
arg_dtypes[u.arg.slot].append((i, u.dtype)) arg_dtypes[u.arg].append((i, u.dtype))
return tuple(tuple(a) for a in arg_dtypes), return tuple(tuple(a) for a in arg_dtypes),
def supported_dtypes(self): return {d for d in super().supported_dtypes() def supported_dtypes(self): return {d for d in super().supported_dtypes()
+1 -1
View File
@@ -164,7 +164,7 @@ class LLVMRenderer(Renderer):
if u.arg is not None: name = u.arg.function_name if u.arg is not None: name = u.arg.function_name
continue continue
if u.op in (Ops.PARAM, Ops.DEFINE_VAR): if u.op in (Ops.PARAM, Ops.DEFINE_VAR):
r[u] = f"%data{u.arg.slot}" if u.op is Ops.PARAM else f"%{u.expr}" r[u] = f"%data{u.arg}" if u.op is Ops.PARAM else f"%{u.expr}"
args.append((r[u], u.dtype)) args.append((r[u], u.dtype))
elif u.op in (Ops.DEFINE_LOCAL, Ops.DEFINE_REG): elif u.op in (Ops.DEFINE_LOCAL, Ops.DEFINE_REG):
r[u] = f"%{'local' if u.op is Ops.DEFINE_LOCAL else 'reg'}_{str(u.arg).replace('(', '').replace(')', '').replace(',', '_').replace(' ', '')}" r[u] = f"%{'local' if u.op is Ops.DEFINE_LOCAL else 'reg'}_{str(u.arg).replace('(', '').replace(')', '').replace(',', '_').replace(' ', '')}"
+3 -3
View File
@@ -149,12 +149,12 @@ class NIRRenderer(Renderer):
(UPat(Ops.DEFINE_VAR, name="x"), lambda ctx,x: ctx.param(ctx.b, x, 4)), (UPat(Ops.DEFINE_VAR, name="x"), lambda ctx,x: ctx.param(ctx.b, x, 4)),
(UPat(Ops.SPECIAL, name="x"), lambda ctx,x: nchannel(ctx.b, {'g':ngid, 'l':nlid, 'i': nid}[x.arg[0]](ctx.b), int(x.arg[-1]))), (UPat(Ops.SPECIAL, name="x"), lambda ctx,x: nchannel(ctx.b, {'g':ngid, 'l':nlid, 'i': nid}[x.arg[0]](ctx.b), int(x.arg[-1]))),
(UPat(Ops.STORE, src=(UPat(Ops.INDEX, src=(UPat.var("buf"),UPat.var("off"))).or_casted(), UPat.var("val"))), (UPat(Ops.STORE, src=(UPat(Ops.INDEX, src=(UPat.var("buf"),UPat.var("off"))).or_casted(), UPat.var("val"))),
lambda ctx,buf,off,val: nstore(ctx.b, buf.addrspace, nidx(ctx.b, ctx.r[buf], ctx.r[off], buf.dtype), ctx.r[val], val.dtype)), lambda ctx,buf,off,val: nstore(ctx.b, buf.ptrdtype.addrspace, nidx(ctx.b, ctx.r[buf], ctx.r[off], buf.dtype), ctx.r[val], val.dtype)),
(UPat(Ops.LOAD, src=(UPat(Ops.INDEX, src=(UPat.var("buf"), UPat.var("off"))).or_casted(), UPat.var("alt"), UPat.var("gate")), name="x"), (UPat(Ops.LOAD, src=(UPat(Ops.INDEX, src=(UPat.var("buf"), UPat.var("off"))).or_casted(), UPat.var("alt"), UPat.var("gate")), name="x"),
lambda ctx,x,buf,off,alt,gate: if_phi(ctx.b, ctx.r[gate], lambda ctx,x,buf,off,alt,gate: if_phi(ctx.b, ctx.r[gate],
lambda: nload(ctx.b, buf.addrspace, nidx(ctx.b, ctx.r[buf], ctx.r[off], buf.dtype, ctx.r[gate]), x.dtype), lambda: ctx.r[alt])), lambda: nload(ctx.b, buf.ptrdtype.addrspace, nidx(ctx.b, ctx.r[buf], ctx.r[off], buf.dtype, ctx.r[gate]), x.dtype), lambda: ctx.r[alt])),
(UPat(Ops.LOAD, src=(UPat(Ops.INDEX, src=(UPat.var("buf"), UPat.var("off"))).or_casted(),), name="x"), (UPat(Ops.LOAD, src=(UPat(Ops.INDEX, src=(UPat.var("buf"), UPat.var("off"))).or_casted(),), name="x"),
lambda ctx,x,buf,off: nload(ctx.b, buf.addrspace, nidx(ctx.b, ctx.r[buf], ctx.r[off], buf.dtype), x.dtype)), lambda ctx,x,buf,off: nload(ctx.b, buf.ptrdtype.addrspace, nidx(ctx.b, ctx.r[buf], ctx.r[off], buf.dtype), x.dtype)),
(UPat(Ops.STACK, name="x"), lambda ctx,x: nalu(ctx.b, f"vec{x.dtype.count}", *[ctx.r[src] for src in x.src])), (UPat(Ops.STACK, name="x"), lambda ctx,x: nalu(ctx.b, f"vec{x.dtype.count}", *[ctx.r[src] for src in x.src])),
(UPat(GroupOp.ALU, name="x"), lambda ctx,x: nalu(ctx.b, aop[x.src[0].dtype.scalar()][x.op], *[ctx.r[src] for src in x.src])), (UPat(GroupOp.ALU, name="x"), lambda ctx,x: nalu(ctx.b, aop[x.src[0].dtype.scalar()][x.op], *[ctx.r[src] for src in x.src])),
(UPat(Ops.CAST, name="x"), lambda ctx,x: ncast(ctx.b, ctx.r[x.src[0]], x.src[0].dtype, x.dtype)), (UPat(Ops.CAST, name="x"), lambda ctx,x: ncast(ctx.b, ctx.r[x.src[0]], x.src[0].dtype, x.dtype)),
+4 -4
View File
@@ -63,7 +63,7 @@ def mem_type(x:UOp) -> str:
match x.op: match x.op:
case Ops.AFTER: return mem_type(x.src[0]) case Ops.AFTER: return mem_type(x.src[0])
case Ops.DEFINE_LOCAL: return 'shared' case Ops.DEFINE_LOCAL: return 'shared'
case Ops.PARAM: return 'shared' if x.addrspace == AddrSpace.LOCAL else 'global' case Ops.PARAM: return 'global'
case _: raise RuntimeError(f"{x.op} needs to be memory") case _: raise RuntimeError(f"{x.op} needs to be memory")
def render_wmma(ctx: "PTXRenderer", wmma: UOp): def render_wmma(ctx: "PTXRenderer", wmma: UOp):
@@ -90,7 +90,7 @@ string_rewrite = PatternMatcher([
(UPat.cvar("x", dtypes.bool), lambda ctx, x: f"setp.ne.s16 {ctx.r[x]}, {render_val(x.arg, x.dtype)}, 0;"), (UPat.cvar("x", dtypes.bool), lambda ctx, x: f"setp.ne.s16 {ctx.r[x]}, {render_val(x.arg, x.dtype)}, 0;"),
(UPat.cvar("x"), lambda ctx, x: f"mov.b{ctx.types[x.dtype][1:]} {ctx.r[x]}, {render_val(x.arg, x.dtype)};"), (UPat.cvar("x"), lambda ctx, x: f"mov.b{ctx.types[x.dtype][1:]} {ctx.r[x]}, {render_val(x.arg, x.dtype)};"),
(UPat(Ops.SPECIAL, name="x"), lambda ctx,x: f"mov.u32 %{x.arg}, %{'ctaid' if x.arg[0] == 'g' else 'tid'}.{chr(120+int(x.arg[-1]))};"), (UPat(Ops.SPECIAL, name="x"), lambda ctx,x: f"mov.u32 %{x.arg}, %{'ctaid' if x.arg[0] == 'g' else 'tid'}.{chr(120+int(x.arg[-1]))};"),
(UPat(Ops.PARAM, name="x"), lambda ctx, x: f"ld.param.{ctx.types[dtypes.ulong]} {ctx.r[x]}, [data{x.arg.slot}+0];"), (UPat(Ops.PARAM, name="x"), lambda ctx, x: f"ld.param.{ctx.types[dtypes.ulong]} {ctx.r[x]}, [data{x.arg}+0];"),
(UPat((Ops.CMPLT, Ops.CMPNE, Ops.CMPEQ), name="x", allow_any_len=True, src=(UPat.var("src0"),)), (UPat((Ops.CMPLT, Ops.CMPNE, Ops.CMPEQ), name="x", allow_any_len=True, src=(UPat.var("src0"),)),
lambda ctx, x, src0: ctx.code_for_op[x.op](ctx.r[x], *[ctx.r[v] for v in x.src], src0.dtype, ctx.types[src0.dtype])), lambda ctx, x, src0: ctx.code_for_op[x.op](ctx.r[x], *[ctx.r[v] for v in x.src], src0.dtype, ctx.types[src0.dtype])),
(UPat(GroupOp.ALU, name="x"), lambda ctx, x: ctx.code_for_op[x.op](ctx.r[x], *[ctx.r[v] for v in x.src], x.dtype, ctx.types[x.dtype])), (UPat(GroupOp.ALU, name="x"), lambda ctx, x: ctx.code_for_op[x.op](ctx.r[x], *[ctx.r[v] for v in x.src], x.dtype, ctx.types[x.dtype])),
@@ -202,7 +202,7 @@ class PTXRenderer(Renderer):
r[u] = r[u.src[0]] r[u] = r[u.src[0]]
continue continue
if u.op is Ops.DEFINE_REG: if u.op is Ops.DEFINE_REG:
r[u] = [ssa("reg", u, self.types[u.dtype.base.scalar()]) for _ in range(u.max_numel())] r[u] = [ssa("reg", u, self.types[u.dtype.base.scalar()]) for _ in range(u.ptrdtype.size)]
continue continue
if u.op in {Ops.INDEX, Ops.LOAD, Ops.STORE} and isinstance(u.src[0].dtype, PtrDType) and u.src[0].dtype.addrspace == AddrSpace.REG: if u.op in {Ops.INDEX, Ops.LOAD, Ops.STORE} and isinstance(u.src[0].dtype, PtrDType) and u.src[0].dtype.addrspace == AddrSpace.REG:
if u.op is Ops.INDEX: if u.op is Ops.INDEX:
@@ -219,7 +219,7 @@ class PTXRenderer(Renderer):
elif u.op is Ops.DEFINE_VAR: bufs.append((u.expr, u.dtype)) elif u.op is Ops.DEFINE_VAR: bufs.append((u.expr, u.dtype))
elif u.op is Ops.LOAD: elif u.op is Ops.LOAD:
r[u] = [ssa('val', dtype=self.types[u.dtype.scalar()]) for _ in range(u.dtype.count)] if u.dtype.count > 1 else ssa('val', u) r[u] = [ssa('val', dtype=self.types[u.dtype.scalar()]) for _ in range(u.dtype.count)] if u.dtype.count > 1 else ssa('val', u)
elif u.op is Ops.PARAM: bufs.append((f"data{u.arg.slot}", u.dtype)) elif u.op is Ops.PARAM: bufs.append((f"data{u.arg}", u.dtype))
elif u.op is Ops.WMMA: elif u.op is Ops.WMMA:
# registers for packing/unpacking input and acc # registers for packing/unpacking input and acc
self.wmma_r = [[ssa("wmma_in", dtype="b32") for _ in range(0, len(r[u.src[0]]), 4 // u.src[0].dtype.scalar().itemsize)], self.wmma_r = [[ssa("wmma_in", dtype="b32") for _ in range(0, len(r[u.src[0]]), 4 // u.src[0].dtype.scalar().itemsize)],
+2 -2
View File
@@ -4,7 +4,7 @@ from tinygrad.helpers import to_mv, OSX, WIN, mv_address, suppress_finalizing, u
from tinygrad.device import BufferSpec from tinygrad.device import BufferSpec
from tinygrad.runtime.support.hcq import HCQCompiled, HCQAllocator, HCQBuffer, HWQueue, HCQArgsState, HCQSignal, HCQProgram, MMIOInterface from tinygrad.runtime.support.hcq import HCQCompiled, HCQAllocator, HCQBuffer, HWQueue, HCQArgsState, HCQSignal, HCQProgram, MMIOInterface
from tinygrad.runtime.support.hcq import CLikeArgsState from tinygrad.runtime.support.hcq import CLikeArgsState
from tinygrad.renderer.cstyle import ClangRenderer from tinygrad.renderer.cstyle import ClangJITRenderer
from tinygrad.renderer.llvmir import CPULLVMRenderer from tinygrad.renderer.llvmir import CPULLVMRenderer
from tinygrad.renderer.nir import LVPRenderer from tinygrad.renderer.nir import LVPRenderer
from tinygrad.renderer.isa.x86 import X86Renderer from tinygrad.renderer.isa.x86 import X86Renderer
@@ -138,5 +138,5 @@ class CPUDevice(HCQCompiled):
def __init__(self, device:str=""): def __init__(self, device:str=""):
self.tasks:queue.Queue = queue.Queue() self.tasks:queue.Queue = queue.Queue()
CPUWorker(self, self.tasks, thread_id=0).start() CPUWorker(self, self.tasks, thread_id=0).start()
super().__init__(device, CPUAllocator(self), [ClangRenderer, CPULLVMRenderer, LVPRenderer, X86Renderer], functools.partial(CPUProgram, self), super().__init__(device, CPUAllocator(self), [ClangJITRenderer, CPULLVMRenderer, LVPRenderer, X86Renderer], functools.partial(CPUProgram, self),
CPUSignal, CPUComputeQueue, arch={'amd64':'x86_64', 'aarch64':'arm64'}.get(m:=platform.machine().lower(), m)+",native") CPUSignal, CPUComputeQueue, arch={'amd64':'x86_64', 'aarch64':'arm64'}.get(m:=platform.machine().lower(), m)+",native")
+74 -72
View File
@@ -41,42 +41,41 @@ def generic_wmma_helper(inp, warp_size, WARP_THREADS, K, NUM_A, NUM_B, NUM_C, a_
class PythonProgram: class PythonProgram:
def __init__(self, name:str, lib:bytes, **kwargs): def __init__(self, name:str, lib:bytes, **kwargs):
self.uops: list[UOp] = pickle.loads(lib) self.uops: list[tuple[Ops, DType, list[int], Any]] = pickle.loads(lib)
self.uop_to_index: dict[UOp, int] = {u:i for i,u in enumerate(self.uops)}
self.loop_ends: dict[UOp, int] = {u.src[1]:i for i, u in enumerate(self.uops) if u.op == Ops.END}
def __call__(self, *bufs, global_size:tuple[int,int,int]=(1,1,1), local_size:tuple[int,int,int]=(1,1,1), vals:tuple[int, ...]=(), wait=False, **kw): def __call__(self, *bufs, global_size:tuple[int,int,int]=(1,1,1), local_size:tuple[int,int,int]=(1,1,1), vals:tuple[int, ...]=(), wait=False, **kw):
st = time.perf_counter() st = time.perf_counter()
warp = list(itertools.product(*[range(x) for x in local_size[::-1]])) warp = list(itertools.product(*[range(x) for x in local_size[::-1]]))
warp_size = len(warp) warp_size = len(warp)
void_ops = {Ops.END, Ops.BARRIER, Ops.IF, Ops.ENDIF, Ops.SINK, Ops.NOOP, Ops.GROUP, Ops.STORE} void_ops = {Ops.END, Ops.BARRIER, Ops.IF, Ops.ENDIF, Ops.SINK, Ops.NOOP, Ops.GROUP, Ops.STORE}
loop_ends: dict[int, int] = {srcs[1]:i for i, (uop, _, srcs, _) in enumerate(self.uops) if uop == Ops.END}
for idxs in itertools.product(*[range(x) for x in global_size[::-1]]): for idxs in itertools.product(*[range(x) for x in global_size[::-1]]):
values: dict[UOp, Any] = {} values: dict[int, Any] = {}
pbufs: list[memoryview] = list(bufs) pbufs: list[memoryview] = list(bufs)
pvals: list[int] = list(vals) pvals: list[int] = list(vals)
exec_masks = [[True] * warp_size] exec_masks = [[True] * warp_size]
i = 0 i = 0
while i < len(self.uops): while i < len(self.uops):
u = self.uops[i] uop, dtype, srcs, arg = self.uops[i]
src_values = [values[v] for v in u.src if v.op not in void_ops] src_values = [values[v] for v in srcs if self.uops[v][0] not in void_ops]
src_dtypes = [v.dtype for v in u.src if v.op not in void_ops] src_dtypes = [self.uops[v][1] for v in srcs if self.uops[v][0] not in void_ops]
if getenv("TRACE"): print(i, u.op, u.dtype, u.arg, src_values, src_dtypes) if getenv("TRACE"): print(i, uop, dtype, arg, src_values, src_dtypes)
if u.op is Ops.END: if uop is Ops.END:
i = self.uop_to_index[u.src[1]] i = srcs[1]
continue continue
if u.op is Ops.IF: if uop is Ops.IF:
exec_masks.append([x and y for x,y in zip(exec_masks[-1], src_values[0])]) exec_masks.append([x and y for x,y in zip(exec_masks[-1], src_values[0])])
i += 1 i += 1
continue continue
if u.op is Ops.ENDIF: if uop is Ops.ENDIF:
exec_masks.pop() exec_masks.pop()
i += 1 i += 1
continue continue
if u.op in (Ops.BARRIER, Ops.SINK, Ops.NOOP, Ops.GROUP): if uop in (Ops.BARRIER, Ops.SINK, Ops.NOOP, Ops.GROUP):
# in the python emulator, the warp is always in sync # in the python emulator, the warp is always in sync
i += 1 i += 1
continue continue
assert u.dtype is not None, f"{u.op} is missing a dtype" assert dtype is not None, f"{uop} is missing a dtype"
if u.op is Ops.STORE: if uop is Ops.STORE:
assert len(src_values) == 2, f"STORE must be lowered to 2 srcs, got {len(src_values)}" assert len(src_values) == 2, f"STORE must be lowered to 2 srcs, got {len(src_values)}"
store_gate = exec_masks[-1] store_gate = exec_masks[-1]
for j,val in enumerate(src_values[1] if src_dtypes[1].count > 1 else [src_values[1]]): for j,val in enumerate(src_values[1] if src_dtypes[1].count > 1 else [src_values[1]]):
@@ -84,25 +83,25 @@ class PythonProgram:
if g: _store(m, o+j, v, src_dtypes[1].scalar()) if g: _store(m, o+j, v, src_dtypes[1].scalar())
i += 1 i += 1
continue continue
if u.op is Ops.AFTER: values[u] = src_values[0] if uop is Ops.AFTER: values[i] = src_values[0]
elif u.op in {Ops.PARAM, Ops.DEFINE_LOCAL, Ops.DEFINE_REG}: elif uop in {Ops.PARAM, Ops.DEFINE_LOCAL, Ops.DEFINE_REG}:
assert isinstance(u.dtype, PtrDType), u.dtype assert isinstance(dtype, PtrDType), dtype
storage_fmt = storage_fmt_for_dtype(u.dtype.base.scalar()) storage_fmt = storage_fmt_for_dtype(dtype.base.scalar())
if storage_fmt is None: raise RuntimeError(f"dtype={u.dtype} is not supported") if storage_fmt is None: raise RuntimeError(f"{dtype=} is not supported")
if TYPE_CHECKING or sys.version_info < (3, 12): assert storage_fmt != "e" if TYPE_CHECKING or sys.version_info < (3, 12): assert storage_fmt != "e"
if u.op is Ops.DEFINE_REG: if uop is Ops.DEFINE_REG:
# REGs are per thread # REGs are per thread
values[u] = [memoryview(bytearray(u.dtype.size*u.dtype.itemsize)).cast(storage_fmt) for _ in range(warp_size)] values[i] = [memoryview(bytearray(dtype.size*dtype.itemsize)).cast(storage_fmt) for _ in range(warp_size)]
else: else:
buf = memoryview(bytearray(u.dtype.size*u.dtype.itemsize)) if u.op is not Ops.PARAM else pbufs.pop(0) buf = memoryview(bytearray(dtype.size*dtype.itemsize)) if uop is not Ops.PARAM else pbufs.pop(0)
values[u] = [buf.cast(storage_fmt)] * warp_size values[i] = [buf.cast(storage_fmt)] * warp_size
elif u.op is Ops.DEFINE_VAR: elif uop is Ops.DEFINE_VAR:
values[u] = [pvals.pop(0)] * warp_size values[i] = [pvals.pop(0)] * warp_size
elif u.op is Ops.SPECIAL: elif uop is Ops.SPECIAL:
if u.arg[0] == 'g': values[u] = [idxs[2-int(u.arg[-1])]] * warp_size if arg[0] == 'g': values[i] = [idxs[2-int(arg[-1])]] * warp_size
elif u.arg[0] == 'l': values[u] = [x[2-int(u.arg[-1])] for x in warp] elif arg[0] == 'l': values[i] = [x[2-int(arg[-1])] for x in warp]
elif u.op is Ops.CONST: values[u] = [u.arg] * warp_size elif uop is Ops.CONST: values[i] = [arg] * warp_size
elif u.op is Ops.INDEX: elif uop is Ops.INDEX:
ret:list = [] ret:list = []
if isinstance(src_dtypes[0], ImageDType): if isinstance(src_dtypes[0], ImageDType):
assert len(src_values) == 3, "image index must be 3 srcs" assert len(src_values) == 3, "image index must be 3 srcs"
@@ -112,33 +111,33 @@ class PythonProgram:
else: else:
assert len(src_values) == 2, "non-image index must be 2 srcs" assert len(src_values) == 2, "non-image index must be 2 srcs"
for m,o in zip(*src_values): ret.append((m,o)) for m,o in zip(*src_values): ret.append((m,o))
values[u] = ret values[i] = ret
elif u.op is Ops.CAST and isinstance(u.dtype, PtrDType): elif uop is Ops.CAST and isinstance(dtype, PtrDType):
values[u] = src_values[0] values[i] = src_values[0]
elif u.op is Ops.RANGE: elif uop is Ops.RANGE:
if u not in values: values[u] = [0] * warp_size if i not in values: values[i] = [0] * warp_size
else: else:
for j in range(len(values[u])): for j in range(len(values[i])):
values[u][j] += 1 values[i][j] += 1
if values[u][0] == src_values[0][0]: if values[i][0] == src_values[0][0]:
del values[u] del values[i]
i = self.loop_ends[u] + 1 i = loop_ends[i] + 1
continue continue
elif u.op is Ops.STACK: values[u] = src_values elif uop is Ops.STACK: values[i] = src_values
elif u.op is Ops.BITCAST: values[u] = [bitcast(x, src_dtypes[0], u.dtype) for x in src_values[0]] elif uop is Ops.BITCAST: values[i] = [bitcast(x, src_dtypes[0], dtype) for x in src_values[0]]
elif u.op is Ops.CAST: elif uop is Ops.CAST:
values[u] = [truncate.get(u.dtype, lambda dt: dt)(u.dtype.const(x)) for x in src_values[0]] values[i] = [truncate.get(dtype, lambda dt: dt)(dtype.const(x)) for x in src_values[0]]
elif u.op is Ops.LOAD: elif uop is Ops.LOAD:
if u.dtype.count > 1: if dtype.count > 1:
values[u] = [load([src_values[k][j] if k != 0 and src_dtypes[k].count > 1 else src_values[k] \ values[i] = [load([src_values[i][j] if i != 0 and src_dtypes[i].count > 1 else src_values[i] \
for k in range(len(src_values))], j, u.dtype.scalar()) for j in range(u.dtype.count)] for i in range(len(src_values))], j, dtype.scalar()) for j in range(dtype.count)]
else: else:
values[u] = load(src_values, 0, u.dtype) values[i] = load(src_values, 0, dtype)
elif u.op is Ops.GEP: values[u] = src_values[0][get_single_element(u.arg)] elif uop is Ops.GEP: values[i] = src_values[0][get_single_element(arg)]
elif u.op is Ops.WMMA: elif uop is Ops.WMMA:
first_src_dtype = u.src[0].dtype first_src_dtype = self.uops[srcs[0]][1]
assert isinstance(first_src_dtype, DType) # mypy assert isinstance(first_src_dtype, DType) # mypy
dims, dtype_in, device, threads = u.arg[1], first_src_dtype.scalar(), u.arg[4], u.arg[5] dims, dtype_in, device, threads = arg[1], first_src_dtype.scalar(), arg[4], arg[5]
wmma_helper = functools.partial(generic_wmma_helper, src_values, warp_size) wmma_helper = functools.partial(generic_wmma_helper, src_values, warp_size)
# TODO: refactor these to a shared TensorCoreLayout # TODO: refactor these to a shared TensorCoreLayout
if device == "METAL": if device == "METAL":
@@ -146,17 +145,17 @@ class PythonProgram:
def a_b_elem(x, i, j, goff): return x[(i%2)][goff+(i//2)%2+(j%4)*2+(i//4)*8+(j//4)*16] def a_b_elem(x, i, j, goff): return x[(i%2)][goff+(i//2)%2+(j%4)*2+(i//4)*8+(j//4)*16]
# (i, j), C, D (2 elements on 32 threads): row major same as A/B # (i, j), C, D (2 elements on 32 threads): row major same as A/B
def c_map(lane, elem): return (elem + ((lane%2)*2) + ((lane//8)%2)*4, ((lane//2)%4) + (lane//16)*4) def c_map(lane, elem): return (elem + ((lane%2)*2) + ((lane//8)%2)*4, ((lane//2)%4) + (lane//16)*4)
values[u] = wmma_helper(32, 8, 2, 2, 2, a_b_elem, a_b_elem, c_map) values[i] = wmma_helper(32, 8, 2, 2, 2, a_b_elem, a_b_elem, c_map)
elif device == "AMD" and threads == 64: elif device == "AMD" and threads == 64:
def a_elem(x, k, row, goff): return x[k%(dims[2]//4)][goff + (k//(dims[2]//4))*16 + row] def a_elem(x, k, row, goff): return x[k%(dims[2]//4)][goff + (k//(dims[2]//4))*16 + row]
def b_elem(x, col, k, goff): return a_elem(x, k, col, goff) # pylint: disable=arguments-out-of-order def b_elem(x, col, k, goff): return a_elem(x, k, col, goff) # pylint: disable=arguments-out-of-order
def c_map(lane, elem): return (lane%16, (lane//16)*4 + elem) def c_map(lane, elem): return (lane%16, (lane//16)*4 + elem)
values[u] = wmma_helper(64, dims[2], len(src_values[0]), len(src_values[1]), len(src_values[2]), a_elem, b_elem, c_map) values[i] = wmma_helper(64, dims[2], len(src_values[0]), len(src_values[1]), len(src_values[2]), a_elem, b_elem, c_map)
elif device == "AMD" and len(src_values[0]) == 8: # RDNA4 elif device == "AMD" and len(src_values[0]) == 8: # RDNA4
def a_elem(x, k, row, goff): return x[k - [0, 4, 4, 8][k//4]][goff + row + [0, 16, 0, 16][k//4]] def a_elem(x, k, row, goff): return x[k - [0, 4, 4, 8][k//4]][goff + row + [0, 16, 0, 16][k//4]]
def b_elem(x, col, k, goff): return a_elem(x, k, col, goff) def b_elem(x, col, k, goff): return a_elem(x, k, col, goff)
def c_map(lane, elem): return (lane%16, (lane//16)*8 + elem) def c_map(lane, elem): return (lane%16, (lane//16)*8 + elem)
values[u] = wmma_helper(32, 16, 8, 8, 8, a_elem, b_elem, c_map) values[i] = wmma_helper(32, 16, 8, 8, 8, a_elem, b_elem, c_map)
elif device == "AMD": elif device == "AMD":
# A (16 elements on 32 threads): col major, lane 16-32 == lane 0-15 # A (16 elements on 32 threads): col major, lane 16-32 == lane 0-15
def a_elem(x, k, row, goff): def a_elem(x, k, row, goff):
@@ -165,7 +164,7 @@ class PythonProgram:
# B (16 elements on 32 threads): row major, lane 16-32 == lane 0-15 # B (16 elements on 32 threads): row major, lane 16-32 == lane 0-15
def b_elem(x, col, k, goff): return a_elem(x, k, col, goff) # pylint: disable=arguments-out-of-order def b_elem(x, col, k, goff): return a_elem(x, k, col, goff) # pylint: disable=arguments-out-of-order
def c_map(lane, elem): return (lane%16, lane//16+elem*2) # (i, j), C, D (8 elements on 32 threads): row major def c_map(lane, elem): return (lane%16, lane//16+elem*2) # (i, j), C, D (8 elements on 32 threads): row major
values[u] = wmma_helper(32, 16, 16, 16, 8, a_elem, b_elem, c_map) values[i] = wmma_helper(32, 16, 16, 16, 8, a_elem, b_elem, c_map)
elif device == "CUDA": elif device == "CUDA":
# (col, row) given (lane, elem) for C & D (4 elements on 32 threads); shared by all tc shapes with M=16 N=8 # (col, row) given (lane, elem) for C & D (4 elements on 32 threads); shared by all tc shapes with M=16 N=8
def c_map(lane, elem): return (elem%2 + (lane%4)*2, lane//4 + (elem//2)*8) def c_map(lane, elem): return (elem%2 + (lane%4)*2, lane//4 + (elem//2)*8)
@@ -173,24 +172,24 @@ class PythonProgram:
if dims == (8,16,16): if dims == (8,16,16):
def a_elem(x, k, row, goff): return x[k%2 + (row//8)*2 + (k//8)*4][goff + (k//2)%4 + (row%8)*4] def a_elem(x, k, row, goff): return x[k%2 + (row//8)*2 + (k//8)*4][goff + (k//2)%4 + (row%8)*4]
def b_elem(x, col, k, goff): return x[k%2 + (k//8)*2][goff + (k//2)%4 + col*4] def b_elem(x, col, k, goff): return x[k%2 + (k//8)*2][goff + (k//2)%4 + col*4]
values[u] = wmma_helper(32, 16, 8, 4, 4, a_elem, b_elem, c_map) values[i] = wmma_helper(32, 16, 8, 4, 4, a_elem, b_elem, c_map)
elif dims == (8,16,32): elif dims == (8,16,32):
def a_elem(x, k, row, goff): return x[k%4 + (row//8)*4 + (k//16)*8][goff + (k//4)%4 + (row%8)*4] def a_elem(x, k, row, goff): return x[k%4 + (row//8)*4 + (k//16)*8][goff + (k//4)%4 + (row%8)*4]
def b_elem(x, col, k, goff): return x[k%4 + (k//16)*4][goff + (k//4)%4 + col*4] def b_elem(x, col, k, goff): return x[k%4 + (k//16)*4][goff + (k//4)%4 + col*4]
values[u] = wmma_helper(32, 32, 16, 8, 4, a_elem, b_elem, c_map) values[i] = wmma_helper(32, 32, 16, 8, 4, a_elem, b_elem, c_map)
elif dims == (8,16,8) and dtype_in == dtypes.half: elif dims == (8,16,8) and dtype_in == dtypes.half:
def a_elem(x, k, row, goff): return x[k%2 + (row//8)*2][goff + k//2 + (row%8)*4] def a_elem(x, k, row, goff): return x[k%2 + (row//8)*2][goff + k//2 + (row%8)*4]
def b_elem(x, col, k, goff): return x[k%2][goff + k//2 + col*4] def b_elem(x, col, k, goff): return x[k%2][goff + k//2 + col*4]
values[u] = wmma_helper(32, 8, 4, 2, 4, a_elem, b_elem, c_map) values[i] = wmma_helper(32, 8, 4, 2, 4, a_elem, b_elem, c_map)
elif dims == (8,16,8) and dtype_in == dtypes.float: elif dims == (8,16,8) and dtype_in == dtypes.float:
def a_elem(x, k, row, goff): return x[(k//4)*2 + row//8][goff + k%4 + (row%8)*4] def a_elem(x, k, row, goff): return x[(k//4)*2 + row//8][goff + k%4 + (row%8)*4]
def b_elem(x, col, k, goff): return x[k//4][goff + k%4 + col*4] def b_elem(x, col, k, goff): return x[k//4][goff + k%4 + col*4]
values[u] = wmma_helper(32, 8, 4, 2, 4, a_elem, b_elem, c_map) values[i] = wmma_helper(32, 8, 4, 2, 4, a_elem, b_elem, c_map)
else: raise NotImplementedError(f"unimplemented tensor core {u.arg}") else: raise NotImplementedError(f"unimplemented tensor core {arg}")
elif device == "INTEL": elif device == "INTEL":
# A (16 elements on 8 threads) # A (16 elements on 8 threads)
def a_elem(x, k, row, goff): return x[k%2+row*2][goff+k//2] def a_elem(x, k, row, goff): return x[k%2+row*2][goff+k//2]
@@ -198,17 +197,17 @@ class PythonProgram:
def b_elem(x, col, k, goff): return x[k][goff+col] def b_elem(x, col, k, goff): return x[k][goff+col]
# C, D (8 elements on 8 threads) # C, D (8 elements on 8 threads)
def c_map(lane, elem): return (lane, elem) def c_map(lane, elem): return (lane, elem)
values[u] = wmma_helper(8, 16, 16, 16, 8, a_elem, b_elem, c_map) values[i] = wmma_helper(8, 16, 16, 16, 8, a_elem, b_elem, c_map)
elif device == "CPU": elif device == "CPU":
def elem(x, col, row, _): return x[col+row][0] # k is always 0 def elem(x, col, row, _): return x[col+row][0] # k is always 0
def c_map(lane, elem): return (elem%16, elem//16) def c_map(lane, elem): return (elem%16, elem//16)
values[u] = wmma_helper(1, 1, 16, 16, 256, elem, elem, c_map) values[i] = wmma_helper(1, 1, 16, 16, 256, elem, elem, c_map)
else: raise NotImplementedError(f"unimplemented tensor core {u.arg}") else: raise NotImplementedError(f"unimplemented tensor core {arg}")
elif u.op in GroupOp.ALU: elif uop in GroupOp.ALU:
assert all_same([len(x) for x in src_values]), f"{[len(x) for x in src_values]} doesn't match on {u.op}" assert all_same([len(x) for x in src_values]), f"{[len(x) for x in src_values]} doesn't match on {uop}"
assert all_same([u.dtype] + src_dtypes) or u.op in {*GroupOp.Comparison, Ops.WHERE}, f"dtype mismatch on {u.op}" assert all_same([dtype] + src_dtypes) or uop in {*GroupOp.Comparison, Ops.WHERE}, f"dtype mismatch on {uop}"
values[u] = [exec_alu(u.op, u.dtype, p) for p in zip(*src_values)] values[i] = [exec_alu(uop, dtype, p) for p in zip(*src_values)]
assert u in values, u assert i in values, (uop, dtype, srcs, arg)
i += 1 i += 1
return time.perf_counter() - st return time.perf_counter() - st
@@ -235,7 +234,10 @@ class PythonRenderer(Renderer):
elif IMAGE and not target.arch: self.target = replace(target, arch="IMAGE_PITCH_ALIGNMENT=1") elif IMAGE and not target.arch: self.target = replace(target, arch="IMAGE_PITCH_ALIGNMENT=1")
else: self.target = target else: self.target = target
def render(self, uops:list[UOp]) -> str: return base64.b64encode(pickle.dumps(uops)).decode() def render(self, uops:list[UOp]) -> str:
# the value of SPECIAL comes from local/global_size, not form its source
lops = [(u.op, u.dtype, [uops.index(v) for v in u.src if u.op is not Ops.SPECIAL], u.arg) for u in uops]
return base64.b64encode(pickle.dumps(lops)).decode()
def supported_dtypes(self): return {d for d in super().supported_dtypes() if d != dtypes.half or sys.version_info >= (3, 12)} def supported_dtypes(self): return {d for d in super().supported_dtypes() if d != dtypes.half or sys.version_info >= (3, 12)}
+1 -1
View File
@@ -20,7 +20,7 @@ BUFTYPE_BUF, BUFTYPE_TEX, BUFTYPE_IBO = 0, 1, 2
def dcache_flush(): def dcache_flush():
from tinygrad.uop.ops import UOp, Ops, KernelInfo from tinygrad.uop.ops import UOp, Ops, KernelInfo
from tinygrad.codegen import to_program from tinygrad.codegen import to_program
buf, n = UOp.param(0, dtypes.uint8.ptr()), UOp.param(1, dtypes.uint8.ptr()) buf, n = UOp(Ops.PARAM, dtypes.uint8.ptr(), arg=0), UOp(Ops.PARAM, dtypes.uint8.ptr(), arg=1)
i = UOp.range(n.cast(dtypes.int), 0, dtype=dtypes.int) i = UOp.range(n.cast(dtypes.int), 0, dtype=dtypes.int)
flush = UOp(Ops.CUSTOM, dtypes.void, (buf.cast(dtypes.ulong) + i.cast(dtypes.ulong) * UOp.const(dtypes.ulong, 64),), flush = UOp(Ops.CUSTOM, dtypes.void, (buf.cast(dtypes.ulong) + i.cast(dtypes.ulong) * UOp.const(dtypes.ulong, 64),),
arg='__asm__ volatile("dc cvac, %0" :: "r"({0}) : "memory");') arg='__asm__ volatile("dc cvac, %0" :: "r"({0}) : "memory");')
+147 -143
View File
@@ -1,120 +1,157 @@
import functools, struct import functools, struct
from tinygrad.device import Compiled, Allocator, BufferSpec from tinygrad.device import Compiled, Allocator, BufferSpec
from tinygrad.renderer.wgsl import WGSLRenderer from tinygrad.renderer.wgsl import WGSLRenderer
from tinygrad.helpers import round_up, suppress_finalizing, getenv, to_mv from tinygrad.helpers import round_up, suppress_finalizing
from tinygrad.runtime.autogen import webgpu from tinygrad.runtime.autogen import webgpu
from tinygrad.runtime.support import c from tinygrad.runtime.support import c
from typing import Callable from typing import cast, List, Any, TypeAlias
import ctypes import ctypes
import os
backend_types = {v: k for k, v in webgpu.enum_WGPUBackendType.items()} WGPUDevPtr: TypeAlias = webgpu.WGPUDevice
instance = webgpu.wgpuCreateInstance(webgpu.WGPUInstanceDescriptor(features=webgpu.WGPUInstanceFeatures(timedWaitAnyEnable=True))) WGPUBufPtr: TypeAlias = webgpu.WGPUBuffer
def from_wgpu_str(string_view:webgpu.WGPUStringView) -> str: return ctypes.string_at(string_view.data, string_view.length).decode() backend_types = {v: k for k, v in webgpu.enum_WGPUBackendType.items() }
def to_wgpu_str(_str:str) -> webgpu.WGPUStringView: return webgpu.WGPUStringView(data=ctypes.create_string_buffer(_str.encode()), length=len(_str))
# gets a memoryview from a buffer, which is assumed to have MAP_READ (see _readable_buffer) instance = webgpu.wgpuCreateInstance(webgpu.WGPUInstanceDescriptor(features = webgpu.WGPUInstanceFeatures(timedWaitAnyEnable = True)))
def buf_to_mv(buf:webgpu.WGPUBuffer) -> memoryview:
BufferMapAsync(buf, webgpu.WGPUMapMode_Read, 0, size:=webgpu.wgpuBufferGetSize(buf))
return to_mv(webgpu.wgpuBufferGetConstMappedRange(buf, 0, size), size)
# turns a webgpu function returning a future into python-synchronous function def to_c_string(_str:str) -> ctypes.Array: return ctypes.create_string_buffer(_str.encode('utf-8'))
# the new function handles the status code and optional error message, returning the other callback arguments
def synchronous(status_enum:dict[int, str], has_emsg:bool=False):
def wrap(fn:Callable[..., webgpu.WGPUFuture]) -> Callable:
@functools.wraps(fn)
def wrapper(*args):
status, payload, emsg = 0, [], None
@next(ty for nm, ty, *_ in fn.argtypes[-1]._real_fields_ if nm == "callback") # type: ignore def from_wgpu_str(string_view:webgpu.struct_WGPUStringView) -> str: return ctypes.string_at(string_view.data, string_view.length).decode("utf-8")
def cb(s:int, *args):
nonlocal status, payload, emsg
# the last two arguments are "userdata1" and "userdata2", which we drop
# we must process wgpu strings in this callback, as they will be freed after we return
status, (*payload, emsg) = s, [from_wgpu_str(a) if type(a) is webgpu.WGPUStringView else a for a in args[:-2]] + ([] if has_emsg else [None])
future = fn(*args, fn.argtypes[-1](mode=webgpu.WGPUCallbackMode_WaitAnyOnly, callback=cb)) # type: ignore def to_wgpu_str(_str:str) -> webgpu.struct_WGPUStringView:
if (future_status:=webgpu.wgpuInstanceWaitAny(instance, 1, webgpu.WGPUFutureWaitInfo(future), 2**64-1)) != webgpu.WGPUWaitStatus_Success: return webgpu.WGPUStringView(data=ctypes.cast(ctypes.pointer(to_c_string(_str)), ctypes.POINTER(ctypes.c_char)), length=len(_str))
raise RuntimeError(f"error while waiting for future ({fn.__name__}): {webgpu.enum_WGPUWaitStatus.get(future_status)}")
if status != 1: raise RuntimeError(f"[{status_enum.get(status)}]{emsg or ''}") def _wait(future:webgpu.struct_WGPUFuture):
return payload if len(payload) > 1 else payload[0] if len(payload) == 1 else None assert webgpu.wgpuInstanceWaitAny(instance, 1, webgpu.WGPUFutureWaitInfo(future=future), 2**64-1) == webgpu.WGPUWaitStatus_Success, "Future failed"
return wrapper
return wrap
BufferMapAsync = synchronous(webgpu.enum_WGPUBufferMapAsyncStatus, True)(webgpu.wgpuBufferMapAsync2) def write_buffer(device:WGPUDevPtr, buf:WGPUBufPtr, offset:int, src:memoryview|bytearray|bytes):
DevicePopErrorScope = synchronous(webgpu.enum_WGPUPopErrorScopeStatus)(webgpu.wgpuDevicePopErrorScope2) src = bytearray(src)
DeviceCreateComputePipeline = synchronous(webgpu.enum_WGPUCreatePipelineAsyncStatus, True)(webgpu.wgpuDeviceCreateComputePipelineAsync2) webgpu.wgpuQueueWriteBuffer(webgpu.wgpuDeviceGetQueue(device), buf, offset, (ctypes.c_uint8 * len(src)).from_buffer(src), len(src))
InstanceRequestAdapter = synchronous(webgpu.enum_WGPURequestAdapterStatus, True)(webgpu.wgpuInstanceRequestAdapter2)
AdapterRequestDevice = synchronous(webgpu.enum_WGPURequestDeviceStatus, True)(webgpu.wgpuAdapterRequestDevice2) def _run(async_fun, cb_info_type, cb_type, status_enum:dict|None, res_idx:int|None, msg_idx:int|None, *params):
QueueOnSubmittedWorkDone = synchronous(webgpu.enum_WGPUQueueWorkDoneStatus)(webgpu.wgpuQueueOnSubmittedWorkDone2) result: List[Any] = []
def cb(*params):
result[:] = params
if msg_idx: result[msg_idx] = from_wgpu_str(result[msg_idx])
cb_info = cb_info_type(mode=webgpu.WGPUCallbackMode_WaitAnyOnly, callback=cb_type(cb))
_wait(async_fun(*params, cb_info))
if result[0] != 1: raise RuntimeError(f"[{status_enum.get(result[0]) if status_enum else 'ERROR'}]{result[msg_idx] if msg_idx else ''}")
return result[res_idx] if res_idx else None
def copy_buffer_to_buffer(dev:WGPUDevPtr, src:WGPUBufPtr, src_offset:int, dst:WGPUBufPtr, dst_offset:int, size:int):
encoder = webgpu.wgpuDeviceCreateCommandEncoder(dev, webgpu.WGPUCommandEncoderDescriptor())
webgpu.wgpuCommandEncoderCopyBufferToBuffer(encoder, src, src_offset, dst, dst_offset, size)
cb = webgpu.wgpuCommandEncoderFinish(encoder, webgpu.WGPUCommandBufferDescriptor())
webgpu.wgpuQueueSubmit(webgpu.wgpuDeviceGetQueue(dev), 1, (webgpu.WGPUCommandBuffer*1)(cb))
webgpu.wgpuCommandBufferRelease(cb)
webgpu.wgpuCommandEncoderRelease(encoder)
def read_buffer(dev:WGPUDevPtr, buf:WGPUBufPtr) -> memoryview:
size = webgpu.wgpuBufferGetSize(buf)
tmp_buffer = webgpu.wgpuDeviceCreateBuffer(dev, webgpu.WGPUBufferDescriptor(size=size,
usage=webgpu.WGPUBufferUsage_CopyDst | webgpu.WGPUBufferUsage_MapRead, mappedAtCreation=False))
copy_buffer_to_buffer(dev, buf, 0, tmp_buffer, 0, size)
_run(webgpu.wgpuBufferMapAsync2, webgpu.WGPUBufferMapCallbackInfo2, webgpu.WGPUBufferMapCallback2, webgpu.enum_WGPUBufferMapAsyncStatus, None, 0,
tmp_buffer, webgpu.WGPUMapMode_Read, 0, size)
void_ptr = ctypes.cast(webgpu.wgpuBufferGetConstMappedRange(tmp_buffer, 0, size), ctypes.c_void_p)
buf_copy = bytearray((ctypes.c_uint8 * size).from_address(void_ptr.value))
webgpu.wgpuBufferUnmap(tmp_buffer)
webgpu.wgpuBufferDestroy(tmp_buffer)
return memoryview(buf_copy).cast("B")
def pop_error(device:WGPUDevPtr) -> str:
return _run(webgpu.wgpuDevicePopErrorScopeF, webgpu.WGPUPopErrorScopeCallbackInfo, webgpu.WGPUPopErrorScopeCallback, None, 2, 2, device)
def create_uniform(wgpu_device:WGPUDevPtr, val:int|float) -> WGPUBufPtr:
buf = webgpu.wgpuDeviceCreateBuffer(wgpu_device,
webgpu.WGPUBufferDescriptor(size=4, usage=webgpu.WGPUBufferUsage_Uniform | webgpu.WGPUBufferUsage_CopyDst))
write_buffer(wgpu_device, buf, 0, val.to_bytes(4, "little") if isinstance(val, int) else struct.pack('<f', val))
return buf
class WebGPUProgram: class WebGPUProgram:
def __init__(self, dev:'WebGpuDevice', name:str, lib:bytes, **kwargs): def __init__(self, dev:tuple[WGPUDevPtr, bool], name:str, lib:bytes, **kwargs):
self.dev, self.name = dev, to_wgpu_str(name) (self.dev, self.timestamp_supported) = dev
# Creating shader module # Creating shader module
shader = webgpu.WGPUShaderModuleWGSLDescriptor(code=to_wgpu_str(lib.decode()), shader = webgpu.WGPUShaderModuleWGSLDescriptor(code=to_wgpu_str(lib.decode()),
chain=webgpu.WGPUChainedStruct(sType=webgpu.WGPUSType_ShaderSourceWGSL)) chain=webgpu.WGPUChainedStruct(sType=webgpu.WGPUSType_ShaderSourceWGSL))
module = webgpu.WGPUShaderModuleDescriptor(nextInChain=ctypes.cast(ctypes.pointer(shader), ctypes.POINTER(webgpu.struct_WGPUChainedStruct))) module = webgpu.WGPUShaderModuleDescriptor()
module.nextInChain = ctypes.cast(ctypes.pointer(shader), c.POINTER[webgpu.struct_WGPUChainedStruct])
# Check compiler error # Check compiler error
webgpu.wgpuDevicePushErrorScope(self.dev.device_res, webgpu.WGPUErrorFilter_Validation) webgpu.wgpuDevicePushErrorScope(self.dev, webgpu.WGPUErrorFilter_Validation)
self.prg = webgpu.wgpuDeviceCreateShaderModule(self.dev.device_res, module) shader_module = webgpu.wgpuDeviceCreateShaderModule(self.dev, module)
if err := self.dev.pop_error(): raise RuntimeError(f"Shader compilation failed: {err}")
@suppress_finalizing if err := pop_error(self.dev): raise RuntimeError(f"Shader compilation failed: {err}")
def __del__(self): webgpu.wgpuShaderModuleRelease(self.prg)
def __call__(self, *bufs:webgpu.WGPUBuffer, global_size:tuple[int,int,int]=(1,1,1), local_size:tuple[int,int,int]=(1,1,1), self.name, self.lib, self.prg = name, lib, shader_module
def __call__(self, *bufs:WGPUBufPtr, global_size:tuple[int,int,int]=(1,1,1), local_size:tuple[int,int,int]=(1,1,1),
vals:tuple[int, ...]=(), wait=False, **kw) -> float|None: vals:tuple[int, ...]=(), wait=False, **kw) -> float|None:
wait = wait and webgpu.WGPUFeatureName_TimestampQuery in self.dev.features wait = wait and self.timestamp_supported
tmp_bufs = [*bufs]
buf_patch = False
# WebGPU does not allow using the same buffer for input and output
for i in range(1, len(bufs)):
if ctypes.addressof(bufs[i]) == ctypes.addressof(bufs[0]):
tmp_bufs[0] = webgpu.wgpuDeviceCreateBuffer(self.dev,
webgpu.WGPUBufferDescriptor(size=webgpu.wgpuBufferGetSize(bufs[0]), usage=webgpu.wgpuBufferGetUsage(bufs[0])))
buf_patch = True
# Creating bind group layout # Creating bind group layout
def bgl_entry(n:int, ty:str): binding_layouts = [webgpu.WGPUBindGroupLayoutEntry(binding=0, visibility= webgpu.WGPUShaderStage_Compute,
return webgpu.WGPUBindGroupLayoutEntry(binding=n, visibility=webgpu.WGPUShaderStage_Compute, buffer=webgpu.WGPUBufferBindingLayout(type=webgpu.WGPUBufferBindingType_Uniform))]
buffer=webgpu.WGPUBufferBindingLayout(type=getattr(webgpu, f'WGPUBufferBindingType_{ty}'))) binding_layouts += [webgpu.WGPUBindGroupLayoutEntry(binding=i+1, visibility=webgpu.WGPUShaderStage_Compute,
bind_entries = (webgpu.WGPUBindGroupLayoutEntry * (1+len(bufs)+len(vals)))( buffer=webgpu.WGPUBufferBindingLayout(type=webgpu.WGPUBufferBindingType_Uniform if i >= len(tmp_bufs)
bgl_entry(0, 'Uniform'), *(bgl_entry(i+1, 'Uniform' if i >= len(bufs) else 'Storage') for i in range(len(bufs)+len(vals)))) else webgpu.WGPUBufferBindingType_Storage)) for i in range(len(tmp_bufs)+len(vals))]
webgpu.wgpuDevicePushErrorScope(self.dev.device_res, webgpu.WGPUErrorFilter_Validation) bl_arr_type = webgpu.WGPUBindGroupLayoutEntry * len(binding_layouts)
bind_layout = webgpu.wgpuDeviceCreateBindGroupLayout(self.dev.device_res, webgpu.wgpuDevicePushErrorScope(self.dev, webgpu.WGPUErrorFilter_Validation)
webgpu.WGPUBindGroupLayoutDescriptor(entryCount=len(bind_entries), entries=bind_entries)) bind_group_layouts = [webgpu.wgpuDeviceCreateBindGroupLayout(self.dev, webgpu.WGPUBindGroupLayoutDescriptor(
entryCount=len(binding_layouts), entries=ctypes.cast(bl_arr_type(*binding_layouts), ctypes.POINTER(webgpu.WGPUBindGroupLayoutEntry))))]
if err := self.dev.pop_error(): raise RuntimeError(f"Error creating bind group layout: {err}") if bg_layout_err := pop_error(self.dev): raise RuntimeError(f"Error creating bind group layout: {bg_layout_err}")
# Creating pipeline layout # Creating pipeline layout
pipeline_layout_desc = webgpu.WGPUPipelineLayoutDescriptor(bindGroupLayoutCount=1, bindGroupLayouts=(webgpu.WGPUBindGroupLayout*1)(bind_layout)) pipeline_layout_desc = webgpu.WGPUPipelineLayoutDescriptor(bindGroupLayoutCount=len(bind_group_layouts),
bindGroupLayouts = (webgpu.WGPUBindGroupLayout * len(bind_group_layouts))(*bind_group_layouts))
webgpu.wgpuDevicePushErrorScope(self.dev.device_res, webgpu.WGPUErrorFilter_Validation) webgpu.wgpuDevicePushErrorScope(self.dev, webgpu.WGPUErrorFilter_Validation)
pipeline_layout = webgpu.wgpuDeviceCreatePipelineLayout(self.dev.device_res, pipeline_layout_desc) pipeline_layout = webgpu.wgpuDeviceCreatePipelineLayout(self.dev, pipeline_layout_desc)
if err := self.dev.pop_error(): raise RuntimeError(f"Error creating pipeline layout: {err}")
if pipe_err := pop_error(self.dev): raise RuntimeError(f"Error creating pipeline layout: {pipe_err}")
# Creating bind group # Creating bind group
def bg_entry(n:int, x:webgpu.WGPUBuffer|int|float): bindings = [webgpu.WGPUBindGroupEntry(binding=0, buffer=create_uniform(self.dev, float('inf')), offset=0, size=4)]
buf = x if isinstance(x, webgpu.WGPUBuffer) else self.dev.create_uniform(x) bindings += [webgpu.WGPUBindGroupEntry(binding=i+1, buffer=create_uniform(self.dev, cast(int, x)) if i >= len(tmp_bufs) else x, offset=0,
return webgpu.WGPUBindGroupEntry(binding=n, buffer=buf, offset=0, size=webgpu.wgpuBufferGetSize(buf)) size=4 if i >= len(tmp_bufs) else webgpu.wgpuBufferGetSize(x)) for i,x in enumerate(tuple(tmp_bufs)+vals)]
bindings = (webgpu.WGPUBindGroupEntry * (1+len(bufs)+len(vals)))(bg_entry(0, float('inf')), *(bg_entry(i+1, x) for i,x in enumerate(bufs+vals)))
bind_group_desc = webgpu.WGPUBindGroupDescriptor(layout=bind_layout, entryCount=len(bindings), entries=bindings) bg_arr_type = webgpu.WGPUBindGroupEntry * len(bindings)
webgpu.wgpuDevicePushErrorScope(self.dev.device_res, webgpu.WGPUErrorFilter_Validation) bind_group_desc = webgpu.WGPUBindGroupDescriptor(layout=bind_group_layouts[0], entryCount=len(bindings), entries=bg_arr_type(*bindings))
bind_group = webgpu.wgpuDeviceCreateBindGroup(self.dev.device_res, bind_group_desc) webgpu.wgpuDevicePushErrorScope(self.dev, webgpu.WGPUErrorFilter_Validation)
if err := self.dev.pop_error(): raise RuntimeError(f"Error creating bind group: {err}") bind_group = webgpu.wgpuDeviceCreateBindGroup(self.dev, bind_group_desc)
if bind_err := pop_error(self.dev): raise RuntimeError(f"Error creating bind group: {bind_err}")
# Creating compute pipeline # Creating compute pipeline
compute_desc = webgpu.WGPUComputePipelineDescriptor(layout=pipeline_layout, compute_desc = webgpu.WGPUComputePipelineDescriptor(layout=pipeline_layout,
compute=webgpu.WGPUComputeState(module=self.prg, entryPoint=self.name)) compute=webgpu.WGPUComputeState(module=self.prg, entryPoint=to_wgpu_str(self.name)))
pipeline_result = DeviceCreateComputePipeline(self.dev.device_res, compute_desc) pipeline_result = _run(webgpu.wgpuDeviceCreateComputePipelineAsync2, webgpu.WGPUCreateComputePipelineAsyncCallbackInfo2,
webgpu.WGPUCreateComputePipelineAsyncCallback2, webgpu.enum_WGPUCreatePipelineAsyncStatus, 1, None, self.dev, compute_desc)
command_encoder = webgpu.wgpuDeviceCreateCommandEncoder(self.dev.device_res, webgpu.WGPUCommandEncoderDescriptor()) command_encoder = webgpu.wgpuDeviceCreateCommandEncoder(self.dev, webgpu.WGPUCommandEncoderDescriptor())
comp_pass_desc = webgpu.WGPUComputePassDescriptor() comp_pass_desc = webgpu.WGPUComputePassDescriptor()
if wait: if wait:
query_set = webgpu.wgpuDeviceCreateQuerySet(self.dev.device_res, webgpu.WGPUQuerySetDescriptor(type=webgpu.WGPUQueryType_Timestamp, count=2)) query_set = webgpu.wgpuDeviceCreateQuerySet(self.dev, webgpu.WGPUQuerySetDescriptor(type=webgpu.WGPUQueryType_Timestamp, count=2))
query_buf = webgpu.wgpuDeviceCreateBuffer( query_buf = webgpu.wgpuDeviceCreateBuffer(self.dev,
self.dev.device_res, webgpu.WGPUBufferDescriptor(size=16, usage=webgpu.WGPUBufferUsage_QueryResolve | webgpu.WGPUBufferUsage_CopySrc)) webgpu.WGPUBufferDescriptor(size=16, usage=webgpu.WGPUBufferUsage_QueryResolve | webgpu.WGPUBufferUsage_CopySrc))
comp_pass_desc.timestampWrites = c.pointer(webgpu.WGPUComputePassTimestampWrites(querySet=query_set, beginningOfPassWriteIndex=0, comp_pass_desc.timestampWrites = c.pointer(webgpu.WGPUComputePassTimestampWrites(
endOfPassWriteIndex=1)) querySet=query_set, beginningOfPassWriteIndex=0, endOfPassWriteIndex=1))
# Begin compute pass # Begin compute pass
compute_pass = webgpu.wgpuCommandEncoderBeginComputePass(command_encoder, comp_pass_desc) compute_pass = webgpu.wgpuCommandEncoderBeginComputePass(command_encoder, comp_pass_desc)
@@ -126,96 +163,63 @@ class WebGPUProgram:
if wait: webgpu.wgpuCommandEncoderResolveQuerySet(command_encoder, query_set, 0, 2, query_buf, 0) if wait: webgpu.wgpuCommandEncoderResolveQuerySet(command_encoder, query_set, 0, 2, query_buf, 0)
cmd_buf = webgpu.wgpuCommandEncoderFinish(command_encoder, webgpu.WGPUCommandBufferDescriptor()) cmd_buf = webgpu.wgpuCommandEncoderFinish(command_encoder, webgpu.WGPUCommandBufferDescriptor())
webgpu.wgpuQueueSubmit(self.dev.queue, 1, (webgpu.WGPUCommandBuffer*1)(cmd_buf)) webgpu.wgpuQueueSubmit(webgpu.wgpuDeviceGetQueue(self.dev), 1, (webgpu.WGPUCommandBuffer*1)(cmd_buf))
# release created objects if buf_patch:
webgpu.wgpuBindGroupLayoutRelease(bind_layout) copy_buffer_to_buffer(self.dev, tmp_bufs[0], 0, bufs[0], 0, webgpu.wgpuBufferGetSize(bufs[0]))
webgpu.wgpuPipelineLayoutRelease(pipeline_layout) webgpu.wgpuBufferDestroy(tmp_bufs[0])
webgpu.wgpuBindGroupRelease(bind_group)
webgpu.wgpuComputePipelineRelease(pipeline_result)
webgpu.wgpuCommandEncoderRelease(command_encoder)
webgpu.wgpuComputePassEncoderRelease(compute_pass)
webgpu.wgpuCommandBufferRelease(cmd_buf)
if wait: if wait:
time = ((timestamps:=buf_to_mv(tmp_buf:=self.dev._readable_buffer(query_buf)).cast("Q").tolist())[1] - timestamps[0]) / 1e9 time = ((timestamps:=read_buffer(self.dev, query_buf).cast("Q").tolist())[1] - timestamps[0]) / 1e9
self.dev.free(query_buf) webgpu.wgpuBufferDestroy(query_buf)
self.dev.free(tmp_buf)
webgpu.wgpuQuerySetDestroy(query_set) webgpu.wgpuQuerySetDestroy(query_set)
webgpu.wgpuQuerySetRelease(query_set)
return time return time
return None return None
class WebGpuAllocator(Allocator['WebGpuDevice']): class WebGpuAllocator(Allocator['WebGpuDevice']):
def _alloc(self, size:int, options:BufferSpec) -> webgpu.WGPUBuffer: def _alloc(self, size:int, options:BufferSpec) -> WGPUBufPtr:
# WebGPU buffers have to be 4-byte aligned # WebGPU buffers have to be 4-byte aligned
return webgpu.wgpuDeviceCreateBuffer(self.dev.device_res, webgpu.WGPUBufferDescriptor(size=round_up(size, 4), return webgpu.wgpuDeviceCreateBuffer(self.dev.device_res, webgpu.WGPUBufferDescriptor(size=round_up(size, 4),
usage=webgpu.WGPUBufferUsage_Storage | webgpu.WGPUBufferUsage_CopyDst | webgpu.WGPUBufferUsage_CopySrc)) usage=webgpu.WGPUBufferUsage_Storage | webgpu.WGPUBufferUsage_CopyDst | webgpu.WGPUBufferUsage_CopySrc))
def _copyin(self, dest:webgpu.WGPUBuffer, src:memoryview): def _copyin(self, dest:WGPUBufPtr, src:memoryview):
if src.nbytes % 4: if src.nbytes % 4:
padded_src = bytearray(round_up(src.nbytes, 4)) padded_src = bytearray(round_up(src.nbytes, 4))
padded_src[:src.nbytes] = src padded_src[:src.nbytes] = src
self.dev.write_buffer(dest, padded_src if src.nbytes % 4 else src) write_buffer(self.dev.device_res, dest, 0, padded_src if src.nbytes % 4 else src)
def _copyout(self, dest:memoryview, src:webgpu.WGPUBuffer): def _copyout(self, dest:memoryview, src:WGPUBufPtr):
dest[:] = buf_to_mv(tmp_buf:=self.dev._readable_buffer(src))[:dest.nbytes] buffer_data = read_buffer(self.dev.device_res, src)
self.dev.free(tmp_buf) dest[:] = buffer_data[:dest.nbytes] if webgpu.wgpuBufferGetSize(src) > dest.nbytes else buffer_data
@suppress_finalizing
def _free(self, opaque:webgpu.WGPUBuffer, options:BufferSpec): self.dev.free(opaque) def _free(self, opaque:WGPUBufPtr, options:BufferSpec): webgpu.wgpuBufferDestroy(opaque)
class WebGpuDevice(Compiled): class WebGpuDevice(Compiled):
def __init__(self, device:str): def __init__(self, device:str):
# Requesting an adapter # Requesting an adapter
adapter_res = InstanceRequestAdapter(instance, webgpu.WGPURequestAdapterOptions( adapter_res = _run(webgpu.wgpuInstanceRequestAdapterF, webgpu.WGPURequestAdapterCallbackInfo, webgpu.WGPURequestAdapterCallback,
powerPreference=webgpu.WGPUPowerPreference_HighPerformance, backendType=backend_types.get(getenv("WEBGPU_BACKEND", ""), 0))) webgpu.enum_WGPUCreatePipelineAsyncStatus, 1, 2, instance, webgpu.WGPURequestAdapterOptions(
powerPreference=webgpu.WGPUPowerPreference_HighPerformance, backendType=backend_types.get(os.getenv("WEBGPU_BACKEND", ""), 0)))
# Get supported features # Get supported features
webgpu.wgpuAdapterGetFeatures(adapter_res, supported_features:=webgpu.WGPUSupportedFeatures()) supported_features = webgpu.WGPUSupportedFeatures()
self.features = [feat for i in range(supported_features.featureCount) webgpu.wgpuAdapterGetFeatures(adapter_res, supported_features)
if (feat:=supported_features.features[i]) in [webgpu.WGPUFeatureName_TimestampQuery, webgpu.WGPUFeatureName_ShaderF16]] supported = [supported_features.features[i] for i in range(supported_features.featureCount)]
webgpu.wgpuSupportedFeaturesFreeMembers(supported_features) features = [feat for feat in [webgpu.WGPUFeatureName_TimestampQuery, webgpu.WGPUFeatureName_ShaderF16] if feat in supported]
dev_desc = webgpu.WGPUDeviceDescriptor(requiredFeatureCount=len(self.features), dev_desc = webgpu.WGPUDeviceDescriptor(requiredFeatureCount=len(features),
requiredFeatures=(webgpu.WGPUFeatureName * len(self.features))(*self.features)) requiredFeatures=c.Array(webgpu.WGPUFeatureName, len(features))(*features)) # type: ignore
# Limits # Limits
webgpu.wgpuAdapterGetLimits(adapter_res, supported_limits:=webgpu.WGPUSupportedLimits()) supported_limits = webgpu.WGPUSupportedLimits()
dev_desc.requiredLimits = c.pointer(webgpu.WGPURequiredLimits(limits=supported_limits.limits)) webgpu.wgpuAdapterGetLimits(adapter_res, ctypes.cast(ctypes.pointer(supported_limits),ctypes.POINTER(webgpu.struct_WGPUSupportedLimits)))
limits = webgpu.WGPURequiredLimits(limits=supported_limits.limits)
dev_desc.requiredLimits = c.pointer(limits)
# Requesting a device # Requesting a device
self.device_res = AdapterRequestDevice(adapter_res, dev_desc) self.device_res = _run(webgpu.wgpuAdapterRequestDeviceF, webgpu.WGPURequestDeviceCallbackInfo, webgpu.WGPURequestDeviceCallback,
self.queue = webgpu.wgpuDeviceGetQueue(self.device_res) webgpu.enum_WGPURequestDeviceStatus, 1, 2, adapter_res, dev_desc)
webgpu.wgpuAdapterRelease(adapter_res) program = functools.partial(WebGPUProgram, (self.device_res, webgpu.WGPUFeatureName_TimestampQuery in supported))
super().__init__(device, WebGpuAllocator(self), [WGSLRenderer], program, arch="shader-f16" * (webgpu.WGPUFeatureName_ShaderF16 in supported))
super().__init__(device, WebGpuAllocator(self), [WGSLRenderer], functools.partial(WebGPUProgram, self), def synchronize(self):
arch="shader-f16" * (webgpu.WGPUFeatureName_ShaderF16 in self.features)) _run(webgpu.wgpuQueueOnSubmittedWorkDone2, webgpu.WGPUQueueWorkDoneCallbackInfo2, webgpu.WGPUQueueWorkDoneCallback2,
webgpu.enum_WGPUQueueWorkDoneStatus, None, None, webgpu.wgpuDeviceGetQueue(self.device_res))
def synchronize(self): QueueOnSubmittedWorkDone(self.queue)
@suppress_finalizing
def free(self, buf:webgpu.WGPUBuffer):
if webgpu.wgpuBufferGetMapState(buf) == webgpu.WGPUBufferMapState_Mapped: webgpu.wgpuBufferUnmap(buf)
webgpu.wgpuBufferDestroy(buf)
webgpu.wgpuBufferRelease(buf)
def pop_error(self) -> str: return DevicePopErrorScope(self.device_res)[1]
def create_uniform(self, val:int|float) -> webgpu.WGPUBuffer:
buf = webgpu.wgpuDeviceCreateBuffer(self.device_res,
webgpu.WGPUBufferDescriptor(size=4, usage=webgpu.WGPUBufferUsage_Uniform | webgpu.WGPUBufferUsage_CopyDst))
self.write_buffer(buf, val.to_bytes(4, "little") if isinstance(val, int) else struct.pack('<f', val))
return buf
def _readable_buffer(self, buf:webgpu.WGPUBuffer) -> webgpu.WGPUBuffer:
size = webgpu.wgpuBufferGetSize(buf)
ret = webgpu.wgpuDeviceCreateBuffer(self.device_res,
webgpu.WGPUBufferDescriptor(size=size, usage=webgpu.WGPUBufferUsage_CopyDst | webgpu.WGPUBufferUsage_MapRead, mappedAtCreation=False))
# copy_buffer_to_buffer
encoder = webgpu.wgpuDeviceCreateCommandEncoder(self.device_res, webgpu.WGPUCommandEncoderDescriptor())
webgpu.wgpuCommandEncoderCopyBufferToBuffer(encoder, buf, 0, ret, 0, size)
cmd_buf = webgpu.wgpuCommandEncoderFinish(encoder, webgpu.WGPUCommandBufferDescriptor())
webgpu.wgpuQueueSubmit(self.queue, 1, (webgpu.WGPUCommandBuffer*1)(cmd_buf))
webgpu.wgpuCommandBufferRelease(cmd_buf)
webgpu.wgpuCommandEncoderRelease(encoder)
return ret
def write_buffer(self, buf:webgpu.WGPUBuffer, src:memoryview|bytearray|bytes):
webgpu.wgpuQueueWriteBuffer(self.queue, buf, 0, (ctypes.c_uint8 * len(src)).from_buffer_copy(src), len(src))
-1
View File
@@ -132,7 +132,6 @@ class DLL(ctypes.CDLL):
nonlocal cfunc nonlocal cfunc
if cfunc is None: (cfunc:=getattr(self, fn.__name__)).argtypes, cfunc.restype = argtypes, restype if cfunc is None: (cfunc:=getattr(self, fn.__name__)).argtypes, cfunc.restype = argtypes, restype
return cfunc(*args) return cfunc(*args)
wrapper.restype, wrapper.argtypes = restype, argtypes # type: ignore
return wrapper return wrapper
return wrap return wrap
+2 -2
View File
@@ -4,7 +4,7 @@ from tinygrad.helpers import getenv, capstone_flatdump, DEBUG, unwrap
from tinygrad.runtime.support.elf import jit_loader from tinygrad.runtime.support.elf import jit_loader
from tinygrad.runtime.autogen import llvm from tinygrad.runtime.autogen import llvm
class ClangCompiler(Compiler): class ClangJITCompiler(Compiler):
def __init__(self, arch:list[str], cachekey="compile_clang_jit"): def __init__(self, arch:list[str], cachekey="compile_clang_jit"):
assert len(arch) >= 2, f"invalid arch string: {','.join(arch)!r}, expected '<arch>,<cpu>,[<feats>]' (eg. 'x86_64,znver2')" assert len(arch) >= 2, f"invalid arch string: {','.join(arch)!r}, expected '<arch>,<cpu>,[<feats>]' (eg. 'x86_64,znver2')"
self.arch, cpu, *feats = arch self.arch, cpu, *feats = arch
@@ -98,7 +98,7 @@ class CPULLVMCompiler(LLVMCompiler):
if cpu == "native": if cpu == "native":
cpu = ctypes.string_at(llvm.LLVMGetHostCPUName()).decode() cpu = ctypes.string_at(llvm.LLVMGetHostCPUName()).decode()
featstr = (featstr + "," if featstr else "") + ctypes.string_at(llvm.LLVMGetHostCPUFeatures()).decode() featstr = (featstr + "," if featstr else "") + ctypes.string_at(llvm.LLVMGetHostCPUFeatures()).decode()
# +reserve-x18 here does the same thing as -ffixed-x18 in ClangCompiler, see comments there for why it's needed on arm osx # +reserve-x18 here does the same thing as -ffixed-x18 in ClangJITCompiler, see comments there for why it's needed on arm osx
super().__init__(self.arch, cpu, ('+reserve-x18,' if self.arch == "arm64" else '') + featstr, cache_key) super().__init__(self.arch, cpu, ('+reserve-x18,' if self.arch == "arm64" else '') + featstr, cache_key)
def disassemble(self, lib:bytes): capstone_flatdump(lib, self.arch) def disassemble(self, lib:bytes): capstone_flatdump(lib, self.arch)
+1 -1
View File
@@ -77,7 +77,7 @@ def create_new_buffer(ctx:tuple[dict[UOp, UOp], tuple[UOp, ...]], b:UOp):
return ret return ret
pm_post_sched_cache = PatternMatcher([ pm_post_sched_cache = PatternMatcher([
(UPat(Ops.PARAM, name="x"), lambda ctx,x: ctx[1][x.arg.slot]), (UPat(Ops.PARAM, name="x"), lambda ctx,x: ctx[1][x.arg]),
# create new BUFFERs for LUNIQUE BUFFERs from rangeify # create new BUFFERs for LUNIQUE BUFFERs from rangeify
(UPat(Ops.BUFFER, src=(UPat(Ops.LUNIQUE), UPat(Ops.DEVICE)), name="b"), create_new_buffer), (UPat(Ops.BUFFER, src=(UPat(Ops.LUNIQUE), UPat(Ops.DEVICE)), name="b"), create_new_buffer),
]) ])
+2 -2
View File
@@ -128,7 +128,7 @@ def _apply_reshape(in_shape:tuple[sint,...], out_shape:tuple[sint, ...], urngs:U
@functools.cache @functools.cache
def apply_movement_op(op:Ops, in_shape:tuple[sint,...], arg:tuple, rngs:tuple[UOp, ...]) -> tuple[UOp, ...]: def apply_movement_op(op:Ops, in_shape:tuple[sint,...], arg:tuple, rngs:tuple[UOp, ...]) -> tuple[UOp, ...]:
match op: match op:
case Ops.SHRINK: rngs = tuple(a if off == 0 else a+off for a,(off,_) in zip(rngs, arg)) case Ops.SHRINK: rngs = tuple(a if off == 0 else a+off for a,(_,off) in zip(rngs, arg))
case Ops.PERMUTE: rngs = tuple(rngs[p] for p in argsort(arg)) case Ops.PERMUTE: rngs = tuple(rngs[p] for p in argsort(arg))
case Ops.FLIP: rngs = tuple(((s-1)-a) if f else a for a,s,f in zip(rngs, in_shape, arg)) case Ops.FLIP: rngs = tuple(((s-1)-a) if f else a for a,s,f in zip(rngs, in_shape, arg))
case Ops.EXPAND: rngs = tuple(a if in_sh == out_sh else a.const_like(0) for a,in_sh,out_sh in zip(rngs, in_shape, arg)) case Ops.EXPAND: rngs = tuple(a if in_sh == out_sh else a.const_like(0) for a,in_sh,out_sh in zip(rngs, in_shape, arg))
@@ -136,7 +136,7 @@ def apply_movement_op(op:Ops, in_shape:tuple[sint,...], arg:tuple, rngs:tuple[UO
# NOTE: the .where(r-s, i) is not inside the graph_rewrite so that `convert_pad_to_where_to_keep_behavior_local` # NOTE: the .where(r-s, i) is not inside the graph_rewrite so that `convert_pad_to_where_to_keep_behavior_local`
# wraps the pad with only the newly added valid # wraps the pad with only the newly added valid
rngs = tuple(r if (sz == sh and off == 0) else graph_rewrite((r >= off) & (r < (sh+off)), rngs = tuple(r if (sz == sh and off == 0) else graph_rewrite((r >= off) & (r < (sh+off)),
symbolic+pm_simplify_valid, name="pad").where(r-off, UOp.invalid()) for r,sh,(off,sz) in zip(rngs, in_shape, arg)) symbolic+pm_simplify_valid, name="pad").where(r-off, UOp.invalid()) for r,sh,(sz,off) in zip(rngs, in_shape, arg))
case Ops.RESHAPE: case Ops.RESHAPE:
sink = UOp.sink(*rngs).simplify() # NOTE: this applies any commutative flips to the rngs early sink = UOp.sink(*rngs).simplify() # NOTE: this applies any commutative flips to the rngs early
sub_array = {r:UOp.range(r.src[0], i, AxisType.PLACEHOLDER) for i,r in enumerate(sink.ranges)} sub_array = {r:UOp.range(r.src[0], i, AxisType.PLACEHOLDER) for i,r in enumerate(sink.ranges)}
+8 -8
View File
@@ -88,8 +88,8 @@ def expand_multi(root:UOp, multi:UOp):
return multi.src[0].expand(new_shape).multi(multi.axis) return multi.src[0].expand(new_shape).multi(multi.axis)
def pad_multi(root:UOp, multi:UOp): def pad_multi(root:UOp, multi:UOp):
assert multi.axis is None or root.marg[multi.axis] == (0, multi.shape[multi.axis]), f"padding not supported for {root.marg=}" assert multi.axis is None or root.marg[multi.axis] == (multi.shape[multi.axis], 0), f"padding not supported for {root.marg=}"
local_pad = tuple((0, multi.src[0].shape[multi.axis]) if a == multi.axis else s for a,s in enumerate(root.marg)) local_pad = tuple((multi.src[0].shape[multi.axis], 0) if a == multi.axis else s for a,s in enumerate(root.marg))
return multi.src[0]._mop(Ops.PAD, local_pad).multi(multi.axis) return multi.src[0]._mop(Ops.PAD, local_pad).multi(multi.axis)
def permute_multi(root:UOp, multi:UOp): def permute_multi(root:UOp, multi:UOp):
@@ -97,15 +97,15 @@ def permute_multi(root:UOp, multi:UOp):
return multi.src[0].permute(root.marg).multi(root.axis) return multi.src[0].permute(root.marg).multi(root.axis)
def shrink_multi(root:UOp, multi:UOp): def shrink_multi(root:UOp, multi:UOp):
shard_bounds = tuple((s,e-s) for s,e in multi.bounds) if multi.axis is not None else () shard_bounds = tuple((e-s,s) for s,e in multi.bounds) if multi.axis is not None else ()
assert multi.axis is None or root.marg[multi.axis] == (0, multi.shape[multi.axis]) or root.marg[multi.axis] in shard_bounds, \ assert multi.axis is None or root.marg[multi.axis] == (multi.shape[multi.axis], 0) or root.marg[multi.axis] in shard_bounds, \
f"shrinking not supported for {root.marg=}" f"shrinking not supported for {root.marg=}"
if multi.axis is not None and root.marg[multi.axis] in shard_bounds and root.marg[multi.axis] != (0, multi.shape[multi.axis]): if multi.axis is not None and root.marg[multi.axis] in shard_bounds and root.marg[multi.axis] != (multi.shape[multi.axis], 0):
# NOTE: shrink on the shard axis is only allowed when result is a single partition, denoted by the new real # NOTE: shrink on the shard axis is only allowed when result is a single partition, denoted by the new real
# we just copy it to all the devices, no real. this will be optimized out later # we just copy it to all the devices, no real. this will be optimized out later
non_shard_shrink = tuple((0, multi.src[0].shape[i]) if i == multi.axis else s for i, s in enumerate(root.marg)) non_shard_shrink = tuple((multi.src[0].shape[i], 0) if i == multi.axis else s for i, s in enumerate(root.marg))
return multi.src[0].copy_to_device(multi.device, arg=shard_bounds.index(root.marg[multi.axis]))._mop(Ops.SHRINK, non_shard_shrink) return multi.src[0].copy_to_device(multi.device, arg=shard_bounds.index(root.marg[multi.axis]))._mop(Ops.SHRINK, non_shard_shrink)
local_shrink = tuple((0, multi.src[0].shape[multi.axis]) if a == multi.axis else s for a,s in enumerate(root.marg)) local_shrink = tuple((multi.src[0].shape[multi.axis], 0) if a == multi.axis else s for a,s in enumerate(root.marg))
return multi.src[0]._mop(Ops.SHRINK, local_shrink).multi(multi.axis) return multi.src[0]._mop(Ops.SHRINK, local_shrink).multi(multi.axis)
def flip_multi(root:UOp, multi:UOp): def flip_multi(root:UOp, multi:UOp):
@@ -137,7 +137,7 @@ def rewrite_into_function(call:UOp):
def param_to_multi(p:UOp): def param_to_multi(p:UOp):
if p.axis is None: return None if p.axis is None: return None
return UOp.param(p.arg.slot, p.dtype, p.shard_shape, p.device, p.arg.vmin_vmax, p.arg.name, p.arg.addrspace).multi(p.axis) return UOp.param(p.arg, p.dtype, p.shard_shape, p.device).multi(p.axis)
# NOTE: this is the same pattern as Ops.UNROLL # NOTE: this is the same pattern as Ops.UNROLL
multi_pm = PatternMatcher([ multi_pm = PatternMatcher([
+10 -12
View File
@@ -1,7 +1,7 @@
from dataclasses import dataclass, field, replace from dataclasses import dataclass, field, replace
import itertools import itertools
from tinygrad.dtype import dtypes, PtrDType, AddrSpace, Invalid from tinygrad.dtype import dtypes, PtrDType, AddrSpace, Invalid
from tinygrad.uop.ops import PatternMatcher, UPat, Ops, UOp, resolve, GroupOp, _substitute, KernelInfo, ParamArg from tinygrad.uop.ops import PatternMatcher, UPat, Ops, UOp, resolve, GroupOp, _substitute, KernelInfo
from tinygrad.uop.ops import graph_rewrite, sint, AxisType, BottomUpGate, profile_matches, identity_element from tinygrad.uop.ops import graph_rewrite, sint, AxisType, BottomUpGate, profile_matches, identity_element
from tinygrad.uop.symbolic import symbolic from tinygrad.uop.symbolic import symbolic
from tinygrad.helpers import prod, all_same, getenv, dedup, all_int, DEBUG, SPLIT_REDUCEOP, DEBUG_RANGEIFY, VIZ, MAX_KERNEL_BUFFERS from tinygrad.helpers import prod, all_same, getenv, dedup, all_int, DEBUG, SPLIT_REDUCEOP, DEBUG_RANGEIFY, VIZ, MAX_KERNEL_BUFFERS
@@ -52,7 +52,7 @@ def found_after(ctx:dict[UOp, UOp], after:UOp, src:UOp):
if x.op is Ops.PERMUTE: x, after = x.src[0], after.permute(argsort(x.marg)) if x.op is Ops.PERMUTE: x, after = x.src[0], after.permute(argsort(x.marg))
elif x.op is Ops.RESHAPE: x, after = x.src[0], after.reshape(x.src[0].shape) elif x.op is Ops.RESHAPE: x, after = x.src[0], after.reshape(x.src[0].shape)
elif x.op is Ops.WHERE and x.src[2].base.arg == Invalid and x.src[1].op is Ops.PAD: elif x.op is Ops.WHERE and x.src[2].base.arg == Invalid and x.src[1].op is Ops.PAD:
x, after = x.src[1].src[0], after.shrink(tuple((o, s+o) for (o,_),s in zip(x.src[1].marg, x.src[1].src[0].shape))) x, after = x.src[1].src[0], after.shrink(tuple((o, s+o) for (_,o),s in zip(x.src[1].marg, x.src[1].src[0].shape)))
else: break else: break
ctx[x] = after ctx[x] = after
@@ -130,15 +130,15 @@ def resolve_function(c:UOp, allow_param_mismatch=True) -> UOp|None:
if c.arg.precompile: return None if c.arg.precompile: return None
params: list[UOp] = [] params: list[UOp] = []
graph_rewrite(c.src[0], pm_gather_params, bottom_up=True, ctx=params, name="gather params") graph_rewrite(c.src[0], pm_gather_params, bottom_up=True, ctx=params, name="gather params")
params = sorted(params, key=lambda x: x.arg.slot) params = sorted(params, key=lambda x: x.arg)
args = c.src[1:] args = c.src[1:]
# NOTE: this isn't really needed. it's okay if there's unused args in the function # NOTE: this isn't really needed. it's okay if there's unused args in the function
if not allow_param_mismatch: if not allow_param_mismatch:
if [x.arg.slot for x in params] != list(range(len(params))): raise RuntimeError(f"params not in order: {[x.arg.slot for x in params]}") if [x.arg for x in params] != list(range(len(params))): raise RuntimeError(f"params not in order: {[x.arg for x in params]}")
if len(params) != len(args): raise TypeError(f"expected {len(params)} args, got {len(args)}") if len(params) != len(args): raise TypeError(f"expected {len(params)} args, got {len(args)}")
dict_map = {x:args[x.arg.slot] for x in params} dict_map = {x:args[x.arg] for x in params}
for i, (p, a) in enumerate(dict_map.items()): for i, (p, a) in enumerate(dict_map.items()):
if p.axis != a.axis: raise TypeError(f"arg {i} axis mismatch: expected {p.axis}, got {a.axis}") if p.axis != a.axis: raise TypeError(f"arg {i} axis mismatch: expected {p.axis}, got {a.axis}")
if p.max_shape != a.max_shape: raise TypeError(f"arg {i} shape mismatch: expected {p.shape}, got {a.shape}") if p.max_shape != a.max_shape: raise TypeError(f"arg {i} shape mismatch: expected {p.shape}, got {a.shape}")
@@ -477,7 +477,7 @@ class LocalAddBufferContext:
opts:tuple|None = None opts:tuple|None = None
def debuf(ctx:LocalAddBufferContext, buf:UOp): def debuf(ctx:LocalAddBufferContext, buf:UOp):
ret = UOp(Ops.PARAM, buf.dtype.ptr(prod(buf.max_shape), buf.addrspace), arg=ParamArg(ctx.dg, addrspace=buf.addrspace)).reshape(buf.max_shape) ret = UOp(Ops.PARAM, buf.dtype.ptr(prod(buf.max_shape)), arg=ctx.dg).reshape(buf.max_shape)
# if the buffer has symbolic shape, shrink the max-sized view to the actual shape # if the buffer has symbolic shape, shrink the max-sized view to the actual shape
if buf.max_shape != buf.shape: ret = ret.shrink(tuple((0, s) for s in buf.shape)) if buf.max_shape != buf.shape: ret = ret.shrink(tuple((0, s) for s in buf.shape))
if buf not in ctx.map: ctx.map[buf] = buf if buf not in ctx.map: ctx.map[buf] = buf
@@ -489,7 +489,7 @@ def unbind_kernel(ctx:LocalAddBufferContext, b:UOp):
return b.src[0] return b.src[0]
def handle_after(ctx:LocalAddBufferContext, after:UOp): def handle_after(ctx:LocalAddBufferContext, after:UOp):
if isinstance(after.dtype, PtrDType) and after.addrspace == AddrSpace.LOCAL: return None if isinstance(after.dtype, PtrDType) and after.ptrdtype.addrspace == AddrSpace.LOCAL: return None
buf = after.buf_uop buf = after.buf_uop
# HACK to put the buffer in the MAP instead of MSTACK/MSELECT # HACK to put the buffer in the MAP instead of MSTACK/MSELECT
if buf.op in {Ops.MSTACK, Ops.MSELECT}: buf = buf.src[0] if buf.op in {Ops.MSTACK, Ops.MSELECT}: buf = buf.src[0]
@@ -512,11 +512,9 @@ def find_bufs(x:UOp):
to_define_global = PatternMatcher([ to_define_global = PatternMatcher([
(UPat(Ops.STORE, name="x"), find_bufs), (UPat(Ops.STORE, name="x"), find_bufs),
(UPat(Ops.BUFFER, name="buf"), debuf), (UPat(Ops.BUFFER, name="buf"), debuf),
(UPat(Ops.PARAM, name="v"), lambda v: (UPat(Ops.PARAM, src=(UPat(), UPat(Ops.DEVICE)), name="buf"), debuf),
UOp.variable(v.arg.name, v.arg.vmin_vmax[0], v.arg.vmin_vmax[1], v.dtype) (UPat(Ops.PARAM, src=(UPat(), UPat(), UPat.cvar('vmin'), UPat.cvar('vmax'), UPat.var("nm")), name="v"),
if v.arg.name is not None and v.arg.vmin_vmax is not None else None), lambda v, vmin, vmax, nm: UOp.variable(nm.arg, vmin.arg, vmax.arg, v.dtype)),
(UPat(Ops.PARAM, name="buf"), lambda ctx, buf:
None if isinstance(buf.dtype, PtrDType) or buf.arg.name is not None or buf._shape is None else debuf(ctx, buf)),
(UPat(Ops.INDEX, src=(UPat(Ops.DEFINE_VAR, name="v"),)), lambda v: v), (UPat(Ops.INDEX, src=(UPat(Ops.DEFINE_VAR, name="v"),)), lambda v: v),
(UPat(Ops.BIND, name="b"), unbind_kernel), (UPat(Ops.BIND, name="b"), unbind_kernel),
+6 -5
View File
@@ -160,7 +160,7 @@ class Tensor(OpMixin):
def const_like(self, b:ConstType) -> Tensor: return Tensor(self.uop.const_like(b)) def const_like(self, b:ConstType) -> Tensor: return Tensor(self.uop.const_like(b))
@staticmethod @staticmethod
def const(dtype:DType, b:ConstType|UOp, device:str|tuple[str, ...]|None=None) -> Tensor: def const(dtype:DType, b:ConstType|UOp, device:str|tuple[str, ...]|None=None) -> Tensor:
return Tensor(UOp.const(dtype, b, device)) return Tensor(b if isinstance(b, UOp) else UOp.const(dtype, b, device))
@staticmethod @staticmethod
def unique_const(fill_value:ConstType|UOp, **kwargs) -> Tensor: def unique_const(fill_value:ConstType|UOp, **kwargs) -> Tensor:
if isinstance(fill_value, UOp): return Tensor(fill_value, **kwargs) if isinstance(fill_value, UOp): return Tensor(fill_value, **kwargs)
@@ -203,7 +203,7 @@ class Tensor(OpMixin):
def as_param(self, slot:int): def as_param(self, slot:int):
if self.uop.axis is not None: if self.uop.axis is not None:
param = UOp.param(slot, self.dtype, self.uop.shard_shape, self.device, axis=self.uop.axis) param = UOp.param(slot, self.dtype, self.uop.shard_shape, self.device).multi(self.uop.axis)
else: else:
param = UOp.param(slot, self.dtype, self.shape, self.device) param = UOp.param(slot, self.dtype, self.shape, self.device)
return Tensor(param) return Tensor(param)
@@ -580,7 +580,7 @@ class Tensor(OpMixin):
def _multi_like(self, fxn, *args, **kwargs) -> Tensor: def _multi_like(self, fxn, *args, **kwargs) -> Tensor:
dtype = kwargs.pop("dtype", self.dtype) dtype = kwargs.pop("dtype", self.dtype)
if kwargs.pop("device", None) is not None: raise RuntimeError("cannot specify `device` on `*_like` of a multi device tensor") if kwargs.get("device") is not None: raise RuntimeError("cannot specify `device` on `*_like` of a multi device tensor")
assert isinstance(self.device, tuple), f"_multi_like needs a multi device tensor, got {self.device}" assert isinstance(self.device, tuple), f"_multi_like needs a multi device tensor, got {self.device}"
if self.uop.axis is None: return fxn(self.shape, *args, dtype=dtype, **kwargs).shard(self.device) if self.uop.axis is None: return fxn(self.shape, *args, dtype=dtype, **kwargs).shard(self.device)
stacked = UOp.mstack(*[fxn(self.uop.shard_shape, *args, device=d, dtype=dtype, **kwargs).uop for d in self.device]) stacked = UOp.mstack(*[fxn(self.uop.shard_shape, *args, device=d, dtype=dtype, **kwargs).uop for d in self.device])
@@ -598,8 +598,9 @@ class Tensor(OpMixin):
print(Tensor.full_like(t, 42).numpy()) print(Tensor.full_like(t, 42).numpy())
``` ```
""" """
if isinstance(self.device, tuple): return self._multi_like(Tensor.full, fill_value, dtype=dtype or self.dtype, device=device) if device is None: return super().full_like(fill_value, dtype)
return Tensor.full(self.shape, fill_value, dtype=dtype or self.dtype, device=self.device if device is None else device) if isinstance(self.device, tuple): raise RuntimeError("cannot specify `device` on `full_like` of a multi device tensor")
return Tensor.full(self.shape, fill_value, dtype=dtype or self.dtype, device=device)
def rand_like(self, **kwargs) -> Tensor: def rand_like(self, **kwargs) -> Tensor:
""" """
+2 -2
View File
@@ -477,7 +477,7 @@ def get_late_rewrite_patterns(ops:tuple[Ops, ...], disable_fast_idiv:bool) -> Pa
if (v:=powers_of_two.get(c.arg, 0)) else None)] if (v:=powers_of_two.get(c.arg, 0)) else None)]
if not disable_fast_idiv: if not disable_fast_idiv:
# fast_idiv handles non-pow2: only fire on non-negative inputs (signed magic-mul is unreliable for x<0) # fast_idiv handles non-pow2: only fire on non-negative inputs (signed magic-mul is unreliable for x<0)
pat += [(UPat(Ops.CDIV, src=(UPat.var("x", dtypes.ints), UPat.cvar("d"))), pat += [(UPat(Ops.CDIV, src=(UPat.var("x", dtypes.ints), UPat.cvar("d", vec=False))),
lambda ctx, x, d: fast_idiv(ctx, x, d.arg) if x.vmin >= 0 or x.dtype in dtypes.uints else None)] lambda ctx, x, d: fast_idiv(ctx, x, d.arg) if x.vmin >= 0 or x.dtype in dtypes.uints else None)]
# rewrite raw CMOD -> x - d*CDIV(x,d) so fast_idiv can pick up the CDIV. only on non-negative inputs; # rewrite raw CMOD -> x - d*CDIV(x,d) so fast_idiv can pick up the CDIV. only on non-negative inputs;
# avoids disturbing floormod_to_mod's general-path output (which uses a trunc Ops.CMOD as an implementation detail) # avoids disturbing floormod_to_mod's general-path output (which uses a trunc Ops.CMOD as an implementation detail)
@@ -493,7 +493,7 @@ def get_late_rewrite_patterns(ops:tuple[Ops, ...], disable_fast_idiv:bool) -> Pa
((UPat.cvar("c", dtypes.sints) < UPat.var("x", dtypes.sints)).logical_not(), lambda x,c: x<c+1), ((UPat.cvar("c", dtypes.sints) < UPat.var("x", dtypes.sints)).logical_not(), lambda x,c: x<c+1),
(UPat.var("x", dtypes.sints)*-1 < UPat.var("y", dtypes.sints)*UPat.cvar("c"), lambda x,y,c: y*(-c)<x), (UPat.var("x", dtypes.sints)*-1 < UPat.var("y", dtypes.sints)*UPat.cvar("c"), lambda x,y,c: y*(-c)<x),
(UPat.var("x", dtypes.sints)*-1 < UPat.cvar("c"), lambda x,c:-c<x), (UPat.var("x", dtypes.sints)*-1 < UPat.cvar("c"), lambda x,c:-c<x),
((UPat.cvar("c1")<UPat.var("x", dtypes.sints)) & (UPat.var("x", dtypes.sints)<UPat.cvar("c2")), ((UPat.cvar("c1",vec=False)<UPat.var("x", dtypes.sints)) & (UPat.var("x", dtypes.sints)<UPat.cvar("c2",vec=False)),
lambda x,c1,c2: x.eq(c1+1) if c1.arg+1==c2.arg-1 else None), # (c-1)<x & x<(c+1) -> x==c lambda x,c1,c2: x.eq(c1+1) if c1.arg+1==c2.arg-1 else None), # (c-1)<x & x<(c+1) -> x==c
] ]
if Ops.CMPEQ in ops: pat += [(UPat.var('x').ne(UPat.var('y')).logical_not(), lambda x,y: x.alu(Ops.CMPEQ, y))] if Ops.CMPEQ in ops: pat += [(UPat.var('x').ne(UPat.var('y')).logical_not(), lambda x,y: x.alu(Ops.CMPEQ, y))]
+1 -1
View File
@@ -108,7 +108,7 @@ div_and_mod_symbolic = PatternMatcher([
# (x//c+a)//d -> (x+a*c)//(c*d) for c>0, d>0 # (x//c+a)//d -> (x+a*c)//(c*d) for c>0, d>0
((UPat.var("x")//UPat.cvar("c") + UPat.cvar("a"))//UPat.cvar("d"), lambda x,c,a,d: (x+a*c)//(c*d) if c.vmin>0 and d.vmin>0 else None), ((UPat.var("x")//UPat.cvar("c") + UPat.cvar("a"))//UPat.cvar("d"), lambda x,c,a,d: (x+a*c)//(c*d) if c.vmin>0 and d.vmin>0 else None),
# (x+c)//d -> (x+c%d)//d + c//d for d>0 (split out the multiple of d in the constant) # (x+c)//d -> (x+c%d)//d + c//d for d>0 (split out the multiple of d in the constant)
((UPat.var("x", dtypes.weakint)+UPat.cvar("c"))//UPat.cvar("d"), ((UPat.var("x", dtypes.weakint)+UPat.cvar("c", vec=False))//UPat.cvar("d", vec=False),
lambda x,c,d: (x+c.arg%d.arg)//d + c.arg//d.arg if c.arg%d.arg!=c.arg and d.arg>0 else None), lambda x,c,d: (x+c.arg%d.arg)//d + c.arg//d.arg if c.arg%d.arg!=c.arg and d.arg>0 else None),
# ** 2. Slow Rules ** # ** 2. Slow Rules **
+34 -58
View File
@@ -18,19 +18,6 @@ class AxisType(Enum):
def __repr__(self): return str(self) def __repr__(self): return str(self)
GLOBAL = auto(); WARP = auto(); LOCAL = auto(); LOOP = auto(); GROUP_REDUCE = auto(); REDUCE = auto(); UPCAST = auto(); UNROLL = auto() # noqa: E702 GLOBAL = auto(); WARP = auto(); LOCAL = auto(); LOOP = auto(); GROUP_REDUCE = auto(); REDUCE = auto(); UPCAST = auto(); UNROLL = auto() # noqa: E702
THREAD = auto(); PLACEHOLDER = auto() # noqa: E702 THREAD = auto(); PLACEHOLDER = auto() # noqa: E702
@dataclass(frozen=True, order=True)
class ParamArg:
slot: int
vmin_vmax: tuple[PyConst, PyConst]|None = None
name: str|None = None
addrspace: AddrSpace = AddrSpace.GLOBAL
axis: int|None = None
device: str|tuple[str, ...]|None = None
def __repr__(self):
fields = (("vmin_vmax", None), ("name", None), ("addrspace", AddrSpace.GLOBAL), ("axis", None), ("device", None))
args = [str(self.slot)] + [f"{k}={v!r}" for k,default in fields if (v:=getattr(self, k)) != default]
return f"ParamArg({', '.join(args)})"
axis_letters = {AxisType.GLOBAL: "g", AxisType.THREAD: "t", AxisType.LOCAL: "l", AxisType.WARP: "w", AxisType.LOOP: "L", AxisType.UPCAST: "u", axis_letters = {AxisType.GLOBAL: "g", AxisType.THREAD: "t", AxisType.LOCAL: "l", AxisType.WARP: "w", AxisType.LOOP: "L", AxisType.UPCAST: "u",
AxisType.GROUP_REDUCE: "G", AxisType.REDUCE: "R", AxisType.UNROLL: "r"} AxisType.GROUP_REDUCE: "G", AxisType.REDUCE: "R", AxisType.UNROLL: "r"}
axis_colors = {AxisType.GLOBAL: "blue", AxisType.THREAD: "BLUE", AxisType.LOCAL: "cyan", AxisType.WARP: "CYAN", AxisType.LOOP: "WHITE", axis_colors = {AxisType.GLOBAL: "blue", AxisType.THREAD: "BLUE", AxisType.LOCAL: "cyan", AxisType.WARP: "CYAN", AxisType.LOOP: "WHITE",
@@ -294,7 +281,9 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
case Ops.PARAM: case Ops.PARAM:
if isinstance(self.dtype, ImageDType): return self.dtype.shape if isinstance(self.dtype, ImageDType): return self.dtype.shape
if isinstance(self.dtype, PtrDType): return (self.ptrdtype.size,) if isinstance(self.dtype, PtrDType): return (self.ptrdtype.size,)
return tuple(self.src[0].sgep(i) for i in range(self.src[0].dtype.count)) if len(self.src) >= 1 else None # NOTE: copied from marg
if len(self.src) >= 1: return tuple(self.src[0].sgep(i) for i in range(self.src[0].dtype.count))
return None
# wmma output shape = accumulator shape (src[2]) # wmma output shape = accumulator shape (src[2])
case Ops.WMMA | Ops.SHAPED_WMMA: return self.src[2]._shape case Ops.WMMA | Ops.SHAPED_WMMA: return self.src[2]._shape
@@ -316,7 +305,7 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
return ps[:-1]+(ssimplify((ps[-1]*input_sz) // output_sz),) if len(ps) > 0 else ps return ps[:-1]+(ssimplify((ps[-1]*input_sz) // output_sz),) if len(ps) > 0 else ps
return ps return ps
# MULTI marker has no shape # MULTI marker (axis info in PARAM sources) has no shape
case Ops.MULTI if len(self.src) == 0: return None case Ops.MULTI if len(self.src) == 0: return None
# movement ops change the shape # movement ops change the shape
@@ -338,20 +327,20 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
return tuple(ps[i] for i in self.marg) return tuple(ps[i] for i in self.marg)
case Ops.PAD: case Ops.PAD:
# TODO: why do i need resolve here? # TODO: why do i need resolve here?
if len(ps) != len(self.marg) or not all(resolve(sz>=0) and resolve(0<=o) and resolve(o+s<=sz) for s,(o,sz) in zip(ps, self.marg)): if len(ps) != len(self.marg) or not all(resolve(sz>=0) and resolve(0<=o) and resolve(o+s<=sz) for s,(sz,o) in zip(ps, self.marg)):
raise ValueError(f"invalid pad {self.marg} for {ps}") raise ValueError(f"invalid pad {self.marg} for {ps}")
return tuple(ssimplify(sz) for _,sz in self.marg) return tuple(ssimplify(sz) for sz,_ in self.marg)
case Ops.SHRINK: case Ops.SHRINK:
# TODO: why do i need resolve here? # TODO: why do i need resolve here?
if len(ps) != len(self.marg) or not all(resolve(0<=o) and resolve(sz>=0) and resolve(o+sz<=s) for s,(o,sz) in zip(ps, self.marg)): if len(ps) != len(self.marg) or not all(resolve(0<=b) and resolve(sz>=0) and resolve(b+sz<=s) for s,(sz,b) in zip(ps, self.marg)):
raise ValueError(f"invalid shrink {self.marg} for {ps}") raise ValueError(f"invalid shrink {self.marg} for {ps}")
return tuple(ssimplify(sz) for _,sz in self.marg) return tuple(ssimplify(sz) for sz,_ in self.marg)
case Ops.FLIP: case Ops.FLIP:
if len(ps) != len(self.marg) or not all(isinstance(x, bool) for x in self.marg): raise ValueError(f"bad flip on {ps}, {self.marg}") if len(ps) != len(self.marg) or not all(isinstance(x, bool) for x in self.marg): raise ValueError(f"bad flip on {ps}, {self.marg}")
return ps return ps
case Ops.MULTI: return tuple(s*len(self.device) if a == self.axis else s for a,s in enumerate(ps)) case Ops.MULTI: return tuple(s*len(self.device) if a == self.axis else s for a,s in enumerate(ps))
case Ops.REDUCE: case Ops.REDUCE:
axis_arg = self.arg[1] axis_arg = self.arg[1] if self.op is Ops.REDUCE else self.arg[7]
if not isinstance(axis_arg, tuple) or not all(isinstance(x, int) and x>=0 and x<len(ps) for x in axis_arg): if not isinstance(axis_arg, tuple) or not all(isinstance(x, int) and x>=0 and x<len(ps) for x in axis_arg):
raise ValueError(f"invalid type for axis: {axis_arg}") raise ValueError(f"invalid type for axis: {axis_arg}")
return tuple(1 if i in axis_arg else s for i,s in enumerate(ps)) return tuple(1 if i in axis_arg else s for i,s in enumerate(ps))
@@ -379,7 +368,6 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
@property @property
def max_shape(self) -> tuple[int, ...]: return to_max_shape(self.shape) def max_shape(self) -> tuple[int, ...]: return to_max_shape(self.shape)
def max_numel(self) -> int: return prod(self.max_shape)
@property @property
def shard_shape(self) -> tuple[sint, ...]: def shard_shape(self) -> tuple[sint, ...]:
@@ -537,7 +525,7 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
return UOp(op, out_dtype, all_srcs, **kwargs) return UOp(op, out_dtype, all_srcs, **kwargs)
@staticmethod @staticmethod
def const(dtype:DType, b:ConstLike, device:str|tuple[str, ...]|None=None, shape:tuple[sint, ...]|None=None): def const(dtype:DType, b:ConstLike, device:str|tuple[str, ...]|None=None, shape:tuple[sint, ...]|None=None):
if isinstance(b, UOp): return b.cast(dtype) if isinstance(b, UOp): return b.unbind()[0] if b.op is Ops.BIND else b
if isinstance(b, tuple) and all_same(b): if isinstance(b, tuple) and all_same(b):
assert len(b) > 0, "can't create const from empty tuple" assert len(b) > 0, "can't create const from empty tuple"
b = b[0] # doesn't have to be a STACK if they are all the same b = b[0] # doesn't have to be a STACK if they are all the same
@@ -566,7 +554,7 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
@staticmethod @staticmethod
def invalid(count=1): return UOp(Ops.CONST, dtypes.weakint.vec(count), src=(), arg=Invalid) def invalid(count=1): return UOp(Ops.CONST, dtypes.weakint.vec(count), src=(), arg=Invalid)
def valid(self, cond): def valid(self, cond):
return cond.where(self.cast(dtypes.weakint), UOp.invalid(self.dtype.count)) return self if cond.op is Ops.WHERE and cond.arg else cond.where(self.cast(dtypes.weakint), UOp.invalid(self.dtype.count))
def get_idx(self) -> UOp: def get_idx(self) -> UOp:
assert self.dtype.scalar() is dtypes.weakint, "Can only call get_idx on index dtype" assert self.dtype.scalar() is dtypes.weakint, "Can only call get_idx on index dtype"
return self.src[1] if self.op is Ops.WHERE and self.src[2].arg is Invalid else self return self.src[1] if self.op is Ops.WHERE and self.src[2].arg is Invalid else self
@@ -622,12 +610,16 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
if self.op is Ops.GETTUPLE: if self.op is Ops.GETTUPLE:
in_tuple = self.src[0].src[0] if self.src[0].op is Ops.FUNCTION else self.src[0] in_tuple = self.src[0].src[0] if self.src[0].op is Ops.FUNCTION else self.src[0]
return in_tuple.src[self.arg].axis if in_tuple.op is Ops.TUPLE else None return in_tuple.src[self.arg].axis if in_tuple.op is Ops.TUPLE else None
if self.op is Ops.PARAM: return self.arg.axis # PARAM: axis is stored as a MULTI source
if self.op is Ops.PARAM:
for s in self.src:
if s.op is Ops.MULTI: return s.arg
return None
# NOTE: they all have to share an axis, we always choose [-1] # NOTE: they all have to share an axis, we always choose [-1]
if self.op in GroupOp.ALU: return axes[-1] if (axes := dedup([x.axis for x in self.src if x.axis is not None])) else None if self.op in GroupOp.ALU: return axes[-1] if (axes := dedup([x.axis for x in self.src if x.axis is not None])) else None
if len(self.src) == 0: return None if len(self.src) == 0: return None
src_axis = self.src[0].axis src_axis = self.src[0].axis
if self.op is Ops.SHRINK and src_axis is not None and self.marg[src_axis] != (0, self.src[0].shape[src_axis]): if self.op is Ops.SHRINK and src_axis is not None and self.marg[src_axis] != (self.src[0].shape[src_axis], 0):
return None # SHRINK will remove the sharding if it's on axis return None # SHRINK will remove the sharding if it's on axis
if self.op is Ops.REDUCE: return None if src_axis is not None and src_axis in self.arg[1] else src_axis if self.op is Ops.REDUCE: return None if src_axis is not None and src_axis in self.arg[1] else src_axis
if self.op is Ops.RESHAPE: if self.op is Ops.RESHAPE:
@@ -739,7 +731,6 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
return ret.after(ret.store(src)) return ret.after(ret.store(src))
@recursive_property @recursive_property
def device(self) -> str|tuple[str, ...]|None: def device(self) -> str|tuple[str, ...]|None:
if self.op is Ops.PARAM: return self.arg.device
if self.op is Ops.DEVICE: return self.arg if self.op is Ops.DEVICE: return self.arg
if self.op is Ops.STAGE: return self.arg.device if self.op is Ops.STAGE: return self.arg.device
if self.op is Ops.AFTER: return self.src[0].device if self.op is Ops.AFTER: return self.src[0].device
@@ -751,22 +742,6 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
for x in self.src: for x in self.src:
if x.device is not None: return x.device if x.device is not None: return x.device
return None return None
@recursive_property
def addrspace(self) -> AddrSpace|None:
if self.op is Ops.PARAM: return self.arg.addrspace
if self.op is Ops.BUFFER: return AddrSpace.GLOBAL
if self.op is Ops.DEFINE_LOCAL: return AddrSpace.LOCAL
if self.op is Ops.DEFINE_REG: return AddrSpace.REG
# LOAD brings things into registers
if self.op is Ops.LOAD: return AddrSpace.REG
if self.op in {Ops.INDEX, Ops.CAST, Ops.AFTER, Ops.REDUCE, Ops.GEP}:
return self.src[0].addrspace
if self.op in GroupOp.Movement: return self.src[0].addrspace
if self.op is Ops.STACK or self.op in GroupOp.Elementwise:
ad = [x.addrspace for x in self.src if x.addrspace is not None]
if not len(ad) or not all_same(ad): return None
return ad[0]
return None
@property @property
def buf_uop(self) -> UOp: def buf_uop(self) -> UOp:
if self.op in {Ops.BUFFER, Ops.PARAM}: return self if self.op in {Ops.BUFFER, Ops.PARAM}: return self
@@ -974,7 +949,7 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
# float has NAN issue and we use explicit NAN in transcendental # float has NAN issue and we use explicit NAN in transcendental
if self.op is Ops.WHERE and dtypes.is_int(self.dtype): return min(self.src[1].vmin, self.src[2].vmin), max(self.src[1].vmax, self.src[2].vmax) if self.op is Ops.WHERE and dtypes.is_int(self.dtype): return min(self.src[1].vmin, self.src[2].vmin), max(self.src[1].vmax, self.src[2].vmax)
# NOTE: returned UOp is assumed to be CONST # NOTE: returned UOp is assumed to be CONST
if self.op is Ops.PARAM and self.arg.vmin_vmax is not None: return self.arg.vmin_vmax if self.op is Ops.PARAM and len(self.src) >= 4: return self.src[2].arg, self.src[3].arg
if self.op is Ops.DEFINE_VAR and self.arg: return self.arg[1], self.arg[2] if self.op is Ops.DEFINE_VAR and self.arg: return self.arg[1], self.arg[2]
if self.op in (Ops.RANGE, Ops.SPECIAL): return 0, (self.src[0]-1).vmax if self.op in (Ops.RANGE, Ops.SPECIAL): return 0, (self.src[0]-1).vmax
if self.op is Ops.BIND: return self.src[0]._min_max # ignore the bound value if self.op is Ops.BIND: return self.src[0]._min_max # ignore the bound value
@@ -1019,8 +994,7 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
@staticmethod @staticmethod
def placeholder(shape:tuple[int, ...], dtype:DType, slot:int, addrspace=AddrSpace.GLOBAL): def placeholder(shape:tuple[int, ...], dtype:DType, slot:int, addrspace=AddrSpace.GLOBAL):
lookup = {AddrSpace.GLOBAL: Ops.PARAM, AddrSpace.LOCAL: Ops.DEFINE_LOCAL, AddrSpace.REG: Ops.DEFINE_REG} lookup = {AddrSpace.GLOBAL: Ops.PARAM, AddrSpace.LOCAL: Ops.DEFINE_LOCAL, AddrSpace.REG: Ops.DEFINE_REG}
arg = ParamArg(slot, addrspace=addrspace) if addrspace is AddrSpace.GLOBAL else slot ret = UOp(lookup[addrspace], dtype.ptr(prod(shape), addrspace), arg=slot)
ret = UOp(lookup[addrspace], dtype.ptr(prod(shape), addrspace), arg=arg)
if len(shape) > 1: ret = ret.reshape(shape) if len(shape) > 1: ret = ret.reshape(shape)
return ret return ret
def placeholder_like(self, slot:int): def placeholder_like(self, slot:int):
@@ -1033,17 +1007,18 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
# TODO: this should replace placeholder # TODO: this should replace placeholder
@staticmethod @staticmethod
def param(slot:int, dtype:DType, shape:tuple[sint, ...]|None=None, device=None, vmin_vmax:tuple[PyConst, PyConst]|None=None, name=None, def param(slot:int, dtype:DType, shape:tuple[sint, ...]|None=None, device=None, vmin_vmax:tuple[PyConst, PyConst]|None=None, name=None):
addrspace=AddrSpace.GLOBAL, axis:int|None=None): src: tuple[UOp, ...] = (UOp(Ops.NOOP) if shape is None else shape_to_shape_arg(shape),) + \
if shape is not None and axis is not None and isinstance(device, tuple): (UOp(Ops.NOOP) if device is None else UOp(Ops.DEVICE, arg=device),)
shape = tuple(s*len(device) if i == axis else s for i,s in enumerate(shape)) if vmin_vmax is not None: src += (UOp.const(dtype, vmin_vmax[0]), UOp.const(dtype.scalar(), vmin_vmax[1]))
src: tuple[UOp, ...] = (UOp(Ops.NOOP) if shape is None else shape_to_shape_arg(shape),) if name is not None: src += (UOp(Ops.NOOP, arg=name),)
return UOp(Ops.PARAM, dtype, src, arg=ParamArg(slot, vmin_vmax, name, addrspace, axis, device)) return UOp(Ops.PARAM, dtype, src, arg=slot)
def param_like(self, slot:int): def param_like(self, slot:int):
addrspace = self.addrspace if isinstance(self.dtype, (PtrDType, ImageDType)) else AddrSpace.GLOBAL
if self.op is Ops.BIND: if self.op is Ops.BIND:
return UOp.param(slot, self.dtype, self._shape, self.device, cast(tuple[int, int], self._min_max), self.src[0].arg[0], addrspace) return UOp.param(slot, self.dtype, self._shape, self.device, self._min_max, self.src[0].arg[0])
return UOp.param(slot, self.dtype, self.shard_shape if self.axis is not None else self._shape, self.device, addrspace=addrspace, axis=self.axis) p = UOp.param(slot, self.dtype, self._shape, self.device)
if self.axis is not None: p = p.replace(src=p.src + (UOp(Ops.MULTI, arg=self.axis),))
return p
# opaque bodies stay as Ops.CALL; value-producing bodies become Ops.FUNCTION (wrapped in TUPLE) # opaque bodies stay as Ops.CALL; value-producing bodies become Ops.FUNCTION (wrapped in TUPLE)
_OPAQUE_CALL_BODIES = {Ops.SINK, Ops.PROGRAM, Ops.LINEAR, Ops.COPY, Ops.SLICE, Ops.CUSTOM_FUNCTION} _OPAQUE_CALL_BODIES = {Ops.SINK, Ops.PROGRAM, Ops.LINEAR, Ops.COPY, Ops.SLICE, Ops.CUSTOM_FUNCTION}
@@ -1107,10 +1082,10 @@ class ProgramInfo:
local_size: list[int]|None = [1, 1, 1] local_size: list[int]|None = [1, 1, 1]
for u in sink.toposort(): for u in sink.toposort():
if u.op is Ops.DEFINE_VAR: _vars.append(u) if u.op is Ops.DEFINE_VAR: _vars.append(u)
if u.op is Ops.PARAM: _globals.append(u.arg.slot) if u.op is Ops.PARAM: _globals.append(u.arg)
if u.op in (Ops.STORE, Ops.LOAD): if u.op in (Ops.STORE, Ops.LOAD):
if (idx:=u.src[0]).op is Ops.INDEX or (u.src[0].op is Ops.CAST and (idx:=u.src[0].src[0]).op is Ops.INDEX): if (idx:=u.src[0]).op is Ops.INDEX or (u.src[0].op is Ops.CAST and (idx:=u.src[0].src[0]).op is Ops.INDEX):
if (buf:=idx.src[0]).op is Ops.PARAM: (outs if u.op is Ops.STORE else ins).append(buf.arg.slot) if (buf:=idx.src[0]).op is Ops.PARAM: (outs if u.op is Ops.STORE else ins).append(buf.arg)
if u.op is Ops.SPECIAL: if u.op is Ops.SPECIAL:
if u.arg[0] == 'i': local_size = None if u.arg[0] == 'i': local_size = None
special_size = local_size if u.arg[0] == 'l' else global_size special_size = local_size if u.arg[0] == 'l' else global_size
@@ -1236,7 +1211,8 @@ class UPat(OpMixin):
def var(name:str|None=None, dtype:DType|tuple[DType, ...]|None=None): return UPat(dtype=dtype, name=name) def var(name:str|None=None, dtype:DType|tuple[DType, ...]|None=None): return UPat(dtype=dtype, name=name)
@staticmethod @staticmethod
@functools.cache @functools.cache
def cvar(name:str|None=None, dtype:DType|tuple[DType, ...]|None=None, arg=None): return UPat(Ops.CONST, dtype, name=name, arg=arg) def cvar(name:str|None=None, dtype:DType|tuple[DType, ...]|None=None, vec=True, arg=None):
return UPat(Ops.CONST, dtype, name=name, arg=arg)
@staticmethod @staticmethod
def const(dtype:DType|tuple[DType, ...]|None, b:ConstType, device=None): return UPat(Ops.CONST, dtype=dtype, arg=b) def const(dtype:DType|tuple[DType, ...]|None, b:ConstType, device=None): return UPat(Ops.CONST, dtype=dtype, arg=b)
@@ -1654,7 +1630,7 @@ pm_lower_index_dtype = PatternMatcher([
def _index_to_concrete_int(u:UOp) -> UOp: return graph_rewrite(u.sink(), pm_lower_index_dtype).src[0] def _index_to_concrete_int(u:UOp) -> UOp: return graph_rewrite(u.sink(), pm_lower_index_dtype).src[0]
_substitute = PatternMatcher([(UPat(tuple(Ops), name="x"), lambda ctx,x: ctx.get(x,None))]) _substitute = PatternMatcher([(UPat(tuple(Ops), name="x"), lambda ctx,x: ctx.get(x,None))])
_pm_resolve_params = PatternMatcher([(UPat(Ops.PARAM, name="p"), lambda ctx,p: ctx[p.arg.slot])]) _pm_resolve_params = PatternMatcher([(UPat(Ops.PARAM, name="p"), lambda ctx,p: ctx[p.arg])])
remove_all_tags = PatternMatcher([(UPat(GroupOp.All, name="x"), lambda x: x.replace(tag=None) if x.tag is not None else None)]) remove_all_tags = PatternMatcher([(UPat(GroupOp.All, name="x"), lambda x: x.replace(tag=None) if x.tag is not None else None)])
def gate_kernel_sink(x:UOp) -> bool: def gate_kernel_sink(x:UOp) -> bool:
+1 -1
View File
@@ -34,7 +34,7 @@ def strip_binary_parens(x:UOp, left:str, right:str, code_for_op) -> str:
renderer = PatternMatcher([ renderer = PatternMatcher([
(UPat((Ops.DEFINE_VAR,), name="x"), lambda x: x.expr), (UPat((Ops.DEFINE_VAR,), name="x"), lambda x: x.expr),
(UPat(Ops.PARAM, name="x"), lambda x: x.arg.name if x.arg.name is not None else f"p{x.arg.slot}"), (UPat(Ops.PARAM, src=(UPat(), UPat(), UPat(), UPat(), UPat(Ops.NOOP, name="x"))), lambda x: x.arg),
(UPat((Ops.SPECIAL), name="x"), lambda x: x.arg), (UPat((Ops.SPECIAL), name="x"), lambda x: x.arg),
(UPat(Ops.RANGE, name="x"), lambda x: f"r{range_str(x)}"), (UPat(Ops.RANGE, name="x"), lambda x: f"r{range_str(x)}"),
(UPat(Ops.CONST, name="x"), lambda x: str(x.arg)), (UPat(Ops.CONST, name="x"), lambda x: str(x.arg)),
+10 -6
View File
@@ -1,6 +1,6 @@
import math import math
from typing import cast, Any from typing import cast, Any
from tinygrad.uop.ops import PatternMatcher, UPat, GroupOp, Ops, UOp, AxisType, KernelInfo, ParamArg from tinygrad.uop.ops import PatternMatcher, UPat, GroupOp, Ops, UOp, AxisType, KernelInfo
from tinygrad.uop.render import print_uops, pyrender from tinygrad.uop.render import print_uops, pyrender
from tinygrad.dtype import DType, ImageDType, dtypes, PtrDType, AddrSpace, Invalid, ConstFloat from tinygrad.dtype import DType, ImageDType, dtypes, PtrDType, AddrSpace, Invalid, ConstFloat
from tinygrad.helpers import DEBUG, Context, prod, SPEC, Metadata, panic, CHECK_OOB from tinygrad.helpers import DEBUG, Context, prod, SPEC, Metadata, panic, CHECK_OOB
@@ -71,8 +71,7 @@ spec_shared = PatternMatcher([
(UPat(Ops.END, src=(UPat(),), allow_any_len=True, name="x"), lambda x: all(u.op is Ops.RANGE for u in x.src[1:])), (UPat(Ops.END, src=(UPat(),), allow_any_len=True, name="x"), lambda x: all(u.op is Ops.RANGE for u in x.src[1:])),
# PARAM (that's really a DEFINE_GLOBAL) # PARAM (that's really a DEFINE_GLOBAL)
(UPat(Ops.PARAM, name="x"), lambda x: isinstance(x.arg, ParamArg) and isinstance(x.dtype, (PtrDType, ImageDType)) and (UPat(Ops.PARAM, name="x"), lambda x: isinstance(x.dtype, (PtrDType, ImageDType)) and x.dtype.addrspace == AddrSpace.GLOBAL),
x.addrspace == x.dtype.addrspace),
# GROUP of stores (or groups, or NOOPs) # GROUP of stores (or groups, or NOOPs)
# TODO: remove UNROLL here, it's for SPEC=2 # TODO: remove UNROLL here, it's for SPEC=2
@@ -125,12 +124,15 @@ spec_tensor = PatternMatcher([
# CONST with a UNIQUE or DEVICE # CONST with a UNIQUE or DEVICE
(UPat(Ops.CONST, src=(UPat(Ops.DEVICE),)), lambda: True), (UPat(Ops.CONST, src=(UPat(Ops.DEVICE),)), lambda: True),
(UPat(Ops.CONST, src=(UPat((Ops.UNIQUE, Ops.LUNIQUE)), UPat(Ops.DEVICE)), name="c"), lambda c: c.arg is Invalid), (UPat(Ops.CONST, src=(UPat((Ops.UNIQUE, Ops.LUNIQUE)), UPat(Ops.DEVICE))), lambda: True),
# BUFFER # BUFFER
(UPat(Ops.BUFFER, src=(UPat((Ops.UNIQUE, Ops.LUNIQUE)), UPat(Ops.DEVICE)), name="buf"), (UPat(Ops.BUFFER, src=(UPat((Ops.UNIQUE, Ops.LUNIQUE)), UPat(Ops.DEVICE)), name="buf"),
lambda buf: isinstance(buf.arg, int) and isinstance(buf.dtype, DType)), lambda buf: isinstance(buf.arg, int) and isinstance(buf.dtype, DType)),
# PARAM (that's really a variable)
(UPat(Ops.PARAM, src=(UPat(), UPat(), UPat(), UPat(), UPat()), name="x"), lambda x: True),
# Tensor variable bindings # Tensor variable bindings
(UPat(Ops.BIND, (dtypes.int, dtypes.weakint,), (UPat(Ops.DEFINE_VAR), UPat.cvar(dtype=(dtypes.int,dtypes.weakint,))), arg=None), lambda: True), (UPat(Ops.BIND, (dtypes.int, dtypes.weakint,), (UPat(Ops.DEFINE_VAR), UPat.cvar(dtype=(dtypes.int,dtypes.weakint,))), arg=None), lambda: True),
@@ -146,7 +148,9 @@ spec_tensor = PatternMatcher([
(UPat(Ops.GETTUPLE, src=(UPat((Ops.FUNCTION, Ops.TUPLE)),), name="g"), lambda g: isinstance(g.arg, int)), (UPat(Ops.GETTUPLE, src=(UPat((Ops.FUNCTION, Ops.TUPLE)),), name="g"), lambda g: isinstance(g.arg, int)),
# PARAM # PARAM
(UPat(Ops.PARAM, src=(UPat(),), name="x"), lambda x: isinstance(x.arg, ParamArg)), (UPat(Ops.PARAM, src=(UPat(), UPat(Ops.NOOP)), name="x"), lambda x: True), # TODO: why does this have NOOP?
(UPat(Ops.PARAM, src=(UPat(), UPat(Ops.DEVICE)), name="x"), lambda x: True),
(UPat(Ops.PARAM, src=(UPat(), UPat(Ops.DEVICE), UPat(Ops.MULTI)), name="x"), lambda x: True),
# inputs to movement ops # inputs to movement ops
(UPat(Ops.STACK), lambda: True), (UPat(Ops.STACK), lambda: True),
@@ -260,7 +264,7 @@ from tinygrad.schedule.rangeify import BufferizeOpts
glbls:dict[str, Any] = {"inf": math.inf, "nan": math.nan, "KernelInfo": KernelInfo, "Metadata": Metadata, glbls:dict[str, Any] = {"inf": math.inf, "nan": math.nan, "KernelInfo": KernelInfo, "Metadata": Metadata,
"UOp": UOp, "dtypes": dtypes, "Ops": Ops, "AxisType": AxisType, "Invalid": Invalid, "UOp": UOp, "dtypes": dtypes, "Ops": Ops, "AxisType": AxisType, "Invalid": Invalid,
"Opt": Opt, "OptOps": OptOps, "BufferizeOpts": BufferizeOpts, "AddrSpace": AddrSpace, "panic": panic, "Opt": Opt, "OptOps": OptOps, "BufferizeOpts": BufferizeOpts, "AddrSpace": AddrSpace, "panic": panic,
"ConstFloat": ConstFloat, "ParamArg": ParamArg} "ConstFloat": ConstFloat}
def eval_pyrender(code:str) -> UOp: def eval_pyrender(code:str) -> UOp:
lcls:dict[str, Any] = {} lcls:dict[str, Any] = {}
exec(code, glbls, lcls) exec(code, glbls, lcls)
+13 -13
View File
@@ -98,14 +98,14 @@ symbolic_simple = propagate_invalid + PatternMatcher([
((UPat.var() % UPat.var("y")).named("base") % UPat.var("y"), lambda base,y: base), # (x%y)%y = -> x%y (rewritten with base for speed) ((UPat.var() % UPat.var("y")).named("base") % UPat.var("y"), lambda base,y: base), # (x%y)%y = -> x%y (rewritten with base for speed)
# variations of (x%c)+(x//c)*c = x # variations of (x%c)+(x//c)*c = x
(UPat(Ops.ADD, dtype=dtypes.weakint, name="x"), fold_add_divmod_recombine), (UPat(Ops.ADD, dtype=dtypes.weakint, name="x"), fold_add_divmod_recombine),
(UPat.var("x", dtype=dtypes.bool) & UPat.cvar("c"), lambda x,c: x if c.arg else c), (UPat.var("x", dtype=dtypes.bool) & UPat.cvar("c", vec=False), lambda x,c: x if c.arg else c),
(UPat.var("x", dtype=dtypes.bool) | UPat.cvar("c"), lambda x,c: c if c.arg else x), (UPat.var("x", dtype=dtypes.bool) | UPat.cvar("c", vec=False), lambda x,c: c if c.arg else x),
(UPat(GroupOp.Idempotent, src=(UPat.var("x"), UPat.var("x"))), lambda x: x), (UPat(GroupOp.Idempotent, src=(UPat.var("x"), UPat.var("x"))), lambda x: x),
(UPat.var("x", dtype=dtypes.bool).logical_not().logical_not(), lambda x: x), (UPat.var("x", dtype=dtypes.bool).logical_not().logical_not(), lambda x: x),
(UPat.var("x", dtype=dtypes.bool).where(UPat.const(dtypes.bool, True), UPat.const(dtypes.bool, False)), lambda x: x), (UPat.var("x", dtype=dtypes.bool).where(UPat.const(dtypes.bool, True), UPat.const(dtypes.bool, False)), lambda x: x),
(UPat.var("x", dtype=dtypes.bool).where(UPat.const(dtypes.bool, False), UPat.const(dtypes.bool, True)), lambda x: x.logical_not()), (UPat.var("x", dtype=dtypes.bool).where(UPat.const(dtypes.bool, False), UPat.const(dtypes.bool, True)), lambda x: x.logical_not()),
# CAST(bool -> int) != const — CAST(True)=1, CAST(False)=0, so fold based on const value # CAST(bool -> int) != const — CAST(True)=1, CAST(False)=0, so fold based on const value
(UPat.var("x", dtype=dtypes.bool).cast(dtypes.ints+(dtypes.weakint,)) != UPat.cvar("c"), (UPat.var("x", dtype=dtypes.bool).cast(dtypes.ints+(dtypes.weakint,)) != UPat.cvar("c", vec=False),
lambda x,c: x if c.arg == 0 else x.logical_not() if c.arg == 1 else x.const_like(True)), lambda x,c: x if c.arg == 0 else x.logical_not() if c.arg == 1 else x.const_like(True)),
(UPat.var("x", dtype=dtypes.ints+(dtypes.bool, dtypes.weakint)).trunc(), lambda x: x), (UPat.var("x", dtype=dtypes.ints+(dtypes.bool, dtypes.weakint)).trunc(), lambda x: x),
# ** zero folding ** # ** zero folding **
@@ -115,7 +115,7 @@ symbolic_simple = propagate_invalid + PatternMatcher([
(UPat.var("x") & 0, lambda x: x.const_like(0)), # x&0 -> 0 (UPat.var("x") & 0, lambda x: x.const_like(0)), # x&0 -> 0
# (x&mask)>>k -> x>>k when mask only clears bits below k # (x&mask)>>k -> x>>k when mask only clears bits below k
# TODO: combine this with "# rules for threefry" below # TODO: combine this with "# rules for threefry" below
((UPat.var("x") & UPat.cvar("mask")) >> UPat.cvar("k"), ((UPat.var("x") & UPat.cvar("mask", vec=False)) >> UPat.cvar("k", vec=False),
lambda x,mask,k: x >> k.arg if mask.arg | ((1 << k.arg) - 1) == -1 else None), lambda x,mask,k: x >> k.arg if mask.arg | ((1 << k.arg) - 1) == -1 else None),
(UPat.var("x", dtype=dtypes.ints+(dtypes.bool, dtypes.weakint)) != UPat.var("x"), (UPat.var("x", dtype=dtypes.ints+(dtypes.bool, dtypes.weakint)) != UPat.var("x"),
lambda x: x.const_like(False).cast(dtypes.bool.vec(x.dtype.count))), # x != x -> False (only ints) lambda x: x.const_like(False).cast(dtypes.bool.vec(x.dtype.count))), # x != x -> False (only ints)
@@ -148,9 +148,9 @@ symbolic_simple = propagate_invalid + PatternMatcher([
(UPat.var('x').cast(name="a").cast(name="b"), lambda x,a,b: x if x.dtype == b.dtype and can_lossless_cast(b.dtype, a.dtype) else None), (UPat.var('x').cast(name="a").cast(name="b"), lambda x,a,b: x if x.dtype == b.dtype and can_lossless_cast(b.dtype, a.dtype) else None),
(UPat.var("x").cast(dtypes.bool), lambda x: x != 0), (UPat.var("x").cast(dtypes.bool), lambda x: x != 0),
# ** pow ** # ** pow **
(UPat.var("x").alu(Ops.POW, UPat.cvar("c")), simplify_pow), (UPat.var("x").alu(Ops.POW, UPat.cvar("c", vec=False)), simplify_pow),
# positive const ** x # positive const ** x
(UPat.cvar("c").alu(Ops.POW, UPat.var("x")), lambda c,x: c if c.arg == 1 else (x*math.log2(c.arg)).exp2() if c.arg > 0 else None), (UPat.cvar("c", vec=False).alu(Ops.POW, UPat.var("x")), lambda c,x: c if c.arg == 1 else (x*math.log2(c.arg)).exp2() if c.arg > 0 else None),
# rules for threefry # rules for threefry
((UPat.var('x', dtypes.uint64)&0xFFFFFFFF).cast(dtypes.uint32), lambda x: x.cast(dtypes.uint32)), ((UPat.var('x', dtypes.uint64)&0xFFFFFFFF).cast(dtypes.uint32), lambda x: x.cast(dtypes.uint32)),
(((UPat.var(None, dtypes.uint64)*(1<<32)) | UPat.var('y', dtypes.uint32).cast(dtypes.uint64)).cast(dtypes.uint32), lambda y: y), (((UPat.var(None, dtypes.uint64)*(1<<32)) | UPat.var('y', dtypes.uint32).cast(dtypes.uint64)).cast(dtypes.uint32), lambda y: y),
@@ -160,7 +160,7 @@ symbolic_simple = propagate_invalid + PatternMatcher([
# ** simple where folding ** # ** simple where folding **
# a conditional with the same results either way is a noop, also fold const conditionals # a conditional with the same results either way is a noop, also fold const conditionals
(UPat.var().where(UPat.var("val"), UPat.var("val")), lambda val: val), (UPat.var().where(UPat.var("val"), UPat.var("val")), lambda val: val),
(UPat.cvar("gate").where(UPat.var("c0"), UPat.var("c1")), lambda gate, c0, c1: c0 if gate.arg else c1), (UPat.cvar("gate", vec=False).where(UPat.var("c0"), UPat.var("c1")), lambda gate, c0, c1: c0 if gate.arg else c1),
# a.where(b.where(c, d), d) -> (a & b).where(c, d) # a.where(b.where(c, d), d) -> (a & b).where(c, d)
(UPat.var("a").where(UPat.var("b").where(UPat.var("c"), UPat.var("d")), UPat.var("d")), lambda a,b,c,d: (a&b).where(c,d)), (UPat.var("a").where(UPat.var("b").where(UPat.var("c"), UPat.var("d")), UPat.var("d")), lambda a,b,c,d: (a&b).where(c,d)),
]) ])
@@ -205,7 +205,7 @@ gep_pushing = PatternMatcher([
lambda g1, g2: g2.src[0].gep(tuple(g2.arg[g1.arg[i]] for i in range(len(g1.arg))))), lambda g1, g2: g2.src[0].gep(tuple(g2.arg[g1.arg[i]] for i in range(len(g1.arg))))),
(UPat(Ops.STACK, name='vec').f(Ops.GEP, name='gep'), (UPat(Ops.STACK, name='vec').f(Ops.GEP, name='gep'),
lambda gep, vec: UOp(Ops.STACK, gep.dtype, tuple(vec.src[i] for i in gep.arg)) if len(gep.arg) > 1 else vec.src[gep.arg[0]]), lambda gep, vec: UOp(Ops.STACK, gep.dtype, tuple(vec.src[i] for i in gep.arg)) if len(gep.arg) > 1 else vec.src[gep.arg[0]]),
(UPat.cvar("c").f(Ops.GEP, name="gep"), lambda gep, c: gep.const_like(c.arg)), (UPat.cvar("c", vec=False).f(Ops.GEP, name="gep"), lambda gep, c: gep.const_like(c.arg)),
# GEP on void is skipped # GEP on void is skipped
(UPat(Ops.GEP, src=(UPat(dtype=dtypes.void, name="x"),)), lambda x: x), (UPat(Ops.GEP, src=(UPat(dtype=dtypes.void, name="x"),)), lambda x: x),
# GEP in order is removed # GEP in order is removed
@@ -270,20 +270,20 @@ symbolic = symbolic_simple+commutative+PatternMatcher([
((UPat.var("x") // UPat.cvar("c1")) // UPat.cvar("c2"), lambda x,c1,c2: x//(c1*c2) if c2.vmin>0 else None), ((UPat.var("x") // UPat.cvar("c1")) // UPat.cvar("c2"), lambda x,c1,c2: x//(c1*c2) if c2.vmin>0 else None),
# ** lt ** # ** lt **
# c0*x<c1 for positive int c0,c1 # c0*x<c1 for positive int c0,c1
((UPat.cvar("c0")*UPat.var("x", dtype=dtypes.weakint))<UPat.cvar("c1"), ((UPat.cvar("c0", vec=False)*UPat.var("x", dtype=dtypes.weakint))<UPat.cvar("c1", vec=False),
lambda x,c0,c1: x<math.ceil(c1.arg/c0.arg) if c0.arg > 0 and c1.arg > 0 else None), lambda x,c0,c1: x<math.ceil(c1.arg/c0.arg) if c0.arg > 0 and c1.arg > 0 else None),
# c0*x<c1 for negative int c0 and non-positive c1 # c0*x<c1 for negative int c0 and non-positive c1
((UPat.cvar("c0")*UPat.var("x", dtype=dtypes.weakint))<UPat.cvar("c1"), ((UPat.cvar("c0", vec=False)*UPat.var("x", dtype=dtypes.weakint))<UPat.cvar("c1", vec=False),
lambda x,c0,c1: (-x)<(-(math.floor(-c1.arg/-c0.arg))) if c0.arg < 0 and c0.arg != -1 and c1.arg <= 0 else None), lambda x,c0,c1: (-x)<(-(math.floor(-c1.arg/-c0.arg))) if c0.arg < 0 and c0.arg != -1 and c1.arg <= 0 else None),
# x//d<c -> x<c*d for d>0 # x//d<c -> x<c*d for d>0
((UPat.var("x", dtype=dtypes.weakint)//UPat.cvar("d"))<UPat.cvar("c"), ((UPat.var("x", dtype=dtypes.weakint)//UPat.cvar("d", vec=False))<UPat.cvar("c", vec=False),
lambda x,d,c: x<(c.arg*d.arg) if d.arg > 0 else None), lambda x,d,c: x<(c.arg*d.arg) if d.arg > 0 else None),
# ** move add/mul consts to end (NOTE: this is still happening before constant folding) ** # ** move add/mul consts to end (NOTE: this is still happening before constant folding) **
((UPat.var("x") + UPat.cvar("c1")) + UPat.var("y"), lambda x,c1,y: (x+y)+c1), ((UPat.var("x") + UPat.cvar("c1")) + UPat.var("y"), lambda x,c1,y: (x+y)+c1),
((UPat.var("x") * UPat.cvar("c1")) * UPat.var("y"), lambda x,c1,y: (x*y)*c1), ((UPat.var("x") * UPat.cvar("c1")) * UPat.var("y"), lambda x,c1,y: (x*y)*c1),
# *** rules from symbolic *** # *** rules from symbolic ***
# generic lt folding # generic lt folding
(UPat.var("x", dtypes.weakint)<UPat.cvar("c"), lambda x,c: lt_folding(x, c.arg) if 0 < c.arg else None), (UPat.var("x", dtypes.weakint)<UPat.cvar("c", vec=False), lambda x,c: lt_folding(x, c.arg) if 0 < c.arg else None),
(UPat.var("x", dtypes.weakint)*-1 < UPat.var("y")*-1, lambda x,y: y<x), (UPat.var("x", dtypes.weakint)*-1 < UPat.var("y")*-1, lambda x,y: y<x),
# canonicalize a simplex with positive coefficients > 0. NOTE: not x < 1 means x > 0 # canonicalize a simplex with positive coefficients > 0. NOTE: not x < 1 means x > 0
((UPat.var("x", dtypes.weakint)<1).ne(True), lambda x: (newx<1).ne(True) if (newx:=canonicalize_simplex(x)) is not None else None), ((UPat.var("x", dtypes.weakint)<1).ne(True), lambda x: (newx<1).ne(True) if (newx:=canonicalize_simplex(x)) is not None else None),
@@ -467,7 +467,7 @@ sym = symbolic+pm_simplify_valid+PatternMatcher([
(UPat.var("x") * ((1+UPat.var("x")).reciprocal().named("d")*UPat.var("y")), lambda x,y,d: y*(1-d)), (UPat.var("x") * ((1+UPat.var("x")).reciprocal().named("d")*UPat.var("y")), lambda x,y,d: y*(1-d)),
(UPat.var("x") * ((1+UPat.var("x")).reciprocal().named("d")+UPat.var("y")), lambda x,y,d: (1-d)+x*y), (UPat.var("x") * ((1+UPat.var("x")).reciprocal().named("d")+UPat.var("y")), lambda x,y,d: (1-d)+x*y),
# move const multiply after REDUCE (NOTE: the mul chain can do this, but only if it's a same dtype reduce) # move const multiply after REDUCE (NOTE: the mul chain can do this, but only if it's a same dtype reduce)
((UPat.var("x")*UPat.cvar("c")).reduce(arg=Ops.ADD, name="r", allow_any_len=True), lambda x,c,r: r.replace(src=(x,)+r.src[1:])*c.arg), ((UPat.var("x")*UPat.cvar("c", vec=False)).reduce(arg=Ops.ADD, name="r", allow_any_len=True), lambda x,c,r: r.replace(src=(x,)+r.src[1:])*c.arg),
# reduce mul chain, move muls after the reduce # reduce mul chain, move muls after the reduce
(UPat(Ops.MUL).reduce(name="r", allow_any_len=True), reduce_mul_chain), (UPat(Ops.MUL).reduce(name="r", allow_any_len=True), reduce_mul_chain),
# clean up GROUP/SINK # clean up GROUP/SINK
+3 -3
View File
@@ -102,18 +102,18 @@
fill: #FFD700; fill: #FFD700;
stroke: #B8860B; stroke: #B8860B;
} }
g.tag.collapsed circle, g.tag.collapsed rect { g.tag.collapsed circle {
fill: #5CD68D; fill: #5CD68D;
stroke: #4a4b57; stroke: #4a4b57;
} }
g.tag.expanded circle, g.tag.expanded rect { g.tag.expanded circle {
fill: #9FDDE6; fill: #9FDDE6;
stroke: #4a4b57; stroke: #4a4b57;
} }
g.port circle { g.port circle {
fill: #b3dcc2; fill: #b3dcc2;
} }
g.tag circle, g.tag rect, #edge-labels circle { g.tag circle, #edge-labels circle {
stroke-width: 0.8; stroke-width: 0.8;
} }
g.tag text, #edge-labels text { g.tag text, #edge-labels text {
+4 -11
View File
@@ -57,9 +57,7 @@ function intersectRect(r1, r2) {
} }
function addTags(root, path) { function addTags(root, path) {
root.selectAll("circle").data(d => d.rect ? [] : [d]).join("circle").attr("r", 5).style("fill", d => d.fill ?? null).style("stroke", d => d.stroke ?? null); root.selectAll("circle").data(d => [d]).join("circle").attr("r", 5).style("fill", d => d.fill ?? null);
root.selectAll("rect").data(d => d.rect ? [d] : []).join("rect").attr("x", d => -d.width/2).attr("y", d => -d.height/2)
.attr("width", d => d.width).attr("height", d => d.height).style("fill", d => d.fill ?? null).style("stroke", d => d.stroke ?? null);
if (path != null) root.selectAll("path").data(d => [d]).join("path").attr("d", path); if (path != null) root.selectAll("path").data(d => [d]).join("path").attr("d", path);
else root.selectAll("text").data(d => [d]).join("text").text(d => d.text).attr("dy", "0.35em"); else root.selectAll("text").data(d => [d]).join("text").text(d => d.text).attr("dy", "0.35em");
} }
@@ -87,7 +85,7 @@ const drawGraph = (data) => {
.attr("x", d => -d.width/2).attr("y", d => -d.height/2).classed("node", true); .attr("x", d => -d.width/2).attr("y", d => -d.height/2).classed("node", true);
const STROKE_WIDTH = 1.4, textSpace = g.graph().textSpace; const STROKE_WIDTH = 1.4, textSpace = g.graph().textSpace;
const labels = nodes.selectAll("g.label").data(d => [d]).join("g").attr("class", "label"); const labels = nodes.selectAll("g.label").data(d => [d]).join("g").attr("class", "label");
labels.attr("transform", d => `translate(${d.labelX-d.labelWidth/2}, -${d.labelHeight/2+STROKE_WIDTH*2})`); labels.attr("transform", d => `translate(-${d.labelWidth/2}, -${d.labelHeight/2+STROKE_WIDTH*2})`);
const rectGroup = labels.selectAll("g.rect-group").data(d => [d]).join("g").attr("class", "rect-group"); const rectGroup = labels.selectAll("g.rect-group").data(d => [d]).join("g").attr("class", "rect-group");
const tokens = labels.selectAll("g.text-group").data(d => [d]).join("g").attr("class", "text-group").selectAll("text").data(d => { const tokens = labels.selectAll("g.text-group").data(d => [d]).join("g").attr("class", "text-group").selectAll("text").data(d => {
if (Array.isArray(d.label)) return [d.label]; if (Array.isArray(d.label)) return [d.label];
@@ -115,13 +113,8 @@ const drawGraph = (data) => {
}); });
addTags(nodes.selectAll("g.tag").data(d => d.tag != null ? [d] : []).join("g").attr("class", "tag") addTags(nodes.selectAll("g.tag").data(d => d.tag != null ? [d] : []).join("g").attr("class", "tag")
.attr("transform", d => `translate(${-d.width/2+8}, ${-d.height/2+8})`).datum(e => ({ text:e.tag }))); .attr("transform", d => `translate(${-d.width/2+8}, ${-d.height/2+8})`).datum(e => ({ text:e.tag })));
addTags(nodes.selectAll("g.addrspace").data(d => d.addrspace != null ? [d] : []).join("g").attr("class", "tag addrspace") addTags(nodes.selectAll("g.type").data(d => d.collapsible ? [d] : []).join("g").attr("class", d => `tag ${d.collapsed ? 'collapsed' : 'expanded'}`)
.attr("transform", d => `translate(${d.width/2-8}, ${-d.height/2+8})`).datum(e => ({ rect:true, width:10, height:10, fill:e.addrspace, stroke:"none" }))); .attr("transform", d => `translate(${-d.width/2}, ${0})`).datum(d => ({ ...d, text:d.collapsed ? "+" : "", fill:d.callNode ? null : d.color })).on("click", (e,d) => {
const CALL_TAG_WIDTH = 14;
addTags(nodes.selectAll("g.type").data(d => d.collapsible ? [d] : []).join("g").attr("class", d => `tag clickable ${d.collapsed ? 'collapsed' : 'expanded'}`)
.attr("transform", d => d.callNode ? `translate(${CALL_TAG_WIDTH/2-d.width/2}, ${0})` : `translate(${-d.width/2}, ${0})`)
.datum(d => ({ ...d, text:d.collapsed ? "+" : "", fill:d.callNode ? null : d.color,
...(d.callNode && { rect:true, width:CALL_TAG_WIDTH }) })).on("click", (e,d) => {
e.stopPropagation(); e.stopPropagation();
const t = d3.zoomTransform(document.getElementById("graph-svg")); const t = d3.zoomTransform(document.getElementById("graph-svg"));
const [x, y] = t.apply([d.x, d.y]); const [x, y] = t.apply([d.x, d.y]);
+4 -7
View File
@@ -31,7 +31,7 @@ const layoutCfg = (g, { blocks, paths, pc_tokens }) => {
width = Math.max(width, ctx.measureText(tokens.map((t) => t.st).join("")).width); width = Math.max(width, ctx.measureText(tokens.map((t) => t.st).join("")).width);
height += lineHeight; height += lineHeight;
} }
g.setNode(lead, { ...rectDims(width, height), label, labelX:0, id:lead, color:"#1a1b26", addrspace:null }); g.setNode(lead, { ...rectDims(width, height), label, id:lead, color:"#1a1b26" });
} }
// paths become edges between basic blocks // paths become edges between basic blocks
const pathColors = {0:"#3f7564", 1:"#7a4540", 2:"#3b5f7e"}; const pathColors = {0:"#3f7564", 1:"#7a4540", 2:"#3b5f7e"};
@@ -45,9 +45,9 @@ const layoutUOp = (g, { graph, change }, opts) => {
const lineHeight = 14; const lineHeight = 14;
g.setGraph({ rankdir: "LR", font:"sans-serif", lh:lineHeight }); g.setGraph({ rankdir: "LR", font:"sans-serif", lh:lineHeight });
ctx.font = `350 ${lineHeight}px ${g.graph().font}`; ctx.font = `350 ${lineHeight}px ${g.graph().font}`;
if (change?.length) g.setNode("overlay", {label:"", labelWidth:0, labelHeight:0, labelX:0, className:"overlay"}); if (change?.length) g.setNode("overlay", {label:"", labelWidth:0, labelHeight:0, className:"overlay"});
let callCount = 0; let callCount = 0;
for (const [k, {label, src, ref, color, tag, exclude, addrspace}] of Object.entries(graph)) { for (const [k, {label, src, ref, color, tag, exclude }] of Object.entries(graph)) {
// adjust node dims by label size (excluding escape codes) + add padding // adjust node dims by label size (excluding escape codes) + add padding
let [width, height] = [0, 0]; let [width, height] = [0, 0];
for (line of label.replace(/\u001B\[(?:K|.*?m)/g, "").split("\n")) { for (line of label.replace(/\u001B\[(?:K|.*?m)/g, "").split("\n")) {
@@ -56,7 +56,7 @@ const layoutUOp = (g, { graph, change }, opts) => {
} }
const callNode = label.startsWith("CALL\n") || label.startsWith("FUNCTION\n"); const callNode = label.startsWith("CALL\n") || label.startsWith("FUNCTION\n");
if (callNode) callCount++; if (callNode) callCount++;
g.setNode(k, {...rectDims(width, height), label, labelX:0, ref, id:k, color, tag, callNode, exclude, addrspace}); g.setNode(k, {...rectDims(width, height), label, ref, id:k, color, tag, callNode, exclude});
// add edges // add edges
const edgeCounts = {}; const edgeCounts = {};
for (const [_, s] of src) edgeCounts[s] = (edgeCounts[s] || 0)+1; for (const [_, s] of src) edgeCounts[s] = (edgeCounts[s] || 0)+1;
@@ -79,7 +79,6 @@ const layoutUOp = (g, { graph, change }, opts) => {
} }
// optionally remove node srcs, track affected nodes // optionally remove node srcs, track affected nodes
const disconnected = new Set(); const disconnected = new Set();
const CALL_TAG_WIDTH = 14;
for (const n of g.nodes()) { for (const n of g.nodes()) {
const node = g.node(n); const node = g.node(n);
for (const consumerId of (g.successors(n) || [])) { for (const consumerId of (g.successors(n) || [])) {
@@ -89,8 +88,6 @@ const layoutUOp = (g, { graph, change }, opts) => {
const collapsible = consumer.callNode ? edge?.label?.text === 0 : node.exclude; const collapsible = consumer.callNode ? edge?.label?.text === 0 : node.exclude;
if (!collapsible) continue; if (!collapsible) continue;
consumer.collapsible = true; consumer.collapsible = true;
// increase width of call/function nodes to make space for a toggle
if (consumer.callNode) { consumer.width = consumer.labelWidth+NODE_PADDING*2+CALL_TAG_WIDTH; consumer.labelX = CALL_TAG_WIDTH/2; }
// make sources invisible if UI has toggled it off // make sources invisible if UI has toggled it off
const collapsed = consumer.callNode ? opts.showCallSrc === opts.callSrcMask.has(consumerId) : !opts.expandedNodes.has(consumerId); const collapsed = consumer.callNode ? opts.showCallSrc === opts.callSrcMask.has(consumerId) : !opts.expandedNodes.has(consumerId);
if (!collapsed) continue; if (!collapsed) continue;
+3 -6
View File
@@ -43,7 +43,7 @@ from tinygrad.uop.ops import TrackedGraphRewrite, RewriteTrace, UOp, Ops, GroupO
from tinygrad.uop.ops import KernelInfo from tinygrad.uop.ops import KernelInfo
from tinygrad.uop.render import print_uops, pyrender from tinygrad.uop.render import print_uops, pyrender
from tinygrad.device import ProfileDeviceEvent, ProfileGraphEvent, ProfileGraphEntry, ProfileProgramEvent from tinygrad.device import ProfileDeviceEvent, ProfileGraphEvent, ProfileGraphEntry, ProfileProgramEvent
from tinygrad.dtype import dtypes, AddrSpace from tinygrad.dtype import dtypes
uops_colors = {Ops.LOAD: "#ffc0c0", Ops.STORE: "#87CEEB", Ops.CONST: "#e0e0e0", Ops.REDUCE: "#FF5B5B", uops_colors = {Ops.LOAD: "#ffc0c0", Ops.STORE: "#87CEEB", Ops.CONST: "#e0e0e0", Ops.REDUCE: "#FF5B5B",
**{x:"#f2cb91" for x in {Ops.DEFINE_LOCAL, Ops.DEFINE_REG}}, Ops.SHAPED_WMMA: "#FF5B5B", **{x:"#f2cb91" for x in {Ops.DEFINE_LOCAL, Ops.DEFINE_REG}}, Ops.SHAPED_WMMA: "#FF5B5B",
@@ -56,8 +56,6 @@ uops_colors = {Ops.LOAD: "#ffc0c0", Ops.STORE: "#87CEEB", Ops.CONST: "#e0e0e0",
Ops.ALLREDUCE: "#ff40a0", Ops.MSELECT: "#d040a0", Ops.MSTACK: "#d040a0", Ops.CONTIGUOUS: "#FFC14D", Ops.ALLREDUCE: "#ff40a0", Ops.MSELECT: "#d040a0", Ops.MSTACK: "#d040a0", Ops.CONTIGUOUS: "#FFC14D",
Ops.STAGE: "#AC640D", Ops.REWRITE_ERROR: "#ff2e2e", Ops.AFTER: "#8A7866", Ops.END: "#524C46"} Ops.STAGE: "#AC640D", Ops.REWRITE_ERROR: "#ff2e2e", Ops.AFTER: "#8A7866", Ops.END: "#524C46"}
addrspace_colors = {AddrSpace.REG:"#e68181", AddrSpace.LOCAL:"#e7c86a", AddrSpace.GLOBAL:"#75bd7b"}
# VIZ API # VIZ API
# A step is a lightweight descriptor for a trace entry # A step is a lightweight descriptor for a trace entry
@@ -123,7 +121,7 @@ def uop_to_json(data:VizData, x:UOp) -> dict[int, dict]:
if u.op is Ops.STACK and len(u.src) == 0: excluded.add(u) if u.op is Ops.STACK and len(u.src) == 0: excluded.add(u)
# exclude RESHAPE/EXPAND that only serve to broadcast a CONST # exclude RESHAPE/EXPAND that only serve to broadcast a CONST
if u.op in {Ops.RESHAPE, Ops.EXPAND} and len(u.src) >= 1 and u.src[0] in excluded and u is not x: excluded.add(u) if u.op in {Ops.RESHAPE, Ops.EXPAND} and len(u.src) >= 1 and u.src[0] in excluded and u is not x: excluded.add(u)
if u.op in GroupOp.Movement: excluded.update(s for s in u.src if s.op is Ops.STACK and all(x.op is Ops.CONST for x in s.src)) if u.op in GroupOp.Movement: excluded.update(s for s in u.src if s.op is Ops.STACK)
for u in toposort: for u in toposort:
argst = codecs.decode(str(u.arg), "unicode_escape") argst = codecs.decode(str(u.arg), "unicode_escape")
if u.op in GroupOp.Movement: argst = (mask_to_str if u.op in {Ops.SHRINK, Ops.PAD} else shape_to_str)(u.marg) if u.op in GroupOp.Movement: argst = (mask_to_str if u.op in {Ops.SHRINK, Ops.PAD} else shape_to_str)(u.marg)
@@ -163,8 +161,7 @@ def uop_to_json(data:VizData, x:UOp) -> dict[int, dict]:
if u.op is Ops.SOURCE and len(lines:=label.split("\n")) > 40: if u.op is Ops.SOURCE and len(lines:=label.split("\n")) > 40:
label = "\n".join(lines[:30]) + "\n..." label = "\n".join(lines[:30]) + "\n..."
graph[id(u)] = {"label":label, "src":[(i,id(x)) for i,x in enumerate(u.src)], "exclude":u in excluded, "color":uops_colors.get(u.op, "#ffffff"), graph[id(u)] = {"label":label, "src":[(i,id(x)) for i,x in enumerate(u.src)], "exclude":u in excluded, "color":uops_colors.get(u.op, "#ffffff"),
"ref":ref, "tag":repr(u.tag) if u.tag is not None else None, "ref":ref, "tag":repr(u.tag) if u.tag is not None else None}
"addrspace":addrspace_colors.get(u.addrspace, None) if u.addrspace is not None else None}
return graph return graph
def _reconstruct(data:VizData, a:int, depth:int|None=None): def _reconstruct(data:VizData, a:int, depth:int|None=None):