Compare commits

...
49 Commits
Author SHA1 Message Date
geohot c43f8edc3e small diff 2025-12-16 17:09:47 -04:00
geohot f7a9805dcf move pad to mixin 2025-12-16 17:02:45 -04:00
George HotzandGitHub ee45669d14 pre extract afters + sched cleanups (#13720)
* pre extract afters + sched cleanups

* claude.md lesson

* tests for schedule cache

* Revert "tests for schedule cache"

This reverts commit fb3f2e800a.
2025-12-16 16:14:30 -04:00
George HotzandGitHub 4b741e893f remove REMOTE=1 (#13722)
* remove REMOTE=1

* leave ibverbs
2025-12-16 15:58:10 -04:00
George HotzandGitHub 4d8d821f56 create schedule before the cache (#13717)
* create schedule before the cache

* move create_schedule

* simpler

* simpler

* simpler
2025-12-16 14:15:31 -04:00
bfe374c7f5 support symbolic shapes in split/chunk when split dim is concrete (#13718)
* support symbolic shapes in split/chunk when split dim is concrete

Previously split() and chunk() required all dimensions to be concrete.
Now they only require the dimension being split to be concrete, allowing
them to work with tensors that have symbolic shapes in other dimensions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* update CLAUDE.md: add pre-commit and no-amend rules

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* fix dim resolution order in split/chunk

Ensure dim_sz is retrieved after dim is resolved, not before.
The previous one-liner evaluated self.shape[dim] with the original
unresolved dim value.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
2025-12-16 13:55:06 -04:00
chenyuandGitHub e428fbfab6 verify dtype of llama model params (#13719) 2025-12-16 12:32:02 -05:00
George HotzandGitHub e5a66ace80 multi custom kernel support (#13716)
* multi custom kernel support

* custom kernel xfrom

* works

* no SPEC=2 on ck

* panic

* touchups
2025-12-16 11:36:30 -04:00
nimlgenandGitHub 5778722979 am: restore queues (#13714)
* am: restore queues

* l

* cmnt
2025-12-16 15:21:42 +03:00
chenyuandGitHub 041e9a41c9 add contiguous in BertIntermediate (#13713)
faster step with a lot less recomputation
2025-12-15 22:37:36 -05:00
George HotzandGitHub 7589c897b2 split usbgpu tests into their own benchmark [pr] (#13711) 2025-12-15 21:42:40 -04:00
qazalandGitHub 6bafd90248 remove unused process replay input [pr] (#13712) 2025-12-16 09:29:35 +08:00
321ab943b2 qwen model is working (#13690)
* qwen model is mostly working

* add Q4_K quantization support to GGUF parser, add qwen3:1.7b model

- Add Q4_K (type 12) dequantization in nn/state.py
- Add qwen3:1.7b model using Q4_K_M quantization (smaller than Q8_0)
- Make bos_token_id optional for models like Qwen3 that don't have it
- Fix line length issues and add preset parameter to SimpleTokenizer

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* smaller diff

* test dequant

* half split

* better

* simple tok

* mock token

* polish

* better

* fix

* replace

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
2025-12-15 18:00:34 -04:00
George HotzandGitHub d43e4c7553 llm args + lil html page (#13710)
* update llm args

* lil html page

* lil

* line size

* qol
2025-12-15 17:09:31 -04:00
George HotzandGitHub ee4a7ee12f rope half-split (#13706)
* rope half

* nicer

* this

* rearrange
2025-12-15 15:31:11 -04:00
sirhcmandGitHub 2359e88f0c wrap cdll redo (#13705)
* wrap CDLL with custom findlib

* lint

* regen

* fix

* mypy

* hardcode libc on macos

* fix frameworks

* fix webgpu win

* remove supports

* regen metal

* regen libclang

* regen

* simpler

* regen

* regen

* find nvrtc

* fix

* regen

* fix

* typo

* regen

* split

* rsplit one

* typo

* try load DLL

* string error
2025-12-15 13:15:02 -05:00
wozeparrotandGitHub 5d509499b2 tk: kernel finish groups stores (#13704) 2025-12-15 09:16:17 -08:00
George HotzandGitHub 54a22aa298 add test for jit footguns (#13701)
* add test for jit footguns

* shorter

* notes
2025-12-15 10:47:44 -05:00
George HotzandGitHub fd49bb512d download cache by job (#13703) 2025-12-15 10:47:17 -05:00
a657a4e0f4 add Q4_K GGUF quantization support (#13700)
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <[email protected]>
2025-12-15 10:17:56 -05:00
nimlgenandGitHub 615dcab767 am: minimal mi300 boot (#13679)
* nbio7_9

* psp

* gmc

* gfx

* sdma

* ih

* linter

* linter

* minor

* finish

* add missing

* do not allow warm boot for now
2025-12-15 15:55:03 +03:00
qazalandGitHub 72e006cd59 fast VIZ=2 startup (#13682) 2025-12-15 19:16:43 +08:00
qazalandGitHub 50d34428bd fix viz endstream (#13687) 2025-12-15 16:54:18 +08:00
wozeparrotandGitHub 7ef7ce2856 tk reg local store (#13689) 2025-12-14 23:07:30 -08:00
George HotzandGitHub 572ca80046 fast tinygrad.apps.llm (#13685)
* llm: add --benchmark support

* fix speed

* debug logging

* fix test attention
2025-12-14 21:05:21 -05:00
chenyuandGitHub 6cad622f59 don't FREE_INTERMEDIATE in bert (#13684)
hangs green hcq consistently after an hour of training
2025-12-14 14:27:42 -05:00
chenyuandGitHub 871ab8415f some onnx cleanups (#13683) 2025-12-14 13:58:54 -05:00
nimlgenandGitHub 75832ce4f6 am: psp with no autoload (#13681) 2025-12-14 20:20:09 +03:00
nimlgenandGitHub 8bcb1038e4 am: nbio 7.9.0 (#13680) 2025-12-14 18:35:29 +03:00
George HotzandGitHub 013240938b llm: add --benchmark support (#13678) 2025-12-14 08:35:05 -05:00
Robbe DerksandGitHub cddbdaf5e1 usbgpu: patch: auto-detect controller PID/VID (#13645)
* auto-detect controller

* fix lint?

* needs ''

* just try
2025-12-14 00:54:51 -05:00
George HotzandGitHub d7fb5d9b62 speedups: early return from simplify (#13665)
* early return from simplify

* pm_rewrite

* more speed

* remove again

* early return from simplify

* ugh
2025-12-14 00:51:28 -05:00
geohot bcbf832399 add chrism 2025-12-14 00:45:57 -05:00
chenyuandGitHub ed962786d6 use assign in Tensor.backward (#13674)
preserve the grad object so that jit works
2025-12-13 22:43:06 -05:00
chenyuandGitHub 721a379c41 Revert "autogen: use wrapped CDLL with custom findlib (#13666)" (#13675)
This reverts commit f6cc3b13b9.
2025-12-13 22:42:41 -05:00
nimlgenandGitHub 6402dcf940 am: xccs queue creation (#13672) 2025-12-13 18:37:09 +03:00
nimlgenandGitHub 8430ee7d5f am: stop hqd only when active (#13670)
* am: stop hqd only when active

* this better
2025-12-13 17:41:44 +03:00
nimlgenandGitHub a49ba241bb am: use fb_base/fb_end as mc aperture (#13671) 2025-12-13 17:29:03 +03:00
nimlgenandGitHub 0b15c573ca amd: xccs in PCIIface (#13669) 2025-12-13 17:22:11 +03:00
qazalandGitHub 019e71f8ca lds bank count tests from pmc counters (#13667)
* lds bank count tests from pmc counters

* these tests run on the RDNA3 card too

* rename duration to cycles, other rename comment

* add SQ_LDS_IDX_ACTIVE to gfx9 defaults
2025-12-13 17:39:32 +08:00
qazalandGitHub a6dfd8a672 viz server cleanups (#13668)
* viz server cleanups

* comment
2025-12-13 17:27:53 +08:00
sirhcmandGitHub f6cc3b13b9 autogen: use wrapped CDLL with custom findlib (#13666)
* wrap CDLL with custom findlib

* lint

* regen

* fix

* mypy

* hardcode libc on macos

* fix frameworks

* fix webgpu win

* remove supports

* regen metal

* regen libclang

* regen

* simpler

* regen

* regen

* find nvrtc

* fix

* regen

* fix

* typo

* regen

* split

* rsplit one

* typo
2025-12-13 01:31:30 -05:00
55845f7de7 schedule: cache unbinds for consistent cache keys (#13664)
* schedule: cache unbinds for consistent cache keys

strip BIND values before computing cache key so different bound values
(e.g. KV cache positions) hit the same schedule cache entry.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* spec: allow single-src BIND for schedule cache key normalization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* docs: add lessons learned to CLAUDE.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* more claude.md

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
2025-12-12 17:27:42 -05:00
geohot 27845353a0 add CLAUDE.md 2025-12-12 16:50:11 -05:00
geohot 8c87a0bf8d Revert "schedule: cache unbinds for consistent cache keys (#13662)"
This reverts commit af86cae10c.
2025-12-12 16:49:50 -05:00
geohot 443b7fea80 Revert "add notes about jit to claude.md"
This reverts commit 429f82e6a9.
2025-12-12 16:49:48 -05:00
geohot 429f82e6a9 add notes about jit to claude.md 2025-12-12 16:48:23 -05:00
af86cae10c schedule: cache unbinds for consistent cache keys (#13662)
* schedule: cache unbinds for consistent cache keys

different bound variable values (e.g. kv cache positions) now produce
the same schedule cache key by unbinding BIND(DEFINE_VAR, CONST) before
computing the cache key and rebinding after lookup.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* schedule: cache unbinds for consistent cache keys

When scheduling, BIND(DEFINE_VAR, CONST) nodes are now unbound to
tagged DEFINE_VARs before computing the cache key. This ensures that
the same computation with different bound values (e.g., different
KV cache positions in LLM) gets the same cache key and reuses the
cached schedule.

The fix:
- pm_pre_sched_cache: replaces BIND with tagged DEFINE_VAR
- pm_post_sched_cache: restores tagged DEFINE_VAR back to original BIND
- pm_remove_rangeify_tags: excludes DEFINE_VAR to preserve tags through rangeify
- var_vals extracted from BINDs before cache key computation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* schedule: fix BIND handling and add CLAUDE.md

- Handle BIND to RANGE in create_schedule (not matched by CONST pattern)
- Assert all BINDs on same variable have same value
- Add CLAUDE.md codebase guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
2025-12-12 16:40:10 -05:00
chenyuandGitHub fcaed1e1dd don't use empty in bert fake data (#13661)
somehow jit does not count empty as input
2025-12-12 15:59:50 -05:00
92 changed files with 2954 additions and 1741 deletions
+2 -2
View File
@@ -70,13 +70,13 @@ runs:
uses: actions/cache@v4
with:
path: ~/.cache/tinygrad/downloads/
key: downloads-cache-${{ inputs.key }}-${{ env.CACHE_VERSION }}
key: downloads-${{ github.job }}-${{ inputs.key }}-${{ env.CACHE_VERSION }}
- name: Cache downloads (macOS)
if: inputs.key != '' && runner.os == 'macOS'
uses: actions/cache@v4
with:
path: ~/Library/Caches/tinygrad/downloads/
key: osx-downloads-cache-${{ inputs.key }}-${{ env.CACHE_VERSION }}
key: downloads-${{ github.job }}-${{ inputs.key }}-${{ env.CACHE_VERSION }}
# **** Python deps ****
+31 -21
View File
@@ -14,12 +14,6 @@ on:
- update_benchmark
- update_benchmark_staging
workflow_dispatch:
inputs:
run_process_replay:
description: "Run process replay tests"
required: false
default: false
type: boolean
jobs:
testmacbenchmark:
@@ -124,18 +118,6 @@ jobs:
# TODO: too slow
# - name: Run 10 CIFAR training steps w winograd
# run: BENCHMARK_LOG=cifar_10steps_wino JIT=1 ASSERT_MIN_STEP_TIME=150 WINO=1 STEPS=10 python3.11 examples/hlb_cifar10.py | tee train_cifar_wino.txt
- name: UsbGPU boot time
run: sudo -E PYTHONPATH=. DEBUG=2 AM_RESET=1 AMD=1 AMD_IFACE=USB time python3.11 test/test_tiny.py TestTiny.test_plus
- name: UsbGPU tiny tests
run: sudo -E PYTHONPATH=. AMD=1 AMD_IFACE=USB python3.11 test/test_tiny.py
- name: UsbGPU copy speeds
run: sudo -E PYTHONPATH=. AMD=1 AMD_IFACE=USB python3.11 test/external/external_test_usb_asm24.py TestDevCopySpeeds
#- name: UsbGPU openpilot test
# run: sudo -E PYTHONPATH=. AMD=1 AMD_IFACE=USB GRAPH_ONE_KERNEL=1 python3.11 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/9118973ed03c1ae1d40cf69a29507ec2cc78efd7/selfdrive/modeld/models/supercombo.onnx
- name: UsbGPU (USB4/TB) boot time
run: PYTHONPATH=. DEBUG=3 NV=1 NV_IFACE=PCI NV_NAK=1 time python3.11 test/test_tiny.py TestTiny.test_plus
- name: UsbGPU (USB4/TB) tiny tests
run: PYTHONPATH=. NV=1 NV_IFACE=PCI NV_NAK=1 python3.11 test/test_tiny.py
- uses: actions/upload-artifact@v4
with:
name: Speed (Mac)
@@ -169,6 +151,37 @@ jobs:
- name: Run process replay tests
run: cp test/external/process_replay/process_replay.py ./process_replay.py && git fetch origin master && git -c advice.detachedHead=false checkout origin/master && PYTHONPATH=. python3.11 process_replay.py
testusbgpu:
name: UsbGPU Benchmark
env:
PYTHONPYCACHEPREFIX: /tmp/tiny_python_pycache
runs-on: [self-hosted, macOS]
timeout-minutes: 10
defaults:
run:
shell: bash -e -o pipefail {0}
if: github.repository_owner == 'tinygrad'
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: setup staging db
if: github.ref == 'refs/heads/update_benchmark_staging'
run: |
echo "CACHEDB=/tmp/staging.db" >> $GITHUB_ENV
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
- name: UsbGPU boot time
run: sudo -E PYTHONPATH=. DEBUG=2 AM_RESET=1 AMD=1 AMD_IFACE=USB time python3.11 test/test_tiny.py TestTiny.test_plus
- name: UsbGPU tiny tests
run: sudo -E PYTHONPATH=. AMD=1 AMD_IFACE=USB python3.11 test/test_tiny.py
- name: UsbGPU copy speeds
run: sudo -E PYTHONPATH=. AMD=1 AMD_IFACE=USB python3.11 test/external/external_test_usb_asm24.py TestDevCopySpeeds
#- name: UsbGPU openpilot test
# run: sudo -E PYTHONPATH=. AMD=1 AMD_IFACE=USB GRAPH_ONE_KERNEL=1 python3.11 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/9118973ed03c1ae1d40cf69a29507ec2cc78efd7/selfdrive/modeld/models/supercombo.onnx
- name: UsbGPU (USB4/TB) boot time
run: PYTHONPATH=. DEBUG=3 NV=1 NV_IFACE=PCI NV_NAK=1 time python3.11 test/test_tiny.py TestTiny.test_plus
- name: UsbGPU (USB4/TB) tiny tests
run: PYTHONPATH=. NV=1 NV_IFACE=PCI NV_NAK=1 python3.11 test/test_tiny.py
testnvidiabenchmark:
name: tinybox green Benchmark
runs-on: [self-hosted, Linux, tinyboxgreen]
@@ -541,8 +554,6 @@ jobs:
run: time BENCHMARK_LOG=cifar AMD=1 DEFAULT_FLOAT=HALF STEPS=1000 TARGET_EVAL_ACC_PCT=93.0 python3 examples/hlb_cifar10.py | tee train_cifar_one_gpu.txt
- name: Run full CIFAR training steps w 6 GPUS
run: time BENCHMARK_LOG=cifar_6gpu AMD=1 DEFAULT_FLOAT=HALF STEPS=350 BS=1536 GPUS=6 TARGET_EVAL_ACC_PCT=93.0 python3 examples/hlb_cifar10.py | tee train_cifar_six_gpu.txt
- name: Run full CIFAR training steps w 6 GPUS (REMOTE)
run: time BENCHMARK_LOG=cifar_6gpu_remote REMOTE=1 REMOTEDEV=AMD DEFAULT_FLOAT=HALF STEPS=350 BS=1536 GPUS=6 TARGET_EVAL_ACC_PCT=93.0 python3 examples/hlb_cifar10.py | tee train_cifar_six_gpu_remote.txt
- uses: actions/upload-artifact@v4
with:
name: Speed (AMD Training)
@@ -554,7 +565,6 @@ jobs:
train_cifar_wino.txt
train_cifar_one_gpu.txt
train_cifar_six_gpu.txt
train_cifar_six_gpu_remote.txt
- name: Run process replay tests
run: cp test/external/process_replay/process_replay.py ./process_replay.py && git fetch origin master && git -c advice.detachedHead=false checkout origin/master && PYTHONPATH=. python3 process_replay.py
+1 -90
View File
@@ -310,7 +310,7 @@ jobs:
deps: testing_unit
python-version: '3.14'
- name: Test SPEC=2
run: IGNORE_OOB=0 SPEC=2 PYTHONPATH="." pytest --maxfail=10 -n auto --durations=30 --ignore=test/models --ignore test/unit/test_hashing.py --timeout 60 -k "not test_setitem_big" --splits 2 --group ${{ matrix.group }}
run: IGNORE_OOB=0 SPEC=2 PYTHONPATH="." pytest --maxfail=10 -n auto --durations=30 --ignore=test/models --ignore test/test_custom_kernel.py --ignore test/unit/test_hashing.py --timeout 60 -k "not test_setitem_big" --splits 2 --group ${{ matrix.group }}
fuzzing:
name: Fuzzing
@@ -721,71 +721,6 @@ jobs:
- name: Run process replay tests
uses: ./.github/actions/process-replay
amdremote:
name: Linux (remote)
runs-on: ubuntu-22.04
timeout-minutes: 20
env:
REMOTE: 1
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Environment
uses: ./.github/actions/setup-tinygrad
with:
key: linux-remote
deps: testing_minimal
amd: 'true'
llvm: 'true'
opencl: 'true'
- name: Start remote server
run: |
start_server() {
systemd-run --user \
--unit="$1" \
--setenv=REMOTEDEV="$2" \
--setenv=MOCKGPU=1 \
--setenv=PYTHONPATH=. \
--setenv=PORT="$3" \
--working-directory="$(pwd)" \
python tinygrad/runtime/ops_remote.py
}
start_server "remote-server-amd-1" "AMD" 6667
start_server "remote-server-amd-2" "AMD" 6668
start_server "remote-server-gpu" "CL" 7667
start_server "remote-server-cpu" "CPU" 8667
- name: Check Device.DEFAULT and print some source
env:
HOST: 127.0.0.1:6667*6,127.0.0.1:6668*6
run: |
python -c "from tinygrad import Device; assert Device.DEFAULT == 'REMOTE', Device.DEFAULT"
python -c "from tinygrad import Device; assert Device.default.properties.real_device == 'AMD', Device.default.properties.real_device"
DEBUG=4 python3 test/test_tiny.py TestTiny.test_plus
- name: Run REMOTE=1 Test (AMD)
env:
HOST: 127.0.0.1:6667*6,127.0.0.1:6668*6
run: |
python3 -m pytest test/test_tiny.py test/test_jit.py test/test_subbuffer.py test/test_graph.py test/test_multitensor.py test/test_remote.py test/test_tensor_variable.py --durations 20
- name: Run REMOTE=1 Test (CL)
env:
HOST: 127.0.0.1:7667*6
run: |
python3 -m pytest test/test_tiny.py test/test_image_dtype.py test/test_jit.py --durations 20
IMAGE=2 python3 -m pytest test/test_tiny.py test/test_image_dtype.py
- name: Run REMOTE=1 Test (CPU)
env:
HOST: 127.0.0.1:8667*6
run: |
python3 -m pytest test/test_tiny.py test/test_jit.py test/test_multitensor.py --durations 20
- name: Show remote server logs
if: always()
run: |
journalctl --user -u remote-server-amd-1 --no-pager
journalctl --user -u remote-server-amd-2 --no-pager
journalctl --user -u remote-server-gpu --no-pager
journalctl --user -u remote-server-cpu --no-pager
# ****** OSX Tests ******
testmetal:
@@ -883,30 +818,6 @@ jobs:
- name: Test ONNX Runner (WEBGPU)
run: WEBGPU=1 python3 test/external/external_test_onnx_runner.py
osxremote:
name: MacOS (remote metal)
runs-on: macos-15
timeout-minutes: 10
env:
REMOTE: 1
REMOTEDEV: METAL
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Environment
uses: ./.github/actions/setup-tinygrad
with:
key: macos-remote
deps: testing_minimal
- name: Check Device.DEFAULT and print some source
run: |
python -c "from tinygrad import Device; assert Device.DEFAULT == 'REMOTE', Device.DEFAULT"
python -c "from tinygrad import Device; assert Device.default.properties.real_device == 'METAL', Device.default.properties.real_device"
DEBUG=4 python3 test/test_tiny.py TestTiny.test_plus
- name: Run REMOTE=1 Test
run: |
python3 -m pytest test/test_tiny.py test/test_jit.py test/test_subbuffer.py test/test_graph.py test/test_multitensor.py test/test_tensor_variable.py
osxtests:
strategy:
fail-fast: false
+227
View File
@@ -0,0 +1,227 @@
# Claude Code Guide for tinygrad
## Architecture Overview
tinygrad compiles tensor operations into optimized kernels. The pipeline:
1. **Tensor** (`tensor.py`) - User-facing API, creates UOp graph
2. **UOp** (`uop/ops.py`) - Unified IR for all operations (both tensor and kernel level)
3. **Schedule** (`engine/schedule.py`, `schedule/`) - Converts tensor UOps to kernel UOps
4. **Codegen** (`codegen/`) - Converts kernel UOps to device code
5. **Runtime** (`runtime/`) - Device-specific execution
## Key Concepts
### UOp (Universal Operation)
Everything is a UOp - tensors, operations, buffers, kernels. Key properties:
- `op`: The operation type (Ops enum)
- `dtype`: Data type
- `src`: Tuple of source UOps
- `arg`: Operation-specific argument
- `tag`: Optional tag for graph transformations
UOps are **immutable and cached** - creating the same UOp twice returns the same object (ucache).
### PatternMatcher
Used extensively for graph transformations:
```python
pm = PatternMatcher([
(UPat(Ops.ADD, src=(UPat.cvar("x"), UPat.cvar("x"))), lambda x: x * 2),
])
result = graph_rewrite(uop, pm)
```
### Schedule Cache
Schedules are cached by graph structure. BIND nodes (variables with bound values) are unbound before cache key computation so different values hit the same cache.
## Directory Structure
```
tinygrad/
├── tensor.py # Tensor class, user API
├── device.py # Buffer, device management
├── dtype.py # Data types
├── helpers.py # Utilities, environment vars
├── uop/
│ ├── ops.py # UOp class, Ops enum, PatternMatcher
│ ├── spec.py # UOp type verification
│ └── symbolic.py # Symbolic math simplification
├── engine/
│ ├── schedule.py # Schedule creation, caching
│ ├── realize.py # Tensor realization
│ ├── jit.py # JIT compilation
│ └── memory.py # Memory planning
├── schedule/
│ ├── rangeify.py # Convert movements to ranges
│ └── indexing.py # Index calculations
├── codegen/
│ ├── kernel.py # Kernel optimization
│ └── uopgraph.py # UOp graph transformations
├── renderer/ # Code generation (CUDA, Metal, etc.)
└── runtime/ # Device backends
```
## Testing
```bash
# Run specific test
python -m pytest test/unit/test_schedule_cache.py -xvs
# Run with timeout
python -m pytest test/test_symbolic_ops.py -x --timeout=60
# Debug with print
DEBUG=2 python -m pytest test/test_schedule.py::test_name -xvs
# Visualize UOp graphs
VIZ=1 python -c "from tinygrad import Tensor; Tensor.ones(10).sum().realize()"
```
## Common Environment Variables
- `DEBUG=1-4` - Increasing verbosity
- `VIZ=1` - Enable graph visualization
- `SPEC=1` - Enable UOp spec verification
- `NOOPT=1` - Disable optimizations
- `DEVICE=CPU/CUDA/AMD/METAL` - Set default device
## Debugging Tips
1. **Print UOp graphs**: `print(tensor.uop)` or `print(tensor.uop.sink())`
2. **Check schedule**: `tensor.schedule()` returns list of ScheduleItems
3. **Trace graph rewrites**: Use `VIZ=1` or add print in PatternMatcher callbacks
4. **Find UOps by type**: `[u for u in uop.toposort() if u.op is Ops.SOMETHING]`
## Workflow Rules
- **NEVER commit without explicit user approval** - always show the diff and wait for approval
- **NEVER amend commits** - always create a new commit instead
- Run `pre-commit run --all-files` before committing to catch linting/type errors
- Run tests before proposing commits
- Test with `SPEC=2` when modifying UOp-related code
## Style Notes
- 2-space indentation, 150 char line limit
- PatternMatchers should be defined at module level (slow to construct)
- Prefer `graph_rewrite` over manual graph traversal
- UOp methods like `.replace()` preserve tags unless explicitly changed
- Use `.rtag(value)` to add tags to UOps
## Lessons Learned
### UOp ucache Behavior
UOps are cached by their contents - creating a UOp with identical (op, dtype, src, arg) returns the **same object**. This means:
- `uop.replace(tag=None)` on a tagged UOp returns the original untagged UOp if it exists in cache
- Two UOps with same structure are identical (`is` comparison works)
### Spec Validation
When adding new UOp patterns, update `tinygrad/uop/spec.py`. Test with:
```bash
SPEC=2 python3 test/unit/test_something.py
```
Spec issues appear as `RuntimeError: SPEC ISSUE None: UOp(...)`.
### Schedule Cache Key Normalization
The schedule cache strips values from BIND nodes so different bound values (e.g., KV cache positions) hit the same cache entry:
- `pm_pre_sched_cache`: BIND(DEFINE_VAR, CONST) → BIND(DEFINE_VAR) for cache key
- `pm_post_sched_cache`: restores original BIND from context
- When accessing `bind.src[1]`, check `len(bind.src) > 1` first (might be stripped)
- Extract var_vals from `input_buffers` dict after graph_rewrite (avoids extra toposort)
### Avoiding Extra Work
- Use ctx dict from graph_rewrite to collect info during traversal instead of separate toposort
- Only extract var_vals when schedule is non-empty (no kernels = no vars needed)
- PatternMatchers are slow to construct - define at module level, not in functions
### Readability Over Speed
Don't add complexity for marginal performance gains. Simpler code that's slightly slower is often better:
```python
# BAD: "optimized" with extra complexity
if has_afters: # skip toposort if no AFTERs
after_map = [(u, u.buf_uop) for u in big_sink.toposort() if u.op is Ops.AFTER]
# GOOD: simple, always works
after_map = [(u, u.buf_uop) for u in big_sink.toposort() if u.op is Ops.AFTER]
```
The conditional check adds complexity, potential bugs, and often negligible speedup. Only optimize when profiling shows a real bottleneck.
### Testing LLM Changes
```bash
# Quick smoke test
echo "Hello" | DEBUG=1 python tinygrad/apps/llm.py --model "llama3.2:1b"
# Check cache hits (should see "cache hit" after warmup)
echo "Hello world" | DEBUG=1 python tinygrad/apps/llm.py --model "llama3.2:1b" 2>&1 | grep cache
# Test with beam search
echo "Hello" | BEAM=2 python tinygrad/apps/llm.py --model "llama3.2:1b"
```
## Common Patterns
### Graph Transformation
```python
def my_transform(ctx, x):
# Return new UOp or None to skip
return x.replace(arg=new_arg)
pm = PatternMatcher([
(UPat(Ops.SOMETHING, name="x"), my_transform),
])
result = graph_rewrite(input_uop, pm, ctx={})
```
### Finding Variables
```python
# Get all variables in a UOp graph
variables = uop.variables()
# Get bound variable values
var, val = bind_uop.unbind()
```
### Shape Handling
```python
# Shapes can be symbolic (contain UOps)
shape = tensor.shape # tuple[sint, ...] where sint = int | UOp
```
## Performance Optimization
When optimizing tinygrad internals:
1. **Measure wall time, not just call counts** - Reducing `graph_rewrite` calls doesn't always improve wall time. The overhead of conditional checks can exceed the cost of the operation being skipped.
2. **Profile each optimization individually** - Run benchmarks with and without each change to measure actual impact. Use `test/external/external_benchmark_schedule.py` for schedule/rewrite timing.
3. **Early exits in hot paths are effective** - Simple checks like `if self.op is Ops.CONST: return self` in `simplify()` can eliminate many unnecessary `graph_rewrite` calls.
4. **`graph_rewrite` is expensive** - Each call has overhead even for small graphs. Avoid calling it when the result is trivially known (e.g., simplifying a CONST returns itself).
5. **Beware iterator overhead** - Checks like `all(x.op is Ops.CONST for x in self.src)` can be slower than just running the operation, especially for small sequences.
6. **Verify cache hit rates before adding/keeping caches** - Measure actual hit rates with real workloads. A cache with 0% hit rate is pure overhead (e.g., `pm_cache` was removed because the algorithm guarantees each UOp is only passed to `pm_rewrite` once).
7. **Use `TRACK_MATCH_STATS=2` to profile pattern matching** - This shows match rates and time per pattern. Look for patterns with 0% match rate that still cost significant time - these are pure overhead for that workload.
8. **Cached properties beat manual traversal** - `backward_slice` uses `@functools.cached_property`. A DFS with early-exit sounds faster but is actually slower because it doesn't benefit from caching. The cache hit benefit often outweighs algorithmic improvements.
9. **Avoid creating intermediate objects in hot paths** - For example, `any(x.op in ops for x in self.backward_slice)` is faster than `any(x.op in ops for x in {self:None, **self.backward_slice})` because it avoids dict creation.
## Pattern Matching Profiling
Use `TRACK_MATCH_STATS=2` to identify expensive patterns:
```bash
TRACK_MATCH_STATS=2 PYTHONPATH="." python3 test/external/external_benchmark_schedule.py
```
Output format: `matches / attempts -- match_time / total_time ms -- location`
Key patterns to watch (from ResNet50 benchmark):
- `split_load_store`: ~146ms, 31% match rate - does real work
- `simplify_valid`: ~75ms, 0% match rate in this workload - checks AND ops for INDEX in backward slice
- `vmin==vmax folding`: ~55ms, 0.33% match rate - checks 52K ops but rarely matches
Patterns with 0% match rate are workload-specific overhead. They may be useful in other workloads, so don't remove them without understanding their purpose.
+7 -7
View File
@@ -223,13 +223,13 @@ def get_mlperf_bert_model():
def get_fake_data_bert(BS:int):
return {
"input_ids": Tensor.empty((BS, 512), dtype=dtypes.int32, device="CPU"),
"input_mask": Tensor.empty((BS, 512), dtype=dtypes.int32, device="CPU"),
"segment_ids": Tensor.empty((BS, 512), dtype=dtypes.int32, device="CPU"),
"masked_lm_positions": Tensor.empty((BS, 76), dtype=dtypes.int32, device="CPU"),
"masked_lm_ids": Tensor.empty((BS, 76), dtype=dtypes.int32, device="CPU"),
"masked_lm_weights": Tensor.empty((BS, 76), dtype=dtypes.float32, device="CPU"),
"next_sentence_labels": Tensor.empty((BS, 1), dtype=dtypes.int32, device="CPU"),
"input_ids": Tensor.zeros((BS, 512), dtype=dtypes.int32, device="CPU").contiguous(),
"input_mask": Tensor.zeros((BS, 512), dtype=dtypes.int32, device="CPU").contiguous(),
"segment_ids": Tensor.zeros((BS, 512), dtype=dtypes.int32, device="CPU").contiguous(),
"masked_lm_positions": Tensor.zeros((BS, 76), dtype=dtypes.int32, device="CPU").contiguous(),
"masked_lm_ids": Tensor.zeros((BS, 76), dtype=dtypes.int32, device="CPU").contiguous(),
"masked_lm_weights": Tensor.zeros((BS, 76), dtype=dtypes.float32, device="CPU").contiguous(),
"next_sentence_labels": Tensor.zeros((BS, 1), dtype=dtypes.int32, device="CPU").contiguous(),
}
def find_matches(match_quality_matrix:np.ndarray, high_threshold:float=0.5, low_threshold:float=0.4, allow_low_quality_matches:bool=False) -> np.ndarray:
+11 -8
View File
@@ -1177,7 +1177,8 @@ def train_bert():
if MLLOGGER and RUNMLPERF:
MLLOGGER.start(key=mllog_constants.EVAL_START, value=None, metadata={"epoch_num": i*GBS, "step_num": i})
if getenv("RESET_STEP"): train_step_bert.reset()
elif getenv("FREE_INTERMEDIATE", 1) and train_step_bert.captured is not None:
elif getenv("FREE_INTERMEDIATE") and train_step_bert.captured is not None:
# TODO: this hangs on tiny green after 90 minutes of training
train_step_bert.captured.free_intermediates()
eval_lm_losses = []
eval_clsf_losses = []
@@ -1212,7 +1213,7 @@ def train_bert():
return
if getenv("RESET_STEP"): eval_step_bert.reset()
elif getenv("FREE_INTERMEDIATE", 1) and eval_step_bert.captured is not None: eval_step_bert.captured.free_intermediates()
elif getenv("FREE_INTERMEDIATE") and eval_step_bert.captured is not None: eval_step_bert.captured.free_intermediates()
del eval_data
avg_lm_loss = sum(eval_lm_losses) / len(eval_lm_losses)
@@ -1313,12 +1314,14 @@ def train_llama3():
opt_base_learning_rate = getenv("LR", 8e-5 * GBS / 1152) # NOTE: cannot change for benchmark
opt_end_learning_rate = getenv("END_LR", 8e-7)
# TODO: confirm weights are in bf16
model_params = MODEL_PARAMS[getenv("LLAMA3_SIZE", "8B")]["args"]
# vocab_size from the mixtral tokenizer
params = MODEL_PARAMS[getenv("LLAMA3_SIZE", "8B")]["args"]
params = params | {"vocab_size": 32000} if not SMALL else params
if (llama_layers:=getenv("LLAMA_LAYERS")) != 0: params['n_layers'] = llama_layers
model = Transformer(**params, max_context=SEQLEN, jit=False, disable_kv_cache=True)
if not SMALL: model_params |= {"vocab_size": 32000}
if (llama_layers:=getenv("LLAMA_LAYERS")) != 0: model_params['n_layers'] = llama_layers
model = Transformer(**model_params, max_context=SEQLEN, jit=False, disable_kv_cache=True)
params = get_parameters(model)
# weights are all bfloat16 for now
assert params and all(p.dtype == dtypes.bfloat16 for p in params)
if getenv("FAKEDATA"):
for v in get_parameters(model):
@@ -1408,7 +1411,7 @@ def train_llama3():
# ** data iters **
def fake_data(bs, samples):
for _ in range(samples // bs):
yield Tensor.randint(bs, SEQLEN + 1, low=0, high=params["vocab_size"], dtype=dtypes.int32, device=Device.DEFAULT)
yield Tensor.randint(bs, SEQLEN + 1, low=0, high=model_params["vocab_size"], dtype=dtypes.int32, device=Device.DEFAULT)
def get_train_iter():
if getenv("FAKEDATA", 0):
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env python3
from tinygrad.helpers import Context
from tinygrad.runtime.support.system import System, PCIDevice, PCIDevImplBase
from tinygrad.runtime.support.am.amdev import AMDev
if __name__ == "__main__":
gpus = System.pci_scan_bus(0x1002, [(0xffff, [0x74a1])])
pcidevs = [PCIDevice(f"reset:{gpu}", gpu, bars=[0, 2, 5]) for gpu in gpus]
amdevs = []
with Context(DEBUG=2):
for pcidev in pcidevs:
amdevs.append(AMDev(pcidev, reset_mode=True))
for amdev in amdevs: amdev.smu.mode1_reset()
+2 -1
View File
@@ -242,7 +242,8 @@ class BertIntermediate:
def __call__(self, hidden_states):
x = self.dense(hidden_states)
# tinygrad gelu is openai gelu but we need the original bert gelu
return gelu(x)
# NOTE: contiguous for speed
return gelu(x).contiguous()
class BertAttention:
def __init__(self, hidden_size, num_attention_heads, attention_probs_dropout_prob, hidden_dropout_prob):
+1 -1
View File
@@ -1,5 +1,4 @@
import ctypes, pathlib, argparse, pickle, re, functools, dataclasses, itertools, threading
from tabulate import tabulate
from typing import Generator
from tinygrad.helpers import temp, unwrap, DEBUG
from tinygrad.device import ProfileEvent, ProfileDeviceEvent, ProfileProgramEvent
@@ -161,6 +160,7 @@ def decode(profile:list[ProfileEvent]) -> _ROCParseCtx:
def print_pmc(events:list[ProfilePMCEvent]) -> None:
from tinygrad.viz.serve import unpack_pmc
from tabulate import tabulate
for e in events:
print("**", e.kern)
data = unpack_pmc(e)
+39 -1
View File
@@ -6,8 +6,11 @@ import unittest
import functools, contextlib
import numpy as np
from tinygrad import Tensor, Context, Device
from tinygrad.uop.ops import UOp, KernelInfo, AxisType
from tinygrad.dtype import dtypes, AddrSpace
from tinygrad.uop.ops import UOp, Ops, KernelInfo, AxisType
from tinygrad.runtime.ops_amd import ProfilePMCEvent
from tinygrad.engine.realize import get_runner
from tinygrad.viz.serve import unpack_pmc
from extra.sqtt.roc import print_pmc
def copy_kernel(B, A, stride=1):
@@ -19,6 +22,16 @@ def copy_kernel(B, A, stride=1):
index = (i * stride) % A.size
return B[index].store(A[index]).sink(arg=KernelInfo(name=f"copy_{A.size}_stride_{stride}", opts_to_apply=()))
def lds_kernel(offset:UOp, size:int, inst:str) -> UOp:
tid = UOp.range(offset.size, 0, AxisType.LOCAL)
dst = UOp.placeholder((size,), dtypes.float32, 1, AddrSpace.REG)
#lds = UOp.placeholder((1024,), dtypes.float32, 2, AddrSpace.LOCAL)
u = UOp(Ops.CUSTOM, arg='__builtin_amdgcn_s_waitcnt(0);')
u = UOp(Ops.CUSTOM, arg='__builtin_amdgcn_s_barrier();', src=(u,))
u = UOp(Ops.CUSTOM, arg='__builtin_amdgcn_sched_barrier(0);', src=(u,))
u = UOp(Ops.CUSTOM, arg=f'asm volatile("{inst} '+'%0, %1" : "=v"({0}) : "v"({1}));', src=(dst, offset[tid], u))
return UOp.sink(u, arg=KernelInfo(name="test_lds", opts_to_apply=()))
dev = Device[Device.DEFAULT]
@contextlib.contextmanager
@@ -45,5 +58,30 @@ class TestPMC(unittest.TestCase):
def test_copy_uncoalesced(self): return self.test_copy(stride=17)
# test with two threads issuing ds_reads at different offsets
def test_ds_read(self, size=1, inst='ds_read_b32'):
test_banks = 256
offsets = [Tensor([0, b*4]) for b in range(1, test_banks)]
with Context(DEBUG=0): Tensor.realize(*offsets)
k = Tensor.custom_kernel(offsets[0], fxn=functools.partial(lds_kernel, size=size, inst=inst))[0]
# sample all kernels
with save_pmc() as pmc_events:
runner = get_runner(Device.DEFAULT, k.schedule()[0].ast)
# TODO: llvm eliminates lds definition from the ELF, is there another way to pin lds size?
runner._prg.group_segment_size = 1024
for offset in offsets: runner([offset.uop.buffer])
# find read offsets that created bank conflicts from the pmc counters
found:list[Tensor] = []
for i,e in enumerate(pmc_events):
pmc = unpack_pmc(e)["rows"]
# SQ on gfx9, renamed to SQC after gfx10
val = next(total for name,total,_all_instances in pmc if name in {"SQ_LDS_BANK_CONFLICT", "SQC_LDS_BANK_CONFLICT"})
if val > 0: found.append(offsets[i])
print("Found bank conflicts at offsets:", [s.numpy() for s in found])
def test_ds_read_b64(self): self.test_ds_read(2, 'ds_read_b64')
def test_ds_read_b128(self): self.test_ds_read(4, 'ds_read_b128')
if __name__ == "__main__":
unittest.main()
+23 -1
View File
@@ -404,7 +404,29 @@ class Group:
dst, src = cast(UOp, dst), cast(UOp, src)
assert isinstance(dst.dtype, PtrDType) and isinstance(src.dtype, PtrDType)
dst_dtype, src_dtype = dst.dtype, src.dtype
if src_dtype.addrspace == AddrSpace.REG and dst_dtype.addrspace == AddrSpace.GLOBAL and isinstance(src, RT):
if src_dtype.addrspace == AddrSpace.REG and dst_dtype.addrspace == AddrSpace.LOCAL:
laneid = self.ker.laneid
st, rt = cast(ST, dst), cast(RT, src)
elements_per_thread = rt.base_shape.elements_per_thread
for height in self.ker.range(src.shape[-3], track=False):
for width in self.ker.range(src.shape[-2], track=False):
for inner in self.ker.range(elements_per_thread, track=False):
if rt.layout != st.layout:
row = rt.base_shape.stride * (laneid // rt.base_shape.cols) + inner
col = laneid % rt.base_shape.cols
else:
row = laneid % rt.base_shape.rows
col = rt.base_shape.stride * (laneid // rt.base_shape.rows) + inner
srow, scol = cast(ST, dst).swizzle(row, col)
src_load = src[*src_idxs, height, width, inner]
if src.dtype.base != dst.dtype.base:
src_load = src_load.cast(dst.dtype.base)
dst_store = dst[*idxs[:-2], height, width, srow, scol].store(src_load)
dst_store = dst_store.end(height, width, inner)
elif src_dtype.addrspace == AddrSpace.REG and dst_dtype.addrspace == AddrSpace.GLOBAL and isinstance(src, RT):
dstf = dst.flatten()
row_stride = prod(dst.shape[axis+1:])
+8 -4
View File
@@ -82,14 +82,18 @@ class Kernel(AbstractContextManager):
def push_store(self, store:UOp, uop:UOp): self.store_stack.append((store, uop))
def finish(self):
def finish(self, stores:int=1):
# end all ranges
rngs = []
while self.range_stack: rngs.append(self.range_stack.pop(0)._rng)
last_store = self.store_stack.pop()[0]
if hasattr(last_store, '_uop'): uop = last_store._uop
else: uop = last_store
# end stores stores
store_uops = []
for _i in range(stores):
store = self.store_stack.pop()[0]
if hasattr(store, '_uop'): store_uops.append(store._uop)
else: store_uops.append(store)
uop = UOp.group(*store_uops)
return uop.end(*rngs).sink(arg=KernelInfo(name=self.name, opts_to_apply=())).simplify()
+15 -4
View File
@@ -3,6 +3,13 @@ import sys, os, zlib, struct, hashlib
from tinygrad.helpers import DEBUG, getenv, fetch
from tinygrad.runtime.support.usb import USB3
SUPPORTED_CONTROLLERS = [
(0x174C, 0x2464),
(0x174C, 0x2463),
(0xADD1, 0x0001),
]
if getenv("USBDEV", ""): SUPPORTED_CONTROLLERS.insert(0, (int(x, 16) for x in getenv("USBDEV", "").split(":")))
def patch(input_filepath, file_hash, patches):
with open(input_filepath, 'rb') as infile: data = bytearray(infile.read())
@@ -40,10 +47,14 @@ if not os.path.exists(file_path):
patches = [(0x2a0d + 1 + 4, b'\x0a', b'\x05')]
patched_fw = patch(file_path, file_hash, patches)
vendor, device = [int(x, base=16) for x in getenv("USBDEV", "174C:2464").split(":")]
try: dev = USB3(vendor, device, 0x81, 0x83, 0x02, 0x04)
except RuntimeError as e:
raise RuntimeError(f'{e}. You can set USBDEV environment variable to your device\'s vendor and device ID (e.g., USBDEV="174C:2464")') from e
dev = None
for vendor, device in SUPPORTED_CONTROLLERS:
try:
dev = USB3(vendor, device, 0x81, 0x83, 0x02, 0x04)
break
except RuntimeError: pass
if dev is None:
raise RuntimeError('Could not open controller. You can set USBDEV environment variable to your device\'s vendor and device ID (e.g., USBDEV="174C:2464")')
config1 = bytes([
0xFF, 0xFF, 0xFF, 0xFF, 0x41, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x42, 0x30, 0x30, 0x36, 0x30,
+1 -1
View File
@@ -1,7 +1,7 @@
# extra/weekly_commits_table.py
import os, subprocess, datetime as dt
NAMES = ["chenyu","George Hotz","nimlgen","qazal","wozeparrot"]
NAMES = ["chenyu","George Hotz","nimlgen","qazal","wozeparrot","Christopher Milan"]
REPO = os.environ.get("REPO_PATH",".")
today = dt.date.today()
days = [(today - dt.timedelta(i)).strftime("%Y-%m-%d") for i in range(6,-1,-1)]
+1 -1
View File
@@ -8,7 +8,7 @@ def multidevice_test(fxn):
def ret(self):
for device in Device._devices:
# broken on OSX USB AMD, why?
if device in ["REMOTE", "DISK", "NPY", "FAKE", "DSP", "NULL"] or (OSX and device in ["AMD"]): continue
if device in ["DISK", "NPY", "FAKE", "DSP", "NULL"] or (OSX and device in ["AMD"]): continue
if not CI: print(device)
if device in exclude_devices:
if not CI: print(f"WARNING: {device} test is excluded")
+1 -2
View File
@@ -69,5 +69,4 @@ def needs_second_gpu(fn):
return fn(self, *args, **kwargs)
return wrapper
# NOTE: This will open REMOTE if it's the default device
REAL_DEV = (Device.DEFAULT if Device.DEFAULT != "REMOTE" else Device['REMOTE'].properties.real_device)
REAL_DEV = Device.DEFAULT
+12 -2
View File
@@ -1,5 +1,5 @@
import unittest
from tinygrad import Tensor, UOp, Context
from tinygrad import Tensor, UOp
from tinygrad.dtype import AddrSpace
from tinygrad.uop.ops import KernelInfo, AxisType
@@ -117,6 +117,17 @@ class TestCustomKernel(unittest.TestCase):
out = c.flatten().tolist()
assert all(x == 2 for x in out), "all 2"
def test_simple_sharded(self):
devs = ("CPU:0", "CPU:1")
a = Tensor.ones(16, 16).contiguous().shard(devs, axis=0)
b = Tensor.ones(16, 16).contiguous().shard(devs, axis=0)
# ugly construction to get a sharded empty tensor
c = Tensor(Tensor.empty(8, 16, device=devs).uop.multi(0), device=devs)
c = Tensor.custom_kernel(c,a,b, fxn=custom_elementwise_add_kernel)[0]
out = c.flatten().tolist()
assert all(x == 2 for x in out), "all 2"
def test_multioutput(self):
a = Tensor.full((16, 16), 3.).contiguous()
b = Tensor.full((16, 16), 3.).contiguous()
@@ -184,7 +195,6 @@ class TestCustomKernel(unittest.TestCase):
def test_gemm_backward_custom(self): self.test_gemm_backward(True)
# NOTE: grad_fxn doesn't work with pyrender
@Context(SPEC=1)
def test_gemm_backward(self, custom_backward_gemm=False):
N = 4
a_rand = Tensor.randn(N, 8)
+270
View File
@@ -0,0 +1,270 @@
#!/usr/bin/env python
"""
JIT Footguns: Documenting unexpected behavior changes when using @TinyJit
Each test shows behavior that works without JIT but changes with JIT.
Comments marked "should be X!" indicate the intuitively expected value.
SILENT MISMATCHES (highest priority - wrong results, no error):
tensors_in_containers_ignored EASY only checks t.__class__ is Tensor, could scan lists/dicts
non_tensor_outputs_frozen EASY could warn/error if return contains non-Tensor values
class_method_shared_across_instances EASY could check if first arg is self and warn
output_buffer_reuse MED performance tradeoff, could add option or better docs
python_constants_frozen HARD inherent to tracing JITs
conditional_branches_frozen HARD inherent to tracing JITs
ERRORS RAISED (lower priority - at least users know):
positional_kwargs_cannot_mix EASY normalize positional args to kwargs using function signature
duplicate_inputs_fail MED would need to handle aliasing in input_replace
nested_jit_fails_on_second_call MED could fail on first call instead of second
"""
import unittest
import numpy as np
from tinygrad import Tensor, TinyJit
class TestJitFootguns(unittest.TestCase):
def test_output_buffer_reuse(self):
"""Output tensors share buffer after capture - old references get overwritten."""
@TinyJit
def f(x): return x.sum().realize()
r1 = f(Tensor([1, 1])) # warmup
r2 = f(Tensor([2, 2])) # capture
r3 = f(Tensor([3, 3])) # jit exec
self.assertEqual(r1.item(), 2) # warmup result independent
self.assertEqual(r3.item(), 6) # latest is correct
self.assertEqual(r2.item(), 6) # should be 4! (overwritten by r3)
def test_output_buffer_workaround(self):
"""Use .clone().realize() to get independent copies."""
@TinyJit
def f(x): return x.sum().realize()
r1 = f(Tensor([1, 1])).clone().realize()
r2 = f(Tensor([2, 2])).clone().realize()
r3 = f(Tensor([3, 3])).clone().realize()
self.assertEqual([r1.item(), r2.item(), r3.item()], [2, 4, 6])
def test_non_tensor_outputs_frozen(self):
"""Non-tensor return values are frozen at capture time."""
@TinyJit
def f(x, mult): return (x * 2).realize(), mult * 10
# collect results, copying tensor values immediately (buffer reuse!)
results = []
for i in range(5):
t, s = f(Tensor([i]), i)
results.append((t.item(), s))
# tensor outputs work correctly
self.assertEqual([r[0] for r in results[2:]], [4, 6, 8])
# scalar outputs frozen at capture (i=1) - should be 20, 30, 40!
self.assertEqual([r[1] for r in results[2:]], [10, 10, 10])
def test_duplicate_inputs_fail(self):
"""JIT cannot handle the same tensor passed as multiple arguments."""
@TinyJit
def f(a, b): return (a + b).realize()
x = Tensor([1, 2, 3])
with self.assertRaises(AssertionError):
f(x, x)
def test_tensors_in_containers_ignored(self):
"""Tensors inside lists/dicts are not tracked as inputs."""
@TinyJit
def f(a, arr): return (a + arr[0]).realize()
results = []
for i in range(4):
a, b = Tensor([1, 1, 1]).realize(), Tensor([i, i, i]).realize()
results.append(f(a, [b]).numpy().copy())
np.testing.assert_array_equal(results[0], [1, 1, 1]) # warmup
np.testing.assert_array_equal(results[1], [2, 2, 2]) # capture
np.testing.assert_array_equal(results[2], [2, 2, 2]) # should be [3,3,3]!
np.testing.assert_array_equal(results[3], [2, 2, 2]) # should be [4,4,4]!
def test_nested_jit_fails_on_second_call(self):
"""Nested JIT works on first call but fails on second."""
@TinyJit
def inner(t): return t + 1
@TinyJit
def outer(t): return inner(t) * 3
self.assertEqual(outer(Tensor([1])).realize().item(), 6) # works!
with self.assertRaises(RuntimeError):
outer(Tensor([2])).realize() # fails
def test_implicit_inputs_need_realize(self):
"""Closure tensors must be realized before JIT call."""
x = Tensor([0])
@TinyJit
def f(): return (x * 2).realize()
for i in range(5):
x.assign(Tensor([i])).realize() # must realize!
self.assertEqual(f().item(), i * 2)
def test_views_with_different_offsets_fail(self):
"""JIT requires consistent tensor views across calls."""
@TinyJit
def f(a): return (a + 1).realize()
base = Tensor.randn(10, 10).realize()
with self.assertRaises(AssertionError):
for i in range(1, 5):
f(base[:, i:i+2]) # different offset each time
def test_shape_change_after_capture_fails(self):
"""Shapes are locked at capture time."""
@TinyJit
def f(a, b): return (a + b).realize()
f(Tensor.randn(10, 10), Tensor.randn(10, 10)) # warmup
f(Tensor.randn(10, 10), Tensor.randn(10, 10)) # capture
with self.assertRaises(AssertionError):
f(Tensor.randn(20, 20), Tensor.randn(20, 20))
def test_python_constants_frozen(self):
"""Python variables inside JIT use capture-time values."""
mult = 1
@TinyJit
def f(x): return (x * mult).realize()
results = []
for i in range(5):
mult = i + 1
results.append(f(Tensor([10])).item())
self.assertEqual(results[0], 10) # warmup, mult=1
self.assertEqual(results[1], 20) # capture, mult=2
self.assertEqual(results[2], 20) # should be 30!
self.assertEqual(results[3], 20) # should be 40!
def test_conditional_branches_frozen(self):
"""Only the branch taken during capture runs thereafter."""
@TinyJit
def f(x, use_square):
if use_square:
return (x * x).realize()
return (x * 2).realize()
f(Tensor([3]), True) # warmup
f(Tensor([3]), False) # capture (False branch)
result = f(Tensor([3]), True) # passing True but False branch runs
self.assertEqual(result.item(), 6) # should be 9!
def test_positional_kwargs_cannot_mix(self):
"""Must use same calling convention after capture."""
@TinyJit
def f(a, b): return (a + b).realize()
f(Tensor([1]), Tensor([2])) # warmup with positional
f(Tensor([1]), Tensor([2])) # capture with positional
with self.assertRaises(AssertionError):
f(a=Tensor([3]), b=Tensor([4])) # kwargs fail
def test_class_method_shared_across_instances(self):
"""JIT on instance methods is shared at class level."""
class Model:
def __init__(self, scale):
self.scale = Tensor([scale])
@TinyJit
def forward(self, x):
return (x * self.scale).realize()
m1, m2 = Model(2), Model(3)
m1.forward(Tensor([5])) # warmup
m1.forward(Tensor([5])) # capture with m1.scale=2
self.assertEqual(m1.forward(Tensor([5])).item(), 10)
self.assertEqual(m2.forward(Tensor([5])).item(), 10) # should be 15!
def test_side_effects_only_during_capture(self):
"""Function body not executed during JIT replay."""
call_count = [0]
@TinyJit
def f(x):
call_count[0] += 1
return (x * 2).realize()
f(Tensor([1])) # warmup
f(Tensor([2])) # capture
self.assertEqual(call_count[0], 2)
f(Tensor([3]))
f(Tensor([4]))
f(Tensor([5]))
self.assertEqual(call_count[0], 2) # still 2, not 5!
def test_nothing_realized_fails(self):
"""Must JIT at least one kernel."""
@TinyJit
def f(a, b): return None
with self.assertRaises(AssertionError):
for _ in range(3):
f(Tensor([1]), Tensor([2]))
class TestJitCorrectBehavior(unittest.TestCase):
"""Behaviors that work correctly - documented for clarity."""
def test_random_regenerates(self):
"""Random tensors regenerate each call."""
@TinyJit
def f(x):
return (x + Tensor.rand(3)).realize()
f(Tensor([0, 0, 0])) # warmup
f(Tensor([0, 0, 0])) # capture
results = {tuple(f(Tensor([0, 0, 0])).numpy().tolist()) for _ in range(5)}
self.assertEqual(len(results), 5)
def test_unrealized_return_auto_realized(self):
"""Unrealized return tensors are auto-realized."""
@TinyJit
def f(a, b): return a + b # no explicit realize
for _ in range(5):
a, b = Tensor.randn(10), Tensor.randn(10)
np.testing.assert_allclose(f(a, b).numpy(), a.numpy() + b.numpy(), atol=1e-5)
def test_kwargs_order_doesnt_matter(self):
"""Kwargs are sorted by name, so order doesn't matter."""
@TinyJit
def f(first, second): return (first / second).realize()
for _ in range(3):
a, b = Tensor.randn(10), Tensor.randn(10) + 1
np.testing.assert_allclose(f(second=b, first=a).numpy(), a.numpy() / b.numpy(), atol=1e-4)
np.testing.assert_allclose(f(first=a, second=b).numpy(), a.numpy() / b.numpy(), atol=1e-4)
def test_input_mutation_consistent(self):
"""Input mutation via assign works consistently."""
@TinyJit
def f(x):
x += 1
x.realize()
return x
a = Tensor([0]).contiguous().realize()
for _ in range(5):
f(a)
self.assertEqual(a.item(), 5)
if __name__ == '__main__':
unittest.main()
-101
View File
@@ -1,101 +0,0 @@
import numpy as np, unittest, string
from hypothesis import given, strategies as st
from tinygrad import Device, Tensor, TinyJit, dtypes
from tinygrad.runtime.ops_remote import RemoteDevice, parse_hosts
from tinygrad.runtime.graph.remote import RemoteGraph
from tinygrad.helpers import LazySeq, all_same, Context
def multihost_env(devices):
def same_hosts(devices): return all_same([h for h,_ in devices])
return isinstance(devices, list) and len(devices) >= 12 and not same_hosts(devices[0:12]) and same_hosts(devices[0:6]) and same_hosts(devices[6:12])
@unittest.skipUnless(Device.DEFAULT == "REMOTE" and multihost_env(RemoteDevice.devices), "Requires special environment")
class TestRemoteMultiHost(unittest.TestCase):
def test_mutlihost_transfer(self):
a = Tensor.arange(0, 16, device='REMOTE:0').contiguous().realize()
b = a.to('REMOTE:6').contiguous().realize()
np.testing.assert_equal(b.numpy(), np.arange(0, 16))
@Context(JIT_BATCH_SIZE=2**32)
@unittest.skip("kernel must all be multibuffer")
def test_multihost_matmul_jit_graph(self):
@TinyJit
def do(a:Tensor, b:Tensor): return (a @ b).contiguous().realize()
ds = ('REMOTE:0', 'REMOTE:1', 'REMOTE:6', 'REMOTE:7')
for _ in range(3):
na, nb = np.random.rand(128, 128).astype(np.float32), np.random.rand(128, 128).astype(np.float32)
a, b = Tensor(na).shard(ds, 0).contiguous().realize(), Tensor(nb).shard(ds, 0).contiguous().realize()
nc = na @ nb
c = do(a, b)
np.testing.assert_allclose(nc, c.numpy(), rtol=3e-2, atol=1e-4) # tolerances from extra/gemm/simple_matmul.py
# Verify that everything is in one big cross-host graph
assert len(do.captured._jit_cache) == 1 and isinstance(do.captured._jit_cache[0].prg, RemoteGraph), repr(do.captured)
@Context(JIT_BATCH_SIZE=2**32)
@unittest.skip("assign target and input devices mismatch")
def test_multihost_aware_schedule(self):
@TinyJit
def do(*ts:Tensor):
acc = Tensor.zeros(1, dtype=dtypes.float32).contiguous().realize()
for t in ts: acc += t.sum()
return acc.realize()
def do_np(*ts:np.ndarray):
acc = np.zeros(1, np.float32)
for t in ts: acc += t.sum()
return acc
ds = ('REMOTE:0', 'REMOTE:1', 'REMOTE:6', 'REMOTE:7')
TS = 64
for _ in range(3):
inp_np = [np.random.rand(256).astype(np.float32) for _ in range(TS)]
inp = [Tensor(inp).shard(ds, 0).contiguous().realize() for inp in inp_np]
out_np = do_np(*inp_np)
out = do(*inp)
np.testing.assert_allclose(out_np, out.numpy(), rtol=3e-2, atol=1e-4)
# Verify that everything is in one big cross-host graph and that the scheduling is reasonable
assert len(do.captured._jit_cache) == 1 and isinstance(do.captured._jit_cache[0].prg, RemoteGraph), repr(do.captured)
# At the time of writing this: 2050 graph breaks without multihost aware scheduling, 14 with it. I've set fail threshold to 28 to not fail on
# unrelated scheduling changes. Maybe 2x is a bit too pessimistic, but remote should perform just fine as long as this is not like a half hundred
# or more here.
self.assertLess(len(do.captured._jit_cache[0].prg.template), 28, "Very bad scheduling! Many unnecesary graph breaks!")
class TestParseHosts(unittest.TestCase):
def assert_seq(self, result:LazySeq, host:str):
self.assertIsInstance(result, LazySeq)
for i in [0, 1, 5, 10]: self.assertEqual(result[i], (host, i))
@given(st.sampled_from(["", "localhost", "192.168.1.1:8080", "host"]))
def test_single_host_no_count(self, host:str):
self.assert_seq(parse_hosts(host), host)
@given(host=st.sampled_from(["localhost", "host", "192.168.1.1:8080"]), count=st.integers(0, 10))
def test_single_host_with_count(self, host:str, count:int):
self.assertEqual(parse_hosts(f"{host}*{count}"), [(host, i) for i in range(count)])
def test_multiple_hosts_with_counts_simple(self):
self.assertEqual(parse_hosts("host1*2,host2*3"), [("host1", i) for i in range(2)] + [("host2", i) for i in range(3)])
@given(st.lists(st.tuples(st.text(alphabet=string.ascii_letters + string.digits + ".-:"), st.integers(1, 16)), min_size=1))
def test_multiple_hosts_with_counts_sampled(self, host_count_pairs):
hosts_str = ",".join(f"{host}*{count}" for host, count in host_count_pairs)
expected = [(host, i) for host, count in host_count_pairs for i in range(count)]
self.assertEqual(parse_hosts(hosts_str), expected)
@given(st.sampled_from(["host1*2,host2", "a*1,b", "x*3,y*2,z"]))
def test_mixed_hosts_fails(self, hosts):
with self.assertRaises(AssertionError): parse_hosts(hosts)
@given(st.sampled_from(["host*abc", "test*xyz", "a*1.5"]))
def test_invalid_count_fails(self, hosts):
with self.assertRaises(ValueError): parse_hosts(hosts)
@given(st.sampled_from(["host*2*3", "a*1*2*3", "test*x*y"]))
def test_multiple_asterisks_fails(self, hosts):
with self.assertRaises(ValueError): parse_hosts(hosts)
if __name__ == '__main__':
unittest.main()
+7 -7
View File
@@ -70,15 +70,15 @@ class TestTinygrad(unittest.TestCase):
out = out.log_softmax()
out = out.mul(m).add(m).sum()
out.backward()
xgrad,wgrad = x.grad, W.grad
xgrad, wgrad = x.grad.numpy(), W.grad.numpy()
out.backward()
xgrad2,wgrad2 = x.grad, W.grad
xgrad2, wgrad2 = x.grad.numpy(), W.grad.numpy()
out.backward() # no need to retain again since we will not re-run backward
xgrad3,wgrad3 = x.grad, W.grad
np.testing.assert_allclose(xgrad3.numpy(), xgrad.numpy() * 3., atol=1e-6)
np.testing.assert_allclose(wgrad3.numpy(), wgrad.numpy() * 3., atol=1e-6)
np.testing.assert_allclose(xgrad2.numpy(), xgrad.numpy() * 2., atol=1e-6)
np.testing.assert_allclose(wgrad2.numpy(), wgrad.numpy() * 2., atol=1e-6)
xgrad3, wgrad3 = x.grad.numpy(), W.grad.numpy()
np.testing.assert_allclose(xgrad3, xgrad * 3., atol=1e-6)
np.testing.assert_allclose(wgrad3, wgrad * 3., atol=1e-6)
np.testing.assert_allclose(xgrad2, xgrad * 2., atol=1e-6)
np.testing.assert_allclose(wgrad2, wgrad * 2., atol=1e-6)
def test_second_order_backward_pass(self):
def test_pytorch():
+31
View File
@@ -95,6 +95,37 @@ class TestTensorVariable(unittest.TestCase):
assert t.uop.base.buffer.size == 30
assert t.uop.shape == (3, vb)
def test_symbolic_chunk(self):
# chunk should work when split dimension is concrete, even if other dims are symbolic
vv = Variable("a", 1, 10).bind(4)
t = Tensor.ones(10, 8).contiguous()[:vv, :] # shape (vv, 8)
chunks = t.chunk(2, dim=-1) # split along concrete dim 8
assert len(chunks) == 2
assert chunks[0].shape[1] == 4
assert chunks[1].shape[1] == 4
# verify the values by shrinking to concrete shape first
np.testing.assert_equal(chunks[0].shrink(((0, 4), (0, 4))).numpy(), np.ones((4, 4)))
np.testing.assert_equal(chunks[1].shrink(((0, 4), (0, 4))).numpy(), np.ones((4, 4)))
def test_symbolic_split(self):
# split should work when split dimension is concrete, even if other dims are symbolic
vv = Variable("a", 1, 10).bind(3)
t = Tensor.arange(30).reshape(10, 3).contiguous()[:, :vv] # shape (10, vv)
splits = t.split(5, dim=0) # split along concrete dim 10
assert len(splits) == 2
assert splits[0].shape[0] == 5
assert splits[1].shape[0] == 5
# verify the values by shrinking to concrete shape first
np.testing.assert_equal(splits[0].shrink(((0, 5), (0, 3))).numpy(), np.arange(30).reshape(10, 3)[:5, :3])
np.testing.assert_equal(splits[1].shrink(((0, 5), (0, 3))).numpy(), np.arange(30).reshape(10, 3)[5:, :3])
def test_symbolic_chunk_error_on_symbolic_dim(self):
# chunk should fail when trying to split along a symbolic dimension
vv = Variable("a", 1, 10).bind(4)
t = Tensor.ones(10, 8).contiguous()[:vv, :] # shape (vv, 8)
with self.assertRaises(AssertionError):
t.chunk(2, dim=0) # can't split along symbolic dim
if __name__ == '__main__':
unittest.main()
+92 -4
View File
@@ -31,14 +31,16 @@ class TestTK(unittest.TestCase):
a_smem = ker.st((BLOCK_SIZE, BLOCK_SIZE), dtypes.bfloat16)
b_smem = ker.st((BLOCK_SIZE, BLOCK_SIZE), dtypes.bfloat16)
c_smem = ker.st((BLOCK_SIZE, BLOCK_SIZE), dtypes.float32)
a_reg = ker.rt((BLOCK_SIZE, BLOCK_SIZE), dtypes.bfloat16)
b_reg = ker.rt((BLOCK_SIZE, BLOCK_SIZE), dtypes.bfloat16, TileLayout.COL)
c_reg = ker.rt((BLOCK_SIZE, BLOCK_SIZE), dtypes.float32, TileLayout.COL)
c_reg_col = ker.rt((BLOCK_SIZE, BLOCK_SIZE), dtypes.float32, TileLayout.COL)
c_reg = ker.rt((BLOCK_SIZE, BLOCK_SIZE), dtypes.float32)
col, row = ker.blockIdx_x, ker.blockIdx_y
c_reg = warp.zero(c_reg)
c_reg_col = warp.zero(c_reg_col)
for tile in ker.range(N // BLOCK_SIZE):
a_smem = warp.load(a_smem, a, (), (0, 0, row, tile), axis=2)
b_smem = warp.load(b_smem, b, (), (0, 0, tile, col), axis=2)
@@ -46,8 +48,11 @@ class TestTK(unittest.TestCase):
a_reg = warp.load(a_reg, a_smem)
b_reg = warp.load(b_reg, b_smem)
c_reg = warp.mma_AB(c_reg, a_reg, b_reg)
c_reg = ker.endrange()
c_reg_col = warp.mma_AB(c_reg_col, a_reg, b_reg)
c_reg_col = ker.endrange()
c_smem = warp.store(c_smem, c_reg_col)
c_reg = warp.load(c_reg, c_smem)
c = warp.store(c, c_reg, (0, 0, row, col), (), axis=2)
@@ -152,6 +157,89 @@ class TestTK(unittest.TestCase):
np.testing.assert_allclose(b.numpy(), ref.numpy())
def test_load_store_local_hop(self):
N = 64
BLOCK_SIZE = 32
with Kernel("load_store_local_hop", (N // BLOCK_SIZE, N // BLOCK_SIZE, 1), WARP_THREADS) as ker:
warp = ker.warp
b = ker.gl((1, 1, N, N), dtypes.float32)
a = ker.gl((1, 1, N, N), dtypes.float32)
a_smem = ker.st((BLOCK_SIZE, BLOCK_SIZE), dtypes.float32)
b_smem = ker.st((BLOCK_SIZE, BLOCK_SIZE), dtypes.float32)
a_reg = ker.rt((BLOCK_SIZE, BLOCK_SIZE), dtypes.float32)
b_reg = ker.rt((BLOCK_SIZE, BLOCK_SIZE), dtypes.float32)
col, row = ker.blockIdx_x, ker.blockIdx_y
a_smem = warp.load(a_smem, a, (), (0, 0, row, col), axis=2)
a_reg = warp.load(a_reg, a_smem)
b_reg = warp.copy(b_reg, a_reg)
b_smem = warp.store(b_smem, b_reg)
b_reg = warp.load(b_reg, b_smem)
b = warp.store(b, b_reg, (0, 0, row, col), (), axis=2)
sink = ker.finish()
with Context(DEBUG=0):
a = Tensor.rand(1, 1, N, N, dtype="float32").contiguous()
b = Tensor.empty(1, 1, N, N, dtype="float32")
Tensor.realize(a, b)
ei = ExecItem(get_runner(Device.DEFAULT, sink), [t.uop.buffer for t in (b, a)])
for _ in range(5): ei.run(wait=True)
b = b.float()
ref = a.float()
np.testing.assert_allclose(b.numpy(), ref.numpy())
def test_load_store_multioutput(self):
N = 64
BLOCK_SIZE = 32
with Kernel("load_store_multioutput", (N // BLOCK_SIZE, N // BLOCK_SIZE, 1), WARP_THREADS) as ker:
warp = ker.warp
b = ker.gl((1, 1, N, N), dtypes.float32)
c = ker.gl((1, 1, N, N), dtypes.float32)
a = ker.gl((1, 1, N, N), dtypes.float32)
a_smem = ker.st((BLOCK_SIZE, BLOCK_SIZE), dtypes.float32)
b_smem = ker.st((BLOCK_SIZE, BLOCK_SIZE), dtypes.float32)
a_reg = ker.rt((BLOCK_SIZE, BLOCK_SIZE), dtypes.float32)
b_reg = ker.rt((BLOCK_SIZE, BLOCK_SIZE), dtypes.float32)
col, row = ker.blockIdx_x, ker.blockIdx_y
a_smem = warp.load(a_smem, a, (), (0, 0, row, col), axis=2)
a_reg = warp.load(a_reg, a_smem)
b_reg = warp.copy(b_reg, a_reg)
b_smem = warp.store(b_smem, b_reg)
b_reg = warp.load(b_reg, b_smem)
b = warp.store(b, b_reg, (0, 0, row, col), (), axis=2)
c = warp.store(c, b_reg, (0, 0, row, col), (), axis=2)
sink = ker.finish(2)
with Context(DEBUG=0):
a = Tensor.rand(1, 1, N, N, dtype="float32").contiguous()
b = Tensor.empty(1, 1, N, N, dtype="float32")
c = Tensor.empty(1, 1, N, N, dtype="float32")
Tensor.realize(a, b, c)
ei = ExecItem(get_runner(Device.DEFAULT, sink), [t.uop.buffer for t in (b, c, a)])
for _ in range(5): ei.run(wait=True)
b = b.float()
c = c.float()
ref = a.float()
np.testing.assert_allclose(b.numpy(), ref.numpy())
np.testing.assert_allclose(c.numpy(), ref.numpy())
@unittest.skip("TODO")
def test_load_store_group(self):
N = 256
+8 -2
View File
@@ -1,8 +1,14 @@
import unittest
from tinygrad import Tensor, dtypes, TinyJit, UOp
from tinygrad.apps.llm import apply_rope
from tinygrad.apps.llm import apply_rope as apply_rope_new, precompute_freqs_cis
#from tinygrad.engine.realize import run_schedule
def apply_rope(x:Tensor, start_pos:int):
B, H, T, Hd = x.shape
precompute_freqs_cis.cache_clear()
freqs_cis = precompute_freqs_cis(Hd, start_pos+T)[start_pos:start_pos+T]
return apply_rope_new(x, freqs_cis)
# TODO: test_scheduler, but just in uint
class TestAttention(unittest.TestCase):
def test_half_qkv_buffers(self):
@@ -39,7 +45,7 @@ class TestAttention(unittest.TestCase):
prune_size = len(rope_prune.captured.jit_cache)
self.assertGreater(noprune_size, prune_size)
self.assertGreaterEqual(noprune_size, 3)
self.assertGreaterEqual(noprune_size, 2)
self.assertEqual(prune_size, 1)
if __name__ == '__main__':
+1
View File
@@ -58,6 +58,7 @@ class TestGGUF(unittest.TestCase):
def test_dequantization_q4_0(self): self._test_dequantization(ggml.GGML_TYPE_Q4_0)
def test_dequantization_q4_1(self): self._test_dequantization(ggml.GGML_TYPE_Q4_1)
def test_dequantization_q8_0(self): self._test_dequantization(ggml.GGML_TYPE_Q8_0)
def test_dequantization_q4_k(self): self._test_dequantization(ggml.GGML_TYPE_Q4_K)
def test_dequantization_q6_k(self): self._test_dequantization(ggml.GGML_TYPE_Q6_K)
def test_dequantization_mxfp4(self):
MXFP4 = 39
+12
View File
@@ -110,6 +110,18 @@ class TestTensorGradient(unittest.TestCase):
with self.assertRaises(RuntimeError): x.sum().gradient(x)
with self.assertRaises(RuntimeError): x.float().sum().gradient(x)
def test_multiple_backward(self):
x = Tensor([3.], requires_grad=True)
(x*2)[0].backward()
np.testing.assert_allclose(x.grad.numpy(), [2.0])
old_grad = x.grad
(x*3)[0].backward()
np.testing.assert_allclose(x.grad.numpy(), [2.0+3.0])
self.assertIs(x.grad, old_grad)
(x*x)[0].backward()
np.testing.assert_allclose(x.grad.numpy(), [2.0+3.0+2*3.0])
self.assertIs(x.grad, old_grad)
class TestRealizeMeansRealize(unittest.TestCase):
def test_randn_realizes(self):
x = Tensor.randn(2, 3, 64, 64, requires_grad=True).realize()
+1
View File
@@ -10,6 +10,7 @@ class TestLLMServer(unittest.TestCase):
cls.mock_tok.role = Mock(return_value=[100, 101])
cls.mock_tok.encode = Mock(return_value=[200, 201, 202])
cls.mock_tok.decode = Mock(return_value="Hello")
cls.mock_tok.end_turn = Mock(return_value=[998])
cls.mock_model = Mock()
cls.mock_model.generate = Mock(side_effect=lambda ids, **kwargs: iter([300, 301, 999]))
+24 -1
View File
@@ -1,8 +1,31 @@
import unittest
from tinygrad import Tensor
from tinygrad import Tensor, Variable
from tinygrad.engine.schedule import schedule_cache
class TestScheduleCache(unittest.TestCase):
def test_bound_variable_reuses_cache(self):
schedule_cache.clear()
v = Variable('v', 1, 100)
x = Tensor.ones(10).contiguous().realize()
# first run with v=5
t1 = (x + Tensor(v.bind(5))).sum()
self.assertEqual(t1.item(), 60.0)
cache_size_after_first = len(schedule_cache)
# second run with v=10 should reuse cache
t2 = (x + Tensor(v.bind(10))).sum()
self.assertEqual(t2.item(), 110.0)
self.assertEqual(len(schedule_cache), cache_size_after_first)
def test_bound_variable_var_vals(self):
v = Variable('pos', 1, 100)
x = Tensor.ones(10).contiguous().realize()
t = x + Tensor(v.bind(42))
_, var_vals = t.schedule_with_vars()
self.assertEqual(var_vals, {'pos': 42})
def test_simple(self):
a = Tensor.ones(10).contiguous()
b = Tensor.ones(10).contiguous()
+122 -43
View File
@@ -1,15 +1,17 @@
from __future__ import annotations
import sys, argparse, typing, re, unicodedata, json, uuid, time
import sys, argparse, typing, re, unicodedata, json, uuid, time, functools
from tinygrad import Tensor, nn, UOp, TinyJit, getenv
from tinygrad.helpers import partition, TCPServerWithReuse, HTTPRequestHandler, tqdm, DEBUG
from tinygrad.helpers import partition, TCPServerWithReuse, HTTPRequestHandler, DEBUG, Timing, GlobalCounters, stderr_log, colored
class SimpleTokenizer:
def __init__(self, normal_tokens:dict[str, int], special_tokens:dict[str, int]):
def __init__(self, normal_tokens:dict[str, int], special_tokens:dict[str, int], preset:str="llama3"):
if preset not in ("llama3","llama-v3","llama-bpe","qwen2"): raise ValueError(f"Invalid tokenizer preset '{preset}'")
# https://github.com/openai/gpt-2/blob/9b63575ef42771a015060c964af2c3da4cf7c8ab/src/encoder.py#L9
bs = [*range(33, 127), *range(161, 173), *range(174, 256)] # bytes that map to themselves
self._byte_decoder = {chr(b): b for b in bs} | {chr(256+i): b for i,b in enumerate(b for b in range(256) if b not in bs)}
# https://github.com/ggml-org/llama.cpp/blob/94933c8c2eeaa9a7983e3f6c08af76bd86724094/src/llama-vocab.cpp#L286
# TODO: ucat_range is slow
def ucat_range(pre: str): return "".join(re.escape(chr(cp)) for cp in range(sys.maxunicode + 1) if unicodedata.category(chr(cp)).startswith(pre))
r_ws, r_p_N, r_p_L = r"\t\n\x0b\x0c\r\x85" + ucat_range("Z"), ucat_range("N"), ucat_range("L")
self._split_to_word = re.compile("(?i:'s|'t|'re|'ve|'m|'ll|'d)|" + \
@@ -19,14 +21,14 @@ class SimpleTokenizer:
self._normal_tokens = {bytes(self._byte_decoder[c] for c in tok): tid for tok, tid in normal_tokens.items()}
self._special_tokens = special_tokens
self._tok2bytes = {tid: tok for tok, tid in self._normal_tokens.items()} | {tid: tok.encode() for tok, tid in self._special_tokens.items()}
self.preset = preset
@staticmethod
def from_gguf_kv(kv:dict):
# https://github.com/ggml-org/llama.cpp/blob/94933c8c2eeaa9a7983e3f6c08af76bd86724094/src/llama-vocab.cpp#L1818-L1820
if kv["tokenizer.ggml.pre"] not in ("llama3","llama-v3","llama-bpe"): raise ValueError(f"Invalid tokenizer preset '{kv['tokenizer.ggml.pre']}'")
vocab: typing.Iterable[tuple[str, int]] = ((tok, idx) for idx, tok in enumerate(kv["tokenizer.ggml.tokens"]))
normal_tokens, special_tokens = partition(vocab, lambda e: kv["tokenizer.ggml.token_type"][e[1]] == 1)
return SimpleTokenizer(dict(normal_tokens), dict(special_tokens))
return SimpleTokenizer(dict(normal_tokens), dict(special_tokens), kv["tokenizer.ggml.pre"])
def _encode_word(self, word:bytes) -> list[int]:
if (early_token:=self._normal_tokens.get(word)) is not None: return [early_token]
@@ -48,38 +50,45 @@ class SimpleTokenizer:
pos = match.end(0)
return tokens + self._encode_sentence(text[pos:])
def decode(self, ids:list[int]) -> str: return b''.join(self._tok2bytes[tid] for tid in ids).decode()
def role(self, role:str): return self.encode("<|start_header_id|>" + role + "<|end_header_id|>\n\n")
def decode(self, ids:list[int]) -> str: return b''.join(self._tok2bytes[tid] for tid in ids).decode(errors='replace')
def role(self, role:str):
if self.preset == 'qwen2': return self.encode("<|im_start|>" + role + "\n")
return self.encode("<|start_header_id|>" + role + "<|end_header_id|>\n\n")
def end_turn(self, eos_id:int): return [eos_id] + self.encode("\n") if self.preset == 'qwen2' else [eos_id]
def apply_rope(x:Tensor, start_pos:int|UOp, base:float = 10000.0) -> Tensor:
B, H, T, Hd = x.shape
assert isinstance(Hd, int) and (Hd & 1) == 0, "RoPE requires an even head dimension"
half = Hd // 2
t_start_pos = start_pos if isinstance(start_pos, int) else Tensor(start_pos)
angles = (Tensor.arange(T, dtype="float32") + t_start_pos)[:, None] * (base ** (-(Tensor.arange(half, dtype="float32") / half)))[None, :]
# contiguous here allows RoPE to be pruned in the JIT
cos, sin = angles.cos().reshape(1, 1, T, half).cast(x.dtype).contiguous(), angles.sin().reshape(1, 1, T, half).cast(x.dtype).contiguous()
x_pairs = x.reshape(B, H, T, half, 2)
return Tensor.stack(x_pairs[..., 0] * cos - x_pairs[..., 1] * sin,
x_pairs[..., 0] * sin + x_pairs[..., 1] * cos, dim=-1).reshape(B, H, T, Hd)
@functools.cache
def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0) -> Tensor:
freqs = 1.0 / (theta ** (Tensor.arange(0, dim, 2)[:(dim // 2)] / dim))
freqs = Tensor.arange(end).unsqueeze(dim=1) * freqs.unsqueeze(dim=0)
return freqs.cos().cat(freqs.sin(), dim=-1).contiguous()
def apply_rope(x:Tensor, freqs_cis:Tensor) -> Tensor:
assert x.shape[-1] % 2 == 0
cos, sin = freqs_cis.reshape(1, 1, x.shape[2], -1).chunk(2, dim=-1)
x1, x2 = x.chunk(2, dim=-1)
return (x1 * cos - x2 * sin).cat(x2 * cos + x1 * sin, dim=-1)
class TransformerBlock:
def __init__(self, dim:int, hidden_dim:int, n_heads:int, n_kv_heads:int, norm_eps:float, max_context:int=0):
def __init__(self, dim:int, hidden_dim:int, n_heads:int, n_kv_heads:int, norm_eps:float, head_dim:int, rope_theta:float,
max_context:int=0, qk_norm:bool=False):
self.n_heads = n_heads
self.n_kv_heads = n_kv_heads
self.head_dim = dim // n_heads
self.head_dim = head_dim
self.max_context = max_context
self.rope_theta = rope_theta
# --- attention projections (all linear, bias-free) ------------------
kv_proj_out = self.head_dim * n_kv_heads # Llama-3 uses the same dim for K/V
self.attn_q = nn.Linear(dim, dim, bias=False)
q_proj_out = self.head_dim * n_heads
kv_proj_out = self.head_dim * n_kv_heads
self.attn_q = nn.Linear(dim, q_proj_out, bias=False)
self.attn_k = nn.Linear(dim, kv_proj_out, bias=False)
self.attn_v = nn.Linear(dim, kv_proj_out, bias=False)
self.attn_output = nn.Linear(dim, dim, bias=False)
self.attn_output = nn.Linear(q_proj_out, dim, bias=False)
# --- RMSNorms --------------------------------------------------------
self.attn_norm = nn.RMSNorm(dim, norm_eps)
self.ffn_norm = nn.RMSNorm(dim, norm_eps)
if qk_norm: self.attn_q_norm, self.attn_k_norm = nn.RMSNorm(self.head_dim, norm_eps), nn.RMSNorm(self.head_dim, norm_eps)
# --- feed-forward ----------------------------------------------------
self.ffn_gate = nn.Linear(dim, hidden_dim, bias=False)
@@ -95,8 +104,12 @@ class TransformerBlock:
k = k.reshape(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2) # (B,KvH,T,Hd)
v = v.reshape(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2) # (B,KvH,T,Hd)
q = apply_rope(q, start_pos)
k = apply_rope(k, start_pos)
if hasattr(self, 'attn_q_norm'): q, k = self.attn_q_norm(q), self.attn_k_norm(k)
# TODO: make UOp have SupportsIndex
freqs_cis = precompute_freqs_cis(self.head_dim, self.max_context, self.rope_theta)[start_pos:start_pos+T] # type: ignore
q = apply_rope(q, freqs_cis)
k = apply_rope(k, freqs_cis)
# TODO: remove these kv cache realizes
if not hasattr(self, "cache_kv"):
@@ -114,15 +127,18 @@ class TransformerBlock:
def _feed_forward(self, h: Tensor) -> Tensor:
h_norm = self.ffn_norm(h)
gated = self.ffn_gate(h_norm).silu() * self.ffn_up(h_norm)
# TODO: remove the need for this contiguous
gated = self.ffn_gate(h_norm).silu().contiguous() * self.ffn_up(h_norm)
return h + self.ffn_down(gated)
def __call__(self, x: Tensor, start_pos: int|UOp):
return self._feed_forward(self._attention(x, start_pos)).contiguous()
class Transformer:
def __init__(self, *, num_blocks, dim, hidden_dim, n_heads, n_kv_heads, norm_eps, vocab_size, max_context):
self.blk = [TransformerBlock(dim, hidden_dim, n_heads, n_kv_heads, norm_eps, max_context) for _ in range(num_blocks)]
def __init__(self, *, num_blocks, dim, hidden_dim, n_heads, n_kv_heads, norm_eps, vocab_size, head_dim:int, rope_theta:float,
max_context:int=0, qk_norm:bool=False):
self.blk = [TransformerBlock(dim, hidden_dim, n_heads, n_kv_heads, norm_eps, head_dim, rope_theta, max_context, qk_norm)
for _ in range(num_blocks)]
self.token_embd = nn.Embedding(vocab_size, dim)
self.output_norm = nn.RMSNorm(dim, norm_eps)
self.output = nn.Linear(dim, vocab_size, bias=False)
@@ -152,9 +168,18 @@ class Transformer:
arch = kv['general.architecture']
max_context = min(max_context, kv[f'{arch}.context_length']) if max_context is not None else kv[f'{arch}.context_length']
n_heads, n_kv_heads = kv[f'{arch}.attention.head_count'], kv[f'{arch}.attention.head_count_kv']
# permute Q/K weights from interleaved to half-split RoPE layout: [0,1,2,3,4,5...] -> [0,2,4,...,1,3,5,...]
if arch != 'qwen3':
for name in state_dict:
if 'attn_q.weight' in name: state_dict[name] = state_dict[name].rearrange("(n h two) d -> (n two h) d", n=n_heads, two=2)
if 'attn_k.weight' in name: state_dict[name] = state_dict[name].rearrange("(n h two) d -> (n two h) d", n=n_kv_heads, two=2)
model = Transformer(num_blocks=kv[f'{arch}.block_count'], dim=kv[f'{arch}.embedding_length'], hidden_dim=kv[f'{arch}.feed_forward_length'],
n_heads=kv[f'{arch}.attention.head_count'], n_kv_heads=kv[f'{arch}.attention.head_count_kv'],
norm_eps=kv[f'{arch}.attention.layer_norm_rms_epsilon'], vocab_size=len(kv['tokenizer.ggml.tokens']), max_context=max_context)
n_heads=n_heads, n_kv_heads=n_kv_heads, norm_eps=kv[f'{arch}.attention.layer_norm_rms_epsilon'],
vocab_size=len(kv['tokenizer.ggml.tokens']), head_dim=kv[f'{arch}.attention.key_length'],
rope_theta=kv[f'{arch}.rope.freq_base'], max_context=max_context, qk_norm='blk.0.attn_q_norm.weight' in state_dict)
nn.state.load_state_dict(model, state_dict, verbose=False, consume=True, realize=False) # NOTE: rope_freqs.weight (32,) is unused
# NOTE: without this contiguous, it unpacks the weights from the model every time. we shouldn't need this, but for now it's faster
for s in (params:=nn.state.get_parameters(model)): s.replace(s.contiguous())
@@ -175,36 +200,79 @@ class Transformer:
models = {
"llama3.2:1b": "https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q6_K.gguf",
"llama3.2:1b-q4": "https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q4_K_M.gguf",
"llama3.2:3b": "https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF/resolve/main/Llama-3.2-3B-Instruct-Q6_K.gguf",
"llama3.2:3b-f16": "https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF/resolve/main/Llama-3.2-3B-Instruct-f16.gguf",
"llama3.1:8b": "https://huggingface.co/bartowski/Meta-Llama-3.1-8B-Instruct-GGUF/resolve/main/Meta-Llama-3.1-8B-Instruct-Q8_0.gguf",
"qwen3:0.6b": "https://huggingface.co/Qwen/Qwen3-0.6B-GGUF/resolve/main/Qwen3-0.6B-Q8_0.gguf",
"qwen3:1.7b": "https://huggingface.co/unsloth/Qwen3-1.7B-GGUF/resolve/main/Qwen3-1.7B-Q4_K_M.gguf",
"qwen3:8b": "https://huggingface.co/Qwen/Qwen3-8B-GGUF/resolve/main/Qwen3-8B-Q4_K_M.gguf",
}
# *** simple OpenAI compatible server on 11434 to match ollama ***
# OPENAI_BASE_URL=http://localhost:11434/v1 OPENAI_API_KEY=ollama uvx --from gpt-command-line gpt
CHAT_HTML = b'''<!DOCTYPE html><html><head><title>tinygrad chat</title><style>
* { margin: 0 }
body { background: #212121; color: #e3e3e3; font-family: system-ui;
height: 100vh; display: flex; flex-direction: column }
#chat { flex: 1; overflow-y: auto; padding: 20px }
.msg { padding: 10px 16px; margin: 8px 0; white-space: pre-wrap; border-radius: 18px }
.user { background: #2f2f2f; margin-left: auto; width: fit-content; max-width: 70% }
#input { max-width: 768px; width: 100%; margin: 20px auto; padding: 14px 20px;
background: #2f2f2f; color: inherit; font: inherit;
border: none; outline: none; resize: none; border-radius: 24px; field-sizing: content }
</style></head><body><div id="chat"></div>
<textarea id="input" rows="1" placeholder="Ask anything"></textarea>
<script>
input.onkeydown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send() } }
const msgs = [];
async function send() {
if (!input.value.trim()) return;
msgs.push({role: 'user', content: input.value.trim()});
chat.innerHTML += '<div class="msg user">' + input.value.trim().replace(/</g, '&lt;') + '</div>';
input.value = '';
const d = document.createElement('div'); d.className = 'msg'; chat.appendChild(d);
const r = await fetch('/v1/chat/completions', {method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({model: 'llama', messages: msgs, stream: true})});
for (const rd = r.body.getReader(), dec = new TextDecoder();;) {
const {done, value} = await rd.read();
if (done) break;
for (const ln of dec.decode(value).split('\\n'))
if (ln.startsWith('data: ') && !ln.includes('[DONE]'))
try { d.textContent += JSON.parse(ln.slice(6)).choices[0]?.delta?.content || '' } catch {}
chat.scrollTop = chat.scrollHeight;
}
msgs.push({role: 'assistant', content: d.textContent});
}
</script></body></html>'''
class Handler(HTTPRequestHandler):
def log_request(self, code='-', size='-'): pass
def do_GET(self): self.send_data(CHAT_HTML, content_type="text/html")
def run_model(self, ids:list[int], model_name:str, include_usage=False):
stderr_log(f"{self.path} {colored('--', 'BLACK')} in:{len(ids):5d} {colored('--', 'BLACK')} ")
tmpl = {"id":f"chatcmpl-{uuid.uuid4().hex[:24]}", "object":"chat.completion.chunk", "created":int(time.time()), "model":model_name}
yield {"choices": [{"index":0, "delta":{"role":"assistant","content":""}, "finish_reason":None}], **tmpl}
out = []
for next_id in tqdm(model.generate(ids), disable=not DEBUG>=1):
out: list[int] = []
st = time.perf_counter()
for next_id in model.generate(ids):
if len(out) == 0: stderr_log(f"prefill:{len(ids)/((pt:=time.perf_counter())-st):4.0f} tok/s {colored('--', 'BLACK')} ")
if next_id == eos_id: break
out.append(next_id)
yield {"choices": [{"index":0, "delta":{"content":tok.decode([next_id])}, "finish_reason":None}], **tmpl}
yield {"choices": [{"index":0, "delta":{},"finish_reason":"stop"}], **tmpl}
if include_usage:
yield {"choices": [], "usage": {"prompt_tokens": len(ids), "completion_tokens": len(out), "total_tokens": len(ids) + len(out)}, **tmpl}
stderr_log(f"out:{len(out):5d} {colored('--', 'BLACK')} gen: {len(out)/(time.perf_counter()-pt):4.0f} tok/s\n")
def do_POST(self):
raw_body = self.rfile.read(int(self.headers.get("Content-Length", "0")))
body: dict[str, typing.Any] = json.loads(raw_body.decode("utf-8"))
if DEBUG >= 1:
print(self.path)
print(json.dumps(body, indent=2))
if DEBUG >= 1: print(json.dumps(body, indent=2))
if self.path == "/v1/chat/completions":
# extract tokens
ids = [bos_id]
ids: list[int] = [bos_id] if bos_id is not None else []
for msg in body["messages"]:
ids += tok.role(msg["role"])
# content can be a str or a list
@@ -215,7 +283,8 @@ class Handler(HTTPRequestHandler):
if c["type"] == "text": ids += tok.encode(c["text"])
else: raise RuntimeError(f"unhandled type: {c['type']}")
else: raise RuntimeError(f"unknown content type: {type(content)}")
ids += tok.role("assistant")
ids += tok.end_turn(eos_id)
ids += tok.role("assistant")
# reply
chunks = self.run_model(ids, body["model"], not body.get("stream") or body.get("stream_options",{}).get("include_usage", False))
@@ -232,26 +301,36 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--model", choices=list(models.keys()), default=list(models.keys())[0], help="Model choice")
parser.add_argument("--max_context", type=int, default=4096, help="Max Context Length")
parser.add_argument("--serve", action="store_true", help="Run OpenAI compatible API")
parser.add_argument("--serve", nargs='?', type=int, const=11434, metavar="PORT", help="Run OpenAI compatible API (optional port, default 11434)")
parser.add_argument("--benchmark", nargs='?', type=int, const=20, metavar="COUNT", help="Benchmark tok/s (optional count, default 20)")
args = parser.parse_args()
# load the model
model, kv = Transformer.from_gguf(Tensor.from_url(models[args.model]), args.max_context)
if DEBUG >= 1: print(f"using model {args.model}")
# do benchmark
if args.benchmark:
param_bytes = sum(x.nbytes() for x in nn.state.get_parameters(model))
gen = model.generate([0], 0)
for _ in range(args.benchmark):
GlobalCounters.reset()
with Timing(on_exit=lambda x: f", {1e9/x:6.2f} tok/s, {GlobalCounters.global_mem/x:7.2f} GB/s, param {param_bytes/x:7.2f} GB/s"): next(gen)
exit(0)
# extract some metadata
tok = SimpleTokenizer.from_gguf_kv(kv)
bos_id: int = kv['tokenizer.ggml.bos_token_id']
bos_id: int|None = kv.get('tokenizer.ggml.bos_token_id') if kv.get('tokenizer.ggml.add_bos_token', True) else None
eos_id: int = kv['tokenizer.ggml.eos_token_id']
# start server
if args.serve: TCPServerWithReuse(('', 11434), Handler).serve_forever()
if args.serve: TCPServerWithReuse(('', args.serve), Handler).serve_forever()
ids: list[int] = [bos_id]
ids: list[int] = [bos_id] if bos_id is not None else []
while 1:
start_pos = len(ids) - 1
start_pos = max(len(ids) - 1, 0)
try:
ids += tok.role("user") + tok.encode(input('>>> ')) + [eos_id] + tok.role("assistant")
ids += tok.role("user") + tok.encode(input('>>> ')) + tok.end_turn(eos_id) + tok.role("assistant")
except EOFError:
break
for next_id in model.generate(ids, start_pos):
+63 -48
View File
@@ -1,6 +1,6 @@
import time
from typing import cast
from dataclasses import dataclass, field, replace
from dataclasses import dataclass, field
from collections import deque
from tinygrad.uop.ops import UOp, Ops, buffers, UOpMetaClass, track_rewrites
from tinygrad.uop.ops import PatternMatcher, UPat, graph_rewrite, graph_rewrite_map
@@ -13,19 +13,17 @@ from tinygrad.helpers import Metadata, DEBUG, cpu_profile, TracingKey, SPEC, fla
@dataclass(frozen=True)
class ScheduleItem:
ast: UOp
bufs: tuple[Buffer, ...]
bufs: tuple[Buffer, ...] = ()
metadata: tuple[Metadata, ...] = ()
fixedvars: dict[str, int] = field(default_factory=dict)
bound_ranges: tuple[UOp, ...] = ()
# **** schedule linearizer
def create_schedule_with_vars(sched_sink:UOp) -> tuple[list[ScheduleItem], dict[str, int]]:
def create_schedule(sched_sink:UOp) -> tuple[list[ScheduleItem], UOp]:
with cpu_profile(TracingKey("toposort sched_sink")):
# construct the KERNEL children graph based on assigns
children: dict[UOp, list[UOp]] = {}
in_degree: dict[UOp, int] = {}
var_vals: dict[str, int] = {}
for u in sched_sink.toposort():
if u.op is Ops.RANGE:
in_degree.setdefault(u, 0)
@@ -44,44 +42,26 @@ def create_schedule_with_vars(sched_sink:UOp) -> tuple[list[ScheduleItem], dict[
assert ss.op is Ops.AFTER, f"ss.op is not AFTER, it's {ss.op}"
children.setdefault(ss.src[1], []).append(k)
in_degree[k] += 1
elif s.op is Ops.BUFFER:
pass # a BUFFER is already realized, nothing to do here
elif s.op is Ops.BIND:
# for RANGE this is in fixedvars
if s.src[1].op is not Ops.RANGE:
var, val = s.unbind()
assert var.expr not in var_vals or var_vals[var.expr] == val, f"bind mismatch on {var}, {var_vals[var.expr]} != {val}"
var_vals[var.expr] = val
elif s.op in {Ops.BUFFER, Ops.BIND}:
pass # a BUFFER is already realized, BINDs are handled in complete_create_schedule_with_vars
else:
raise RuntimeError(f"input to kernel must be AFTER or BUFFER, not {s.op}")
with cpu_profile(TracingKey("linearize to ScheduleItem")):
with cpu_profile(TracingKey("linearize schedule")):
queue: deque[UOp] = deque()
for k,v in in_degree.items():
if v == 0: queue.append(k)
schedule: list[ScheduleItem|UOp] = []
schedule: list[tuple|UOp] = []
while len(queue):
k = rk = queue.popleft()
if k.op is Ops.END: k = k.src[0]
if k.op is Ops.RANGE: schedule.append(k)
elif k.op is Ops.KERNEL:
ast = k.arg.ast
# create subbuffers if needed
if ast.op is Ops.BUFFER_VIEW:
base = k.src[1].buf_uop.buffer
assert isinstance(base, Buffer), "base can't be MultiBuffer"
buffers[k.src[0]] = base.view(k.size, ast.dtype, ast.arg[1]*base.dtype.itemsize)
ubufs = tuple(s.buf_uop.buffer for s in k.src if s.op is not Ops.BIND)
bound_ranges = tuple(s for s in k.src if s.op is Ops.BIND and s.src[1].op is Ops.RANGE)
if any(isinstance(x, MultiBuffer) for x in ubufs):
assert all(isinstance(x, MultiBuffer) for x in ubufs), "kernel must all be multibuffer"
dnums = [x for x in ast.variables() if x.arg[0] == '_device_num']
for i,bufs in enumerate(zip(*[x.bufs for x in cast(tuple[MultiBuffer, ...], ubufs)])):
schedule.append(ScheduleItem(ast, bufs, k.arg.metadata, {dnums[0].expr:i} if len(dnums) else {}, bound_ranges=bound_ranges))
else:
# ONE -> ONE
schedule.append(ScheduleItem(ast, cast(tuple[Buffer, ...], ubufs), k.arg.metadata, bound_ranges=bound_ranges))
buf_uops = tuple(s.buf_uop for s in k.src if s.op is not Ops.BIND)
bound_ranges = tuple(s for s in k.src if s.op is Ops.BIND and len(s.src) > 1 and s.src[1].op is Ops.RANGE)
schedule.append((ast, buf_uops, k.arg.metadata, {}, bound_ranges))
if rk.op is Ops.END: schedule.append(rk)
else:
raise RuntimeError(f"can't schedule {k.op}")
@@ -90,10 +70,11 @@ def create_schedule_with_vars(sched_sink:UOp) -> tuple[list[ScheduleItem], dict[
if in_degree[x] == 0: queue.append(x)
with cpu_profile(TracingKey("expand ranges")):
real_schedule: list[ScheduleItem] = []
pre_schedule: list[ScheduleItem] = []
buf_uops_list: list[UOp] = []
sched_ptr = 0
in_ranges = {}
range_ptrs = {}
in_ranges: dict[UOp, int] = {}
range_ptrs: dict[UOp, int] = {}
while sched_ptr < len(schedule):
si = schedule[sched_ptr]
if isinstance(si, UOp):
@@ -106,9 +87,12 @@ def create_schedule_with_vars(sched_sink:UOp) -> tuple[list[ScheduleItem], dict[
sched_ptr = range_ptrs[si.src[1]]
continue
else:
real_schedule.append(replace(si, fixedvars=si.fixedvars | {s.src[0].arg[0]:in_ranges[s.src[1]] for s in si.bound_ranges}, bound_ranges=()))
ast, buf_uops, metadata, fixedvars, bound_ranges = si
fixedvars = fixedvars | {s.src[0].arg[0]:in_ranges[s.src[1]] for s in bound_ranges}
pre_schedule.append(ScheduleItem(ast, (), metadata, fixedvars))
buf_uops_list.append(UOp.sink(*buf_uops))
sched_ptr += 1
return real_schedule, var_vals
return pre_schedule, UOp.sink(*buf_uops_list)
from tinygrad.engine.memory import memory_planner
from tinygrad.schedule.rangeify import get_rangeify_map
@@ -129,6 +113,8 @@ pm_pre_sched_cache = PatternMatcher([
(UPat(Ops.BUFFER, src=(UPat(Ops.UNIQUE), UPat(Ops.DEVICE)), name="b"), replace_input_buffer),
# remove unique consts
(UPat(Ops.CONST, src=(UPat(Ops.DEVICE), UPat(Ops.UNIQUE)), name="b"), replace_input_buffer),
# strip value from BIND for cache key normalization, so different values hit same cache
(UPat(Ops.BIND, src=(UPat(Ops.DEFINE_VAR), UPat(Ops.CONST)), name="b"), lambda ctx,b: ctx.setdefault(b, b.replace(src=(b.src[0],)))),
])
def replace_input_buffer_back(ctx:dict[UOp, UOp], b:UOp):
@@ -141,15 +127,17 @@ def replace_input_buffer_back(ctx:dict[UOp, UOp], b:UOp):
pm_post_sched_cache = PatternMatcher([
(UPat(Ops.BUFFER, src=(UPat(Ops.LUNIQUE), UPat(Ops.DEVICE)), name="b"), replace_input_buffer_back),
(UPat(Ops.CONST, src=(UPat(Ops.DEVICE), UPat(Ops.LUNIQUE)), name="b"), replace_input_buffer_back),
# restore BIND value stripped in pm_pre_sched_cache
(UPat(Ops.BIND, src=(UPat(Ops.DEFINE_VAR),), name="b"), lambda ctx,b: ctx.get(b)),
])
schedule_cache: dict[bytes, tuple[UOp, UOp]] = {}
schedule_cache: dict[bytes, tuple[list[ScheduleItem], UOp]] = {}
@track_rewrites(lambda _,ret: f"Schedule {pluralize('Kernel', len(ret[1]))}")
def complete_create_schedule_with_vars(big_sink:UOp) -> tuple[dict[UOp, UOp], list[ScheduleItem], dict[str, int]]:
# big_sink srcs are all the Tensors
st = time.perf_counter()
# replace all UNIQUE buffers with LUNIQUE
# replace all UNIQUE buffers with LUNIQUE, strip BIND values for cache key
input_buffers: dict[UOp, UOp] = {}
big_sink_cache = graph_rewrite(big_sink, pm_pre_sched_cache, ctx=input_buffers, name="rewrite for sched cache")
sched_cache_key = big_sink_cache.key
@@ -172,26 +160,53 @@ def complete_create_schedule_with_vars(big_sink:UOp) -> tuple[dict[UOp, UOp], li
tensor_map |= get_rangeify_map(big_sink_cache)
big_sink = big_sink_cache.substitute(tensor_map, name="Apply Kernelize Map")
# save in schedule cache
tensor_map_sink = UOp.sink(*flatten([(k,v) for k,v in tensor_map.items()]))
schedule_cache[sched_cache_key] = (big_sink, tensor_map_sink)
pre_schedule, buf_uops_sink = create_schedule(big_sink)
# save in schedule cache (include AFTERs in tensor_map so we don't need big_sink)
after_map = [(u, u.buf_uop) for u in big_sink.toposort() if u.op is Ops.AFTER]
tensor_map_sink = UOp.sink(*flatten([(k,v) for k,v in tensor_map.items()]), *flatten(after_map))
combined_sink = UOp.sink(tensor_map_sink, buf_uops_sink)
schedule_cache[sched_cache_key] = (pre_schedule, combined_sink)
else:
# schedule cache hit
del big_sink_cache
big_sink, tensor_map_sink = sc_ret
pre_schedule, combined_sink = sc_ret
# replace all the LUNIQUEs with UNIQUEs
# replace all the LUNIQUEs with UNIQUEs (single graph_rewrite for everything)
input_buffers_reverse = {v:k for k,v in input_buffers.items()}
big_sink = graph_rewrite(big_sink, pm_post_sched_cache, ctx=input_buffers_reverse, name="unrewrite for sched cache")
tm_src = graph_rewrite(tensor_map_sink, pm_post_sched_cache, ctx=input_buffers_reverse, name="unrewrite for tensor map").src
combined = graph_rewrite(combined_sink, pm_post_sched_cache, ctx=input_buffers_reverse, name="unrewrite combined")
tensor_map_sink, buf_uops_sink = combined.src
tm_src = tensor_map_sink.src
tensor_map = {tm_src[i]:tm_src[i+1] for i in range(0, len(tm_src), 2)}
# create the schedule
schedule, var_vals = create_schedule_with_vars(big_sink)
# add bufs to pre_schedule
schedule: list[ScheduleItem] = []
for i, si in enumerate(pre_schedule):
buf_uops = buf_uops_sink.src[i].src
# create subbuffers if needed
if si.ast.op is Ops.BUFFER_VIEW:
base = buf_uops[1].buffer
assert isinstance(base, Buffer), "base can't be MultiBuffer"
buffers[buf_uops[0]] = base.view(buf_uops[0].arg, si.ast.dtype, si.ast.arg[1]*base.dtype.itemsize)
ubufs = tuple(b.buffer for b in buf_uops)
if any(isinstance(x, MultiBuffer) for x in ubufs):
assert all(isinstance(x, MultiBuffer) for x in ubufs), "kernel must all be multibuffer"
dnums = [x for x in si.ast.variables() if x.arg[0] == '_device_num']
for j, bufs in enumerate(zip(*[x.bufs for x in cast(tuple[MultiBuffer, ...], ubufs)])):
schedule.append(ScheduleItem(si.ast, bufs, si.metadata, si.fixedvars | ({dnums[0].expr:j} if len(dnums) else {})))
else:
# ONE -> ONE
schedule.append(ScheduleItem(si.ast, cast(tuple[Buffer, ...], ubufs), si.metadata, si.fixedvars))
with cpu_profile(TracingKey("memory planner")): schedule = memory_planner(schedule)
# remove all AFTERs, after scheduling, the tensors are just buffers
tensor_map |= {u:u.buf_uop for u in big_sink.toposort() if u.op is Ops.AFTER}
# extract var_vals from BINDs that were stripped (only if there are kernels)
var_vals: dict[str, int] = {}
if schedule:
for u in input_buffers:
if u.op is Ops.BIND:
var, val = u.unbind()
assert var.expr not in var_vals or var_vals[var.expr] == val, f"bind mismatch on {var}, {var_vals[var.expr]} != {val}"
var_vals[var.expr] = val
if (DEBUG >= 1 and len(schedule) > 1) or DEBUG >= 3:
print(f"scheduled {len(schedule):4d} kernels in {(time.perf_counter()-st)*1000:8.2f} ms"+\
+1 -1
View File
@@ -42,7 +42,7 @@ pm_gradient = PatternMatcher([
(UPat(Ops.MULTI, name="ret"), lambda ctx, ret: ctx.shard(ret.device, ret.axis).src),
# NOTE: this is only correct when the KERNEL has a single output
(UPat(Ops.AFTER), lambda ctx: (ctx, ctx)),
(UPat(Ops.KERNEL, name="k"), lambda ctx, k: k.arg.grad_fxn(ctx, k)),
(UPat(Ops.CUSTOM_KERNEL, name="k"), lambda ctx, k: k.arg.grad_fxn(ctx, k)),
# there's no gradient for bitcast
(UPat(Ops.BITCAST), lambda: (None,)),
])
+4
View File
@@ -149,6 +149,10 @@ def getenv(key:str, default:Any=0): return type(default)(os.getenv(key, default)
def temp(x:str, append_user:bool=False) -> str:
return (pathlib.Path(tempfile.gettempdir()) / (f"{x}.{getpass.getuser()}" if append_user else x)).as_posix()
def stderr_log(msg):
sys.stderr.write(msg)
sys.stderr.flush()
class Context(contextlib.ContextDecorator):
def __init__(self, **kwargs): self.kwargs = kwargs
def __enter__(self):
+16 -1
View File
@@ -1,6 +1,6 @@
# mixins add syntactic sugar to Tensor and UOp
import functools
from typing import TypeAlias, TYPE_CHECKING, Self
from typing import TypeAlias, TYPE_CHECKING, Self, Sequence
from tinygrad.uop import Ops
from tinygrad.helpers import prod, argfix, flatten, dedup, make_tuple, ceildiv
from tinygrad.uop.ops import resolve, smax
@@ -16,6 +16,10 @@ def _align_left(*shapes: tuple[sint, ...]) -> tuple[tuple[sint, ...], ...]:
return tuple((1,) * (max_dim - len(shape)) + shape for shape in shapes)
# `(padding_left, padding_right, padding_top, padding_bottom, ...)` -> `(..., (padding_top, padding_bottom), (padding_left, padding_right))`
def _flat_to_grouped(padding:Sequence[sint]) -> tuple[tuple[sint, sint], ...]: return tuple(zip(padding[-2::-2], padding[::-2]))
class MovementMixin:
# required to implement
def _mop(self, op: Ops, arg) -> Self:
@@ -374,3 +378,14 @@ class MovementMixin:
x = x.shrink_to(noop + flatten((k, o, 1) for k, o in zip(k_, o_))).reshape(noop + flatten((k, o) for k, o in zip(k_, o_)))
# permute to move reduce to the end
return x.permute(*range(len(noop)), *[len(noop) + i * 2 + 1 for i in range(len(i_))], *[len(noop) + i * 2 for i in range(len(i_))])
# **** pad ****
def pad(self, padding:Sequence[tuple[sint, sint]|None]) -> Self:
"""
Returns a tensor with constant zero padding applied based on the input `padding`.
`padding` must have the same length as `self.ndim`. For each axis, padding can be `None` (no padding) or a tuple `(before, after)`.
"""
pX = tuple((0,0) if p is None else p for p in padding)
if len(pX) != self.ndim: raise ValueError(f"padding length is improper, {padding=} {self.ndim=}")
return self._mop(Ops.PAD, pX)
+13 -15
View File
@@ -498,13 +498,15 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
def _axes(axes, noop_with_empty_axes): return axes or ([] if noop_with_empty_axes else None)
# (padding_top, padding_left, ..., padding_bottom, padding_right, ...) -> (padding_left, padding_right, padding_top, padding_bottom, ...)
def _onnx_pads_to_tiny_pads(pads): return tuple(flatten(reversed(list(zip(pads, pads[len(pads)//2:])))))
def _onnx_pads_to_tiny_pads(pads):
n = len(pads) // 2
return tuple(x for i in range(n-1, -1, -1) for x in (pads[i], pads[i+n]))
AUTO_PAD_OPTIONS = Literal["NOTSET", "SAME_UPPER", "SAME_LOWER", "VALID"]
# (padding_height, padding_width) -> (padding_top, padding_left, padding_bottom, padding_right)
def _auto_pad(pads, auto_pad: AUTO_PAD_OPTIONS):
if auto_pad == "SAME_UPPER": return [pads[i]//2 for i in range(len(pads))] + [pads[i]-pads[i]//2 for i in range(len(pads))]
return [pads[i]-pads[i]//2 for i in range(len(pads))] + [pads[i]//2 for i in range(len(pads))]
first = [p//2 for p in pads] if auto_pad == "SAME_UPPER" else [p - p//2 for p in pads]
return first + [p - f for p, f in zip(pads, first)]
def _resolve_pool_pads(x:Tensor, p_, k_, d_, s_, auto_pad:AUTO_PAD_OPTIONS):
if auto_pad == "VALID": return [0]*(len(k_)*2)
@@ -647,7 +649,7 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
def Mod(x:Tensor,y:Tensor,fmod=0): return x - x.div(y, rounding_mode="trunc") * y if fmod else x % y
# ***** Casting Ops *****
# TODO: saturate
# TODO: saturate parameter is ignored in Cast and CastLike
def Cast(x:Tensor, to:int, saturate:int=1): return x.cast(dtype_fallback(OnnxDataType(to).to_dtype(), "Cast op"))
def CastLike(x:Tensor, target_type:Tensor, saturate:int=1): return x.cast(target_type.dtype)
@@ -699,8 +701,8 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
def Concat(*xs:Tensor, axis:int): return Tensor.cat(*xs, dim=axis)
def Slice(data:Tensor, starts:list[int], ends:list[int], axes:list[int]|None=None, steps:list[int]|None=None):
axes = axes or list(range(data.ndim))
steps = steps or [1]*data.ndim
slices = [slice(0,x,1) for x in data.shape]
steps = steps or [1] * data.ndim
slices = [slice(None)] * data.ndim
for i, axis in enumerate(axes): slices[axis] = slice(starts[i], ends[i], steps[i])
return data[tuple(slices)]
@@ -810,7 +812,7 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
input_shape = cast(tuple[int, ...], X.shape[2:])
if scales is not None: assert all(sc==1 for sc in scales[:-len(input_shape)]), "resizing batch_size dim or channel dim not supported"
if sizes is not None: assert tuple(sizes[:-2]) == tuple(X.shape[X.ndim-len(sizes):-2]), "resizing batch_size dim or channel dim not supported"
if sizes is not None: assert tuple(sizes[:-2]) == tuple(X.shape[X.ndim-len(sizes):-2]), "resizing batch_size dim or channel dim not supported"
scales, sizes = (None if scales is None else scales[-len(input_shape):]), (None if sizes is None else sizes[-len(input_shape):])
if sizes is not None:
@@ -934,11 +936,8 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
# https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.EmbedLayerNormalization
assert (segment_ids is None) is (segment_embedding is None)
assert mask is None and not mask_index_type, "functionality not supported yet" # TODO
input_shape = input_ids.shape
seq_length = input_shape[1]
compute_seg_emb = (segment_embedding is not None and segment_ids is not None)
input_shape, seq_length = input_ids.shape, input_ids.shape[1]
vocab_size, max_position_embeddings = word_embedding.shape[0], position_embedding.shape[0]
type_vocab_size = (segment_embedding.shape[0] if compute_seg_emb else None)
def embedding(x:Tensor, vocab_size, weight:Tensor) -> Tensor:
return x.unsqueeze(-1).expand(*x.shape, vocab_size)._one_hot_along_dim(vocab_size) @ weight
@@ -947,10 +946,9 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
if position_ids is None: position_ids = Tensor.arange(seq_length, requires_grad=False).unsqueeze(0).expand(*input_shape)
wrd_embedding_res = embedding(input_ids, vocab_size, word_embedding)
pos_embedding_res = embedding(position_ids, max_position_embeddings, position_embedding)
seg_embedding_res = embedding(segment_ids, type_vocab_size, segment_embedding) if compute_seg_emb else None
embedding_sum = wrd_embedding_res + pos_embedding_res
if seg_embedding_res is not None: embedding_sum = embedding_sum + seg_embedding_res
if segment_embedding is not None: embedding_sum = embedding_sum + embedding(segment_ids, segment_embedding.shape[0], segment_embedding)
out = embedding_sum.layernorm(eps=epsilon) * gamma + beta
return out, None, embedding_sum
def MeanVarianceNormalization(x:Tensor, axis:list[int]|None=None):
@@ -1004,7 +1002,7 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
return (base_grid @ theta.transpose(1, 2)).reshape(N, *spatial_dims, -1)
def attention_contrib(x:Tensor, weights:Tensor, bias:Tensor|None=None, mask_index:Tensor|None=None, past:Tensor|None=None,
attention_bias:Tensor|None=None, past_sequence_length:Tensor|None=None, do_rotary:int=0, mask_filter_value:float=-10000.0,
attention_bias:Tensor|None=None, past_sequence_length:Tensor|None=None, do_rotary:int=0, mask_filter_value:float=-10000.0,
num_heads:int|None=None, past_present_share_buffer:int|None=None, qkv_hidden_sizes:list[int]|None=None,
rotary_embedding_dim:int|None=None, scale:float|None=None, unidirectional:int=0):
assert not do_rotary and not attention_bias, "TODO"
@@ -1288,7 +1286,7 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
# Tensor ops
**{op: getattr(Tensor, op.lower()) for op in ("Neg", "Reciprocal", "Pow", "Sqrt", "Sign", "Abs", "Exp", "Log", "Mish", "Sin", "Cos", "Tan",
"Asin", "Acos", "Atan", "Relu", "Sigmoid", "MatMul", "Floor", "Ceil", "IsNaN", "Softplus", "HardSwish", "Where", "Mul", "Sinh", "Cosh",
"Tanh", "Softsign", "Asinh", "Acosh", "Atanh", "Elu", "Celu", "Selu", "Round", "Erf")},
"Tanh", "Softsign", "Asinh", "Acosh", "Atanh", "Elu", "Celu", "Selu", "Round", "Erf")},
# Implemented ops
**{name:obj for name,obj in locals().items() if isinstance(obj, types.FunctionType) and not name.startswith("_") and name[0].isupper()},
# Version ops
+9 -2
View File
@@ -308,7 +308,7 @@ def ggml_data_to_tensor(t: Tensor, n: int, ggml_type: int) -> Tensor:
Converts ggml tensor data to a tinygrad tensor.
Supported native types: float32 (id: 0), float16 (id: 1), int8 (id: 16), int16 (id: 17), int32 (id: 18)
Supported quantized types: Q4_0 (id: 2), Q4_1 (id: 3), Q8_0 (id: 8), Q6_K (id: 14), MXFP4 (id: 39)
Supported quantized types: Q4_0 (id: 2), Q4_1 (id: 3), Q8_0 (id: 8), Q4_K (id: 12), Q6_K (id: 14), MXFP4 (id: 39)
"""
# https://github.com/ggerganov/ggml/blob/323951f1bdcdfbd5b5ff3a9a7c3770e63b1a560e/include/ggml.h#L356
@@ -322,13 +322,20 @@ def ggml_data_to_tensor(t: Tensor, n: int, ggml_type: int) -> Tensor:
return t.unsqueeze(-1).expand((*t.shape,8//b)).idiv(shift_tensor).bitwise_and(bitmask).transpose(-1, -2).flatten(-2)
# map to (number of elements, number of bytes)
if (nelements_nbytes := { 2: (32, 18), 3: (32, 20), 14: (256, 210), 8: (32, 34), 39: (32, 17) }.get(ggml_type)) is not None:
if (nelements_nbytes := { 2: (32, 18), 3: (32, 20), 8: (32, 34), 12: (256, 144), 14: (256, 210), 39: (32, 17) }.get(ggml_type)) is not None:
blocks = t[:(n//nelements_nbytes[0])*nelements_nbytes[1]].reshape((-1, nelements_nbytes[1]))
if ggml_type == 2: return (q_to_uint8(blocks[:,2:], 4).bitcast(dtypes.int8) - 8) * blocks[:,:2].bitcast(dtypes.float16).cast(dtypes.float32)
if ggml_type == 3:
d, m = (blocks[:,s:s+2].bitcast(dtypes.float16).cast(dtypes.float32) for s in [ 0, 2 ])
return q_to_uint8(blocks[:,4:], 4).bitcast(dtypes.int8) * d + m
if ggml_type == 8: return blocks[:,:2].bitcast(dtypes.float16).cast(dtypes.float32) * blocks[:,2:].bitcast(dtypes.int8)
if ggml_type == 12: # Q4_K: 256 elements per 144-byte block (d:2, dmin:2, scales:12, qs:128)
d, dmin = (blocks[:,i:i+2].bitcast(dtypes.float16).cast(dtypes.float32).unsqueeze(-1) for i in [0, 2])
s = blocks[:,4:16] # 12 bytes: 6-bit scales[0-3], 6-bit mins[0-3], high bits[4-7]
sc = s[:,0:4].bitwise_and(63).cat(s[:,8:12].bitwise_and(0xF).bitwise_or(s[:,0:4].rshift(6).lshift(4)), dim=-1)
mn = s[:,4:8].bitwise_and(63).cat(s[:,8:12].rshift(4).bitwise_or(s[:,4:8].rshift(6).lshift(4)), dim=-1)
q = Tensor.stack((qs:=blocks[:,16:144].reshape(-1,4,32)).bitwise_and(0xF), qs.rshift(4), dim=2).reshape(-1,8,32).cast(dtypes.float32)
return (d * sc.unsqueeze(-1) * q - dmin * mn.unsqueeze(-1)).flatten(-2)
if ggml_type == 14:
xl, xh = q_to_uint8(blocks[:,:128].reshape((-1, 2, 64)), 4), q_to_uint8(blocks[:,128:192].reshape((-1, 2, 32)), 2).lshift(4)
scales = blocks[:,192:208].bitcast(dtypes.int8).unsqueeze(-1).expand((-1, 16, 16)).reshape((-1, 256))
+44 -41
View File
@@ -8,6 +8,12 @@ ffmpeg_src = "https://ffmpeg.org/releases/ffmpeg-8.0.1.tar.gz"
rocr_src = "https://github.com/ROCm/rocm-systems/archive/refs/tags/rocm-7.1.1.tar.gz"
macossdk = "/var/db/xcode_select_link/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk"
llvm_lib = (r"'C:\\Program Files\\LLVM\\bin\\LLVM-C.dll' if WIN else '/opt/homebrew/opt/llvm@20/lib/libLLVM.dylib' if OSX else " +
repr(['LLVM'] + [f'LLVM-{i}' for i in reversed(range(14, 21+1))]))
webgpu_lib = "os.path.join(sysconfig.get_paths()['purelib'], 'pydawn', 'lib', 'libwebgpu_dawn.dll') if WIN else 'webgpu_dawn'"
nv_lib_path = "f'/usr/local/cuda/targets/{sysconfig.get_config_var(\"MULTIARCH\").rsplit(\"-\", 1)[0]}/lib'"
def load(name, dll, files, **kwargs):
if not (f:=(root/(path:=kwargs.pop("path", __name__)).replace('.','/')/f"{name}.py")).exists() or getenv('REGEN'):
files, kwargs['args'] = files() if callable(files) else files, args() if callable(args:=kwargs.get('args', [])) else args
@@ -21,22 +27,22 @@ def load(name, dll, files, **kwargs):
if (preprocess:=kwargs.pop('preprocess', None)): preprocess(base)
files = flatten(sorted(glob.glob(p, recursive=True)) if isinstance(p, str) and '*' in p else [p] for p in files)
kwargs['epilog'] = (epi(base) if tarball else epi()) if callable(epi:=kwargs.get('epilog', [])) else epi
f.write_text(importlib.import_module("tinygrad.runtime.support.autogen").gen(dll, files, **kwargs))
f.write_text(importlib.import_module("tinygrad.runtime.support.autogen").gen(name, dll, files, **kwargs))
return importlib.import_module(f"{path}.{name.replace('/', '.')}")
def __getattr__(nm):
match nm:
case "libc": return load("libc", ["find_library('c')"], lambda: (
case "libc": return load("libc", "'c'", lambda: (
[i for i in system("dpkg -L libc6-dev").split() if 'sys/mman.h' in i or 'sys/syscall.h' in i] +
["/usr/include/string.h", "/usr/include/elf.h", "/usr/include/unistd.h", "/usr/include/asm-generic/mman-common.h"]), use_errno=True)
case "avcodec": return load("avcodec", [], ["{}/libavcodec/hevc/hevc.h", "{}/libavcodec/cbs_h265.h"], tarball=ffmpeg_src)
case "opencl": return load("opencl", ["find_library('OpenCL')"], ["/usr/include/CL/cl.h"])
case "cuda": return load("cuda", ["find_library('cuda')"], ["/usr/include/cuda.h"], args=["-D__CUDA_API_VERSION_INTERNAL"], parse_macros=False)
case "nvrtc": return load("nvrtc", ["find_library('nvrtc')"], ["/usr/include/nvrtc.h"])
case "nvjitlink": load("nvjitlink", ["find_library('nvJitLink')"], [root/"extra/nvJitLink.h"])
case "kfd": return load("kfd", [], ["/usr/include/linux/kfd_ioctl.h"])
["/usr/include/string.h", "/usr/include/elf.h", "/usr/include/unistd.h", "/usr/include/asm-generic/mman-common.h"]), errno=True)
case "avcodec": return load("avcodec", None, ["{}/libavcodec/hevc/hevc.h", "{}/libavcodec/cbs_h265.h"], tarball=ffmpeg_src)
case "opencl": return load("opencl", "'OpenCL'", ["/usr/include/CL/cl.h"])
case "cuda": return load("cuda", "'cuda'", ["/usr/include/cuda.h"], args=["-D__CUDA_API_VERSION_INTERNAL"], parse_macros=False)
case "nvrtc": return load("nvrtc", "'nvrtc'", ["/usr/include/nvrtc.h"], paths=nv_lib_path, prolog=["import sysconfig"])
case "nvjitlink": load("nvjitlink", "'nvJitLink'", [root/"extra/nvJitLink.h"], paths=nv_lib_path, prolog=["import sysconfig"])
case "kfd": return load("kfd", None, ["/usr/include/linux/kfd_ioctl.h"])
case "nv_570" | "nv_580":
return load(nm, [], [
return load(nm, None, [
*[root/"extra/nv_gpu_driver"/s for s in ["clc9b0.h", "clc6c0qmd.h","clcec0qmd.h", "nvdec_drv.h"]], "{}/kernel-open/common/inc/nvmisc.h",
*[f"{{}}/src/common/sdk/nvidia/inc/class/cl{s}.h" for s in ["0000", "0070", "0080", "2080", "2080_notification", "c56f", "c86f", "c96f", "c761",
"83de", "c6c0", "cdc0"]],
@@ -51,7 +57,7 @@ def __getattr__(nm):
"-include", "{}/src/common/sdk/nvidia/inc/nvtypes.h", "-I{}/src/common/inc", "-I{}/kernel-open/nvidia-uvm", "-I{}/kernel-open/common/inc",
"-I{}/src/common/sdk/nvidia/inc", "-I{}/src/nvidia/arch/nvalloc/unix/include", "-I{}/src/common/sdk/nvidia/inc/ctrl"
], rules=[(r'MW\(([^:]+):(.+)\)',r'(\1, \2)')], tarball=nv_src[nm], anon_names={"{}/kernel-open/common/inc/nvstatus.h:37":"nv_status_codes"})
case "nv": return load("nv", [], [
case "nv": return load("nv", None, [
*[f"{{}}/src/nvidia/inc/kernel/gpu/{s}.h" for s in ["fsp/kern_fsp_cot_payload", "gsp/gsp_init_args"]],
*[f"{{}}/src/nvidia/arch/nvalloc/common/inc/{s}.h" for s in ["gsp/gspifpub", "gsp/gsp_fw_wpr_meta", "gsp/gsp_fw_sr_meta", "rmRiscvUcode",
"fsp/fsp_nvdm_format"]],
@@ -69,46 +75,43 @@ def __getattr__(nm):
"{}/src/nvidia/inc/kernel/vgpu/rpc_global_enums.h:244": "rpc_events"
})
# this defines all syscall numbers. should probably unify linux autogen?
case "io_uring": return load("io_uring", [], ["/usr/include/liburing.h", "/usr/include/linux/io_uring.h", "/usr/include/asm-generic/unistd.h"],
case "io_uring": return load("io_uring", None, ["/usr/include/liburing.h", "/usr/include/linux/io_uring.h", "/usr/include/asm-generic/unistd.h"],
rules=[('__NR', 'NR')])
case "ib": return load("ib", ["ibverbs"], ["/usr/include/infiniband/verbs.h", "/usr/include/infiniband/verbs_api.h",
"/usr/include/infiniband/ib_user_ioctl_verbs.h","/usr/include/rdma/ib_user_verbs.h"], use_errno=True)
case "llvm": return load("llvm", ["LLVM_PATH"], lambda: [system("llvm-config-20 --includedir")+"/llvm-c/**/*.h"],
args=lambda: system("llvm-config-20 --cflags").split(), recsym=True,
prolog=["from tinygrad.runtime.support.llvm import LLVM_PATH"])
case "pci": return load("pci", [], ["/usr/include/linux/pci_regs.h"])
case "vfio": return load("vfio", [], ["/usr/include/linux/vfio.h"])
case "ib": return load("ib", "'ibverbs'", ["/usr/include/infiniband/verbs.h", "/usr/include/infiniband/verbs_api.h",
"/usr/include/infiniband/ib_user_ioctl_verbs.h","/usr/include/rdma/ib_user_verbs.h"], errno=True)
case "llvm": return load("llvm", llvm_lib, lambda: [system("llvm-config-20 --includedir")+"/llvm-c/**/*.h"],
args=lambda: system("llvm-config-20 --cflags").split(), recsym=True, prolog=["from tinygrad.helpers import WIN, OSX"])
case "pci": return load("pci", None, ["/usr/include/linux/pci_regs.h"])
case "vfio": return load("vfio", None, ["/usr/include/linux/vfio.h"])
# could add rule: WGPU_COMMA -> ','
case "webgpu":
return load("webgpu", ["WEBGPU_PATH"], [root/"extra/webgpu/webgpu.h"], prolog=["from tinygrad.runtime.support.webgpu import WEBGPU_PATH"])
case "libusb": return load("libusb", ["os.getenv('LIBUSB_PATH', find_library('usb-1.0'))"], ["/usr/include/libusb-1.0/libusb.h"])
case "hip": return load("hip", ["os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libamdhip64.so'"], ["/opt/rocm/include/hip/hip_ext.h",
case "webgpu": return load("webgpu", webgpu_lib, [root/"extra/webgpu/webgpu.h"],
prolog=["from tinygrad.helpers import WIN, OSX", "import sysconfig, os"])
case "libusb": return load("libusb", "'usb-1.0'", ["/usr/include/libusb-1.0/libusb.h"])
case "hip": return load("hip", "os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libamdhip64.so'", ["/opt/rocm/include/hip/hip_ext.h",
"/opt/rocm/include/hip/hiprtc.h", "/opt/rocm/include/hip/hip_runtime_api.h", "/opt/rocm/include/hip/driver_types.h"],
args=["-D__HIP_PLATFORM_AMD__", "-I/opt/rocm/include", "-x", "c++"])
args=["-D__HIP_PLATFORM_AMD__", "-I/opt/rocm/include", "-x", "c++"], prolog=["import os"])
case "comgr" | "comgr_3":
return load("comgr_3" if nm == "comgr_3" else "comgr", [
"os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libamd_comgr.so'", "'/usr/local/lib/libamd_comgr.dylib'", "'/opt/homebrew/lib/libamd_comgr.dylib'"
], ["/opt/rocm/include/amd_comgr/amd_comgr.h"], args=["-D__HIP_PLATFORM_AMD__", "-I/opt/rocm/include", "-x", "c++"])
case "hsa": return load("hsa", ["os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libhsa-runtime64.so'", "find_library('hsa-runtime64')"], [
return load("comgr_3" if nm == "comgr_3" else "comgr", "[os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libamd_comgr.so', 'amd_comgr']",
["/opt/rocm/include/amd_comgr/amd_comgr.h"], args=["-D__HIP_PLATFORM_AMD__", "-I/opt/rocm/include", "-x", "c++"],
prolog=["import os"])
case "hsa": return load("hsa", "[os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libhsa-runtime64.so', 'hsa-runtime64']", [
*[f"{{}}/projects/rocr-runtime/runtime/hsa-runtime/core/inc/{s}.h" for s in ["registers"]],
*[f"{{}}/projects/rocr-runtime/runtime/hsa-runtime/inc/{s}.h" for s in ["hsa", "hsa_ext_amd", "amd_hsa_signal", "amd_hsa_queue",
"amd_hsa_kernel_code", "hsa_ext_finalize",
"hsa_ext_image", "hsa_ven_amd_aqlprofile"]]],
tarball=rocr_src, args=["-DLITTLEENDIAN_CPU"])
case "amd_gpu": return load("amd_gpu", [], [root/f"extra/hip_gpu_driver/{s}.h" for s in ["sdma_registers", "nvd", "gc_11_0_0_offset",
"sienna_cichlid_ip_offset"]],
tarball=rocr_src, args=["-DLITTLEENDIAN_CPU"], prolog=["import os"])
case "amd_gpu": return load("amd_gpu", None, [root/f"extra/hip_gpu_driver/{s}.h" for s in ["sdma_registers", "nvd", "gc_11_0_0_offset",
"sienna_cichlid_ip_offset"]],
args=["-I/opt/rocm/include", "-x", "c++"])
case "kgsl": return load("kgsl", [], [root/"extra/qcom_gpu_driver/msm_kgsl.h"], args=["-D__user="])
case "kgsl": return load("kgsl", None, [root/"extra/qcom_gpu_driver/msm_kgsl.h"], args=["-D__user="])
case "qcom_dsp":
return load("qcom_dsp", [], [root/f"extra/dsp/include/{s}.h" for s in ["ion", "msm_ion", "adsprpc_shared", "remote_default", "apps_std"]])
case "sqtt": return load("sqtt", [], [root/"extra/sqtt/sqtt.h"])
return load("qcom_dsp", None, [root/f"extra/dsp/include/{s}.h" for s in ["ion", "msm_ion", "adsprpc_shared", "remote_default", "apps_std"]])
case "sqtt": return load("sqtt", None, [root/"extra/sqtt/sqtt.h"])
case "rocprof":
return load("rocprof", ["find_library('rocprof-trace-decoder')", p:="'/usr/local/lib/rocprof-trace-decoder.so'", p.replace('so','dylib')],
return load("rocprof", "['rocprof-trace-decoder', p:='/usr/local/lib/rocprof-trace-decoder.so', p.replace('so','dylib')]",
[f"{{}}/include/{s}.h" for s in ["rocprof_trace_decoder", "trace_decoder_instrument", "trace_decoder_types"]],
tarball="https://github.com/ROCm/rocprof-trace-decoder/archive/dd0485100971522cc4cd8ae136bdda431061a04d.tar.gz")
case "mesa": return load("mesa", ["find_library('tinymesa_cpu')",
"(BASE:=os.getenv('MESA_PATH', f\"/usr{'/local/' if OSX else '/'}lib\"))+'/libtinymesa_cpu'+(EXT:='.dylib' if OSX else '.so')",
"f'{BASE}/libtinymesa{EXT}'", "'/opt/homebrew/lib/libtinymesa_cpu.dylib'", "'/opt/homebrew/lib/libtinymesa.dylib'"], [
case "mesa": return load("mesa", "['tinymesa_cpu', 'tinymesa']", [
*[f"{{}}/src/compiler/nir/{s}.h" for s in ["nir", "nir_builder", "nir_shader_compiler_options", "nir_serialize"]], "{}/gen/nir_intrinsics.h",
*[f"{{}}/src/nouveau/{s}.h" for s in ["headers/nv_device_info", "compiler/nak"]],
*[f"{{}}/src/gallium/auxiliary/gallivm/lp_bld{s}.h" for s in ["", "_passmgr", "_misc", "_type", "_init", "_nir", "_struct", "_jit_types",
@@ -127,13 +130,13 @@ def __getattr__(nm):
*[f"python3 src/compiler/{s}_h.py > gen/{s.split('/')[-1]}.h" for s in ["nir/nir_opcodes", "nir/nir_builder_opcodes"]],
*[f"python3 src/compiler/nir/nir_{s}_h.py --outdir gen" for s in ["intrinsics", "intrinsics_indices"]]]), cwd=path, shell=True, check=True),
tarball="https://gitlab.freedesktop.org/mesa/mesa/-/archive/mesa-25.2.7/mesa-25.2.7.tar.gz",
prolog=["import gzip, base64", "from tinygrad.helpers import OSX"], epilog=lambda path: [system(f"{root}/extra/mesa/lvp_nir_options.sh {path}")])
prolog=["import gzip, base64"], epilog=lambda path: [system(f"{root}/extra/mesa/lvp_nir_options.sh {path}")])
case "libclang":
return load("libclang", ["os.getenv('LIBCLANG_PATH', find_library('clang-20'))"],
return load("libclang", "'clang-20'",
lambda: [f"{system('llvm-config-20 --includedir')}/clang-c/{s}.h" for s in ["Index", "CXString", "CXSourceLocation", "CXFile"]],
args=lambda: system("llvm-config-20 --cflags").split())
case "metal":
return load("metal", ["find_library('Metal')"],[f"{macossdk}/System/Library/Frameworks/Metal.framework/Headers/MTL{s}.h" for s in
return load("metal", "'Metal'", [f"{macossdk}/System/Library/Frameworks/Metal.framework/Headers/MTL{s}.h" for s in
["ComputeCommandEncoder", "ComputePipeline", "CommandQueue", "Device", "IndirectCommandBuffer", "Resource", "CommandEncoder"]],
args=["-xobjective-c","-isysroot",macossdk], types={"dispatch_data_t":"objc.id_"})
case _: raise AttributeError(f"no such autogen: {nm}")
+5 -2
View File
@@ -6,8 +6,9 @@ inc = ["-include", "stdint.h"]
def __getattr__(nm):
match nm:
case "am": return load("am/am", [], [root/f"extra/amdpci/headers/{s}.h" for s in ["v11_structs", "v12_structs", "amdgpu_vm", "discovery",
"amdgpu_ucode", "psp_gfx_if", "amdgpu_psp", "amdgpu_irq", "amdgpu_doorbell"]]+[f"{AMD}/include/soc15_ih_clientid.h"], args=inc, tarball=am_src)
case "am": return load("am/am", [], [root/f"extra/amdpci/headers/{s}.h" for s in ["v11_structs", "v12_structs", "amdgpu_vm",
"discovery", "amdgpu_ucode", "psp_gfx_if", "amdgpu_psp", "amdgpu_irq", "amdgpu_doorbell"]] + \
[f"{AMD}/include/{s}.h" for s in ["v9_structs", "soc15_ih_clientid"]], args=inc, tarball=am_src)
case "pm4_soc15": return load("am/pm4_soc15", [], [f"{AMD}/amdkfd/kfd_pm4_headers_ai.h", f"{AMD}/amdgpu/soc15d.h"], tarball=am_src)
case "pm4_nv": return load("am/pm4_nv", [], [f"{AMD}/amdkfd/kfd_pm4_headers_ai.h", f"{AMD}/amdgpu/nvd.h"], tarball=am_src)
case "sdma_4_0_0": return load("am/sdma_4_0_0", [], [root/"extra/hip_gpu_driver/sdma_registers.h", f"{AMD}/amdgpu/vega10_sdma_pkt_open.h"],
@@ -18,6 +19,8 @@ def __getattr__(nm):
args=["-I/opt/rocm/include", "-x", "c++"], tarball=am_src)
case "smu_v13_0_0": return load("am/smu_v13_0_0",[],[f"{AMD}/pm/swsmu/inc/pmfw_if/{s}.h" for s in ["smu_v13_0_0_ppsmc","smu13_driver_if_v13_0_0"]]
+[root/"extra/amdpci/headers/amdgpu_smu.h"], args=inc, tarball=am_src)
case "smu_v13_0_6": return load("am/smu_v13_0_6",[],[f"{AMD}/pm/swsmu/inc/pmfw_if/{s}.h" for s in ["smu_v13_0_6_ppsmc","smu13_driver_if_v13_0_6"]]
+[root/"extra/amdpci/headers/amdgpu_smu.h"], args=inc, tarball=am_src)
case "smu_v14_0_2": return load("am/smu_v14_0_2", [], [f"{AMD}/pm/swsmu/inc/pmfw_if/{s}.h" for s in ["smu_v14_0_0_pmfw", "smu_v14_0_2_ppsmc",
"smu14_driver_if_v14_0"]]+[root/"extra/amdpci/headers/amdgpu_smu.h"], args=inc, tarball=am_src)
case _: raise AttributeError(f"no such autogen: {nm}")
+740 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
class struct_v11_gfx_mqd(Struct): pass
struct_v11_gfx_mqd._fields_ = [
('shadow_base_lo', ctypes.c_uint32),
@@ -3878,6 +3877,745 @@ AMDGPU_DOORBELL_LAYOUT1_LAST_NON_CP = enum_AMDGPU_DOORBELL_ASSIGNMENT_LAYOUT1.de
AMDGPU_DOORBELL_LAYOUT1_MAX_ASSIGNMENT = enum_AMDGPU_DOORBELL_ASSIGNMENT_LAYOUT1.define('AMDGPU_DOORBELL_LAYOUT1_MAX_ASSIGNMENT', 488)
AMDGPU_DOORBELL_LAYOUT1_INVALID = enum_AMDGPU_DOORBELL_ASSIGNMENT_LAYOUT1.define('AMDGPU_DOORBELL_LAYOUT1_INVALID', 65535)
class struct_v9_sdma_mqd(Struct): pass
struct_v9_sdma_mqd._fields_ = [
('sdmax_rlcx_rb_cntl', uint32_t),
('sdmax_rlcx_rb_base', uint32_t),
('sdmax_rlcx_rb_base_hi', uint32_t),
('sdmax_rlcx_rb_rptr', uint32_t),
('sdmax_rlcx_rb_rptr_hi', uint32_t),
('sdmax_rlcx_rb_wptr', uint32_t),
('sdmax_rlcx_rb_wptr_hi', uint32_t),
('sdmax_rlcx_rb_wptr_poll_cntl', uint32_t),
('sdmax_rlcx_rb_rptr_addr_hi', uint32_t),
('sdmax_rlcx_rb_rptr_addr_lo', uint32_t),
('sdmax_rlcx_ib_cntl', uint32_t),
('sdmax_rlcx_ib_rptr', uint32_t),
('sdmax_rlcx_ib_offset', uint32_t),
('sdmax_rlcx_ib_base_lo', uint32_t),
('sdmax_rlcx_ib_base_hi', uint32_t),
('sdmax_rlcx_ib_size', uint32_t),
('sdmax_rlcx_skip_cntl', uint32_t),
('sdmax_rlcx_context_status', uint32_t),
('sdmax_rlcx_doorbell', uint32_t),
('sdmax_rlcx_status', uint32_t),
('sdmax_rlcx_doorbell_log', uint32_t),
('sdmax_rlcx_watermark', uint32_t),
('sdmax_rlcx_doorbell_offset', uint32_t),
('sdmax_rlcx_csa_addr_lo', uint32_t),
('sdmax_rlcx_csa_addr_hi', uint32_t),
('sdmax_rlcx_ib_sub_remain', uint32_t),
('sdmax_rlcx_preempt', uint32_t),
('sdmax_rlcx_dummy_reg', uint32_t),
('sdmax_rlcx_rb_wptr_poll_addr_hi', uint32_t),
('sdmax_rlcx_rb_wptr_poll_addr_lo', uint32_t),
('sdmax_rlcx_rb_aql_cntl', uint32_t),
('sdmax_rlcx_minor_ptr_update', uint32_t),
('sdmax_rlcx_midcmd_data0', uint32_t),
('sdmax_rlcx_midcmd_data1', uint32_t),
('sdmax_rlcx_midcmd_data2', uint32_t),
('sdmax_rlcx_midcmd_data3', uint32_t),
('sdmax_rlcx_midcmd_data4', uint32_t),
('sdmax_rlcx_midcmd_data5', uint32_t),
('sdmax_rlcx_midcmd_data6', uint32_t),
('sdmax_rlcx_midcmd_data7', uint32_t),
('sdmax_rlcx_midcmd_data8', uint32_t),
('sdmax_rlcx_midcmd_cntl', uint32_t),
('reserved_42', uint32_t),
('reserved_43', uint32_t),
('reserved_44', uint32_t),
('reserved_45', uint32_t),
('reserved_46', uint32_t),
('reserved_47', uint32_t),
('reserved_48', uint32_t),
('reserved_49', uint32_t),
('reserved_50', uint32_t),
('reserved_51', uint32_t),
('reserved_52', uint32_t),
('reserved_53', uint32_t),
('reserved_54', uint32_t),
('reserved_55', uint32_t),
('reserved_56', uint32_t),
('reserved_57', uint32_t),
('reserved_58', uint32_t),
('reserved_59', uint32_t),
('reserved_60', uint32_t),
('reserved_61', uint32_t),
('reserved_62', uint32_t),
('reserved_63', uint32_t),
('reserved_64', uint32_t),
('reserved_65', uint32_t),
('reserved_66', uint32_t),
('reserved_67', uint32_t),
('reserved_68', uint32_t),
('reserved_69', uint32_t),
('reserved_70', uint32_t),
('reserved_71', uint32_t),
('reserved_72', uint32_t),
('reserved_73', uint32_t),
('reserved_74', uint32_t),
('reserved_75', uint32_t),
('reserved_76', uint32_t),
('reserved_77', uint32_t),
('reserved_78', uint32_t),
('reserved_79', uint32_t),
('reserved_80', uint32_t),
('reserved_81', uint32_t),
('reserved_82', uint32_t),
('reserved_83', uint32_t),
('reserved_84', uint32_t),
('reserved_85', uint32_t),
('reserved_86', uint32_t),
('reserved_87', uint32_t),
('reserved_88', uint32_t),
('reserved_89', uint32_t),
('reserved_90', uint32_t),
('reserved_91', uint32_t),
('reserved_92', uint32_t),
('reserved_93', uint32_t),
('reserved_94', uint32_t),
('reserved_95', uint32_t),
('reserved_96', uint32_t),
('reserved_97', uint32_t),
('reserved_98', uint32_t),
('reserved_99', uint32_t),
('reserved_100', uint32_t),
('reserved_101', uint32_t),
('reserved_102', uint32_t),
('reserved_103', uint32_t),
('reserved_104', uint32_t),
('reserved_105', uint32_t),
('reserved_106', uint32_t),
('reserved_107', uint32_t),
('reserved_108', uint32_t),
('reserved_109', uint32_t),
('reserved_110', uint32_t),
('reserved_111', uint32_t),
('reserved_112', uint32_t),
('reserved_113', uint32_t),
('reserved_114', uint32_t),
('reserved_115', uint32_t),
('reserved_116', uint32_t),
('reserved_117', uint32_t),
('reserved_118', uint32_t),
('reserved_119', uint32_t),
('reserved_120', uint32_t),
('reserved_121', uint32_t),
('reserved_122', uint32_t),
('reserved_123', uint32_t),
('reserved_124', uint32_t),
('reserved_125', uint32_t),
('sdma_engine_id', uint32_t),
('sdma_queue_id', uint32_t),
]
class struct_v9_mqd(Struct): pass
class struct_v9_mqd_0(ctypes.Union): pass
class struct_v9_mqd_0_0(Struct): pass
struct_v9_mqd_0_0._fields_ = [
('compute_static_thread_mgmt_se4', uint32_t),
('compute_static_thread_mgmt_se5', uint32_t),
('compute_static_thread_mgmt_se6', uint32_t),
('compute_static_thread_mgmt_se7', uint32_t),
]
class struct_v9_mqd_0_1(Struct): pass
struct_v9_mqd_0_1._fields_ = [
('compute_current_logic_xcc_id', uint32_t),
('compute_restart_cg_tg_id', uint32_t),
('compute_tg_chunk_size', uint32_t),
('compute_restore_tg_chunk_size', uint32_t),
]
struct_v9_mqd_0._anonymous_ = ['_0', '_1']
struct_v9_mqd_0._fields_ = [
('_0', struct_v9_mqd_0_0),
('_1', struct_v9_mqd_0_1),
]
class struct_v9_mqd_1(ctypes.Union): pass
class struct_v9_mqd_1_0(Struct): pass
struct_v9_mqd_1_0._fields_ = [
('reserved_225', uint32_t),
('reserved_226', uint32_t),
]
class struct_v9_mqd_1_1(Struct): pass
struct_v9_mqd_1_1._fields_ = [
('pm4_target_xcc_in_xcp', uint32_t),
('cp_mqd_stride_size', uint32_t),
]
struct_v9_mqd_1._anonymous_ = ['_0', '_1']
struct_v9_mqd_1._fields_ = [
('_0', struct_v9_mqd_1_0),
('_1', struct_v9_mqd_1_1),
]
struct_v9_mqd._anonymous_ = ['_0', '_1']
struct_v9_mqd._fields_ = [
('header', uint32_t),
('compute_dispatch_initiator', uint32_t),
('compute_dim_x', uint32_t),
('compute_dim_y', uint32_t),
('compute_dim_z', uint32_t),
('compute_start_x', uint32_t),
('compute_start_y', uint32_t),
('compute_start_z', uint32_t),
('compute_num_thread_x', uint32_t),
('compute_num_thread_y', uint32_t),
('compute_num_thread_z', uint32_t),
('compute_pipelinestat_enable', uint32_t),
('compute_perfcount_enable', uint32_t),
('compute_pgm_lo', uint32_t),
('compute_pgm_hi', uint32_t),
('compute_tba_lo', uint32_t),
('compute_tba_hi', uint32_t),
('compute_tma_lo', uint32_t),
('compute_tma_hi', uint32_t),
('compute_pgm_rsrc1', uint32_t),
('compute_pgm_rsrc2', uint32_t),
('compute_vmid', uint32_t),
('compute_resource_limits', uint32_t),
('compute_static_thread_mgmt_se0', uint32_t),
('compute_static_thread_mgmt_se1', uint32_t),
('compute_tmpring_size', uint32_t),
('compute_static_thread_mgmt_se2', uint32_t),
('compute_static_thread_mgmt_se3', uint32_t),
('compute_restart_x', uint32_t),
('compute_restart_y', uint32_t),
('compute_restart_z', uint32_t),
('compute_thread_trace_enable', uint32_t),
('compute_misc_reserved', uint32_t),
('compute_dispatch_id', uint32_t),
('compute_threadgroup_id', uint32_t),
('compute_relaunch', uint32_t),
('compute_wave_restore_addr_lo', uint32_t),
('compute_wave_restore_addr_hi', uint32_t),
('compute_wave_restore_control', uint32_t),
('_0', struct_v9_mqd_0),
('reserved_43', uint32_t),
('reserved_44', uint32_t),
('reserved_45', uint32_t),
('reserved_46', uint32_t),
('reserved_47', uint32_t),
('reserved_48', uint32_t),
('reserved_49', uint32_t),
('reserved_50', uint32_t),
('reserved_51', uint32_t),
('reserved_52', uint32_t),
('reserved_53', uint32_t),
('reserved_54', uint32_t),
('reserved_55', uint32_t),
('reserved_56', uint32_t),
('reserved_57', uint32_t),
('reserved_58', uint32_t),
('reserved_59', uint32_t),
('reserved_60', uint32_t),
('reserved_61', uint32_t),
('reserved_62', uint32_t),
('reserved_63', uint32_t),
('reserved_64', uint32_t),
('compute_user_data_0', uint32_t),
('compute_user_data_1', uint32_t),
('compute_user_data_2', uint32_t),
('compute_user_data_3', uint32_t),
('compute_user_data_4', uint32_t),
('compute_user_data_5', uint32_t),
('compute_user_data_6', uint32_t),
('compute_user_data_7', uint32_t),
('compute_user_data_8', uint32_t),
('compute_user_data_9', uint32_t),
('compute_user_data_10', uint32_t),
('compute_user_data_11', uint32_t),
('compute_user_data_12', uint32_t),
('compute_user_data_13', uint32_t),
('compute_user_data_14', uint32_t),
('compute_user_data_15', uint32_t),
('cp_compute_csinvoc_count_lo', uint32_t),
('cp_compute_csinvoc_count_hi', uint32_t),
('reserved_83', uint32_t),
('reserved_84', uint32_t),
('reserved_85', uint32_t),
('cp_mqd_query_time_lo', uint32_t),
('cp_mqd_query_time_hi', uint32_t),
('cp_mqd_connect_start_time_lo', uint32_t),
('cp_mqd_connect_start_time_hi', uint32_t),
('cp_mqd_connect_end_time_lo', uint32_t),
('cp_mqd_connect_end_time_hi', uint32_t),
('cp_mqd_connect_end_wf_count', uint32_t),
('cp_mqd_connect_end_pq_rptr', uint32_t),
('cp_mqd_connect_end_pq_wptr', uint32_t),
('cp_mqd_connect_end_ib_rptr', uint32_t),
('cp_mqd_readindex_lo', uint32_t),
('cp_mqd_readindex_hi', uint32_t),
('cp_mqd_save_start_time_lo', uint32_t),
('cp_mqd_save_start_time_hi', uint32_t),
('cp_mqd_save_end_time_lo', uint32_t),
('cp_mqd_save_end_time_hi', uint32_t),
('cp_mqd_restore_start_time_lo', uint32_t),
('cp_mqd_restore_start_time_hi', uint32_t),
('cp_mqd_restore_end_time_lo', uint32_t),
('cp_mqd_restore_end_time_hi', uint32_t),
('disable_queue', uint32_t),
('reserved_107', uint32_t),
('gds_cs_ctxsw_cnt0', uint32_t),
('gds_cs_ctxsw_cnt1', uint32_t),
('gds_cs_ctxsw_cnt2', uint32_t),
('gds_cs_ctxsw_cnt3', uint32_t),
('reserved_112', uint32_t),
('reserved_113', uint32_t),
('cp_pq_exe_status_lo', uint32_t),
('cp_pq_exe_status_hi', uint32_t),
('cp_packet_id_lo', uint32_t),
('cp_packet_id_hi', uint32_t),
('cp_packet_exe_status_lo', uint32_t),
('cp_packet_exe_status_hi', uint32_t),
('gds_save_base_addr_lo', uint32_t),
('gds_save_base_addr_hi', uint32_t),
('gds_save_mask_lo', uint32_t),
('gds_save_mask_hi', uint32_t),
('ctx_save_base_addr_lo', uint32_t),
('ctx_save_base_addr_hi', uint32_t),
('dynamic_cu_mask_addr_lo', uint32_t),
('dynamic_cu_mask_addr_hi', uint32_t),
('cp_mqd_base_addr_lo', uint32_t),
('cp_mqd_base_addr_hi', uint32_t),
('cp_hqd_active', uint32_t),
('cp_hqd_vmid', uint32_t),
('cp_hqd_persistent_state', uint32_t),
('cp_hqd_pipe_priority', uint32_t),
('cp_hqd_queue_priority', uint32_t),
('cp_hqd_quantum', uint32_t),
('cp_hqd_pq_base_lo', uint32_t),
('cp_hqd_pq_base_hi', uint32_t),
('cp_hqd_pq_rptr', uint32_t),
('cp_hqd_pq_rptr_report_addr_lo', uint32_t),
('cp_hqd_pq_rptr_report_addr_hi', uint32_t),
('cp_hqd_pq_wptr_poll_addr_lo', uint32_t),
('cp_hqd_pq_wptr_poll_addr_hi', uint32_t),
('cp_hqd_pq_doorbell_control', uint32_t),
('reserved_144', uint32_t),
('cp_hqd_pq_control', uint32_t),
('cp_hqd_ib_base_addr_lo', uint32_t),
('cp_hqd_ib_base_addr_hi', uint32_t),
('cp_hqd_ib_rptr', uint32_t),
('cp_hqd_ib_control', uint32_t),
('cp_hqd_iq_timer', uint32_t),
('cp_hqd_iq_rptr', uint32_t),
('cp_hqd_dequeue_request', uint32_t),
('cp_hqd_dma_offload', uint32_t),
('cp_hqd_sema_cmd', uint32_t),
('cp_hqd_msg_type', uint32_t),
('cp_hqd_atomic0_preop_lo', uint32_t),
('cp_hqd_atomic0_preop_hi', uint32_t),
('cp_hqd_atomic1_preop_lo', uint32_t),
('cp_hqd_atomic1_preop_hi', uint32_t),
('cp_hqd_hq_status0', uint32_t),
('cp_hqd_hq_control0', uint32_t),
('cp_mqd_control', uint32_t),
('cp_hqd_hq_status1', uint32_t),
('cp_hqd_hq_control1', uint32_t),
('cp_hqd_eop_base_addr_lo', uint32_t),
('cp_hqd_eop_base_addr_hi', uint32_t),
('cp_hqd_eop_control', uint32_t),
('cp_hqd_eop_rptr', uint32_t),
('cp_hqd_eop_wptr', uint32_t),
('cp_hqd_eop_done_events', uint32_t),
('cp_hqd_ctx_save_base_addr_lo', uint32_t),
('cp_hqd_ctx_save_base_addr_hi', uint32_t),
('cp_hqd_ctx_save_control', uint32_t),
('cp_hqd_cntl_stack_offset', uint32_t),
('cp_hqd_cntl_stack_size', uint32_t),
('cp_hqd_wg_state_offset', uint32_t),
('cp_hqd_ctx_save_size', uint32_t),
('cp_hqd_gds_resource_state', uint32_t),
('cp_hqd_error', uint32_t),
('cp_hqd_eop_wptr_mem', uint32_t),
('cp_hqd_aql_control', uint32_t),
('cp_hqd_pq_wptr_lo', uint32_t),
('cp_hqd_pq_wptr_hi', uint32_t),
('reserved_184', uint32_t),
('reserved_185', uint32_t),
('reserved_186', uint32_t),
('reserved_187', uint32_t),
('reserved_188', uint32_t),
('reserved_189', uint32_t),
('reserved_190', uint32_t),
('reserved_191', uint32_t),
('iqtimer_pkt_header', uint32_t),
('iqtimer_pkt_dw0', uint32_t),
('iqtimer_pkt_dw1', uint32_t),
('iqtimer_pkt_dw2', uint32_t),
('iqtimer_pkt_dw3', uint32_t),
('iqtimer_pkt_dw4', uint32_t),
('iqtimer_pkt_dw5', uint32_t),
('iqtimer_pkt_dw6', uint32_t),
('iqtimer_pkt_dw7', uint32_t),
('iqtimer_pkt_dw8', uint32_t),
('iqtimer_pkt_dw9', uint32_t),
('iqtimer_pkt_dw10', uint32_t),
('iqtimer_pkt_dw11', uint32_t),
('iqtimer_pkt_dw12', uint32_t),
('iqtimer_pkt_dw13', uint32_t),
('iqtimer_pkt_dw14', uint32_t),
('iqtimer_pkt_dw15', uint32_t),
('iqtimer_pkt_dw16', uint32_t),
('iqtimer_pkt_dw17', uint32_t),
('iqtimer_pkt_dw18', uint32_t),
('iqtimer_pkt_dw19', uint32_t),
('iqtimer_pkt_dw20', uint32_t),
('iqtimer_pkt_dw21', uint32_t),
('iqtimer_pkt_dw22', uint32_t),
('iqtimer_pkt_dw23', uint32_t),
('iqtimer_pkt_dw24', uint32_t),
('iqtimer_pkt_dw25', uint32_t),
('iqtimer_pkt_dw26', uint32_t),
('iqtimer_pkt_dw27', uint32_t),
('iqtimer_pkt_dw28', uint32_t),
('iqtimer_pkt_dw29', uint32_t),
('iqtimer_pkt_dw30', uint32_t),
('iqtimer_pkt_dw31', uint32_t),
('_1', struct_v9_mqd_1),
('reserved_227', uint32_t),
('set_resources_header', uint32_t),
('set_resources_dw1', uint32_t),
('set_resources_dw2', uint32_t),
('set_resources_dw3', uint32_t),
('set_resources_dw4', uint32_t),
('set_resources_dw5', uint32_t),
('set_resources_dw6', uint32_t),
('set_resources_dw7', uint32_t),
('reserved_236', uint32_t),
('reserved_237', uint32_t),
('reserved_238', uint32_t),
('reserved_239', uint32_t),
('queue_doorbell_id0', uint32_t),
('queue_doorbell_id1', uint32_t),
('queue_doorbell_id2', uint32_t),
('queue_doorbell_id3', uint32_t),
('queue_doorbell_id4', uint32_t),
('queue_doorbell_id5', uint32_t),
('queue_doorbell_id6', uint32_t),
('queue_doorbell_id7', uint32_t),
('queue_doorbell_id8', uint32_t),
('queue_doorbell_id9', uint32_t),
('queue_doorbell_id10', uint32_t),
('queue_doorbell_id11', uint32_t),
('queue_doorbell_id12', uint32_t),
('queue_doorbell_id13', uint32_t),
('queue_doorbell_id14', uint32_t),
('queue_doorbell_id15', uint32_t),
('reserved_256', uint32_t),
('reserved_257', uint32_t),
('reserved_258', uint32_t),
('reserved_259', uint32_t),
('reserved_260', uint32_t),
('reserved_261', uint32_t),
('reserved_262', uint32_t),
('reserved_263', uint32_t),
('reserved_264', uint32_t),
('reserved_265', uint32_t),
('reserved_266', uint32_t),
('reserved_267', uint32_t),
('reserved_268', uint32_t),
('reserved_269', uint32_t),
('reserved_270', uint32_t),
('reserved_271', uint32_t),
('reserved_272', uint32_t),
('reserved_273', uint32_t),
('reserved_274', uint32_t),
('reserved_275', uint32_t),
('reserved_276', uint32_t),
('reserved_277', uint32_t),
('reserved_278', uint32_t),
('reserved_279', uint32_t),
('reserved_280', uint32_t),
('reserved_281', uint32_t),
('reserved_282', uint32_t),
('reserved_283', uint32_t),
('reserved_284', uint32_t),
('reserved_285', uint32_t),
('reserved_286', uint32_t),
('reserved_287', uint32_t),
('reserved_288', uint32_t),
('reserved_289', uint32_t),
('reserved_290', uint32_t),
('reserved_291', uint32_t),
('reserved_292', uint32_t),
('reserved_293', uint32_t),
('reserved_294', uint32_t),
('reserved_295', uint32_t),
('reserved_296', uint32_t),
('reserved_297', uint32_t),
('reserved_298', uint32_t),
('reserved_299', uint32_t),
('reserved_300', uint32_t),
('reserved_301', uint32_t),
('reserved_302', uint32_t),
('reserved_303', uint32_t),
('reserved_304', uint32_t),
('reserved_305', uint32_t),
('reserved_306', uint32_t),
('reserved_307', uint32_t),
('reserved_308', uint32_t),
('reserved_309', uint32_t),
('reserved_310', uint32_t),
('reserved_311', uint32_t),
('reserved_312', uint32_t),
('reserved_313', uint32_t),
('reserved_314', uint32_t),
('reserved_315', uint32_t),
('reserved_316', uint32_t),
('reserved_317', uint32_t),
('reserved_318', uint32_t),
('reserved_319', uint32_t),
('reserved_320', uint32_t),
('reserved_321', uint32_t),
('reserved_322', uint32_t),
('reserved_323', uint32_t),
('reserved_324', uint32_t),
('reserved_325', uint32_t),
('reserved_326', uint32_t),
('reserved_327', uint32_t),
('reserved_328', uint32_t),
('reserved_329', uint32_t),
('reserved_330', uint32_t),
('reserved_331', uint32_t),
('reserved_332', uint32_t),
('reserved_333', uint32_t),
('reserved_334', uint32_t),
('reserved_335', uint32_t),
('reserved_336', uint32_t),
('reserved_337', uint32_t),
('reserved_338', uint32_t),
('reserved_339', uint32_t),
('reserved_340', uint32_t),
('reserved_341', uint32_t),
('reserved_342', uint32_t),
('reserved_343', uint32_t),
('reserved_344', uint32_t),
('reserved_345', uint32_t),
('reserved_346', uint32_t),
('reserved_347', uint32_t),
('reserved_348', uint32_t),
('reserved_349', uint32_t),
('reserved_350', uint32_t),
('reserved_351', uint32_t),
('reserved_352', uint32_t),
('reserved_353', uint32_t),
('reserved_354', uint32_t),
('reserved_355', uint32_t),
('reserved_356', uint32_t),
('reserved_357', uint32_t),
('reserved_358', uint32_t),
('reserved_359', uint32_t),
('reserved_360', uint32_t),
('reserved_361', uint32_t),
('reserved_362', uint32_t),
('reserved_363', uint32_t),
('reserved_364', uint32_t),
('reserved_365', uint32_t),
('reserved_366', uint32_t),
('reserved_367', uint32_t),
('reserved_368', uint32_t),
('reserved_369', uint32_t),
('reserved_370', uint32_t),
('reserved_371', uint32_t),
('reserved_372', uint32_t),
('reserved_373', uint32_t),
('reserved_374', uint32_t),
('reserved_375', uint32_t),
('reserved_376', uint32_t),
('reserved_377', uint32_t),
('reserved_378', uint32_t),
('reserved_379', uint32_t),
('reserved_380', uint32_t),
('reserved_381', uint32_t),
('reserved_382', uint32_t),
('reserved_383', uint32_t),
('reserved_384', uint32_t),
('reserved_385', uint32_t),
('reserved_386', uint32_t),
('reserved_387', uint32_t),
('reserved_388', uint32_t),
('reserved_389', uint32_t),
('reserved_390', uint32_t),
('reserved_391', uint32_t),
('reserved_392', uint32_t),
('reserved_393', uint32_t),
('reserved_394', uint32_t),
('reserved_395', uint32_t),
('reserved_396', uint32_t),
('reserved_397', uint32_t),
('reserved_398', uint32_t),
('reserved_399', uint32_t),
('reserved_400', uint32_t),
('reserved_401', uint32_t),
('reserved_402', uint32_t),
('reserved_403', uint32_t),
('reserved_404', uint32_t),
('reserved_405', uint32_t),
('reserved_406', uint32_t),
('reserved_407', uint32_t),
('reserved_408', uint32_t),
('reserved_409', uint32_t),
('reserved_410', uint32_t),
('reserved_411', uint32_t),
('reserved_412', uint32_t),
('reserved_413', uint32_t),
('reserved_414', uint32_t),
('reserved_415', uint32_t),
('reserved_416', uint32_t),
('reserved_417', uint32_t),
('reserved_418', uint32_t),
('reserved_419', uint32_t),
('reserved_420', uint32_t),
('reserved_421', uint32_t),
('reserved_422', uint32_t),
('reserved_423', uint32_t),
('reserved_424', uint32_t),
('reserved_425', uint32_t),
('reserved_426', uint32_t),
('reserved_427', uint32_t),
('reserved_428', uint32_t),
('reserved_429', uint32_t),
('reserved_430', uint32_t),
('reserved_431', uint32_t),
('reserved_432', uint32_t),
('reserved_433', uint32_t),
('reserved_434', uint32_t),
('reserved_435', uint32_t),
('reserved_436', uint32_t),
('reserved_437', uint32_t),
('reserved_438', uint32_t),
('reserved_439', uint32_t),
('reserved_440', uint32_t),
('reserved_441', uint32_t),
('reserved_442', uint32_t),
('reserved_443', uint32_t),
('reserved_444', uint32_t),
('reserved_445', uint32_t),
('reserved_446', uint32_t),
('reserved_447', uint32_t),
('reserved_448', uint32_t),
('reserved_449', uint32_t),
('reserved_450', uint32_t),
('reserved_451', uint32_t),
('reserved_452', uint32_t),
('reserved_453', uint32_t),
('reserved_454', uint32_t),
('reserved_455', uint32_t),
('reserved_456', uint32_t),
('reserved_457', uint32_t),
('reserved_458', uint32_t),
('reserved_459', uint32_t),
('reserved_460', uint32_t),
('reserved_461', uint32_t),
('reserved_462', uint32_t),
('reserved_463', uint32_t),
('reserved_464', uint32_t),
('reserved_465', uint32_t),
('reserved_466', uint32_t),
('reserved_467', uint32_t),
('reserved_468', uint32_t),
('reserved_469', uint32_t),
('reserved_470', uint32_t),
('reserved_471', uint32_t),
('reserved_472', uint32_t),
('reserved_473', uint32_t),
('reserved_474', uint32_t),
('reserved_475', uint32_t),
('reserved_476', uint32_t),
('reserved_477', uint32_t),
('reserved_478', uint32_t),
('reserved_479', uint32_t),
('reserved_480', uint32_t),
('reserved_481', uint32_t),
('reserved_482', uint32_t),
('reserved_483', uint32_t),
('reserved_484', uint32_t),
('reserved_485', uint32_t),
('reserved_486', uint32_t),
('reserved_487', uint32_t),
('reserved_488', uint32_t),
('reserved_489', uint32_t),
('reserved_490', uint32_t),
('reserved_491', uint32_t),
('reserved_492', uint32_t),
('reserved_493', uint32_t),
('reserved_494', uint32_t),
('reserved_495', uint32_t),
('reserved_496', uint32_t),
('reserved_497', uint32_t),
('reserved_498', uint32_t),
('reserved_499', uint32_t),
('reserved_500', uint32_t),
('reserved_501', uint32_t),
('reserved_502', uint32_t),
('reserved_503', uint32_t),
('reserved_504', uint32_t),
('reserved_505', uint32_t),
('reserved_506', uint32_t),
('reserved_507', uint32_t),
('reserved_508', uint32_t),
('reserved_509', uint32_t),
('reserved_510', uint32_t),
('reserved_511', uint32_t),
]
class struct_v9_mqd_allocation(Struct): pass
struct_v9_mqd_allocation._fields_ = [
('mqd', struct_v9_mqd),
('wptr_poll_mem', uint32_t),
('rptr_report_mem', uint32_t),
('dynamic_cu_mask', uint32_t),
('dynamic_rb_mask', uint32_t),
]
class struct_v9_ce_ib_state(Struct): pass
struct_v9_ce_ib_state._fields_ = [
('ce_ib_completion_status', uint32_t),
('ce_constegnine_count', uint32_t),
('ce_ibOffset_ib1', uint32_t),
('ce_ibOffset_ib2', uint32_t),
('ce_chainib_addrlo_ib1', uint32_t),
('ce_chainib_addrlo_ib2', uint32_t),
('ce_chainib_addrhi_ib1', uint32_t),
('ce_chainib_addrhi_ib2', uint32_t),
('ce_chainib_size_ib1', uint32_t),
('ce_chainib_size_ib2', uint32_t),
]
class struct_v9_de_ib_state(Struct): pass
struct_v9_de_ib_state._fields_ = [
('ib_completion_status', uint32_t),
('de_constEngine_count', uint32_t),
('ib_offset_ib1', uint32_t),
('ib_offset_ib2', uint32_t),
('chain_ib_addrlo_ib1', uint32_t),
('chain_ib_addrlo_ib2', uint32_t),
('chain_ib_addrhi_ib1', uint32_t),
('chain_ib_addrhi_ib2', uint32_t),
('chain_ib_size_ib1', uint32_t),
('chain_ib_size_ib2', uint32_t),
('preamble_begin_ib1', uint32_t),
('preamble_begin_ib2', uint32_t),
('preamble_end_ib1', uint32_t),
('preamble_end_ib2', uint32_t),
('chain_ib_pream_addrlo_ib1', uint32_t),
('chain_ib_pream_addrlo_ib2', uint32_t),
('chain_ib_pream_addrhi_ib1', uint32_t),
('chain_ib_pream_addrhi_ib2', uint32_t),
('draw_indirect_baseLo', uint32_t),
('draw_indirect_baseHi', uint32_t),
('disp_indirect_baseLo', uint32_t),
('disp_indirect_baseHi', uint32_t),
('gds_backup_addrlo', uint32_t),
('gds_backup_addrhi', uint32_t),
('index_base_addrlo', uint32_t),
('index_base_addrhi', uint32_t),
('sample_cntl', uint32_t),
]
class struct_v9_gfx_meta_data(Struct): pass
struct_v9_gfx_meta_data._fields_ = [
('ce_payload', struct_v9_ce_ib_state),
('reserved1', (uint32_t * 54)),
('de_payload', struct_v9_de_ib_state),
('DeIbBaseAddrLo', uint32_t),
('DeIbBaseAddrHi', uint32_t),
('reserved2', (uint32_t * 931)),
]
enum_soc15_ih_clientid = CEnum(ctypes.c_uint32)
SOC15_IH_CLIENTID_IH = enum_soc15_ih_clientid.define('SOC15_IH_CLIENTID_IH', 0)
SOC15_IH_CLIENTID_ACP = enum_soc15_ih_clientid.define('SOC15_IH_CLIENTID_ACP', 1)
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
class union_PM4_MES_TYPE_3_HEADER(ctypes.Union): pass
enum_mes_set_resources_queue_type_enum = CEnum(ctypes.c_uint32)
queue_type__mes_set_resources__kernel_interface_queue_kiq = enum_mes_set_resources_queue_type_enum.define('queue_type__mes_set_resources__kernel_interface_queue_kiq', 0)
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
class union_PM4_MES_TYPE_3_HEADER(ctypes.Union): pass
enum_mes_set_resources_queue_type_enum = CEnum(ctypes.c_uint32)
queue_type__mes_set_resources__kernel_interface_queue_kiq = enum_mes_set_resources_queue_type_enum.define('queue_type__mes_set_resources__kernel_interface_queue_kiq', 0)
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
class rocr_AMD_SDMA_PKT_COPY_LINEAR_TAG(Struct): pass
class rocr_AMD_SDMA_PKT_COPY_LINEAR_TAG_HEADER_UNION(ctypes.Union): pass
class rocr_AMD_SDMA_PKT_COPY_LINEAR_TAG_HEADER_UNION_0(Struct): pass
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
class rocr_AMD_SDMA_PKT_COPY_LINEAR_TAG(Struct): pass
class rocr_AMD_SDMA_PKT_COPY_LINEAR_TAG_HEADER_UNION(ctypes.Union): pass
class rocr_AMD_SDMA_PKT_COPY_LINEAR_TAG_HEADER_UNION_0(Struct): pass
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
class rocr_AMD_SDMA_PKT_COPY_LINEAR_TAG(Struct): pass
class rocr_AMD_SDMA_PKT_COPY_LINEAR_TAG_HEADER_UNION(ctypes.Union): pass
class rocr_AMD_SDMA_PKT_COPY_LINEAR_TAG_HEADER_UNION_0(Struct): pass
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
FEATURE_PWR_DOMAIN_e = CEnum(ctypes.c_uint32)
FEATURE_PWR_ALL = FEATURE_PWR_DOMAIN_e.define('FEATURE_PWR_ALL', 0)
FEATURE_PWR_S5 = FEATURE_PWR_DOMAIN_e.define('FEATURE_PWR_S5', 1)
+531
View File
@@ -0,0 +1,531 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
PPSMC_Result = ctypes.c_uint32
PPSMC_MSG = ctypes.c_uint32
I2cControllerPort_e = CEnum(ctypes.c_uint32)
I2C_CONTROLLER_PORT_0 = I2cControllerPort_e.define('I2C_CONTROLLER_PORT_0', 0)
I2C_CONTROLLER_PORT_1 = I2cControllerPort_e.define('I2C_CONTROLLER_PORT_1', 1)
I2C_CONTROLLER_PORT_COUNT = I2cControllerPort_e.define('I2C_CONTROLLER_PORT_COUNT', 2)
I2cSpeed_e = CEnum(ctypes.c_uint32)
UNSUPPORTED_1 = I2cSpeed_e.define('UNSUPPORTED_1', 0)
I2C_SPEED_STANDARD_100K = I2cSpeed_e.define('I2C_SPEED_STANDARD_100K', 1)
I2C_SPEED_FAST_400K = I2cSpeed_e.define('I2C_SPEED_FAST_400K', 2)
I2C_SPEED_FAST_PLUS_1M = I2cSpeed_e.define('I2C_SPEED_FAST_PLUS_1M', 3)
UNSUPPORTED_2 = I2cSpeed_e.define('UNSUPPORTED_2', 4)
UNSUPPORTED_3 = I2cSpeed_e.define('UNSUPPORTED_3', 5)
I2C_SPEED_COUNT = I2cSpeed_e.define('I2C_SPEED_COUNT', 6)
I2cCmdType_e = CEnum(ctypes.c_uint32)
I2C_CMD_READ = I2cCmdType_e.define('I2C_CMD_READ', 0)
I2C_CMD_WRITE = I2cCmdType_e.define('I2C_CMD_WRITE', 1)
I2C_CMD_COUNT = I2cCmdType_e.define('I2C_CMD_COUNT', 2)
ERR_CODE_e = CEnum(ctypes.c_uint32)
CODE_DAGB0 = ERR_CODE_e.define('CODE_DAGB0', 0)
CODE_EA0 = ERR_CODE_e.define('CODE_EA0', 5)
CODE_UTCL2_ROUTER = ERR_CODE_e.define('CODE_UTCL2_ROUTER', 10)
CODE_VML2 = ERR_CODE_e.define('CODE_VML2', 11)
CODE_VML2_WALKER = ERR_CODE_e.define('CODE_VML2_WALKER', 12)
CODE_MMCANE = ERR_CODE_e.define('CODE_MMCANE', 13)
CODE_VIDD = ERR_CODE_e.define('CODE_VIDD', 14)
CODE_VIDV = ERR_CODE_e.define('CODE_VIDV', 15)
CODE_JPEG0S = ERR_CODE_e.define('CODE_JPEG0S', 16)
CODE_JPEG0D = ERR_CODE_e.define('CODE_JPEG0D', 17)
CODE_JPEG1S = ERR_CODE_e.define('CODE_JPEG1S', 18)
CODE_JPEG1D = ERR_CODE_e.define('CODE_JPEG1D', 19)
CODE_JPEG2S = ERR_CODE_e.define('CODE_JPEG2S', 20)
CODE_JPEG2D = ERR_CODE_e.define('CODE_JPEG2D', 21)
CODE_JPEG3S = ERR_CODE_e.define('CODE_JPEG3S', 22)
CODE_JPEG3D = ERR_CODE_e.define('CODE_JPEG3D', 23)
CODE_JPEG4S = ERR_CODE_e.define('CODE_JPEG4S', 24)
CODE_JPEG4D = ERR_CODE_e.define('CODE_JPEG4D', 25)
CODE_JPEG5S = ERR_CODE_e.define('CODE_JPEG5S', 26)
CODE_JPEG5D = ERR_CODE_e.define('CODE_JPEG5D', 27)
CODE_JPEG6S = ERR_CODE_e.define('CODE_JPEG6S', 28)
CODE_JPEG6D = ERR_CODE_e.define('CODE_JPEG6D', 29)
CODE_JPEG7S = ERR_CODE_e.define('CODE_JPEG7S', 30)
CODE_JPEG7D = ERR_CODE_e.define('CODE_JPEG7D', 31)
CODE_MMSCHD = ERR_CODE_e.define('CODE_MMSCHD', 32)
CODE_SDMA0 = ERR_CODE_e.define('CODE_SDMA0', 33)
CODE_SDMA1 = ERR_CODE_e.define('CODE_SDMA1', 34)
CODE_SDMA2 = ERR_CODE_e.define('CODE_SDMA2', 35)
CODE_SDMA3 = ERR_CODE_e.define('CODE_SDMA3', 36)
CODE_HDP = ERR_CODE_e.define('CODE_HDP', 37)
CODE_ATHUB = ERR_CODE_e.define('CODE_ATHUB', 38)
CODE_IH = ERR_CODE_e.define('CODE_IH', 39)
CODE_XHUB_POISON = ERR_CODE_e.define('CODE_XHUB_POISON', 40)
CODE_SMN_SLVERR = ERR_CODE_e.define('CODE_SMN_SLVERR', 40)
CODE_WDT = ERR_CODE_e.define('CODE_WDT', 41)
CODE_UNKNOWN = ERR_CODE_e.define('CODE_UNKNOWN', 42)
CODE_COUNT = ERR_CODE_e.define('CODE_COUNT', 43)
GC_ERROR_CODE_e = CEnum(ctypes.c_uint32)
SH_FED_CODE = GC_ERROR_CODE_e.define('SH_FED_CODE', 0)
GCEA_CODE = GC_ERROR_CODE_e.define('GCEA_CODE', 1)
SQ_CODE = GC_ERROR_CODE_e.define('SQ_CODE', 2)
LDS_CODE = GC_ERROR_CODE_e.define('LDS_CODE', 3)
GDS_CODE = GC_ERROR_CODE_e.define('GDS_CODE', 4)
SP0_CODE = GC_ERROR_CODE_e.define('SP0_CODE', 5)
SP1_CODE = GC_ERROR_CODE_e.define('SP1_CODE', 6)
TCC_CODE = GC_ERROR_CODE_e.define('TCC_CODE', 7)
TCA_CODE = GC_ERROR_CODE_e.define('TCA_CODE', 8)
TCX_CODE = GC_ERROR_CODE_e.define('TCX_CODE', 9)
CPC_CODE = GC_ERROR_CODE_e.define('CPC_CODE', 10)
CPF_CODE = GC_ERROR_CODE_e.define('CPF_CODE', 11)
CPG_CODE = GC_ERROR_CODE_e.define('CPG_CODE', 12)
SPI_CODE = GC_ERROR_CODE_e.define('SPI_CODE', 13)
RLC_CODE = GC_ERROR_CODE_e.define('RLC_CODE', 14)
SQC_CODE = GC_ERROR_CODE_e.define('SQC_CODE', 15)
TA_CODE = GC_ERROR_CODE_e.define('TA_CODE', 16)
TD_CODE = GC_ERROR_CODE_e.define('TD_CODE', 17)
TCP_CODE = GC_ERROR_CODE_e.define('TCP_CODE', 18)
TCI_CODE = GC_ERROR_CODE_e.define('TCI_CODE', 19)
GC_ROUTER_CODE = GC_ERROR_CODE_e.define('GC_ROUTER_CODE', 20)
VML2_CODE = GC_ERROR_CODE_e.define('VML2_CODE', 21)
VML2_WALKER_CODE = GC_ERROR_CODE_e.define('VML2_WALKER_CODE', 22)
ATCL2_CODE = GC_ERROR_CODE_e.define('ATCL2_CODE', 23)
GC_CANE_CODE = GC_ERROR_CODE_e.define('GC_CANE_CODE', 24)
MP5_CODE_SMN_SLVERR = GC_ERROR_CODE_e.define('MP5_CODE_SMN_SLVERR', 40)
MP5_CODE_UNKNOWN = GC_ERROR_CODE_e.define('MP5_CODE_UNKNOWN', 42)
class SwI2cCmd_t(Struct): pass
uint8_t = ctypes.c_ubyte
SwI2cCmd_t._fields_ = [
('ReadWriteData', uint8_t),
('CmdConfig', uint8_t),
]
class SwI2cRequest_t(Struct): pass
SwI2cRequest_t._fields_ = [
('I2CcontrollerPort', uint8_t),
('I2CSpeed', uint8_t),
('SlaveAddress', uint8_t),
('NumCmds', uint8_t),
('SwI2cCmds', (SwI2cCmd_t * 24)),
]
class SwI2cRequestExternal_t(Struct): pass
uint32_t = ctypes.c_uint32
SwI2cRequestExternal_t._fields_ = [
('SwI2cRequest', SwI2cRequest_t),
('Spare', (uint32_t * 8)),
('MmHubPadding', (uint32_t * 8)),
]
PPCLK_e = CEnum(ctypes.c_uint32)
PPCLK_VCLK = PPCLK_e.define('PPCLK_VCLK', 0)
PPCLK_DCLK = PPCLK_e.define('PPCLK_DCLK', 1)
PPCLK_SOCCLK = PPCLK_e.define('PPCLK_SOCCLK', 2)
PPCLK_UCLK = PPCLK_e.define('PPCLK_UCLK', 3)
PPCLK_FCLK = PPCLK_e.define('PPCLK_FCLK', 4)
PPCLK_LCLK = PPCLK_e.define('PPCLK_LCLK', 5)
PPCLK_COUNT = PPCLK_e.define('PPCLK_COUNT', 6)
GpioIntPolarity_e = CEnum(ctypes.c_uint32)
GPIO_INT_POLARITY_ACTIVE_LOW = GpioIntPolarity_e.define('GPIO_INT_POLARITY_ACTIVE_LOW', 0)
GPIO_INT_POLARITY_ACTIVE_HIGH = GpioIntPolarity_e.define('GPIO_INT_POLARITY_ACTIVE_HIGH', 1)
UCLK_DPM_MODE_e = CEnum(ctypes.c_uint32)
UCLK_DPM_MODE_BANDWIDTH = UCLK_DPM_MODE_e.define('UCLK_DPM_MODE_BANDWIDTH', 0)
UCLK_DPM_MODE_LATENCY = UCLK_DPM_MODE_e.define('UCLK_DPM_MODE_LATENCY', 1)
class AvfsDebugTableAid_t(Struct): pass
uint16_t = ctypes.c_uint16
AvfsDebugTableAid_t._fields_ = [
('avgPsmCount', (uint16_t * 30)),
('minPsmCount', (uint16_t * 30)),
('avgPsmVoltage', (ctypes.c_float * 30)),
('minPsmVoltage', (ctypes.c_float * 30)),
]
class AvfsDebugTableXcd_t(Struct): pass
AvfsDebugTableXcd_t._fields_ = [
('avgPsmCount', (uint16_t * 30)),
('minPsmCount', (uint16_t * 30)),
('avgPsmVoltage', (ctypes.c_float * 30)),
('minPsmVoltage', (ctypes.c_float * 30)),
]
class struct_smu_hw_power_state(Struct): pass
struct_smu_hw_power_state._fields_ = [
('magic', ctypes.c_uint32),
]
class struct_smu_power_state(Struct): pass
enum_smu_state_ui_label = CEnum(ctypes.c_uint32)
SMU_STATE_UI_LABEL_NONE = enum_smu_state_ui_label.define('SMU_STATE_UI_LABEL_NONE', 0)
SMU_STATE_UI_LABEL_BATTERY = enum_smu_state_ui_label.define('SMU_STATE_UI_LABEL_BATTERY', 1)
SMU_STATE_UI_TABEL_MIDDLE_LOW = enum_smu_state_ui_label.define('SMU_STATE_UI_TABEL_MIDDLE_LOW', 2)
SMU_STATE_UI_LABEL_BALLANCED = enum_smu_state_ui_label.define('SMU_STATE_UI_LABEL_BALLANCED', 3)
SMU_STATE_UI_LABEL_MIDDLE_HIGHT = enum_smu_state_ui_label.define('SMU_STATE_UI_LABEL_MIDDLE_HIGHT', 4)
SMU_STATE_UI_LABEL_PERFORMANCE = enum_smu_state_ui_label.define('SMU_STATE_UI_LABEL_PERFORMANCE', 5)
SMU_STATE_UI_LABEL_BACO = enum_smu_state_ui_label.define('SMU_STATE_UI_LABEL_BACO', 6)
enum_smu_state_classification_flag = CEnum(ctypes.c_uint32)
SMU_STATE_CLASSIFICATION_FLAG_BOOT = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_BOOT', 1)
SMU_STATE_CLASSIFICATION_FLAG_THERMAL = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_THERMAL', 2)
SMU_STATE_CLASSIFICATIN_FLAG_LIMITED_POWER_SOURCE = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATIN_FLAG_LIMITED_POWER_SOURCE', 4)
SMU_STATE_CLASSIFICATION_FLAG_RESET = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_RESET', 8)
SMU_STATE_CLASSIFICATION_FLAG_FORCED = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_FORCED', 16)
SMU_STATE_CLASSIFICATION_FLAG_USER_3D_PERFORMANCE = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_USER_3D_PERFORMANCE', 32)
SMU_STATE_CLASSIFICATION_FLAG_USER_2D_PERFORMANCE = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_USER_2D_PERFORMANCE', 64)
SMU_STATE_CLASSIFICATION_FLAG_3D_PERFORMANCE = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_3D_PERFORMANCE', 128)
SMU_STATE_CLASSIFICATION_FLAG_AC_OVERDIRVER_TEMPLATE = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_AC_OVERDIRVER_TEMPLATE', 256)
SMU_STATE_CLASSIFICATION_FLAG_UVD = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_UVD', 512)
SMU_STATE_CLASSIFICATION_FLAG_3D_PERFORMANCE_LOW = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_3D_PERFORMANCE_LOW', 1024)
SMU_STATE_CLASSIFICATION_FLAG_ACPI = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_ACPI', 2048)
SMU_STATE_CLASSIFICATION_FLAG_HD2 = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_HD2', 4096)
SMU_STATE_CLASSIFICATION_FLAG_UVD_HD = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_UVD_HD', 8192)
SMU_STATE_CLASSIFICATION_FLAG_UVD_SD = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_UVD_SD', 16384)
SMU_STATE_CLASSIFICATION_FLAG_USER_DC_PERFORMANCE = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_USER_DC_PERFORMANCE', 32768)
SMU_STATE_CLASSIFICATION_FLAG_DC_OVERDIRVER_TEMPLATE = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_DC_OVERDIRVER_TEMPLATE', 65536)
SMU_STATE_CLASSIFICATION_FLAG_BACO = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_BACO', 131072)
SMU_STATE_CLASSIFICATIN_FLAG_LIMITED_POWER_SOURCE2 = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATIN_FLAG_LIMITED_POWER_SOURCE2', 262144)
SMU_STATE_CLASSIFICATION_FLAG_ULV = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_ULV', 524288)
SMU_STATE_CLASSIFICATION_FLAG_UVD_MVC = enum_smu_state_classification_flag.define('SMU_STATE_CLASSIFICATION_FLAG_UVD_MVC', 1048576)
class struct_smu_state_classification_block(Struct): pass
struct_smu_state_classification_block._fields_ = [
('ui_label', enum_smu_state_ui_label),
('flags', enum_smu_state_classification_flag),
('bios_index', ctypes.c_int32),
('temporary_state', ctypes.c_bool),
('to_be_deleted', ctypes.c_bool),
]
class struct_smu_state_pcie_block(Struct): pass
struct_smu_state_pcie_block._fields_ = [
('lanes', ctypes.c_uint32),
]
enum_smu_refreshrate_source = CEnum(ctypes.c_uint32)
SMU_REFRESHRATE_SOURCE_EDID = enum_smu_refreshrate_source.define('SMU_REFRESHRATE_SOURCE_EDID', 0)
SMU_REFRESHRATE_SOURCE_EXPLICIT = enum_smu_refreshrate_source.define('SMU_REFRESHRATE_SOURCE_EXPLICIT', 1)
class struct_smu_state_display_block(Struct): pass
struct_smu_state_display_block._fields_ = [
('disable_frame_modulation', ctypes.c_bool),
('limit_refreshrate', ctypes.c_bool),
('refreshrate_source', enum_smu_refreshrate_source),
('explicit_refreshrate', ctypes.c_int32),
('edid_refreshrate_index', ctypes.c_int32),
('enable_vari_bright', ctypes.c_bool),
]
class struct_smu_state_memory_block(Struct): pass
struct_smu_state_memory_block._fields_ = [
('dll_off', ctypes.c_bool),
('m3arb', ctypes.c_ubyte),
('unused', (ctypes.c_ubyte * 3)),
]
class struct_smu_state_software_algorithm_block(Struct): pass
struct_smu_state_software_algorithm_block._fields_ = [
('disable_load_balancing', ctypes.c_bool),
('enable_sleep_for_timestamps', ctypes.c_bool),
]
class struct_smu_temperature_range(Struct): pass
struct_smu_temperature_range._fields_ = [
('min', ctypes.c_int32),
('max', ctypes.c_int32),
('edge_emergency_max', ctypes.c_int32),
('hotspot_min', ctypes.c_int32),
('hotspot_crit_max', ctypes.c_int32),
('hotspot_emergency_max', ctypes.c_int32),
('mem_min', ctypes.c_int32),
('mem_crit_max', ctypes.c_int32),
('mem_emergency_max', ctypes.c_int32),
('software_shutdown_temp', ctypes.c_int32),
('software_shutdown_temp_offset', ctypes.c_int32),
]
class struct_smu_state_validation_block(Struct): pass
struct_smu_state_validation_block._fields_ = [
('single_display_only', ctypes.c_bool),
('disallow_on_dc', ctypes.c_bool),
('supported_power_levels', ctypes.c_ubyte),
]
class struct_smu_uvd_clocks(Struct): pass
struct_smu_uvd_clocks._fields_ = [
('vclk', ctypes.c_uint32),
('dclk', ctypes.c_uint32),
]
enum_smu_power_src_type = CEnum(ctypes.c_uint32)
SMU_POWER_SOURCE_AC = enum_smu_power_src_type.define('SMU_POWER_SOURCE_AC', 0)
SMU_POWER_SOURCE_DC = enum_smu_power_src_type.define('SMU_POWER_SOURCE_DC', 1)
SMU_POWER_SOURCE_COUNT = enum_smu_power_src_type.define('SMU_POWER_SOURCE_COUNT', 2)
enum_smu_ppt_limit_type = CEnum(ctypes.c_uint32)
SMU_DEFAULT_PPT_LIMIT = enum_smu_ppt_limit_type.define('SMU_DEFAULT_PPT_LIMIT', 0)
SMU_FAST_PPT_LIMIT = enum_smu_ppt_limit_type.define('SMU_FAST_PPT_LIMIT', 1)
enum_smu_ppt_limit_level = CEnum(ctypes.c_int32)
SMU_PPT_LIMIT_MIN = enum_smu_ppt_limit_level.define('SMU_PPT_LIMIT_MIN', -1)
SMU_PPT_LIMIT_CURRENT = enum_smu_ppt_limit_level.define('SMU_PPT_LIMIT_CURRENT', 0)
SMU_PPT_LIMIT_DEFAULT = enum_smu_ppt_limit_level.define('SMU_PPT_LIMIT_DEFAULT', 1)
SMU_PPT_LIMIT_MAX = enum_smu_ppt_limit_level.define('SMU_PPT_LIMIT_MAX', 2)
enum_smu_memory_pool_size = CEnum(ctypes.c_uint32)
SMU_MEMORY_POOL_SIZE_ZERO = enum_smu_memory_pool_size.define('SMU_MEMORY_POOL_SIZE_ZERO', 0)
SMU_MEMORY_POOL_SIZE_256_MB = enum_smu_memory_pool_size.define('SMU_MEMORY_POOL_SIZE_256_MB', 268435456)
SMU_MEMORY_POOL_SIZE_512_MB = enum_smu_memory_pool_size.define('SMU_MEMORY_POOL_SIZE_512_MB', 536870912)
SMU_MEMORY_POOL_SIZE_1_GB = enum_smu_memory_pool_size.define('SMU_MEMORY_POOL_SIZE_1_GB', 1073741824)
SMU_MEMORY_POOL_SIZE_2_GB = enum_smu_memory_pool_size.define('SMU_MEMORY_POOL_SIZE_2_GB', 2147483648)
enum_smu_clk_type = CEnum(ctypes.c_uint32)
SMU_GFXCLK = enum_smu_clk_type.define('SMU_GFXCLK', 0)
SMU_VCLK = enum_smu_clk_type.define('SMU_VCLK', 1)
SMU_DCLK = enum_smu_clk_type.define('SMU_DCLK', 2)
SMU_VCLK1 = enum_smu_clk_type.define('SMU_VCLK1', 3)
SMU_DCLK1 = enum_smu_clk_type.define('SMU_DCLK1', 4)
SMU_ECLK = enum_smu_clk_type.define('SMU_ECLK', 5)
SMU_SOCCLK = enum_smu_clk_type.define('SMU_SOCCLK', 6)
SMU_UCLK = enum_smu_clk_type.define('SMU_UCLK', 7)
SMU_DCEFCLK = enum_smu_clk_type.define('SMU_DCEFCLK', 8)
SMU_DISPCLK = enum_smu_clk_type.define('SMU_DISPCLK', 9)
SMU_PIXCLK = enum_smu_clk_type.define('SMU_PIXCLK', 10)
SMU_PHYCLK = enum_smu_clk_type.define('SMU_PHYCLK', 11)
SMU_FCLK = enum_smu_clk_type.define('SMU_FCLK', 12)
SMU_SCLK = enum_smu_clk_type.define('SMU_SCLK', 13)
SMU_MCLK = enum_smu_clk_type.define('SMU_MCLK', 14)
SMU_PCIE = enum_smu_clk_type.define('SMU_PCIE', 15)
SMU_LCLK = enum_smu_clk_type.define('SMU_LCLK', 16)
SMU_OD_CCLK = enum_smu_clk_type.define('SMU_OD_CCLK', 17)
SMU_OD_SCLK = enum_smu_clk_type.define('SMU_OD_SCLK', 18)
SMU_OD_MCLK = enum_smu_clk_type.define('SMU_OD_MCLK', 19)
SMU_OD_VDDC_CURVE = enum_smu_clk_type.define('SMU_OD_VDDC_CURVE', 20)
SMU_OD_RANGE = enum_smu_clk_type.define('SMU_OD_RANGE', 21)
SMU_OD_VDDGFX_OFFSET = enum_smu_clk_type.define('SMU_OD_VDDGFX_OFFSET', 22)
SMU_OD_FAN_CURVE = enum_smu_clk_type.define('SMU_OD_FAN_CURVE', 23)
SMU_OD_ACOUSTIC_LIMIT = enum_smu_clk_type.define('SMU_OD_ACOUSTIC_LIMIT', 24)
SMU_OD_ACOUSTIC_TARGET = enum_smu_clk_type.define('SMU_OD_ACOUSTIC_TARGET', 25)
SMU_OD_FAN_TARGET_TEMPERATURE = enum_smu_clk_type.define('SMU_OD_FAN_TARGET_TEMPERATURE', 26)
SMU_OD_FAN_MINIMUM_PWM = enum_smu_clk_type.define('SMU_OD_FAN_MINIMUM_PWM', 27)
SMU_CLK_COUNT = enum_smu_clk_type.define('SMU_CLK_COUNT', 28)
class struct_smu_user_dpm_profile(Struct): pass
struct_smu_user_dpm_profile._fields_ = [
('fan_mode', ctypes.c_uint32),
('power_limit', ctypes.c_uint32),
('fan_speed_pwm', ctypes.c_uint32),
('fan_speed_rpm', ctypes.c_uint32),
('flags', ctypes.c_uint32),
('user_od', ctypes.c_uint32),
('clk_mask', (ctypes.c_uint32 * 28)),
('clk_dependency', ctypes.c_uint32),
]
class struct_smu_table(Struct): pass
class struct_amdgpu_bo(Struct): pass
struct_smu_table._fields_ = [
('size', ctypes.c_uint64),
('align', ctypes.c_uint32),
('domain', ctypes.c_ubyte),
('mc_address', ctypes.c_uint64),
('cpu_addr', ctypes.c_void_p),
('bo', ctypes.POINTER(struct_amdgpu_bo)),
('version', ctypes.c_uint32),
]
enum_smu_perf_level_designation = CEnum(ctypes.c_uint32)
PERF_LEVEL_ACTIVITY = enum_smu_perf_level_designation.define('PERF_LEVEL_ACTIVITY', 0)
PERF_LEVEL_POWER_CONTAINMENT = enum_smu_perf_level_designation.define('PERF_LEVEL_POWER_CONTAINMENT', 1)
class struct_smu_performance_level(Struct): pass
struct_smu_performance_level._fields_ = [
('core_clock', ctypes.c_uint32),
('memory_clock', ctypes.c_uint32),
('vddc', ctypes.c_uint32),
('vddci', ctypes.c_uint32),
('non_local_mem_freq', ctypes.c_uint32),
('non_local_mem_width', ctypes.c_uint32),
]
class struct_smu_clock_info(Struct): pass
struct_smu_clock_info._fields_ = [
('min_mem_clk', ctypes.c_uint32),
('max_mem_clk', ctypes.c_uint32),
('min_eng_clk', ctypes.c_uint32),
('max_eng_clk', ctypes.c_uint32),
('min_bus_bandwidth', ctypes.c_uint32),
('max_bus_bandwidth', ctypes.c_uint32),
]
class struct_smu_bios_boot_up_values(Struct): pass
struct_smu_bios_boot_up_values._fields_ = [
('revision', ctypes.c_uint32),
('gfxclk', ctypes.c_uint32),
('uclk', ctypes.c_uint32),
('socclk', ctypes.c_uint32),
('dcefclk', ctypes.c_uint32),
('eclk', ctypes.c_uint32),
('vclk', ctypes.c_uint32),
('dclk', ctypes.c_uint32),
('vddc', ctypes.c_uint16),
('vddci', ctypes.c_uint16),
('mvddc', ctypes.c_uint16),
('vdd_gfx', ctypes.c_uint16),
('cooling_id', ctypes.c_ubyte),
('pp_table_id', ctypes.c_uint32),
('format_revision', ctypes.c_uint32),
('content_revision', ctypes.c_uint32),
('fclk', ctypes.c_uint32),
('lclk', ctypes.c_uint32),
('firmware_caps', ctypes.c_uint32),
]
enum_smu_table_id = CEnum(ctypes.c_uint32)
SMU_TABLE_PPTABLE = enum_smu_table_id.define('SMU_TABLE_PPTABLE', 0)
SMU_TABLE_WATERMARKS = enum_smu_table_id.define('SMU_TABLE_WATERMARKS', 1)
SMU_TABLE_CUSTOM_DPM = enum_smu_table_id.define('SMU_TABLE_CUSTOM_DPM', 2)
SMU_TABLE_DPMCLOCKS = enum_smu_table_id.define('SMU_TABLE_DPMCLOCKS', 3)
SMU_TABLE_AVFS = enum_smu_table_id.define('SMU_TABLE_AVFS', 4)
SMU_TABLE_AVFS_PSM_DEBUG = enum_smu_table_id.define('SMU_TABLE_AVFS_PSM_DEBUG', 5)
SMU_TABLE_AVFS_FUSE_OVERRIDE = enum_smu_table_id.define('SMU_TABLE_AVFS_FUSE_OVERRIDE', 6)
SMU_TABLE_PMSTATUSLOG = enum_smu_table_id.define('SMU_TABLE_PMSTATUSLOG', 7)
SMU_TABLE_SMU_METRICS = enum_smu_table_id.define('SMU_TABLE_SMU_METRICS', 8)
SMU_TABLE_DRIVER_SMU_CONFIG = enum_smu_table_id.define('SMU_TABLE_DRIVER_SMU_CONFIG', 9)
SMU_TABLE_ACTIVITY_MONITOR_COEFF = enum_smu_table_id.define('SMU_TABLE_ACTIVITY_MONITOR_COEFF', 10)
SMU_TABLE_OVERDRIVE = enum_smu_table_id.define('SMU_TABLE_OVERDRIVE', 11)
SMU_TABLE_I2C_COMMANDS = enum_smu_table_id.define('SMU_TABLE_I2C_COMMANDS', 12)
SMU_TABLE_PACE = enum_smu_table_id.define('SMU_TABLE_PACE', 13)
SMU_TABLE_ECCINFO = enum_smu_table_id.define('SMU_TABLE_ECCINFO', 14)
SMU_TABLE_COMBO_PPTABLE = enum_smu_table_id.define('SMU_TABLE_COMBO_PPTABLE', 15)
SMU_TABLE_WIFIBAND = enum_smu_table_id.define('SMU_TABLE_WIFIBAND', 16)
SMU_TABLE_COUNT = enum_smu_table_id.define('SMU_TABLE_COUNT', 17)
PPSMC_Result_OK = 0x1
PPSMC_Result_Failed = 0xFF
PPSMC_Result_UnknownCmd = 0xFE
PPSMC_Result_CmdRejectedPrereq = 0xFD
PPSMC_Result_CmdRejectedBusy = 0xFC
PPSMC_MSG_TestMessage = 0x1
PPSMC_MSG_GetSmuVersion = 0x2
PPSMC_MSG_GfxDriverReset = 0x3
PPSMC_MSG_GetDriverIfVersion = 0x4
PPSMC_MSG_EnableAllSmuFeatures = 0x5
PPSMC_MSG_DisableAllSmuFeatures = 0x6
PPSMC_MSG_RequestI2cTransaction = 0x7
PPSMC_MSG_GetMetricsVersion = 0x8
PPSMC_MSG_GetMetricsTable = 0x9
PPSMC_MSG_GetEccInfoTable = 0xA
PPSMC_MSG_GetEnabledSmuFeaturesLow = 0xB
PPSMC_MSG_GetEnabledSmuFeaturesHigh = 0xC
PPSMC_MSG_SetDriverDramAddrHigh = 0xD
PPSMC_MSG_SetDriverDramAddrLow = 0xE
PPSMC_MSG_SetToolsDramAddrHigh = 0xF
PPSMC_MSG_SetToolsDramAddrLow = 0x10
PPSMC_MSG_SetSystemVirtualDramAddrHigh = 0x11
PPSMC_MSG_SetSystemVirtualDramAddrLow = 0x12
PPSMC_MSG_SetSoftMinByFreq = 0x13
PPSMC_MSG_SetSoftMaxByFreq = 0x14
PPSMC_MSG_GetMinDpmFreq = 0x15
PPSMC_MSG_GetMaxDpmFreq = 0x16
PPSMC_MSG_GetDpmFreqByIndex = 0x17
PPSMC_MSG_SetPptLimit = 0x18
PPSMC_MSG_GetPptLimit = 0x19
PPSMC_MSG_DramLogSetDramAddrHigh = 0x1A
PPSMC_MSG_DramLogSetDramAddrLow = 0x1B
PPSMC_MSG_DramLogSetDramSize = 0x1C
PPSMC_MSG_GetDebugData = 0x1D
PPSMC_MSG_HeavySBR = 0x1E
PPSMC_MSG_SetNumBadHbmPagesRetired = 0x1F
PPSMC_MSG_DFCstateControl = 0x20
PPSMC_MSG_GetGmiPwrDnHyst = 0x21
PPSMC_MSG_SetGmiPwrDnHyst = 0x22
PPSMC_MSG_GmiPwrDnControl = 0x23
PPSMC_MSG_EnterGfxoff = 0x24
PPSMC_MSG_ExitGfxoff = 0x25
PPSMC_MSG_EnableDeterminism = 0x26
PPSMC_MSG_DisableDeterminism = 0x27
PPSMC_MSG_DumpSTBtoDram = 0x28
PPSMC_MSG_STBtoDramLogSetDramAddrHigh = 0x29
PPSMC_MSG_STBtoDramLogSetDramAddrLow = 0x2A
PPSMC_MSG_STBtoDramLogSetDramSize = 0x2B
PPSMC_MSG_SetSystemVirtualSTBtoDramAddrHigh = 0x2C
PPSMC_MSG_SetSystemVirtualSTBtoDramAddrLow = 0x2D
PPSMC_MSG_GfxDriverResetRecovery = 0x2E
PPSMC_MSG_TriggerVFFLR = 0x2F
PPSMC_MSG_SetSoftMinGfxClk = 0x30
PPSMC_MSG_SetSoftMaxGfxClk = 0x31
PPSMC_MSG_GetMinGfxDpmFreq = 0x32
PPSMC_MSG_GetMaxGfxDpmFreq = 0x33
PPSMC_MSG_PrepareForDriverUnload = 0x34
PPSMC_MSG_ReadThrottlerLimit = 0x35
PPSMC_MSG_QueryValidMcaCount = 0x36
PPSMC_MSG_McaBankDumpDW = 0x37
PPSMC_MSG_GetCTFLimit = 0x38
PPSMC_MSG_ClearMcaOnRead = 0x39
PPSMC_MSG_QueryValidMcaCeCount = 0x3A
PPSMC_MSG_McaBankCeDumpDW = 0x3B
PPSMC_MSG_SelectPLPDMode = 0x40
PPSMC_MSG_RmaDueToBadPageThreshold = 0x43
PPSMC_MSG_SelectPstatePolicy = 0x44
PPSMC_MSG_SetPhsDetWRbwThreshold = 0x45
PPSMC_MSG_SetPhsDetWRbwFreqHigh = 0x46
PPSMC_MSG_SetPhsDetWRbwFreqLow = 0x47
PPSMC_MSG_SetPhsDetWRbwHystDown = 0x48
PPSMC_MSG_SetPhsDetWRbwAlpha = 0x49
PPSMC_MSG_SetPhsDetOnOff = 0x4A
PPSMC_MSG_GetPhsDetResidency = 0x4B
PPSMC_Message_Count = 0x4C
PPSMC_RESET_TYPE_DRIVER_MODE_1_RESET = 0x1
PPSMC_RESET_TYPE_DRIVER_MODE_2_RESET = 0x2
PPSMC_RESET_TYPE_DRIVER_MODE_3_RESET = 0x3
PPSMC_THROTTLING_LIMIT_TYPE_SOCKET = 0x1
PPSMC_THROTTLING_LIMIT_TYPE_HBM = 0x2
PPSMC_AID_THM_TYPE = 0x1
PPSMC_CCD_THM_TYPE = 0x2
PPSMC_XCD_THM_TYPE = 0x3
PPSMC_HBM_THM_TYPE = 0x4
PPSMC_PLPD_MODE_DEFAULT = 0x1
PPSMC_PLPD_MODE_OPTIMIZED = 0x2
SMU13_0_6_DRIVER_IF_VERSION = 0x08042024
NUM_I2C_CONTROLLERS = 8
I2C_CONTROLLER_ENABLED = 1
I2C_CONTROLLER_DISABLED = 0
MAX_SW_I2C_COMMANDS = 24
CMDCONFIG_STOP_BIT = 0
CMDCONFIG_RESTART_BIT = 1
CMDCONFIG_READWRITE_BIT = 2
CMDCONFIG_STOP_MASK = (1 << CMDCONFIG_STOP_BIT)
CMDCONFIG_RESTART_MASK = (1 << CMDCONFIG_RESTART_BIT)
CMDCONFIG_READWRITE_MASK = (1 << CMDCONFIG_READWRITE_BIT)
IH_INTERRUPT_ID_TO_DRIVER = 0xFE
IH_INTERRUPT_CONTEXT_ID_THERMAL_THROTTLING = 0x7
THROTTLER_PROCHOT_BIT = 0
THROTTLER_PPT_BIT = 1
THROTTLER_THERMAL_SOCKET_BIT = 2
THROTTLER_THERMAL_VR_BIT = 3
THROTTLER_THERMAL_HBM_BIT = 4
ClearMcaOnRead_UE_FLAG_MASK = 0x1
ClearMcaOnRead_CE_POLL_MASK = 0x2
int32_t = int
SMU_THERMAL_MINIMUM_ALERT_TEMP = 0
SMU_THERMAL_MAXIMUM_ALERT_TEMP = 255
SMU_TEMPERATURE_UNITS_PER_CENTIGRADES = 1000
SMU_FW_NAME_LEN = 0x24
SMU_DPM_USER_PROFILE_RESTORE = (1 << 0)
SMU_CUSTOM_FAN_SPEED_RPM = (1 << 1)
SMU_CUSTOM_FAN_SPEED_PWM = (1 << 2)
SMU_THROTTLER_PPT0_BIT = 0
SMU_THROTTLER_PPT1_BIT = 1
SMU_THROTTLER_PPT2_BIT = 2
SMU_THROTTLER_PPT3_BIT = 3
SMU_THROTTLER_SPL_BIT = 4
SMU_THROTTLER_FPPT_BIT = 5
SMU_THROTTLER_SPPT_BIT = 6
SMU_THROTTLER_SPPT_APU_BIT = 7
SMU_THROTTLER_TDC_GFX_BIT = 16
SMU_THROTTLER_TDC_SOC_BIT = 17
SMU_THROTTLER_TDC_MEM_BIT = 18
SMU_THROTTLER_TDC_VDD_BIT = 19
SMU_THROTTLER_TDC_CVIP_BIT = 20
SMU_THROTTLER_EDC_CPU_BIT = 21
SMU_THROTTLER_EDC_GFX_BIT = 22
SMU_THROTTLER_APCC_BIT = 23
SMU_THROTTLER_TEMP_GPU_BIT = 32
SMU_THROTTLER_TEMP_CORE_BIT = 33
SMU_THROTTLER_TEMP_MEM_BIT = 34
SMU_THROTTLER_TEMP_EDGE_BIT = 35
SMU_THROTTLER_TEMP_HOTSPOT_BIT = 36
SMU_THROTTLER_TEMP_SOC_BIT = 37
SMU_THROTTLER_TEMP_VR_GFX_BIT = 38
SMU_THROTTLER_TEMP_VR_SOC_BIT = 39
SMU_THROTTLER_TEMP_VR_MEM0_BIT = 40
SMU_THROTTLER_TEMP_VR_MEM1_BIT = 41
SMU_THROTTLER_TEMP_LIQUID0_BIT = 42
SMU_THROTTLER_TEMP_LIQUID1_BIT = 43
SMU_THROTTLER_VRHOT0_BIT = 44
SMU_THROTTLER_VRHOT1_BIT = 45
SMU_THROTTLER_PROCHOT_CPU_BIT = 46
SMU_THROTTLER_PROCHOT_GFX_BIT = 47
SMU_THROTTLER_PPM_BIT = 56
SMU_THROTTLER_FIT_BIT = 57
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
class struct_SMU14_Firmware_Footer(Struct): pass
uint32_t = ctypes.c_uint32
struct_SMU14_Firmware_Footer._packed_ = True
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
class rocr_AMD_SDMA_PKT_COPY_LINEAR_TAG(Struct): pass
class rocr_AMD_SDMA_PKT_COPY_LINEAR_TAG_HEADER_UNION(ctypes.Union): pass
class rocr_AMD_SDMA_PKT_COPY_LINEAR_TAG_HEADER_UNION_0(Struct): pass
+4 -13
View File
@@ -1,17 +1,8 @@
# mypy: ignore-errors
import ctypes, os
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
def dll():
try: return ctypes.CDLL(unwrap(os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libamd_comgr.so'))
except: pass
try: return ctypes.CDLL(unwrap('/usr/local/lib/libamd_comgr.dylib'))
except: pass
try: return ctypes.CDLL(unwrap('/opt/homebrew/lib/libamd_comgr.dylib'))
except: pass
return None
dll = dll()
import ctypes
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
import os
dll = DLL('comgr', [os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libamd_comgr.so', 'amd_comgr'])
amd_comgr_status_s = CEnum(ctypes.c_uint32)
AMD_COMGR_STATUS_SUCCESS = amd_comgr_status_s.define('AMD_COMGR_STATUS_SUCCESS', 0)
AMD_COMGR_STATUS_ERROR = amd_comgr_status_s.define('AMD_COMGR_STATUS_ERROR', 1)
+4 -13
View File
@@ -1,17 +1,8 @@
# mypy: ignore-errors
import ctypes, os
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
def dll():
try: return ctypes.CDLL(unwrap(os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libamd_comgr.so'))
except: pass
try: return ctypes.CDLL(unwrap('/usr/local/lib/libamd_comgr.dylib'))
except: pass
try: return ctypes.CDLL(unwrap('/opt/homebrew/lib/libamd_comgr.dylib'))
except: pass
return None
dll = dll()
import ctypes
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
import os
dll = DLL('comgr_3', [os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libamd_comgr.so', 'amd_comgr'])
amd_comgr_status_s = CEnum(ctypes.c_uint32)
AMD_COMGR_STATUS_SUCCESS = amd_comgr_status_s.define('AMD_COMGR_STATUS_SUCCESS', 0)
AMD_COMGR_STATUS_ERROR = amd_comgr_status_s.define('AMD_COMGR_STATUS_ERROR', 1)
+2 -9
View File
@@ -1,14 +1,7 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from ctypes.util import find_library
def dll():
try: return ctypes.CDLL(unwrap(find_library('cuda')))
except: pass
return None
dll = dll()
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
dll = DLL('cuda', 'cuda')
cuuint32_t = ctypes.c_uint32
cuuint64_t = ctypes.c_uint64
CUdeviceptr_v2 = ctypes.c_uint64
+4 -9
View File
@@ -1,13 +1,8 @@
# mypy: ignore-errors
import ctypes, os
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
def dll():
try: return ctypes.CDLL(unwrap(os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libamdhip64.so'))
except: pass
return None
dll = dll()
import ctypes
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
import os
dll = DLL('hip', os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libamdhip64.so')
hipError_t = CEnum(ctypes.c_uint32)
hipSuccess = hipError_t.define('hipSuccess', 0)
hipErrorInvalidValue = hipError_t.define('hipErrorInvalidValue', 1)
+4 -12
View File
@@ -1,16 +1,8 @@
# mypy: ignore-errors
import ctypes, os
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from ctypes.util import find_library
def dll():
try: return ctypes.CDLL(unwrap(os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libhsa-runtime64.so'))
except: pass
try: return ctypes.CDLL(unwrap(find_library('hsa-runtime64')))
except: pass
return None
dll = dll()
import ctypes
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
import os
dll = DLL('hsa', [os.getenv('ROCM_PATH', '/opt/rocm')+'/lib/libhsa-runtime64.so', 'hsa-runtime64'])
enum_SQ_RSRC_BUF_TYPE = CEnum(ctypes.c_uint32)
SQ_RSRC_BUF = enum_SQ_RSRC_BUF_TYPE.define('SQ_RSRC_BUF', 0)
SQ_RSRC_BUF_RSVD_1 = enum_SQ_RSRC_BUF_TYPE.define('SQ_RSRC_BUF_RSVD_1', 1)
+2 -8
View File
@@ -1,13 +1,7 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
def dll():
try: return ctypes.CDLL(unwrap(ibverbs), use_errno=True)
except: pass
return None
dll = dll()
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
dll = DLL('ib', 'ibverbs', use_errno=True)
class union_ibv_gid(ctypes.Union): pass
uint8_t = ctypes.c_ubyte
class union_ibv_gid_global(Struct): pass
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
class struct_io_uring_sq(Struct): pass
class struct_io_uring_sqe(Struct): pass
__u8 = ctypes.c_ubyte
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
class struct_kfd_ioctl_get_version_args(Struct): pass
__u32 = ctypes.c_uint32
struct_kfd_ioctl_get_version_args._fields_ = [
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
enum_kgsl_user_mem_type = CEnum(ctypes.c_uint32)
KGSL_USER_MEM_TYPE_PMEM = enum_kgsl_user_mem_type.define('KGSL_USER_MEM_TYPE_PMEM', 0)
KGSL_USER_MEM_TYPE_ASHMEM = enum_kgsl_user_mem_type.define('KGSL_USER_MEM_TYPE_ASHMEM', 1)
+2 -9
View File
@@ -1,14 +1,7 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from ctypes.util import find_library
def dll():
try: return ctypes.CDLL(unwrap(find_library('c')), use_errno=True)
except: pass
return None
dll = dll()
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
dll = DLL('libc', 'c', use_errno=True)
off_t = ctypes.c_int64
mode_t = ctypes.c_uint32
size_t = ctypes.c_uint64
+3 -10
View File
@@ -1,14 +1,7 @@
# mypy: ignore-errors
import ctypes, os
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from ctypes.util import find_library
def dll():
try: return ctypes.CDLL(unwrap(os.getenv('LIBCLANG_PATH', find_library('clang-20'))))
except: pass
return None
dll = dll()
import ctypes
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
dll = DLL('libclang', 'clang-20')
CXIndex = ctypes.c_void_p
class struct_CXTargetInfoImpl(Struct): pass
CXTargetInfo = ctypes.POINTER(struct_CXTargetInfoImpl)
+3 -10
View File
@@ -1,14 +1,7 @@
# mypy: ignore-errors
import ctypes, os
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from ctypes.util import find_library
def dll():
try: return ctypes.CDLL(unwrap(os.getenv('LIBUSB_PATH', find_library('usb-1.0'))))
except: pass
return None
dll = dll()
import ctypes
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
dll = DLL('libusb', 'usb-1.0')
enum_libusb_class_code = CEnum(ctypes.c_uint32)
LIBUSB_CLASS_PER_INTERFACE = enum_libusb_class_code.define('LIBUSB_CLASS_PER_INTERFACE', 0)
LIBUSB_CLASS_AUDIO = enum_libusb_class_code.define('LIBUSB_CLASS_AUDIO', 1)
+3 -9
View File
@@ -1,14 +1,8 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.llvm import LLVM_PATH
def dll():
try: return ctypes.CDLL(unwrap(LLVM_PATH))
except: pass
return None
dll = dll()
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.helpers import WIN, OSX
dll = DLL('llvm', 'C:\\Program Files\\LLVM\\bin\\LLVM-C.dll' if WIN else '/opt/homebrew/opt/llvm@20/lib/libLLVM.dylib' if OSX else ['LLVM', 'LLVM-21', 'LLVM-20', 'LLVM-19', 'LLVM-18', 'LLVM-17', 'LLVM-16', 'LLVM-15', 'LLVM-14'])
intmax_t = ctypes.c_int64
try: (imaxabs:=dll.imaxabs).restype, imaxabs.argtypes = intmax_t, [intmax_t]
except AttributeError: pass
+3 -19
View File
@@ -1,24 +1,8 @@
# mypy: ignore-errors
import ctypes, os
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
import ctypes
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
import gzip, base64
from tinygrad.helpers import OSX
from ctypes.util import find_library
def dll():
try: return ctypes.CDLL(unwrap(find_library('tinymesa_cpu')))
except: pass
try: return ctypes.CDLL(unwrap((BASE:=os.getenv('MESA_PATH', f"/usr{'/local/' if OSX else '/'}lib"))+'/libtinymesa_cpu'+(EXT:='.dylib' if OSX else '.so')))
except: pass
try: return ctypes.CDLL(unwrap(f'{BASE}/libtinymesa{EXT}'))
except: pass
try: return ctypes.CDLL(unwrap('/opt/homebrew/lib/libtinymesa_cpu.dylib'))
except: pass
try: return ctypes.CDLL(unwrap('/opt/homebrew/lib/libtinymesa.dylib'))
except: pass
return None
dll = dll()
dll = DLL('mesa', ['tinymesa_cpu', 'tinymesa'])
class struct_u_printf_info(Struct): pass
u_printf_info = struct_u_printf_info
uint32_t = ctypes.c_uint32
+2 -9
View File
@@ -1,15 +1,8 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from ctypes.util import find_library
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support import objc
def dll():
try: return ctypes.CDLL(unwrap(find_library('Metal')))
except: pass
return None
dll = dll()
dll = DLL('metal', 'Metal')
class MTLDispatchThreadgroupsIndirectArguments(Struct): pass
uint32_t = ctypes.c_uint32
MTLDispatchThreadgroupsIndirectArguments._fields_ = [
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
class MCTP_HEADER(Struct): pass
NvU32 = ctypes.c_uint32
NvU8 = ctypes.c_ubyte
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
_anonenum0 = CEnum(ctypes.c_uint32)
AES128_NONE = _anonenum0.define('AES128_NONE', 0)
AES128_CTR = _anonenum0.define('AES128_CTR', 1)
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
_anonenum0 = CEnum(ctypes.c_uint32)
AES128_NONE = _anonenum0.define('AES128_NONE', 0)
AES128_CTR = _anonenum0.define('AES128_CTR', 1)
+3 -9
View File
@@ -1,14 +1,8 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from ctypes.util import find_library
def dll():
try: return ctypes.CDLL(unwrap(find_library('nvJitLink')))
except: pass
return None
dll = dll()
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
import sysconfig
dll = DLL('nvjitlink', 'nvJitLink', f'/usr/local/cuda/targets/{sysconfig.get_config_var("MULTIARCH").rsplit("-", 1)[0]}/lib')
nvJitLinkResult = CEnum(ctypes.c_uint32)
NVJITLINK_SUCCESS = nvJitLinkResult.define('NVJITLINK_SUCCESS', 0)
NVJITLINK_ERROR_UNRECOGNIZED_OPTION = nvJitLinkResult.define('NVJITLINK_ERROR_UNRECOGNIZED_OPTION', 1)
+3 -9
View File
@@ -1,14 +1,8 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from ctypes.util import find_library
def dll():
try: return ctypes.CDLL(unwrap(find_library('nvrtc')))
except: pass
return None
dll = dll()
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
import sysconfig
dll = DLL('nvrtc', 'nvrtc', f'/usr/local/cuda/targets/{sysconfig.get_config_var("MULTIARCH").rsplit("-", 1)[0]}/lib')
nvrtcResult = CEnum(ctypes.c_uint32)
NVRTC_SUCCESS = nvrtcResult.define('NVRTC_SUCCESS', 0)
NVRTC_ERROR_OUT_OF_MEMORY = nvrtcResult.define('NVRTC_ERROR_OUT_OF_MEMORY', 1)
+2 -9
View File
@@ -1,14 +1,7 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from ctypes.util import find_library
def dll():
try: return ctypes.CDLL(unwrap(find_library('OpenCL')))
except: pass
return None
dll = dll()
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
dll = DLL('opencl', 'OpenCL')
class struct__cl_platform_id(Struct): pass
cl_platform_id = ctypes.POINTER(struct__cl_platform_id)
class struct__cl_device_id(Struct): pass
+1 -3
View File
@@ -1,8 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
PCI_CFG_SPACE_SIZE = 256
PCI_CFG_SPACE_EXP_SIZE = 4096
PCI_STD_HEADER_SIZEOF = 64
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
ion_user_handle_t = ctypes.c_int32
enum_ion_heap_type = CEnum(ctypes.c_uint32)
ION_HEAP_TYPE_SYSTEM = enum_ion_heap_type.define('ION_HEAP_TYPE_SYSTEM', 0)
+2 -13
View File
@@ -1,18 +1,7 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from ctypes.util import find_library
def dll():
try: return ctypes.CDLL(unwrap(find_library('rocprof-trace-decoder')))
except: pass
try: return ctypes.CDLL(unwrap('/usr/local/lib/rocprof-trace-decoder.so'))
except: pass
try: return ctypes.CDLL(unwrap('/usr/local/lib/rocprof-trace-decoder.dylib'))
except: pass
return None
dll = dll()
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
dll = DLL('rocprof', ['rocprof-trace-decoder', p:='/usr/local/lib/rocprof-trace-decoder.so', p.replace('so','dylib')])
rocprofiler_thread_trace_decoder_status_t = CEnum(ctypes.c_uint32)
ROCPROFILER_THREAD_TRACE_DECODER_STATUS_SUCCESS = rocprofiler_thread_trace_decoder_status_t.define('ROCPROFILER_THREAD_TRACE_DECODER_STATUS_SUCCESS', 0)
ROCPROFILER_THREAD_TRACE_DECODER_STATUS_ERROR = rocprofiler_thread_trace_decoder_status_t.define('ROCPROFILER_THREAD_TRACE_DECODER_STATUS_ERROR', 1)
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
class struct_sqtt_data_info(Struct): pass
uint32_t = ctypes.c_uint32
class struct_sqtt_data_info_0(ctypes.Union): pass
+1 -2
View File
@@ -1,7 +1,6 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
class struct_vfio_info_cap_header(Struct): pass
__u16 = ctypes.c_uint16
__u32 = ctypes.c_uint32
+4 -9
View File
@@ -1,14 +1,9 @@
# mypy: ignore-errors
import ctypes
from tinygrad.helpers import unwrap
from tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support.webgpu import WEBGPU_PATH
def dll():
try: return ctypes.CDLL(unwrap(WEBGPU_PATH))
except: pass
return None
dll = dll()
from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR
from tinygrad.helpers import WIN, OSX
import sysconfig, os
dll = DLL('webgpu', os.path.join(sysconfig.get_paths()['purelib'], 'pydawn', 'lib', 'libwebgpu_dawn.dll') if WIN else 'webgpu_dawn')
WGPUFlags = ctypes.c_uint64
WGPUBool = ctypes.c_uint32
class struct_WGPUAdapterImpl(Struct): pass
-113
View File
@@ -1,113 +0,0 @@
import time, itertools
from tinygrad.engine.jit import MultiGraphRunner
from tinygrad.engine.realize import CompiledRunner, BufferXfer, ExecItem
from tinygrad.device import Device, Compiled, Buffer
from tinygrad.runtime.ops_remote import RemoteDevice, RemoteConnection, RemoteRequest, GraphComputeItem, Transfer, GraphAlloc, GraphFree, GraphExec
from tinygrad.runtime.ops_remote import BatchTransfer, Event, Wait
from tinygrad.helpers import unwrap, flatten, dedup
from enum import Enum, auto
from dataclasses import replace
from collections import defaultdict
from typing import cast
class StagingType(Enum): NONE = auto(); GRAPH = auto(); TRANSFER = auto() # noqa: E702
def rd(dev:Compiled) -> RemoteDevice: return cast(RemoteDevice, dev)
def dev_key(dev:RemoteDevice): return dev.conn if dev.properties.graph_supports_multi else dev
def map_rawbuf(rawbuf:Buffer): return (cast(RemoteDevice, Device[rawbuf.device]).session, rawbuf._buf)
class RemoteGraph(MultiGraphRunner):
def __init__(self, jit_cache: list[ExecItem], rawbufs: list[Buffer], var_vals: dict[str, int]):
super().__init__(jit_cache, rawbufs, var_vals)
devices = dedup(flatten([[Device[unwrap(buf).device] for buf in ji.bufs] for ji in jit_cache]))
c2d = {device.conn: device for device in devices}
self.handle_indexes = {map_rawbuf(rawbufs[i]): i for i in sorted(dedup(self.input_replace.values()))}
self.template: list[RemoteRequest] = []
stagings: dict[RemoteDevice|RemoteConnection, list[GraphComputeItem|Transfer]] = defaultdict(list)
clobbered_buffers: set[Buffer] = set()
cur_staging_type: StagingType = StagingType.NONE
def _flush(new_staging_type:StagingType, force_break:bool=False):
nonlocal cur_staging_type
if cur_staging_type == new_staging_type and not force_break: return
# Pre-sync
if cur_staging_type == StagingType.TRANSFER:
for sdev,ddev in itertools.permutations(c2d.values(), 2):
self.template.append(Event(ddev.session, event:=next(ddev.event_num), session=sdev.session))
self.template.append(Wait(event, session=ddev.session))
# Flush
for dev in devices:
dk = dev_key(dev)
staging = stagings[dk]
if not staging: continue
match cur_staging_type:
case StagingType.GRAPH:
bufs = tuple(map_rawbuf(rawbufs[i]) for i in sorted(dedup(self.input_replace.values())) if dev_key(rd(Device[rawbufs[i].device])) == dk)
dev.q(GraphAlloc(graph_num:=next(dev.graph_num), tuple(staging), tuple(bufs), var_vals))
self.template.append(GraphExec(graph_num, bufs, var_vals, wait=False, session=dev.session))
case StagingType.TRANSFER:
st = cast(list[Transfer], staging)
for host in dedup(t.dsession.host for t in st):
sbuffer_nums = [(unwrap(t.session), t.buffer_num) for t in st if t.dsession.host == host]
dbuffer_nums = [(t.dsession, t.dbuffer_num) for t in st if t.dsession.host == host]
self.template.append(BatchTransfer(sbuffer_nums, dbuffer_nums, session=dev.session))
staging.clear()
# Post-sync
if cur_staging_type == StagingType.TRANSFER:
for sdev,ddev in itertools.permutations(c2d.values(), 2):
self.template.append(Event(ddev.session, event:=next(ddev.event_num), session=sdev.session))
self.template.append(Wait(event, session=ddev.session))
cur_staging_type = new_staging_type
clobbered_buffers.clear()
for ji in jit_cache:
match ji.prg:
case CompiledRunner():
_flush(StagingType.GRAPH)
gi = GraphComputeItem(ji.prg.dev.session, ji.prg._prg.name, ji.prg._prg.datahash, tuple(unwrap(buf)._buf for buf in ji.bufs),
tuple(ji.prg.p.vars), ji.fixedvars, tuple(ji.prg.p.ins), tuple(ji.prg.p.outs),
tuple(ji.prg.p.global_size) if ji.prg.p.global_size is not None else None,
tuple(ji.prg.p.local_size) if ji.prg.p.local_size is not None else None)
stagings[dev_key(ji.prg.dev)].append(gi)
case BufferXfer():
dest, src = ji.bufs[0:2]
dest_dev, src_dev = cast(RemoteDevice, Device[unwrap(dest).device]), cast(RemoteDevice, Device[unwrap(src).device])
assert dest is not None and src is not None, ji
ti = Transfer(session=src_dev.session, buffer_num=src._buf, dsession=dest_dev.session, dbuffer_num=dest._buf)
if dev_key(dest_dev) == dev_key(src_dev):
_flush(StagingType.GRAPH)
stagings[dev_key(src_dev)].append(ti)
elif dest_dev.conn == src_dev.conn:
_flush(StagingType.NONE)
self.template.append(ti)
else:
_flush(StagingType.TRANSFER, force_break=src in clobbered_buffers)
clobbered_buffers.add(dest)
stagings[dev_key(src_dev)].append(ti)
case _: raise NotImplementedError(ji.prg)
_flush(StagingType.NONE)
def __del__(self):
for req in self.template:
match req:
case GraphExec(): RemoteConnection(unwrap(req.session).host).q(GraphFree(req.graph_num, session=req.session))
def __call__(self, rawbufs: list[Buffer], var_vals: dict[str, int], wait=False):
if wait: st = time.perf_counter()
rmap = {orig: map_rawbuf(rawbufs[replace_idx]) for orig,replace_idx in self.handle_indexes.items()}
for req in self.template:
match req:
case GraphExec():
req = replace(req, bufs=tuple(rmap[buf] for buf in req.bufs), var_vals=var_vals, wait=wait)
case Transfer():
if (req.session, req.buffer_num) in rmap: req = replace(req, buffer_num=rmap[(req.session, req.buffer_num)][1])
if (req.dsession, req.dbuffer_num) in rmap: req = replace(req, dbuffer_num=rmap[(req.dsession, req.dbuffer_num)][1])
case BatchTransfer():
req = replace(req, sbuffer_nums=[rmap.get(b, b) for b in req.sbuffer_nums], dbuffer_nums=[rmap.get(b, b) for b in req.dbuffer_nums])
case Event()|Wait():
pass # event number can be reused
case _: raise NotImplementedError(req)
RemoteConnection(unwrap(req.session).host).q(req)
if wait:
RemoteConnection(unwrap(req.session).host).batch_submit()
return time.perf_counter() - st
+18 -10
View File
@@ -796,7 +796,7 @@ class PCIIface(PCIIfaceBase):
gpus:ClassVar[list[str]] = []
def __init__(self, dev, dev_id):
super().__init__(dev, dev_id, vendor=0x1002, devices=[(0xffff, [0x744c, 0x7480, 0x7550, 0x7590])], bars=[0, 2, 5], vram_bar=0,
super().__init__(dev, dev_id, vendor=0x1002, devices=[(0xffff, [0x74a1, 0x744c, 0x7480, 0x7550, 0x7590])], bars=[0, 2, 5], vram_bar=0,
va_start=AMMemoryManager.va_allocator.base, va_size=AMMemoryManager.va_allocator.size)
self._setup_adev(self.pci_dev)
self.pci_dev.write_config(pci.PCI_COMMAND, self.pci_dev.read_config(pci.PCI_COMMAND, 2) | pci.PCI_COMMAND_MASTER, 2)
@@ -808,25 +808,32 @@ class PCIIface(PCIIfaceBase):
self.ip_versions = self.dev_impl.ip_ver
gfxver = int(f"{self.dev_impl.ip_ver[am.GC_HWIP][0]:02d}{self.dev_impl.ip_ver[am.GC_HWIP][1]:02d}{self.dev_impl.ip_ver[am.GC_HWIP][2]:02d}")
array_count = self.dev_impl.gc_info.gc_num_sa_per_se * self.dev_impl.gc_info.gc_num_se
self.props = {'cu_per_simd_array': (cu_per_sa:=2 * (self.dev_impl.gc_info.gc_num_wgp0_per_sa + self.dev_impl.gc_info.gc_num_wgp1_per_sa)),
'simd_count': 2 * cu_per_sa * array_count, 'simd_per_cu': 2, 'array_count': array_count, 'gfx_target_version': gfxver,
if self.dev_impl.gc_info.header.version_major == 2:
cu_per_sa = self.dev_impl.gc_info.gc_num_cu_per_sh
max_sh_per_se = self.dev_impl.gc_info.gc_num_sh_per_se
else:
cu_per_sa = 2 * (self.dev_impl.gc_info.gc_num_wgp0_per_sa + self.dev_impl.gc_info.gc_num_wgp1_per_sa)
max_sh_per_se = self.dev_impl.gc_info.gc_num_sa_per_se
array_count = max_sh_per_se * self.dev_impl.gc_info.gc_num_se * self.dev_impl.gfx.xccs
self.props = {'cu_per_simd_array': cu_per_sa, 'simd_count': 2 * cu_per_sa * array_count, 'simd_per_cu': 2, 'array_count': array_count,
'max_slots_scratch_cu': self.dev_impl.gc_info.gc_max_scratch_slots_per_cu, 'max_waves_per_simd': self.dev_impl.gc_info.gc_max_waves_per_simd,
'simd_arrays_per_engine': self.dev_impl.gc_info.gc_num_sa_per_se, 'lds_size_in_kb': self.dev_impl.gc_info.gc_lds_size}
'simd_arrays_per_engine': max_sh_per_se, 'lds_size_in_kb': self.dev_impl.gc_info.gc_lds_size, 'num_xcc': self.dev_impl.gfx.xccs,
'gfx_target_version': {90403: 90402}.get(gfxver, gfxver)}
def create_queue(self, queue_type, ring, gart, rptr, wptr, eop_buffer=None, cwsr_buffer=None, ctl_stack_size=0, ctx_save_restore_size=0, xcc_id=0):
assert cwsr_buffer is None, "no cwsr buffer for am"
if queue_type == kfd.KFD_IOC_QUEUE_TYPE_SDMA:
self.dev_impl.sdma.setup_ring(ring_addr=ring.va_addr, ring_size=ring.size, rptr_addr=gart.va_addr+rptr, wptr_addr=gart.va_addr+wptr,
pv = self.dev_impl.sdma.setup_ring(ring_addr=ring.va_addr, ring_size=ring.size, rptr_addr=gart.va_addr+rptr, wptr_addr=gart.va_addr+wptr,
doorbell=(doorbell_index:=am.AMDGPU_NAVI10_DOORBELL_sDMA_ENGINE0), pipe=0, queue=0)
else:
self.dev_impl.gfx.setup_ring(ring_addr=ring.va_addr, ring_size=ring.size, rptr_addr=gart.va_addr+rptr, wptr_addr=gart.va_addr+wptr,
pv = self.dev_impl.gfx.setup_ring(ring_addr=ring.va_addr, ring_size=ring.size, rptr_addr=gart.va_addr+rptr, wptr_addr=gart.va_addr+wptr,
eop_addr=eop_buffer.va_addr, eop_size=eop_buffer.size, doorbell=(doorbell_index:=am.AMDGPU_NAVI10_DOORBELL_MEC_RING0), pipe=0, queue=0,
aql=(queue_type==kfd.KFD_IOC_QUEUE_TYPE_COMPUTE_AQL))
return AMDQueueDesc(ring=ring.cpu_view().view(fmt='I'), doorbells=[self.dev_impl.doorbell64.view(doorbell_index * 8, 8, fmt='Q')],
read_ptrs=[gart.cpu_view().view(offset=rptr, size=8, fmt='Q')], write_ptrs=[gart.cpu_view().view(offset=wptr, size=8, fmt='Q')])
read_ptrs=[gart.cpu_view().view(offset=rptr, size=8, fmt='Q')], write_ptrs=[gart.cpu_view().view(offset=wptr, size=8, fmt='Q')], put_value=pv)
def sleep(self, timeout):
if hasattr(self.pci_dev, 'irq_poller') and self.pci_dev.irq_poller is not None and (events_cnt:=len(self.pci_dev.irq_poller.poll(timeout))):
@@ -944,7 +951,8 @@ class AMDDevice(HCQCompiled):
self.pmc_counters = import_pmc(self.target)
# validate counters
pmc_default = "TCC_HIT,TCC_MISS,SQ_LDS_BANK_CONFLICT" if self.target[0] == 9 else "GL2C_HIT,GL2C_MISS,SQC_LDS_IDX_ACTIVE,SQC_LDS_BANK_CONFLICT"
pmc_default = "TCC_HIT,TCC_MISS,SQ_LDS_IDX_ACTIVE,SQ_LDS_BANK_CONFLICT" if self.target[0] == 9 \
else "GL2C_HIT,GL2C_MISS,SQC_LDS_IDX_ACTIVE,SQC_LDS_BANK_CONFLICT"
for k in (PMC_COUNTERS:=getenv("PMC_COUNTERS", pmc_default).split(",")):
if k not in self.pmc_counters: raise RuntimeError(f"PMC counter {k} is not supported. Available: {','.join(self.pmc_counters.keys())}")
@@ -974,7 +982,7 @@ class AMDDevice(HCQCompiled):
gart.cpu_view().view(fmt='B')[:ctypes.sizeof(aql_desc)] = bytes(aql_desc)
self.aql_desc = hsa.amd_queue_t.from_address(gart.cpu_view().addr)
cwsr_buffer_size = round_up((ctx_save_restore_size + debug_memory_size) * self.iface.props.get('num_xcc', 1), mmap.PAGESIZE)
cwsr_buffer_size = round_up((ctx_save_restore_size + debug_memory_size) * self.xccs, mmap.PAGESIZE)
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
-491
View File
@@ -1,491 +0,0 @@
# the REMOTE=1 device is a process boundary between the frontend/runtime
# normally tinygrad is frontend <-> middleware <-> runtime <-> hardware
# with REMOTE tinygrad is frontend <-> middleware <-> RemoteDevice ///HTTP/// remote_server <-> runtime <-> hardware
# this client and server can be on the same machine, same network, or just same internet
# it should be a secure (example: no use of pickle) boundary. HTTP is used for RPC
from __future__ import annotations
from typing import Callable, Iterator, Any, cast
from collections import defaultdict
from dataclasses import dataclass, field, replace
import multiprocessing, threading, functools, itertools, asyncio, http, http.client, hashlib, time, os, binascii, struct, ast, contextlib, weakref
import traceback, builtins
from tinygrad.renderer import Renderer, ProgramSpec
from tinygrad.dtype import DTYPES_DICT, dtypes
from tinygrad.uop.ops import UOp, Ops, Variable, sint
from tinygrad.helpers import getenv, DEBUG, fromimport, unwrap, LazySeq, Timing
from tinygrad.engine.jit import GraphRunner, MultiGraphRunner, ExecItem, graph_class
from tinygrad.engine.realize import CompiledRunner, BufferXfer
from tinygrad.device import Compiled, Buffer, Allocator, Compiler, Device, BufferSpec, CompilerSet, CompilerPair
from tinygrad.runtime.support.ib import IBCtx, IBConn, SGE
# ***** API *****
@dataclass(frozen=True)
class SessionKey: host: str; idx: int; nonce: str # noqa: E702
@dataclass(frozen=True)
class RemoteRequest: session: SessionKey|None = field(default=None, kw_only=True)
@dataclass(frozen=True)
class SessionFree(RemoteRequest): pass
@dataclass(frozen=True)
class RemoteProperties:
real_device: str
renderer: tuple[str, str, tuple[Any, ...]]
offset_supported: bool
graph_supported: bool
graph_supports_multi: bool
ib_gid: bytes|None
@dataclass(frozen=True)
class RemoteException:
exc: Exception
trace: str = ""
@dataclass(frozen=True)
class GetProperties(RemoteRequest): pass
@dataclass(frozen=True)
class Event(RemoteRequest): event_session: SessionKey; event: int # noqa: E702
@dataclass(frozen=True)
class Wait(RemoteRequest): event: int
@dataclass(frozen=True)
class IBConnect(RemoteRequest): host: str; gid: bytes; qp_num: int # noqa: E702
@dataclass(frozen=True)
class BufferAlloc(RemoteRequest): buffer_num: int; size: int; options: BufferSpec # noqa: E702
@dataclass(frozen=True)
class BufferOffset(RemoteRequest): buffer_num: int; size: int; offset: int; sbuffer_num: int # noqa: E702
@dataclass(frozen=True)
class BufferIOVAS(RemoteRequest): buffer_nums: list[tuple[SessionKey, int]] # noqa: E702
@dataclass(frozen=True)
class BufferFree(RemoteRequest): buffer_num: int # noqa: E702
@dataclass(frozen=True)
class CopyIn(RemoteRequest): buffer_num: int; datahash: str # noqa: E702
@dataclass(frozen=True)
class CopyOut(RemoteRequest): buffer_num: int
@dataclass(frozen=True)
class Transfer(RemoteRequest): buffer_num: int; dsession: SessionKey; dbuffer_num: int # noqa: E702
@dataclass(frozen=True)
class BatchTransfer(RemoteRequest):
sbuffer_nums: list[tuple[SessionKey, int]]
dbuffer_nums: list[tuple[SessionKey, int]]
@dataclass(frozen=True)
class ProgramAlloc(RemoteRequest): name: str; datahash: str # noqa: E702
@dataclass(frozen=True)
class ProgramFree(RemoteRequest): name: str; datahash: str # noqa: E702
@dataclass(frozen=True)
class ProgramExec(RemoteRequest):
name: str; datahash: str; bufs: tuple[int, ...]; vals: tuple[int, ...] # noqa: E702
global_size: tuple[int, ...]|None; local_size: tuple[int, ...]|None; wait: bool # noqa: E702
@dataclass(frozen=True)
class GraphComputeItem:
session: SessionKey
name: str
datahash: str
bufs: tuple[int, ...]
vars: tuple[Variable, ...]
fixedvars: dict[str, int]
ins: tuple[int, ...]
outs: tuple[int, ...]
global_size: tuple[sint, ...]|None
local_size: tuple[sint, ...]|None
@dataclass(frozen=True)
class GraphAlloc(RemoteRequest):
graph_num: int
jit_cache: tuple[GraphComputeItem|Transfer, ...]
bufs: tuple[tuple[SessionKey, int], ...]
var_vals: dict[str, int]
@dataclass(frozen=True)
class GraphFree(RemoteRequest):
graph_num: int
@dataclass(frozen=True)
class GraphExec(RemoteRequest):
graph_num: int
bufs: tuple[tuple[SessionKey, int], ...]
var_vals: dict[str, int]
wait: bool
# for safe deserialization
eval_excs = [v for k,v in builtins.__dict__.items() if isinstance(v, type) and issubclass(v, Exception) and not k.endswith("Warning")]
eval_globals = {x.__name__:x for x in [SessionKey, SessionFree, RemoteProperties, GetProperties, Event, Wait, BufferAlloc, BufferOffset, BufferIOVAS,
BufferFree, CopyIn, CopyOut, Transfer, BatchTransfer, IBConnect, ProgramAlloc, ProgramFree, ProgramExec,
GraphComputeItem, GraphAlloc, GraphFree, GraphExec, BufferSpec, UOp, Ops, dtypes, RemoteException] + eval_excs}
attribute_whitelist: dict[Any, set[str]] = {dtypes: {*DTYPES_DICT.keys(), 'imagef', 'imageh'}, Ops: {x.name for x in Ops}}
eval_fxns = {ast.Constant: lambda x: x.value, ast.Tuple: lambda x: tuple(map(safe_eval, x.elts)), ast.List: lambda x: list(map(safe_eval, x.elts)),
ast.Dict: lambda x: {safe_eval(k):safe_eval(v) for k,v in zip(x.keys, x.values)},
ast.Call: lambda x: safe_eval(x.func)(*[safe_eval(arg) for arg in x.args], **{kwarg.arg: safe_eval(kwarg.value) for kwarg in x.keywords}),
ast.Name: lambda x: eval_globals[x.id], ast.Attribute: lambda x: safe_getattr(safe_eval(x.value), x.attr)}
def safe_getattr(value, attr):
assert attr in attribute_whitelist.get(value, set()), f'getattr({value}, {repr(attr)}) is not whitelisted'
return getattr(value, attr)
def safe_eval(node): return eval_fxns[node.__class__](node)
class BatchRequest:
def __init__(self):
self._q: list[RemoteRequest] = []
self._h: dict[str, bytes] = {}
def h(self, d:bytes|memoryview) -> str:
datahash = hashlib.sha256(d).hexdigest() # NOTE: this is very slow, should use blake3 on gpu instead
if datahash not in self._h:
self._h[datahash] = bytes.fromhex(datahash)+struct.pack("<Q", len(d))+bytes(d)
return datahash
def q(self, x:RemoteRequest): self._q.append(x)
def serialize(self) -> bytes:
self.h(repr(self._q).encode())
return b''.join(self._h.values())
def deserialize(self, dat:bytes) -> BatchRequest:
ptr = 0
while ptr < len(dat):
datahash, datalen = binascii.hexlify(dat[ptr:ptr+0x20]).decode(), struct.unpack("<Q", dat[ptr+0x20:ptr+0x28])[0]
self._h[datahash] = dat[ptr+0x28:ptr+0x28+datalen]
ptr += 0x28+datalen
self._q = safe_eval(ast.parse(self._h[datahash], mode="eval").body)
return self
# ***** backend *****
@dataclass
class RemoteSession:
programs: dict[tuple[str, str], Any] = field(default_factory=dict)
graphs: dict[int, GraphRunner] = field(default_factory=dict)
buffers: dict[int, Buffer] = field(default_factory=dict)
events: defaultdict[int, asyncio.Event] = field(default_factory=functools.partial(defaultdict, asyncio.Event))
class RemoteHandler:
def __init__(self, base_device: str):
self.base_device = base_device
self.sessions: defaultdict[SessionKey, RemoteSession] = defaultdict(RemoteSession)
try: self.ib_ctx: IBCtx|None = IBCtx(getenv("IB_DEV", 0))
except (RuntimeError, IndexError, AttributeError): self.ib_ctx = None
self.ib_lock = asyncio.Lock()
self.ib_conns: dict[str, IBConn|None] = {}
self.iova_cache: dict[tuple[SessionKey, int], tuple[int, int, int]] = {}
async def __call__(self, reader:asyncio.StreamReader, writer:asyncio.StreamWriter):
while (req_hdr:=(await reader.readline()).decode().strip()):
req_method, req_path, _ = req_hdr.split(' ')
req_headers = {}
while (hdr:=(await reader.readline()).decode().strip()):
key, value = hdr.split(':', 1)
req_headers[key.lower()] = value.strip()
req_body = await reader.readexactly(int(req_headers.get("content-length", "0")))
try: res_status, res_body = await self.handle(req_method, req_path, req_body)
except Exception as e:
res_status, res_body = http.HTTPStatus.INTERNAL_SERVER_ERROR, repr(RemoteException(e, traceback.format_exc())).encode()
print(f"{traceback.format_exc()}", flush=True)
writer.write(f"HTTP/1.1 {res_status.value} {res_status.phrase}\r\nContent-Length: {len(res_body)}\r\n\r\n".encode() + res_body)
async def ib_connect(self, ssession:SessionKey, dsession:SessionKey) -> IBConn|None:
if self.ib_ctx is None: return None
await self.ib_lock.acquire()
conn = RemoteConnection(dsession.host)
if dsession.host not in self.ib_conns:
props = safe_eval(ast.parse(conn.q(GetProperties(session=dsession), wait=True), mode="eval").body)
if props.ib_gid is not None:
self.ib_conns[dsession.host] = ib_conn = IBConn(self.ib_ctx)
ibxc_ret = conn.q(IBConnect(ssession.host, ib_conn.gid, ib_conn.qp_num, session=dsession), wait=True)
ib_conn.connect(*struct.unpack('<16sQ', ibxc_ret))
else:
self.ib_conns[dsession.host] = None
self.ib_lock.release()
return self.ib_conns[dsession.host]
async def get_iovas(self, bufs:list[tuple[SessionKey, int]]) -> list[tuple[int, int, int]]:
await self.ib_lock.acquire()
if (rbufs:=[buf for buf in bufs if buf not in self.iova_cache]):
conn = RemoteConnection(rbufs[0][0].host)
resp = await conn.aq(BufferIOVAS(rbufs, session=rbufs[0][0]), wait=True)
self.iova_cache.update({rbuf: struct.unpack('<QQQ', resp[i*24:(i+1)*24]) for i,rbuf in enumerate(rbufs)})
self.ib_lock.release()
return [self.iova_cache[buf] for buf in bufs]
async def handle(self, method:str, path:str, body:bytes) -> tuple[http.HTTPStatus, bytes]:
status, ret = http.HTTPStatus.OK, b""
if path == "/batch" and method == "POST":
# TODO: streaming deserialize?
req = BatchRequest().deserialize(body)
# the cmds are always last (currently in datahash)
for c in req._q:
if DEBUG >= 1: print(c)
session, dev = self.sessions[unwrap(c.session)], Device[f"{self.base_device}:{unwrap(c.session).idx}"]
match c:
case SessionFree(): del self.sessions[unwrap(c.session)]
case GetProperties():
cls, args = dev.renderer.__reduce__()
graph_cls = graph_class(Device[self.base_device])
rp = RemoteProperties(
real_device=dev.device, renderer=(cls.__module__, cls.__name__, args), offset_supported=hasattr(dev.allocator, '_offset'),
graph_supported=graph_cls is not None,
graph_supports_multi=graph_cls is not None and issubclass(graph_cls, MultiGraphRunner) and hasattr(dev.allocator, '_transfer'),
ib_gid=bytes(self.ib_ctx.gid_attr.raw) if self.ib_ctx is not None else None,
)
ret = repr(rp).encode()
case Event():
if c.session == c.event_session:
session.events[c.event].set()
else:
for d in Device._opened_devices: Device[d].synchronize() # wait for device*s* to finish executing previous stuff
# TODO: don't wait, just send
await RemoteConnection(c.event_session.host).aq(Event(c.event_session, c.event, session=c.event_session), wait=True)
case Wait():
assert await session.events[c.event].wait()
del session.events[c.event] # do not leak memory
case IBConnect():
self.ib_conns[c.host] = ibc = IBConn(unwrap(self.ib_ctx))
ibc.connect(c.gid, c.qp_num)
ret = struct.pack('<16sQ', ibc.gid, ibc.qp_num)
case BufferAlloc():
assert c.buffer_num not in session.buffers, f"buffer {c.buffer_num} already allocated"
session.buffers[c.buffer_num] = Buffer(dev.device, c.size, dtypes.uint8, options=c.options, preallocate=True)
case BufferIOVAS():
rets = []
for buffer_session,buffer_num in c.buffer_nums:
iova, mr = unwrap(self.ib_ctx).reg(buf:=self.sessions[buffer_session].buffers[buffer_num])
rets.append(struct.pack("<QQQ", iova, mr.contents.rkey, buf.nbytes))
ret = b"".join(rets)
case BufferOffset():
assert c.buffer_num not in session.buffers, f"buffer {c.buffer_num} already exists"
session.buffers[c.buffer_num] = session.buffers[c.sbuffer_num].view(c.size, dtypes.uint8, c.offset).allocate()
case BufferFree(): del session.buffers[c.buffer_num]
case CopyIn(): session.buffers[c.buffer_num].copyin(memoryview(bytearray(req._h[c.datahash])))
case CopyOut(): session.buffers[c.buffer_num].copyout(memoryview(ret:=bytearray(session.buffers[c.buffer_num].nbytes)))
case Transfer():
if c.dsession.host == unwrap(c.session).host:
dsession, ddev = self.sessions[c.dsession], Device[f"{self.base_device}:{unwrap(c.dsession).idx}"]
dbuf, sbuf = dsession.buffers[c.dbuffer_num], session.buffers[c.buffer_num]
if hasattr(ddev.allocator, '_transfer'):
assert dbuf.nbytes == sbuf.nbytes, f"{dbuf.nbytes} != {sbuf.nbytes}"
ddev.allocator._transfer(dbuf._buf, sbuf._buf, dbuf.nbytes, dest_dev=ddev, src_dev=dev)
else:
sbuf.copyout(data:=memoryview(bytearray(sbuf.nbytes)))
dbuf.copyin(data)
else:
conn, ib_conn = RemoteConnection(c.dsession.host), await self.ib_connect(unwrap(c.session), c.dsession)
sbuf = session.buffers[c.buffer_num]
if ib_conn is not None:
src_iova, src_mr = unwrap(self.ib_ctx).reg(sbuf)
dst_iova, dst_key, dst_size = (await self.get_iovas([(c.dsession, c.dbuffer_num)]))[0]
assert sbuf.nbytes == dst_size, f"{sbuf.nbytes} != {dst_size}"
for d in Device._opened_devices: Device[d].synchronize()
ib_conn.rdma_write([SGE(dst_iova, dst_key, src_iova, src_mr.contents.lkey, dst_size)])
else:
sbuf.copyout(data:=memoryview(bytearray(sbuf.nbytes)))
await conn.aq(CopyIn(c.dbuffer_num, conn.req.h(data), session=c.dsession), wait=True)
case BatchTransfer():
conn, ib_conn = RemoteConnection(c.dbuffer_nums[0][0].host), await self.ib_connect(c.sbuffer_nums[0][0], c.dbuffer_nums[0][0])
if ib_conn is not None:
sbufs = [unwrap(self.ib_ctx).reg(self.sessions[s].buffers[bi]) for s,bi in c.sbuffer_nums]
dbufs = await self.get_iovas(c.dbuffer_nums)
for d in Device._opened_devices: Device[d].synchronize()
ib_conn.rdma_write([SGE(di, dk, si, sm.contents.lkey, ds) for (di,dk,ds),(si,sm) in zip(dbufs, sbufs)])
else:
for (sbuf_session,sbuf_num),(dbuf_session,dbuf_num) in zip(c.sbuffer_nums, c.dbuffer_nums):
sbuf = self.sessions[sbuf_session].buffers[sbuf_num]
sbuf.copyout(data:=memoryview(bytearray(sbuf.nbytes)))
await conn.aq(CopyIn(dbuf_num, conn.req.h(data), session=dbuf_session), wait=True)
case ProgramAlloc():
lib = dev.compiler.compile_cached(req._h[c.datahash].decode())
session.programs[(c.name, c.datahash)] = dev.runtime(c.name, lib)
case ProgramFree():
key = (c.name, c.datahash)
# WORKAROUND: should be unconditional once the protocol supports proper exception handling
if key in session.programs: del session.programs[key]
case ProgramExec():
bufs = [session.buffers[x]._buf for x in c.bufs]
extra_args = {k:v for k,v in [("global_size", c.global_size), ("local_size", c.local_size)] if v is not None}
r = session.programs[(c.name, c.datahash)](*bufs, vals=c.vals, wait=c.wait, **extra_args)
if r is not None: ret = str(r).encode()
case GraphAlloc():
graph_fn: Callable = unwrap(dev.graph)
def _parse_ji(gi: GraphComputeItem|Transfer):
match gi:
case GraphComputeItem():
prg = self.sessions[gi.session].programs[(gi.name, gi.datahash)]
ps = ProgramSpec(gi.name, '', f"{self.base_device}:{gi.session.idx}", UOp(Ops.NOOP),
vars=list(gi.vars), ins=list(gi.ins), outs=list(gi.outs),
global_size=list(cast(tuple[int], gi.global_size)) if gi.global_size is not None else None,
local_size=list(cast(tuple[int], gi.local_size)) if gi.local_size is not None else None)
return ExecItem(CompiledRunner(ps, precompiled=b'', prg=prg), [self.sessions[gi.session].buffers[buf] for buf in gi.bufs],
fixedvars=gi.fixedvars)
case Transfer():
dbuf, sbuf = self.sessions[gi.dsession].buffers[gi.dbuffer_num], self.sessions[unwrap(gi.session)].buffers[gi.buffer_num]
assert dbuf.nbytes == sbuf.nbytes, f"{dbuf.nbytes} != {sbuf.nbytes}"
return ExecItem(BufferXfer(dbuf.nbytes, dbuf.device, sbuf.device), [dbuf, sbuf])
assert c.graph_num not in session.graphs, f"graph {c.graph_num} already allocated"
session.graphs[c.graph_num] = graph_fn(list(map(_parse_ji, c.jit_cache)), [self.sessions[s].buffers[i] for s,i in c.bufs], c.var_vals)
case GraphFree(): del session.graphs[c.graph_num]
case GraphExec():
r = session.graphs[c.graph_num]([self.sessions[s].buffers[i] for s,i in c.bufs], c.var_vals, wait=c.wait)
if r is not None: ret = str(r).encode()
else: status, ret = http.HTTPStatus.NOT_FOUND, b"Not Found"
return status, ret
def remote_server(port:int):
device = getenv("REMOTEDEV", next(Device.get_available_devices()) if Device.DEFAULT == "REMOTE" else Device.DEFAULT)
async def _inner_async(port:int, device:str):
print(f"start remote server on {port} with device {device}")
await (await asyncio.start_server(RemoteHandler(device), host='', port=port)).serve_forever()
asyncio.run(_inner_async(port, device))
# ***** frontend *****
class RemoteAllocator(Allocator['RemoteDevice']):
def __init__(self, dev:RemoteDevice):
if dev.properties.offset_supported: self._offset = self._dyn_offset
super().__init__(dev)
# TODO: ideally we shouldn't have to deal with images here
def _alloc(self, size:int, options:BufferSpec) -> int:
self.dev.q(BufferAlloc(buffer_num:=next(self.dev.buffer_num), size, options))
return buffer_num
# TODO: options should not be here in any Allocator
def _free(self, opaque:int, options):
try: self.dev.q(BufferFree(opaque))
except (TypeError, AttributeError): pass
def _copyin(self, dest:int, src:memoryview): self.dev.q(CopyIn(dest, self.dev.conn.req.h(src)))
def _copyout(self, dest:memoryview, src:int):
resp = self.dev.q(CopyOut(src), wait=True)
assert len(resp) == len(dest), f"buffer length mismatch {len(resp)} != {len(dest)}"
dest[:] = resp
def _transfer(self, dest, src, sz, src_dev, dest_dev):
if dest_dev.conn != src_dev.conn:
dest_dev.q(Event(src_dev.session, start_event:=next(src_dev.event_num)))
src_dev.q(Wait(start_event))
src_dev.q(Transfer(src, dest_dev.session, dest))
if dest_dev.conn != src_dev.conn:
src_dev.q(Event(dest_dev.session, end_event:=next(dest_dev.event_num)))
dest_dev.q(Wait(end_event))
if DEBUG >= 2: dest_dev.conn.batch_submit()
def _dyn_offset(self, opaque:int, size:int, offset:int) -> int:
self.dev.q(BufferOffset(buffer_num:=next(self.dev.buffer_num), size, offset, opaque))
return buffer_num
class RemoteProgram:
def __init__(self, dev:RemoteDevice, name:str, lib:bytes):
self.dev, self.name = dev, name
self.datahash = self.dev.conn.req.h(lib)
self.dev.q(ProgramAlloc(self.name, self.datahash))
super().__init__()
weakref.finalize(self, self._fini, self.dev, self.name, self.datahash)
@staticmethod
def _fini(dev:RemoteDevice, name:str, datahash:str): dev.q(ProgramFree(name, datahash))
def __call__(self, *bufs, global_size=None, local_size=None, vals:tuple[int, ...]=(), wait=False):
ret = self.dev.q(ProgramExec(self.name, self.datahash, bufs, vals, global_size, local_size, wait), wait=wait)
if wait: return float(ret)
@functools.cache
class RemoteConnection:
q_lock = threading.Lock()
all: dict[RemoteConnection, None] = {} # dict instead of set for deterministic ordering
def __init__(self, host:str):
if DEBUG >= 1: print(f"remote with host {host}")
while 1:
try:
self.conn = http.client.HTTPConnection(host, timeout=getenv("REMOTE_TIMEOUT", 300.0))
self.conn.connect()
break
except Exception as e:
print(e)
time.sleep(0.1)
self.req: BatchRequest = BatchRequest()
RemoteConnection.all[self] = None
def q(self, x:RemoteRequest, wait:bool=False):
with RemoteConnection.q_lock:
self.req.q(x)
if wait: return self.batch_submit(take_q=False)
async def aq(self, x:RemoteRequest, wait:bool=False): return await asyncio.to_thread(self.q, x, wait=wait)
def batch_submit(self, take_q:bool=True):
if take_q: RemoteConnection.q_lock.acquire()
conns = RemoteConnection.all.keys()
datas = {conn: conn.req.serialize() for conn in conns}
reqs, hashes, hash_datas = sum(len(c.req._q) for c in conns), sum(len(c.req._h) for c in conns), sum(len(data) for data in datas.values())
ret, resps = None, []
with Timing(f"*** send {reqs:-3d} requests {hashes:-3d} hashes with len {hash_datas/1024:.2f} kB in ", enabled=DEBUG>=3):
for conn,data in datas.items(): conn.conn.request("POST", "/batch", data)
for conn in datas.keys():
resp = conn.conn.getresponse()
body = resp.read()
resps.append((conn, resp, body))
conn.req = BatchRequest()
if take_q: RemoteConnection.q_lock.release()
for conn,resp,body in resps:
match resp.status:
case http.HTTPStatus.OK: pass
case http.HTTPStatus.INTERNAL_SERVER_ERROR:
exc_wrapper = safe_eval(ast.parse(body.decode(), mode="eval").body)
exc_wrapper.exc.add_note(exc_wrapper.trace)
raise exc_wrapper.exc
case code: raise RuntimeError(f"POST /batch failed with {code}: {body.decode()}")
if conn == self: ret = body
return ret
def parse_hosts(hs:str) -> list[tuple[str, int]]|LazySeq[tuple[str, int]]:
hosts = [(unwrap(h), int(c) if c is not None else c) for h,c in ((h.split("*", maxsplit=1)+[None,])[:2] for h in hs.split(","))]
if len(hosts) == 1 and hosts[0][1] is None: return LazySeq(lambda idx: (hosts[0][0], idx))
return [(h, i) for h,c in hosts for i in range(unwrap(c))]
class RemoteDevice(Compiled):
devices = parse_hosts(getenv("HOST", ""))
def __init__(self, device:str):
host, idx = RemoteDevice.devices[int(device.split(":")[1]) if ":" in device else 0]
# connection is shared between sessions on the same host
self.session: SessionKey = SessionKey(host or RemoteDevice.local_server(), idx, binascii.hexlify(os.urandom(0x10)).decode())
self.conn: RemoteConnection = RemoteConnection(self.session.host)
# state for the session
self.buffer_num: Iterator[int] = itertools.count(0)
self.graph_num: Iterator[int] = itertools.count(0)
self.event_num: Iterator[int] = itertools.count(0)
self.properties: RemoteProperties = safe_eval(ast.parse(self.q(GetProperties(), wait=True), mode="eval").body)
if DEBUG >= 1: print(f"remote has device {self.properties.real_device}")
# TODO: how to we have BEAM be cached on the backend? this should just send a specification of the compute. rethink what goes in Renderer
renderer = self.properties.renderer
if not renderer[0].startswith("tinygrad.") or not renderer[1].endswith("Renderer"): raise RuntimeError(f"bad renderer {renderer}")
renderer_class = fromimport(renderer[0], renderer[1]) # TODO: is this secure?
if not issubclass(renderer_class, Renderer): raise RuntimeError(f"renderer isn't a Renderer {renderer}")
graph = fromimport('tinygrad.runtime.graph.remote', "RemoteGraph") if self.properties.graph_supported else None
compilers = CompilerSet([CompilerPair(functools.partial(renderer_class, *renderer[2]), Compiler)])
super().__init__(device, RemoteAllocator(self), compilers, functools.partial(RemoteProgram, self), graph, id(self.conn))
self.renderer.device = device
def finalize(self):
with contextlib.suppress(ConnectionError, http.client.HTTPException): self.q(SessionFree(), wait=True)
def q(self, x:RemoteRequest, wait:bool=False): return self.conn.q(replace(x, session=self.session), wait=wait)
@functools.cache
@staticmethod
def local_server():
multiprocessing.Process(target=remote_server, args=(6667,), name="MainProcess", daemon=True).start()
return "127.0.0.1:6667"
if __name__ == "__main__": remote_server(getenv("PORT", 6667))
+59 -26
View File
@@ -41,40 +41,62 @@ class AMFirmware:
self.ucode_start: dict[str, int] = {}
self.descs: list[tuple[list[int], memoryview]] = []
blob, hdr = self.load_fw(f"smu_{fmt_ver(am.MP1_HWIP)}.bin", am.struct_smc_firmware_header_v1_0)
self.smu_psp_desc = self.desc(blob, hdr.header.ucode_array_offset_bytes, hdr.header.ucode_size_bytes, am.GFX_FW_TYPE_SMU)
# SMU firmware
blob, hdr = self.load_fw(f"smu_{fmt_ver(am.MP1_HWIP)}.bin", versioned_header="struct_smc_firmware_header")
if self.adev.ip_ver[am.GC_HWIP] >= (11,0,0):
self.smu_psp_desc = self.desc(blob, hdr.v1_0.header.ucode_array_offset_bytes, hdr.v1_0.header.ucode_size_bytes, am.GFX_FW_TYPE_SMU)
else:
p2stables = (am.struct_smc_soft_pptable_entry * hdr.pptable_count).from_buffer(blob[hdr.pptable_entry_offset:])
for p2stable in p2stables:
if p2stable.id == (__P2S_TABLE_ID_X:=0x50325358):
self.descs += [self.desc(blob, p2stable.ppt_offset_bytes, p2stable.ppt_size_bytes, am.GFX_FW_TYPE_P2S_TABLE)]
# SDMA firmware
blob, hdr = self.load_fw(f"sdma_{fmt_ver(am.SDMA0_HWIP)}.bin", versioned_header='struct_sdma_firmware_header')
if hdr.header.header_version_major < 3:
blob, hdr = self.load_fw(f"sdma_{fmt_ver(am.SDMA0_HWIP)}.bin", versioned_header="struct_sdma_firmware_header")
if hdr.header.header_version_major == 1:
self.descs += [self.desc(blob, hdr.header.ucode_array_offset_bytes, hdr.header.ucode_size_bytes, am.GFX_FW_TYPE_SDMA0)]
elif hdr.header.header_version_major == 2:
self.descs += [self.desc(blob, hdr.ctl_ucode_offset, hdr.ctl_ucode_size_bytes, am.GFX_FW_TYPE_SDMA_UCODE_TH1)]
self.descs += [self.desc(blob, hdr.header.ucode_array_offset_bytes, hdr.ctx_ucode_size_bytes, am.GFX_FW_TYPE_SDMA_UCODE_TH0)]
else: self.descs += [self.desc(blob, hdr.header.ucode_array_offset_bytes, hdr.ucode_size_bytes, am.GFX_FW_TYPE_SDMA_UCODE_TH0)]
# PFP, ME, MEC firmware
for (fw_name, fw_cnt) in ([('PFP', 1), ('ME', 1)] if self.adev.ip_ver[am.GC_HWIP] >= (12,0,0) else []) + [('MEC', 1)]:
blob, hdr = self.load_fw(f"gc_{fmt_ver(am.GC_HWIP)}_{fw_name.lower()}.bin", am.struct_gfx_firmware_header_v2_0)
blob, hdr = self.load_fw(f"gc_{fmt_ver(am.GC_HWIP)}_{fw_name.lower()}.bin", versioned_header="struct_gfx_firmware_header")
# Code part
self.descs += [self.desc(blob, hdr.header.ucode_array_offset_bytes, hdr.ucode_size_bytes, getattr(am, f'GFX_FW_TYPE_RS64_{fw_name}'))]
# Stack
stack_fws = [getattr(am, f'GFX_FW_TYPE_RS64_{fw_name}_P{fwnum}_STACK') for fwnum in range(fw_cnt)]
self.descs += [self.desc(blob, hdr.data_offset_bytes, hdr.data_size_bytes, *stack_fws)]
self.ucode_start[fw_name] = hdr.ucode_start_addr_lo | (hdr.ucode_start_addr_hi << 32)
ucode_off = hdr.header.ucode_array_offset_bytes
if hdr.header.header_version_major == 1:
# Code
self.descs += [self.desc(blob, ucode_off, hdr.header.ucode_size_bytes - hdr.jt_size * 4, getattr(am, f'GFX_FW_TYPE_CP_{fw_name}'))]
# JT
self.descs += [self.desc(blob, ucode_off + hdr.jt_offset * 4, hdr.jt_size * 4, getattr(am, f'GFX_FW_TYPE_CP_{fw_name}_ME1'))]
else:
# Code
self.descs += [self.desc(blob, ucode_off, hdr.ucode_size_bytes, getattr(am, f'GFX_FW_TYPE_RS64_{fw_name}'))]
# Stack
stack_fws = [getattr(am, f'GFX_FW_TYPE_RS64_{fw_name}_P{fwnum}_STACK') for fwnum in range(fw_cnt)]
self.descs += [self.desc(blob, hdr.data_offset_bytes, hdr.data_size_bytes, *stack_fws)]
self.ucode_start[fw_name] = hdr.ucode_start_addr_lo | (hdr.ucode_start_addr_hi << 32)
# IMU firmware
blob, hdr = self.load_fw(f"gc_{fmt_ver(am.GC_HWIP)}_imu.bin", am.struct_imu_firmware_header_v1_0)
imu_i_off, imu_i_sz, imu_d_sz = hdr.header.ucode_array_offset_bytes, hdr.imu_iram_ucode_size_bytes, hdr.imu_dram_ucode_size_bytes
self.descs += [self.desc(blob, imu_i_off, imu_i_sz, am.GFX_FW_TYPE_IMU_I), self.desc(blob, imu_i_off + imu_i_sz, imu_d_sz, am.GFX_FW_TYPE_IMU_D)]
if self.adev.ip_ver[am.GC_HWIP] >= (11,0,0):
blob, hdr = self.load_fw(f"gc_{fmt_ver(am.GC_HWIP)}_imu.bin", am.struct_imu_firmware_header_v1_0)
imu_i_off, imu_i_sz, imu_d_sz = hdr.header.ucode_array_offset_bytes, hdr.imu_iram_ucode_size_bytes, hdr.imu_dram_ucode_size_bytes
self.descs += [self.desc(blob, imu_i_off, imu_i_sz, am.GFX_FW_TYPE_IMU_I), self.desc(blob, imu_i_off+imu_i_sz, imu_d_sz, am.GFX_FW_TYPE_IMU_D)]
# RLC firmware
blob, hdr0, _hdr1, hdr2, hdr3 = self.load_fw(f"gc_{fmt_ver(am.GC_HWIP)}_rlc.bin", am.struct_rlc_firmware_header_v2_0,
blob, hdr0, hdr1, hdr2, hdr3 = self.load_fw(f"gc_{fmt_ver(am.GC_HWIP)}_rlc.bin", am.struct_rlc_firmware_header_v2_0,
am.struct_rlc_firmware_header_v2_1, am.struct_rlc_firmware_header_v2_2, am.struct_rlc_firmware_header_v2_3)
for mem,fmem in [('IRAM', 'iram'), ('DRAM_BOOT', 'dram')]:
off, sz = getattr(hdr2, f'rlc_{fmem}_ucode_offset_bytes'), getattr(hdr2, f'rlc_{fmem}_ucode_size_bytes')
self.descs += [self.desc(blob, off, sz, getattr(am, f'GFX_FW_TYPE_RLC_{mem}'))]
if hdr0.header.header_version_minor == 1:
for mem,fmem in [('LIST_SRM_CNTL', 'list_cntl'), ('LIST_GPM_MEM', 'list_gpm'), ('LIST_SRM_MEM', 'list_srm')]:
off, sz = getattr(hdr1, f'save_restore_{fmem}_offset_bytes'), getattr(hdr1, f'save_restore_{fmem}_size_bytes')
self.descs += [self.desc(blob, off, sz, getattr(am, f'GFX_FW_TYPE_RLC_RESTORE_{mem}'))]
if hdr0.header.header_version_minor >= 2:
for mem,fmem in [('IRAM', 'iram'), ('DRAM_BOOT', 'dram')]:
off, sz = getattr(hdr2, f'rlc_{fmem}_ucode_offset_bytes'), getattr(hdr2, f'rlc_{fmem}_ucode_size_bytes')
self.descs += [self.desc(blob, off, sz, getattr(am, f'GFX_FW_TYPE_RLC_{mem}'))]
if hdr0.header.header_version_minor == 3:
for mem in ['P', 'V']:
@@ -107,7 +129,7 @@ class AMPageTableEntry:
def address(self, entry_id:int) -> int:
assert self.entries[entry_id] & am.AMDGPU_PTE_SYSTEM == 0, "should not be system address"
return self.adev.xgmi2paddr(self.entries[entry_id] & 0x0000FFFFFFFFF000)
def is_page(self, entry_id:int) -> bool: return self.lv == am.AMDGPU_VM_PTB or self.adev.gmc.is_pte_huge_page(self.entries[entry_id])
def is_page(self, entry_id:int) -> bool: return self.lv == am.AMDGPU_VM_PTB or self.adev.gmc.is_pte_huge_page(self.lv, self.entries[entry_id])
def supports_huge_page(self, paddr:int): return self.lv >= am.AMDGPU_VM_PDB2
class AMMemoryManager(MemoryManager):
@@ -121,7 +143,7 @@ class AMMemoryManager(MemoryManager):
class AMDev(PCIDevImplBase):
Version = 0xA0000006
def __init__(self, pci_dev:PCIDevice, dma_regions:list[tuple[int, MMIOInterface]]|None=None):
def __init__(self, pci_dev:PCIDevice, dma_regions:list[tuple[int, MMIOInterface]]|None=None, reset_mode=False):
self.pci_dev, self.devfmt, self.dma_regions = pci_dev, pci_dev.pcibus, dma_regions
self.vram, self.doorbell64, self.mmio = self.pci_dev.map_bar(0), self.pci_dev.map_bar(2, fmt='Q'), self.pci_dev.map_bar(5, fmt='I')
@@ -147,7 +169,11 @@ class AMDev(PCIDevImplBase):
# Init hw for IP blocks where it is needed
if not self.partial_boot:
if self.psp.is_sos_alive() and self.smu.is_smu_alive(): self.smu.mode1_reset()
if self.psp.is_sos_alive() and self.smu.is_smu_alive():
if self.gmc.xgmi_seg_sz > 0:
if reset_mode: return # in reset mode, do not raise
raise RuntimeError("Malformed state. Use extra/amdpci/hive_reset.py to reset the hive")
self.smu.mode1_reset()
for ip in [self.soc, self.gmc, self.ih, self.psp, self.smu]:
ip.init_hw()
if DEBUG >= 2: print(f"am {self.devfmt}: {ip.__class__.__name__} initialized")
@@ -199,14 +225,14 @@ class AMDev(PCIDevImplBase):
def reg(self, reg:str) -> AMRegister: return self.__dict__[reg]
def rreg(self, reg:int) -> int:
val = self.indirect_rreg(reg * 4) if reg > len(self.mmio) else self.mmio[reg]
val = self.indirect_rreg(reg) if reg > len(self.mmio) else self.mmio[reg]
if AM_DEBUG >= 4 and getattr(self, '_prev_rreg', None) != (reg, val): print(f"am {self.devfmt}: Reading register {reg:#x} with value {val:#x}")
self._prev_rreg = (reg, val)
return val
def wreg(self, reg:int, val:int):
if AM_DEBUG >= 4: print(f"am {self.devfmt}: Writing register {reg:#x} with value {val:#x}")
if reg > len(self.mmio): self.indirect_wreg(reg * 4, val)
if reg > len(self.mmio): self.indirect_wreg(reg, val)
else: self.mmio[reg] = val
def wreg_pair(self, reg_base:str, lo_suffix:str, hi_suffix:str, val:int, inst:int=0):
@@ -214,13 +240,19 @@ class AMDev(PCIDevImplBase):
self.reg(f"{reg_base}{hi_suffix}").write(val >> 32, inst=inst)
def indirect_rreg(self, reg:int) -> int:
self.reg("regBIF_BX_PF0_RSMU_INDEX").write(reg)
self.reg("regBIF_BX_PF0_RSMU_INDEX").write(reg * 4)
return self.reg("regBIF_BX_PF0_RSMU_DATA").read()
def indirect_wreg(self, reg:int, val:int):
self.reg("regBIF_BX_PF0_RSMU_INDEX").write(reg)
self.reg("regBIF_BX_PF0_RSMU_INDEX").write(reg * 4)
self.reg("regBIF_BX_PF0_RSMU_DATA").write(val)
def indirect_wreg_pcie(self, reg:int, val:int, aid:int=0):
self.reg("regBIF_BX0_PCIE_INDEX2").write(reg * 4 + ((((aid & 0b11) << 32) | (1 << 34)) if aid > 0 else 0))
self.reg("regBIF_BX0_PCIE_INDEX2").read()
self.reg("regBIF_BX0_PCIE_DATA2").write(val)
self.reg("regBIF_BX0_PCIE_DATA2").read()
def _read_vram(self, addr, size) -> bytes:
assert addr % 4 == 0 and size % 4 == 0, f"Invalid address {addr:#x} or size {size:#x}"
res = []
@@ -268,6 +300,7 @@ class AMDev(PCIDevImplBase):
def _build_regs(self):
mods = [("mp", am.MP0_HWIP), ("hdp", am.HDP_HWIP), ("gc", am.GC_HWIP), ("mmhub", am.MMHUB_HWIP), ("osssys", am.OSSSYS_HWIP),
("nbio" if self.ip_ver[am.GC_HWIP] < (12,0,0) else "nbif", am.NBIO_HWIP)]
if self.ip_ver[am.SDMA0_HWIP] == (4,4,2): mods += [("sdma", am.SDMA0_HWIP)]
for prefix, hwip in mods:
self.__dict__.update(import_asic_regs(prefix, self.ip_ver[hwip], cls=functools.partial(AMRegister, adev=self, bases=self.regs_offset[hwip])))
+188 -120
View File
@@ -15,42 +15,53 @@ class AM_SOC(AM_IP):
def init_sw(self): self.module = import_soc(self.adev.ip_ver[am.GC_HWIP])
def init_hw(self):
self.adev.regRCC_DEV0_EPF2_STRAP2.update(strap_no_soft_reset_dev0_f2=0x0)
if self.adev.ip_ver[am.NBIO_HWIP] == (7,9,0):
self.adev.regXCC_DOORBELL_FENCE.write(0x0)
self.adev.regBIFC_GFX_INT_MONITOR_MASK.write(0x7ff)
self.adev.regBIFC_DOORBELL_ACCESS_EN_PF.write(0xfffff)
else: self.adev.regRCC_DEV0_EPF2_STRAP2.update(strap_no_soft_reset_dev0_f2=0x0)
self.adev.regRCC_DEV0_EPF0_RCC_DOORBELL_APER_EN.write(0x1)
def set_clockgating_state(self): self.adev.regHDP_MEM_POWER_CTRL.update(atomic_mem_power_ctrl_en=1, atomic_mem_power_ds_en=1)
def set_clockgating_state(self):
if self.adev.ip_ver[am.HDP_HWIP] >= (5,2,1): self.adev.regHDP_MEM_POWER_CTRL.update(atomic_mem_power_ctrl_en=1, atomic_mem_power_ds_en=1)
def doorbell_enable(self, port, awid=0, awaddr_31_28_value=0, offset=0, size=0):
self.adev.reg(f"{'regGDC_S2A0_S2A' if self.adev.ip_ver[am.GC_HWIP] >= (12,0,0) else 'regS2A'}_DOORBELL_ENTRY_{port}_CTRL").update(
**{f"s2a_doorbell_port{port}_enable":1, f"s2a_doorbell_port{port}_awid":awid, f"s2a_doorbell_port{port}_awaddr_31_28_value":awaddr_31_28_value,
f"s2a_doorbell_port{port}_range_offset":offset, f"s2a_doorbell_port{port}_range_size":size})
reg = self.adev.reg(f"{'regGDC_S2A0_S2A' if self.adev.ip_ver[am.GC_HWIP] >= (12,0,0) else 'regS2A'}_DOORBELL_ENTRY_{port}_CTRL")
val = reg.encode(**{f"s2a_doorbell_port{port}_enable":1, f"s2a_doorbell_port{port}_awid":awid, f"s2a_doorbell_port{port}_range_size":size,
f"s2a_doorbell_port{port}_awaddr_31_28_value":awaddr_31_28_value, f"s2a_doorbell_port{port}_range_offset":offset})
if self.adev.ip_ver[am.NBIO_HWIP] == (7,9,0): self.adev.indirect_wreg_pcie(reg.addr[0], val)
else: reg.write(val)
class AM_GMC(AM_IP):
def init_sw(self):
self.vmhubs = len(self.adev.regs_offset[am.MMHUB_HWIP])
# XGMI (for supported systems)
xgmi_phys_id = self.adev.regMMMC_VM_XGMI_LFB_CNTL.read_bitfields()['pf_lfb_region'] if hasattr(self.adev, 'regMMMC_VM_XGMI_LFB_CNTL') else 0
xgmi_seg_sz = (self.adev.regMMMC_VM_XGMI_LFB_SIZE.read_bitfields()['pf_lfb_size'] << 24) if hasattr(self.adev, 'regMMMC_VM_XGMI_LFB_SIZE') else 0
self.xgmi_phys_id = self.adev.regMMMC_VM_XGMI_LFB_CNTL.read_bitfields()['pf_lfb_region'] if hasattr(self.adev, 'regMMMC_VM_XGMI_LFB_CNTL') else 0
self.xgmi_seg_sz = self.adev.regMMMC_VM_XGMI_LFB_SIZE.read_bitfields()['pf_lfb_size']<<24 if hasattr(self.adev, 'regMMMC_VM_XGMI_LFB_SIZE') else 0
self.paddr_base = xgmi_phys_id * xgmi_seg_sz
self.paddr_base = self.xgmi_phys_id * self.xgmi_seg_sz
self.fb_base = (self.adev.regMMMC_VM_FB_LOCATION_BASE.read() & 0xFFFFFF) << 24
self.fb_end = (self.adev.regMMMC_VM_FB_LOCATION_TOP.read() & 0xFFFFFF) << 24
# Memory controller aperture
self.mc_base = self.fb_base + self.paddr_base
self.mc_end = self.mc_base + self.adev.mm.vram_size - 1
# VM aperture
self.vm_base = self.adev.mm.va_base
self.vm_end = min(self.vm_base + (1 << self.adev.mm.va_bits) - 1, 0x7fffffffffff)
self.trans_futher = self.adev.ip_ver[am.GC_HWIP] < (10, 0, 0)
# GFX11/GFX12 has 44-bit address space
self.address_space_mask = (1 << 44) - 1
self.memscratch_xgmi_paddr = self.adev.paddr2xgmi(self.adev.mm.palloc(0x1000, zero=False, boot=True))
self.dummy_page_xgmi_paddr = self.adev.paddr2xgmi(self.adev.mm.palloc(0x1000, zero=False, boot=True))
self.hub_initted = {"MM": False, "GC": False}
# MM hub is inited before any tlb flushes and is still valid during partial_boot, so set it to true
self.hub_initted = {"MM": True, "GC": False}
self.pf_status_reg = lambda ip: f"reg{ip}VM_L2_PROTECTION_FAULT_STATUS{'_LO32' if self.adev.ip_ver[am.GC_HWIP] >= (12,0,0) else ''}"
@@ -63,32 +74,30 @@ class AM_GMC(AM_IP):
# Can't issue TLB invalidation if the hub isn't initialized.
if not self.hub_initted[ip]: return
if ip == "MM": wait_cond(lambda: self.adev.regMMVM_INVALIDATE_ENG17_SEM.read() & 0x1, value=1, msg="mm flush_tlb timeout")
for inst in range(self.adev.gmc.vmhubs if ip == "MM" else self.adev.gfx.xccs):
if ip == "MM": wait_cond(lambda: self.adev.regMMVM_INVALIDATE_ENG17_SEM.read(inst=inst) & 0x1, value=1, msg="mm flush_tlb timeout")
self.adev.reg(f"reg{ip}VM_INVALIDATE_ENG17_REQ").write(flush_type=flush_type, per_vmid_invalidate_req=(1 << vmid), invalidate_l2_ptes=1,
invalidate_l2_pde0=1, invalidate_l2_pde1=1, invalidate_l2_pde2=1, invalidate_l1_ptes=1, clear_protection_fault_status_addr=0)
self.adev.reg(f"reg{ip}VM_INVALIDATE_ENG17_REQ").write(flush_type=flush_type, per_vmid_invalidate_req=(1 << vmid), invalidate_l2_ptes=1,
invalidate_l2_pde0=1, invalidate_l2_pde1=1, invalidate_l2_pde2=1, invalidate_l1_ptes=1, clear_protection_fault_status_addr=0, inst=inst)
wait_cond(lambda: self.adev.reg(f"reg{ip}VM_INVALIDATE_ENG17_ACK").read() & (1 << vmid), value=(1 << vmid), msg="flush_tlb timeout")
wait_cond(lambda: self.adev.reg(f"reg{ip}VM_INVALIDATE_ENG17_ACK").read(inst=inst) & (1 << vmid), value=(1 << vmid), msg="flush_tlb timeout")
if ip == "MM":
self.adev.regMMVM_INVALIDATE_ENG17_SEM.write(0x0)
self.adev.regMMVM_L2_BANK_SELECT_RESERVED_CID2.update(reserved_cache_private_invalidation=1)
if ip == "MM": self.adev.regMMVM_INVALIDATE_ENG17_SEM.write(0x0, inst=inst)
if self.adev.ip_ver[am.GC_HWIP] >= (11,0,0) and ip == "MM":
self.adev.regMMVM_L2_BANK_SELECT_RESERVED_CID2.update(reserved_cache_private_invalidation=1, inst=inst)
# Read back the register to ensure the invalidation is complete
self.adev.regMMVM_L2_BANK_SELECT_RESERVED_CID2.read()
# Read back the register to ensure the invalidation is complete
self.adev.regMMVM_L2_BANK_SELECT_RESERVED_CID2.read(inst=inst)
def enable_vm_addressing(self, page_table, ip:Literal["MM", "GC"], vmid, inst):
self.adev.wreg_pair(f"reg{ip}VM_CONTEXT{vmid}_PAGE_TABLE_START_ADDR", "_LO32", "_HI32", self.vm_base >> 12, inst=inst)
self.adev.wreg_pair(f"reg{ip}VM_CONTEXT{vmid}_PAGE_TABLE_END_ADDR", "_LO32", "_HI32", self.vm_end >> 12, inst=inst)
self.adev.wreg_pair(f"reg{ip}VM_CONTEXT{vmid}_PAGE_TABLE_BASE_ADDR", "_LO32", "_HI32", self.adev.paddr2xgmi(page_table.paddr) | 1, inst=inst)
self.adev.reg(f"reg{ip}VM_CONTEXT{vmid}_CNTL").write(0x1800000, pde0_protection_fault_enable_interrupt=1, pde0_protection_fault_enable_default=1,
dummy_page_protection_fault_enable_interrupt=1, dummy_page_protection_fault_enable_default=1,
range_protection_fault_enable_interrupt=1, range_protection_fault_enable_default=1,
valid_protection_fault_enable_interrupt=1, valid_protection_fault_enable_default=1,
read_protection_fault_enable_interrupt=1, read_protection_fault_enable_default=1,
write_protection_fault_enable_interrupt=1, write_protection_fault_enable_default=1,
execute_protection_fault_enable_interrupt=1, execute_protection_fault_enable_default=1,
enable_context=1, page_table_depth=(3 - page_table.lv), inst=inst)
fault_flags = {f'{x}_protection_fault_enable_interrupt':1 for x in ['pde0', 'dummy_page', 'range', 'valid', 'read', 'write', 'execute']}
en_def_flags = {f'{x}_protection_fault_enable_default':1 for x in ['pde0', 'dummy_page', 'range', 'valid', 'read', 'write', 'execute']}
self.adev.reg(f"reg{ip}VM_CONTEXT{vmid}_CNTL").write(0x1800000, **fault_flags, **en_def_flags, enable_context=1,
page_table_depth=((2 if self.trans_futher else 3) - page_table.lv), page_table_block_size=9 if self.trans_futher else 0, inst=inst)
def init_hub(self, ip:Literal["MM", "GC"], inst_cnt:int):
# Init system apertures
@@ -97,8 +106,8 @@ class AM_GMC(AM_IP):
self.adev.reg(f"reg{ip}MC_VM_AGP_BOT").write(0xffffffffffff >> 24, inst=inst) # disable AGP
self.adev.reg(f"reg{ip}MC_VM_AGP_TOP").write(0, inst=inst)
self.adev.reg(f"reg{ip}MC_VM_SYSTEM_APERTURE_LOW_ADDR").write(self.mc_base >> 18, inst=inst)
self.adev.reg(f"reg{ip}MC_VM_SYSTEM_APERTURE_HIGH_ADDR").write(self.mc_end >> 18, inst=inst)
self.adev.reg(f"reg{ip}MC_VM_SYSTEM_APERTURE_LOW_ADDR").write(self.fb_base >> 18, inst=inst)
self.adev.reg(f"reg{ip}MC_VM_SYSTEM_APERTURE_HIGH_ADDR").write(self.fb_end >> 18, inst=inst)
self.adev.wreg_pair(f"reg{ip}MC_VM_SYSTEM_APERTURE_DEFAULT_ADDR", "_LSB", "_MSB", self.memscratch_xgmi_paddr >> 12, inst=inst)
self.adev.wreg_pair(f"reg{ip}VM_L2_PROTECTION_FAULT_DEFAULT_ADDR", "_LO32", "_HI32", self.dummy_page_xgmi_paddr >> 12, inst=inst)
@@ -106,15 +115,15 @@ class AM_GMC(AM_IP):
# Init TLB and cache
self.adev.reg(f"reg{ip}MC_VM_MX_L1_TLB_CNTL").update(enable_l1_tlb=1, system_access_mode=3, enable_advanced_driver_model=1,
system_aperture_unmapped_access=0, eco_bits=0, mtype=self.adev.soc.module.MTYPE_UC, inst=inst)
system_aperture_unmapped_access=0, mtype=self.adev.soc.module.MTYPE_UC, inst=inst)
self.adev.reg(f"reg{ip}VM_L2_CNTL").update(enable_l2_cache=1, enable_l2_fragment_processing=0, enable_default_page_out_to_system_memory=1,
l2_pde0_cache_tag_generation_mode=0, pde_fault_classification=0, context1_identity_access_mode=1, identity_mode_fragment_size=0, inst=inst)
self.adev.reg(f"reg{ip}VM_L2_CNTL2").update(invalidate_all_l1_tlbs=1, invalidate_l2_cache=1, inst=inst)
self.adev.reg(f"reg{ip}VM_L2_CNTL3").write(bank_select=9, l2_cache_bigk_fragment_size=6,l2_cache_4k_associativity=1,
l2_cache_bigk_associativity=1, inst=inst)
self.adev.reg(f"reg{ip}VM_L2_CNTL3").write(l2_cache_4k_associativity=1, l2_cache_bigk_associativity=1,
bank_select=12 if self.trans_futher else 9, l2_cache_bigk_fragment_size=9 if self.trans_futher else 6, inst=inst)
self.adev.reg(f"reg{ip}VM_L2_CNTL4").write(l2_cache_4k_partition_count=1, inst=inst)
self.adev.reg(f"reg{ip}VM_L2_CNTL5").write(walker_priority_client_id=0x1ff, inst=inst)
if self.adev.ip_ver[am.GC_HWIP] >= (10,0,0): self.adev.reg(f"reg{ip}VM_L2_CNTL5").write(walker_priority_client_id=0x1ff, inst=inst)
self.enable_vm_addressing(self.adev.mm.root_page_table, ip, vmid=0, inst=inst)
@@ -133,11 +142,18 @@ class AM_GMC(AM_IP):
if self.adev.ip_ver[am.GC_HWIP] >= (12,0,0):
extra |= am.AMDGPU_PTE_MTYPE_GFX12(0, self.adev.soc.module.MTYPE_UC if uncached else 0)
extra |= (am.AMDGPU_PDE_PTE_GFX12 if not is_table and pte_lv != am.AMDGPU_VM_PTB else (am.AMDGPU_PTE_IS_PTE if not is_table else 0))
else:
elif self.adev.ip_ver[am.GC_HWIP] >= (10,0,0):
extra |= am.AMDGPU_PTE_MTYPE_NV10(0, self.adev.soc.module.MTYPE_UC if uncached else 0)
extra |= (am.AMDGPU_PDE_PTE if not is_table and pte_lv != am.AMDGPU_VM_PTB else 0)
else:
extra |= am.AMDGPU_PTE_MTYPE_VG10(0, self.adev.soc.module.MTYPE_UC if uncached else 0)
if is_table and pte_lv == am.AMDGPU_VM_PDB1: extra |= am.AMDGPU_PDE_BFS(0x9)
if is_table and pte_lv == am.AMDGPU_VM_PDB0: extra |= am.AMDGPU_PTE_TF
if not is_table and pte_lv not in {am.AMDGPU_VM_PTB, am.AMDGPU_VM_PDB0}: extra |= am.AMDGPU_PDE_PTE
return extra
def is_pte_huge_page(self, pte): return pte & (am.AMDGPU_PDE_PTE_GFX12 if self.adev.ip_ver[am.GC_HWIP] >= (12,0,0) else am.AMDGPU_PDE_PTE)
def is_pte_huge_page(self, pte_lv, pte):
if self.adev.ip_ver[am.GC_HWIP] < (10,0,0): return (pte & am.AMDGPU_PDE_PTE) if pte_lv != am.AMDGPU_VM_PDB0 else not (pte & am.AMDGPU_PTE_TF)
return pte & (am.AMDGPU_PDE_PTE_GFX12 if self.adev.ip_ver[am.GC_HWIP] >= (12,0,0) else am.AMDGPU_PDE_PTE)
def on_interrupt(self):
for ip in ["MM", "GC"]:
@@ -156,12 +172,13 @@ class AM_SMU(AM_IP):
self._send_msg(self.smu_mod.PPSMC_MSG_EnableAllSmuFeatures, 0)
def is_smu_alive(self):
with contextlib.suppress(RuntimeError): self._send_msg(self.smu_mod.PPSMC_MSG_GetSmuVersion, 0, timeout=100)
with contextlib.suppress(TimeoutError): self._send_msg(self.smu_mod.PPSMC_MSG_GetSmuVersion, 0, timeout=100)
return self.adev.mmMP1_SMN_C2PMSG_90.read() != 0
def mode1_reset(self):
if DEBUG >= 2: print(f"am {self.adev.devfmt}: mode1 reset")
if self.adev.ip_ver[am.MP0_HWIP] >= (14,0,0): self._send_msg(__DEBUGSMC_MSG_Mode1Reset:=2, 0, debug=True)
elif self.adev.ip_ver[am.MP0_HWIP] == (13,0,6): self._send_msg(self.smu_mod.PPSMC_MSG_GfxDriverReset, 1)
else: self._send_msg(self.smu_mod.PPSMC_MSG_Mode1Reset, 0)
time.sleep(0.5) # 500ms
@@ -171,6 +188,8 @@ class AM_SMU(AM_IP):
def read_metrics(self): return self.read_table(self.smu_mod.SmuMetricsExternal_t, self.smu_mod.TABLE_SMU_METRICS)
def set_clocks(self, level):
if self.adev.ip_ver[am.MP0_HWIP] == (13,0,6): return # TODO
if not hasattr(self, 'clcks'):
self.clcks = {}
for clck in [self.smu_mod.PPCLK_GFXCLK, self.smu_mod.PPCLK_UCLK, self.smu_mod.PPCLK_FCLK, self.smu_mod.PPCLK_SOCCLK]:
@@ -205,17 +224,26 @@ class AM_GFX(AM_IP):
# NOTE: Golden reg for gfx11. No values for this reg provided. The kernel just ors 0x20000000 to this reg.
for xcc in range(self.xccs): self.adev.regTCP_CNTL.write(self.adev.regTCP_CNTL.read() | 0x20000000, inst=xcc)
for xcc in range(self.xccs): self.adev.regRLC_CNTL.write(0x1, inst=xcc)
for xcc in range(self.xccs): self.adev.regRLC_SRM_CNTL.update(srm_enable=1, auto_incr_addr=1, inst=xcc)
self.adev.soc.doorbell_enable(port=0, awid=0x3, awaddr_31_28_value=0x3)
self.adev.soc.doorbell_enable(port=3, awid=0x6, awaddr_31_28_value=0x3)
for xcc in range(self.xccs): self.adev.regRLC_SPM_MC_CNTL.write(0xf, inst=xcc)
if self.adev.ip_ver[am.NBIO_HWIP] != (7,9,0):
self.adev.soc.doorbell_enable(port=0, awid=0x3, awaddr_31_28_value=0x3)
self.adev.soc.doorbell_enable(port=3, awid=0x6, awaddr_31_28_value=0x3)
for xcc in range(self.xccs):
if self.adev.ip_ver[am.GC_HWIP] == (9,4,3):
self.adev.regGB_ADDR_CONFIG.write(0x2a114042, inst=xcc) # Golden value for mi300
self.adev.regTCP_UTCL1_CNTL2.update(spare=1, inst=xcc)
self.adev.regGRBM_CNTL.update(read_timeout=0xff, inst=xcc)
for i in range(0, 16):
self._grbm_select(vmid=i, inst=xcc)
self.adev.regSH_MEM_CONFIG.write(address_mode=self.adev.soc.module.SH_MEM_ADDRESS_MODE_64,
alignment_mode=self.adev.soc.module.SH_MEM_ALIGNMENT_MODE_UNALIGNED, initial_inst_prefetch=3, inst=xcc)
self.adev.regSH_MEM_CONFIG.write(**({'initial_inst_prefetch':3} if self.adev.ip_ver[am.GC_HWIP][0] >= 10 else {}),
address_mode=self.adev.soc.module.SH_MEM_ADDRESS_MODE_64, alignment_mode=self.adev.soc.module.SH_MEM_ALIGNMENT_MODE_UNALIGNED, inst=xcc)
# Configure apertures:
# LDS: 0x10000000'00000000 - 0x10000001'00000000 (4GB)
@@ -224,54 +252,62 @@ class AM_GFX(AM_IP):
self._grbm_select(inst=xcc)
# Configure MEC doorbell range
self.adev.regCP_MEC_DOORBELL_RANGE_LOWER.write(0x0, inst=xcc)
self.adev.regCP_MEC_DOORBELL_RANGE_UPPER.write(0x450, inst=xcc)
self.adev.regCP_MEC_DOORBELL_RANGE_LOWER.write(0x100 * xcc, inst=xcc)
self.adev.regCP_MEC_DOORBELL_RANGE_UPPER.write(0x100 * xcc + 0xf8, inst=xcc)
# Enable MEC
self.adev.regCP_MEC_RS64_CNTL.update(mec_invalidate_icache=0, mec_pipe0_reset=0, mec_pipe0_active=1, mec_halt=0, inst=xcc)
if self.adev.ip_ver[am.GC_HWIP] < (10,0,0): self.adev.regCP_MEC_CNTL.write(0x0, inst=xcc)
else: self.adev.regCP_MEC_RS64_CNTL.update(mec_invalidate_icache=0, mec_pipe0_reset=0, mec_pipe0_active=1, mec_halt=0, inst=xcc)
# NOTE: Wait for MEC to be ready. The kernel does udelay here as well.
time.sleep(0.05)
# Set 1 partition
if self.xccs > 1 and not self.adev.partial_boot: self.adev.psp._spatial_partition_cmd(1)
def fini_hw(self):
self._grbm_select(me=1, pipe=0, queue=0)
self.adev.regCP_HQD_DEQUEUE_REQUEST.write(0x2) # 1 - DRAIN_PIPE; 2 - RESET_WAVES
self._grbm_select()
self.adev.regGCVM_CONTEXT0_CNTL.write(0)
for xcc in range(self.xccs):
self._grbm_select(me=1, pipe=0, queue=0, inst=xcc)
if self.adev.regCP_HQD_ACTIVE.read(inst=xcc) & 1: self.adev.regCP_HQD_DEQUEUE_REQUEST.write(0x2, inst=xcc) # 1 - DRAIN_PIPE; 2 - RESET_WAVES
self._grbm_select(inst=xcc)
for xcc in range(self.xccs): self.adev.regGCVM_CONTEXT0_CNTL.write(0, inst=xcc)
def setup_ring(self, ring_addr:int, ring_size:int, rptr_addr:int, wptr_addr:int, eop_addr:int, eop_size:int, doorbell:int, pipe:int, queue:int,
aql:bool):
mqd = self.adev.mm.valloc(0x1000, uncached=True, contiguous=True)
aql:bool) -> int:
for xcc in range(self.xccs if aql else 1):
mqd = self.adev.mm.valloc(0x1000, uncached=True, contiguous=True)
struct_t = getattr(am, f"struct_v{self.adev.ip_ver[am.GC_HWIP][0]}_compute_mqd")
mqd_struct = struct_t(header=0xC0310800, cp_mqd_base_addr_lo=lo32(mqd.va_addr), cp_mqd_base_addr_hi=hi32(mqd.va_addr),
cp_hqd_persistent_state=self.adev.regCP_HQD_PERSISTENT_STATE.encode(preload_size=0x55, preload_req=1),
cp_hqd_pipe_priority=0x2, cp_hqd_queue_priority=0xf, cp_hqd_quantum=0x111,
cp_hqd_pq_base_lo=lo32(ring_addr>>8), cp_hqd_pq_base_hi=hi32(ring_addr>>8),
cp_hqd_pq_rptr_report_addr_lo=lo32(rptr_addr), cp_hqd_pq_rptr_report_addr_hi=hi32(rptr_addr),
cp_hqd_pq_wptr_poll_addr_lo=lo32(wptr_addr), cp_hqd_pq_wptr_poll_addr_hi=hi32(wptr_addr),
cp_hqd_pq_doorbell_control=self.adev.regCP_HQD_PQ_DOORBELL_CONTROL.encode(doorbell_offset=doorbell*2, doorbell_en=1),
cp_hqd_pq_control=self.adev.regCP_HQD_PQ_CONTROL.encode(rptr_block_size=5, unord_dispatch=0, queue_size=(ring_size//4).bit_length()-2,
**({'queue_full_en':1, 'slot_based_wptr':2, 'no_update_rptr':1} if aql else {})),
cp_hqd_ib_control=self.adev.regCP_HQD_IB_CONTROL.encode(min_ib_avail_size=0x3), cp_hqd_hq_status0=0x20004000,
cp_mqd_control=self.adev.regCP_MQD_CONTROL.encode(priv_state=1), cp_hqd_vmid=0, cp_hqd_aql_control=int(aql),
cp_hqd_eop_base_addr_lo=lo32(eop_addr>>8), cp_hqd_eop_base_addr_hi=hi32(eop_addr>>8),
cp_hqd_eop_control=self.adev.regCP_HQD_EOP_CONTROL.encode(eop_size=(eop_size//4).bit_length()-2))
for se in range(8): setattr(mqd_struct, f'compute_static_thread_mgmt_se{se}', 0xffffffff)
struct_t = getattr(am, f"struct_v{self.adev.ip_ver[am.GC_HWIP][0]}{'_compute' if self.adev.ip_ver[am.GC_HWIP][0] >= 10 else ''}_mqd")
mqd_struct = struct_t(header=0xC0310800, cp_mqd_base_addr_lo=lo32(mqd.va_addr), cp_mqd_base_addr_hi=hi32(mqd.va_addr),
cp_hqd_persistent_state=self.adev.regCP_HQD_PERSISTENT_STATE.encode(preload_size=0x55, preload_req=1),
cp_hqd_pipe_priority=0x2, cp_hqd_queue_priority=0xf, cp_hqd_quantum=0x111,
cp_hqd_pq_base_lo=lo32(ring_addr>>8), cp_hqd_pq_base_hi=hi32(ring_addr>>8),
cp_hqd_pq_rptr_report_addr_lo=lo32(rptr_addr), cp_hqd_pq_rptr_report_addr_hi=hi32(rptr_addr),
cp_hqd_pq_wptr_poll_addr_lo=lo32(wptr_addr), cp_hqd_pq_wptr_poll_addr_hi=hi32(wptr_addr),
cp_hqd_pq_doorbell_control=self.adev.regCP_HQD_PQ_DOORBELL_CONTROL.encode(doorbell_offset=doorbell*2, doorbell_en=1),
cp_hqd_pq_control=self.adev.regCP_HQD_PQ_CONTROL.encode(rptr_block_size=5, unord_dispatch=0, queue_size=(ring_size//4).bit_length()-2,
**({'queue_full_en':1, 'slot_based_wptr':2, 'no_update_rptr':xcc==0} if aql else {})),
cp_hqd_ib_control=self.adev.regCP_HQD_IB_CONTROL.encode(min_ib_avail_size=0x3), cp_hqd_hq_status0=0x20004000,
cp_mqd_control=self.adev.regCP_MQD_CONTROL.encode(priv_state=1), cp_hqd_vmid=0, cp_hqd_aql_control=int(aql),
cp_hqd_eop_base_addr_lo=lo32(eop_addr>>8), cp_hqd_eop_base_addr_hi=hi32(eop_addr>>8),
cp_hqd_eop_control=self.adev.regCP_HQD_EOP_CONTROL.encode(eop_size=(eop_size//4).bit_length()-2),
**({'compute_tg_chunk_size':1, 'compute_current_logic_xcc_id':xcc} if aql and self.xccs > 1 else {}))
for se in range(8 if self.adev.ip_ver[am.GC_HWIP][0] >= 10 else 4): setattr(mqd_struct, f'compute_static_thread_mgmt_se{se}', 0xffffffff)
# Copy mqd into memory
self.adev.vram.view(mqd.paddrs[0][0], ctypes.sizeof(mqd_struct))[:] = memoryview(mqd_struct).cast('B')
self.adev.gmc.flush_hdp()
# Copy mqd into memory
self.adev.vram.view(mqd.paddrs[0][0], ctypes.sizeof(mqd_struct))[:] = memoryview(mqd_struct).cast('B')
self.adev.gmc.flush_hdp()
self._grbm_select(me=1, pipe=pipe, queue=queue)
self._grbm_select(me=1, pipe=pipe, queue=queue, inst=xcc)
mqd_st_mv = to_mv(ctypes.addressof(mqd_struct), ctypes.sizeof(mqd_struct)).cast('I')
for i, reg in enumerate(range(self.adev.regCP_MQD_BASE_ADDR.addr[0], self.adev.regCP_HQD_PQ_WPTR_HI.addr[0] + 1)):
self.adev.wreg(reg, mqd_st_mv[0x80 + i])
self.adev.regCP_HQD_ACTIVE.write(0x1)
mqd_st_mv = to_mv(ctypes.addressof(mqd_struct), ctypes.sizeof(mqd_struct)).cast('I')
for i, reg in enumerate(range(self.adev.regCP_MQD_BASE_ADDR.addr[xcc], self.adev.regCP_HQD_PQ_WPTR_HI.addr[xcc] + 1)):
self.adev.wreg(reg, mqd_st_mv[0x80 + i])
self.adev.regCP_HQD_ACTIVE.write(0x1, inst=xcc)
self._grbm_select()
self._grbm_select(inst=xcc)
self.adev.reg(f"regCP_ME1_PIPE{pipe}_INT_CNTL").update(time_stamp_int_enable=1, generic0_int_enable=1)
self.adev.reg(f"regCP_ME1_PIPE{pipe}_INT_CNTL").update(time_stamp_int_enable=1, generic0_int_enable=1, inst=xcc)
return 0
def set_clockgating_state(self):
if hasattr(self.adev, 'regMM_ATC_L2_MISC_CG'): self.adev.regMM_ATC_L2_MISC_CG.write(enable=1, mem_ls_enable=1)
@@ -284,11 +320,13 @@ class AM_GFX(AM_IP):
self.adev.regCP_RB_WPTR_POLL_CNTL.update(poll_frequency=0x100, idle_poll_count=0x90, inst=xcc)
self.adev.regCP_INT_CNTL.update(cntx_busy_int_enable=1, cntx_empty_int_enable=1, cmp_busy_int_enable=1, gfx_idle_int_enable=1, inst=xcc)
self.adev.regSDMA0_RLC_CGCG_CTRL.update(cgcg_int_enable=1, inst=xcc)
self.adev.regSDMA1_RLC_CGCG_CTRL.update(cgcg_int_enable=1, inst=xcc)
if self.adev.ip_ver[am.GC_HWIP] >= (10,0,0):
self.adev.regSDMA0_RLC_CGCG_CTRL.update(cgcg_int_enable=1, inst=xcc)
self.adev.regSDMA1_RLC_CGCG_CTRL.update(cgcg_int_enable=1, inst=xcc)
self.adev.regRLC_CGTT_MGCG_OVERRIDE.update(perfmon_clock_state=1, gfxip_fgcg_override=0, gfxip_repeater_fgcg_override=0,
grbm_cgtt_sclk_override=0, rlc_cgtt_sclk_override=0, gfxip_mgcg_override=0, gfxip_cgls_override=0, gfxip_cgcg_override=0, inst=xcc)
feats_gfx11 = {'perfmon_clock_state':1, 'gfxip_repeater_fgcg_override':0} if self.adev.ip_ver[am.GC_HWIP] >= (11,0,0) else {}
self.adev.regRLC_CGTT_MGCG_OVERRIDE.update(**feats_gfx11, gfxip_fgcg_override=0, grbm_cgtt_sclk_override=0, rlc_cgtt_sclk_override=0,
gfxip_mgcg_override=0, gfxip_cgls_override=0, gfxip_cgcg_override=0, inst=xcc)
self.adev.regRLC_SAFE_MODE.write(message=0, cmd=1, inst=xcc)
@@ -305,10 +343,13 @@ class AM_GFX(AM_IP):
self.adev.reg(f"regCP_{cntl_reg}_CNTL").update(**{f"{eng_name.lower()}_pipe{pipe}_reset": 0 for pipe in range(pipe_cnt)}, inst=xcc)
for xcc in range(self.adev.gfx.xccs):
if self.adev.ip_ver[am.GC_HWIP] < (10,0,0):
self.adev.regCP_MEC_CNTL.update(mec_invalidate_icache=1, mec_me1_pipe0_reset=1, mec_me2_pipe0_reset=1, mec_me1_halt=1,mec_me2_halt=1,inst=xcc)
if self.adev.ip_ver[am.GC_HWIP] >= (12,0,0):
_config_helper(eng_name="PFP", cntl_reg="ME", eng_reg="PFP", pipe_cnt=1, xcc=xcc)
_config_helper(eng_name="ME", cntl_reg="ME", eng_reg="ME", pipe_cnt=1, xcc=xcc)
_config_helper(eng_name="MEC", cntl_reg="MEC_RS64", eng_reg="MEC_RS64", pipe_cnt=1, me=1, xcc=xcc)
if self.adev.ip_ver[am.GC_HWIP] >= (10,0,0):
_config_helper(eng_name="MEC", cntl_reg="MEC_RS64", eng_reg="MEC_RS64", pipe_cnt=1, me=1, xcc=xcc)
class AM_IH(AM_IP):
def init_sw(self):
@@ -330,15 +371,17 @@ class AM_IH(AM_IP):
self.adev.reg(f"regIH_DOORBELL_RPTR{suf}").write(offset=(am.AMDGPU_NAVI10_DOORBELL_IH + ring_id) * 2, enable=1)
self.adev.regIH_STORM_CLIENT_LIST_CNTL.update(client18_is_storm_client=1)
self.adev.regIH_INT_FLOOD_CNTL.update(flood_cntl_enable=1)
self.adev.regIH_MSI_STORM_CTRL.update(delay=3)
if self.adev.ip_ver[am.OSSSYS_HWIP] != (4,4,2):
self.adev.regIH_STORM_CLIENT_LIST_CNTL.update(client18_is_storm_client=1)
self.adev.regIH_INT_FLOOD_CNTL.update(flood_cntl_enable=1)
self.adev.regIH_MSI_STORM_CTRL.update(delay=3)
# toggle interrupts
for _, rwptr_vm, suf, ring_id in self.rings:
self.adev.reg(f"regIH_RB_CNTL{suf}").update(rb_enable=1, **({'enable_intr': 1} if ring_id == 0 else {}))
self.adev.soc.doorbell_enable(port=1, awid=0x0, awaddr_31_28_value=0x0, offset=am.AMDGPU_NAVI10_DOORBELL_IH*2, size=2)
if self.adev.ip_ver[am.NBIO_HWIP] != (7,9,0):
self.adev.soc.doorbell_enable(port=1, awid=0x0, awaddr_31_28_value=0x0, offset=am.AMDGPU_NAVI10_DOORBELL_IH*2, size=2)
def interrupt_handler(self):
_, rwptr_vm, suf, _ = self.rings[0]
@@ -353,37 +396,53 @@ class AM_IH(AM_IP):
class AM_SDMA(AM_IP):
def init_sw(self): self.sdma_name = "F32" if self.adev.ip_ver[am.SDMA0_HWIP] < (7,0,0) else "MCU"
def init_hw(self):
for pipe in range(2):
self.adev.reg(f"regSDMA{pipe}_WATCHDOG_CNTL").update(queue_hang_count=100) # 10s, 100ms per unit
self.adev.reg(f"regSDMA{pipe}_UTCL1_CNTL").update(resp_mode=3, redo_delay=9)
for pipe_id in range(1):
pipe = "" if self.adev.ip_ver[am.SDMA0_HWIP] < (5,0,0) else str(pipe_id)
# rd=noa, wr=bypass
self.adev.reg(f"regSDMA{pipe}_UTCL1_PAGE").update(rd_l2_policy=0x2, wr_l2_policy=0x3, **({'llc_noalloc':1} if self.sdma_name == "F32" else {}))
self.adev.reg(f"regSDMA{pipe}_{self.sdma_name}_CNTL").update(halt=0, **{f"{'th1_' if self.sdma_name == 'F32' else ''}reset":0})
self.adev.reg(f"regSDMA{pipe}_CNTL").update(ctxempty_int_enable=1, trap_enable=1)
self.adev.soc.doorbell_enable(port=2, awid=0xe, awaddr_31_28_value=0x3, offset=am.AMDGPU_NAVI10_DOORBELL_sDMA_ENGINE0*2, size=4)
if self.adev.ip_ver[am.SDMA0_HWIP] >= (6,0,0):
self.adev.reg(f"regSDMA{pipe}_WATCHDOG_CNTL").update(queue_hang_count=100) # 10s, 100ms per unit
self.adev.reg(f"regSDMA{pipe}_UTCL1_CNTL").update(resp_mode=3, redo_delay=9)
# rd=noa, wr=bypass
self.adev.reg(f"regSDMA{pipe}_UTCL1_PAGE").update(rd_l2_policy=2, wr_l2_policy=3, **({'llc_noalloc':1} if self.sdma_name == "F32" else {}))
self.adev.reg(f"regSDMA{pipe}_{self.sdma_name}_CNTL").update(halt=0, **{f"{'th1_' if self.sdma_name == 'F32' else ''}reset":0})
self.adev.reg(f"regSDMA{pipe}_CNTL").update(ctxempty_int_enable=1, trap_enable=1,
**({'utc_l1_enable':1} if self.adev.ip_ver[am.SDMA0_HWIP] <= (5,2,0) else {}))
if self.adev.ip_ver[am.NBIO_HWIP] == (7,9,0):
self.adev.regDOORBELL0_CTRL_ENTRY_1.write(bif_doorbell1_range_offset_entry=am.AMDGPU_NAVI10_DOORBELL_sDMA_ENGINE0*2,
bif_doorbell1_range_size_entry=4)
self.adev.soc.doorbell_enable(port=2, awid=0xe, awaddr_31_28_value=0x1, offset=0xe, size=4)
else: self.adev.soc.doorbell_enable(port=2, awid=0xe, awaddr_31_28_value=0x3, offset=am.AMDGPU_NAVI10_DOORBELL_sDMA_ENGINE0*2, size=4)
def fini_hw(self):
self.adev.regSDMA0_QUEUE0_RB_CNTL.update(rb_enable=0)
self.adev.regSDMA0_QUEUE0_IB_CNTL.update(ib_enable=0)
self.adev.regGRBM_SOFT_RESET.write(soft_reset_sdma0=1)
time.sleep(0.01)
self.adev.regGRBM_SOFT_RESET.write(0x0)
reg, inst = ("regSDMA_GFX", 0) if self.adev.ip_ver[am.SDMA0_HWIP] == (4,4,2) else ("regSDMA0_QUEUE0", 0)
def setup_ring(self, ring_addr:int, ring_size:int, rptr_addr:int, wptr_addr:int, doorbell:int, pipe:int, queue:int):
self.adev.reg(f"{reg}_RB_CNTL").update(rb_enable=0, inst=inst)
self.adev.reg(f"{reg}_IB_CNTL").update(ib_enable=0, inst=inst)
if self.adev.ip_ver[am.SDMA0_HWIP] >= (6,0,0):
self.adev.regGRBM_SOFT_RESET.write(soft_reset_sdma0=1)
time.sleep(0.01)
self.adev.regGRBM_SOFT_RESET.write(0x0)
def setup_ring(self, ring_addr:int, ring_size:int, rptr_addr:int, wptr_addr:int, doorbell:int, pipe:int, queue:int) -> int:
# Setup the ring
self.adev.reg(f"regSDMA{pipe}_QUEUE{queue}_MINOR_PTR_UPDATE").write(0x1)
self.adev.wreg_pair(f"regSDMA{pipe}_QUEUE{queue}_RB_RPTR", "", "_HI", 0)
self.adev.wreg_pair(f"regSDMA{pipe}_QUEUE{queue}_RB_WPTR", "", "_HI", 0)
self.adev.wreg_pair(f"regSDMA{pipe}_QUEUE{queue}_RB_BASE", "", "_HI", ring_addr >> 8)
self.adev.wreg_pair(f"regSDMA{pipe}_QUEUE{queue}_RB_RPTR_ADDR", "_LO", "_HI", rptr_addr)
self.adev.wreg_pair(f"regSDMA{pipe}_QUEUE{queue}_RB_WPTR_POLL_ADDR", "_LO", "_HI", wptr_addr)
self.adev.reg(f"regSDMA{pipe}_QUEUE{queue}_DOORBELL_OFFSET").update(offset=doorbell * 2)
self.adev.reg(f"regSDMA{pipe}_QUEUE{queue}_DOORBELL").update(enable=1)
self.adev.reg(f"regSDMA{pipe}_QUEUE{queue}_MINOR_PTR_UPDATE").write(0x0)
self.adev.reg(f"regSDMA{pipe}_QUEUE{queue}_RB_CNTL").write(rb_vmid=0, rptr_writeback_enable=1, rptr_writeback_timer=4,
**{f'{self.sdma_name.lower()}_wptr_poll_enable':1}, rb_size=(ring_size//4).bit_length()-1, rb_enable=1, rb_priv=1)
self.adev.reg(f"regSDMA{pipe}_QUEUE{queue}_IB_CNTL").update(ib_enable=1)
reg, inst = ("regSDMA_GFX", pipe*4+queue) if self.adev.ip_ver[am.SDMA0_HWIP] == (4,4,2) else (f"regSDMA{pipe}_QUEUE{queue}", 0)
self.adev.reg(f"{reg}_MINOR_PTR_UPDATE").write(0x1, inst=inst)
self.adev.wreg_pair(f"{reg}_RB_RPTR", "", "_HI", 0, inst=inst)
self.adev.wreg_pair(f"{reg}_RB_WPTR", "", "_HI", 0, inst=inst)
self.adev.wreg_pair(f"{reg}_RB_BASE", "", "_HI", ring_addr >> 8, inst=inst)
self.adev.wreg_pair(f"{reg}_RB_RPTR_ADDR", "_LO", "_HI", rptr_addr, inst=inst)
self.adev.wreg_pair(f"{reg}_RB_WPTR_POLL_ADDR", "_LO", "_HI", wptr_addr, inst=inst)
self.adev.reg(f"{reg}_DOORBELL_OFFSET").update(offset=doorbell * 2, inst=inst)
self.adev.reg(f"{reg}_DOORBELL").update(enable=1, inst=inst)
self.adev.reg(f"{reg}_MINOR_PTR_UPDATE").write(0x0, inst=inst)
self.adev.reg(f"{reg}_RB_CNTL").write(**({f'{self.sdma_name.lower()}_wptr_poll_enable':1} if self.adev.ip_ver[am.SDMA0_HWIP] != (4,4,2) else {}),
rb_vmid=0, rptr_writeback_enable=1, rptr_writeback_timer=4, rb_enable=1, rb_priv=1, rb_size=(ring_size//4).bit_length()-1, inst=inst)
self.adev.reg(f"{reg}_IB_CNTL").update(ib_enable=1, inst=inst)
return self.adev.reg(f"{reg}_RB_WPTR").read() | (self.adev.reg(f"{reg}_RB_WPTR_HI").read() << 32)
class AM_PSP(AM_IP):
def init_sw(self):
@@ -403,10 +462,10 @@ class AM_PSP(AM_IP):
self.ring_size = 0x10000
self.ring_paddr = self.adev.mm.palloc(self.ring_size, zero=False, boot=True)
self.max_tmr_size = 0x1300000
self.boot_time_tmr = self.adev.ip_ver[am.GC_HWIP] >= (12,0,0)
if not self.boot_time_tmr:
self.tmr_paddr = self.adev.mm.palloc(self.max_tmr_size, align=am.PSP_TMR_ALIGNMENT, zero=False, boot=True)
self.max_tmr_size, self.tmr_size = 0x1300000, 0
self.boot_time_tmr = self.adev.ip_ver[am.MP0_HWIP] in {(13,0,6), (13,0,14), (14,0,2), (14,0,3)}
self.autoload_tmr = self.adev.ip_ver[am.MP0_HWIP] not in {(13,0,6), (13,0,14)}
self.tmr_paddr = self.adev.mm.palloc(self.max_tmr_size, align=am.PSP_TMR_ALIGNMENT, zero=False, boot=True) if not self.boot_time_tmr else 0
def init_hw(self):
spl_key = am.PSP_FW_TYPE_PSP_SPL if self.adev.ip_ver[am.MP0_HWIP] >= (14,0,0) else am.PSP_FW_TYPE_PSP_KDB
@@ -420,14 +479,16 @@ class AM_PSP(AM_IP):
while not self.is_sos_alive(): time.sleep(0.01)
self._ring_create()
self._tmr_init()
if am.PSP_FW_TYPE_PSP_TOC in self.adev.fw.sos_fw: self._tmr_init()
# SMU fw should be loaded before TMR.
self._load_ip_fw_cmd(*self.adev.fw.smu_psp_desc)
if not self.boot_time_tmr: self._tmr_load_cmd()
if hasattr(self.adev.fw, 'smu_psp_desc'): self._load_ip_fw_cmd(*self.adev.fw.smu_psp_desc)
if not self.boot_time_tmr or not self.autoload_tmr: self._tmr_load_cmd()
for psp_desc in self.adev.fw.descs: self._load_ip_fw_cmd(*psp_desc)
self._rlc_autoload_cmd()
if self.adev.ip_ver[am.GC_HWIP] >= (11,0,0): self._rlc_autoload_cmd()
else: self._load_ip_fw_cmd([am.GFX_FW_TYPE_REG_LIST], self.adev.fw.sos_fw[am.PSP_FW_TYPE_PSP_RL])
def is_sos_alive(self): return self.adev.reg(f"{self.reg_pref}_81").read() != 0x0
@@ -507,11 +568,13 @@ class AM_PSP(AM_IP):
self._ring_submit(cmd)
def _tmr_load_cmd(self) -> am.struct_psp_gfx_cmd_resp:
tmr_paddr = self.adev.paddr2xgmi(self.tmr_paddr) if self.tmr_paddr else 0
cmd = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_SETUP_TMR)
cmd.cmd.cmd_setup_tmr.buf_phy_addr_hi, cmd.cmd.cmd_setup_tmr.buf_phy_addr_lo = data64(self.adev.paddr2mc(self.tmr_paddr))
cmd.cmd.cmd_setup_tmr.system_phy_addr_hi, cmd.cmd.cmd_setup_tmr.system_phy_addr_lo = data64(self.adev.paddr2xgmi(self.tmr_paddr))
cmd.cmd.cmd_setup_tmr.buf_phy_addr_hi, cmd.cmd.cmd_setup_tmr.buf_phy_addr_lo = data64(self.adev.paddr2mc(self.tmr_paddr) if self.tmr_paddr else 0)
cmd.cmd.cmd_setup_tmr.system_phy_addr_hi, cmd.cmd.cmd_setup_tmr.system_phy_addr_lo = data64(tmr_paddr)
cmd.cmd.cmd_setup_tmr.bitfield.virt_phy_addr = 1
cmd.cmd.cmd_setup_tmr.buf_size = self.tmr_size
cmd.cmd.cmd_setup_tmr.buf_size = self.tmr_size if self.tmr_paddr else 0
return self._ring_submit(cmd)
def _load_toc_cmd(self, toc_size:int) -> am.struct_psp_gfx_cmd_resp:
@@ -520,4 +583,9 @@ class AM_PSP(AM_IP):
cmd.cmd.cmd_load_toc.toc_size = toc_size
return self._ring_submit(cmd)
def _spatial_partition_cmd(self, mode):
cmd = am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_SRIOV_SPATIAL_PART)
cmd.cmd.cmd_spatial_part.mode = mode
return self._ring_submit(cmd)
def _rlc_autoload_cmd(self): return self._ring_submit(am.struct_psp_gfx_cmd_resp(cmd_id=am.GFX_CMD_ID_AUTOLOAD_RLC))
+7 -1
View File
@@ -79,7 +79,12 @@ def import_pmc(ip) -> dict[str, tuple[str, int]]:
def import_asic_regs(prefix:str, version:tuple[int, ...], cls=AMDReg) -> dict[str, AMDReg]:
def _split_name(name): return name[:(pos:=next((i for i,c in enumerate(name) if c.isupper()), len(name)))], name[pos:]
def _extract_regs(txt):
return {m.group(1): int(m.group(2), 0) for line in txt.splitlines() if (m:=re.match(r'#define\s+(\S+)\s+(0x[\da-fA-F]+|\d+)', line))}
x = {}
for k,v in {m.group(1): int(m.group(2), 0) for line in txt.splitlines() if (m:=re.match(r'#define\s+(\S+)\s+(0x[\da-fA-F]+|\d+)', line))}.items():
if k.startswith('VM_') or k.startswith('MC_'): x[prefix.upper()[:2]+k] = v
elif k.startswith('regVM_') or k.startswith('regMC_'): x["reg"+prefix.upper()[:2]+k[3:]] = v
else: x[k] = v
return x
def _download_file(ver, suff) -> str:
dir_prefix = {"osssys": "oss"}.get(prefix, prefix)
fetch_name, file_name = f"{prefix}_{'_'.join(map(str, ver))}_{suff}.h", f"{prefix}_{'_'.join(map(str, version))}_{suff}.h"
@@ -98,6 +103,7 @@ def import_asic_regs(prefix:str, version:tuple[int, ...], cls=AMDReg) -> dict[st
for field_name, field_mask in sh_masks.items():
if not ('__' in field_name and field_name.endswith('_MASK')): continue
reg_name, reg_field_name = field_name[:-len('_MASK')].split('__')
if reg_name.startswith('MC_') or reg_name.startswith('VM_'): reg_name = f"{prefix.upper()[:2]}{reg_name}"
fields[reg_name][reg_field_name.lower()] = ((field_mask & -field_mask).bit_length()-1, field_mask.bit_length()-1)
# NOTE: Some registers like regGFX_IMU_FUSESTRAP in gc_11_0_0 are missing base idx, just skip them
+5 -7
View File
@@ -1,5 +1,5 @@
import ctypes, itertools, re, functools, os
from tinygrad.helpers import flatten, unwrap
from tinygrad.helpers import unwrap
from tinygrad.runtime.autogen import libclang as clang # use REGEN=1 to regenerate libclang bindings
def unwrap_cursor(c: clang.CXCursor) -> clang.CXCursor:
@@ -91,7 +91,7 @@ fns, specs = (clang.CXType_FunctionProto, clang.CXType_FunctionNoProto), (clang.
# https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families
arc_families = ['alloc', 'copy', 'mutableCopy', 'new']
def gen(dll, files, args=[], prolog=[], rules=[], epilog=[], recsym=False, use_errno=False, anon_names={}, types={}, parse_macros=True):
def gen(name, dll, files, args=[], prolog=[], rules=[], epilog=[], recsym=False, errno=False, anon_names={}, types={}, parse_macros=True, paths=[]):
macros, lines, anoncnt, types, objc = [], [], itertools.count().__next__, {k:(v,True) for k,v in types.items()}, False
def tname(t, suggested_name=None, typedef=None) -> str:
suggested_name = anon_names.get(f"{loc_file(loc(decl:=clang.clang_getTypeDeclaration(t)))}:{loc_line(loc(decl))}", suggested_name)
@@ -257,11 +257,9 @@ def gen(dll, files, args=[], prolog=[], rules=[], epilog=[], recsym=False, use_e
lines, types = rollback
clang.clang_disposeTranslationUnit(tu)
clang.clang_disposeIndex(idx)
main = (f"# mypy: ignore-errors\nimport ctypes{', os' if any('os' in s for s in dll) else ''}\n"
"from tinygrad.helpers import unwrap\nfrom tinygrad.runtime.support.c import Struct, CEnum, _IO, _IOW, _IOR, _IOWR\n" + '\n'.join([*prolog,
*(["from ctypes.util import find_library"]*any('find_library' in s for s in dll)), *(["from tinygrad.runtime.support import objc"]*objc),
*(["def dll():",*flatten([[f" try: return ctypes.CDLL(unwrap({d}){', use_errno=True' if use_errno else ''})",' except: pass'] for d in dll]),
" return None", "dll = dll()\n"]*bool(dll)), *lines]) + '\n')
main = '\n'.join(["# mypy: ignore-errors", "import ctypes", "from tinygrad.runtime.support.c import DLL, Struct, CEnum, _IO, _IOW, _IOR, _IOWR",
*prolog, *(["from tinygrad.runtime.support import objc"]*objc),
*([f"dll = DLL('{name}', {dll}{f', {paths}'*bool(paths)}{', use_errno=True'*errno})"] if dll else []), *lines]) + '\n'
macros = [r for m in macros if (r:=functools.reduce(lambda s,r:re.sub(r[0], r[1], s), rules + base_rules, m))]
while True:
try:
+38 -2
View File
@@ -1,6 +1,6 @@
import ctypes, functools, sys
import ctypes, functools, os, pathlib, re, sys, sysconfig
from typing import TYPE_CHECKING
from tinygrad.helpers import flatten, WIN
from tinygrad.helpers import flatten, getenv, DEBUG, OSX, WIN
from _ctypes import _SimpleCData
def _do_ioctl(__idir, __base, __nr, __struct, __fd, *args, __payload=None, **kwargs):
@@ -37,6 +37,42 @@ def CEnum(typ: type[ctypes._SimpleCData]):
return _CEnum
class DLL(ctypes.CDLL):
@staticmethod
def findlib(nm:str, paths:list[str], extra_paths=[]):
if nm == 'libc' and OSX: return '/usr/lib/libc.dylib'
if pathlib.Path(path:=getenv(nm.replace('-', '_').upper()+"_PATH", '')).is_file(): return path
for p in paths:
libpaths = {"posix": ["/usr/lib", "/usr/local/lib"], "nt": os.environ['PATH'].split(os.pathsep),
"darwin": ["/opt/homebrew/lib", f"/System/Library/Frameworks/{p}.framework"],
'linux': ['/lib', f"/lib/{sysconfig.get_config_var('MULTIARCH')}"]}
if (pth:=pathlib.Path(p)).is_absolute():
if pth.is_file(): return p
else: continue
for pre in (pathlib.Path(pre) for pre in libpaths.get(os.name, []) + libpaths.get(sys.platform, []) + extra_paths):
if not pre.is_dir(): continue
if WIN or OSX:
for base in ([f"lib{p}.dylib", f"{p}.dylib", str(p)] if OSX else [f"{p}.dll"]):
if (l:=pre / base).is_file() or (OSX and 'framework' in str(l) and l.is_symlink()): return str(l)
else:
for l in (l for l in pre.iterdir() if l.is_file() and re.fullmatch(f"lib{p}\\.so\\.?[0-9]*", l.name)):
# filter out linker scripts
with open(l, 'rb') as f:
if f.read(4) == b'\x7FELF': return str(l)
def __init__(self, nm:str, paths:str|list[str], extra_paths=[], emsg="", **kwargs):
self.nm, self.emsg, self.loaded = nm, emsg, False
if (path:= DLL.findlib(nm, paths if isinstance(paths, list) else [paths], extra_paths if isinstance(extra_paths, list) else [extra_paths])):
if DEBUG >= 3: print(f"loading {nm} from {path}")
try:
super().__init__(path, **kwargs)
self.loaded = True
except OSError as e: self.emsg = str(e)
def __getattr__(self, nm):
if not self.loaded: raise AttributeError(f"failed to load library {self.nm}: " + (self.emsg or f"try setting {self.nm.upper()+'_PATH'}?"))
return super().__getattr__(nm)
# supports gcc (C11) __attribute__((packed))
if TYPE_CHECKING: Struct = ctypes.Structure
else:
-173
View File
@@ -1,173 +0,0 @@
from __future__ import annotations
import resource, ctypes, weakref, functools, itertools
from tinygrad.runtime.autogen import ib
from typing import Iterator
from dataclasses import dataclass
from weakref import WeakKeyDictionary
from tinygrad.device import Buffer, DMACPURef, DMAFdRef
from tinygrad.helpers import getenv, round_up, DEBUG
DEFAULT_PORT, DEFAULT_GID = getenv("DEFAULT_PORT", 1), getenv("DEFAULT_GID", 3) # DEFAULT_GID=0 for RXE
IOVA_ALIGN = resource.getpagesize()
def checkz(x, ret=None):
if x != 0: raise RuntimeError(f'{x} != 0 (errno {ctypes.get_errno()})')
return ret
@dataclass(frozen=True)
class SGE:
dst_iova: int
dst_key: int
src_iova: int
src_key: int
size: int
class IBCtx:
def __init__(self, idx:int):
# Open the device (aka Host Channel Adapter in ib-speak)
devs = ib.ibv_get_device_list(ctypes.byref(ndevs:=ctypes.c_int32()))
if idx >= ndevs.value: raise IndexError(f"{idx} > {ndevs.value}")
self.ctx = ib.ibv_open_device(devs[idx])
ib.ibv_free_device_list(devs)
# HACK: remove this (and all usage of `ctx.contents.ops`) when clang2py can deal with `static inline` wrapper-functions
self.vctx = ctypes.cast(ctypes.addressof(self.ctx.contents) - ib.struct_verbs_context.context.offset, ctypes.POINTER(ib.struct_verbs_context))
# Get attributes. Something like port_attr.max_msg_sz sound like it might requre taking the min of host's and remote's attributes if they differ
self.device_attr = checkz(ib.ibv_query_device(self.ctx, ctypes.byref(da:=ib.struct_ibv_device_attr())), da)
self.port_attr = checkz(self.vctx.contents.query_port(self.ctx, DEFAULT_PORT, ctypes.byref(pa:=ib.struct_ibv_port_attr()), ctypes.sizeof(pa)), pa)
self.gid_attr = checkz(ib.ibv_query_gid(self.ctx, DEFAULT_PORT, DEFAULT_GID, ctypes.byref(ga:=ib.union_ibv_gid())), ga)
# Allocate protection domain
self.pd = ib.ibv_alloc_pd(self.ctx)
self.next_iova: int = IOVA_ALIGN # don't start at zero (nullptr)
# weakref(buf) => (iova, mr, mr_dealloc). mr_dealloc is kept here to avoid double freeing mrs that are deallocated in __del__
self.mrs: WeakKeyDictionary[Buffer, tuple[int, ctypes._Pointer[ib.struct_ibv_mr], weakref.finalize]] = WeakKeyDictionary()
# Default soft fd limit is 1024, which is not enough, set soft to hard (maximum allowed by the os)
IBCtx.rlimit_fix()
def __del__(self):
# must deallocate all mrs in protection domain before deallocating the protection domain
if hasattr(self, "mrs"): [fin() for _,_,fin in self.mrs.values()]
if hasattr(self, "pd"): ib.ibv_dealloc_pd(self.pd)
if hasattr(self, "ctx"): ib.ibv_close_device(self.ctx)
@functools.cache # run once
@staticmethod
def rlimit_fix():
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard))
if DEBUG>=2: print(f"IB: Increased fd limit from {soft} to {hard}")
def alloc_iova(self, size:int, required_offset:int):
iova = round_up(self.next_iova - required_offset, IOVA_ALIGN) + required_offset
self.next_iova = iova + size
return iova
def reg(self, buf:Buffer) -> tuple[int, ctypes._Pointer[ib.struct_ibv_mr]]:
buf = buf.base
if buf not in self.mrs:
if buf.nbytes > self.device_attr.max_mr_size: raise RuntimeError(f"Buffer too big: {buf.nbytes:#x} > {self.device_attr.max_mr_size:#x}")
if len(self.mrs) >= self.device_attr.max_mr: raise RuntimeError(f"Out of memory region cap: {len(self.mrs)} >= {self.device_attr.max_mr}")
# Local read is implied (but still have to create the memory region, except for short sends/writes with IBV_SEND_INLINE that are inlined by cpu)
mr_flags = ib.IBV_ACCESS_LOCAL_WRITE | ib.IBV_ACCESS_REMOTE_READ | ib.IBV_ACCESS_REMOTE_WRITE
match (dmaref:=buf.as_dmaref()):
case DMACPURef():
iova = self.alloc_iova(dmaref.size, dmaref.addr % IOVA_ALIGN)
mr = ib.ibv_reg_mr_iova2(self.pd, ctypes.c_void_p(dmaref.addr), dmaref.size, iova, mr_flags)
case DMAFdRef():
iova = self.alloc_iova(dmaref.size, dmaref.offset % IOVA_ALIGN)
mr = ib.ibv_reg_dmabuf_mr(self.pd, dmaref.offset, dmaref.size, iova, dmaref.fd, mr_flags)
case _: raise RuntimeError(f"Unknown type of dma ref: {dmaref}")
if not mr: raise RuntimeError(f"Couldn't register memory region for {buf} {dmaref} (errno={ctypes.get_errno()})")
self.mrs[buf] = (iova, mr, weakref.finalize(buf, ib.ibv_dereg_mr, mr))
return self.mrs[buf][0:2]
class IBConn:
def __init__(self, ctx:IBCtx):
self.ctx = ctx
# Create Completion Channel. It is a file descriptor that kernel sends notifications through, not a thing in infiniband spec, just linux-ism
self.comp_channel = ib.ibv_create_comp_channel(self.ctx.ctx)
# Create Completion Queue. When a Work Request with signaled flag is completed a Completion Queue Entry is pushed onto this queue
self.cq = ib.ibv_create_cq(self.ctx.ctx, _capacity:=256, _cq_context:=None, self.comp_channel, _comp_vector:=0)
self.pending_wrids: set[int] = set()
self.wrid_num: Iterator[int] = itertools.count(0) # wc_id is uint64, this will never overflow
# Create Queue Pair. It's the closest thing to a socket in infiniband with QP num being the closest thing to a port, except it's allocated by hca
qp_init_attrs_cap = ib.struct_ibv_qp_cap(max_send_wr=1024, max_recv_wr=64, max_send_sge=8, max_recv_sge=8, max_inline_data=64)
qp_init_attrs = ib.struct_ibv_qp_init_attr(send_cq=self.cq, recv_cq=self.cq, cap=qp_init_attrs_cap, qp_type=ib.IBV_QPT_RC) # Reliable Connection
self.qp = ib.ibv_create_qp(self.ctx.pd, ctypes.byref(qp_init_attrs))
self.qp_cap = qp_init_attrs.cap
# The most important thing about QPs is their state, when a new QP is created it's in the RESET state, before it can be properly used it has to go
# through Init, Ready To Receive, Ready To Send. A good docs on QP state machine: https://www.rdmamojo.com/2012/05/05/qp-state-machine/
# INIT
qp_access_flags = ib.IBV_ACCESS_REMOTE_WRITE | ib.IBV_ACCESS_REMOTE_READ
qpa = ib.struct_ibv_qp_attr(qp_state=ib.IBV_QPS_INIT, port_num=DEFAULT_PORT, qp_access_flags=qp_access_flags)
checkz(ib.ibv_modify_qp(self.qp, qpa, ib.IBV_QP_STATE | ib.IBV_QP_PORT | ib.IBV_QP_ACCESS_FLAGS | ib.IBV_QP_PKEY_INDEX))
self.gid, self.qp_num = bytes(self.ctx.gid_attr.raw), self.qp.contents.qp_num
# Exchange GID and QP num with remote. At least in RoCEv2 gid can be guessed from remote's ip, QP num can't.
def connect(self, remote_gid:bytes, remote_qp_num:int):
# RTR
qp_ah_attr_grh = ib.struct_ibv_global_route(hop_limit=1, dgid=ib.union_ibv_gid(raw=(ctypes.c_ubyte * 16)(*remote_gid)), sgid_index=DEFAULT_GID)
qp_ah_attr = ib.struct_ibv_ah_attr(is_global=1, port_num=DEFAULT_PORT, grh=qp_ah_attr_grh)
qpa = ib.struct_ibv_qp_attr(qp_state=ib.IBV_QPS_RTR, path_mtu=ib.IBV_MTU_4096, dest_qp_num=remote_qp_num, rq_psn=0, max_dest_rd_atomic=1,
min_rnr_timer=12, ah_attr=qp_ah_attr)
checkz(ib.ibv_modify_qp(self.qp, qpa, ib.IBV_QP_STATE | ib.IBV_QP_PATH_MTU | ib.IBV_QP_DEST_QPN | ib.IBV_QP_RQ_PSN | \
ib.IBV_QP_MAX_DEST_RD_ATOMIC | ib.IBV_QP_MIN_RNR_TIMER | ib.IBV_QP_AV))
# RTS
qpa = ib.struct_ibv_qp_attr(qp_state=ib.IBV_QPS_RTS, timeout=14, retry_cnt=7, rnr_retry=7, sq_psn=0, max_rd_atomic=1)
checkz(ib.ibv_modify_qp(self.qp, qpa, ib.IBV_QP_STATE | ib.IBV_QP_TIMEOUT | ib.IBV_QP_RETRY_CNT | ib.IBV_QP_RNR_RETRY | ib.IBV_QP_SQ_PSN | \
ib.IBV_QP_MAX_QP_RD_ATOMIC))
def __del__(self):
self.wait_cq() # need to wait for **everything** to complete before it's safe to dealloc queues and stuff
ib.ibv_destroy_qp(self.qp)
ib.ibv_destroy_cq(self.cq)
ib.ibv_destroy_comp_channel(self.comp_channel)
def next_wrid(self):
self.pending_wrids.add(wrid:=next(self.wrid_num))
return wrid
def wait_cq(self, wr_id: int|None=None):
while (wr_id in self.pending_wrids) if wr_id is not None else self.pending_wrids:
if self.ctx.ctx.contents.ops.poll_cq(self.cq, _num_entries:=1, ctypes.byref(wc:=ib.struct_ibv_wc())):
if wc.status != ib.IBV_WC_SUCCESS:
raise RuntimeError(f'Work Request completed with error: wr_id={wc.wr_id} status={ib.enum_ibv_wc_status.get(wc.status, wc.status)}')
self.pending_wrids.remove(wc.wr_id)
def rdma_write(self, sgl:list[SGE]):
swr: ctypes._Pointer[ib.struct_ibv_send_wr]|None = None
swr_cnt, wr_id = 0, self.next_wrid()
def _post():
nonlocal swr, swr_cnt, wr_id
if swr is not None:
# The swr can be freed when this returns, the memory that sge points to can be unmapped after work completion is retrieved from cq
checkz(self.ctx.ctx.contents.ops.post_send(self.qp, swr, ctypes.byref(_bad_wr:=ctypes.POINTER(ib.struct_ibv_send_wr)())))
# TODO: async
self.wait_cq(wr_id)
swr, swr_cnt, wr_id = None, 0, self.next_wrid()
# Everything is in reverse for elegant chaining
for sg in reversed(sgl):
# Message size limit (max 2GB per ib spec, 1GB on tinybox mellanoxes) applies to both scatter-gather entries and entire wrs
for off in reversed(range(0, sg.size, self.ctx.port_attr.max_msg_sz)):
# Scatter-Gather Entry for local memory
sge = ctypes.pointer(ib.struct_ibv_sge(addr=sg.src_iova+off, length=min(sg.size-off, self.ctx.port_attr.max_msg_sz), lkey=sg.src_key))
# RDMA struct for remote memory
wr = ib.struct_ibv_send_wr_wr(rdma=ib.struct_ibv_send_wr_wr_rdma(remote_addr=sg.dst_iova+off, rkey=sg.dst_key))
# Signal (with chosen work request id) if it's the last wr (first in the loop since it's reversed)
wid, flags = (wr_id, ib.IBV_SEND_SIGNALED) if swr is None else (0, 0)
# Create Send Request
swr = ctypes.pointer(ib.struct_ibv_send_wr(opcode=ib.IBV_WR_RDMA_WRITE, sg_list=sge, num_sge=1, wr=wr, wr_id=wid, send_flags=flags, next=swr))
# Flush if queue is being overrun
if (swr_cnt:=swr_cnt + 1) >= self.qp_cap.max_send_wr: _post()
_post()
-25
View File
@@ -1,25 +0,0 @@
import ctypes.util, os, sys
from tinygrad.helpers import DEBUG, OSX, getenv, system
if sys.platform == 'win32':
# Windows llvm distribution doesn't seem to add itself to PATH or anywhere else where it can be easily retrieved from.
# winget also doesn't have something like `brew --prefix llvm` so just hardcode default installation path with an option to override
LLVM_PATH = getenv('LLVM_PATH', 'C:\\Program Files\\LLVM\\bin\\LLVM-C.dll')
if not os.path.exists(LLVM_PATH):
raise FileNotFoundError('LLVM not found, you can install it with `winget install LLVM.LLVM` or point at a custom dll with LLVM_PATH')
elif OSX:
# Will raise FileNotFoundError if brew is not installed
# `brew --prefix` will return even if formula is not installed
if not os.path.exists(brew_prefix:=system("brew --prefix llvm@20")):
raise FileNotFoundError('LLVM not found, you can install it with `brew install llvm@20`')
LLVM_PATH: str|None = os.path.join(brew_prefix, 'lib', 'libLLVM.dylib')
else:
LLVM_PATH = ctypes.util.find_library('LLVM')
# use newer LLVM if possible
for ver in reversed(range(14, 21+1)):
if LLVM_PATH is not None: break
LLVM_PATH = ctypes.util.find_library(f'LLVM-{ver}')
if LLVM_PATH is None:
raise FileNotFoundError("No LLVM library found on the system. Install it via your distro's package manager and ensure it's findable as 'LLVM'")
if DEBUG>=3: print(f'Using LLVM at {repr(LLVM_PATH)}')
-18
View File
@@ -1,18 +0,0 @@
import ctypes.util, os, platform, sysconfig
from tinygrad.helpers import system, OSX
WEBGPU_PATH: str | None
if OSX:
if not os.path.exists(brew_prefix:=system("brew --prefix dawn")):
raise FileNotFoundError('dawn library not found. Install it with `brew tap wpmed92/dawn && brew install dawn`')
WEBGPU_PATH = os.path.join(brew_prefix, 'lib', 'libwebgpu_dawn.dylib')
elif platform.system() == "Windows":
if not os.path.exists(pydawn_path:=os.path.join(sysconfig.get_paths()["purelib"], "pydawn")):
raise FileNotFoundError("dawn library not found. Install it with `pip install dawn-python`")
WEBGPU_PATH = os.path.join(pydawn_path, "lib", "libwebgpu_dawn.dll")
else:
if (WEBGPU_PATH:=ctypes.util.find_library('webgpu_dawn')) is None:
raise FileNotFoundError("dawn library not found. " +
"Install it with `sudo curl -L https://github.com/wpmed92/pydawn/releases/download/v0.3.0/" +
f"libwebgpu_dawn_{platform.machine()}.so -o /usr/lib/libwebgpu_dawn.so`")
+5
View File
@@ -218,6 +218,11 @@ multi_pm = PatternMatcher([
lambda multi,device,red: multi.src[0].allreduce(red.arg, device).multi(axis=multi.axis)),
(UPat((Ops.CAST, Ops.BITCAST, Ops.CONTIGUOUS, Ops.DETACH, Ops.CONTIGUOUS_BACKWARD),
src=(UPat(Ops.MULTI, name="multi"), ), name="root"), passthrough_multi),
# multi supports custom kernels with CUSTOM_KERNEL + AFTER
(UPat(Ops.CUSTOM_KERNEL, src=UPat(Ops.MULTI), name="ck"),
lambda ck: ck.replace(src=tuple(m.src[0] for m in ck.src))),
(UPat(Ops.AFTER, src=(UPat(Ops.MULTI, name="multi"), UPat(Ops.CUSTOM_KERNEL)), name="a"),
lambda multi,a: a.replace(src=(multi.src[0],)+a.src[1:]).multi(multi.axis))
])+replace_allreduce
def get_multi_map(big_sink:UOp) -> dict[UOp, UOp]:
+7
View File
@@ -63,10 +63,17 @@ mop_cleanup = PatternMatcher([
lambda x,x2: x.replace(src=(x2.src[0], x.src[1])) if x.tag is None and x2.tag is None else None),
])
def resolve_custom_kernel(ck:UOp) -> UOp:
placeholders = [UOp.placeholder_like(s, slot=i) for i,s in enumerate(ck.src)]
return UOp(Ops.KERNEL, src=ck.src, arg=Kernel(ck.arg.fxn(*placeholders)))
earliest_rewrites = mop_cleanup+PatternMatcher([
# just removing it works...
(UPat((Ops.DETACH, Ops.CONTIGUOUS_BACKWARD), name="x"), lambda x: x.src[0]),
# resolve custom kernels
(UPat(Ops.CUSTOM_KERNEL, name="ck"), resolve_custom_kernel),
# remove CONTIGUOUS if the BUFFER is already contiguous
(UPat(Ops.BUFFER).f(Ops.RESHAPE, allow_any_len=True, name="r").f(Ops.CONTIGUOUS, name="c"), lambda r,c: r.replace(tag=c.tag)),
+12 -11
View File
@@ -10,7 +10,7 @@ from tinygrad.helpers import IMAGE, WINO, Metadata, TRACEMETA, ceildiv, fetch, p
from tinygrad.helpers import suppress_finalizing, disable_gc
from tinygrad.gradient import compute_gradient
from tinygrad.mixin import OpMixin
from tinygrad.mixin.movement import _align_left
from tinygrad.mixin.movement import _align_left, _flat_to_grouped
from tinygrad.uop.ops import smax, smin, resolve, UOp, Ops, sint, identity_element, all_metadata, _index_to_concrete_int, sint_to_uop, Variable
from tinygrad.engine.schedule import ScheduleItem, complete_create_schedule_with_vars
from tinygrad.device import Device, Buffer
@@ -91,8 +91,6 @@ def _masked_setitem(target:Tensor, values:Tensor, mask:Tensor, axes:tuple[int, .
# select from values for each True element in mask else select from target
return mask.where(values, target)
# `(padding_left, padding_right, padding_top, padding_bottom, ...)` -> `(..., (padding_top, padding_bottom), (padding_left, padding_right))`
def _flat_to_grouped(padding:Sequence[sint]) -> tuple[tuple[sint, sint], ...]: return tuple(zip(padding[-2::-2], padding[::-2]))
ReductionStr = Literal["mean", "sum", "none"]
@@ -1018,7 +1016,8 @@ class Tensor(OpMixin):
# clear contexts
for t,g in zip(tensors_need_grad, self.gradient(*tensors_need_grad, gradient=gradient, materialize_grads=True)):
assert g.shape == t.shape, f"grad shape must match tensor shape, {g.shape!r} != {t.shape!r}"
t.grad = g if t.grad is None else (t.grad + g)
if t.grad is None: t.grad = g
else: t.grad.assign(t.grad + g)
return self
# ***** movement low level ops *****
@@ -1068,7 +1067,7 @@ class Tensor(OpMixin):
X, pads = self, tuple((smax(pB,0), smax(pA,0)) for pB,pA in pX)
if mode == "constant":
def _constant(x:Tensor,px,v) -> Tensor:
return x._apply_uop(UOp.pad, arg=px) if v == 0 else (x._apply_uop(UOp.pad, arg=px)+Tensor.ones_like(x)._apply_uop(UOp.pad, arg=px).where(0,v))
return x._mop(Ops.PAD, px) if v == 0 else (x._mop(Ops.PAD, px)+Tensor.ones_like(x)._mop(Ops.PAD, px).where(0,v))
return _constant(X, pX, value) if all(resolve(p >= 0) for p in flatten(pX)) else \
_constant(X.shrink(tuple((-smin(pB,0),smin(pA+s,s)) for (pB,pA),s in zip(pX, X.shape))), pads, value)
assert all_int(self.shape), f"does not support symbolic shape {self.shape}"
@@ -1333,10 +1332,11 @@ class Tensor(OpMixin):
print("\\n".join([repr(x.numpy()) for x in split]))
```
"""
assert all_int(self.shape), f"does not support symbolic shape {self.shape}"
dim = self._resolve_dim(dim)
if isinstance(sizes, int): sizes = [min(sizes, self.shape[dim]-i) for i in range(0, max(1, self.shape[dim]), max(1, sizes))]
assert sum(sizes) == self.shape[dim], f"expect sizes to sum exactly to {self.shape[dim]}, but got {sum(sizes)}"
dim_sz = self.shape[dim]
assert isinstance(dim_sz, int), f"does not support symbolic shape in split dimension {dim}: {self.shape}"
if isinstance(sizes, int): sizes = [min(sizes, dim_sz-i) for i in range(0, max(1, dim_sz), max(1, sizes))]
assert sum(sizes) == dim_sz, f"expect sizes to sum exactly to {dim_sz}, but got {sum(sizes)}"
return tuple(self[sl] for sl in [tuple([slice(None)]*dim + [slice(sum(sizes[:i]), sum(sizes[:i + 1]))]) for i in range(len(sizes))])
def chunk(self, chunks:int, dim:int=0) -> list[Tensor]:
@@ -1358,10 +1358,11 @@ class Tensor(OpMixin):
print("\\n".join([repr(x.numpy()) for x in chunked]))
```
"""
assert all_int(self.shape), f"does not support symbolic shape {self.shape}"
assert chunks > 0, f"expect chunks to be greater than 0, got: {chunks}"
dim = self._resolve_dim(dim)
return list(self.split(ceildiv(self.shape[dim], chunks) if self.shape[dim] else [0]*chunks, dim=dim))
dim_sz = self.shape[dim]
assert isinstance(dim_sz, int), f"does not support symbolic shape in split dimension {dim}: {self.shape}"
assert chunks > 0, f"expect chunks to be greater than 0, got: {chunks}"
return list(self.split(ceildiv(dim_sz, chunks) if dim_sz else [0]*chunks, dim=dim))
def unfold(self, dim:int, size:sint, step:int) -> Tensor:
"""
+1
View File
@@ -75,6 +75,7 @@ class Ops(FastEnum):
# tensor graph ops
UNIQUE = auto(); DEVICE = auto(); KERNEL = auto(); ASSIGN = auto()
CUSTOM_KERNEL = auto()
# local unique
LUNIQUE = auto()
+20 -13
View File
@@ -7,7 +7,7 @@ from tinygrad.uop import Ops, GroupOp
from tinygrad.dtype import ConstType, ImageDType, dtypes, DType, truncate, PtrDType, least_upper_dtype, Invalid, InvalidType, AddrSpace
from tinygrad.helpers import ContextVar, all_int, prod, getenv, all_same, Context, partition, temp, unwrap, T, argfix, Metadata, flatten, TRACEMETA
from tinygrad.helpers import PROFILE, dedup, cdiv, cmod, diskcache_put, to_function_name, cpu_profile, TracingKey, VIZ, SPEC, CI
from tinygrad.helpers import strip_parens, colored, ansilen, printable
from tinygrad.helpers import strip_parens, colored, ansilen, printable, panic
if TYPE_CHECKING:
from tinygrad.device import Buffer, MultiBuffer
@@ -158,7 +158,9 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
@property
def backward_slice_with_self(self:UOp) -> dict[UOp, None]: return {self:None, **self.backward_slice}
def op_in_backward_slice_with_self(self, *ops:Ops): return any(x.op in ops for x in self.backward_slice_with_self)
def op_in_backward_slice_with_self(self, *ops:Ops) -> bool:
# Check self first, then iterate backward_slice (avoids creating intermediate dict)
return self.op in ops or any(x.op in ops for x in self.backward_slice)
def toposort(self, gate:Callable|None=None) -> dict[UOp, None]:
cache: dict[UOp, None] = {}
@@ -216,7 +218,7 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
match self.op:
# late ops don't have shape
case Ops.UNIQUE | Ops.LUNIQUE | Ops.DEVICE | Ops.RANGE | Ops.LOAD | Ops.IF | Ops.BARRIER | Ops.CUSTOM | Ops.CUSTOMI | \
Ops.VECTORIZE | Ops.VCONST | Ops.GEP | Ops.SPECIAL | Ops.UNROLL | Ops.CONTRACT:
Ops.VECTORIZE | Ops.VCONST | Ops.GEP | Ops.SPECIAL | Ops.UNROLL | Ops.CONTRACT | Ops.CUSTOM_KERNEL:
return None
case Ops.INDEX:
@@ -340,6 +342,7 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
# *** uop evaluation ***
def simplify(self, tracked=False):
if self.op in {Ops.CONST, Ops.VCONST}: return self
# late import!
from tinygrad.uop.symbolic import symbolic
with Context(TRACK_MATCH_STATS=0 if not tracked else TRACK_MATCH_STATS.value):
@@ -467,7 +470,7 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
def is_contiguous(self):
# TODO: this is is_realized
if self.op is Ops.RESHAPE: return self.src[0].is_contiguous()
if self.op in {Ops.RESHAPE, Ops.MULTI}: return self.src[0].is_contiguous()
return self.op is Ops.BUFFER
def contiguous(self, *args, **kwargs):
@@ -587,7 +590,7 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
#def reshape(self, arg:tuple[sint, ...]): return self._mop(Ops.RESHAPE, arg, same_shape_noop=True)
#def expand(self, arg:tuple[sint, ...]): return self._mop(Ops.EXPAND, arg, same_shape_noop=True)
#def shrink(self, arg:tuple[tuple[sint, sint], ...]): return self._mop(Ops.SHRINK, arg, same_shape_noop=True)
def pad(self, arg:tuple[tuple[sint, sint], ...]): return self._mop(Ops.PAD, arg, same_shape_noop=True)
#def pad(self, arg:tuple[tuple[sint, sint], ...]): return self._mop(Ops.PAD, arg, same_shape_noop=True) # now in MovementMixin
# in these two, we have custom logic to check if they are a no-op
#def permute(self, arg:tuple[int, ...]): return self._mop(Ops.PERMUTE, arg, same_shape_noop=False) if arg != tuple(range(len(self.shape))) else self
@@ -837,9 +840,8 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
return self.src[0].after(self.store(val).end(*argfix(end)))
def custom_kernel(*srcs:UOp, fxn:Callable, grad_fxn:Callable|None=None) -> list[UOp]:
placeholders = [UOp.placeholder_like(s, slot=i) for i,s in enumerate(srcs)]
contig_srcs = tuple(x.contiguous() for x in srcs)
kernel = UOp(Ops.KERNEL, src=tuple(x.base for x in contig_srcs), arg=Kernel(fxn(*placeholders), grad_fxn=grad_fxn))
kernel = UOp(Ops.CUSTOM_KERNEL, src=contig_srcs, arg=CustomKernel(fxn=fxn, grad_fxn=grad_fxn))
return [s.after(kernel) for s in contig_srcs]
@dataclass(frozen=True)
@@ -852,6 +854,14 @@ class KernelInfo:
@property
def function_name(self): return to_function_name(self.name)
@dataclass(frozen=True)
class CustomKernel:
fxn: Callable
grad_fxn: Callable|None = None
# sadly CustomKernel can't be pickled or reconstructed as a str
def __reduce__(self): return (CustomKernel, (panic,))
def __repr__(self): return "CustomKernel(panic)"
@dataclass(frozen=True)
class Kernel:
ast: UOp
@@ -1191,16 +1201,13 @@ class BottomUpGate(Exception): pass
class RewriteContext:
def __init__(self, pm, bpm, ctx=None):
self.pm: PatternMatcher|None = pm
self.pm_cache: dict[UOp, UOp|None] = {}
self.bpm: PatternMatcher|None = bpm
self.bpm_cache: dict[UOp, UOp|None] = {}
self.ctx = ctx
self.replace: dict[UOp, UOp] = {}
def cached_pm_rewrite(self, x:UOp) -> UOp|None:
if (ret:=self.pm_cache.get(x,SENTINEL)) is not SENTINEL: return ret
ret = self.pm_cache[x] = unwrap(self.pm).rewrite(x, self.ctx)
return ret
# no cache needed: pm_rewrite is called at most once per UOp due to the replace dict check in unified_rewrite
def pm_rewrite(self, x:UOp) -> UOp|None: return unwrap(self.pm).rewrite(x, self.ctx)
def cached_bpm_rewrite(self, x:UOp) -> UOp|None:
if (ret:=self.bpm_cache.get(x,SENTINEL)) is not SENTINEL: return ret
@@ -1247,7 +1254,7 @@ class RewriteContext:
# in stage 1, once all srcs are rewritten, rebuild (if changed) or run top-down rewrite
if (new_src:=tuple(tmp)) == new_n.src:
# if top down, do the rewrite. if no rewrite or bottom up, we are done rewriting this node so we add it to the dict
if self.pm is None or (new_src_n:=self.cached_pm_rewrite(new_n)) is None:
if self.pm is None or (new_src_n:=self.pm_rewrite(new_n)) is None:
self.replace[n] = new_n
continue
else:
+10 -5
View File
@@ -1,8 +1,8 @@
import math
from typing import cast, Any
from tinygrad.uop.ops import PatternMatcher, UPat, GroupOp, Ops, UOp, print_uops, AxisType, KernelInfo, pyrender, Kernel
from tinygrad.uop.ops import PatternMatcher, UPat, GroupOp, Ops, UOp, print_uops, AxisType, KernelInfo, pyrender, Kernel, CustomKernel
from tinygrad.dtype import DType, ImageDType, dtypes, PtrDType, AddrSpace, Invalid
from tinygrad.helpers import DEBUG, Context, prod, SPEC, Metadata
from tinygrad.helpers import DEBUG, Context, prod, SPEC, Metadata, panic
from tinygrad.uop.validate import validate_index
# four specs:
@@ -54,7 +54,10 @@ movement_ops = PatternMatcher([
(UPat({Ops.ADD, Ops.MUL, Ops.IDIV}, dtype=dtypes.index), lambda: True),
# AFTER on Movement Op
(UPat(Ops.AFTER, src=(UPat(GroupOp.Movement),), allow_any_len=True), lambda: True),
(UPat(Ops.AFTER, src=(UPat(GroupOp.Movement.union({Ops.MULTI})),), allow_any_len=True), lambda: True),
# custom kernels allowed here
(UPat(Ops.CUSTOM_KERNEL), lambda: True),
])
_tensor_spec = PatternMatcher([
@@ -83,6 +86,8 @@ _tensor_spec = PatternMatcher([
# Tensor variable bindings
(UPat(Ops.BIND, (dtypes.int,dtypes.index,), (UPat(Ops.DEFINE_VAR), UPat.cvar(dtype=(dtypes.int,dtypes.index,))), arg=None), lambda: True),
# single-src BIND used for schedule cache key normalization
(UPat(Ops.BIND, (dtypes.int,dtypes.index,), (UPat(Ops.DEFINE_VAR),), arg=None), lambda: True),
# device or unique
(UPat(Ops.CONST, src=(UPat(Ops.DEVICE),)), lambda: True),
@@ -272,8 +277,8 @@ def type_verify(ast:UOp|list[UOp], check_spec:PatternMatcher):
from tinygrad.codegen.opt import Opt, OptOps
from tinygrad.schedule.rangeify import BufferizeOpts
glbls:dict[str, Any] = {"inf": math.inf, "nan": math.nan, "KernelInfo": KernelInfo, "Kernel": Kernel, "Metadata": Metadata,
"UOp": UOp, "dtypes": dtypes, "Ops": Ops, "AxisType": AxisType, "Invalid": Invalid,
"Opt": Opt, "OptOps": OptOps, "BufferizeOpts": BufferizeOpts, "AddrSpace": AddrSpace}
"UOp": UOp, "dtypes": dtypes, "Ops": Ops, "AxisType": AxisType, "Invalid": Invalid, "CustomKernel": CustomKernel,
"Opt": Opt, "OptOps": OptOps, "BufferizeOpts": BufferizeOpts, "AddrSpace": AddrSpace, "panic": panic}
def eval_pyrender(code:str) -> UOp:
lcls:dict[str, Any] = {}
exec(code, glbls, lcls)
+34 -21
View File
@@ -698,6 +698,29 @@ const toggleLabel = d3.create("label").text("Show indexing (r)").node();
const toggle = d3.create("input").attr("type", "checkbox").attr("id", "show-indexing").property("checked", true).node();
toggleLabel.prepend(toggle);
function appendSteps(root, idx, steps) {
const stack = [];
for (const [j,u] of steps.entries()) {
while (stack.length && stack.at(-1).depth >= u.depth) stack.pop();
const list = stack.length > 0 ? stack.at(-1).li : root;
u.li = list.appendChild(document.createElement("ul"));
u.li.id = `step-${idx}-${j}`
const p = u.li.appendChild(document.createElement("p"));
p.appendChild(colored(`${u.name}`+(u.match_count ? ` - ${u.match_count}` : '')));
p.onclick = (e) => {
e.stopPropagation();
const subrewrites = getSubrewrites(e.currentTarget.parentElement);
if (subrewrites.length) { e.currentTarget.parentElement.classList.toggle("expanded"); }
setState({ currentStep:j, currentCtx:idx, currentRewrite:0 });
}
stack.push(u);
}
for (const l of root.querySelectorAll("ul > ul > p")) {
const subrewrites = getSubrewrites(l.parentElement);
if (subrewrites.length > 0) { l.appendChild(d3.create("span").text(` (${subrewrites.length})`).node()); l.parentElement.classList.add("has-children"); }
}
}
async function main() {
// ** left sidebar context list
if (ctxs == null) {
@@ -712,26 +735,7 @@ async function main() {
p.onclick = () => {
setState(i === state.currentCtx ? { expandSteps:!state.expandSteps } : { expandSteps:true, currentCtx:i, currentStep:0, currentRewrite:0 });
}
const stack = []; let list = ul;
for (const [j,u] of steps.entries()) {
while (stack.length && stack.at(-1).depth >= u.depth) stack.pop();
const list = stack.length > 0 ? stack.at(-1).li : ul;
u.li = list.appendChild(document.createElement("ul"));
u.li.id = `step-${i}-${j}`
const p = u.li.appendChild(document.createElement("p"));
p.appendChild(colored(`${u.name}`+(u.match_count ? ` - ${u.match_count}` : '')));
p.onclick = (e) => {
e.stopPropagation();
const subrewrites = getSubrewrites(e.currentTarget.parentElement);
if (subrewrites.length) { e.currentTarget.parentElement.classList.toggle("expanded"); }
setState({ currentStep:j, currentCtx:i, currentRewrite:0 });
}
stack.push(u);
}
for (const l of ul.querySelectorAll("ul > ul > p")) {
const subrewrites = getSubrewrites(l.parentElement);
if (subrewrites.length > 0) { l.appendChild(d3.create("span").text(` (${subrewrites.length})`).node()); l.parentElement.classList.add("has-children"); }
}
appendSteps(ul, i, steps);
}
return setState({ currentCtx:-1 });
}
@@ -756,6 +760,15 @@ async function main() {
// ** Disassembly view
if (!ckey.startsWith("/rewrites")) {
if (!(ckey in cache)) cache[ckey] = ret = await fetchValue(ckey);
if (ret.steps?.length > 0) {
const el = select(state.currentCtx, state.currentStep);
if (el.step.querySelectorAll("ul").length === ret.steps.length) return;
// re render the list with new items
ctx.steps.push(...ret.steps);
while (el.ctx.children.length > 1) el.ctx.children[1].remove();
appendSteps(el.ctx, state.currentCtx, ctx.steps);
return setState({ currentStep:state.currentStep+1, expandSteps:true });
}
// cycles on the x axis
if (ret instanceof ArrayBuffer) {
opts = {heightScale:0.5, hideLabels:true, levelKey:(e) => parseInt(e.name.split(" ")[1].split(":")[1])};
@@ -817,7 +830,7 @@ async function main() {
const eventSource = new EventSource(ckey);
evtSources.push(eventSource);
eventSource.onmessage = (e) => {
if (e.data === "END") return eventSource.close();
if (e.data === "[DONE]") return eventSource.close();
const chunk = JSON.parse(e.data);
ret.push(chunk);
// if it's the first one render this new rgaph
+92 -73
View File
@@ -9,14 +9,14 @@ from tinygrad.helpers import colored, getenv, tqdm, unwrap, word_wrap, TRACEMETA
from tinygrad.helpers import printable, system, TCPServerWithReuse, HTTPRequestHandler
from tinygrad.uop.ops import TrackedGraphRewrite, RewriteTrace, UOp, Ops, GroupOp, srender, sint, sym_infer, range_str, pyrender
from tinygrad.uop.ops import print_uops, range_start, multirange_str
from tinygrad.device import ProfileDeviceEvent, ProfileGraphEvent, ProfileGraphEntry, Device
from tinygrad.device import ProfileDeviceEvent, ProfileGraphEvent, ProfileGraphEntry, Device, ProfileProgramEvent
from tinygrad.renderer import ProgramSpec
from tinygrad.dtype import dtypes
uops_colors = {Ops.LOAD: "#ffc0c0", Ops.STORE: "#87CEEB", Ops.CONST: "#e0e0e0", Ops.VCONST: "#e0e0e0", Ops.REDUCE: "#FF5B5B",
Ops.DEFINE_GLOBAL:"#cb9037", **{x:"#f2cb91" for x in {Ops.DEFINE_LOCAL, Ops.DEFINE_REG}}, Ops.REDUCE_AXIS: "#FF6B6B",
Ops.RANGE: "#c8a0e0", Ops.ASSIGN: "#909090", Ops.BARRIER: "#ff8080", Ops.IF: "#c8b0c0", Ops.SPECIAL: "#c0c0ff",
Ops.INDEX: "#cef263", Ops.WMMA: "#efefc0", Ops.MULTI: "#f6ccff", Ops.KERNEL: "#3e7f55",
Ops.INDEX: "#cef263", Ops.WMMA: "#efefc0", Ops.MULTI: "#f6ccff", Ops.KERNEL: "#3e7f55", Ops.CUSTOM_KERNEL: "#3ebf55",
**{x:"#D8F9E4" for x in GroupOp.Movement}, **{x:"#ffffc0" for x in GroupOp.ALU}, Ops.THREEFRY:"#ffff80",
Ops.BUFFER_VIEW: "#E5EAFF", Ops.BUFFER: "#B0BDFF", Ops.COPY: "#a040a0", Ops.ENCDEC: "#bf71b6",
Ops.ALLREDUCE: "#ff40a0", Ops.MSELECT: "#d040a0", Ops.MSTACK: "#d040a0", Ops.CONTIGUOUS: "#FFC14D",
@@ -58,7 +58,8 @@ class GraphRewriteDetails(TypedDict):
def shape_to_str(s:tuple[sint, ...]): return "(" + ','.join(srender(x) for x in s) + ")"
def mask_to_str(s:tuple[tuple[sint, sint], ...]): return "(" + ','.join(shape_to_str(x) for x in s) + ")"
def pystr(u:UOp, i:int) -> str:
def pystr(u:UOp) -> str:
# pyrender may check for shape mismatch
try: return pyrender(u)
except Exception: return str(u)
@@ -111,19 +112,18 @@ def _reconstruct(a:int):
arg = type(arg)(_reconstruct(arg.ast), arg.metadata) if op is Ops.KERNEL else arg
return UOp(op, dtype, tuple(_reconstruct(s) for s in src), arg, *rest)
def get_full_rewrite(ctx:TrackedGraphRewrite, i:int=0) -> Generator[GraphRewriteDetails, None, None]:
def get_full_rewrite(ctx:TrackedGraphRewrite) -> Generator[GraphRewriteDetails, None, None]:
next_sink = _reconstruct(ctx.sink)
# in the schedule graph we don't show indexing ops (unless it's in a kernel AST or rewriting dtypes.index sink)
yield {"graph":uop_to_json(next_sink), "uop":pystr(next_sink,i), "changed_nodes":None, "diff":None, "upat":None}
yield {"graph":uop_to_json(next_sink), "uop":pystr(next_sink), "changed_nodes":None, "diff":None, "upat":None}
replaces: dict[UOp, UOp] = {}
for u0_num,u1_num,upat_loc,dur in tqdm(ctx.matches):
replaces[u0:=_reconstruct(u0_num)] = u1 = _reconstruct(u1_num)
try: new_sink = next_sink.substitute(replaces)
except RuntimeError as e: new_sink = UOp(Ops.NOOP, arg=str(e))
match_repr = f"# {dur*1e6:.2f} us\n"+printable(upat_loc)
yield {"graph":(sink_json:=uop_to_json(new_sink)), "uop":pystr(new_sink,i),
"changed_nodes":[id(x) for x in u1.toposort() if id(x) in sink_json],
"diff":list(difflib.unified_diff(pystr(u0,i).splitlines(),pystr(u1,i).splitlines())), "upat":(upat_loc, match_repr)}
yield {"graph":(sink_json:=uop_to_json(new_sink)), "uop":pystr(new_sink), "changed_nodes":[id(x) for x in u1.toposort() if id(x) in sink_json],
"diff":list(difflib.unified_diff(pystr(u0).splitlines(), pystr(u1).splitlines())), "upat":(upat_loc, match_repr)}
if not ctx.bottom_up: next_sink = new_sink
# encoder helpers
@@ -234,70 +234,66 @@ def unpack_pmc(e) -> dict:
rows.append(row)
return {"rows":rows, "cols":agg_cols}
@soft_err(lambda err: ctxs.append({"name":"ERR", "steps":[create_step("Loader error", ("render",len(ctxs),0), err)]}))
def load_sqtt(profile:list[ProfileEvent]) -> None:
# ** on startup, list all the performance counter traces
def load_counters(profile:list[ProfileEvent]) -> None:
from tinygrad.runtime.ops_amd import ProfileSQTTEvent, ProfilePMCEvent
counter_events:dict[tuple[str, int], list[ProfileSQTTEvent|ProfilePMCEvent]] = {}
counter_events:dict[tuple[str, int], dict] = {}
durations:dict[str, list[float]] = {}
prg_events:dict[str, ProfileProgramEvent] = {}
dev_events:dict[str, ProfileDeviceEvent] = {}
for e in profile:
if isinstance(e, (ProfilePMCEvent, ProfileSQTTEvent)): counter_events.setdefault((e.kern, e.exec_tag), []).append(e)
if isinstance(e, (ProfilePMCEvent, ProfileSQTTEvent)): counter_events.setdefault((e.kern, e.exec_tag), {}).setdefault(type(e), []).append(e)
if isinstance(e, ProfileRangeEvent) and e.device.startswith("AMD") and e.en is not None:
durations.setdefault(str(e.name), []).append(float(e.en-e.st))
if not counter_events: return
# ** init decoder
if isinstance(e, ProfileProgramEvent): prg_events[str(e.name)] = e
if isinstance(e, ProfileDeviceEvent): dev_events[e.device] = e
ctxs.append({"name":"All Counters", "steps":[create_step("PMC", ("/all-pmc", len(ctxs), 0), \
(durations, {k:v[ProfilePMCEvent][0] for k,v in counter_events.items()}))]})
run_number = {n:0 for n,_ in counter_events}
for k,v in counter_events.items():
prg = trace.keys[r].ret if (r:=ref_map.get(k[0])) else None
name = prg.name if prg is not None else k[0]
run_number[k[0]] += 1
steps:list[dict] = []
if (pmc:=v.get(ProfilePMCEvent)): steps.append(create_step("PMC", ("/prg-pmc", len(ctxs), len(steps)), pmc))
if (sqtt:=v.get(ProfileSQTTEvent)):
# to decode a SQTT trace, we need the raw stream, program binary and device properties
steps.append(create_step("SQTT", ("/prg-sqtt", len(ctxs), len(steps)), (k, [*sqtt, prg_events[k[0]], dev_events[sqtt[0].device]])))
if getenv("SQTT_PARSE"):
# run our decoder on startup, we don't use this since it only works on gfx11
from extra.sqtt.attempt_sqtt_parse import parse_sqtt_print_packets
for e in sqtt: parse_sqtt_print_packets(e.blob)
ctxs.append({"name":f"Exec {name} n{run_number[k[0]]}", "steps":steps})
# ** SQTT OCC only unpacks wave start, end time and SIMD location
def unpack_sqtt(key:tuple[str, int], profile:list[ProfileEvent]) -> tuple[dict[str, list[ProfileEvent]], list[str], dict[str, dict[str, dict]]]:
# * init decoder
from extra.sqtt.roc import decode
rctx = decode(profile)
if getenv("SQTT_PARSE"):
from extra.sqtt.attempt_sqtt_parse import parse_sqtt_print_packets
for counters in counter_events.values():
for e in counters:
if isinstance(e, ProfileSQTTEvent): parse_sqtt_print_packets(e.blob)
# ** decode traces for each run
all_runs:dict = {"cols":{}, "rows":[]}
steps:list[dict] = [create_step("All", ("/all", len(ctxs), 0), data=all_runs)]
for key,counters in counter_events.items():
# ** Run summary
program = trace.keys[r].ret if (r:=ref_map.get(key[0])) else None
summary = [f"{program.global_size=} {program.local_size=}"] if program else [repr(key)]
# ** SQTT events
disasm = rctx.disasms[key[0]]
cu_events:dict[str, list[ProfileEvent]] = {}
# * INST waves
wave_insts:dict[str, dict[str, dict]] = {}
inst_units:dict[str, itertools.count] = {}
for w in rctx.inst_execs.get(key, []):
if (u:=w.wave_loc) not in inst_units: inst_units[u] = itertools.count(0)
n = next(inst_units[u])
if (events:=cu_events.get(w.cu_loc)) is None: cu_events[w.cu_loc] = events = []
events.append(ProfileRangeEvent(w.simd_loc, loc:=f"INST WAVE:{w.wave_id} N:{n}", Decimal(w.begin_time), Decimal(w.end_time)))
wave_insts.setdefault(w.cu_loc, {})[f"{u} N:{n}"] = {"wave":w, "disasm":disasm, "run_number":n, "loc":loc}
# * OCC waves
units:dict[str, itertools.count] = {}
wave_start:dict[str, int] = {}
for occ in rctx.occ_events.get(key, []):
if (u:=occ.wave_loc) not in units: units[u] = itertools.count(0)
if u in inst_units: continue
if occ.start: wave_start[u] = occ.time
else:
if (events:=cu_events.get(occ.cu_loc)) is None: cu_events[occ.cu_loc] = events = []
events.append(ProfileRangeEvent(occ.simd_loc, f"OCC WAVE:{occ.wave_id} N:{next(units[u])}", Decimal(wave_start.pop(u)), Decimal(occ.time)))
prg_cu = sorted(cu_events, key=row_tuple)
if cu_events: summary.append(f"Scheduled on {len(prg_cu)} CUs")
steps.append(create_step(program.name if program else key[0], ("/prg-run", len(ctxs), len(steps)), {"src":"\n\n".join(summary)}, depth=0))
# ** PMC events
if (pmc_event:=next((e for e in counters if isinstance(e, ProfilePMCEvent)), None)) is not None:
steps.append(create_step("PMC", ("/pmc", len(ctxs), len(steps)), pmc_table:=unpack_pmc(pmc_event), depth=1))
all_runs["cols"].update([(r[0], None) for r in pmc_table["rows"]])
all_runs["rows"].append((key[0], durations[key[0]].pop(0), *[r[1] for r in pmc_table["rows"]]))
for cu in prg_cu:
events = [ProfilePointEvent(unit, "start", unit, ts=Decimal(0)) for unit in units]+cu_events[cu]
steps.append(create_step(f"{cu} {len(cu_events[cu])}", ("/counters", len(ctxs), len(steps)),
{"value":get_profile(events, sort_fn=row_tuple), "content_type":"application/octet-stream"}, depth=1))
for k in sorted(wave_insts.get(cu, []), key=row_tuple):
data = wave_insts[cu][k]
steps.append(create_step(k.replace(cu, ""), ("/sqtt-insts", len(ctxs), len(steps)), data, loc=data["loc"], depth=2))
all_runs["cols"] = ["Kernel", "Duration", *all_runs["cols"]]
ctxs.append({"name":"Counters", "steps":steps})
disasm = rctx.disasms[key[0]]
cu_events:dict[str, list[ProfileEvent]] = {}
# * INST waves
wave_insts:dict[str, dict[str, dict]] = {}
inst_units:dict[str, itertools.count] = {}
for w in rctx.inst_execs.get(key, []):
if (u:=w.wave_loc) not in inst_units: inst_units[u] = itertools.count(0)
n = next(inst_units[u])
if (events:=cu_events.get(w.cu_loc)) is None: cu_events[w.cu_loc] = events = []
events.append(ProfileRangeEvent(w.simd_loc, loc:=f"INST WAVE:{w.wave_id} N:{n}", Decimal(w.begin_time), Decimal(w.end_time)))
wave_insts.setdefault(w.cu_loc, {})[f"{u} N:{n}"] = {"wave":w, "disasm":disasm, "run_number":n, "loc":loc}
# * OCC waves
units:dict[str, itertools.count] = {}
wave_start:dict[str, int] = {}
for occ in rctx.occ_events.get(key, []):
if (u:=occ.wave_loc) not in units: units[u] = itertools.count(0)
if u in inst_units: continue
if occ.start: wave_start[u] = occ.time
else:
if (events:=cu_events.get(occ.cu_loc)) is None: cu_events[occ.cu_loc] = events = []
events.append(ProfileRangeEvent(occ.simd_loc, f"OCC WAVE:{occ.wave_id} N:{next(units[u])}", Decimal(wave_start.pop(u)), Decimal(occ.time)))
return cu_events, list(units), wave_insts
def device_sort_fn(k:str) -> tuple[int, str, int]:
order = {"GC": 0, "USER": 1, "TINY": 2, "DISK": 999}
@@ -307,13 +303,12 @@ def device_sort_fn(k:str) -> tuple[int, str, int]:
def get_profile(profile:list[ProfileEvent], sort_fn:Callable[[str], Any]=device_sort_fn) -> bytes|None:
# start by getting the time diffs
for ev in profile:
if isinstance(ev,ProfileDeviceEvent): device_ts_diffs[ev.device] = (ev.comp_tdiff, ev.copy_tdiff if ev.copy_tdiff is not None else ev.comp_tdiff)
# load device specific counters
device_decoders:dict[str, Callable[[list[ProfileEvent]], None]] = {}
for device in device_ts_diffs:
d = device.split(":")[0]
if d == "AMD": device_decoders[d] = load_sqtt
for ev in profile:
if isinstance(ev, ProfileDeviceEvent):
device_ts_diffs[ev.device] = (ev.comp_tdiff,ev.copy_tdiff if ev.copy_tdiff is not None else ev.comp_tdiff)
if (d:=ev.device.split(":")[0]) == "AMD": device_decoders[d] = load_counters
# load device specific counters
for fxn in device_decoders.values(): fxn(profile)
# map events per device
dev_events:dict[str, list[tuple[int, int, float, DevEvent]]] = {}
@@ -381,6 +376,8 @@ def amd_readelf(lib:bytes) -> list[dict]:
".group_segment_fixed_size":"LDS size", ".private_segment_fixed_size":"Scratch size"}
return [{"label":label, "value":v} for k,label in keys.items() if (v:=notes["amdhsa.kernels"][0][k]) > 0]
# ** Main render function to get the complete details about a trace event
def get_render(i:int, j:int, fmt:str) -> dict:
data = ctxs[i]["steps"][j]["data"]
if fmt == "uops": return {"src":get_stdout(lambda: print_uops(data.uops or [])), "lang":"txt"}
@@ -397,8 +394,30 @@ def get_render(i:int, j:int, fmt:str) -> dict:
with soft_err(lambda err: metadata.append(err)):
metadata.append(amd_readelf(compiler.compile(data.src)))
return {"src":disasm_str, "lang":"amdgpu" if data.device.startswith("AMD") else None, "metadata":metadata}
if fmt == "all-pmc":
durations, pmc = data
ret:dict = {"cols":{}, "rows":[]}
for (prg,_),events in pmc.items():
pmc_table = unpack_pmc(events)
ret["cols"].update([(r[0], None) for r in pmc_table["rows"]])
ret["rows"].append((prg, durations[prg].pop(0), *[r[1] for r in pmc_table["rows"]]))
ret["cols"] = ["Kernel", "Duration", *ret["cols"]]
return ret
if fmt == "prg-pmc": return unpack_pmc(data[0])
if fmt == "prg-sqtt":
ret = {}
if len((steps:=ctxs[i]["steps"])[j+1:]) == 0:
with soft_err(lambda err: ret.update(err)):
cu_events, units, wave_insts = unpack_sqtt(*data)
for cu in sorted(cu_events, key=row_tuple):
steps.append(create_step(f"{cu} {len(cu_events[cu])}", ("/cu-sqtt", i, len(steps)), depth=1,
data=[ProfilePointEvent(unit, "start", unit, ts=Decimal(0)) for unit in units]+cu_events[cu]))
for k in sorted(wave_insts.get(cu, []), key=row_tuple):
steps.append(create_step(k.replace(cu, ""), ("/sqtt-insts", i, len(steps)), loc=(data:=wave_insts[cu][k])["loc"], depth=2, data=data))
return {**ret, "steps":[{k:v for k,v in s.items() if k != "data"} for s in steps[j+1:]]}
if fmt == "cu-sqtt": return {"value":get_profile(data, sort_fn=row_tuple), "content_type":"application/octet-stream"}
if fmt == "sqtt-insts":
columns = ["PC", "Instruction", "Hits", "Duration", "Stall", "Type"]
columns = ["PC", "Instruction", "Hits", "Cycles", "Stall", "Type"]
inst_columns = ["N", "Clk", "Idle", "Dur", "Stall"]
# Idle: The total time gap between the completion of previous instruction and the beginning of the current instruction.
# The idle time can be caused by:
@@ -445,7 +464,7 @@ class Handler(HTTPRequestHandler):
elif (query:=parse_qs(url.query)):
i, j = get_int(query, "ctx"), get_int(query, "step")
if (fmt:=url.path.lstrip("/")) == "rewrites":
try: return self.stream_json(get_full_rewrite(trace.rewrites[i][j], i))
try: return self.stream_json(get_full_rewrite(trace.rewrites[i][j]))
except (KeyError, IndexError): status_code = 404
else:
render_src = get_render(i, j, fmt)