mirror of
https://github.com/tinygrad/tinygrad.git
synced 2026-08-14 02:18:26 +00:00
Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6eaea3c9d9 | ||
|
|
8de6db15ac | ||
|
|
5954a0975f | ||
|
|
2e0eb88549 | ||
|
|
d6f9606e93 | ||
|
|
bd4a9473b0 | ||
|
|
a2c7b807e0 | ||
|
|
9eff7cd1d8 | ||
|
|
56cd47a159 | ||
|
|
a044648111 | ||
|
|
9f94c25a25 | ||
|
|
5276fbc9c5 | ||
|
|
b979162c5d | ||
|
|
dbd3b67657 | ||
|
|
9635592141 | ||
|
|
d7553721d1 | ||
|
|
5f08a3e928 | ||
|
|
de4cb722a4 | ||
|
|
6589c9e643 | ||
|
|
be7b0b6970 | ||
|
|
220a2a88d7 | ||
|
|
12ab3f8b06 | ||
|
|
8af8808c61 | ||
|
|
00391db628 | ||
|
|
dd413e1208 | ||
|
|
70c3f1fb29 | ||
|
|
1d307f568c | ||
|
|
bcc7623025 | ||
|
|
8c987b3293 | ||
|
|
bf467c623d | ||
|
|
02353588cb | ||
|
|
712a5c651a | ||
|
|
9c9e337c78 | ||
|
|
57ad69160a | ||
|
|
c5b52e9321 | ||
|
|
31619774a9 | ||
|
|
2ea54d7337 | ||
|
|
b67345caa3 |
@@ -121,7 +121,7 @@ runs:
|
||||
echo 'Acquire::GzipIndexes "true";' | sudo tee /etc/apt/apt.conf.d/gzip
|
||||
echo 'Acquire::http::Pipeline-Depth "5";' | sudo tee -a /etc/apt/apt.conf.d/99parallel
|
||||
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' | sudo tee -a /etc/apt/apt.conf.d/99keep-debs
|
||||
|
||||
|
||||
- name: Add OpenCL Repo
|
||||
if: inputs.opencl == 'true' && runner.os == 'Linux'
|
||||
shell: bash
|
||||
@@ -174,7 +174,7 @@ runs:
|
||||
if [[ "${{ inputs.llvm }}" == "true" ]]; then
|
||||
pkgs+=" libllvm20 clang-20 lld-20"
|
||||
fi
|
||||
|
||||
|
||||
echo "pkgs=$pkgs" >> "$GITHUB_OUTPUT"
|
||||
echo "hash=$(echo -n "$pkgs" | sha256sum | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -183,21 +183,21 @@ runs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /var/cache/apt/archives/
|
||||
key: ${{ runner.os }}-apt-${{ steps.apt-pkgs.outputs.hash }}
|
||||
key: ${{ runner.os }}-apt-${{ steps.apt-pkgs.outputs.hash }}-${{ env.APT_CACHE_VERSION }}
|
||||
|
||||
- name: Run apt Update + Install
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.cuda == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true')
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt -qq update || true
|
||||
|
||||
|
||||
# ******** do install ********
|
||||
if [[ -n "${{ steps.apt-pkgs.outputs.pkgs }}" ]]; then
|
||||
sudo apt-get -y --allow-unauthenticated --no-install-recommends install ${{ steps.apt-pkgs.outputs.pkgs }}
|
||||
fi
|
||||
|
||||
|
||||
sudo chown -R $USER:$USER /var/cache/apt/archives/
|
||||
|
||||
|
||||
# **** AMD ****
|
||||
- name: Setup AMD (Linux)
|
||||
if: inputs.amd == 'true' && runner.os == 'Linux'
|
||||
@@ -234,7 +234,7 @@ runs:
|
||||
cache-name: cache-gpuocelot-build
|
||||
with:
|
||||
path: ${{ github.workspace }}/gpuocelot/ocelot
|
||||
key: ${{ runner.os }}-gpuocelot-b16039dc940dc6bc4ea0a98380495769ff35ed99-rebuild-0
|
||||
key: ${{ runner.os }}-gpuocelot-b16039dc940dc6bc4ea0a98380495769ff35ed99-rebuild-${{ env.BUILD_CACHE_VERSION }}
|
||||
- name: Clone/compile gpuocelot
|
||||
if: inputs.ocelot == 'true' && steps.cache-build.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
- name: Run model inference benchmark
|
||||
run: METAL=1 python3.11 test/external/external_model_benchmark.py
|
||||
- name: Test speed vs torch
|
||||
run: BIG=2 MPS=1 python3.11 test/test_speed_v_torch.py | tee torch_speed.txt
|
||||
run: BIG=2 MPS=1 python3.11 test/speed/external_test_speed_v_torch.py | tee torch_speed.txt
|
||||
- name: Test tensor cores
|
||||
run: METAL=1 python3.11 test/test_linearizer.py TestLinearizer.test_tensor_cores TestLinearizer.test_tensor_cores_padded TestLinearizer.test_tensor_cores_padded_uops
|
||||
- name: Test AMX tensor cores
|
||||
@@ -187,7 +187,7 @@ jobs:
|
||||
- name: Run model inference benchmark
|
||||
run: NV=1 CAPTURE_PROCESS_REPLAY=0 NOCLANG=1 python3 test/external/external_model_benchmark.py
|
||||
- name: Test speed vs torch
|
||||
run: NV=1 CAPTURE_PROCESS_REPLAY=0 HALF=1 BIG=2 TORCHCUDA=1 python3 test/test_speed_v_torch.py | tee torch_speed.txt
|
||||
run: NV=1 CAPTURE_PROCESS_REPLAY=0 HALF=1 BIG=2 TORCHCUDA=1 python3 test/speed/external_test_speed_v_torch.py | tee torch_speed.txt
|
||||
- name: Test speed vs theoretical
|
||||
run: NV=1 IGNORE_BEAM_CACHE=1 BEAM_DEBUG=1 DEBUG=1 python -m pytest -rA test/external/speed_v_theoretical.py --durations=20
|
||||
- name: Test benchmark allreduce
|
||||
@@ -389,7 +389,7 @@ jobs:
|
||||
#- name: Test speed vs torch
|
||||
# run: |
|
||||
# python3 -c "import torch; print(torch.__version__)"
|
||||
# LD_PRELOAD="/opt/rocm/lib/libhsa-runtime64.so" HSA=1 BIG=2 TORCHCUDA=1 python3 test/test_speed_v_torch.py | tee torch_speed.txt
|
||||
# LD_PRELOAD="/opt/rocm/lib/libhsa-runtime64.so" HSA=1 BIG=2 TORCHCUDA=1 python3 test/speed/external_test_speed_v_torch.py | tee torch_speed.txt
|
||||
- name: Test speed vs theoretical
|
||||
run: AMD=1 IGNORE_BEAM_CACHE=1 BEAM_DEBUG=1 DEBUG=1 python -m pytest -rA test/external/speed_v_theoretical.py --durations=20
|
||||
- name: Test tensor cores
|
||||
|
||||
+67
-21
@@ -1,8 +1,10 @@
|
||||
name: Unit Tests
|
||||
env:
|
||||
# increment this when downloads substantially change to avoid the internet
|
||||
DOWNLOAD_CACHE_VERSION: '11'
|
||||
PYTHON_CACHE_VERSION: '2'
|
||||
DOWNLOAD_CACHE_VERSION: '12'
|
||||
PYTHON_CACHE_VERSION: '3'
|
||||
APT_CACHE_VERSION: '1'
|
||||
BUILD_CACHE_VERSION: '1'
|
||||
CAPTURE_PROCESS_REPLAY: 1
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -30,9 +32,9 @@ jobs:
|
||||
- name: External Benchmark Schedule
|
||||
run: PYTHONPATH="." python3 test/external/external_benchmark_schedule.py
|
||||
- name: Speed Test
|
||||
run: LLVM=1 python3 test/test_speed_v_torch.py
|
||||
run: LLVM=1 python3 test/speed/external_test_speed_v_torch.py
|
||||
- name: Speed Test (BEAM=2)
|
||||
run: BEAM=2 LLVM=1 python3 test/test_speed_v_torch.py
|
||||
run: BEAM=2 LLVM=1 python3 test/speed/external_test_speed_v_torch.py
|
||||
|
||||
docs:
|
||||
name: Docs
|
||||
@@ -378,8 +380,8 @@ jobs:
|
||||
PYTHONPATH=. python extra/optimization/extract_dataset.py
|
||||
gzip -c /tmp/sops > extra/datasets/sops.gz
|
||||
DEBUG=1 MIN_ASTS=1 PYTHONPATH=. python extra/optimization/get_action_space.py
|
||||
- name: Repo line count < 17000 lines
|
||||
run: MAX_LINE_COUNT=17000 python sz.py
|
||||
- name: Repo line count < 17500 lines
|
||||
run: MAX_LINE_COUNT=17500 python sz.py
|
||||
|
||||
fuzzing:
|
||||
name: Fuzzing
|
||||
@@ -458,7 +460,7 @@ jobs:
|
||||
testopenpilot:
|
||||
name: 'openpilot Compile Tests'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
IGNORE_OOB: 0
|
||||
steps:
|
||||
@@ -589,6 +591,56 @@ jobs:
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
testrangeify:
|
||||
name: Linux (rangeify)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: rangeify-minimal-llvm
|
||||
deps: testing_minimal
|
||||
llvm: "true"
|
||||
- name: Test CPU=1 RANGEIFY=1
|
||||
# TODO: add more passing tests here
|
||||
# test_symbolic_arange_sym_step is passing now
|
||||
# test_threefry_doesnt_use_long is because there's a contig after the long now
|
||||
run: |
|
||||
CPU=1 RANGEIFY=1 python3 -m pytest -n auto --durations 20 \
|
||||
-k "not test_symbolic_arange_sym_step and not test_threefry_doesnt_use_long" \
|
||||
test/test_tiny.py test/test_rangeify.py test/test_ops.py test/test_tensor_variable.py \
|
||||
test/test_outerworld_range.py test/test_sample.py test/test_randomness.py test/test_tensor_data.py
|
||||
- name: Test CPU=1 RANGEIFY=2
|
||||
run: CPU=1 RANGEIFY=2 python3 -m pytest -n auto test/test_tiny.py test/test_rangeify.py test/test_ops.py --durations 20
|
||||
- name: Test LLVM=1 RANGEIFY=1 (slow tests)
|
||||
run: LLVM=1 RANGEIFY=1 python3 -m pytest -n auto test/models/test_mnist.py --durations 20
|
||||
|
||||
testdevectorize:
|
||||
name: Linux (devectorize)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
IGNORE_OOB: 0
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: devectorize-minimal
|
||||
deps: testing_minimal
|
||||
pydeps: "pillow"
|
||||
llvm: "true"
|
||||
- name: Test LLVM=1 DEVECTORIZE=0
|
||||
run: LLVM=1 DEVECTORIZE=0 python3 -m pytest -n auto test/test_tiny.py test/test_ops.py -k "not test_avg_pool3d_failure"
|
||||
- name: Test LLVM=1 DEVECTORIZE=0 for model
|
||||
run: PYTHONPATH="." LLVM=1 DEVECTORIZE=0 python3 test/models/test_efficientnet.py
|
||||
- name: Test CPU=1 DEVECTORIZE=0
|
||||
run: CPU=1 DEVECTORIZE=0 FUSE_ARANGE=0 python3 -m pytest -n auto test/test_tiny.py test/test_ops.py -k "not test_avg_pool3d_failure"
|
||||
|
||||
testdsp:
|
||||
name: Linux (DSP)
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -624,12 +676,6 @@ jobs:
|
||||
run: CC=clang-20 PYTHONPATH="." DEBUG=2 DSP=1 python test/test_transcendental.py TestTranscendentalVectorized
|
||||
- name: Test quantize onnx
|
||||
run: PYTHONPATH="." DEBUG=2 DSP=1 python3 test/test_quantize_onnx.py
|
||||
- name: Test LLVM=1 DEVECTORIZE=0
|
||||
run: LLVM=1 DEVECTORIZE=0 python3 -m pytest -n auto test/test_tiny.py test/test_ops.py -k "not test_avg_pool3d_failure"
|
||||
- name: Test LLVM=1 DEVECTORIZE=0 for model
|
||||
run: PYTHONPATH="." LLVM=1 DEVECTORIZE=0 python3 test/models/test_efficientnet.py
|
||||
- name: Test CPU=1 DEVECTORIZE=0
|
||||
run: CPU=1 DEVECTORIZE=0 FUSE_ARANGE=0 python3 -m pytest -n auto test/test_tiny.py test/test_ops.py -k "not test_avg_pool3d_failure"
|
||||
|
||||
testwebgpu:
|
||||
name: Linux (WebGPU)
|
||||
@@ -689,9 +735,9 @@ jobs:
|
||||
DEBUG=5 FORWARD_ONLY=1 python3 test/test_ops.py TestOps.test_add
|
||||
- name: Run LLVM test
|
||||
if: matrix.backend=='amdllvm'
|
||||
run: python test/test_amd_llvm.py
|
||||
run: python test/device/test_amd_llvm.py
|
||||
- name: Run pytest (amd)
|
||||
run: python -m pytest -n=auto test/test_ops.py test/test_dtype.py test/test_dtype_alu.py test/test_linearizer.py test/test_randomness.py test/test_jit.py test/test_graph.py test/test_multitensor.py test/test_hcq.py --durations=20
|
||||
run: python -m pytest -n=auto test/test_ops.py test/test_dtype.py test/test_dtype_alu.py test/test_linearizer.py test/test_randomness.py test/test_jit.py test/test_graph.py test/test_multitensor.py test/device/test_hcq.py --durations=20
|
||||
- name: Run pytest (amd)
|
||||
run: python -m pytest test/external/external_test_am.py --durations=20
|
||||
- name: Run TRANSCENDENTAL math
|
||||
@@ -816,14 +862,14 @@ jobs:
|
||||
AMD: 1
|
||||
FORWARD_ONLY: 1
|
||||
run: |
|
||||
python3 -m pytest -n=auto test/test_hcq.py test/test_tiny.py --durations=20
|
||||
python3 -m pytest -n=auto test/device/test_hcq.py test/test_tiny.py --durations=20
|
||||
- name: Run pytest (amd with llvm backend)
|
||||
env:
|
||||
MOCKGPU: 1
|
||||
AMD: 1
|
||||
FORWARD_ONLY: 1
|
||||
run: |
|
||||
python -m pytest -n=auto test/test_hcq.py test/test_tiny.py test/test_amd_llvm.py --durations=20
|
||||
python -m pytest -n=auto test/device/test_hcq.py test/test_tiny.py test/device/test_amd_llvm.py --durations=20
|
||||
- name: Run pytest (ptx)
|
||||
env:
|
||||
MOCKGPU: 1
|
||||
@@ -831,7 +877,7 @@ jobs:
|
||||
NV: 1
|
||||
FORWARD_ONLY: 1
|
||||
run: |
|
||||
python3 -m pytest -n=auto test/test_hcq.py test/test_tiny.py --durations=20
|
||||
python3 -m pytest -n=auto test/device/test_hcq.py test/test_tiny.py --durations=20
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
@@ -942,18 +988,18 @@ jobs:
|
||||
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
|
||||
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 (GPU)
|
||||
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
|
||||
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
|
||||
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: |
|
||||
|
||||
@@ -198,11 +198,7 @@ generate_amd() {
|
||||
clang2py -k cdefstum \
|
||||
extra/hip_gpu_driver/sdma_registers.h \
|
||||
extra/hip_gpu_driver/nvd.h \
|
||||
extra/hip_gpu_driver/kfd_pm4_headers_ai.h \
|
||||
extra/hip_gpu_driver/soc21_enum.h \
|
||||
extra/hip_gpu_driver/sdma_v6_0_0_pkt_open.h \
|
||||
extra/hip_gpu_driver/gc_11_0_0_offset.h \
|
||||
extra/hip_gpu_driver/gc_10_3_0_offset.h \
|
||||
extra/hip_gpu_driver/sienna_cichlid_ip_offset.h \
|
||||
--clang-args="-I/opt/rocm/include -x c++" \
|
||||
-o $BASE/amd_gpu.py
|
||||
@@ -376,26 +372,6 @@ generate_am() {
|
||||
-o $BASE/am/pm4_nv.py
|
||||
fixup $BASE/am/pm4_nv.py
|
||||
|
||||
clang2py -k cdefstum \
|
||||
$AMKERN_INC/vega10_enum.h \
|
||||
-o $BASE/am/vega10.py
|
||||
fixup $BASE/am/vega10.py
|
||||
|
||||
clang2py -k cdefstum \
|
||||
$AMKERN_INC/navi10_enum.h \
|
||||
-o $BASE/am/navi10.py
|
||||
fixup $BASE/am/navi10.py
|
||||
|
||||
clang2py -k cdefstum \
|
||||
$AMKERN_INC/soc21_enum.h \
|
||||
-o $BASE/am/soc21.py
|
||||
fixup $BASE/am/soc21.py
|
||||
|
||||
clang2py -k cdefstum \
|
||||
$AMKERN_INC/soc24_enum.h \
|
||||
-o $BASE/am/soc24.py
|
||||
fixup $BASE/am/soc24.py
|
||||
|
||||
clang2py -k cdefstum \
|
||||
extra/hip_gpu_driver/sdma_registers.h \
|
||||
$AMKERN_AMD/amdgpu/vega10_sdma_pkt_open.h \
|
||||
|
||||
@@ -1297,6 +1297,9 @@ def train_llama3():
|
||||
SEQLEN = config["SEQLEN"] = getenv("SEQLEN", 8192)
|
||||
TRAIN_ON_VAL = config["TRAIN_ON_VAL"] = getenv("TRAIN_ON_VAL", 0)
|
||||
SAMPLES = config["SAMPLES"] = getenv("SAMPLES", 5_760 if TRAIN_ON_VAL else 1_200_000 * 1152)
|
||||
EVAL_FREQ = config["EVAL_FREQ"] = getenv("EVAL_FREQ", 46080)
|
||||
EVAL_BS = config["EVAL_BS"] = getenv("EVAL_BS", 16)
|
||||
EVAL_TARGET = config["EVAL_TARGET"] = getenv("EVAL_TARGET", 5.6)
|
||||
|
||||
# LR=1e-4 TRAIN_ON_VAL=1 DEFAULT_FLOAT=bfloat16 FUSE_ARANGE=1 JITBEAM=2 OPTIM_DTYPE=bfloat16 LLAMA3_SIZE=1B WARMUP_STEPS=36 DECAY_STEPS=360 SEQLEN=512 PYTHONPATH=. AMD=1 AMD_LLVM=0 MODEL=llama3 python3 examples/mlperf/model_train.py
|
||||
# trains to 7
|
||||
@@ -1375,7 +1378,7 @@ def train_llama3():
|
||||
total_norm += p.grad.float().square().sum()
|
||||
total_norm = total_norm.sqrt().contiguous()
|
||||
for p in optim.params:
|
||||
p.grad = p.grad * opt_gradient_clip_norm / (total_norm + 1e-6)
|
||||
p.grad = p.grad * (opt_gradient_clip_norm / (total_norm + 1e-6)).clamp(max_=1.0)
|
||||
|
||||
optim.step()
|
||||
scheduler.step()
|
||||
@@ -1384,16 +1387,40 @@ def train_llama3():
|
||||
loss.realize(lr)
|
||||
return loss, lr
|
||||
|
||||
if getenv("FAKEDATA", 0):
|
||||
def fake_data():
|
||||
for _ in range(SAMPLES // GBS):
|
||||
yield Tensor.randint(GBS, SEQLEN + 1, low=0, high=32000, dtype=dtypes.int32, device=Device.DEFAULT)
|
||||
iter = fake_data()
|
||||
else:
|
||||
from examples.mlperf.dataloader import batch_load_llama3
|
||||
iter = batch_load_llama3(GBS, SAMPLES, SEQLEN, Path(getenv("BASEDIR", "/raid/datasets/c4/")), seed=SEED, val=bool(TRAIN_ON_VAL))
|
||||
@TinyJit
|
||||
@Tensor.train(False)
|
||||
def eval_step(model, tokens:Tensor):
|
||||
if (DP := getenv("DP", 1)) > 1:
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(DP))
|
||||
tokens = tokens.shard(device, 0)
|
||||
if (MP := getenv("MP", 1)) > 1:
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(MP))
|
||||
tokens = tokens.shard(device)
|
||||
logits:Tensor = model(tokens[:, :-1], start_pos=0, temperature=math.nan)
|
||||
loss = logits.sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
return loss.flatten().float()
|
||||
|
||||
i = 0
|
||||
# ** data iters **
|
||||
def fake_data(bs, samples):
|
||||
for _ in range(samples // bs):
|
||||
yield Tensor.randint(bs, SEQLEN + 1, low=0, high=32000, dtype=dtypes.int32, device=Device.DEFAULT)
|
||||
|
||||
def get_train_iter():
|
||||
if getenv("FAKEDATA", 0):
|
||||
return fake_data(GBS, SAMPLES)
|
||||
else:
|
||||
from examples.mlperf.dataloader import batch_load_llama3
|
||||
return batch_load_llama3(GBS, SAMPLES, SEQLEN, Path(getenv("BASEDIR", "/raid/datasets/c4/")), seed=SEED, val=bool(TRAIN_ON_VAL))
|
||||
|
||||
def get_eval_iter():
|
||||
if getenv("FAKEDATA", 0):
|
||||
return fake_data(EVAL_BS, 5760)
|
||||
else:
|
||||
from examples.mlperf.dataloader import batch_load_llama3
|
||||
return batch_load_llama3(EVAL_BS, 5760, SEQLEN, Path(getenv("BASEDIR", "/raid/datasets/c4/")), seed=SEED, val=True)
|
||||
|
||||
iter = get_train_iter()
|
||||
i, sequences_seen = 0, 0
|
||||
for tokens in tqdm(iter, total=SAMPLES//GBS):
|
||||
t = time.perf_counter()
|
||||
GlobalCounters.reset()
|
||||
@@ -1408,9 +1435,33 @@ def train_llama3():
|
||||
if getenv("CKPT") and (i % 200 == 0 or i == 10):
|
||||
tqdm.write("saving checkpoint")
|
||||
if not os.path.exists(ckpt_dir := "./ckpts"): os.mkdir(ckpt_dir)
|
||||
fn = f"{ckpt_dir}/{i}.safe"
|
||||
fn = f"{ckpt_dir}/llama3_{i}.safe"
|
||||
safe_save(get_state_dict(model), fn)
|
||||
|
||||
i += 1
|
||||
sequences_seen += tokens.shape[0]
|
||||
|
||||
if sequences_seen % EVAL_FREQ == 0 and (i != 1 or EVAL_FREQ == 1):
|
||||
tqdm.write(f"evaluating after {sequences_seen} sequences")
|
||||
|
||||
# run eval
|
||||
eval_losses = []
|
||||
eval_iter = get_eval_iter()
|
||||
tqdm.write(f"evaluating {5760//EVAL_BS} batches of {EVAL_BS} sequences")
|
||||
|
||||
for tokens in tqdm(eval_iter, total=5760//EVAL_BS):
|
||||
eval_losses += eval_step(model, tokens).tolist()
|
||||
log_perplexity = Tensor(eval_losses).mean().float().item()
|
||||
|
||||
tqdm.write(f"eval log perplexity: {log_perplexity:.4f}")
|
||||
|
||||
if log_perplexity < EVAL_TARGET:
|
||||
tqdm.write(f"target achieved after {sequences_seen} sequences")
|
||||
if getenv("CKPT"):
|
||||
if not os.path.exists(ckpt_dir := "./ckpts"): os.mkdir(ckpt_dir)
|
||||
fn = f"{ckpt_dir}/llama3.safe"
|
||||
safe_save(get_state_dict(model), fn)
|
||||
break
|
||||
|
||||
if __name__ == "__main__":
|
||||
multiprocessing.set_start_method('spawn')
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
norecursedirs = extra
|
||||
@@ -35,6 +35,7 @@ lint.select = [
|
||||
line-length = 150
|
||||
|
||||
exclude = [
|
||||
".git/",
|
||||
"docs/",
|
||||
"extra/",
|
||||
"tinygrad/runtime/autogen",
|
||||
|
||||
@@ -18,7 +18,7 @@ testing_minimal = [
|
||||
]
|
||||
|
||||
setup(name='tinygrad',
|
||||
version='0.10.3',
|
||||
version='0.11.0',
|
||||
description='You like pytorch? You like micrograd? You love tinygrad! <3',
|
||||
author='George Hotz',
|
||||
license='MIT',
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import time
|
||||
from tinygrad import Tensor, TinyJit, Device, Context
|
||||
from tinygrad.helpers import Profiling, Timing, GlobalCounters
|
||||
|
||||
# python3 test/test_speed_v_torch.py TestSpeed.test_add_a
|
||||
# python3 test/speed/external_test_speed_v_torch.py TestSpeed.test_add_a
|
||||
|
||||
@TinyJit
|
||||
def plus(a:Tensor, b:Tensor): return a+b
|
||||
|
||||
+2
-4
@@ -99,7 +99,6 @@ def diff(offset:int, fxns:dict[str, Callable[..., tuple|None]]) -> None:
|
||||
except Exception as e:
|
||||
changed += 1
|
||||
warnings.warn(f"{name=} {loc=} {e=}", ProcessReplayWarning)
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
# *** generic runner to map rows of a table to a function in parallel
|
||||
@@ -111,12 +110,11 @@ def _pmap(fxns:dict[str, Callable]) -> None:
|
||||
except sqlite3.OperationalError:
|
||||
raise RuntimeError(f"{TABLE_NAME} isn't accessible in master, did DB_VERSION change?")
|
||||
finally:
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
with multiprocessing.get_context("spawn").Pool(multiprocessing.cpu_count()) as pool:
|
||||
inputs = list(range(0, row_count, PAGE_SIZE))
|
||||
list(tqdm(pool.imap_unordered(functools.partial(diff, fxns=fxns), inputs), total=len(inputs)))
|
||||
bar = tqdm(total=row_count)
|
||||
for _ in pool.imap_unordered(functools.partial(diff, fxns=fxns), range(0, row_count, PAGE_SIZE)): bar.update(PAGE_SIZE)
|
||||
pool.close()
|
||||
pool.join()
|
||||
pool.terminate()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ctypes, time
|
||||
from test.mockgpu.gpu import VirtGPU
|
||||
from tinygrad.helpers import getbits, to_mv, init_c_struct_t
|
||||
import tinygrad.runtime.autogen.amd_gpu as amd_gpu, tinygrad.runtime.autogen.am.pm4_nv as pm4, tinygrad.runtime.autogen.am.soc21 as soc21
|
||||
import tinygrad.runtime.autogen.amd_gpu as amd_gpu, tinygrad.runtime.autogen.am.pm4_nv as pm4
|
||||
|
||||
SDMA_MAX_COPY_SIZE = 0x400000
|
||||
|
||||
@@ -14,6 +14,9 @@ regSQ_THREAD_TRACE_BUF0_SIZE = 0x39e9 + amd_gpu.GC_BASE__INST0_SEG1
|
||||
regSQ_THREAD_TRACE_WPTR = 0x39ef + amd_gpu.GC_BASE__INST0_SEG1
|
||||
regSQ_THREAD_TRACE_STATUS = 0x39f4 + amd_gpu.GC_BASE__INST0_SEG1
|
||||
|
||||
class SQTT_EVENTS:
|
||||
THREAD_TRACE_FINISH = 0x00000037
|
||||
|
||||
CACHE_FLUSH_AND_INV_TS_EVENT = 0x14
|
||||
|
||||
WAIT_REG_MEM_FUNCTION_ALWAYS = 0
|
||||
@@ -208,7 +211,7 @@ class PM4Executor(AMDQueue):
|
||||
assert n == 0
|
||||
event_dw = self._next_dword()
|
||||
match (event_dw & 0xFF): # event type
|
||||
case soc21.THREAD_TRACE_FINISH:
|
||||
case SQTT_EVENTS.THREAD_TRACE_FINISH:
|
||||
old_idx = self.gpu.regs.grbm_index
|
||||
for se in range(self.gpu.regs.n_se):
|
||||
self.gpu.regs.grbm_index = 0b011 << 29 | se << 16 # select se, broadcast sa and instance
|
||||
|
||||
@@ -32,6 +32,7 @@ OPENPILOT_MODEL = "https://github.com/commaai/openpilot/raw/v0.9.4/selfdrive/mod
|
||||
np.random.seed(1337)
|
||||
|
||||
class TestOnnxModel(unittest.TestCase):
|
||||
@unittest.skip("this isn't a test, it can't fail")
|
||||
def test_benchmark_openpilot_model(self):
|
||||
onnx_model = fetch(OPENPILOT_MODEL)
|
||||
run_onnx = OnnxRunner(onnx_model)
|
||||
|
||||
@@ -16,7 +16,7 @@ TRANSCRIPTION_2 = "a slightly longer audio file so that we can test batch transc
|
||||
TEST_FILE_3_URL = 'https://homepage.ntu.edu.tw/~karchung/miniconversations/mc45.mp3'
|
||||
TRANSCRIPTION_3 = "Just lie back and relax. Is the level of pressure about right? Yes, it's fine, and I'd like conditioner please. Sure. I'm going to start the second lathering now. Would you like some Q-tips? How'd you like it cut? I'd like my bangs and the back trimmed, and I'd like the rest thinned out a bit and layered. Where would you like the part? On the left, right about here. Here, have a look. What do you think? It's fine. Here's a thousand anti-dollars. It's 30-ant extra for the rants. Here's your change and receipt. Thank you, and please come again. So how do you like it? It could have been worse, but you'll notice that I didn't ask her for her card. Hmm, yeah. Maybe you can try that place over there next time." # noqa: E501
|
||||
|
||||
@unittest.skipIf(CI and Device.DEFAULT in ["CPU"], "slow")
|
||||
@unittest.skipIf(Device.DEFAULT in ["CPU", "LLVM"], "slow")
|
||||
@unittest.skipUnless(is_dtype_supported(dtypes.float16), "need float16 support")
|
||||
class TestWhisper(unittest.TestCase):
|
||||
@classmethod
|
||||
|
||||
@@ -3,7 +3,7 @@ from tinygrad import Tensor, Device, TinyJit
|
||||
from tinygrad.helpers import Timing, CI, OSX
|
||||
import multiprocessing.shared_memory as shared_memory
|
||||
|
||||
N = 256 if CI else 4096
|
||||
N = 256
|
||||
class TestCopySpeed(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls): Device[Device.DEFAULT].synchronize()
|
||||
@@ -1,7 +1,10 @@
|
||||
import unittest
|
||||
import unittest, io
|
||||
from contextlib import redirect_stdout
|
||||
from tinygrad import Tensor, dtypes, Device
|
||||
from tinygrad.helpers import OSX
|
||||
from tinygrad.engine.realize import lower_schedule
|
||||
from tinygrad.device import is_dtype_supported
|
||||
from tinygrad.engine.realize import get_program
|
||||
|
||||
class TestCompileFailures(unittest.TestCase):
|
||||
def compile(self, out:Tensor):
|
||||
@@ -14,5 +17,17 @@ class TestCompileFailures(unittest.TestCase):
|
||||
def test_add_max_uchar(self):
|
||||
self.compile((Tensor.empty(1024, dtype='uint8') + Tensor.empty(1024, dtype='uint8')).max())
|
||||
|
||||
class TestDisassembly(unittest.TestCase):
|
||||
# TODO: fails on llvm. llvm.LLVMGetHostCPUName() returns "generic"
|
||||
@unittest.skipUnless(Device.DEFAULT in ("CPU",) and OSX, "m series cpus support fp16 arithmetic")
|
||||
def test_float16_alu(self):
|
||||
c = Tensor([1], dtype=dtypes.float16) + Tensor([1], dtype=dtypes.float16)
|
||||
s = c.schedule()[-1]
|
||||
p = get_program(s.ast, Device[Device.DEFAULT].renderer)
|
||||
lib = Device[Device.DEFAULT].compiler.compile(p.src)
|
||||
out = io.StringIO()
|
||||
with redirect_stdout(out): Device[Device.DEFAULT].compiler.disassemble(lib)
|
||||
assert "fcvt" not in out.getvalue()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import unittest, io
|
||||
from tinygrad import Tensor, dtypes
|
||||
from contextlib import redirect_stdout
|
||||
from tinygrad.device import Device
|
||||
from tinygrad.helpers import OSX
|
||||
from tinygrad.engine.realize import get_program
|
||||
|
||||
class TestDisassembly(unittest.TestCase):
|
||||
# TODO: fails on llvm. llvm.LLVMGetHostCPUName() returns "generic"
|
||||
@unittest.skipUnless(Device.DEFAULT in ("CPU",) and OSX, "m series cpus support fp16 arithmetic")
|
||||
def test_float16_alu(self):
|
||||
c = Tensor([1], dtype=dtypes.float16) + Tensor([1], dtype=dtypes.float16)
|
||||
s = c.schedule()[-1]
|
||||
p = get_program(s.ast, Device[Device.DEFAULT].renderer)
|
||||
lib = Device[Device.DEFAULT].compiler.compile(p.src)
|
||||
out = io.StringIO()
|
||||
with redirect_stdout(out): Device[Device.DEFAULT].compiler.disassemble(lib)
|
||||
assert "fcvt" not in out.getvalue()
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+5
-14
@@ -1,16 +1,13 @@
|
||||
import unittest
|
||||
|
||||
import unittest, operator, math
|
||||
from tinygrad import Tensor, dtypes, Device
|
||||
import operator
|
||||
import numpy as np
|
||||
from hypothesis import given, strategies as strat, settings, HealthCheck
|
||||
from tinygrad.dtype import DType
|
||||
from tinygrad.helpers import CI, getenv
|
||||
from tinygrad.engine.realize import run_schedule
|
||||
from tinygrad.uop.ops import GroupOp
|
||||
from tinygrad.tensor import _to_np_dtype
|
||||
from tinygrad.device import is_dtype_supported
|
||||
import pytest, math
|
||||
import numpy as np
|
||||
import pytest
|
||||
from hypothesis import given, strategies as strat, settings, HealthCheck
|
||||
|
||||
pytestmark = pytest.mark.filterwarnings("ignore")
|
||||
|
||||
settings.register_profile("my_profile", max_examples=200, deadline=None, derandomize=getenv("DERANDOMIZE_CI", False))
|
||||
@@ -71,17 +68,11 @@ def universal_test_unary(a, dtype, op):
|
||||
if not isinstance(op, tuple): op = (op, op)
|
||||
ta = Tensor([a], dtype=dtype)
|
||||
out: Tensor = op[0](ta)
|
||||
sched = out.schedule()
|
||||
ast = sched[-1].ast
|
||||
run_schedule(sched)
|
||||
tensor_value = out.numpy()
|
||||
numpy_value = op[1](ta.numpy())
|
||||
if dtype in (dtypes.float16, dtypes.bfloat16): np.testing.assert_allclose(tensor_value, numpy_value, atol=1e-3, rtol=1e-2)
|
||||
elif dtype in dtypes_float: np.testing.assert_allclose(tensor_value, numpy_value, atol=1e-6, rtol=1e-5)
|
||||
else: np.testing.assert_equal(tensor_value, numpy_value)
|
||||
if op[0] != Tensor.reciprocal: # reciprocal is not supported in most backends
|
||||
op = [x for x in ast.toposort() if x.op in GroupOp.Unary][0]
|
||||
assert op.dtype == dtype
|
||||
|
||||
def universal_test_cast(a, in_dtype, dtype):
|
||||
tensor_value = Tensor([a], dtype=in_dtype).cast(dtype)
|
||||
|
||||
@@ -373,11 +373,11 @@ class TestMultiTensor(unittest.TestCase):
|
||||
np.testing.assert_allclose(y.numpy(), y_shard.numpy(), atol=1e-6, rtol=1e-6)
|
||||
|
||||
# NOTE: this is failing on LLVM CI, no idea why. Works locally.
|
||||
@unittest.skipIf(CI and REAL_DEV in ("CUDA", "NV", "LLVM", "CPU"), "slow, and flaky on LLVM/CPU")
|
||||
@unittest.skipIf(CI and REAL_DEV in ("CUDA", "NV", "LLVM", "CPU", "AMD"), "slow, and flaky on LLVM/CPU")
|
||||
def test_data_parallel_resnet(self):
|
||||
from extra.models.resnet import ResNet18
|
||||
|
||||
fake_image = Tensor.rand((2, 3, 224//8, 224//8))
|
||||
fake_image = Tensor.rand((2, 3, 224//16, 224//16))
|
||||
fake_image_sharded = fake_image.shard(devices_2, axis=0)
|
||||
m = ResNet18()
|
||||
m.load_from_pretrained()
|
||||
@@ -409,10 +409,10 @@ class TestMultiTensor(unittest.TestCase):
|
||||
# sometimes there is zeros in these grads... why?
|
||||
np.testing.assert_allclose(grad, shard_grad, atol=1e-5, rtol=1e-5)
|
||||
|
||||
@unittest.skipIf(CI and REAL_DEV in ("CUDA", "NV", "LLVM", "CPU"), "slow, and flaky on LLVM/CPU")
|
||||
@unittest.skipIf(CI and REAL_DEV in ("CUDA", "NV", "LLVM", "CPU", "AMD"), "slow, and flaky on LLVM/CPU")
|
||||
def test_data_parallel_resnet_train_step(self):
|
||||
from extra.models.resnet import ResNet18
|
||||
fake_image = Tensor.rand((2, 3, 224//8, 224//8))
|
||||
fake_image = Tensor.rand((2, 3, 224//16, 224//16))
|
||||
labels = Tensor.randint(2, low=0, high=1000)
|
||||
m = ResNet18()
|
||||
self._test_model_train_step(m, fake_image, labels)
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import time
|
||||
import unittest
|
||||
import torch
|
||||
from tinygrad import Tensor, Device
|
||||
from tinygrad.helpers import Profiling, CI
|
||||
|
||||
@unittest.skipIf(CI and Device.DEFAULT in {"CUDA", "NV"}, "slow")
|
||||
class TestConvSpeed(unittest.TestCase):
|
||||
|
||||
def test_mnist(self):
|
||||
# https://keras.io/examples/vision/mnist_convnet/
|
||||
conv = 3
|
||||
inter_chan, out_chan = 32, 64
|
||||
|
||||
# ****** torch baseline *******
|
||||
|
||||
torch.backends.mkldnn.enabled = False
|
||||
|
||||
conv = 3
|
||||
inter_chan, out_chan = 32, 64
|
||||
c1 = torch.randn(inter_chan,1,conv,conv, requires_grad=True)
|
||||
c2 = torch.randn(out_chan,inter_chan,conv,conv, requires_grad=True)
|
||||
l1 = torch.randn(out_chan*5*5, 10, requires_grad=True)
|
||||
|
||||
c2d = torch.nn.functional.conv2d
|
||||
mp = torch.nn.MaxPool2d((2,2))
|
||||
lsm = torch.nn.LogSoftmax(dim=1)
|
||||
|
||||
cnt = 5
|
||||
fpt, bpt = 0.0, 0.0
|
||||
for i in range(cnt):
|
||||
et0 = time.time()
|
||||
x = torch.randn(128, 1, 28, 28, requires_grad=True)
|
||||
x = mp(c2d(x,c1).relu())
|
||||
x = mp(c2d(x,c2).relu())
|
||||
x = x.reshape(x.shape[0], -1)
|
||||
out = lsm(x.matmul(l1))
|
||||
out = out.mean()
|
||||
et1 = time.time()
|
||||
out.backward()
|
||||
et2 = time.time()
|
||||
fpt += (et1-et0)
|
||||
bpt += (et2-et1)
|
||||
|
||||
fpt_baseline = (fpt*1000/cnt)
|
||||
bpt_baseline = (bpt*1000/cnt)
|
||||
print("torch forward pass: %.3f ms" % fpt_baseline)
|
||||
print("torch backward pass: %.3f ms" % bpt_baseline)
|
||||
|
||||
# ****** tinygrad compare *******
|
||||
|
||||
c1 = Tensor(c1.detach().numpy(), requires_grad=True)
|
||||
c2 = Tensor(c2.detach().numpy(), requires_grad=True)
|
||||
l1 = Tensor(l1.detach().numpy(), requires_grad=True)
|
||||
|
||||
cnt = 5
|
||||
fpt, bpt = 0.0, 0.0
|
||||
for i in range(1+cnt):
|
||||
et0 = time.time()
|
||||
x = Tensor.randn(128, 1, 28, 28)
|
||||
x = x.conv2d(c1).relu().avg_pool2d()
|
||||
x = x.conv2d(c2).relu().max_pool2d()
|
||||
x = x.reshape(shape=(x.shape[0], -1))
|
||||
out = x.dot(l1).log_softmax()
|
||||
out = out.mean()
|
||||
out.backward() # NOTE: we have to now compute this here, but it doesn't realize
|
||||
out.realize()
|
||||
et1 = time.time()
|
||||
[x.grad.realize() for x in [c1, c2, l1]]
|
||||
et2 = time.time()
|
||||
if i == 0:
|
||||
pr = Profiling(sort='time', frac=0.2)
|
||||
pr.__enter__()
|
||||
else:
|
||||
fpt += (et1-et0)
|
||||
bpt += (et2-et1)
|
||||
|
||||
pr.__exit__()
|
||||
fpt = (fpt*1000/cnt)
|
||||
bpt = (bpt*1000/cnt)
|
||||
print("forward pass: %.3f ms, %.2fx off baseline %.3f ms" % (fpt, fpt/fpt_baseline, fpt_baseline))
|
||||
print("backward pass: %.3f ms, %.2fx off baseline %.3f ms" % (bpt, bpt/bpt_baseline, bpt_baseline))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable → Regular
+21
@@ -210,6 +210,27 @@ class TestNN(unittest.TestCase):
|
||||
np.testing.assert_allclose(layer.weight.grad.numpy(), torch_layer.weight.grad.detach().numpy(), atol=5e-4, rtol=5e-4)
|
||||
np.testing.assert_allclose(layer.bias.grad.numpy(), torch_layer.bias.grad.detach().numpy(), atol=5e-4, rtol=5e-4)
|
||||
|
||||
def test_layernorm_forward(self):
|
||||
N, C, H, W = 20, 5, 10, 10
|
||||
|
||||
# create in torch
|
||||
torch_layer = torch.nn.LayerNorm([H, W]).eval()
|
||||
|
||||
# create in tinygrad
|
||||
layer = LayerNorm([H, W])
|
||||
layer.weight = Tensor(torch_layer.weight.detach().numpy(), requires_grad=True)
|
||||
layer.bias = Tensor(torch_layer.bias.detach().numpy(), requires_grad=True)
|
||||
|
||||
x = Tensor.empty(N, C, H, W, requires_grad=True)
|
||||
z = layer(x)
|
||||
z.realize()
|
||||
|
||||
torch_x = torch.tensor(x.numpy(), requires_grad=True)
|
||||
torch_z = torch_layer(torch_x)
|
||||
torch_z.sum().backward()
|
||||
|
||||
np.testing.assert_allclose(z.numpy(), torch_z.detach().numpy(), atol=5e-6, rtol=5e-6)
|
||||
|
||||
def test_layernorm(self):
|
||||
N, C, H, W = 20, 5, 10, 10
|
||||
|
||||
|
||||
+1
-5
@@ -2804,11 +2804,7 @@ class TestOps(unittest.TestCase):
|
||||
helper_test_op(None, lambda x: x.gather(dim=0, index=torch.tensor([2, 1, 0, 1, 2], requires_grad=False)),
|
||||
lambda x: x.gather(dim=0, index=Tensor([2, 1, 0, 1, 2])),
|
||||
vals=[[1., 2., 3.]])
|
||||
|
||||
@unittest.expectedFailure
|
||||
@unittest.skipIf(torch._C._get_privateuse1_backend_name() == "tiny", 'results in a success instead of a failure')
|
||||
def test_gather_failure(self):
|
||||
# gather with inf values do not work, other values results in nan
|
||||
# gather with inf values
|
||||
helper_test_op(None, lambda x: x.gather(dim=0, index=torch.tensor([2, 1, 0, 1, 2], requires_grad=False)),
|
||||
lambda x: x.gather(dim=0, index=Tensor([2, 1, 0, 1, 2])),
|
||||
vals=[[-float("inf"), 2., 3.]])
|
||||
|
||||
+14
-1
@@ -1,6 +1,6 @@
|
||||
import unittest, struct, contextlib, statistics, time, gc
|
||||
from tinygrad import Device, Tensor, dtypes, TinyJit
|
||||
from tinygrad.helpers import CI, getenv, Context, ProfileRangeEvent, cpu_profile, cpu_events
|
||||
from tinygrad.helpers import CI, getenv, Context, ProfileRangeEvent, cpu_profile, cpu_events, ProfilePointEvent, dedup
|
||||
from tinygrad.device import Buffer, BufferSpec, Compiled, ProfileDeviceEvent, ProfileGraphEvent
|
||||
from tinygrad.runtime.support.hcq import HCQCompiled
|
||||
from tinygrad.engine.realize import get_runner
|
||||
@@ -209,5 +209,18 @@ class TestProfiler(unittest.TestCase):
|
||||
for ge in graphs:
|
||||
self.assertEqual(len(ge.ents), len(graphs))
|
||||
|
||||
def test_trace_metadata(self):
|
||||
with Context(TRACEMETA=1):
|
||||
a = Tensor.empty(1)+2
|
||||
b = Tensor.empty(1)+2
|
||||
with helper_collect_profile(TestProfiler.d0) as profile:
|
||||
Tensor.realize(a, b)
|
||||
profile, _ = helper_profile_filter_device(profile, TestProfiler.d0.device)
|
||||
exec_points = [e for e in profile if isinstance(e, ProfilePointEvent) and e.name == "exec"]
|
||||
range_events = [e for e in profile if isinstance(e, ProfileRangeEvent)]
|
||||
self.assertEqual(len(exec_points), len(range_events), 2)
|
||||
self.assertEqual(len(dedup(e.key for e in exec_points)), 1)
|
||||
self.assertEqual(len(dedup(e.arg['metadata'] for e in exec_points)), 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import unittest
|
||||
from tinygrad import Tensor
|
||||
from tinygrad.helpers import RANGEIFY
|
||||
|
||||
N = 256
|
||||
|
||||
@unittest.skipIf(RANGEIFY<1, "tests only for RANGEIFY")
|
||||
class TestRangeify(unittest.TestCase):
|
||||
def test_expand_children(self):
|
||||
A = Tensor.empty(N, N).sum(axis=1)
|
||||
ba = A.expand(N, N)
|
||||
((ba+1).sum(axis=1) + (ba+2).sum(axis=0)).realize()
|
||||
|
||||
def test_double_gemm(self):
|
||||
A = Tensor.empty(N, N)
|
||||
B = Tensor.empty(N, N)
|
||||
C = Tensor.empty(N, N)
|
||||
(A@B@C).realize()
|
||||
|
||||
def test_double_gemm_exp(self):
|
||||
A = Tensor.empty(N, N)
|
||||
B = Tensor.empty(N, N)
|
||||
C = Tensor.empty(N, N)
|
||||
(((A@B).exp()@C).exp()).realize()
|
||||
|
||||
def test_double_gemm_relu(self):
|
||||
A = Tensor.empty(N, N)
|
||||
B = Tensor.empty(N, N)
|
||||
C = Tensor.empty(N, N)
|
||||
(((A@B).relu()@C).relu()).realize()
|
||||
|
||||
def test_double_gemm_relu_half_contig(self):
|
||||
A = Tensor.empty(N, N)
|
||||
B = Tensor.empty(N, N)
|
||||
C = Tensor.empty(N, N)
|
||||
(((A@B).relu().contiguous(arg=(1,))@C).relu()).realize()
|
||||
|
||||
def test_double_gemm_half_contig(self):
|
||||
A = Tensor.empty(N, N)
|
||||
B = Tensor.empty(N, N)
|
||||
C = Tensor.empty(N, N)
|
||||
((A@B).contiguous(arg=(1,))@C).realize()
|
||||
|
||||
def test_double_gemm_contig(self):
|
||||
A = Tensor.empty(N, N)
|
||||
B = Tensor.empty(N, N)
|
||||
C = Tensor.empty(N, N)
|
||||
((A@B).contiguous()@C).realize()
|
||||
|
||||
def test_many_gemm(self):
|
||||
A = Tensor.empty(N, N)
|
||||
B = Tensor.empty(N, N)
|
||||
C = Tensor.empty(N, N)
|
||||
D = Tensor.empty(N, N)
|
||||
E = Tensor.empty(N, N)
|
||||
F = Tensor.empty(N, N)
|
||||
(A@B@C@D@E@F).realize()
|
||||
|
||||
def test_conv2d(self):
|
||||
x = Tensor.empty(1, 4, 32, 32)
|
||||
w1 = Tensor.empty(8, 4, 3, 3)
|
||||
x.conv2d(w1).realize()
|
||||
|
||||
def test_conv2d_t(self):
|
||||
x = Tensor.empty(1, 4, 32, 32)
|
||||
w1 = Tensor.empty(8, 4, 3, 3)
|
||||
(x*2).conv2d(w1).realize()
|
||||
|
||||
def test_double_conv2d(self):
|
||||
x = Tensor.empty(1, 4, 32, 32)
|
||||
w1 = Tensor.empty(8, 4, 3, 3)
|
||||
w2 = Tensor.empty(12, 8, 3, 3)
|
||||
x.conv2d(w1).conv2d(w2).realize()
|
||||
|
||||
def test_double_conv2d_half_contig(self):
|
||||
x = Tensor.empty(1, 4, 32, 32)
|
||||
w1 = Tensor.empty(8, 4, 3, 3)
|
||||
w2 = Tensor.empty(12, 8, 3, 3)
|
||||
# NOTE: this contiguous doesn't help
|
||||
x.conv2d(w1).contiguous(arg=(1,)).conv2d(w2).permute(0,2,3,1).contiguous().realize()
|
||||
|
||||
def test_double_conv2d_contig(self):
|
||||
x = Tensor.empty(1, 4, 32, 32)
|
||||
w1 = Tensor.empty(8, 4, 3, 3)
|
||||
w2 = Tensor.empty(12, 8, 3, 3)
|
||||
x.conv2d(w1).contiguous().conv2d(w2).realize()
|
||||
|
||||
def test_transformer_ffn(self):
|
||||
from tinygrad.apps.llm import TransformerBlock
|
||||
from tinygrad import nn
|
||||
blk = TransformerBlock(1024, 4096, 1, 1, 1e-5)
|
||||
for p in nn.state.get_parameters(blk): p.replace(Tensor.empty(p.shape))
|
||||
|
||||
x = Tensor.empty(128, 1024)
|
||||
out = blk._feed_forward(x)
|
||||
out.realize()
|
||||
|
||||
def test_flash_attention(self):
|
||||
BS = 4
|
||||
HEADS = 2
|
||||
MATDIM = 16
|
||||
EMB = 8
|
||||
q = Tensor.empty(BS, HEADS, MATDIM, EMB)
|
||||
k = Tensor.empty(BS, HEADS, MATDIM, EMB)
|
||||
v = Tensor.empty(BS, HEADS, MATDIM, EMB)
|
||||
q.scaled_dot_product_attention(k, v).realize()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -163,7 +163,7 @@ class TestSoftmaxFusion(unittest.TestCase):
|
||||
out = single_kernel_softmax(self.test)
|
||||
out.realize()
|
||||
|
||||
np.testing.assert_allclose(sout.numpy(), out.numpy())
|
||||
np.testing.assert_allclose(sout.numpy(), out.numpy(), atol=3e-7)
|
||||
|
||||
def test_auto_softmax(self):
|
||||
print("*** softmax ***")
|
||||
@@ -176,7 +176,7 @@ class TestSoftmaxFusion(unittest.TestCase):
|
||||
out = self.test.contiguous().softmax(-1).fuse()
|
||||
run_one_schedule_item(out)
|
||||
|
||||
np.testing.assert_allclose(sout.numpy(), out.numpy())
|
||||
np.testing.assert_allclose(sout.numpy(), out.numpy(), atol=3e-7)
|
||||
|
||||
@unittest.skip("recursion error no longer raised")
|
||||
def test_softmax_bw(self):
|
||||
|
||||
@@ -229,12 +229,12 @@ class TestSymbolicOps(unittest.TestCase):
|
||||
np.testing.assert_allclose(symbolic, expected, atol=1e-6, rtol=1e-6)
|
||||
|
||||
def test_var(self):
|
||||
a = Tensor.rand(10, 3)
|
||||
for i in range(1, 5):
|
||||
vi = Variable("i", 1, 10).bind(i)
|
||||
for axis in [None, 0, 1]:
|
||||
a = Tensor.rand(i, 3)
|
||||
expected = a.var(axis).numpy()
|
||||
symbolic = a.reshape(vi, 3).var(axis).reshape(expected.shape).numpy()
|
||||
expected = a[:i, :].var(axis).numpy()
|
||||
symbolic = a[:vi, :].var(axis).reshape(expected.shape).numpy()
|
||||
np.testing.assert_allclose(symbolic, expected, atol=1e-6, rtol=1e-6)
|
||||
|
||||
def test_var_2d(self):
|
||||
|
||||
+8
-8
@@ -73,17 +73,17 @@ class TestTiny(unittest.TestCase):
|
||||
|
||||
def test_symbolic(self):
|
||||
i = Variable('i', 1, 10)
|
||||
with Context(IGNORE_OOB=1):
|
||||
for s in [2,5]:
|
||||
ret = Tensor.ones(s).contiguous().reshape(i.bind(s)) + 1
|
||||
self.assertListEqual(ret.reshape(s).tolist(), [2.0]*s)
|
||||
ones = Tensor.ones(10).contiguous()
|
||||
for s in [2,5]:
|
||||
ret = ones[:i.bind(s)] + 1
|
||||
self.assertListEqual(ret.contiguous().reshape(s).tolist(), [2.0]*s)
|
||||
|
||||
def test_symbolic_reduce(self):
|
||||
i = Variable('i', 1, 10)
|
||||
with Context(IGNORE_OOB=1):
|
||||
for s in [2,5]:
|
||||
ret = Tensor.ones(s).contiguous().reshape(i.bind(s)).sum()
|
||||
self.assertEqual(ret.item(), s)
|
||||
ones = Tensor.ones(10).contiguous()
|
||||
for s in [2,5]:
|
||||
ret = ones[:i.bind(s)].sum()
|
||||
self.assertEqual(ret.item(), s)
|
||||
|
||||
# *** a model ***
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ def get_stats(x:Tensor):
|
||||
ei = lower_schedule_item(si)
|
||||
return ei.prg.estimates.ops, ei.prg.estimates.mem
|
||||
|
||||
@unittest.skipIf(Device.DEFAULT == "WEBGPU", "webgpu does extra load/store for packed types")
|
||||
class TestMemoryCount(unittest.TestCase):
|
||||
def test_add(self):
|
||||
a = Tensor.empty(1024, 1024, dtype=dtypes.uint8)
|
||||
|
||||
@@ -2,7 +2,6 @@ from typing_extensions import Callable
|
||||
import hashlib, random, unittest
|
||||
from tinygrad import Tensor, Device, getenv, dtypes
|
||||
from tinygrad.device import is_dtype_supported
|
||||
from tinygrad.helpers import CI
|
||||
|
||||
@unittest.skipUnless(is_dtype_supported(dtypes.uint8) and is_dtype_supported(dtypes.uint64), "Device must support uint8 and uint64")
|
||||
@unittest.skipIf(getenv("MOCKGPU") and Device.DEFAULT == "NV", "crashes in NV CI")
|
||||
@@ -12,7 +11,7 @@ class TestHashing(unittest.TestCase):
|
||||
chunk_hashes = [hashlib.shake_128(chunk).digest(16) for chunk in chunks]
|
||||
return hashlib.shake_128(b''.join(chunk_hashes)).digest(16)
|
||||
|
||||
@unittest.skipIf(CI, "very slow")
|
||||
@unittest.skip("very slow")
|
||||
def test_abc(self):
|
||||
expected = self._python_hash_1mb(b"abc" + b"\x00" * (2**20 - 3))
|
||||
out = Tensor(b"abc").hash()
|
||||
@@ -65,7 +64,7 @@ class TestKeccak(unittest.TestCase):
|
||||
data = b"\x00" * 4
|
||||
self.assertEqual(bytes(Tensor(data).keccak("shake_128").tolist()), hashlib.shake_128(data).digest(16))
|
||||
|
||||
data = b"\x00" * (1000 if CI else 4096)
|
||||
data = b"\x00" * 1000
|
||||
self.assertEqual(bytes(Tensor(data).keccak("shake_128").tolist()), hashlib.shake_128(data).digest(16))
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,7 +2,7 @@ import unittest, math
|
||||
import numpy as np
|
||||
from tinygrad import dtypes
|
||||
from tinygrad.uop.ops import UOp, Ops
|
||||
from tinygrad.uop.decompositions import TRANSCENDENTAL_SUPPORTED_DTYPES, payne_hanek_reduction, cody_waite_reduction
|
||||
from tinygrad.uop.decompositions import TRANSCENDENTAL_DTYPES, payne_hanek_reduction, cody_waite_reduction
|
||||
from tinygrad.uop.decompositions import frexp, rintk, xpow, xexp2, xlog2, trig_poly, pow2if
|
||||
from test.helpers import eval_uop
|
||||
|
||||
@@ -89,7 +89,7 @@ class TestTranscendentalVectorizedFunctions(unittest.TestCase):
|
||||
assert u1.op == u2.op, f'expected {u1.op=} but got {u2.op=} for UOps\n{u1=}\n{u2}'
|
||||
[self._check_uops_match(x1, x2) for x1, x2 in zip((u1 if isinstance(u1, tuple) else u1.src), (u2 if isinstance(u2, tuple) else u2.src))]
|
||||
|
||||
def _test_vectorized(self, fxn, scalar_dtypes=TRANSCENDENTAL_SUPPORTED_DTYPES, vals=[-2,1.3,194], vcounts=[1,4,19]):
|
||||
def _test_vectorized(self, fxn, scalar_dtypes=TRANSCENDENTAL_DTYPES, vals=[-2,1.3,194], vcounts=[1,4,19]):
|
||||
for scalar_dtype in scalar_dtypes:
|
||||
for val in vals:
|
||||
for vcount in vcounts:
|
||||
|
||||
@@ -622,6 +622,8 @@ class TestSymbolic(unittest.TestCase):
|
||||
self.helper_test_variable(cond, 0, 1, "(a<2)")
|
||||
self.helper_test_variable(cond.where(u1, u0), 0, 1, "(a<2)")
|
||||
self.helper_test_variable(cond.where(u1, u0).where(u1, u0), 0, 1, "(a<2)")
|
||||
self.helper_test_variable(cond.where(u0, u1), 0, 1, "((a<2)!=True)")
|
||||
self.helper_test_variable(cond.where(u0, u1).where(u0, u1), 0, 1, "(a<2)")
|
||||
|
||||
def test_where_combine(self):
|
||||
cond = Variable("x", 0, 3) < 2
|
||||
|
||||
+10
-1
@@ -5,7 +5,7 @@ from tinygrad.uop.ops import UOp, UPat, Ops, PatternMatcher, TrackedPatternMatch
|
||||
from tinygrad.uop.ops import graph_rewrite, track_rewrites, TRACK_MATCH_STATS
|
||||
from tinygrad.uop.symbolic import sym
|
||||
from tinygrad.dtype import dtypes
|
||||
from tinygrad.helpers import PROFILE, colored, ansistrip, flatten, TracingKey, ProfileRangeEvent
|
||||
from tinygrad.helpers import PROFILE, colored, ansistrip, flatten, TracingKey, ProfileRangeEvent, Context
|
||||
from tinygrad.device import Buffer
|
||||
|
||||
@track_rewrites(name=True)
|
||||
@@ -240,6 +240,15 @@ class TestVizIntegration(BaseTestViz):
|
||||
self.assertEqual(lst[0]["name"], "Schedule 1 Kernel n1")
|
||||
self.assertEqual(lst[1]["name"], prg.name)
|
||||
|
||||
def test_metadata_tracing(self):
|
||||
with Context(TRACEMETA=2):
|
||||
a = Tensor.empty(1)
|
||||
b = Tensor.empty(1)
|
||||
metadata = (alu:=a+b).uop.metadata
|
||||
alu.kernelize()
|
||||
graph = next(get_details(tracked_ctxs[0][0]))["graph"]
|
||||
self.assertEqual(len([n for n in graph.values() if repr(metadata) in n["label"]]), 1)
|
||||
|
||||
from tinygrad.device import ProfileDeviceEvent, ProfileGraphEvent, ProfileGraphEntry
|
||||
from tinygrad.viz.serve import get_profile
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
python3 test/external/process_replay/reset.py
|
||||
CAPTURE_PROCESS_REPLAY=1 pytest -n auto test/test_tiny.py test/test_uop_graph.py test/test_ops.py test/test_linearizer.py
|
||||
while true; do
|
||||
if python3 test/test_tiny.py; then
|
||||
PYTHONPATH="." python3 test/external/process_replay/process_replay.py
|
||||
fi
|
||||
done
|
||||
@@ -364,8 +364,7 @@ def reduce_collapse(red:UOp):
|
||||
replaces[s] = UOp(Ops.DEFINE_VAR, dtype=s.dtype, arg=(f'in{len(replaces)}', s.vmin, s.vmax))
|
||||
collapse_fxn = red.substitute(replaces)
|
||||
sink = graph_rewrite(collapse_fxn, pm_reduce_collapse, name="reduce_collapse")
|
||||
# TODO: why is REDUCE needed here and just RANGE isn't enough?
|
||||
if any(x.op in {Ops.REDUCE, Ops.RANGE} for x in sink.toposort()): return None
|
||||
if any(x.op is Ops.RANGE for x in sink.toposort()): return None
|
||||
return sink.substitute({v:k for k,v in replaces.items()})
|
||||
|
||||
def reduce_unparented(red:UOp):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import cast, Generator
|
||||
import time, pprint
|
||||
import time, pprint, decimal
|
||||
from dataclasses import dataclass, replace, field
|
||||
from tinygrad.helpers import all_same, colored, DEBUG, GlobalCounters, ansilen, BEAM, NOOPT, all_int, CAPTURING, Metadata, TRACEMETA, TracingKey
|
||||
from tinygrad.helpers import DEVECTORIZE, time_to_str, VALIDATE_WITH_CPU, getenv, cpu_profile
|
||||
from tinygrad.helpers import DEVECTORIZE, time_to_str, VALIDATE_WITH_CPU, getenv, cpu_profile, PROFILE, ProfilePointEvent, cpu_events
|
||||
from tinygrad.uop.ops import Ops, PatternMatcher, UOp, UPat, Variable, sym_infer, graph_rewrite, print_uops, track_rewrites, KernelInfo
|
||||
from tinygrad.device import Device, Buffer
|
||||
from tinygrad.renderer import Renderer, ProgramSpec, Estimates
|
||||
@@ -149,6 +149,8 @@ class ExecItem:
|
||||
def run(self, _var_vals:dict[Variable, int]|None=None, wait=False, jit=False, do_update_stats=True) -> float|None:
|
||||
var_vals = self.fixedvars if _var_vals is None else (_var_vals|self.fixedvars)
|
||||
bufs = [cast(Buffer, x) for x in self.bufs] if jit else [cast(Buffer, x).ensure_allocated() for x in self.bufs]
|
||||
if PROFILE: cpu_events.append(ProfilePointEvent(self.prg.device, "exec", decimal.Decimal(time.perf_counter_ns())/1000, self.prg.display_name,
|
||||
{"metadata":self.metadata, "var_vals":var_vals}))
|
||||
et = self.prg(bufs, var_vals, wait=wait or DEBUG >= 2)
|
||||
if do_update_stats:
|
||||
GlobalCounters.kernel_count += 1
|
||||
|
||||
@@ -33,7 +33,7 @@ def create_schedule_with_vars(sched_sink:UOp) -> tuple[list[ScheduleItem], dict[
|
||||
for ss in s.src:
|
||||
if ss.op is Ops.MSELECT: ss = ss.src[0]
|
||||
if ss.op is not Ops.BUFFER:
|
||||
assert ss.op is Ops.ASSIGN
|
||||
assert ss.op is Ops.ASSIGN, f"ss.op is not ASSIGN, it's {ss.op}"
|
||||
children[ss.src[1]].append(k)
|
||||
in_degree[k] += 1
|
||||
elif s.op is Ops.BUFFER:
|
||||
|
||||
@@ -5,7 +5,7 @@ from io import BufferedReader
|
||||
from tinygrad.nn.state import TensorIO
|
||||
from tinygrad.tensor import Tensor, _broadcast_shape, ReductionStr
|
||||
from tinygrad.helpers import getenv, DEBUG, all_same, prod, flatten, make_tuple, argsort, is_numpy_ndarray, get_single_element, polyN
|
||||
from tinygrad.dtype import DType, ConstType, dtypes, _from_np_dtype
|
||||
from tinygrad.dtype import DType, ConstType, dtypes, _from_np_dtype, truncate
|
||||
from tinygrad.device import is_dtype_supported, Device
|
||||
|
||||
# ***** protobuf definitions ******
|
||||
@@ -105,9 +105,7 @@ class PBBufferedReader(BufferedReader):
|
||||
def read_bytes(self) -> Tensor: return self.read_delimited(use_tensor=True)
|
||||
def read_float(self) -> float: return struct.unpack("<f", self.read(4))[0]
|
||||
def read_packed_floats(self) -> Tensor: return self.read_delimited(use_tensor=True)
|
||||
def read_int64(self) -> int:
|
||||
val = self.decode_varint()
|
||||
return val - 2**64 if val & (1 << 63) else val
|
||||
def read_int64(self) -> int: return truncate[dtypes.int64](self.decode_varint())
|
||||
def read_packed_int64s(self) -> list[int]:
|
||||
total_bytes_len = self.decode_varint()
|
||||
old_pos = self.tell()
|
||||
@@ -1074,7 +1072,7 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
|
||||
if X.ndim == 4: X = X.permute(0, 2, 1, 3)
|
||||
elif X.ndim == 3:
|
||||
assert num_heads is not None, "num_heads must be provided for 3D input"
|
||||
X = X.reshape(*X.shape[:-1], num_heads, X.shape[-1] // num_heads)
|
||||
X = X.unflatten(-1, (num_heads, X.shape[-1] // num_heads))
|
||||
|
||||
head_size = cast(int, X.shape[-1])
|
||||
rot_dim = rotary_embedding_dim or head_size
|
||||
@@ -1085,16 +1083,10 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
|
||||
cos = cos[..., :rot_dim//2].unsqueeze(2)
|
||||
sin = sin[..., :rot_dim//2].unsqueeze(2)
|
||||
|
||||
if interleaved:
|
||||
x1, x2 = x_rotate[..., ::2], x_rotate[..., 1::2]
|
||||
real = x1 * cos - x2 * sin
|
||||
imag = x1 * sin + x2 * cos
|
||||
x_rotated = Tensor.stack(real, imag, dim=-1).flatten(start_dim=-2)
|
||||
else:
|
||||
x1, x2 = x_rotate.chunk(2, dim=-1)
|
||||
real = x1 * cos - x2 * sin
|
||||
imag = x1 * sin + x2 * cos
|
||||
x_rotated = real.cat(imag, dim=-1)
|
||||
x1, x2 = (x_rotate[..., ::2], x_rotate[..., 1::2]) if interleaved else x_rotate.chunk(2, dim=-1)
|
||||
real = x1 * cos - x2 * sin
|
||||
imag = x1 * sin + x2 * cos
|
||||
x_rotated = real.stack(imag, dim=-1).flatten(start_dim=-2) if interleaved else real.cat(imag, dim=-1)
|
||||
|
||||
output = x_rotated.cat(x_pass, dim=-1)
|
||||
return output.flatten(start_dim=2) if len(original_input_shape) == 3 else output.permute(0, 2, 1, 3)
|
||||
|
||||
+4
-3
@@ -137,9 +137,10 @@ PICKLE_BUFFERS, PROFILE, LRU = ContextVar("PICKLE_BUFFERS", 1), ContextVar("PROF
|
||||
CACHELEVEL, IGNORE_BEAM_CACHE, DEVECTORIZE = ContextVar("CACHELEVEL", 2), ContextVar("IGNORE_BEAM_CACHE", 0), ContextVar("DEVECTORIZE", 1)
|
||||
DISABLE_COMPILER_CACHE = ContextVar("DISABLE_COMPILER_CACHE", 0)
|
||||
DONT_REALIZE_EXPAND, DONT_GROUP_REDUCES = ContextVar("DONT_REALIZE_EXPAND", 0), ContextVar("DONT_GROUP_REDUCES", 0)
|
||||
QUANTIZE, VALIDATE_WITH_CPU = ContextVar("QUANTIZE", 0), ContextVar("VALIDATE_WITH_CPU", 0)
|
||||
QUANTIZE, VALIDATE_WITH_CPU, DISABLE_FAST_IDIV = ContextVar("QUANTIZE", 0), ContextVar("VALIDATE_WITH_CPU", 0), ContextVar("DISABLE_FAST_IDIV", 0)
|
||||
CORRECT_DIVMOD_FOLDING, FUSE_OPTIM = ContextVar("CORRECT_DIVMOD_FOLDING", 0), ContextVar("FUSE_OPTIM", 0)
|
||||
ALLOW_DEVICE_USAGE, MAX_BUFFER_SIZE, AMD_LLVM = ContextVar("ALLOW_DEVICE_USAGE", 1), ContextVar("MAX_BUFFER_SIZE", 0), ContextVar("AMD_LLVM", 1)
|
||||
RANGEIFY = ContextVar("RANGEIFY", 0)
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Metadata:
|
||||
@@ -205,7 +206,7 @@ class ProfileEvent: pass
|
||||
class ProfileRangeEvent(ProfileEvent): device:str; name:str|TracingKey; st:decimal.Decimal; en:decimal.Decimal|None=None; is_copy:bool=False # noqa: E702
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProfilePointEvent(ProfileEvent): device:str; name:str; ts:decimal.Decimal; key:int; arg:dict=field(default_factory=dict) # noqa: E702
|
||||
class ProfilePointEvent(ProfileEvent): device:str; name:str; ts:decimal.Decimal; key:Any; arg:dict=field(default_factory=dict) # noqa: E702
|
||||
|
||||
cpu_events:list[ProfileEvent] = []
|
||||
@contextlib.contextmanager
|
||||
@@ -293,7 +294,7 @@ def fetch(url:str, name:pathlib.Path|str|None=None, subdir:str|None=None, gunzip
|
||||
else: fp = _ensure_downloads_dir() / (subdir or "") / ((name or hashlib.md5(url.encode('utf-8')).hexdigest()) + (".gunzip" if gunzip else ""))
|
||||
if not fp.is_file() or not allow_caching:
|
||||
(_dir := fp.parent).mkdir(parents=True, exist_ok=True)
|
||||
with urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "tinygrad 0.10.3"}), timeout=10) as r:
|
||||
with urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "tinygrad 0.11.0"}), timeout=10) as r:
|
||||
assert r.status == 200, r.status
|
||||
length = int(r.headers.get('content-length', 0)) if not gunzip else None
|
||||
readfile = gzip.GzipFile(fileobj=r) if gunzip else r
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
from typing import Callable, cast, TYPE_CHECKING
|
||||
import functools, itertools
|
||||
from dataclasses import dataclass, field, replace
|
||||
import functools
|
||||
from dataclasses import dataclass, field
|
||||
from tinygrad.helpers import to_function_name, dedup, prod
|
||||
from tinygrad.uop.ops import Ops, UOp, sym_infer, sint, Variable, ssimplify, GroupOp, PatternMatcher
|
||||
from tinygrad.dtype import AddrSpace, PtrDType
|
||||
@@ -23,6 +23,7 @@ class Estimates:
|
||||
def from_uops(uops:list[UOp], ignore_indexing=False) -> Estimates:
|
||||
flops: sint = 0
|
||||
lds: sint = 0
|
||||
mem: dict[tuple[UOp, Ops], sint] = {}
|
||||
mults: sint = 1
|
||||
mult_stack: list[sint] = []
|
||||
dont_count: set[UOp] = set()
|
||||
@@ -34,6 +35,11 @@ class Estimates:
|
||||
elif u.op is Ops.IF:
|
||||
dont_count = dont_count.union(u.src[0].toposort())
|
||||
for u in uops:
|
||||
if u.op in {Ops.LOAD, Ops.STORE}:
|
||||
buf = u
|
||||
while len(buf.src): buf = buf.src[0]
|
||||
if buf.op is Ops.DEFINE_GLOBAL: # assume all DEFINE_GLOBAL memory is accessed
|
||||
mem[(buf, u.op)] = cast(PtrDType, buf.dtype).size * buf.dtype.itemsize
|
||||
if u.op is Ops.RANGE:
|
||||
mult_stack.append(mults)
|
||||
mults *= cast(sint, u.src[0].ssimplify())
|
||||
@@ -47,7 +53,7 @@ class Estimates:
|
||||
lds += u.src[1].dtype.itemsize * mults
|
||||
elif u.op in GroupOp.ALU and u not in dont_count: flops += (mults * (2 if u.op is Ops.MULACC else 1)) * u.dtype.count
|
||||
elif u.op is Ops.WMMA and u not in dont_count: flops += 2 * prod(u.arg[1]) // u.arg[5] * mults
|
||||
return Estimates(flops, lds, lds) # TODO: properly track memory, lds is always a high estimate
|
||||
return Estimates(flops, lds, sum(mem.values()))
|
||||
|
||||
@dataclass
|
||||
class ProgramSpec:
|
||||
@@ -84,17 +90,9 @@ class ProgramSpec:
|
||||
self.ins = sorted(dedup(self.ins))
|
||||
self._ran_post_init = True
|
||||
|
||||
@functools.cached_property
|
||||
def mem_estimate(self) -> sint:
|
||||
# group non-local bufs by the op type (LOAD or STORE) and the buffer arg. take the max access of that buffer in bytes
|
||||
# TODO: these max and min don't work on symbolic, and results are very wrong.
|
||||
return sum(max(x.src[0].dtype.nbytes() for x in group)
|
||||
for _, group in itertools.groupby([x for x in self.ast.toposort() if x.op in {Ops.LOAD, Ops.STORE} and x.src[0].base.op is Ops.DEFINE_GLOBAL],
|
||||
key=lambda x: (x.op, x.src[0].base.arg)))
|
||||
|
||||
@functools.cached_property
|
||||
def estimates(self) -> Estimates:
|
||||
return replace(Estimates() if self.uops is None else Estimates.from_uops(self.uops, ignore_indexing=True), mem=self.mem_estimate)
|
||||
return Estimates() if self.uops is None else Estimates.from_uops(self.uops, ignore_indexing=True)
|
||||
|
||||
@functools.cached_property
|
||||
def function_name(self) -> str: return to_function_name(self.name)
|
||||
|
||||
@@ -63,8 +63,6 @@ extra_pm = PatternMatcher([
|
||||
# insert a PRECAST before BITCAST to force it to be rendered. not needed on all backends?
|
||||
(UPat(Ops.BITCAST, name="x"), lambda x: UOp(Ops.BITCAST, x.dtype, (UOp(Ops.PRECAST, x.src[0].dtype, x.src),))
|
||||
if x.src[0].op not in {Ops.PRECAST, Ops.LOAD, Ops.CUSTOM} else None),
|
||||
# rewrite MAX to CMPLT + WHERE (max function is annoying on many cstyle backends)
|
||||
(UPat(Ops.MAX, name="m"), lambda m: (m.src[0] < m.src[1]).where(m.src[1], m.src[0])),
|
||||
# devectorize any bools
|
||||
(UPat((*GroupOp.ALU, Ops.CAST, Ops.BITCAST, Ops.INDEX), dtype=dtypes.bool, name="alu"), no_vectorized_alu),
|
||||
# CAST (from bool) can't be vectorized
|
||||
@@ -201,12 +199,13 @@ class ClangRenderer(CStyleLanguage):
|
||||
# language options
|
||||
buffer_suffix = " restrict"
|
||||
type_map = {dtypes.bool:"_Bool", dtypes.half:"__fp16"}
|
||||
code_for_op = {**({k:v for k,v in CStyleLanguage.code_for_op.items() if k not in [Ops.EXP2, Ops.SIN, Ops.LOG2, Ops.TRUNC]}),
|
||||
code_for_op = {**({k:v for k,v in CStyleLanguage.code_for_op.items() if k not in [Ops.EXP2, Ops.SIN, Ops.LOG2, Ops.TRUNC, Ops.RECIP]}),
|
||||
Ops.SQRT: lambda x,dtype: f"__builtin_sqrt({x})" if dtype == dtypes.float64 else f"__builtin_sqrtf({x})",
|
||||
Ops.TRUNC: lambda x,dtype: f"__builtin_trunc({x})" if dtype == dtypes.float64 else f"__builtin_truncf({x})"}
|
||||
Ops.TRUNC: lambda x,dtype: f"__builtin_trunc({x})" if dtype == dtypes.float64 else f"__builtin_truncf({x})",
|
||||
Ops.FDIV: lambda a,b,dtype: f"({a}/{b})"}
|
||||
# LLVM legalizes double => half cast on systems that don't support it natively (like x86 cpus without AVX512-FP16) into a compiler-rt libcall.
|
||||
extra_matcher = PatternMatcher([(UPat.var("x", dtypes.float64).cast(dtypes.float16), lambda x: x.cast(dtypes.float32).cast(dtypes.float16)),
|
||||
(UPat((Ops.SQRT, Ops.TRUNC), name="alu"), no_vectorized_alu),]) + CStyleLanguage.extra_matcher
|
||||
(UPat((Ops.SQRT, Ops.TRUNC), name="alu"), no_vectorized_alu)]) + CStyleLanguage.extra_matcher
|
||||
|
||||
if sys.platform == 'win32':
|
||||
kernel_typedef = "__attribute__((ms_abi)) void"
|
||||
|
||||
+18
-11
@@ -45,10 +45,10 @@ def render_wmma_amx(ctx, wmma: UOp) -> str:
|
||||
f' call void asm sideeffect "nop\\0Anop\\0Anop\\0A.word ({0x201000 + (17 << 5) + 1})", "~{{memory}}"() #0; AMX clr', # clr
|
||||
f' {ctx[wmma]} = load {ldt(wmma.dtype)}, ptr {ctx[wmma]}_amx2, align {wmma.dtype.itemsize}'])
|
||||
|
||||
def render_wmma_amd(ctx, wmma: UOp, arch: str) -> str:
|
||||
dt_map = {dtypes.half: "f16", dtypes.float: "f32", dtypes.bfloat16: "bf16", dtypes.ushort: "bf16"}
|
||||
def render_wmma_amd(ctx, wmma: UOp, cdna=False) -> str:
|
||||
dt_map = {dtypes.half: "f16", dtypes.float: "f32", dtypes.ushort: "bf16.1k" if cdna else "bf16", dtypes.bfloat16: "bf16.1k" if cdna else "bf16"}
|
||||
# https://github.com/llvm/llvm-project/blob/main/clang/test/CodeGenOpenCL/builtins-amdgcn-mfma.cl
|
||||
if arch.split(":")[0] in {"gfx942", "gfx950"}:
|
||||
if cdna:
|
||||
return f" {ctx[wmma]} = call {ldt(wmma.dtype)} @llvm.amdgcn.mfma.{dt_map[wmma.src[-1].dtype.scalar()]}" + \
|
||||
f".16x16x16{dt_map[wmma.src[0].dtype.scalar()]}(" + ", ".join([f"{ldt(w.dtype)} {ctx[w]}" for w in wmma.src]) + ", i32 0, i32 0, i32 0)"
|
||||
# https://github.com/llvm/llvm-project/blob/main/llvm/test/CodeGen/AMDGPU/GlobalISel/llvm.amdgcn.wmma_32.ll
|
||||
@@ -123,11 +123,10 @@ class LLVMRenderer(Renderer):
|
||||
has_local = False
|
||||
global_max: tuple[int, ...] | None = None
|
||||
string_rewrite = base_rewrite + PatternMatcher([(UPat(Ops.WMMA, name="wmma"), render_wmma_amx)])
|
||||
code_for_op = {Ops.FDIV: lambda: None}
|
||||
if AMX: tensor_cores = tc.amx
|
||||
|
||||
extra_matcher = PatternMatcher([
|
||||
# rewrite RECIP with FDIV
|
||||
(UPat(Ops.RECIP, name="x"), lambda x: UOp(Ops.FDIV, x.dtype, (x.const_like(1), x.src[0]))),
|
||||
# rewrite cast to bool to CMPNE 0
|
||||
(UPat(Ops.CAST, dtype=dtypes.bool, name="x"), lambda x: x.src[0] != x.src[0].const_like(0)),
|
||||
# rewrite MAX to CMPLT + WHERE
|
||||
@@ -206,12 +205,13 @@ class AMDLLVMRenderer(LLVMRenderer):
|
||||
string_rewrite = PatternMatcher([
|
||||
(UPat(Ops.SPECIAL, name="x"), lambda ctx, x: f" {ctx[x]} = " + f"{ code_for_workitem[x.arg[0][0]](x.arg[0][-1])}; "),
|
||||
(UPat(Ops.BARRIER), lambda ctx: barrier),
|
||||
(UPat(Ops.CAST, name="x", dtype=dtypes.half.vec(16), src=UPat.var("y", dtypes.half.vec(8))), lambda ctx, x, y: f" {ctx[x]} = shufflevector "\
|
||||
f"<8 x half> {ctx[y]}, <8 x half> zeroinitializer, <16 x i32> <{', '.join([f'i32 {i}, i32 {j}' for i, j in zip(range(0, 8), range(8, 16))])}>"),
|
||||
(UPat(Ops.CAST, name="x", dtype=dtypes.half.vec(8), src=UPat.var("y", dtypes.half.vec(16))), lambda ctx, x, y:
|
||||
f" {ctx[x]}= shufflevector <16 x half> {ctx[y]}, <16 x half> undef, <8 x i32> <{', '.join([f'i32 {x}' for x in range(0, 16, 2)])}>"),
|
||||
]) + base_rewrite
|
||||
extra_matcher = LLVMRenderer.extra_matcher
|
||||
extra_matcher = LLVMRenderer.extra_matcher + PatternMatcher([
|
||||
(UPat(Ops.CAST, name="x", dtype=dtypes.half.vec(16), src=UPat.var("y", dtypes.half.vec(8))),
|
||||
lambda x, y: UOp(Ops.VECTORIZE, dtypes.half.vec(16), tuple(y.gep(i // 2) if i % 2 == 0 else UOp.const(dtypes.half, 0.0) for i in range(16)))),
|
||||
(UPat(Ops.CAST, name="x", dtype=dtypes.half.vec(8), src=UPat.var("y", dtypes.half.vec(16))),
|
||||
lambda x, y: UOp(Ops.VECTORIZE, dtypes.half.vec(8), tuple(y.gep(i * 2) for i in range(8)))),
|
||||
])
|
||||
def _render_footer(self, uops: list[UOp]) -> str:
|
||||
# TODO: this is copied from cstyle
|
||||
requiredMaxThreadsPerBlock = prod(u.arg[1] for u in uops if u.op is Ops.SPECIAL and u.arg[0][0] == "l")
|
||||
@@ -221,7 +221,14 @@ class AMDLLVMRenderer(LLVMRenderer):
|
||||
def __init__(self, arch:str):
|
||||
self.arch = arch
|
||||
self.tensor_cores = AMDRenderer.get_tensor_cores(arch)
|
||||
self.string_rewrite += PatternMatcher([(UPat(Ops.WMMA, name="wmma"), lambda ctx, wmma, arch=arch: render_wmma_amd(ctx, wmma, arch))])
|
||||
self.is_cdna = arch.split(":")[0] in {"gfx942", "gfx950"}
|
||||
self.string_rewrite += PatternMatcher([(UPat(Ops.WMMA, name="wmma"), lambda ctx, wmma, cdna=self.is_cdna: render_wmma_amd(ctx, wmma, cdna))])
|
||||
if self.is_cdna:
|
||||
self.extra_matcher += PatternMatcher([
|
||||
(UPat(Ops.WMMA, name="x", dtype=dtypes.float.vec(4)),
|
||||
lambda x: UOp(Ops.WMMA, dtypes.float.vec(4), (x.src[0].bitcast(dtypes.uint16.vec(4)), x.src[1].bitcast(dtypes.uint16.vec(4)),
|
||||
x.src[2]), (*x.arg,)) if x.src[0].dtype == dtypes.bfloat16.vec(4) else None)
|
||||
])
|
||||
if self.arch.split(":")[0] == "gfx1100":
|
||||
self.extra_matcher += PatternMatcher([
|
||||
(UPat(Ops.WMMA, name="x", dtype=dtypes.half.vec(8)),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+404
-67420
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ from tinygrad.runtime.autogen.am import am
|
||||
from tinygrad.runtime.support.compiler_amd import HIPCompiler, AMDLLVMCompiler
|
||||
from tinygrad.runtime.support.elf import elf_loader
|
||||
from tinygrad.runtime.support.am.amdev import AMDev, AMMemoryManager
|
||||
from tinygrad.runtime.support.amd import AMDReg, AMDIP, import_module, setup_pci_bars
|
||||
from tinygrad.runtime.support.amd import AMDReg, AMDIP, import_module, import_soc, setup_pci_bars
|
||||
from tinygrad.runtime.support.system import System, PCIIfaceBase, PCIAllocationMeta, MAP_FIXED, MAP_NORESERVE
|
||||
from tinygrad.runtime.support.usb import ASM24Controller, USBMMIOInterface
|
||||
if getenv("IOCTL"): import extra.hip_gpu_driver.hip_ioctl # noqa: F401 # pylint: disable=unused-import
|
||||
@@ -751,7 +751,7 @@ class AMDDevice(HCQCompiled):
|
||||
debug_memory_size = round_up((self.max_cu_id + 1 if self.target >= (10,1,0) else 1) * (self.max_wave_id + 1) * 32, 64)
|
||||
if self.target[0] == 10: ctl_stack_size = min(ctl_stack_size, 0x7000)
|
||||
|
||||
self.soc = importlib.import_module(f"tinygrad.runtime.autogen.am.{({9: 'vega10', 10: 'navi10', 11: 'soc21', 12: 'soc24'}[self.target[0]])}")
|
||||
self.soc = import_soc(self.target)
|
||||
self.pm4 = importlib.import_module(f"tinygrad.runtime.autogen.am.pm4_{'nv' if self.target[0] >= 10 else 'soc15'}")
|
||||
self.sdma = import_module('sdma', min(self.iface.ip_versions[am.SDMA0_HWIP], (6, 0, 0)))
|
||||
self.gc = AMDIP('gc', self.iface.ip_versions[am.GC_HWIP], self.iface.ip_offsets[am.GC_HWIP])
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from tinygrad.device import Compiled, Compiler, Allocator
|
||||
from tinygrad.engine.jit import MultiGraphRunner
|
||||
from tinygrad.renderer.cstyle import CStyleLanguage
|
||||
from tinygrad.uop.ops import Ops
|
||||
|
||||
class NullRenderer(CStyleLanguage):
|
||||
device = "NULL"
|
||||
has_local = False
|
||||
float4 = "float4"
|
||||
code_for_op = {**CStyleLanguage.code_for_op, Ops.THREEFRY: lambda a,b,dtype: f"threefry({a},{b})", Ops.MAX: lambda a,b,dtype: f"max({a},{b})"}
|
||||
|
||||
class NullProgram:
|
||||
def __init__(self, name:str, lib:bytes): pass
|
||||
|
||||
@@ -306,7 +306,10 @@ class RemoteHandler:
|
||||
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(): del session.programs[(c.name, c.datahash)]
|
||||
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}
|
||||
@@ -421,19 +424,24 @@ class RemoteConnection:
|
||||
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())
|
||||
resps = []
|
||||
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():
|
||||
response = conn.conn.getresponse()
|
||||
resp = response.read()
|
||||
conn.req = BatchRequest() # no matter what response, reset conn
|
||||
if response.status == http.HTTPStatus.INTERNAL_SERVER_ERROR:
|
||||
exc_wrapper = safe_eval(ast.parse(resp.decode(), mode="eval").body)
|
||||
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
|
||||
assert response.status == http.HTTPStatus.OK, f"POST /batch failed: {resp.decode()}"
|
||||
if conn == self: ret = resp
|
||||
if take_q: RemoteConnection.q_lock.release()
|
||||
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]]:
|
||||
|
||||
@@ -239,7 +239,7 @@ class AMDev(PCIDevImplBase):
|
||||
ip_offset = ctypes.addressof(self.bhdr) + ctypes.sizeof(dhdr) + ihdr.die_info[num_die].die_offset
|
||||
for _ in range(dhdr.num_ips):
|
||||
ip = am.struct_ip_v4.from_address(ip_offset)
|
||||
ba = (ctypes.c_uint32 * ip.num_base_address).from_address(ip_offset + 8)
|
||||
ba = ((ctypes.c_uint64 if ihdr.base_addr_64_bit else ctypes.c_uint32) * ip.num_base_address).from_address(ip_offset + 8)
|
||||
for hw_ip in range(1, am.MAX_HWIP):
|
||||
if hw_ip in hw_id_map and hw_id_map[hw_ip] == ip.hw_id:
|
||||
self.regs_offset[hw_ip][ip.instance_number] = tuple(list(ba))
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import ctypes, time, contextlib, importlib, functools
|
||||
import ctypes, time, contextlib, functools
|
||||
from typing import Literal
|
||||
from tinygrad.runtime.autogen.am import am
|
||||
from tinygrad.helpers import to_mv, data64, lo32, hi32, DEBUG, wait_cond
|
||||
from tinygrad.runtime.autogen.am import am
|
||||
from tinygrad.runtime.support.amd import import_soc
|
||||
|
||||
class AM_IP:
|
||||
def __init__(self, adev): self.adev = adev
|
||||
@@ -11,9 +12,7 @@ class AM_IP:
|
||||
def set_clockgating_state(self): pass # Set clockgating state for this IP
|
||||
|
||||
class AM_SOC(AM_IP):
|
||||
def init_sw(self):
|
||||
self.soc_ver = 24 if self.adev.ip_ver[am.GC_HWIP] >= (12,0,0) else 21
|
||||
self.module = importlib.import_module(f"tinygrad.runtime.autogen.am.soc{self.soc_ver}")
|
||||
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)
|
||||
|
||||
@@ -43,12 +43,22 @@ def fixup_ip_version(ip:str, version:tuple[int, ...]) -> list[tuple[int, ...]]:
|
||||
|
||||
return [version, version[:2], version[:2]+(0,), version[:1]+(0, 0)]
|
||||
|
||||
def header_download(file, name=None, subdir="defines") -> str:
|
||||
url = "https://gitlab.com/linux-kernel/linux-next/-/raw/cf6d949a409e09539477d32dbe7c954e4852e744/drivers/gpu/drm/amd"
|
||||
return fetch(f"{url}/{file}", name=name, subdir=subdir).read_text()
|
||||
|
||||
def import_header(path:str):
|
||||
t = re.sub(r'//.*|/\*.*?\*/','', header_download(path, subdir="defines"), flags=re.S)
|
||||
return {k:int(v,0) for k,v in re.findall(r'\b([A-Za-z_]\w*)\s*=\s*(0x[0-9A-Fa-f]+|\d+)', t)}
|
||||
|
||||
def import_module(name:str, version:tuple[int, ...], version_prefix:str=""):
|
||||
for ver in fixup_ip_version(name, version):
|
||||
try: return importlib.import_module(f"tinygrad.runtime.autogen.am.{name}_{version_prefix}{'_'.join(map(str, ver))}")
|
||||
except ImportError: pass
|
||||
raise ImportError(f"Failed to load autogen module for {name.upper()} {'.'.join(map(str, version))}")
|
||||
|
||||
def import_soc(ip): return type("SOC", (object,), import_header(f"include/{({9: 'vega10', 10: 'navi10', 11: 'soc21', 12: 'soc24'}[ip[0]])}_enum.h"))
|
||||
|
||||
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):
|
||||
@@ -56,8 +66,7 @@ def import_asic_regs(prefix:str, version:tuple[int, ...], cls=AMDReg) -> dict[st
|
||||
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"
|
||||
url = "https://gitlab.com/linux-kernel/linux-next/-/raw/cf6d949a409e09539477d32dbe7c954e4852e744/drivers/gpu/drm/amd/include/asic_reg"
|
||||
return fetch(f"{url}/{dir_prefix}/{fetch_name}", name=file_name, subdir="asic_regs").read_text()
|
||||
return header_download(f"include/asic_reg/{dir_prefix}/{fetch_name}", name=file_name, subdir="asic_regs")
|
||||
|
||||
for ver in fixup_ip_version(prefix, version):
|
||||
try: offs, sh_masks = _extract_regs(_download_file(ver, "offset")), _extract_regs(_download_file(ver, "sh_mask"))
|
||||
|
||||
@@ -438,12 +438,13 @@ class HCQCompiled(Compiled, Generic[SignalType]):
|
||||
return buf, realloced
|
||||
|
||||
def _select_iface(self, *ifaces:Type):
|
||||
errs:str = ""
|
||||
errs, err_short = "", ""
|
||||
if val:=getenv(f'{type(self).__name__[:-6].upper()}_IFACE', ""): ifaces = tuple(x for x in ifaces if x.__name__.startswith(val.upper()))
|
||||
for iface_t in ifaces:
|
||||
try: return iface_t(self, self.device_id)
|
||||
except Exception: errs += f"\n{iface_t.__name__}: {traceback.format_exc()}"
|
||||
raise RuntimeError(f"Cannot find a usable interface for {type(self).__name__[:-6]}:{self.device_id}:\n{errs}")
|
||||
except Exception as e: errs, err_short = errs + f"\n{iface_t.__name__}: {traceback.format_exc()}", err_short + f"\n{iface_t.__name__}: {e}"
|
||||
raise RuntimeError(f"{errs}\nNo interface for {type(self).__name__[:-6]}:{self.device_id} is available:{err_short}\n" \
|
||||
f"\nForce an interface with {type(self).__name__[:-6].upper()}_IFACE={('|'.join(x.__name__[:-5] for x in ifaces))}.")
|
||||
|
||||
def _is_cpu(self) -> bool: return hasattr(self, 'device') and self.device.split(":")[0] in ("CPU", "LLVM")
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import os, mmap, array, functools, ctypes, select, contextlib, dataclasses, sys
|
||||
import os, mmap, array, functools, ctypes, select, contextlib, dataclasses, sys, errno
|
||||
from typing import cast, ClassVar
|
||||
from tinygrad.helpers import round_up, to_mv, getenv, OSX, temp
|
||||
from tinygrad.runtime.autogen import libc, vfio
|
||||
@@ -84,7 +84,11 @@ class PCIDevice:
|
||||
for i in resize_bars or []:
|
||||
if FileIOInterface.exists(rpath:=f"/sys/bus/pci/devices/{self.pcibus}/resource{i}_resize"):
|
||||
try: FileIOInterface(rpath, os.O_RDWR).write(str(int(FileIOInterface(rpath, os.O_RDONLY).read(), 16).bit_length() - 1))
|
||||
except OSError as e: raise RuntimeError(f"Cannot resize BAR {i}: {e}. Ensure the resizable BAR option is enabled on your system.") from e
|
||||
except OSError as e:
|
||||
if e.errno == errno.EPERM:
|
||||
raise RuntimeError(f"Cannot resize BAR {i}: {e}. Permission error: run `extra/amdpci/setup_python_cap.sh`"
|
||||
" to allow python accessing device or run with sudo") from e
|
||||
raise RuntimeError(f"Cannot resize BAR {i}: {e}. Ensure the resizable BAR option is enabled on your system.") from e
|
||||
|
||||
if getenv("VFIO", 0) and (vfio_fd:=System.vfio()) is not None:
|
||||
FileIOInterface(f"/sys/bus/pci/devices/{self.pcibus}/driver_override", os.O_WRONLY).write("vfio-pci")
|
||||
|
||||
@@ -0,0 +1,449 @@
|
||||
from typing import Any
|
||||
from dataclasses import dataclass, field
|
||||
from tinygrad.dtype import dtypes, PtrDType
|
||||
from tinygrad.uop.ops import PatternMatcher, UPat, Ops, UOp, resolve, GroupOp, RewriteNotReady, _substitute
|
||||
from tinygrad.helpers import argsort, prod, all_same, pluralize, getenv, colored, RANGEIFY
|
||||
from tinygrad.schedule.multi import multi_pm
|
||||
|
||||
from tinygrad.schedule.kernelize import Kernel
|
||||
from tinygrad.uop.ops import track_rewrites, graph_rewrite_map, graph_rewrite, KernelInfo, identity_element, sint
|
||||
|
||||
# 0. do some cleanup rewrites, mostly copied from the old stuff
|
||||
|
||||
double_reshape = PatternMatcher([
|
||||
# RESHAPE on RESHAPE is the second reshape
|
||||
(UPat(Ops.RESHAPE, src=(UPat(Ops.RESHAPE),), name="x"), lambda x: x.replace(src=(x.src[0].src[0],))),
|
||||
])
|
||||
|
||||
earliest_rewrites = double_reshape+PatternMatcher([
|
||||
# UOp with size 0 is zero
|
||||
(UPat(GroupOp.All-{Ops.SINK}, name="root"), lambda root: root.const_like(0) if root.base.st is not None and root.size == 0 else None),
|
||||
# DETACH and CONTIGUOUS_BACKWARD are NOOPs here, so is FUSE
|
||||
(UPat((Ops.DETACH, Ops.CONTIGUOUS_BACKWARD, Ops.FUSE), name="x"), lambda x: x.src[0]),
|
||||
# reduce of size 0 is the identity element
|
||||
(UPat(Ops.REDUCE_AXIS, name="reduce", src=(UPat.var("x"),)),
|
||||
lambda reduce,x: reduce.const_like(identity_element(reduce.arg[0], reduce.dtype)) if x.size == 0 and reduce.size != 0 else None),
|
||||
# non shape changing RESHAPE is NOOP
|
||||
(UPat(Ops.RESHAPE, name="x"), lambda x: x.src[0] if x.src[0].shape == x.arg else None),
|
||||
# RESHAPE after COPY
|
||||
(UPat(Ops.COPY, src=(UPat(Ops.RESHAPE, name="r"),UPat(name="d")), name="c"), lambda c,r,d: c.replace(src=(r.src[0],d)).reshape(r.arg)),
|
||||
# TODO: this should be BUFFER_VIEW
|
||||
(UPat(Ops.COPY, src=(UPat(Ops.SHRINK, name="r"),UPat(name="d")), name="c"), lambda c,r,d: c.replace(src=(r.src[0],d)).shrink(r.arg)),
|
||||
# const hacks
|
||||
(UPat(Ops.CONST, name="x"), lambda x:
|
||||
x.replace(src=(x.src[0].src[0],)).reshape((1,)*len(x.shape)).expand(x.shape) if \
|
||||
len(x.src) and x.src[0].op is Ops.VIEW and not any(s == 0 for s in x.shape) else None),
|
||||
# assign only to buffer
|
||||
(UPat(Ops.ASSIGN, src=(UPat(GroupOp.All-{Ops.BUFFER}, name="target"), UPat(name="x"))),
|
||||
lambda x,target: x if target.base.op is not Ops.BUFFER else None),
|
||||
# contiguous/buffer/copy/assign is already contiguous
|
||||
(UPat(Ops.CONTIGUOUS, name="root", src=(UPat((Ops.CONTIGUOUS, Ops.BUFFER, Ops.COPY, Ops.ASSIGN)),)), lambda root: root.src[0]),
|
||||
])
|
||||
|
||||
# 1. add contiguous where we have to
|
||||
|
||||
ALWAYS_CONTIGUOUS: set[Ops] = {Ops.CONTIGUOUS, Ops.ASSIGN, Ops.COPY, Ops.BUFFER, Ops.BUFFER_VIEW,
|
||||
Ops.CONST, Ops.BIND, Ops.DEVICE, Ops.MSELECT, Ops.MSTACK, Ops.DEFINE_GLOBAL,
|
||||
Ops.DEFINE_LOCAL, Ops.DEFINE_REG, Ops.LOAD}
|
||||
|
||||
def realize(ctx:dict[UOp, None], tr:UOp) -> None: ctx[tr] = None
|
||||
|
||||
def realize_parents(ctx:dict[UOp, None], rb:UOp) -> None:
|
||||
for s in rb.src:
|
||||
if s.op not in ALWAYS_CONTIGUOUS: ctx[s] = None
|
||||
|
||||
def realize_assign(ctx:dict[UOp, None], a:UOp) -> None:
|
||||
if a.src[1].op not in ALWAYS_CONTIGUOUS: ctx[a.src[1]] = None
|
||||
|
||||
do_realize = PatternMatcher([
|
||||
# always realize SINK parents
|
||||
(UPat(Ops.SINK, name="s"), lambda ctx,s: ctx.update((x.base, None) for x in s.src if x.base.op not in ALWAYS_CONTIGUOUS)),
|
||||
# always realize ASSIGN/CONTIGUOUS/COPY/BUFFER_VIEW
|
||||
(UPat({Ops.ASSIGN, Ops.COPY, Ops.BUFFER_VIEW}, name="tr"), realize),
|
||||
# realize parents of COPY, MSELECT, MSTACK
|
||||
(UPat((Ops.COPY, Ops.MSELECT, Ops.MSTACK), name="rb"), realize_parents),
|
||||
# realize input to assign (might be optimized out)
|
||||
(UPat(Ops.ASSIGN, name="a"), realize_assign),
|
||||
])
|
||||
|
||||
add_contiguous = PatternMatcher([
|
||||
(UPat(GroupOp.All-{Ops.CONTIGUOUS}, name="x"), lambda ctx,x: x.replace(tag=1).contiguous() if x in ctx and x.tag is None else None),
|
||||
])
|
||||
remove_tags = PatternMatcher([(UPat(GroupOp.All, name="x"), lambda x: x.replace(tag=None) if x.tag is not None else None)])
|
||||
early_cleanups = PatternMatcher([(UPat().contiguous(name="c").contiguous(), lambda c: c),])
|
||||
|
||||
# 2. mark all children
|
||||
|
||||
@dataclass
|
||||
class ChildrenContext: children: dict[UOp, list[UOp]]|None = None
|
||||
def extract_children(ctx:ChildrenContext, x:UOp):
|
||||
if ctx.children is not None: return
|
||||
children_map = x.get_children_map()
|
||||
ctx.children = {}
|
||||
for k,v in children_map.items():
|
||||
non_sink_children = [u for u in v if u.op is not Ops.SINK]
|
||||
if len(non_sink_children) <= 1: continue
|
||||
# NOTE: this gate shouldn't be here
|
||||
if any(x.op is Ops.REDUCE_AXIS for x in k.toposort()) and any(x.op in {Ops.BUFFER, Ops.CONTIGUOUS} for x in k.toposort()):
|
||||
ctx.children[k] = non_sink_children
|
||||
|
||||
def mark_children(ctx:ChildrenContext, x:UOp):
|
||||
assert ctx.children is not None
|
||||
new_srcs = [(UOp(Ops.CHILD, s.dtype, src=(UOp(Ops.CHILDREN, s.dtype, (s,), arg=len(ctx.children[s])),),
|
||||
arg=(ctx.children[s].index(x), len(ctx.children[s]))) if s in ctx.children else s) for s in x.src]
|
||||
return x.replace(src=tuple(new_srcs))
|
||||
|
||||
pm_children = PatternMatcher([
|
||||
(UPat(Ops.SINK, name="x"), extract_children),
|
||||
(UPat(GroupOp.All-{Ops.CHILD, Ops.CHILDREN}, name="x"), mark_children),
|
||||
])
|
||||
|
||||
# 3. rangeify
|
||||
|
||||
@dataclass
|
||||
class RangeifyContext:
|
||||
# block on parent until all children have been seen
|
||||
seen_children: dict[UOp, dict[int, UOp]] = field(default_factory=dict)
|
||||
seen_child: dict[UOp, Any] = field(default_factory=dict)
|
||||
progress: int = 0
|
||||
|
||||
# create ranges
|
||||
range_idx: int = 0
|
||||
def new_range(self, s:sint):
|
||||
ret = UOp.range(dtypes.int, s, self.range_idx)
|
||||
self.range_idx += 1
|
||||
return ret
|
||||
|
||||
def map_reshape(idx:UOp, r:UOp):
|
||||
acc = 1
|
||||
to_sum = []
|
||||
for s,src in list(zip(idx.shape, idx.src[1:]))[::-1]:
|
||||
to_sum.append(acc*src)
|
||||
acc *= s
|
||||
mish = sum(to_sum, start=UOp.const(dtypes.int, 0))
|
||||
ret:list[UOp] = []
|
||||
for s in r.src[0].shape[::-1]:
|
||||
ret.append(mish % s) # NOTE: simplify will turn this to CONST
|
||||
mish //= s
|
||||
tret = ret[0].sink(*ret[1:]).simplify().src[::-1] if len(ret) else ()
|
||||
return r.src[0].index(*tret, dtype=idx.dtype, arg=idx.arg)
|
||||
|
||||
def map_pad(idx:UOp, r:UOp):
|
||||
ret = list(idx.src[1:])
|
||||
bigwhere = UOp.const(dtypes.bool, True)
|
||||
for i,(sh,(s,e)) in enumerate(zip(r.shape, r.arg)):
|
||||
if s == 0 and e == 0: continue
|
||||
where = UOp.const(dtypes.bool, True)
|
||||
if resolve(e > 0): where = where & (ret[i] < (sh-e))
|
||||
if resolve(s > 0): where = where & (ret[i] >= s)
|
||||
bigwhere = bigwhere & where
|
||||
# this is safe but dumb
|
||||
# TODO (S-Lykles): switch to mixed index/valid
|
||||
ret[i] = (ret[i] - s).maximum(0).minimum(r.src[0].shape[i]-1)
|
||||
# PAD is with 0
|
||||
return bigwhere.simplify().where(r.src[0].index(*ret, dtype=idx.dtype, arg=idx.arg), UOp.const(r.dtype, 0))
|
||||
|
||||
def map_expand(r:UOp, idx:UOp):
|
||||
new_rngs = []
|
||||
ending_ranges = []
|
||||
non_ending_ranges = []
|
||||
for a,x,y in zip(idx.src[1:], r.src[0].shape, r.shape):
|
||||
axis_to_range = [u for u in a.toposort() if u.op is Ops.RANGE]
|
||||
if resolve(x!=y, False):
|
||||
ending_ranges.extend(axis_to_range)
|
||||
new_rngs.append(a.const_like(0))
|
||||
else:
|
||||
non_ending_ranges.extend(axis_to_range)
|
||||
new_rngs.append(a)
|
||||
ending_ranges = [x.arg for x in ending_ranges if x not in non_ending_ranges]
|
||||
if idx.arg is not None: ending_ranges.append(idx.arg)
|
||||
return r.src[0].index(*new_rngs, arg=min(ending_ranges) if ending_ranges else None)
|
||||
|
||||
pm_mops = PatternMatcher([
|
||||
# this is like the definitions of these
|
||||
(UPat(Ops.SHRINK, name="r").f(Ops.INDEX, allow_any_len=True, name="idx"),
|
||||
lambda r,idx: r.src[0].index(*[a+ss if resolve(ss != 0) else a for a,(ss,_) in zip(idx.src[1:], r.arg)], dtype=idx.dtype, arg=idx.arg)),
|
||||
(UPat(Ops.PERMUTE, name="r").f(Ops.INDEX, allow_any_len=True, name="idx"),
|
||||
lambda r,idx: r.src[0].index(*[idx.src[1+p] for p in argsort(idx.src[0].arg)], dtype=idx.dtype, arg=idx.arg)),
|
||||
(UPat(Ops.FLIP, name="r").f(Ops.INDEX, allow_any_len=True, name="idx"),
|
||||
lambda r,idx: r.src[0].index(*[((s-1)-a) if f else a for a,s,f in zip(idx.src[1:], r.shape, r.arg)], dtype=idx.dtype, arg=idx.arg)),
|
||||
# expand needs to end ranges
|
||||
(UPat(Ops.EXPAND, name="r").f(Ops.INDEX, allow_any_len=True, name="idx"), map_expand),
|
||||
# reshape does a lot of symbolic stuff
|
||||
(UPat(Ops.RESHAPE, name="r").f(Ops.INDEX, allow_any_len=True, name="idx"), map_reshape),
|
||||
# pad adds min and max
|
||||
(UPat(Ops.PAD, name="r").f(Ops.INDEX, allow_any_len=True, name="idx"), map_pad),
|
||||
])
|
||||
|
||||
def map_partial_contiguous(ctx:RangeifyContext, x:UOp, idx:UOp):
|
||||
if x.arg is None: return None # map_contiguous can handle this
|
||||
# NOTE: all partial contiguous can safely be replaced by full contiguous. we should be able to match old functionality like this
|
||||
if not (RANGEIFY > 1): return idx.replace(src=(x.replace(arg=None),)+idx.src[1:])
|
||||
ranges = []
|
||||
new_ranges = []
|
||||
passthrough_idx = []
|
||||
for i,s in enumerate(x.shape):
|
||||
if i not in x.arg:
|
||||
ranges.append(idx.src[1+i])
|
||||
continue
|
||||
passthrough_idx.append(idx.src[1+i])
|
||||
ranges.append(ctx.new_range(s) if resolve(s!=1) else UOp.const(dtypes.int, 0))
|
||||
new_ranges.append(ranges[-1])
|
||||
ret = x.src[0].index(*ranges).bufferize(*[x for x in new_ranges if x.op is not Ops.CONST], arg=x.device)
|
||||
return ret.index(*passthrough_idx)
|
||||
|
||||
def map_contiguous(ctx:RangeifyContext, x:UOp):
|
||||
if x.arg is not None: return None
|
||||
ranges = []
|
||||
for s in x.shape:
|
||||
ranges.append(ctx.new_range(s) if resolve(s!=1) else UOp.const(dtypes.int, 0))
|
||||
return x.src[0].index(*ranges).bufferize(*[x for x in ranges if x.op is not Ops.CONST], arg=x.device).forced_reshape(x.shape)
|
||||
|
||||
def map_reduce(ctx:RangeifyContext, idx:UOp, red:UOp):
|
||||
rngs = list(idx.src[1:])
|
||||
new_ranges = []
|
||||
for i,s in enumerate(red.src[0].shape):
|
||||
if i in red.arg[1]:
|
||||
rngs[i] = ctx.new_range(s)
|
||||
new_ranges.append(rngs[i])
|
||||
return UOp(Ops.REDUCE, red.dtype, src=(red.src[0].index(*rngs),)+tuple(new_ranges), arg=red.arg[0])
|
||||
|
||||
def index_child(ctx:RangeifyContext, c:UOp, x:UOp, idx:UOp):
|
||||
if c not in ctx.seen_children: ctx.seen_children[c] = {}
|
||||
# wait here until we have seen all the children
|
||||
if len(ctx.seen_children[c]) != x.arg[1]:
|
||||
ctx.progress += 1
|
||||
if ctx.progress > 10000: raise RuntimeError("children not making progress")
|
||||
# NOTE: we mark this here
|
||||
ctx.seen_children[c][x.arg[0]] = idx
|
||||
raise RewriteNotReady
|
||||
ctx.progress = 0
|
||||
|
||||
if c not in ctx.seen_child:
|
||||
all_rngs = zip(*[ch.src[1:] for ch in ctx.seen_children[c].values()])
|
||||
out_rngs = []
|
||||
end_ranges = []
|
||||
idx_ranges = []
|
||||
for i,r in enumerate(all_rngs):
|
||||
if all_same(r):
|
||||
out_rngs.append(r[0])
|
||||
else:
|
||||
out_rngs.append(ctx.new_range(c.shape[i]))
|
||||
end_ranges.append(out_rngs[-1])
|
||||
idx_ranges.append(i)
|
||||
ctx.seen_child[c] = (idx_ranges, end_ranges)
|
||||
else:
|
||||
out_rngs = list(idx.src[1:])
|
||||
idx_ranges, end_ranges = ctx.seen_child[c]
|
||||
for i,nr in zip(idx_ranges, end_ranges): out_rngs[i] = nr
|
||||
# index based on the shared ranges
|
||||
ret = c.index(*out_rngs)
|
||||
# if all ranges aren't the same between children, we have to bufferize
|
||||
if len(idx_ranges) > 0: ret = ret.bufferize(*end_ranges, arg=x.device).index(*[idx.src[1+i] for i in idx_ranges])
|
||||
return ret
|
||||
|
||||
def children_gate(ctx:RangeifyContext, idx:UOp, c:UOp):
|
||||
if len(ctx.seen_children[c]) != c.arg: raise RuntimeError("all children should have been seen by now")
|
||||
return idx.replace(src=(idx.src[0].src[0],)+idx.src[1:])
|
||||
|
||||
def might_end_axis(idx:UOp):
|
||||
if idx.arg is None: return None
|
||||
# TODO: write a proper cost function here
|
||||
if all(x.op not in {Ops.BUFFER, Ops.CONTIGUOUS, Ops.BUFFERIZE} for x in idx.toposort()): return None
|
||||
if all(x.op not in {Ops.REDUCE_AXIS} for x in idx.toposort()): return None
|
||||
to_end_axis = []
|
||||
for i,a in enumerate(idx.src[1:]):
|
||||
if any(x.arg > idx.arg for x in a.toposort() if x.op is Ops.RANGE):
|
||||
to_end_axis.append(i)
|
||||
if to_end_axis: return idx.replace(src=(idx.src[0].contiguous(arg=tuple(to_end_axis)),)+idx.src[1:], arg=None)
|
||||
return idx.replace(arg=None)
|
||||
|
||||
pm_rangeify = pm_mops+PatternMatcher([
|
||||
# sink contigs to kick it off
|
||||
(UPat(Ops.CONTIGUOUS, src=(UPat(),), name="x"), map_contiguous),
|
||||
# if there's an INDEX it can support partial contig
|
||||
(UPat(Ops.INDEX, src=(UPat(Ops.CONTIGUOUS, src=(UPat(),), name="x"),), allow_any_len=True, name="idx"), map_partial_contiguous),
|
||||
|
||||
# if there are new ended children, tag the SINK
|
||||
(UPat(Ops.INDEX, src=(UPat(Ops.CHILD, src=(UPat(name="c"), ), name="x"),), allow_any_len=True, name="idx"), index_child),
|
||||
(UPat(Ops.INDEX, src=(UPat(Ops.CHILDREN, name="c"),), allow_any_len=True, name="idx"), children_gate),
|
||||
|
||||
# if we come across this, remove it. it was a CHILD unused in an INDEX
|
||||
(UPat(Ops.CHILD, src=(UPat(Ops.CHILDREN, src=(UPat.var("x"),)),)), lambda x: x),
|
||||
|
||||
# CONST (or DEFINE_VAR) can't have axes. remove srcs when we INDEX it
|
||||
(UPat(Ops.INDEX, src=(UPat((Ops.CONST, Ops.DEFINE_VAR), name="c"),)), lambda c: c.replace(src=())),
|
||||
|
||||
# handle arg on any op with weight. old endrange stuff
|
||||
(UPat(Ops.INDEX, src=(UPat(GroupOp.Elementwise.union({Ops.REDUCE_AXIS})),), allow_any_len=True, name="idx"), might_end_axis),
|
||||
|
||||
# move MAP through elementwise ALU / reduce. these are the items with cost
|
||||
(UPat(Ops.INDEX, src=(UPat(GroupOp.Elementwise.union({Ops.STORE, Ops.ASSIGN, Ops.COPY, Ops.DEVICE, Ops.BIND})),), allow_any_len=True, name="x"),
|
||||
lambda x: x.src[0].replace(src=tuple([s.index(*x.src[1:]) for s in x.src[0].src]))),
|
||||
(UPat(Ops.INDEX, src=(UPat(Ops.REDUCE_AXIS, name="red"),), allow_any_len=True, name="idx"), map_reduce),
|
||||
])
|
||||
|
||||
# 3.5 cleanups
|
||||
|
||||
# you don't know in the first pass if axes are going to die, this happens if there's an EXPAND to the left
|
||||
# TODO: figure out how to reenable this
|
||||
def cleanup_dead_axes(b:UOp):
|
||||
parents = b.src[0].toposort()
|
||||
new_rng = []
|
||||
hit = False
|
||||
reshape: list[sint] = []
|
||||
for s,rng in zip(b.shape, b.src[1:]):
|
||||
if rng not in parents and rng.op is Ops.RANGE:
|
||||
reshape.append(1)
|
||||
hit = True
|
||||
else:
|
||||
reshape.append(s)
|
||||
new_rng.append(rng)
|
||||
if hit:
|
||||
return b.replace(src=b.src[0:1]+tuple(new_rng)).reshape(tuple(reshape)).expand(b.shape)
|
||||
|
||||
# if a buffer is being stored just for permutes or something, remove it
|
||||
# we want to reexpress the indexes of idx2 in terms of the implied b1
|
||||
def remove_bufferize(b2:UOp, idx2:UOp):
|
||||
# HACK
|
||||
if len(b2.src) != len(idx2.src): return None
|
||||
assert len(b2.src) == len(idx2.src)
|
||||
assert all(x.op is Ops.RANGE for x in b2.src[1:])
|
||||
return b2.src[0].substitute(dict(zip(b2.src[1:], idx2.src[1:])))
|
||||
|
||||
pm_cleanups = double_reshape+pm_mops+PatternMatcher([
|
||||
#(UPat(Ops.BUFFERIZE, name="b"), cleanup_dead_axes),
|
||||
# remove noop buffers. if we look at the next index we can remove even more of these
|
||||
# NOTE: this is mostly the same case as below, but if there's no INDEX this gets more
|
||||
#(UPat(Ops.INDEX, name="idx").f(Ops.BUFFERIZE, allow_any_len=True, name="b2"),
|
||||
# lambda idx,b2: idx.src[0] if idx.src[1:] == b2.src[1:] else None),
|
||||
# remove reindexing
|
||||
(UPat(Ops.INDEX).f(Ops.BUFFERIZE, allow_any_len=True, name="b2").f(Ops.INDEX, allow_any_len=True, name="idx2"), remove_bufferize),
|
||||
# no buffers for const
|
||||
#(UPat(Ops.CONST, name='c').f(Ops.BUFFERIZE, allow_any_len=True, name="b"), lambda c,b: c.reshape((1,)*len(b.shape)).expand(b.shape)),
|
||||
])
|
||||
|
||||
# 4. put in buffers for bufferize
|
||||
# TODO: should BUFFERIZE look a lot more like STORE
|
||||
# BUFFERIZE has device in arg
|
||||
# BUFFERIZE doesn't have indexing, that's implied by the ranges it closes
|
||||
# BUFFERIZE returns the BUFFER ready for INDEXing (doing this will make splitting a lot easier)
|
||||
# NOTE: this has been fixed up a bit
|
||||
|
||||
def bufferize_to_store(x:UOp):
|
||||
rngs = x.src[1:]
|
||||
shape = tuple([int(r.vmax+1) for r in rngs])
|
||||
sdtype = x.dtype.ptr(size=prod(shape))
|
||||
assert prod(shape) > 0, f"no zero sized buffers {shape}"
|
||||
if x.src[0].op is Ops.ASSIGN:
|
||||
assign_target, assign_src = x.src[0].src
|
||||
assert assign_target.op is Ops.INDEX
|
||||
return assign_target.replace(dtype=sdtype).store(assign_src, *rngs, dtype=sdtype)
|
||||
buf = UOp.new_buffer(x.arg, prod(shape), x.dtype)
|
||||
return buf.reshape(shape).index(*rngs, dtype=sdtype).store(x.src[0], *rngs, dtype=sdtype).forced_reshape(shape, dtype=x.dtype)
|
||||
|
||||
pm_add_buffers = pm_mops+PatternMatcher([
|
||||
(UPat(Ops.BUFFERIZE, name="x"), bufferize_to_store),
|
||||
|
||||
# move RESHAPEs through MSELECT/MSTACK
|
||||
(UPat((Ops.MSELECT, Ops.MSTACK), src=UPat(Ops.RESHAPE), name="m"),
|
||||
lambda m: m.replace(src=tuple([x.src[0] for x in m.src])).reshape(m.src[0].arg)),
|
||||
])
|
||||
|
||||
# 5. split into kernels
|
||||
|
||||
@dataclass
|
||||
class LocalAddBufferContext:
|
||||
dg:int = 0
|
||||
map:dict = field(default_factory=dict)
|
||||
vars:dict = field(default_factory=dict)
|
||||
|
||||
def debuf(ctx:LocalAddBufferContext, buf:UOp):
|
||||
ret = UOp(Ops.DEFINE_GLOBAL, buf.dtype.ptr(buf.arg), arg=ctx.dg)
|
||||
if buf not in ctx.map: ctx.map[buf] = buf
|
||||
ctx.dg += 1
|
||||
return ret
|
||||
|
||||
def unbind_kernel(ctx:LocalAddBufferContext, b:UOp):
|
||||
ctx.vars[b] = None
|
||||
return b.src[0]
|
||||
|
||||
def handle_assign(ctx:LocalAddBufferContext, assign:UOp):
|
||||
buf = assign.as_buf()
|
||||
# HACK to put the buffer in the MAP instead of MSTACK/MSELECT
|
||||
if buf.op in {Ops.MSTACK, Ops.MSELECT}: buf = buf.src[0]
|
||||
assert buf not in ctx.map
|
||||
ctx.map[buf] = assign
|
||||
return buf
|
||||
|
||||
to_define_global = PatternMatcher([
|
||||
(UPat(Ops.BUFFER, name="buf"), debuf),
|
||||
(UPat(Ops.BIND, name="b"), unbind_kernel),
|
||||
(UPat((Ops.ASSIGN, Ops.MSTACK, Ops.MSELECT), name="assign"), handle_assign),
|
||||
|
||||
# add loads to non ptr indexes
|
||||
# TODO: this can be moved into codegen?
|
||||
(UPat((Ops.DEFINE_GLOBAL, Ops.STORE), name="dg").f(Ops.INDEX, name="idx", allow_any_len=True),
|
||||
lambda dg,idx: idx.replace(dtype=dg.dtype, arg=None).load() if not isinstance(idx.dtype, PtrDType) else None),
|
||||
|
||||
# TODO: this can be moved into codegen
|
||||
(UPat(Ops.STORE, name="store").f(Ops.INDEX, allow_any_len=True, name="idx").f(Ops.LOAD),
|
||||
lambda store,idx: idx.replace(src=(store.as_buf(),)+idx.src[1:]).load(store)),
|
||||
|
||||
# HACK in case any CONSTs were replaced
|
||||
# this is only needed if you are using symbolic
|
||||
#(UPat(Ops.CONST, name="c"), lambda c: c.replace(src=()) if len(c.src) else None),
|
||||
])
|
||||
|
||||
def split_store(x:UOp):
|
||||
if len(x.ranges): return None
|
||||
ctx = LocalAddBufferContext()
|
||||
ret = graph_rewrite(x, to_define_global, ctx=ctx, name="kernel split", bottom_up=True)
|
||||
|
||||
store_rngs = ret.src[2:]
|
||||
rng = sorted([u for u in ret.toposort() if u.op is Ops.RANGE], key=lambda x: x.arg)
|
||||
name = "k"+colored('_', 'BLACK').join(['']+[colored(s.src[0].render(), "WHITE" if s in store_rngs else "red") for s in rng])
|
||||
|
||||
# NOTE: the hack for COPY is here
|
||||
ret = ret.sink(arg=KernelInfo(name=name)) if ret.src[1].op is not Ops.COPY else ret.src[1]
|
||||
kernel = UOp(Ops.KERNEL, src=tuple(ctx.map.values())+tuple(ctx.vars.keys()), arg=Kernel(ret,()))
|
||||
return x.as_buf().assign(kernel)
|
||||
|
||||
split_kernels = PatternMatcher([
|
||||
(UPat(Ops.STORE, name="x"), split_store),
|
||||
])
|
||||
|
||||
@track_rewrites(name=lambda sink,ret: f"Schedule {pluralize('Kernel',len([u for u in ret[sink].toposort() if u.op is Ops.KERNEL]))}", replay=True)
|
||||
def get_rangeify_map(sink:UOp) -> dict[UOp, UOp]:
|
||||
tensor_map = graph_rewrite_map(sink, multi_pm+earliest_rewrites, name="earliest")
|
||||
realize_map: dict[UOp, UOp] = {}
|
||||
graph_rewrite(tensor_map[sink], do_realize, ctx=realize_map, name="Input Graph")
|
||||
tensor_map = graph_rewrite_map(tensor_map[sink], add_contiguous, ctx=realize_map, bottom_up=True, input_map=tensor_map, name="add contiguous")
|
||||
tensor_map = graph_rewrite_map(tensor_map[sink], early_cleanups+remove_tags, input_map=tensor_map, name="cleanup")
|
||||
tensor_map = graph_rewrite_map(tensor_map[sink], pm_children, ctx=ChildrenContext(), bottom_up=True, input_map=tensor_map, name="children")
|
||||
tensor_map = graph_rewrite_map(tensor_map[sink], pm_rangeify, ctx=RangeifyContext(), bottom_up=True, input_map=tensor_map, name="rangeify")
|
||||
# NOTE: running symbolic can break the graph, leaving RANGE/INDEX/BUFFERIZE in the final graph
|
||||
#tensor_map = graph_rewrite_map(tensor_map[sink], symbolic_simple, input_map=tensor_map, name="symbolic")
|
||||
tensor_map = graph_rewrite_map(tensor_map[sink], pm_cleanups, bottom_up=True, input_map=tensor_map, name="cleanups")
|
||||
if getenv("VIZ"): graph_rewrite(tensor_map[sink], PatternMatcher([]), name="View Rangeify Graph")
|
||||
|
||||
tensor_map = graph_rewrite_map(tensor_map[sink], pm_add_buffers, bottom_up=True, input_map=tensor_map, name="add buffers")
|
||||
tensor_map = graph_rewrite_map(tensor_map[sink], split_kernels, input_map=tensor_map, name="split kernels")
|
||||
|
||||
# if a kernel depends on a buffer, and that buffer is later assigned to, make the assign depend on the kernel's assign
|
||||
kernel_assign: dict[UOp, UOp] = {}
|
||||
assign_rep: dict[UOp, UOp] = {}
|
||||
for u in tensor_map[sink].toposort():
|
||||
if u.op is not Ops.ASSIGN: continue
|
||||
kernel_assign[u.buf_uop] = u
|
||||
for s in u.src[1].src:
|
||||
# TODO: this is probably broken for MSELECT/MSTACK
|
||||
if s.op is not Ops.BUFFER or s is u.buf_uop or (a:=kernel_assign.get(s)) is None: continue
|
||||
if any(x.op is Ops.ASSIGN and x.buf_uop is s for x in u.toposort()):
|
||||
raise RuntimeError(f"cycle detected in graph, kernel for {u.buf_uop} must either depend on ASSIGN or BUFFER")
|
||||
assign_rep[a] = kernel_assign[s] = a.replace(src=a.src+(u,))
|
||||
if assign_rep:
|
||||
tensor_map = graph_rewrite_map(tensor_map[sink], _substitute, ctx=assign_rep, bottom_up=True, input_map=tensor_map, name="fix_assign")
|
||||
|
||||
if getenv("VIZ"): graph_rewrite(tensor_map[sink], PatternMatcher([]), name="View Kernel Graph")
|
||||
return tensor_map
|
||||
+20
-13
@@ -6,7 +6,7 @@ from typing import Callable, ClassVar, Sequence, cast, get_args, Literal, Suppor
|
||||
from tinygrad.dtype import DType, DTypeLike, dtypes, ImageDType, ConstType, least_upper_float, least_upper_dtype, sum_acc_dtype, to_dtype, truncate
|
||||
from tinygrad.dtype import _from_np_dtype, _to_np_dtype
|
||||
from tinygrad.helpers import argfix, make_tuple, flatten, prod, all_int, round_up, merge_dicts, argsort, getenv, all_same, fully_flatten, dedup
|
||||
from tinygrad.helpers import IMAGE, WINO, Metadata, TRACEMETA, ceildiv, fetch, polyN, unwrap, DEBUG, is_numpy_ndarray
|
||||
from tinygrad.helpers import IMAGE, WINO, Metadata, TRACEMETA, ceildiv, fetch, polyN, unwrap, DEBUG, is_numpy_ndarray, RANGEIFY
|
||||
from tinygrad.gradient import compute_gradient
|
||||
from tinygrad.uop.ops import smax, smin, resolve, UOp, Ops, sint, Variable, MathTrait, identity_element, all_metadata
|
||||
from tinygrad.uop.spec import tensor_uop_spec, type_verify
|
||||
@@ -14,6 +14,7 @@ from tinygrad.device import Device, Buffer
|
||||
from tinygrad.engine.realize import run_schedule
|
||||
from tinygrad.engine.memory import memory_planner
|
||||
from tinygrad.engine.schedule import ScheduleItem, create_schedule_with_vars
|
||||
from tinygrad.schedule.rangeify import get_rangeify_map
|
||||
from tinygrad.schedule.kernelize import get_kernelize_map
|
||||
|
||||
# *** all in scope Tensors are here. this gets relevant UOps ***
|
||||
@@ -39,6 +40,9 @@ def _apply_map_to_tensors(applied_map:dict[UOp, UOp], name:str|None=None) -> Non
|
||||
sink = UOp.sink(*[t.uop for t in fixed_tensors])
|
||||
new_sink = sink.substitute(applied_map, name=name)
|
||||
|
||||
# NOTE: you can check the Tensor graph early here
|
||||
#if __debug__: type_verify(list(new_sink.toposort()), tensor_uop_spec)
|
||||
|
||||
# set the relevant uop to the realized UOps
|
||||
for t,s,ns in zip(fixed_tensors, sink.src, new_sink.src):
|
||||
if s is ns: continue
|
||||
@@ -231,7 +235,7 @@ class Tensor(MathTrait):
|
||||
# verify Tensors match the spec
|
||||
if __debug__: type_verify(list(big_sink.toposort()), tensor_uop_spec)
|
||||
|
||||
becomes_map = get_kernelize_map(big_sink)
|
||||
becomes_map = get_rangeify_map(big_sink) if RANGEIFY else get_kernelize_map(big_sink)
|
||||
_apply_map_to_tensors(becomes_map, name="Apply Kernelize Map")
|
||||
return self
|
||||
|
||||
@@ -345,7 +349,8 @@ class Tensor(MathTrait):
|
||||
print(t.tolist())
|
||||
```
|
||||
"""
|
||||
if self.dtype in (dtypes.bfloat16, *dtypes.fp8s): return self.cast(dtypes.float32).tolist()
|
||||
# TODO: remove half once minimum python supports it
|
||||
if self.dtype in (dtypes.half, dtypes.bfloat16, *dtypes.fp8s): return self.cast(dtypes.float32).tolist()
|
||||
return self.data().tolist()
|
||||
|
||||
def numpy(self) -> 'np.ndarray': # type: ignore [name-defined] # noqa: F821
|
||||
@@ -1128,11 +1133,12 @@ class Tensor(MathTrait):
|
||||
if (isinstance(indices, list) and all_int(indices)) or not isinstance(indices, (tuple, list)): indices = [indices]
|
||||
x, indices = self, list(indices)
|
||||
|
||||
# filter ellipsis and fill with slice(None) or fill rest of indices with slice(None)
|
||||
# fill ellipsis or rest of indices with slice(None)
|
||||
if len(ellipsis_idx := [dim for dim, i in enumerate(indices) if i is Ellipsis]) > 1: raise IndexError("indices can only have a single ellipsis")
|
||||
fill_idx = ellipsis_idx[0] if ellipsis_idx else len(indices)
|
||||
# NOTE: None adds a dim later
|
||||
num_indices = len(indices) - len(ellipsis_idx) - sum(1 for i in indices if i is None)
|
||||
if num_indices > self.ndim: raise IndexError(f"too many {num_indices=} for {self.ndim=}")
|
||||
fill_idx = ellipsis_idx[0] if ellipsis_idx else len(indices)
|
||||
indices[fill_idx:fill_idx+1] = [slice(None)] * (self.ndim - num_indices)
|
||||
|
||||
indices_parsed, dim = [], 0
|
||||
@@ -1148,6 +1154,7 @@ class Tensor(MathTrait):
|
||||
index = Tensor([i+size if i<0 else i for i in fully_flatten(index)], self.device, requires_grad=False).reshape(ti.shape)
|
||||
case int() | UOp(): # sint
|
||||
if index >= size or index < -size: raise IndexError(f"{index=} is out of bounds with {size=}")
|
||||
# TODO: is this right for (negative) symbolic?
|
||||
boundary = [index, index+1] if index >= 0 else [index+size, index+size+1]
|
||||
case slice():
|
||||
if index.step == 0: raise ValueError(f"{index=} cannot have 0 as step")
|
||||
@@ -1161,9 +1168,9 @@ class Tensor(MathTrait):
|
||||
elif stride < 0: boundary = [boundary[1] + 1, boundary[0] + 1]
|
||||
# update size for slice
|
||||
size = ceildiv((boundary[1] - boundary[0]), abs(stride))
|
||||
elif (step == 1) and isinstance(step, int) and all(isinstance(s,(int,UOp)) for s in (start, stop)) and resolve((stop-start) > 0, False):
|
||||
elif resolve(step == 1, False) and all(isinstance(s,sint) for s in (start, stop)) and resolve((stop-start) > 0, False):
|
||||
# simple symbolic slice
|
||||
size = cast(UOp|int, cast(UOp, (stop - start)).ssimplify())
|
||||
size = cast(sint, cast(UOp, (stop - start)).ssimplify())
|
||||
else: raise TypeError(f"slice {index=} is not supported")
|
||||
case None: pass # do nothing
|
||||
case _: raise IndexError(f"{type(index).__name__} indexing is not supported")
|
||||
@@ -1175,9 +1182,9 @@ class Tensor(MathTrait):
|
||||
# flip negative strides
|
||||
shrinks, strides = zip(*((i['boundary'], i['stride']) for i in mops))
|
||||
x = x.shrink(shrinks).flip(tuple(i for i,st in enumerate(strides) if st < 0))
|
||||
# handle stride != 1 or -1
|
||||
if any(abs(st) != 1 for st in strides):
|
||||
strides = tuple(abs(s) for s in strides)
|
||||
strides = tuple(map(abs, strides))
|
||||
# apply stride
|
||||
if any(st != 1 for st in strides):
|
||||
# pad shape to multiple of stride
|
||||
if not all_int(x.shape): raise RuntimeError("symbolic shape not supported")
|
||||
x = x.pad(tuple((0, round_up(s, st) - s) for s, st in zip(x.shape, strides)))
|
||||
@@ -1296,7 +1303,7 @@ class Tensor(MathTrait):
|
||||
assert all(s >= i for d,(s,i) in enumerate(zip(self.shape, index.shape)) if d != dim), "requires self.shape[d] >= index.shape[d] for all d != dim"
|
||||
index = index.to(self.device)
|
||||
x = self.shrink(tuple((0, i) if d != dim else None for d,i in enumerate(index.shape))).unsqueeze(-1).transpose(-1, dim)
|
||||
return (x * index.unsqueeze(-1)._one_hot_along_dim(self.shape[dim])).sum(-1, dtype=self.dtype)
|
||||
return (index.unsqueeze(-1)._one_hot_along_dim(self.shape[dim]).where(x, 0)).sum(-1, dtype=self.dtype)
|
||||
|
||||
def cat(self:Tensor, *args:Tensor, dim:int=0) -> Tensor:
|
||||
"""
|
||||
@@ -2681,7 +2688,7 @@ class Tensor(MathTrait):
|
||||
print(t.triu(diagonal=-1).numpy())
|
||||
```
|
||||
"""
|
||||
return Tensor._tri(self.shape[-2], self.shape[-1], diagonal=diagonal, device=self.device, dtype=dtypes.bool).where(self, 0).cast(self.dtype)
|
||||
return Tensor._tri(self.shape[-2], self.shape[-1], diagonal=diagonal, device=self.device, dtype=dtypes.bool).where(self, self.zeros_like())
|
||||
|
||||
def tril(self, diagonal:int=0) -> Tensor:
|
||||
"""
|
||||
@@ -2704,7 +2711,7 @@ class Tensor(MathTrait):
|
||||
print(t.tril(diagonal=-1).numpy())
|
||||
```
|
||||
"""
|
||||
return Tensor._tri(self.shape[-2], self.shape[-1], diagonal=diagonal+1, device=self.device, dtype=dtypes.bool).where(0, self).cast(self.dtype)
|
||||
return Tensor._tri(self.shape[-2], self.shape[-1], diagonal=diagonal+1, device=self.device, dtype=dtypes.bool).where(self.zeros_like(), self)
|
||||
|
||||
def interpolate(self, size:tuple[int, ...], mode:str="linear", align_corners:bool=False) -> Tensor:
|
||||
"""
|
||||
|
||||
@@ -2,10 +2,10 @@ from typing import Callable
|
||||
import math, functools
|
||||
from tinygrad.dtype import dtypes, DType, promo_lattice
|
||||
from tinygrad.device import is_dtype_supported
|
||||
from tinygrad.helpers import polyN, getenv
|
||||
from tinygrad.helpers import polyN, DISABLE_FAST_IDIV
|
||||
from tinygrad.uop.ops import UOp, UPat, Ops, PatternMatcher
|
||||
|
||||
TRANSCENDENTAL_SUPPORTED_DTYPES = (dtypes.float16, dtypes.float32, dtypes.float64)
|
||||
TRANSCENDENTAL_DTYPES = (dtypes.float16, dtypes.float32, dtypes.float64)
|
||||
|
||||
def _lazy_map_numbers(x:UOp, inf:UOp, _inf:UOp, nan:UOp, ratio:UOp):
|
||||
"""replace inf -> inf, -inf -> _inf, nan -> nan, otherwise -> ratio"""
|
||||
@@ -32,14 +32,14 @@ def pow2if(q:UOp, float_dtype:DType):
|
||||
|
||||
def ilogb2k(d:UOp) -> UOp:
|
||||
"""calculate the integer part of log2(d), where d is normalized fp value in the range of [0, +inf)."""
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_SUPPORTED_DTYPES
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_DTYPES
|
||||
dint = d.bitcast({dtypes.float64: dtypes.int64, dtypes.float32: dtypes.int32, dtypes.float16: dtypes.int16}[d.dtype.scalar()].vec(d.dtype.vcount))
|
||||
# -1 <= ilog2bk(d) <= 128
|
||||
return (shr(dint, mantissa_bits(d.dtype)) & exponent_mask(d.dtype)) - exponent_bias(d.dtype)
|
||||
|
||||
def ldexp3k(d:UOp, e:UOp) -> UOp:
|
||||
"""d*2^e. e is a number obtained by casting an integer in the range [-127, 127] to a float. d is any float number."""
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_SUPPORTED_DTYPES and e.dtype.scalar() in TRANSCENDENTAL_SUPPORTED_DTYPES
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_DTYPES and e.dtype.scalar() in TRANSCENDENTAL_DTYPES
|
||||
dtype = {dtypes.float64: dtypes.int64, dtypes.float32: dtypes.int32, dtypes.float16: dtypes.int16}[d.dtype.scalar()].vec(d.dtype.count)
|
||||
m1 = d.bitcast(dtype)
|
||||
m2 = shl(e.cast(dtype), mantissa_bits(d.dtype))
|
||||
@@ -47,12 +47,12 @@ def ldexp3k(d:UOp, e:UOp) -> UOp:
|
||||
|
||||
def ldexp2k(d:UOp, e:UOp) -> UOp:
|
||||
"""d*2^e. much faster than ldexp3k but risky. d > 0 and d is not denormal."""
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_SUPPORTED_DTYPES and e.dtype.scalar() in (dtypes.int16, dtypes.int32, dtypes.int64)
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_DTYPES and e.dtype.scalar() in (dtypes.int16, dtypes.int32, dtypes.int64)
|
||||
return (d * pow2if(shr(e, 1), d.dtype)) * pow2if(e - shr(e, 1), d.dtype)
|
||||
|
||||
def frexp(v:UOp) -> tuple[UOp, UOp]:
|
||||
"""frexp(v) -> (mantissa, exponent) assuming v != 0"""
|
||||
assert v.dtype.scalar() in TRANSCENDENTAL_SUPPORTED_DTYPES
|
||||
assert v.dtype.scalar() in TRANSCENDENTAL_DTYPES
|
||||
# m1 = masks for mantissa, m2 = masks to normalize the mantissa.
|
||||
m1 = {dtypes.float64: 0x000FFFFFFFFFFFFF, dtypes.float32: 0x807FFFFF, dtypes.float16: 0x83FF}[v.dtype.scalar()]
|
||||
m2 = {dtypes.float64: 0x3FE0000000000000, dtypes.float32: 0x3F000000, dtypes.float16: 0x3800}[v.dtype.scalar()]
|
||||
@@ -72,7 +72,7 @@ def payne_hanek_reduction(d:UOp) -> tuple[UOp, UOp]:
|
||||
- `r`[d.dtype] is the reminder value corresponding to `round_to_nearest(x % pi/2)`.
|
||||
- `q`[int32] is an integer, and q % 4 is corresponding to the quadrant of the original angle `d`.
|
||||
"""
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_SUPPORTED_DTYPES
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_DTYPES
|
||||
# https://stackoverflow.com/questions/30463616/payne-hanek-algorithm-implementation-in-c/30465751#30465751
|
||||
# 190 bits of 2/pi for Payne-Hanek style argument reduction
|
||||
two_over_pi_f = [0x00000000, 0x28be60db, 0x9391054a, 0x7f09d5f4, 0x7d4d3770, 0x36d8a566, 0x4f10e410]
|
||||
@@ -174,7 +174,7 @@ def xsin(d:UOp, fast:bool=False, switch_over:float=30.0) -> UOp:
|
||||
- fast=True assumes x <= switch_over.
|
||||
- switch_over is the threshold for switching to payne_hanek_reduction.
|
||||
"""
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_SUPPORTED_DTYPES
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_DTYPES
|
||||
# mask +-inf/nan as zero
|
||||
x = _lazy_map_numbers(d, d.const_like(0.0), d.const_like(0.0), d.const_like(0.0), d)
|
||||
# x_sign = sign(x)
|
||||
@@ -196,7 +196,7 @@ def xexp2(d:UOp) -> UOp:
|
||||
Implements a 1.0 ULP approximation for Ops.EXP2
|
||||
- Paper: https://arxiv.org/pdf/2001.09258
|
||||
"""
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_SUPPORTED_DTYPES
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_DTYPES
|
||||
# mask +=inf/nan as zero.
|
||||
x = _lazy_map_numbers(d, d.const_like(0.0), d.const_like(0.0), d.const_like(0.0), d)
|
||||
q = rintk(x)
|
||||
@@ -222,7 +222,7 @@ def xlog2(d:UOp) -> UOp:
|
||||
Implements a 1.0 ULP approximation for Ops.LOG2
|
||||
Paper: https://arxiv.org/pdf/2001.09258 5.5
|
||||
"""
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_SUPPORTED_DTYPES
|
||||
assert d.dtype.scalar() in TRANSCENDENTAL_DTYPES
|
||||
# TODO: float16 denormal need float32 to achieve precision
|
||||
if d.dtype.scalar() == dtypes.float16: return xlog2(d.cast(dtypes.float32)).cast(dtypes.float16)
|
||||
FLT_MIN = d.const_like(1e-6 if d.dtype.scalar() == dtypes.float16 else 1e-4)
|
||||
@@ -315,10 +315,12 @@ def threefry2x32(x: UOp, key: UOp):
|
||||
powers_of_two = {2**i:i for i in range(64)}
|
||||
@functools.cache
|
||||
def get_late_rewrite_patterns(ops:tuple[Ops, ...], force_transcendental=False):
|
||||
pat: list[tuple[UPat, Callable]] = [(UPat(op, dtype=TRANSCENDENTAL_SUPPORTED_DTYPES, src=(UPat.var("d"),)), f) for op,f in \
|
||||
pat: list[tuple[UPat, Callable]] = [(UPat(op, dtype=TRANSCENDENTAL_DTYPES, src=(UPat.var("d"),)), f) for op,f in \
|
||||
((Ops.EXP2, xexp2), (Ops.LOG2, xlog2), (Ops.SIN, xsin)) if op not in ops or force_transcendental]
|
||||
# no real hardware supports THREEFRY
|
||||
pat.append((UPat(Ops.THREEFRY, dtype=dtypes.uint64, src=(UPat.var("x"), UPat.var("key"))), threefry2x32))
|
||||
# no real hardware supports THREEFRY, but NullRenderer does
|
||||
if Ops.THREEFRY not in ops: pat.append((UPat(Ops.THREEFRY, dtype=dtypes.uint64, src=(UPat.var("x"), UPat.var("key"))), threefry2x32))
|
||||
# MAX can be rewritten as CMPLT + WHERE (max function is annoying on many cstyle backends)
|
||||
if Ops.MAX not in ops and Ops.CMPLT in ops: pat.append((UPat(Ops.MAX, name="m"), lambda m: (m.src[0] < m.src[1]).where(m.src[1], m.src[0])))
|
||||
# rewrite SQRT to xpow 0.5
|
||||
if Ops.SQRT not in ops: pat.append((UPat(Ops.SQRT, src=UPat.var("d")), lambda d: xpow(d, d.const_like(0.5))))
|
||||
# rewrite MOD to AND (which should always be supported, but not for generic in tests): x % (2**y) -> x & (2**y-1)
|
||||
@@ -330,7 +332,7 @@ def get_late_rewrite_patterns(ops:tuple[Ops, ...], force_transcendental=False):
|
||||
pat += [(UPat.var("x", dtypes.uints)//UPat.cvar("c"), lambda x,c: x >> v if (v:=powers_of_two.get(c.arg, 0)) else None)]
|
||||
pat += [(UPat.var("x", dtypes.ints)//UPat.cvar("c"), lambda x,c: (x+(l.const_like(l.vmin) if (l:=(x<0)).vmin==l.vmax else l).where(
|
||||
c-1, 0)) >> v if (v:=powers_of_two.get(c.arg, 0)) else None)] # (x+(x<0).where(c-1, 0)) >> v
|
||||
if not getenv("DISABLE_FAST_IDIV"):
|
||||
if not DISABLE_FAST_IDIV:
|
||||
pat += [(UPat.var("x", dtypes.ints)//UPat.cvar("d"), lambda ctx, x, d: fast_idiv(ctx, x, d.arg))]
|
||||
pat += [(UPat.var("x", dtypes.ints)%UPat.var("d"), lambda x, d: x-d*(x//d))]
|
||||
if Ops.NEG in ops:
|
||||
@@ -348,4 +350,8 @@ def get_late_rewrite_patterns(ops:tuple[Ops, ...], force_transcendental=False):
|
||||
]
|
||||
if Ops.CMPEQ in ops: pat += [(UPat.var('x').ne(UPat.var('y')).logical_not(), lambda x,y: x.alu(Ops.CMPEQ, y))]
|
||||
if Ops.MULACC in ops: pat += [(UPat.var('a')*UPat.var('b')+UPat.var('c'), lambda a,b,c: a.alu(Ops.MULACC, b, c))]
|
||||
# some backends emit FDIV for RECIP, in that case: a*(1/b) -> a/b
|
||||
if Ops.FDIV in ops:
|
||||
pat += [(UPat.var("x").reciprocal(), lambda x: x.const_like(1).alu(Ops.FDIV, x))]
|
||||
pat += [(UPat.var("a", dtypes.floats) * UPat.const(dtypes.floats, 1).alu(Ops.FDIV, UPat.var("b")), lambda a,b: a.alu(Ops.FDIV, b))]
|
||||
return PatternMatcher(pat)
|
||||
|
||||
+35
-10
@@ -6,7 +6,7 @@ from enum import Enum, auto
|
||||
from tinygrad.uop import Ops, GroupOp
|
||||
from tinygrad.uop.mathtraits import MathTrait
|
||||
from tinygrad.dtype import ConstType, ImageDType, dtypes, DType, truncate, PtrDType
|
||||
from tinygrad.helpers import ContextVar, all_int, prod, getenv, all_same, Context, partition, temp, unwrap, T, argfix, Metadata, flatten
|
||||
from tinygrad.helpers import ContextVar, all_int, prod, getenv, all_same, Context, partition, temp, unwrap, T, argfix, Metadata, flatten, TRACEMETA
|
||||
from tinygrad.helpers import PICKLE_BUFFERS, PROFILE, dedup, cdiv, cmod, diskcache_put, to_function_name, cpu_profile, TracingKey
|
||||
if TYPE_CHECKING:
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
@@ -136,13 +136,21 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
|
||||
@functools.cached_property
|
||||
def st(self) -> ShapeTracker|None:
|
||||
if self.op in GroupOp.Block or self.op is Ops.INDEX: return None
|
||||
if self.op is Ops.INDEX and self.src[0].op in {Ops.DEFINE_GLOBAL, Ops.DEFINE_LOCAL, Ops.DEFINE_REG,
|
||||
Ops.BUFFER, Ops.BUFFERIZE, Ops.VECTORIZE, Ops.STORE}:
|
||||
return None
|
||||
if self.op in GroupOp.Block: return None
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
# VIEW and MovementOps define a new ShapeTracker from the arg
|
||||
if self.op is Ops.VIEW: return self.arg
|
||||
if self.op is Ops.BUFFERIZE: return ShapeTracker.from_shape((prod(tuple([int(r.vmax+1) for r in self.src[1:]])),))
|
||||
#if self.op is Ops.BUFFERIZE: return ShapeTracker.from_shape(tuple([r.vmax+1 for r in self.src[1:]]))
|
||||
# allow reshape from nothing
|
||||
if self.op is Ops.RESHAPE and self.src[0].st is None: return ShapeTracker.from_shape(self.arg)
|
||||
if self.op in GroupOp.Movement: return unwrap(self.src[0].st).mop(self.op, self.arg)
|
||||
# CONST with a DEVICE has a shape of ()
|
||||
if self.op is Ops.CONST and len(self.src) and self.src[0].op is Ops.DEVICE: return ShapeTracker.from_shape(())
|
||||
if self.op is Ops.STORE and isinstance(self.dtype, PtrDType): return ShapeTracker.from_shape((self.dtype.size,))
|
||||
# BufferOps and ASSIGN flow ShapeTracker from a direct edge
|
||||
if self.op in {Ops.STORE, Ops.ASSIGN, Ops.LOAD}: return self.src[0].st
|
||||
if self.op in GroupOp.Buffer: return views[0] if (views:=[x.st for x in self.src if x.op is Ops.VIEW]) else None
|
||||
@@ -186,10 +194,15 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
@functools.cached_property
|
||||
def ranges(self) -> dict[UOp, None]:
|
||||
if self.op is Ops.RANGE: return {self:None}
|
||||
if self.op in {Ops.CONTIGUOUS, Ops.REDUCE, Ops.STORE}:
|
||||
if self.op in {Ops.BUFFERIZE, Ops.REDUCE}:
|
||||
ret = self.src[0].ranges.copy()
|
||||
for s in self.src[1:]:
|
||||
if s in ret: del ret[s]
|
||||
elif self.op in {Ops.STORE}:
|
||||
ret = self.src[0].ranges.copy()
|
||||
ret.update(self.src[1].ranges)
|
||||
for s in self.src[2:]:
|
||||
if s in ret: del ret[s]
|
||||
else:
|
||||
ret = {}
|
||||
for s in self.src: ret.update(s.ranges)
|
||||
@@ -197,11 +210,11 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
|
||||
# *** uop evaluation ***
|
||||
|
||||
def simplify(self):
|
||||
def simplify(self, tracked=False):
|
||||
# late import!
|
||||
from tinygrad.uop.symbolic import symbolic
|
||||
with Context(TRACK_MATCH_STATS=0):
|
||||
return graph_rewrite(self, symbolic)
|
||||
with Context(TRACK_MATCH_STATS=0 if not tracked else TRACK_MATCH_STATS.value):
|
||||
return graph_rewrite(self, symbolic, name="simplify")
|
||||
def ssimplify(self) -> UOp|ConstType: return ret.arg if (ret:=self.simplify()).op is Ops.CONST else ret
|
||||
def _eval(self, dtype, expected_type:Type[T]) -> T:
|
||||
assert self.dtype in dtype, f"eval with wrong dtype {self}"
|
||||
@@ -258,7 +271,7 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
i = (i,)
|
||||
return UOp(Ops.GEP, self.dtype.scalar().vec(len(i)) if len(i) > 1 else self.dtype.scalar(), (self,), i)
|
||||
def load(self, *src:UOp, **kwargs): return UOp(Ops.LOAD, dtype=kwargs.pop("dtype", self.dtype.base), src=(self,)+src, **kwargs)
|
||||
def store(self, *src:UOp, **kwargs): return UOp(Ops.STORE, dtypes.void, (self,)+src, **kwargs)
|
||||
def store(self, *src:UOp, **kwargs): return UOp(Ops.STORE, kwargs.pop("dtype", dtypes.void), (self,)+src, **kwargs)
|
||||
def assign(self, x:UOp): return UOp(Ops.ASSIGN, self.dtype, (self, x))
|
||||
def barrier(self, *src:UOp): return UOp(Ops.BARRIER, src=(self,)+src)
|
||||
def alu(self, op, *src:UOp, **kwargs):
|
||||
@@ -293,6 +306,7 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
def reduce(self, *src:UOp, **kwargs): return UOp(Ops.REDUCE, kwargs.pop('dtype', self.dtype), src=(self,)+src, **kwargs)
|
||||
def contiguous(self, *args, **kwargs): return UOp(Ops.CONTIGUOUS, dtype=self.dtype, src=(self,)+args, **kwargs)
|
||||
def contiguous_backward(self): return self.alu(Ops.CONTIGUOUS_BACKWARD)
|
||||
def bufferize(self, *args, **kwargs): return UOp(Ops.BUFFERIZE, dtype=self.dtype, src=(self,)+args, **kwargs)
|
||||
def fuse(self): return self.alu(Ops.FUSE)
|
||||
def allreduce(self, op, device:str|tuple[str, ...]|UOp):
|
||||
assert isinstance(self.device, tuple), f"allreduce must be on tuple {self.device} isn't"
|
||||
@@ -364,6 +378,7 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
if self.st == ret.st: return self # ignore NOOPs, also check ret.st
|
||||
return ret
|
||||
|
||||
def forced_reshape(self, arg:tuple[sint, ...], **kwargs): return UOp(Ops.RESHAPE, kwargs.pop("dtype", self.dtype), src=(self,), arg=arg)
|
||||
def reshape(self, arg:tuple[sint, ...]): return self._mop(Ops.RESHAPE, arg)
|
||||
def pad(self, arg:tuple[tuple[sint, sint], ...]): return self._mop(Ops.PAD, arg)
|
||||
def expand(self, arg:tuple[sint, ...]): return self._mop(Ops.EXPAND, arg)
|
||||
@@ -400,6 +415,15 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
if self.op is Ops.MSTACK: return UOp(Ops.MSTACK, self.dtype, src=tuple(x.buf_uop for x in self.src))
|
||||
assert self.op is Ops.ASSIGN, f"must be ASSIGN {self.op}"
|
||||
return self.src[0].base
|
||||
|
||||
def as_buf(self) -> UOp:
|
||||
if self.op is Ops.MSELECT: return self.src[0].as_buf().mselect(self.arg)
|
||||
if self.op is Ops.MSTACK: return UOp(Ops.MSTACK, self.dtype, src=tuple(x.as_buf() for x in self.src))
|
||||
# TODO: this should be the only one of these. this is the one RANGEIFY uses
|
||||
s = self
|
||||
while len(s.src) and s.op is not Ops.BUFFER: s = s.src[0]
|
||||
return s
|
||||
|
||||
@property
|
||||
def buffer(self) -> Buffer|MultiBuffer:
|
||||
from tinygrad.device import Buffer, MultiBuffer
|
||||
@@ -546,7 +570,8 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
return fxn(**{k.arg[0]:v for k,v in var_vals.items() if k.arg[0] in varnames})
|
||||
|
||||
def render(self, simplify=True, pm:PatternMatcher|None=None) -> str:
|
||||
ret = graph_rewrite(self.simplify() if simplify else self, renderer if pm is None else pm)
|
||||
with Context(TRACK_MATCH_STATS=0):
|
||||
ret = graph_rewrite(self.simplify() if simplify else self, renderer if pm is None else pm)
|
||||
return ret.arg if ret.op is Ops.NOOP else str(ret)
|
||||
|
||||
class AxisType(Enum):
|
||||
@@ -751,7 +776,7 @@ class PatternMatcher:
|
||||
def __reduce__(self): return PatternMatcher, ([(x,deconstruct_function(fxn) if fxn.__name__ == "<lambda>" else fxn) for x,fxn in self.patterns],)
|
||||
|
||||
@functools.cache # pylint: disable=method-cache-max-size-none
|
||||
def __add__(self, more:PatternMatcher): return PatternMatcher(self.patterns+more.patterns)
|
||||
def __add__(self, more:PatternMatcher) -> PatternMatcher: return PatternMatcher(self.patterns+more.patterns)
|
||||
|
||||
def rewrite(self, uop:UOp, ctx=None) -> UOp|None:
|
||||
ler = {u.op for u in uop.src}
|
||||
@@ -770,7 +795,7 @@ def track_uop(u:UOp):
|
||||
uop_number[u] = num = next(ucount)
|
||||
# KERNEL also has a UOp in the arg
|
||||
arg = type(u.arg)(track_uop(u.arg.ast), u.arg.metadata) if u.op is Ops.KERNEL else u.arg
|
||||
uop_fields[num] = (u.op, u.dtype, tuple(track_uop(s) for s in u.src), arg, u.tag)
|
||||
uop_fields[num] = (u.op, u.dtype, tuple(track_uop(s) for s in u.src), arg, u.tag)+((u.metadata,) if TRACEMETA>=2 else ())
|
||||
return num
|
||||
|
||||
# *** tracking pattern matcher ***
|
||||
|
||||
@@ -10,7 +10,7 @@ try:
|
||||
def z3_cdiv(a, b):return z3.If((a<0), z3.If(0<b, (a+(b-1))/b, (a-(b+1))/b), a/b)
|
||||
z3_alu: dict[Ops, Callable] = python_alu | {Ops.MOD: lambda a,b: a-z3_cdiv(a,b)*b, Ops.IDIV: z3_cdiv, Ops.SHR: lambda a,b: a/(2**b.as_long()),
|
||||
Ops.SHL: lambda a,b: a*(2**b.as_long()), Ops.AND: lambda a,b: a%(b+1) if isinstance(b, z3.ArithRef) else a&b, Ops.WHERE: z3.If,
|
||||
Ops.MAX: lambda a,b: z3.If(a<b, b, a)}
|
||||
Ops.MAX: lambda a,b: z3.If(a<b, b, a), Ops.TRUNC: lambda a: a if a.is_int() else z3.ToReal(z3.If(a >= 0, z3.ToInt(a), -z3.ToInt(-a)))}
|
||||
def create_bounded(name:str, vmin, vmax, solver:z3.Solver) -> z3.ArithRef:
|
||||
s = z3.Int(name, ctx=solver.ctx)
|
||||
solver.add(vmin <= s, s <= vmax)
|
||||
@@ -168,8 +168,8 @@ spec = PatternMatcher([
|
||||
(UPat(Ops.LOAD, src=(index_pat,), allow_any_len=True), validate_index),
|
||||
|
||||
# STORE takes a <bufidx, val, gate?>
|
||||
(UPat(Ops.STORE, dtype=dtypes.void, src=(index_pat, UPat(name="val"), UPat(Ops.IF, name="gate")), allow_any_len=True), validate_store),
|
||||
(UPat(Ops.STORE, dtype=dtypes.void, src=(index_pat, UPat(name="val")), allow_any_len=True), validate_store),
|
||||
(UPat(Ops.STORE, src=(index_pat, UPat(name="val"), UPat(Ops.IF, name="gate")), allow_any_len=True), validate_store),
|
||||
(UPat(Ops.STORE, src=(index_pat, UPat(name="val")), allow_any_len=True), validate_store),
|
||||
|
||||
# most ALUs have all matching dtypes, except CMPLT, CMPNE, and WHERE
|
||||
(UPat(Ops.WHERE, name="w", src=(UPat(dtype=dtypes.bool), UPat.var("x"), UPat.var("y"))), lambda w,x,y: w.dtype == x.dtype == y.dtype),
|
||||
|
||||
@@ -41,6 +41,7 @@ symbolic_simple = PatternMatcher([
|
||||
(UPat(GroupOp.Idempotent, src=(UPat.var("x"), UPat.var("x"))), lambda x: x),
|
||||
(UPat.var("x", dtype=dtypes.bool).logical_not().logical_not(), lambda x: x),
|
||||
(UPat.var("x", dtype=dtypes.bool).where(UPat.const(dtypes.bool, True), UPat.const(dtypes.bool, False)), lambda x: x),
|
||||
(UPat.var("x", dtype=dtypes.bool).where(UPat.const(dtypes.bool, False), UPat.const(dtypes.bool, True)), lambda x: x.logical_not()),
|
||||
(UPat.var("x", dtype=dtypes.ints+(dtypes.bool,)).trunc(), lambda x: x),
|
||||
# ** zero folding **
|
||||
(UPat.var("x") < UPat.var("x"), lambda x: x.const_like(False).cast(dtypes.bool.vec(x.dtype.count))), # x < x -> False
|
||||
|
||||
@@ -122,19 +122,6 @@ const colorScheme = {TINY:["#1b5745", "#354f52", "#354f52", "#1d2e62", "#63b0cd"
|
||||
CATEGORICAL:["#ff8080", "#F4A261", "#C8F9D4", "#8D99AE", "#F4A261", "#ffffa2", "#ffffc0", "#87CEEB"],}
|
||||
const cycleColors = (lst, i) => lst[i%lst.length];
|
||||
|
||||
const createPolygons = (source, height) => {
|
||||
const shapes = [];
|
||||
const yscale = d3.scaleLinear().domain([0, source.peak]).range([height, 0]);
|
||||
for (const [i,e] of source.shapes.entries()) {
|
||||
const x = e.x.map((i,_) => source.timestamps[i]-data.st);
|
||||
const y0 = e.y.map(yscale);
|
||||
const y1 = e.y.map(y => yscale(y+e.arg.nbytes));
|
||||
const arg = { tooltipText:`${e.arg.dtype} len:${formatUnit(e.arg.sz)}\n${formatUnit(e.arg.nbytes, "B")}` };
|
||||
shapes.push({ x, y0, y1, arg, fillColor:cycleColors(colorScheme.BUFFER, i) });
|
||||
}
|
||||
return shapes;
|
||||
}
|
||||
|
||||
const rescaleTrack = (source, tid, k) => {
|
||||
for (const e of source.shapes) {
|
||||
for (let i=0; i<e.y0.length; i++) {
|
||||
@@ -205,7 +192,14 @@ async function renderProfiler() {
|
||||
div.style("height", levelHeight*v.maxDepth+padding+"px").style("pointerEvents", "none");
|
||||
} else {
|
||||
const height = heightScale(v.peak);
|
||||
data.tracks.set(k, { shapes:createPolygons(v, height), offsetY, height, peak:v.peak, scaleFactor:maxheight*4/height });
|
||||
const yscale = d3.scaleLinear().domain([0, v.peak]).range([height, 0]);
|
||||
const shapes = [];
|
||||
for (const [i,e] of v.shapes.entries()) {
|
||||
const x = e.x.map(tsIdx => v.timestamps[tsIdx]-st);
|
||||
const arg = {tooltipText:`${e.arg.dtype} len:${formatUnit(e.arg.sz)}\n${formatUnit(e.arg.nbytes, "B")}`};
|
||||
shapes.push({ x, y0:e.y.map(yscale), y1:e.y.map(y => yscale(y+e.arg.nbytes)), arg, fillColor:cycleColors(colorScheme.BUFFER, i) });
|
||||
}
|
||||
data.tracks.set(k, { shapes, offsetY, height, peak:v.peak, scaleFactor:maxheight*4/height });
|
||||
div.style("height", height+padding+"px").style("cursor", "pointer").on("click", (e) => {
|
||||
const newFocus = e.currentTarget.id === focusedDevice ? null : e.currentTarget.id;
|
||||
let offset = 0;
|
||||
|
||||
@@ -7,7 +7,7 @@ from http.server import BaseHTTPRequestHandler
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
from typing import Any, TypedDict, Generator
|
||||
from tinygrad.helpers import colored, getenv, tqdm, unwrap, word_wrap, TRACEMETA, ProfileEvent, ProfileRangeEvent, TracingKey, ProfilePointEvent
|
||||
from tinygrad.uop.ops import TrackedGraphRewrite, UOp, Ops, printable, GroupOp, srender, sint
|
||||
from tinygrad.uop.ops import TrackedGraphRewrite, UOp, Ops, printable, GroupOp, srender, sint, sym_infer
|
||||
from tinygrad.device import ProfileDeviceEvent, ProfileGraphEvent, ProfileGraphEntry, Device
|
||||
from tinygrad.renderer import ProgramSpec
|
||||
from tinygrad.dtype import dtypes
|
||||
@@ -91,9 +91,9 @@ def uop_to_json(x:UOp) -> dict[int, dict]:
|
||||
|
||||
@functools.cache
|
||||
def _reconstruct(a:int):
|
||||
op, dtype, src, arg, tag = contexts[2][a]
|
||||
op, dtype, src, arg, *rest = contexts[2][a]
|
||||
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, tag)
|
||||
return UOp(op, dtype, tuple(_reconstruct(s) for s in src), arg, *rest)
|
||||
|
||||
def get_details(ctx:TrackedGraphRewrite) -> Generator[GraphRewriteDetails, None, None]:
|
||||
yield {"graph":uop_to_json(next_sink:=_reconstruct(ctx.sink)), "uop":str(next_sink), "changed_nodes":None, "diff":None, "upat":None}
|
||||
@@ -126,7 +126,9 @@ def flatten_events(profile:list[ProfileEvent]) -> Generator[tuple[Decimal, Decim
|
||||
def timeline_layout(events:list[tuple[int, int, float, DevEvent]]) -> dict:
|
||||
shapes:list[dict] = []
|
||||
levels:list[int] = []
|
||||
exec_points:dict[str, dict] = {}
|
||||
for st,et,dur,e in events:
|
||||
if isinstance(e, ProfilePointEvent) and e.name == "exec": exec_points[e.key] = e.arg
|
||||
if dur == 0: continue
|
||||
# find a free level to put the event
|
||||
depth = next((i for i,level_et in enumerate(levels) if st>=level_et), len(levels))
|
||||
@@ -135,9 +137,9 @@ def timeline_layout(events:list[tuple[int, int, float, DevEvent]]) -> dict:
|
||||
name, cat, info = e.name, None, None
|
||||
if (ref:=ref_map.get(name)) is not None:
|
||||
name = ctxs[ref]["name"]
|
||||
# TODO: support symbolic by capturing var_vals in profile events
|
||||
if isinstance(p:=contexts[0][ref].ret, ProgramSpec) and all(isinstance(es,int) for es in [p.estimates.ops, p.estimates.mem, p.estimates.lds]):
|
||||
info = f"{p.estimates.ops/(t:=dur*1e3):.2f} GFLOPS {p.estimates.mem/t:4.1f}|{p.estimates.lds/t:.1f} GB/s"
|
||||
if isinstance(p:=contexts[0][ref].ret, ProgramSpec) and (ei:=exec_points.get(p.name)) is not None:
|
||||
info = f"{sym_infer(p.estimates.ops, ei['var_vals'])/(t:=dur*1e3):.2f} GFLOPS {sym_infer(p.estimates.mem, ei['var_vals'])/t:4.1f}"+ \
|
||||
f"|{sym_infer(p.estimates.lds,ei['var_vals'])/t:.1f} GB/s\n{ei['metadata']}"
|
||||
elif isinstance(e.name, TracingKey):
|
||||
name, cat = e.name.display_name, e.name.cat
|
||||
ref = next((v for k in e.name.keys if (v:=ref_map.get(k)) is not None), None)
|
||||
|
||||
Reference in New Issue
Block a user