forked from tinygrad/tinygrad
Compare commits
57
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e740b115f | ||
|
|
2ef5255b09 | ||
|
|
d319a044a6 | ||
|
|
27396b8eed | ||
|
|
0d8a0d7a96 | ||
|
|
4d6e407eb0 | ||
|
|
17adbe86d8 | ||
|
|
ad9dec25b3 | ||
|
|
4c3982c44e | ||
|
|
e28605e324 | ||
|
|
8a7be0a747 | ||
|
|
efe8b5611d | ||
|
|
0d7075f2de | ||
|
|
c44760c89d | ||
|
|
ca41b5e38b | ||
|
|
ca7a641442 | ||
|
|
0c97d6de1b | ||
|
|
d623f6d850 | ||
|
|
857a830dcc | ||
|
|
0806677b51 | ||
|
|
700c11597b | ||
|
|
ae0c3cfff6 | ||
|
|
27bcb9fd1c | ||
|
|
d2bb1bcb97 | ||
|
|
6a232ccdac | ||
|
|
e768773e13 | ||
|
|
7d6c0a8cc7 | ||
|
|
630edcffd8 | ||
|
|
a67e0917c3 | ||
|
|
1181ec0cd2 | ||
|
|
996c907c0b | ||
|
|
1875bc69f9 | ||
|
|
5403a4aeaf | ||
|
|
b0dab6a4cd | ||
|
|
10540414cd | ||
|
|
f7aa1b85fe | ||
|
|
dfb702ef33 | ||
|
|
ef17af85c6 | ||
|
|
dd3d2eb36c | ||
|
|
3e64467322 | ||
|
|
7338ffead0 | ||
|
|
45baec1aab | ||
|
|
09bc377da3 | ||
|
|
14f99ff1a1 | ||
|
|
01c770c77b | ||
|
|
10d388499d | ||
|
|
20e46a175c | ||
|
|
53179953fc | ||
|
|
8ce72d3fad | ||
|
|
44a222a9b2 | ||
|
|
793ace530e | ||
|
|
b232c60def | ||
|
|
16f0edbe90 | ||
|
|
960cc6533a | ||
|
|
1826004ef9 | ||
|
|
82be8abfd2 | ||
|
|
702e38dc19 |
@@ -325,7 +325,7 @@ jobs:
|
||||
run: BENCHMARK_LOG=resnet_10steps_6gpu NV=1 CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=1536 GPUS=6 MODEL=resnet python3 examples/mlperf/model_train.py | tee train_resnet.txt
|
||||
- name: Run 10 MLPerf Bert training steps (6 gpu)
|
||||
# TODO: remove BERT_LAYERS once scheduler is fast
|
||||
run: BENCHMARK_LOG=bert_10steps_6gpu NV=1 CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=66 GPUS=6 BERT_LAYERS=2 FUSE_ARANGE=1 FUSE_ARANGE_UINT=0 MODEL=bert python3 examples/mlperf/model_train.py | tee train_bert.txt
|
||||
run: BENCHMARK_LOG=bert_10steps_6gpu NV=1 CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=66 GPUS=6 BERT_LAYERS=2 MODEL=bert python3 examples/mlperf/model_train.py | tee train_bert.txt
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Speed (NVIDIA Training)
|
||||
@@ -576,7 +576,7 @@ jobs:
|
||||
run: BENCHMARK_LOG=resnet_10steps_6gpu AMD=1 CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=1536 GPUS=6 MODEL=resnet python3 examples/mlperf/model_train.py | tee train_resnet.txt
|
||||
- name: Run 10 MLPerf Bert training steps (6 gpu)
|
||||
# TODO: remove BERT_LAYERS once scheduler is fast
|
||||
run: BENCHMARK_LOG=bert_10steps_6gpu AMD=1 CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=66 GPUS=6 BERT_LAYERS=2 FUSE_ARANGE=1 FUSE_ARANGE_UINT=0 MODEL=bert python3 examples/mlperf/model_train.py | tee train_bert.txt
|
||||
run: BENCHMARK_LOG=bert_10steps_6gpu AMD=1 CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=66 GPUS=6 BERT_LAYERS=2 MODEL=bert python3 examples/mlperf/model_train.py | tee train_bert.txt
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Speed (AMD MLPerf)
|
||||
@@ -611,12 +611,12 @@ jobs:
|
||||
run: BENCHMARK_LOG=openpilot_0_9_7 PYTHONPATH=. QCOM=1 taskset -c 4-7 python3 test/external/external_benchmark_openpilot.py https://github.com/commaai/openpilot/raw/v0.9.7/selfdrive/modeld/models/supercombo.onnx | tee openpilot_0_9_7.txt
|
||||
- name: benchmark openpilot w IMAGE=2 0.9.7
|
||||
run: BENCHMARK_LOG=openpilot_0_9_7_image PYTHONPATH=. NOLOCALS=1 FLOAT16=1 IMAGE=2 QCOM=1 taskset -c 4-7 python3 test/external/external_benchmark_openpilot.py https://github.com/commaai/openpilot/raw/v0.9.7/selfdrive/modeld/models/supercombo.onnx | tee openpilot_image_0_9_7.txt
|
||||
- name: openpilot compile3 0.9.7
|
||||
run: PYTHONPATH="." QCOM=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.9.7/selfdrive/modeld/models/supercombo.onnx
|
||||
- name: openpilot compile3 0.9.7+ tomb raider
|
||||
run: PYTHONPATH="." QCOM=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/e8bea2c78ffa92685ece511e9b554122aaf1a79d/selfdrive/modeld/models/supercombo.onnx
|
||||
- name: openpilot dmonitoring compile3 0.9.7
|
||||
run: PYTHONPATH="." QCOM=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.9.7/selfdrive/modeld/models/dmonitoring_model.onnx
|
||||
- name: openpilot compile3 0.9.9 driving_vision
|
||||
run: PYTHONPATH="." QCOM=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.9.9/selfdrive/modeld/models/driving_vision.onnx
|
||||
- name: openpilot compile3 0.9.9 driving_policy
|
||||
run: PYTHONPATH="." QCOM=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.9.9/selfdrive/modeld/models/driving_policy.onnx
|
||||
- name: openpilot compile3 0.9.9 dmonitoring
|
||||
run: PYTHONPATH="." QCOM=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.9.9/selfdrive/modeld/models/dmonitoring_model.onnx
|
||||
- name: openpilot compile3 Space Lab policy + vision
|
||||
run: |
|
||||
PYTHONPATH="." QCOM=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/22aec22a10ce09384d4a4af2a0bbff08d54af7e0c888503508f356fae4ff0e29
|
||||
@@ -697,7 +697,7 @@ jobs:
|
||||
# run: BENCHMARK_LOG=resnet_10steps AMD=1 MNISTMOCK=1 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=256 GPUS=1 MODEL=resnet python3 examples/mlperf/model_train.py | tee am_train_resnet_one_gpu.txt
|
||||
- name: Run 10 MLPerf Bert training steps (1 gpu)
|
||||
# TODO: remove BERT_LAYERS once scheduler is fast
|
||||
run: BENCHMARK_LOG=bert_10steps AMD=1 CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=66 GPUS=1 BERT_LAYERS=2 FUSE_ARANGE=1 FUSE_ARANGE_UINT=0 MODEL=bert python3 examples/mlperf/model_train.py | tee am_train_bert_one_gpu.txt
|
||||
run: BENCHMARK_LOG=bert_10steps AMD=1 CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=66 GPUS=1 BERT_LAYERS=2 MODEL=bert python3 examples/mlperf/model_train.py | tee am_train_bert_one_gpu.txt
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Speed (AM Driver)
|
||||
@@ -755,7 +755,7 @@ jobs:
|
||||
run: BENCHMARK_LOG=resnet_10steps NV=1 MNISTMOCK=1 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=256 GPUS=1 MODEL=resnet python3 examples/mlperf/model_train.py | tee nv_train_resnet_one_gpu.txt
|
||||
- name: Run 10 MLPerf Bert training steps (1 gpu)
|
||||
# TODO: remove BERT_LAYERS once scheduler is fast
|
||||
run: BENCHMARK_LOG=bert_10steps NV=1 CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=66 GPUS=1 BERT_LAYERS=2 FUSE_ARANGE=1 FUSE_ARANGE_UINT=0 MODEL=bert python3 examples/mlperf/model_train.py | tee nv_train_bert_one_gpu.txt
|
||||
run: BENCHMARK_LOG=bert_10steps NV=1 CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=66 GPUS=1 BERT_LAYERS=2 MODEL=bert python3 examples/mlperf/model_train.py | tee nv_train_bert_one_gpu.txt
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Speed (NV Driver)
|
||||
|
||||
@@ -542,6 +542,8 @@ jobs:
|
||||
run: PYTHONPATH="." GPU=1 IGNORE_BEAM_CACHE=1 python3 -m pytest extra/optimization/test_beam_search.py
|
||||
- name: Test MLPerf stuff
|
||||
run: GPU=1 python -m pytest -n=auto test/external/external_test_optim.py test/external/external_test_losses.py test/external/external_test_metrics.py test/external/external_test_datasets.py --durations=20
|
||||
- name: Test llama 3 training
|
||||
run: MAX_BUFFER_SIZE=0 PYTHONPATH="." DEV=NULL SAMPLES=300 BS=8 SEQLEN=512 GRADIENT_ACC_STEPS=8 FAKEDATA=1 DEFAULT_FLOAT=bfloat16 OPTIM_DTYPE=bfloat16 LLAMA3_SIZE=1B MODEL=llama3 python3 examples/mlperf/model_train.py
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
@@ -975,6 +977,7 @@ jobs:
|
||||
with:
|
||||
key: macos-${{ matrix.backend }}-minimal
|
||||
deps: testing_minimal
|
||||
pydeps: "capstone"
|
||||
llvm: ${{ matrix.backend == 'llvm' && 'true' }}
|
||||
- name: Set env
|
||||
run: printf "${{ matrix.backend == 'llvm' && 'LLVM=1' || matrix.backend == 'cpu' && 'CPU=1' || matrix.backend == 'metal' && 'METAL=1'}}" >> $GITHUB_ENV
|
||||
|
||||
@@ -18,11 +18,11 @@ Group UOps into kernels.
|
||||
|
||||
---
|
||||
|
||||
## tinygrad/opt
|
||||
## tinygrad/codegen/opt
|
||||
|
||||
Transforms the ast into an optimized ast. This is where BEAM search and heuristics live.
|
||||
|
||||
::: tinygrad.opt.get_optimized_ast
|
||||
::: tinygrad.codegen.opt.get_optimized_ast
|
||||
options:
|
||||
members: false
|
||||
show_labels: false
|
||||
|
||||
+3
-3
@@ -47,8 +47,8 @@ Reboot after making these changes or restart the `displayservice.service` servic
|
||||
|
||||
The [default tinybox image](https://github.com/tinygrad/tinyos) ships with tinygrad and PyTorch. While we develop tinygrad, the box is universal hardware. Use whatever framework you desire, run notebooks, download demos, install more things, train, inference, live, laugh, love, you aren't paying per hour for this box so the only limit is your imagination.
|
||||
|
||||
## tinychat
|
||||
## Building the OS image
|
||||
|
||||
Since LLMs are so popular, we ship with a built in tinygrad based chatbot using a LLaMA-3 finetune. Visit the IP (not the BMC IP) of your tinybox in a web browser on your computer or phone, and you'll find a friendly looking chat interface. This chatbot also provides an OpenAI compatible LLM API on that port, so you can script it.
|
||||
The OS image is built using `ubuntu-image` from <https://github.com/tinygrad/tinyos>.
|
||||
|
||||
The conversations you have with this chatbot are between you and your tinybox. Also, the history in the web app is saved on the client, not the tinybox.
|
||||
After cloning, run `make green` or `make red` to build a tinybox green or tinybox red image respectively.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# model based off https://medium.com/data-science/going-beyond-99-mnist-handwritten-digits-recognition-cfff96337392
|
||||
from typing import List, Callable
|
||||
from typing import Callable
|
||||
from tinygrad import Tensor, TinyJit, nn, GlobalCounters
|
||||
from tinygrad.helpers import getenv, colored, trange
|
||||
from tinygrad.nn.datasets import mnist
|
||||
|
||||
class Model:
|
||||
def __init__(self):
|
||||
self.layers: List[Callable[[Tensor], Tensor]] = [
|
||||
self.layers: list[Callable[[Tensor], Tensor]] = [
|
||||
nn.Conv2d(1, 32, 5), Tensor.relu,
|
||||
nn.Conv2d(32, 32, 5), Tensor.relu,
|
||||
nn.BatchNorm(32), Tensor.max_pool2d,
|
||||
@@ -28,7 +28,6 @@ if __name__ == "__main__":
|
||||
def train_step() -> Tensor:
|
||||
opt.zero_grad()
|
||||
samples = Tensor.randint(getenv("BS", 512), high=X_train.shape[0])
|
||||
# TODO: this "gather" of samples is very slow. will be under 5s when this is fixed
|
||||
loss = model(X_train[samples]).sparse_categorical_crossentropy(Y_train[samples]).backward()
|
||||
opt.step()
|
||||
return loss
|
||||
|
||||
@@ -1323,24 +1323,22 @@ def train_llama3():
|
||||
for v in get_parameters(model):
|
||||
v.shard_(device, axis=None)
|
||||
|
||||
# TODO: MP
|
||||
# if (GPUS := getenv("GPUS", 1)) > 1:
|
||||
# device = tuple(f"{Device.DEFAULT}:{i}" for i in range(GPUS))
|
||||
# for k,v in get_state_dict(model).items():
|
||||
# if 'scale' in k: v.shard_(device, axis=None) # from quantized
|
||||
# # elif '.attention.wq' in k: v.shard_(device, axis=0)
|
||||
# # elif '.attention.wk' in k: v.shard_(device, axis=0)
|
||||
# # elif '.attention.wv' in k: v.shard_(device, axis=0)
|
||||
# # elif '.attention.wo' in k: v.shard_(device, axis=1)
|
||||
# # elif '.feed_forward.w1.' in k: v.shard_(device, axis=0)
|
||||
# # elif '.feed_forward.w2.' in k: v.shard_(device, axis=1)
|
||||
# # elif '.feed_forward.w3.' in k: v.shard_(device, axis=0)
|
||||
# # elif 'tok_embeddings.weight' in k: v.shard_(device, axis=0)
|
||||
# elif 'output.weight' in k: v.shard_(device, axis=0) # 243.32
|
||||
# else:
|
||||
# # print(k)
|
||||
# # attention_norm, ffn_norm, norm
|
||||
# v.shard_(device, axis=None)
|
||||
if (MP := getenv("MP", 1)) > 1:
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(MP))
|
||||
for k,v in get_state_dict(model).items():
|
||||
if 'scale' in k: v.shard_(device, axis=None) # from quantized
|
||||
elif '.attention.wq' in k: v.shard_(device, axis=0)
|
||||
elif '.attention.wk' in k: v.shard_(device, axis=0)
|
||||
elif '.attention.wv' in k: v.shard_(device, axis=0)
|
||||
elif '.attention.wo' in k: v.shard_(device, axis=1)
|
||||
elif '.feed_forward.w1.' in k: v.shard_(device, axis=0)
|
||||
elif '.feed_forward.w2.' in k: v.shard_(device, axis=1)
|
||||
elif '.feed_forward.w3.' in k: v.shard_(device, axis=0)
|
||||
elif 'tok_embeddings.weight' in k: v.shard_(device, axis=0)
|
||||
elif 'output.weight' in k: v.shard_(device, axis=0)
|
||||
else:
|
||||
# attention_norm, ffn_norm, norm
|
||||
v.shard_(device, axis=None)
|
||||
|
||||
optim = AdamW(get_parameters(model), lr=0.0,
|
||||
b1=opt_adamw_beta_1, b2=opt_adamw_beta_2, eps=opt_adamw_epsilon, weight_decay=opt_adamw_weight_decay)
|
||||
@@ -1355,6 +1353,9 @@ def train_llama3():
|
||||
if (DP := getenv("DP", 1)) > 1:
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(DP))
|
||||
batch = batch.shard(device, 0)
|
||||
if (MP := getenv("MP", 1)) > 1:
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(MP))
|
||||
batch = batch.shard(device)
|
||||
logits:Tensor = model(batch[:, :-1], start_pos=0, temperature=math.nan)
|
||||
loss = logits.sparse_categorical_crossentropy(batch[:, 1:])
|
||||
loss.backward()
|
||||
@@ -1391,11 +1392,12 @@ def train_llama3():
|
||||
t = time.perf_counter()
|
||||
GlobalCounters.reset()
|
||||
loss, lr = train_step(model, tokens, grad_acc)
|
||||
loss = loss.float().item()
|
||||
# above as tqdm.write f-string
|
||||
tqdm.write(f"{loss.item():.4f} loss, {lr.item():.12f} LR, {GlobalCounters.mem_used / 1e9:.2f} GB used, {time.perf_counter()-t:.2f} s")
|
||||
tqdm.write(f"{loss:.4f} loss, {lr.item():.12f} LR, {GlobalCounters.mem_used / 1e9:.2f} GB used, {time.perf_counter()-t:.2f} s")
|
||||
if (fname:=getenv("LOSS_FILE", "")):
|
||||
with open(fname, "a") as f:
|
||||
f.write(f"{i} {loss.item():.4f} {lr.item():.12f} {GlobalCounters.mem_used / 1e9:.2f}\n")
|
||||
f.write(f"{i} {loss:.4f} {lr.item():.12f} {GlobalCounters.mem_used / 1e9:.2f}\n")
|
||||
|
||||
if getenv("CKPT") and (i % 200 == 0 or i == 10):
|
||||
tqdm.write("saving checkpoint")
|
||||
|
||||
+2
@@ -4,6 +4,8 @@ export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=1 BS=128 EVAL_BS=128
|
||||
|
||||
export IGNORE_OOB=1
|
||||
|
||||
export BEAM=3 BEAM_UOPS_MAX=4000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
|
||||
export IGNORE_JIT_FIRST_BEAM=1
|
||||
# export BEAM_LOG_SURPASS_MAX=1
|
||||
|
||||
+2
@@ -5,6 +5,8 @@ export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" GPUS=8 BS=1024 EVAL_BS=1024
|
||||
export OPT_BASE_LEARNING_RATE=0.0011 OPT_LAMB_BETA_1=0.60466 OPT_LAMB_BETA_2=0.85437 DECAY=0.1
|
||||
|
||||
export IGNORE_OOB=1
|
||||
|
||||
export BEAM=3 BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
|
||||
export IGNORE_JIT_FIRST_BEAM=1 FREE_INTERMEDIATE=0
|
||||
export BASEDIR="/raid/datasets/wiki"
|
||||
|
||||
+2
@@ -8,6 +8,8 @@ export DEFAULT_FLOAT="HALF" GPUS=8 BS=1024 EVAL_BS=1024
|
||||
export OPT_BASE_LEARNING_RATE=0.0011 OPT_LAMB_BETA_1=0.60466 OPT_LAMB_BETA_2=0.85437 DECAY=0.1
|
||||
export TRAIN_STEPS=3900
|
||||
|
||||
export IGNORE_OOB=1
|
||||
|
||||
export BEAM=3 BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
|
||||
export IGNORE_JIT_FIRST_BEAM=1 FREE_INTERMEDIATE=0
|
||||
export BASEDIR="/raid/datasets/wiki"
|
||||
|
||||
+2
@@ -11,6 +11,8 @@ export DEFAULT_FLOAT="HALF" GPUS=8 BS=1024 EVAL_BS=1024
|
||||
export OPT_BASE_LEARNING_RATE=0.0011 OPT_LAMB_BETA_1=0.60466 OPT_LAMB_BETA_2=0.85437 DECAY=0.1
|
||||
export TRAIN_STEPS=3900
|
||||
|
||||
export IGNORE_OOB=1
|
||||
|
||||
export BEAM=3 BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
|
||||
export IGNORE_JIT_FIRST_BEAM=1 FREE_INTERMEDIATE=0
|
||||
export BASEDIR="/raid/datasets/wiki"
|
||||
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=90 EVAL_BS=90
|
||||
|
||||
export FUSE_ARANGE=1 FUSE_ARANGE_UINT=0
|
||||
export IGNORE_OOB=1
|
||||
|
||||
export BEAM=8 BEAM_UOPS_MAX=10000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
|
||||
export IGNORE_JIT_FIRST_BEAM=1
|
||||
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=90 EVAL_BS=90
|
||||
|
||||
export FUSE_ARANGE=1 FUSE_ARANGE_UINT=0
|
||||
export IGNORE_OOB=1
|
||||
|
||||
export BEAM=8 BEAM_UOPS_MAX=10000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
|
||||
export IGNORE_JIT_FIRST_BEAM=1
|
||||
|
||||
+2
-2
@@ -5,9 +5,9 @@ set -o pipefail # Make pipeline fail if any command fails
|
||||
export PYTHONPATH="." NV=1
|
||||
export MODEL="bert"
|
||||
export SUBMISSION_PLATFORM="tinybox_green"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=90 EVAL_BS=90
|
||||
|
||||
export FUSE_ARANGE=1 FUSE_ARANGE_UINT=0
|
||||
export IGNORE_OOB=1
|
||||
|
||||
export BEAM=8 BEAM_UOPS_MAX=10000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
|
||||
export IGNORE_JIT_FIRST_BEAM=1
|
||||
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=90 EVAL_BS=90
|
||||
|
||||
export FUSE_ARANGE=1 FUSE_ARANGE_UINT=0
|
||||
export IGNORE_OOB=1
|
||||
|
||||
export BEAM=5 BEAM_UOPS_MAX=8000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
|
||||
export IGNORE_JIT_FIRST_BEAM=1
|
||||
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=90 EVAL_BS=90
|
||||
|
||||
export FUSE_ARANGE=1 FUSE_ARANGE_UINT=0
|
||||
export IGNORE_OOB=1
|
||||
|
||||
export BEAM=5 BEAM_UOPS_MAX=8000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
|
||||
export IGNORE_JIT_FIRST_BEAM=1
|
||||
|
||||
+2
-2
@@ -5,9 +5,9 @@ set -o pipefail # Make pipeline fail if any command fails
|
||||
export PYTHONPATH="." AMD=1
|
||||
export MODEL="bert"
|
||||
export SUBMISSION_PLATFORM="tinybox_red"
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=96 EVAL_BS=96
|
||||
export DEFAULT_FLOAT="HALF" SUM_DTYPE="HALF" GPUS=6 BS=90 EVAL_BS=90
|
||||
|
||||
export FUSE_ARANGE=1 FUSE_ARANGE_UINT=0
|
||||
export IGNORE_OOB=1
|
||||
|
||||
export BEAM=5 BEAM_UOPS_MAX=8000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5
|
||||
export IGNORE_JIT_FIRST_BEAM=1
|
||||
|
||||
+3
-4
@@ -1,8 +1,7 @@
|
||||
# https://arxiv.org/pdf/2409.02060
|
||||
import time
|
||||
import time, functools
|
||||
import numpy as np
|
||||
np.set_printoptions(suppress=True, linewidth=1000)
|
||||
import functools
|
||||
from tinygrad import Tensor, nn, Device, GlobalCounters
|
||||
from tinygrad.helpers import Timing, getenv
|
||||
from extra.models.llama import Transformer, convert_from_huggingface
|
||||
@@ -17,7 +16,7 @@ class MixtureFeedForward:
|
||||
def __call__(self, x:Tensor) -> Tensor:
|
||||
assert x.shape[0] == 1, "only BS=1"
|
||||
assert x.shape[1] == 1, "only length=1"
|
||||
g = self.gate(x).float().softmax(-1)
|
||||
g = self.gate(x).softmax(-1)
|
||||
|
||||
g = g.squeeze() # (BS, length, num_experts) -> (num_experts,)
|
||||
probs, sel = g.topk(self.activated_experts)
|
||||
@@ -25,7 +24,7 @@ class MixtureFeedForward:
|
||||
# run MoE
|
||||
x_up_gate = x.dot(self.gate_proj[sel].permute(0,2,1)).silu() * x.dot(self.up_proj[sel].permute(0,2,1))
|
||||
x_down = x_up_gate.dot(self.down_proj[sel].permute(0,2,1))
|
||||
return (x_down.float() * probs.reshape(self.activated_experts, 1, 1)).sum(axis=0)
|
||||
return (x_down * probs.reshape(self.activated_experts, 1, 1)).sum(axis=0)
|
||||
|
||||
# model is bf16, 1.3B active, 6.9B total
|
||||
# M3 Max is 400 GB/s, so 400/2.6 = ~154 tok/s
|
||||
|
||||
+9
-9
@@ -71,8 +71,8 @@ def bbox_iou(box1, box2):
|
||||
# get the coordinates of the intersection rectangle
|
||||
inter_rect_x1 = np.maximum(b1_x1, b2_x1)
|
||||
inter_rect_y1 = np.maximum(b1_y1, b2_y1)
|
||||
inter_rect_x2 = np.maximum(b1_x2, b2_x2)
|
||||
inter_rect_y2 = np.maximum(b1_y2, b2_y2)
|
||||
inter_rect_x2 = np.minimum(b1_x2, b2_x2)
|
||||
inter_rect_y2 = np.minimum(b1_y2, b2_y2)
|
||||
#Intersection area
|
||||
inter_area = np.clip(inter_rect_x2 - inter_rect_x1 + 1, 0, 99999) * np.clip(inter_rect_y2 - inter_rect_y1 + 1, 0, 99999)
|
||||
#Union Area
|
||||
@@ -297,13 +297,13 @@ class Darknet:
|
||||
# Get the number of weights of batchnorm
|
||||
num_bn_biases = math.prod(bn.bias.shape)
|
||||
# Load weights
|
||||
bn_biases = Tensor(weights[ptr:ptr + num_bn_biases])
|
||||
bn_biases = Tensor(weights[ptr:ptr + num_bn_biases].astype(np.float32))
|
||||
ptr += num_bn_biases
|
||||
bn_weights = Tensor(weights[ptr:ptr+num_bn_biases])
|
||||
bn_weights = Tensor(weights[ptr:ptr+num_bn_biases].astype(np.float32))
|
||||
ptr += num_bn_biases
|
||||
bn_running_mean = Tensor(weights[ptr:ptr+num_bn_biases])
|
||||
bn_running_mean = Tensor(weights[ptr:ptr+num_bn_biases].astype(np.float32))
|
||||
ptr += num_bn_biases
|
||||
bn_running_var = Tensor(weights[ptr:ptr+num_bn_biases])
|
||||
bn_running_var = Tensor(weights[ptr:ptr+num_bn_biases].astype(np.float32))
|
||||
ptr += num_bn_biases
|
||||
# Cast the loaded weights into dims of model weights
|
||||
bn_biases = bn_biases.reshape(shape=tuple(bn.bias.shape))
|
||||
@@ -319,7 +319,7 @@ class Darknet:
|
||||
# load biases of the conv layer
|
||||
num_biases = math.prod(conv.bias.shape)
|
||||
# Load weights
|
||||
conv_biases = Tensor(weights[ptr: ptr+num_biases])
|
||||
conv_biases = Tensor(weights[ptr: ptr+num_biases].astype(np.float32))
|
||||
ptr += num_biases
|
||||
# Reshape
|
||||
conv_biases = conv_biases.reshape(shape=tuple(conv.bias.shape))
|
||||
@@ -327,7 +327,7 @@ class Darknet:
|
||||
conv.bias = conv_biases
|
||||
# Load weighys for conv layers
|
||||
num_weights = math.prod(conv.weight.shape)
|
||||
conv_weights = Tensor(weights[ptr:ptr+num_weights])
|
||||
conv_weights = Tensor(weights[ptr:ptr+num_weights].astype(np.float32))
|
||||
ptr += num_weights
|
||||
conv_weights = conv_weights.reshape(shape=tuple(conv.weight.shape))
|
||||
conv.weight = conv_weights
|
||||
@@ -371,7 +371,7 @@ class Darknet:
|
||||
if __name__ == "__main__":
|
||||
model = Darknet(fetch('https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/yolov3.cfg').read_bytes())
|
||||
print("Loading weights file (237MB). This might take a while…")
|
||||
model.load_weights('https://pjreddie.com/media/files/yolov3.weights')
|
||||
model.load_weights('https://github.com/shadiakiki1986/yolov3.weights/releases/download/3.0.1/yolov3.weights')
|
||||
if len(sys.argv) > 1:
|
||||
url = sys.argv[1]
|
||||
else:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from typing import Tuple, List, NamedTuple, Any, Dict, Optional, Union, DefaultDict, cast
|
||||
from tinygrad.opt.kernel import Ops, MemOp, UOp
|
||||
from tinygrad.codegen.opt.kernel import Ops, MemOp, UOp
|
||||
from tinygrad.uop.ops import BinaryOps, UnaryOps
|
||||
from tinygrad.dtype import DType, dtypes
|
||||
from tinygrad.helpers import DEBUG
|
||||
|
||||
@@ -3,7 +3,7 @@ from platform import system
|
||||
from typing import Tuple, Dict, List, Optional
|
||||
from tinygrad import dtypes
|
||||
from tinygrad.uop.ops import BinaryOps, UnaryOps, TernaryOps
|
||||
from tinygrad.opt.kernel import Ops, UOp
|
||||
from tinygrad.codegen.opt.kernel import Ops, UOp
|
||||
from tinygrad.helpers import CI
|
||||
from tinygrad.codegen.assembly import uops_to_asmstyle, AssemblyLanguage
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import List
|
||||
import struct
|
||||
from tinygrad.codegen.assembly import uops_to_asmstyle, AssemblyLanguage
|
||||
from tinygrad.opt.kernel import Ops, UOp
|
||||
from tinygrad.codegen.opt.kernel import Ops, UOp
|
||||
from tinygrad import dtypes
|
||||
from tinygrad.uop.ops import BinaryOps, UnaryOps, TernaryOps
|
||||
from tinygrad.runtime.ops_cuda import arch
|
||||
|
||||
@@ -2,7 +2,7 @@ import yaml
|
||||
from typing import Tuple, Set, Dict
|
||||
from tinygrad import dtypes
|
||||
from tinygrad.codegen.assembly import AssemblyCodegen, Register
|
||||
from tinygrad.opt.kernel import Ops
|
||||
from tinygrad.codegen.opt.kernel import Ops
|
||||
from tinygrad.uop.ops import BinaryOps, UnaryOps, TernaryOps
|
||||
from tinygrad.runtime.ops_gpu import ROCM_LLVM_PATH
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import Dict, List, Final, Callable, DefaultDict
|
||||
from collections import defaultdict
|
||||
from tinygrad.uop.ops import UnaryOps, BinaryOps, TernaryOps, Op
|
||||
from tinygrad.helpers import DType, PtrDType, dtypes, ImageDType, DEBUG, getenv
|
||||
from tinygrad.opt.kernel import UOp, Ops
|
||||
from tinygrad.codegen.opt.kernel import UOp, Ops
|
||||
from triton.compiler import compile as triton_compile
|
||||
import linecache
|
||||
import math
|
||||
|
||||
@@ -2,11 +2,11 @@ from tinygrad import Tensor, Device, Context, GlobalCounters, dtypes
|
||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo, graph_rewrite, AxisType, PatternMatcher, UPat
|
||||
from tinygrad.engine.realize import CompiledRunner, ExecItem, get_program
|
||||
from tinygrad.dtype import AddrSpace
|
||||
from tinygrad.schedule.kernelize import merge_views, view_left
|
||||
from tinygrad.helpers import getenv, colored, prod, unwrap
|
||||
from tinygrad.shape.shapetracker import ShapeTracker, View
|
||||
from tinygrad.shape.view import strides_for_shape
|
||||
from tinygrad.opt.kernel import axis_colors
|
||||
from tinygrad.codegen.opt.kernel import axis_colors
|
||||
from tinygrad.codegen.opt.swizzler import merge_views, view_left
|
||||
|
||||
def to_colored(full_shape, axis_types): return '_'.join([colored(str(s), axis_colors[at]) for s,at in zip(full_shape, axis_types)])
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ from typing import Optional, List, Tuple, cast, Dict, Final, DefaultDict, Self
|
||||
from tinygrad.engine.realize import get_program
|
||||
|
||||
# for copied uops
|
||||
from tinygrad.opt.kernel import Kernel, KernelOptError
|
||||
from tinygrad.codegen.opt.kernel import Kernel, KernelOptError
|
||||
from tinygrad.uop.ops import UOp, Ops, BinaryOps, UnaryOps, TernaryOps, KernelInfo
|
||||
from tinygrad.opt.search import Opt, OptOps
|
||||
from tinygrad.codegen.opt.search import Opt, OptOps
|
||||
from tinygrad import Device, dtypes, Tensor
|
||||
from tinygrad.dtype import PtrDType, DType, DTYPES_DICT
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
|
||||
@@ -2,7 +2,7 @@ import numpy as np
|
||||
from tinygrad import dtypes, Tensor
|
||||
from tinygrad.helpers import getenv, get_single_element
|
||||
from tinygrad.dtype import _to_np_dtype
|
||||
from tinygrad.opt.kernel import OptOps
|
||||
from tinygrad.codegen.opt.kernel import OptOps
|
||||
from tinygrad.engine.realize import lower_schedule
|
||||
|
||||
dtype_in = dtypes.half if getenv("HALF") else dtypes.bfloat16 if getenv("BFLOAT16") else dtypes.float
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from tinygrad import Tensor, dtypes, Device
|
||||
from tinygrad.helpers import getenv, DEBUG
|
||||
from tinygrad.opt.kernel import Kernel, Opt, OptOps
|
||||
from tinygrad.codegen.opt.kernel import Kernel, Opt, OptOps
|
||||
from tinygrad.engine.realize import CompiledRunner, ExecItem, get_program
|
||||
from dataclasses import replace
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ B = Tensor.rand(K, N, device="CPU")
|
||||
C = (A.reshape(M, 1, K) * B.permute(1,0).reshape(1, N, K)).sum(axis=2)
|
||||
|
||||
sched = C.schedule()
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.device import CompilerOptions
|
||||
lin = Kernel(sched[-1].ast, CompilerOptions(has_local=False, supports_float4=False))
|
||||
lin.to_program()
|
||||
|
||||
@@ -8,7 +8,6 @@ bert_train_params = {
|
||||
"BS": 96,
|
||||
"EVAL_BS": 96,
|
||||
"FUSE_ARANGE": 1,
|
||||
"FUSE_ARANGE_UINT": 0,
|
||||
"BASEDIR": "/raid/datasets/wiki",
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import numpy as np
|
||||
np.set_printoptions(suppress=True)
|
||||
import math, functools, time, random, statistics
|
||||
from tinygrad.helpers import DEBUG, getenv, CACHELEVEL, diskcache_get, diskcache_put, colored, Profiling
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.device import Buffer, Device, CompileError
|
||||
from tinygrad.opt.search import _ensure_buffer_alloc, get_kernel_actions, _time_program
|
||||
from tinygrad.codegen.opt.search import _ensure_buffer_alloc, get_kernel_actions, _time_program
|
||||
from tinygrad.engine.realize import get_program
|
||||
|
||||
class MCTSNode:
|
||||
|
||||
@@ -181,13 +181,11 @@ class Transformer:
|
||||
def forward(self, tokens:Tensor, start_pos:Union[Variable,int], temperature:float, top_k:int, top_p:float, alpha_f:float, alpha_p:float):
|
||||
_bsz, seqlen = tokens.shape
|
||||
h = self.tok_embeddings(tokens)
|
||||
|
||||
self.freqs_cis = self.freqs_cis.cast(h.dtype).contiguous()
|
||||
freqs_cis = self.freqs_cis[:, start_pos:start_pos+seqlen, :, :, :]
|
||||
freqs_cis = self.freqs_cis.cast(h.dtype)[:, start_pos:start_pos+seqlen, :, :, :]
|
||||
|
||||
mask = Tensor.full((1, 1, seqlen, start_pos+seqlen), float("-inf"), dtype=h.dtype, device=h.device).triu(start_pos+1) if seqlen > 1 else None
|
||||
for layer in self.layers: h = layer(h, start_pos, freqs_cis, mask)
|
||||
logits = self.output(self.norm(h)).float()
|
||||
logits = self.output(self.norm(h))
|
||||
if math.isnan(temperature): return logits
|
||||
|
||||
return sample(logits[:, -1, :].flatten(), temperature, top_k, top_p, alpha_f, alpha_p)
|
||||
|
||||
+430
-331
@@ -1,290 +1,48 @@
|
||||
# mypy: disable-error-code="misc, list-item, assignment, operator, index, arg-type"
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, Sequence, cast, Literal, NamedTuple, Generator, get_args
|
||||
import dataclasses, functools, io, math, types, warnings, pathlib, sys, os, struct, enum
|
||||
from io import BufferedReader
|
||||
from typing import Any, Sequence, cast, Literal, Callable, get_args, NamedTuple
|
||||
import dataclasses, functools, io, math, types, warnings, pathlib, sys, enum, os, struct
|
||||
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
|
||||
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.device import is_dtype_supported, Device
|
||||
|
||||
# Protobuf Wire Types
|
||||
WIRETYPE_VARINT = 0; WIRETYPE_FIXED64 = 1; WIRETYPE_LENGTH_DELIMITED = 2; WIRETYPE_START_GROUP = 3; WIRETYPE_END_GROUP = 4; WIRETYPE_FIXED32 = 5 # noqa: E702
|
||||
# ***** protobuf definitions ******
|
||||
class WireType(enum.IntEnum):
|
||||
"""
|
||||
Protocol Buffer wire types for decoding fields.
|
||||
Reference: https://github.com/protocolbuffers/protobuf/blob/main/python/google/protobuf/internal/wire_format.py#L24-L29
|
||||
"""
|
||||
VARINT = 0; FIXED64 = 1; LENGTH_DELIMITED = 2; START_GROUP = 3; END_GROUP = 4; FIXED32 = 5 # noqa: E702
|
||||
|
||||
# TensorProto.DataType
|
||||
class TensorDataType:
|
||||
UNDEFINED = 0; FLOAT = 1; UINT8 = 2; INT8 = 3; UINT16 = 4; INT16 = 5; INT32 = 6; INT64 = 7 # noqa: E702
|
||||
STRING = 8; BOOL = 9; FLOAT16 = 10; DOUBLE = 11; UINT32 = 12; UINT64 = 13; COMPLEX64 = 14; COMPLEX128 = 15; BFLOAT16 = 16 # noqa: E702
|
||||
class AttributeType(enum.IntEnum):
|
||||
"""
|
||||
ONNX attribute type identifiers.
|
||||
Reference: https://github.com/onnx/onnx/blob/rel-1.18.0/onnx/onnx.proto3#L128-L145
|
||||
"""
|
||||
FLOAT = 1; INT = 2; STRING = 3; TENSOR = 4; FLOATS = 6; INTS = 7; STRINGS = 8 # noqa: E702
|
||||
|
||||
# AttributeProto.AttributeType
|
||||
class AttributeType:
|
||||
UNDEFINED = 0; FLOAT = 1; INT = 2; STRING = 3; TENSOR = 4; GRAPH = 5; SPARSE_TENSOR = 11; TYPE_PROTO = 13; FLOATS = 6; INTS = 7 # noqa: E702
|
||||
STRINGS = 8; TENSORS = 9; GRAPHS = 10; SPARSE_TENSORS = 12; TYPE_PROTOS = 14 # noqa: E702
|
||||
def to_field_name(self) -> str: return {1: "f", 2: "i", 3: "s", 4: "t", 6: "floats", 7: "ints", 8: "strings"}[self.value]
|
||||
|
||||
class PBType: FLOAT = 1; INT = 2; STRING = 3; FLOATS = 4; INTS = 5; STRINGS = 6; BYTES = 7; SUB = 8 # noqa: E702
|
||||
class OnnxDataType(enum.IntEnum):
|
||||
"""
|
||||
ONNX tensor data type identifiers.
|
||||
Reference: https://github.com/onnx/onnx/blob/rel-1.18.0/onnx/onnx.proto3#L500-L544
|
||||
"""
|
||||
FLOAT = 1; UINT8 = 2; INT8 = 3; UINT16 = 4; INT16 = 5; INT32 = 6; INT64 = 7; BOOL = 9; FLOAT16 = 10; DOUBLE = 11; UINT32 = 12 # noqa: E702
|
||||
UINT64 = 13; BFLOAT16 = 16 # noqa: E702
|
||||
|
||||
PB_INFOS: dict[str, dict] = {
|
||||
"OperatorSetIdProto": {1: ("domain", PBType.STRING), 2: ("version", PBType.INT)},
|
||||
"StringStringEntryProto": {1: ("key", PBType.STRING), 2: ("value", PBType.STRING)},
|
||||
"TensorProto": {1: ("dims", PBType.INT, True), 2: ("data_type", PBType.INT), 4: ("float_data", PBType.FLOATS),
|
||||
13: ("external_data", PBType.SUB, True, "StringStringEntryProto"), 14: ("data_location", PBType.INT),
|
||||
5: ("int32_data", PBType.INTS), 7: ("int64_data", PBType.INTS), 8: ("name", PBType.STRING), 9: ("raw_data", PBType.BYTES),
|
||||
10: ("double_data", PBType.FLOATS), 11: ("uint64_data", PBType.INTS)},
|
||||
"TensorShapeProtoDimension": {1: ("dim_value", PBType.INT), 2: ("dim_param", PBType.STRING)},
|
||||
"TensorShapeProto": {1: ("dim", PBType.SUB, True, "TensorShapeProtoDimension")},
|
||||
"ModelProto": {1: ("ir_version", PBType.INT), 5: ("model_version", PBType.INT),
|
||||
2: ("producer_name", PBType.STRING), 3: ("producer_version", PBType.STRING), 4: ("domain", PBType.STRING), 6: ("doc_string", PBType.STRING),
|
||||
7: ("graph", PBType.SUB, False, ("GraphProto", lambda: {"node": [], "initializer": [], "input": [], "output": [], "value_info": []})),
|
||||
8: ("opset_import",PBType.SUB, True, "OperatorSetIdProto")},
|
||||
"GraphProto": {2: ("name", PBType.STRING), 10: ("doc_string", PBType.STRING),
|
||||
1: ("node", PBType.SUB, True, ("NodeProto", lambda: {"input": [], "output": [], "attribute": [], "domain": None})),
|
||||
5: ("initializer", PBType.SUB, True, ("TensorProto", lambda: {"dims": [], "float_data": None, "int32_data": None, "string_data": None,
|
||||
"int64_data": None, "double_data": None, "uint64_data": None, "raw_data": None})),
|
||||
11: ("input", PBType.SUB, True, "ValueInfoProto"), 12: ("output", PBType.SUB, True, "ValueInfoProto")},
|
||||
"NodeProto": { 1: ("input", PBType.STRING, True), 2: ("output", PBType.STRING, True), 3: ("name", PBType.STRING),
|
||||
4: ("op_type", PBType.STRING), 6: ("doc_string", PBType.STRING), 7: ("domain", PBType.STRING),
|
||||
5: ("attribute", PBType.SUB, True, ("AttributeProto", lambda: {"floats": [], "ints": [], "strings": []}))},
|
||||
"AttributeProto": {1: ("name", PBType.STRING), 20: ("type", PBType.INT), 3: ("i", PBType.INT), 8: ("ints", PBType.INT, True),
|
||||
2: ("f", PBType.FLOAT), 7: ("floats", PBType.FLOAT, True), 4: ("s", PBType.BYTES), 9: ("strings", PBType.BYTES, True),
|
||||
5:("t", PBType.SUB, False, ("TensorProto", lambda: {"dims": [], "float_data": None, "int32_data": None, "string_data": None, "int64_data": None,
|
||||
"double_data": None, "uint64_data": None, "raw_data": None}))},
|
||||
"ValueInfoProto": {1: ("name", PBType.STRING), 2: ("type", PBType.SUB, False, "TypeProto"), 3: ("doc_string", PBType.STRING)},
|
||||
"TypeProto": {1: ("tensor_type", PBType.SUB, False, "TypeProtoTensor"), 4: ("sequence_type", PBType.SUB, False, "TypeProtoSequence"),
|
||||
9: ("optional_type", PBType.SUB, False, "TypeProtoOptional"), 6: ("denotation", PBType.STRING)},
|
||||
"TypeProtoSequence": {1: ("elem_type", PBType.SUB, False, "TypeProto")},
|
||||
"TypeProtoOptional": {1: ("elem_type", PBType.SUB, False, "TypeProto")},
|
||||
"TypeProtoTensor": {1: ("elem_type", PBType.INT), 2: ("shape", PBType.SUB, False, ("TensorShapeProto", lambda: {"dim": []}))},
|
||||
}
|
||||
def to_dtype(self) -> DType: return dtypes.fields()[self.name.lower()]
|
||||
|
||||
def onnx_load(fn: Tensor|str|pathlib.Path, load_external_data: bool=True):
|
||||
parser = OnnxParser(fn, load_external_data)
|
||||
onnx_model = parser.parse()
|
||||
model = dict_to_namespace(onnx_model)
|
||||
return model
|
||||
|
||||
def gen_result(obj: dict, key_name, val, repeated: bool):
|
||||
if repeated: obj.setdefault(key_name, []).append(val)
|
||||
else: obj[key_name] = val
|
||||
|
||||
def dict_to_namespace(d):
|
||||
if isinstance(d, dict): return SimpleNamespace(**{k: dict_to_namespace(v) for k, v in d.items()})
|
||||
elif isinstance(d, list): return [dict_to_namespace(i) for i in d]
|
||||
return d
|
||||
|
||||
class OnnxParser:
|
||||
def __init__(self, inp: Tensor|str|pathlib.Path, load_external_data: bool=True):
|
||||
self.file_path: pathlib.Path|None = None
|
||||
self.load_external_data = load_external_data
|
||||
if not isinstance(inp, Tensor):
|
||||
self.file_path = pathlib.Path(inp)
|
||||
self.tensor = Tensor(self.file_path)
|
||||
else: self.tensor = inp
|
||||
self.attr_func_dict = { PBType.BYTES: self._handle_bytes, PBType.SUB: self._handle_sub_message, PBType.FLOATS: self._handle_packed_floats,
|
||||
PBType.INT: self._handle_int64, PBType.INTS: self._handle_packed_int64s, PBType.STRING: self._handle_string, PBType.FLOAT: self._handle_float}
|
||||
self.registered_handles = {}
|
||||
for pb_name in PB_INFOS:
|
||||
res = {}
|
||||
for fid, config in PB_INFOS[pb_name].items():
|
||||
parser_fn, repeated = None, False
|
||||
if len(config) == 2: name, attr = config
|
||||
elif len(config) == 3: name, attr, repeated = config
|
||||
elif len(config) == 4: name, attr, repeated, parser_fn = config
|
||||
handler_fn = self.attr_func_dict[attr]
|
||||
def _wrapper_handler(obj, reader, wt, h=handler_fn, n=name, p=parser_fn, r=repeated): return h(obj, n, reader, wt, parser_func=p, repeated=r)
|
||||
res[fid] = _wrapper_handler
|
||||
self.registered_handles[pb_name] = res
|
||||
|
||||
def parse(self):
|
||||
reader = BufferedReader(TensorIO(self.tensor))
|
||||
return self._parse_message(reader, "ModelProto", lambda: {"opset_import": [], "domain": None, "graph": None})
|
||||
|
||||
def decode_varint(self, reader: BufferedReader) -> int:
|
||||
result = 0
|
||||
shift = 0
|
||||
while True:
|
||||
data = reader.read(1)
|
||||
if data == b"": raise EOFError("decode_varint EOF")
|
||||
result |= (data[0] & 0x7F) << shift
|
||||
if not (data[0] & 0x80): return result
|
||||
shift += 7
|
||||
if shift >= 70: raise ValueError("Varint too long")
|
||||
|
||||
def skip_field_value(self, reader: BufferedReader, wire_type):
|
||||
if wire_type == WIRETYPE_VARINT: self.decode_varint(reader)
|
||||
elif wire_type == WIRETYPE_FIXED64: reader.seek(8, os.SEEK_CUR)
|
||||
elif wire_type == WIRETYPE_FIXED32: reader.seek(4, os.SEEK_CUR)
|
||||
elif wire_type == WIRETYPE_LENGTH_DELIMITED: reader.seek(self.decode_varint(reader), os.SEEK_CUR)
|
||||
else: raise ValueError(f"Unknown wire type: {wire_type}")
|
||||
|
||||
def _parse_message(self, reader, message_field_handlers_name, initial_obj_factory=lambda: {}):
|
||||
message_field_handlers = self.registered_handles[message_field_handlers_name]
|
||||
obj = initial_obj_factory()
|
||||
while True:
|
||||
try:
|
||||
tag_val = self.decode_varint(reader)
|
||||
field_number = tag_val >> 3
|
||||
wire_type = tag_val & 0x07
|
||||
if handler := message_field_handlers.get(field_number):
|
||||
handler(obj, reader, wire_type)
|
||||
else: self.skip_field_value(reader, wire_type)
|
||||
except EOFError: break
|
||||
if message_field_handlers_name == "TensorProto" and self.load_external_data and obj.get("data_location", 0) == 1: self._parse_external_data(obj)
|
||||
return obj
|
||||
|
||||
def _handle_delimited(self, reader:BufferedReader, use_tensor=False) -> Tensor|bytes:
|
||||
str_len = self.decode_varint(reader)
|
||||
if not use_tensor: return reader.read(str_len)
|
||||
raw = reader.raw
|
||||
assert isinstance(raw, TensorIO)
|
||||
res = raw._tensor[reader.tell():(reader.tell()+str_len)]
|
||||
reader.seek(str_len, os.SEEK_CUR)
|
||||
return res
|
||||
|
||||
def _handle_string(self, obj, key_name, reader, wire_type, parser_func=None, repeated=False):
|
||||
if wire_type != WIRETYPE_LENGTH_DELIMITED: raise ValueError(f"Expected length-delimited for string field '{key_name}'")
|
||||
value = self._handle_delimited(reader)
|
||||
assert isinstance(value, bytes)
|
||||
gen_result(obj, key_name, value.decode("utf-8"), repeated)
|
||||
|
||||
def _handle_bytes(self, obj, key_name, reader, wire_type, parser_func=None, repeated=False):
|
||||
if wire_type != WIRETYPE_LENGTH_DELIMITED: raise ValueError(f"Expected length-delimited for bytes field '{key_name}'")
|
||||
value = self._handle_delimited(reader, use_tensor=True)
|
||||
gen_result(obj, key_name, value, repeated)
|
||||
|
||||
def _handle_int64(self, obj, key_name, reader, wire_type, parser_func=None, repeated=False):
|
||||
if wire_type != WIRETYPE_VARINT: raise ValueError(f"Expected varint for int64 field '{key_name}'")
|
||||
val = self.decode_varint(reader)
|
||||
gen_result(obj, key_name, val - 2**64 if val & (1 << 63) else val, repeated)
|
||||
|
||||
def _handle_float(self, obj, key_name, reader, wire_type, parser_func=None, repeated=False):
|
||||
if wire_type != WIRETYPE_FIXED32: raise ValueError(f"Expected fixed32 for float field '{key_name}'")
|
||||
val, = struct.unpack("<f", reader.read(4))
|
||||
gen_result(obj, key_name, val, repeated)
|
||||
|
||||
def _handle_packed_int64s(self, obj, key_name, reader, wire_type, parser_func=None, repeated=False):
|
||||
if wire_type != WIRETYPE_LENGTH_DELIMITED: raise ValueError("Packed int64s expected length_delimited")
|
||||
total_bytes_len = self.decode_varint(reader)
|
||||
old_pos = reader.tell()
|
||||
values = []
|
||||
while reader.tell() < total_bytes_len + old_pos:
|
||||
val = self.decode_varint(reader) # need copy here because packed ints are varint
|
||||
values.append(val - 2**64 if val & (1 << 63) else val)
|
||||
obj[key_name] = values
|
||||
|
||||
def _handle_packed_floats(self, obj, key_name, reader, wire_type, parser_func=None, repeated=False):
|
||||
if wire_type != WIRETYPE_LENGTH_DELIMITED: raise ValueError("Packed floats expected length_delimited")
|
||||
value = self._handle_delimited(reader, use_tensor=True)
|
||||
obj[key_name] = value
|
||||
|
||||
def _handle_sub_message(self, obj, key_name, reader, wire_type, parser_func=None, repeated=False):
|
||||
if wire_type != WIRETYPE_LENGTH_DELIMITED: raise ValueError(f"Expected length-delimited for sub-message field '{key_name}'")
|
||||
value = self._handle_delimited(reader, use_tensor=True)
|
||||
assert isinstance(value, Tensor)
|
||||
if isinstance(parser_func, str): sub_obj = self._parse_message(BufferedReader(TensorIO(value)), parser_func)
|
||||
elif isinstance(parser_func, tuple): sub_obj = self._parse_message(BufferedReader(TensorIO(value)), parser_func[0], parser_func[1])
|
||||
else: sub_obj = parser_func(BufferedReader(TensorIO(value)))
|
||||
gen_result(obj, key_name, sub_obj, repeated)
|
||||
|
||||
def _parse_external_data(self, obj):
|
||||
if "external_data" not in obj: raise ValueError("no external_data")
|
||||
location = None
|
||||
length = None
|
||||
offset = 0
|
||||
for kv in obj["external_data"]:
|
||||
if kv["key"] == "location": location = kv["value"]
|
||||
if kv["key"] == "offset": offset = int(kv["value"])
|
||||
if kv["key"] == "length": length = int(kv["value"])
|
||||
if location is None: raise ValueError("no location in external_data")
|
||||
if self.file_path is None:
|
||||
# get onnx file path from Tensor
|
||||
if isinstance(self.tensor.device, str) and self.tensor.device.startswith("DISK:"):
|
||||
self.file_path = pathlib.Path(self.tensor.device[5:])
|
||||
if not (ext_path := self.file_path.parent.joinpath(location)).exists():
|
||||
raise Exception(f"external location not exists: {ext_path}, may caused by symbolic link, try passing onnx file path to onnx_load")
|
||||
else: raise Exception("onnx external_data need the origin file path, try passing onnx file path to onnx_load")
|
||||
ext_path = self.file_path.parent.joinpath(location)
|
||||
if not ext_path.exists(): raise Exception(f"external location not exists: {ext_path}")
|
||||
ext_tensor = Tensor(ext_path)
|
||||
obj["raw_data"] = ext_tensor[offset:offset+length] if length is not None else ext_tensor[offset:]
|
||||
obj["data_location"] = 0
|
||||
|
||||
# https://github.com/onnx/onnx/blob/rel-1.17.0/onnx/onnx.proto3#L500-L544
|
||||
data_types: dict[int, DType] = {
|
||||
1:dtypes.float32, 2:dtypes.uint8, 3:dtypes.int8, 4:dtypes.uint16, 5:dtypes.int16, 6:dtypes.int32, 7:dtypes.int64,
|
||||
9:dtypes.bool, 10:dtypes.float16, 11:dtypes.double, 12:dtypes.uint32, 13:dtypes.uint64, 16:dtypes.bfloat16,
|
||||
}
|
||||
|
||||
# https://github.com/onnx/onnx/blob/rel-1.17.0/onnx/onnx.proto3#L128-L145
|
||||
attribute_types: dict[int, Callable] = {
|
||||
1: lambda a: float(a.f),
|
||||
2: lambda a: int(a.i),
|
||||
3: lambda a: a.s.data().tobytes().decode("utf8") if isinstance(a.s, Tensor) else a.s.decode("utf8"),
|
||||
4: lambda a: buffer_parse(a.t),
|
||||
6: lambda a: tuple(float(x) for x in a.floats),
|
||||
7: lambda a: tuple(int(x) for x in a.ints),
|
||||
8: lambda a: tuple(x.data().tobytes().decode("utf8") for x in a.strings)
|
||||
}
|
||||
|
||||
# ***** protobuf parsing ******
|
||||
from onnx import AttributeProto, TensorProto, TypeProto
|
||||
|
||||
def has_field(onnx_type: TypeProto|SimpleNamespace, field):
|
||||
if isinstance(onnx_type, TypeProto): return onnx_type.HasField(field)
|
||||
return hasattr(onnx_type, field)
|
||||
|
||||
def dtype_parse(onnx_dtype: int, fallback_context: str | None = None) -> DType:
|
||||
if onnx_dtype not in data_types: raise NotImplementedError(f"onnx dtype id {onnx_dtype} is not supported")
|
||||
if is_dtype_supported(dtype := data_types[onnx_dtype]): return dtype
|
||||
# if fallback_context is provided, we can fall back to a default dtype
|
||||
if fallback_context is not None:
|
||||
default_dtype = dtypes.default_int if dtypes.is_int(dtype) else dtypes.default_float
|
||||
warnings.warn(f"dtype {dtype} on {Device.DEFAULT} from {fallback_context} is not supported, falling back to {default_dtype}")
|
||||
assert is_dtype_supported(default_dtype), f"dtype {default_dtype} must be supported on {Device.DEFAULT}"
|
||||
return default_dtype
|
||||
raise RuntimeError(f"dtype {dtype} on device {Device.DEFAULT} is not supported")
|
||||
|
||||
def attribute_parse(onnx_attribute: AttributeProto):
|
||||
if onnx_attribute.type not in attribute_types: raise NotImplementedError(f"attribute type {onnx_attribute.type} is not supported")
|
||||
return attribute_types[onnx_attribute.type](onnx_attribute)
|
||||
|
||||
def buffer_parse(onnx_tensor: TensorProto) -> Tensor:
|
||||
if onnx_tensor.string_data: raise NotImplementedError("Parsing for buffer with string data is not implemented.")
|
||||
to_dtype, true_dtype = dtype_parse(onnx_tensor.data_type, "buffer parse"), data_types[onnx_tensor.data_type]
|
||||
shape = tuple(onnx_tensor.dims)
|
||||
keys = ['float_data', 'int32_data', 'int64_data', 'double_data', 'uint64_data', "raw_data"]
|
||||
data = next((val for k in keys if (val := getattr(onnx_tensor, k)) is not None), None)
|
||||
if data is None: raise RuntimeError("empty buffer")
|
||||
if not isinstance(data, Tensor): return Tensor(data, dtype=to_dtype).reshape(shape)
|
||||
assert data.dtype is dtypes.uint8, data.dtype
|
||||
data = data.bitcast(true_dtype).reshape(shape)
|
||||
data = data.to(Device.DEFAULT) if true_dtype is to_dtype else data.to("cpu").cast(to_dtype).to(Device.DEFAULT)
|
||||
if shape == ():
|
||||
if data.dtype is dtypes.float16 and sys.version_info < (3, 12): data = data.cast(dtypes.float32)
|
||||
return Tensor(data.item(), dtype=to_dtype).reshape(shape)
|
||||
return data
|
||||
|
||||
def type_parse(onnx_type: TypeProto):
|
||||
elem_type = onnx_type
|
||||
if has_field(elem_type, "map_type") or has_field(elem_type, "sparse_tensor_type") or has_field(elem_type, "opaque_type"):
|
||||
raise NotImplementedError("parsing for map_type, sparse_tensor_type and opaque_type are not implemented")
|
||||
if is_optional := has_field(elem_type, "optional_type"): elem_type = elem_type.optional_type.elem_type
|
||||
if is_sequence := has_field(elem_type, "sequence_type"): elem_type = elem_type.sequence_type.elem_type
|
||||
if has_field(elem_type, "tensor_type"):
|
||||
shape = tuple(getattr(d, "dim_param", None) or getattr(d, "dim_value") for d in elem_type.tensor_type.shape.dim) \
|
||||
if has_field(elem_type.tensor_type, "shape") else None # test_identity_sequence_cpu
|
||||
dtype = data_types[elem_type.tensor_type.elem_type]
|
||||
return OnnxValue(shape, dtype, is_optional, is_sequence)
|
||||
raise RuntimeError(f"TypeProto was not parsed properly: {onnx_type=}")
|
||||
|
||||
# ***** onnx spec *****
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class OnnxValue:
|
||||
shape: tuple[str|int, ...]
|
||||
dtype: DType
|
||||
is_optional: bool
|
||||
is_sequence: bool
|
||||
def dtype_fallback(dtype: DType, fallback_context: str) -> DType:
|
||||
if is_dtype_supported(dtype): return dtype
|
||||
default_dtype = dtypes.default_int if dtypes.is_int(dtype) else dtypes.default_float
|
||||
warnings.warn(f"dtype {dtype} on {Device.DEFAULT} from {fallback_context} is not supported, falling back to {default_dtype}")
|
||||
assert is_dtype_supported(default_dtype), f"dtype {default_dtype} must be supported on {Device.DEFAULT}"
|
||||
return default_dtype
|
||||
|
||||
# ***** onnx spec definitions *****
|
||||
class Domain(enum.Enum):
|
||||
ONNX = "ai.onnx"
|
||||
ONNX_ML = "ai.onnx.ml"
|
||||
@@ -298,15 +56,313 @@ class OpSetId(NamedTuple):
|
||||
domain: Domain
|
||||
version: int
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class OnnxValue:
|
||||
shape: tuple[str|int, ...]
|
||||
dtype: DType
|
||||
is_optional: bool
|
||||
is_sequence: bool
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class OnnxNode:
|
||||
num: int
|
||||
op: str
|
||||
opset_id: OpSetId
|
||||
inputs: tuple[str, ...]
|
||||
outputs: tuple[str, ...]
|
||||
opts: dict[str, Any]
|
||||
|
||||
# ***** protobuf parsing ******
|
||||
class PBBufferedReader(BufferedReader):
|
||||
def __init__(self, tensor: Tensor):
|
||||
assert tensor.dtype is dtypes.uint8, tensor
|
||||
super().__init__(TensorIO(tensor))
|
||||
self.len = tensor.nbytes()
|
||||
|
||||
def decode_varint(self) -> int:
|
||||
"""Reference: https://protobuf.dev/programming-guides/encoding/#varints"""
|
||||
result = 0
|
||||
shift = 0
|
||||
while True:
|
||||
data = self.read(1)
|
||||
if data == b"": raise EOFError("decode_varint EOF")
|
||||
result |= (data[0] & 0x7F) << shift
|
||||
if not (data[0] & 0x80): return result
|
||||
shift += 7
|
||||
if shift >= 70: raise ValueError("Varint too long")
|
||||
|
||||
def read_delimited(self, use_tensor=False):
|
||||
str_len = self.decode_varint()
|
||||
if not use_tensor: return self.read(str_len)
|
||||
raw = self.raw
|
||||
assert isinstance(raw, TensorIO)
|
||||
res = raw._tensor[self.tell():(self.tell()+str_len)]
|
||||
self.seek(str_len, os.SEEK_CUR)
|
||||
return res
|
||||
def read_string(self) -> str: return self.read_delimited().decode("utf-8")
|
||||
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_packed_int64s(self) -> list[int]:
|
||||
total_bytes_len = self.decode_varint()
|
||||
old_pos = self.tell()
|
||||
values = []
|
||||
while self.tell() < total_bytes_len + old_pos:
|
||||
val = self.decode_varint() # need copy here because packed ints are varint
|
||||
values.append(val - 2**64 if val & (1 << 63) else val)
|
||||
return values
|
||||
|
||||
def skip_field(self, wire_type: WireType) -> None:
|
||||
"""Skip a field based on its wire type."""
|
||||
match wire_type:
|
||||
case WireType.VARINT: self.decode_varint()
|
||||
case WireType.FIXED64: self.seek(8, os.SEEK_CUR)
|
||||
case WireType.FIXED32: self.seek(4, os.SEEK_CUR)
|
||||
case WireType.LENGTH_DELIMITED: self.seek(self.decode_varint(), os.SEEK_CUR)
|
||||
case _: raise ValueError(f"Unknown wire type: {wire_type}")
|
||||
|
||||
class OnnxPBParser:
|
||||
"""
|
||||
ONNX protobuf parser.
|
||||
Reference: https://github.com/onnx/onnx/blob/main/onnx/onnx.proto3
|
||||
"""
|
||||
def __init__(self, inp: Tensor|str|pathlib.Path, load_external_data: bool=True):
|
||||
self.file_path: pathlib.Path|None = None
|
||||
self.load_external_data = load_external_data
|
||||
if not isinstance(inp, Tensor):
|
||||
self.file_path = pathlib.Path(inp)
|
||||
self.tensor = Tensor(self.file_path)
|
||||
else: self.tensor = inp
|
||||
self.reader = PBBufferedReader(self.tensor)
|
||||
|
||||
def parse(self) -> dict:
|
||||
"""Parses the ONNX model into a nested dictionary. """
|
||||
return self._parse_ModelProto()
|
||||
|
||||
def _parse_message(self, end_pos: int) -> Generator[tuple[int, WireType], None, None]:
|
||||
while self.reader.tell() < end_pos:
|
||||
tag = self.reader.decode_varint()
|
||||
yield tag >> 3, WireType(tag & 0x07)
|
||||
|
||||
def _decode_end_pos(self) -> int:
|
||||
str_len = self.reader.decode_varint()
|
||||
start_pos = self.reader.tell()
|
||||
return start_pos + str_len
|
||||
|
||||
def _parse_ModelProto(self) -> dict:
|
||||
"""Entry point for parsing the ONNX model."""
|
||||
obj: dict[str, Any] = {"opset_import": []}
|
||||
for fid, wire_type in self._parse_message(self.reader.len):
|
||||
match fid:
|
||||
case 4: obj["domain"] = self.reader.read_string()
|
||||
case 5: obj["model_version"] = self.reader.read_int64()
|
||||
case 7: obj["graph"] = self._parse_GraphProto()
|
||||
case 8: obj["opset_import"].append(self._parse_OperatorSetIdProto())
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
|
||||
# update opset version
|
||||
opset_imports = {Domain.from_onnx(x.get('domain')):x.get('version', 1) for x in obj["opset_import"]}
|
||||
for n in obj["graph"]["node"]:
|
||||
n_ = n["parsed_node"]
|
||||
n["parsed_node"] = OnnxNode(n_.op, OpSetId(n_.opset_id.domain, opset_imports.get(n_.opset_id.domain, 1)), n_.inputs, n_.outputs, n_.opts)
|
||||
return obj
|
||||
|
||||
def _parse_GraphProto(self) -> dict:
|
||||
obj: dict[str, Any] = {"node": [], "initializer": [], "input": [], "output": []}
|
||||
for fid, wire_type in self._parse_message(self._decode_end_pos()):
|
||||
match fid:
|
||||
case 1: obj["node"].append(self._parse_NodeProto())
|
||||
case 2: obj["name"] = self.reader.read_string()
|
||||
case 5: obj["initializer"].append(self._parse_TensorProto())
|
||||
case 11: obj["input"].append(self._parse_ValueInfoProto())
|
||||
case 12: obj["output"].append(self._parse_ValueInfoProto())
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
return obj
|
||||
|
||||
def _parse_NodeProto(self) -> dict:
|
||||
obj: dict[str, Any] = {"input": [], "output": [], "attribute": [], "domain": None}
|
||||
for fid, wire_type in self._parse_message(self._decode_end_pos()):
|
||||
match fid:
|
||||
case 1: obj["input"].append(self.reader.read_string())
|
||||
case 2: obj["output"].append(self.reader.read_string())
|
||||
case 3: obj["name"] = self.reader.read_string()
|
||||
case 4: obj["op_type"] = self.reader.read_string()
|
||||
case 5: obj["attribute"].append(self._parse_AttributeProto())
|
||||
case 6: obj["doc_string"] = self.reader.read_string()
|
||||
case 7: obj["domain"] = self.reader.read_string()
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
|
||||
# parse node
|
||||
attributes = {attr_dict["name"]: attr_dict[AttributeType(attr_dict["type"]).to_field_name()] for attr_dict in obj["attribute"]}
|
||||
opset_id = OpSetId(Domain.from_onnx(obj.get('domain')), 1) # default version, to be updated later in _parse_ModelProto
|
||||
obj["parsed_node"] = OnnxNode(obj["op_type"], opset_id, tuple(obj["input"]), tuple(obj["output"]), attributes)
|
||||
return obj
|
||||
|
||||
def _parse_TensorProto(self) -> dict:
|
||||
obj: dict[str, Any] = {"dims": []}
|
||||
for fid, wire_type in self._parse_message(self._decode_end_pos()):
|
||||
match fid:
|
||||
case 1: obj["dims"].append(self.reader.read_int64())
|
||||
case 2: obj["data_type"] = self.reader.read_int64()
|
||||
case 4: obj["float_data"] = self.reader.read_packed_floats()
|
||||
case 5: obj["int32_data"] = self.reader.read_packed_int64s()
|
||||
case 7: obj["int64_data"] = self.reader.read_packed_int64s()
|
||||
case 8: obj["name"] = self.reader.read_string()
|
||||
case 9: obj["raw_data"] = self.reader.read_bytes()
|
||||
case 10: obj["double_data"] = self.reader.read_packed_floats()
|
||||
case 11: obj["uint64_data"] = self.reader.read_packed_int64s()
|
||||
case 13: obj.setdefault("external_data", []).append(self._parse_StringStringEntryProto())
|
||||
case 14: obj["data_location"] = self.reader.read_int64()
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
|
||||
# load external data
|
||||
if self.load_external_data and obj.get("data_location", 0) == 1:
|
||||
if "external_data" not in obj: raise ValueError("no external_data")
|
||||
location, length, offset = None, None, 0
|
||||
for kv in obj["external_data"]:
|
||||
if kv["key"] == "location": location = kv["value"]
|
||||
if kv["key"] == "offset": offset = int(kv["value"])
|
||||
if kv["key"] == "length": length = int(kv["value"])
|
||||
if location is None: raise ValueError("no location in external_data")
|
||||
|
||||
if self.file_path is None:
|
||||
if isinstance(self.tensor.device, str) and self.tensor.device.startswith("DISK:"):
|
||||
self.file_path = pathlib.Path(self.tensor.device[5:])
|
||||
else: raise Exception("onnx external_data needs the origin file path, try passing onnx file path to onnx_load")
|
||||
ext_path = self.file_path.parent.joinpath(location)
|
||||
if not ext_path.exists(): raise Exception(f"external location not exists: {ext_path}")
|
||||
|
||||
ext_tensor = Tensor(ext_path)
|
||||
obj["raw_data"] = ext_tensor[offset:offset+length] if length is not None else ext_tensor[offset:]
|
||||
obj["data_location"] = 0
|
||||
|
||||
# parse tensor
|
||||
to_dtype = dtype_fallback(true_dtype := OnnxDataType(obj['data_type']).to_dtype(), "buffer parse")
|
||||
shape = tuple(obj['dims'])
|
||||
present_fields = [field for field in ['float_data', 'int32_data', 'int64_data', 'double_data', 'uint64_data', 'raw_data'] if field in obj]
|
||||
assert len(present_fields) == 1, f"only 1 data field is allowed from {obj=}"
|
||||
data = obj[present_fields[0]]
|
||||
if not isinstance(data, Tensor):
|
||||
obj["parsed_tensor"] = Tensor(data, dtype=to_dtype).reshape(shape)
|
||||
return obj
|
||||
assert isinstance(data, Tensor) and data.dtype is dtypes.uint8, data
|
||||
data = data.bitcast(true_dtype).reshape(shape)
|
||||
data = data.to(Device.DEFAULT) if true_dtype is to_dtype else data.to("cpu").cast(to_dtype).to(Device.DEFAULT)
|
||||
# const folding
|
||||
if shape == ():
|
||||
if data.dtype is dtypes.float16 and sys.version_info < (3, 12): data = data.cast(dtypes.float32)
|
||||
data = Tensor(data.item(), dtype=to_dtype).reshape(shape)
|
||||
obj["parsed_tensor"] = data
|
||||
return obj
|
||||
|
||||
def _parse_AttributeProto(self) -> dict:
|
||||
obj: dict[str, Any] = {"floats": [], "ints": [], "strings": []}
|
||||
for fid, wire_type in self._parse_message(self._decode_end_pos()):
|
||||
match fid:
|
||||
case 1: obj["name"] = self.reader.read_string()
|
||||
case 2: obj["f"] = self.reader.read_float()
|
||||
case 3: obj["i"] = self.reader.read_int64()
|
||||
case 4: obj["s"] = self.reader.read_bytes().data().tobytes().decode("utf8")
|
||||
case 5: obj["t"] = self._parse_TensorProto()['parsed_tensor']
|
||||
case 7: obj["floats"].append(self.reader.read_float())
|
||||
case 8: obj["ints"].append(self.reader.read_int64())
|
||||
case 9: obj["strings"].append(self.reader.read_bytes().data().tobytes().decode("utf8"))
|
||||
case 20: obj["type"] = self.reader.read_int64()
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
obj["floats"], obj["ints"], obj["strings"] = tuple(obj["floats"]), tuple(obj["ints"]), tuple(obj["strings"])
|
||||
return obj
|
||||
|
||||
def _parse_ValueInfoProto(self) -> dict:
|
||||
obj: dict[str, Any] = {}
|
||||
for fid, wire_type in self._parse_message(self._decode_end_pos()):
|
||||
match fid:
|
||||
case 1: obj["name"] = self.reader.read_string()
|
||||
case 2: obj["type"] = self._parse_TypeProto()
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
|
||||
# parse type
|
||||
if "type" not in obj: return {**obj, "parsed_type": None}
|
||||
type_obj = obj["type"]
|
||||
if is_optional := "optional_type" in type_obj: type_obj = type_obj["optional_type"]["elem_type"]
|
||||
if is_sequence := "sequence_type" in type_obj: type_obj = type_obj["sequence_type"]["elem_type"]
|
||||
assert "tensor_type" in type_obj, type_obj
|
||||
shape_dims = type_obj['tensor_type'].get('shape', {}).get('dim', [])
|
||||
obj['parsed_type'] = OnnxValue(tuple(d.get('dim_param') or d.get('dim_value') for d in shape_dims),
|
||||
OnnxDataType(type_obj['tensor_type']['elem_type']).to_dtype(), is_optional, is_sequence)
|
||||
return obj
|
||||
|
||||
def _parse_TypeProto(self) -> dict:
|
||||
obj: dict[str, Any] = {}
|
||||
for fid, wire_type in self._parse_message(self._decode_end_pos()):
|
||||
match fid:
|
||||
case 1: obj["tensor_type"] = self._parse_TypeProtoTensor()
|
||||
case 4: obj["sequence_type"] = self._parse_TypeProtoSequence()
|
||||
case 9: obj["optional_type"] = self._parse_TypeProtoOptional()
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
return obj
|
||||
|
||||
def _parse_TypeProtoTensor(self) -> dict:
|
||||
obj: dict[str, Any] = {}
|
||||
for fid, wire_type in self._parse_message(self._decode_end_pos()):
|
||||
match fid:
|
||||
case 1: obj["elem_type"] = self.reader.read_int64()
|
||||
case 2: obj["shape"] = self._parse_TensorShapeProto()
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
return obj
|
||||
|
||||
def _parse_TypeProtoSequence(self) -> dict:
|
||||
obj = {}
|
||||
for fid, wire_type in self._parse_message(self._decode_end_pos()):
|
||||
match fid:
|
||||
case 1: obj["elem_type"] = self._parse_TypeProto()
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
return obj
|
||||
|
||||
def _parse_TypeProtoOptional(self) -> dict:
|
||||
obj = {}
|
||||
for fid, wire_type in self._parse_message(self._decode_end_pos()):
|
||||
match fid:
|
||||
case 1: obj["elem_type"] = self._parse_TypeProto()
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
return obj
|
||||
|
||||
def _parse_TensorShapeProto(self) -> dict:
|
||||
obj: dict[str, Any] = {"dim": []}
|
||||
for fid, wire_type in self._parse_message(self._decode_end_pos()):
|
||||
match fid:
|
||||
case 1: obj["dim"].append(self._parse_TensorShapeProtoDimension())
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
return obj
|
||||
|
||||
def _parse_TensorShapeProtoDimension(self) -> dict:
|
||||
obj: dict[str, Any] = {}
|
||||
for fid, wire_type in self._parse_message(self._decode_end_pos()):
|
||||
match fid:
|
||||
case 1: obj["dim_value"] = self.reader.read_int64()
|
||||
case 2: obj["dim_param"] = self.reader.read_string()
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
return obj
|
||||
|
||||
def _parse_StringStringEntryProto(self) -> dict:
|
||||
obj: dict[str, Any] = {}
|
||||
for fid, wire_type in self._parse_message(self._decode_end_pos()):
|
||||
match fid:
|
||||
case 1: obj["key"] = self.reader.read_string()
|
||||
case 2: obj["value"] = self.reader.read_string()
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
return obj
|
||||
|
||||
def _parse_OperatorSetIdProto(self) -> dict:
|
||||
obj: dict[str, Any] = {}
|
||||
for fid, wire_type in self._parse_message(self._decode_end_pos()):
|
||||
match fid:
|
||||
case 1: obj["domain"] = self.reader.read_string()
|
||||
case 2: obj["version"] = self.reader.read_int64()
|
||||
case _: self.reader.skip_field(wire_type)
|
||||
return obj
|
||||
|
||||
# ***** python const *****
|
||||
required_input_python_consts: dict[str, tuple[int, ...]] = {
|
||||
"Tile": (1,), "Range": (0,1,2), "Expand": (1,), "Reshape": (1,), "Squeeze": (1,), "Unsqueeze": (1,), "Trilu": (1,), "ConstantOfShape": (0,),
|
||||
@@ -344,22 +400,18 @@ class OnnxRunner:
|
||||
model_path: The ONNX model, provided as a file path (a string or Path object) or a Tensor.
|
||||
"""
|
||||
def __init__(self, model_path: Tensor | str | pathlib.Path):
|
||||
model = onnx_load(model_path)
|
||||
self.is_training = any(n.domain in {Domain.AI_ONNX_TRAINING, Domain.AI_ONNX_PREVIEW_TRAINING} for n in model.graph.node)
|
||||
model = OnnxPBParser(model_path, load_external_data=True).parse()
|
||||
graph = model["graph"]
|
||||
self.is_training = any(n['domain'] in {Domain.AI_ONNX_TRAINING, Domain.AI_ONNX_PREVIEW_TRAINING} for n in graph["node"])
|
||||
self.graph_values = {"": None, **{i["name"]: i["parsed_tensor"] for i in graph["initializer"]}}
|
||||
self.graph_inputs = {i["name"]: i["parsed_type"] for i in graph["input"] if i["name"] not in self.graph_values}
|
||||
self.graph_outputs = tuple(o["name"] for o in graph["output"])
|
||||
self.graph_nodes = tuple(n["parsed_node"] for n in graph["node"])
|
||||
|
||||
self.old_training = Tensor.training
|
||||
Tensor.training = True if self.is_training else False
|
||||
self.graph_values = {"": None, **{x.name:buffer_parse(x) for x in model.graph.initializer}}
|
||||
self.graph_inputs = {x.name:type_parse(x.type) for x in model.graph.input if x.name not in self.graph_values}
|
||||
self.graph_outputs = tuple(x.name for x in model.graph.output)
|
||||
opset_imports = {Domain.from_onnx(getattr(x, "domain", "")):x.version for x in model.opset_import}
|
||||
self.graph_nodes = []
|
||||
for num, n in enumerate(model.graph.node):
|
||||
domain = Domain.from_onnx(n.domain)
|
||||
opset_id = OpSetId(domain, opset_imports.get(domain, 1))
|
||||
self.graph_nodes.append(OnnxNode(num, n.op_type, opset_id, tuple(n.input), tuple(n.output), {x.name:attribute_parse(x) for x in n.attribute}))
|
||||
self.graph_nodes = tuple(self.graph_nodes)
|
||||
self.variable_dims: dict[str, int] = {}
|
||||
|
||||
self.variable_dims: dict[str, int] = {}
|
||||
self.onnx_ops = onnx_ops
|
||||
|
||||
def _parse_input(self, name: str, value: Any, spec: OnnxValue):
|
||||
@@ -394,7 +446,7 @@ class OnnxRunner:
|
||||
|
||||
def to(self, device:str|None):
|
||||
self.graph_values = {k:v.to(device) if isinstance(v, Tensor) else v for k,v in self.graph_values.items()}
|
||||
self.graph_nodes = tuple(OnnxNode(n.num, n.op, n.opset_id, tuple(n.inputs), tuple(n.outputs),
|
||||
self.graph_nodes = tuple(OnnxNode(n.op, n.opset_id, tuple(n.inputs), tuple(n.outputs),
|
||||
{k:v.to(device) if isinstance(v, Tensor) else v for k,v in n.opts.items()}) for n in self.graph_nodes)
|
||||
return self
|
||||
|
||||
@@ -403,7 +455,7 @@ class OnnxRunner:
|
||||
if name not in inputs: raise RuntimeError(f"Please provide input data for {name}")
|
||||
self.graph_values[name] = self._parse_input(name, inputs[name], input_spec)
|
||||
|
||||
for node in self.graph_nodes:
|
||||
for num, node in enumerate(self.graph_nodes):
|
||||
inps = [to_python_const(self.graph_values[name], node.op, i) for i,name in enumerate(node.inputs)]
|
||||
opts = node.opts
|
||||
|
||||
@@ -411,7 +463,7 @@ class OnnxRunner:
|
||||
if node.op == "Split" and 'num_outputs' not in opts: opts['num_outputs'] = len(node.outputs)
|
||||
if node.op == "Gradient": opts['intermediate_tensors'] = self.graph_values
|
||||
|
||||
if debug >= 1: print(f"{node.num}: op '{node.op}' opt {opts}")
|
||||
if debug >= 1: print(f"{num}: op '{node.op}' opt {opts}")
|
||||
if debug >= 2 and node.inputs: print("\tinputs:\n" + "\n".join(f"\t\t{x} - {i!r}" for x,i in zip(node.inputs, inps)))
|
||||
ret = self._select_op(node.op, node.opset_id)(*inps, **opts)
|
||||
ret = ret if isinstance(ret, tuple) else (ret,)
|
||||
@@ -419,7 +471,7 @@ class OnnxRunner:
|
||||
|
||||
self.graph_values.update(dict(zip(node.outputs, ret[:len(node.outputs)], strict=True)))
|
||||
|
||||
if node.num == limit:
|
||||
if num == limit:
|
||||
Tensor.training = self.old_training
|
||||
return {name:self.graph_values[name] for name in node.outputs}
|
||||
Tensor.training = self.old_training
|
||||
@@ -515,7 +567,7 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
|
||||
raise ValueError(f"pixel_format={pixel_format!r} is not supported.")
|
||||
|
||||
def EyeLike(x:Tensor, dtype:int|None=None, k:int=0):
|
||||
ret = Tensor.eye(cast(int, min(x.shape)), dtype=dtype_parse(dtype, "EyeLike op") if dtype is not None else x.dtype)
|
||||
ret = Tensor.eye(cast(int, min(x.shape)), dtype=dtype_fallback(OnnxDataType(dtype).to_dtype(), "EyeLike op") if dtype is not None else x.dtype)
|
||||
return ret if x.size(0) == x.size(1) else ret.pad(tuple(None if d == ret.size(0) else (k, d-ret.shape[0]-k) for d in x.shape))
|
||||
|
||||
def OptionalHasElement(x:Tensor|None=None): return Tensor(x is not None and x.numel() > 0)
|
||||
@@ -569,7 +621,7 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
|
||||
|
||||
# ***** Casting Ops *****
|
||||
# TODO: saturate
|
||||
def Cast(x:Tensor, to:int, saturate:int=1): return x.cast(dtype_parse(to, "Cast op"))
|
||||
def Cast(x:Tensor, to:int, saturate:int=1): return x.cast(dtype_fallback(OnnxDataType(to).to_dtype(), "Cast op"))
|
||||
def CastLike(x:Tensor, target_type:Tensor, saturate:int=1): return x.cast(target_type.dtype)
|
||||
|
||||
# ***** Reduce Ops *****
|
||||
@@ -703,53 +755,59 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
|
||||
return x.triu(k_) if upper else x.tril(k_)
|
||||
|
||||
def Resize(X:Tensor, roi:list[float]|None=None, scales:list[float]|None=None, sizes:list[int]|None=None, antialias:int=0,
|
||||
axes:list[int]|None=None, coordinate_transformation_mode:str='half_pixel', cubic_coeff_a:float=-0.75, exclude_outside:int=0,
|
||||
extrapolation_value:float=0.0, keep_aspect_ratio_policy:str='stretch', mode:str='nearest', nearest_mode:str='round_prefer_floor'):
|
||||
def _apply_nearest_mode(index: Tensor, input_dim, mode: str):
|
||||
if mode == "round_prefer_floor": index = (index - 0.5).ceil()
|
||||
elif mode == "round_prefer_ceil": index = (index + 0.5).floor()
|
||||
elif mode in ["floor", "ceil"]: index = getattr(index, mode)()
|
||||
else: raise ValueError(f"invalid {nearest_mode=}")
|
||||
return index.cast(dtypes.int32).clip(0, input_dim-1)
|
||||
def _apply_transformation(index: Tensor, input_dim, scale_dim, mode):
|
||||
# TODO: needs more testing, not confident in this
|
||||
# NOTE: their reference implementation differ from the implementation in their reference docs
|
||||
# https://github.com/onnx/onnx/blob/main/onnx/reference/ops/op_resize.py
|
||||
# https://github.com/onnx/onnx/blob/main/docs/Operators.md#Resize
|
||||
output_dim = scale_dim * input_dim
|
||||
if mode == "half_pixel": index = (index + 0.5) / scale_dim - 0.5
|
||||
elif mode == "align_corners": index = index * (input_dim - 1) / (output_dim - 1) if output_dim != 1 else Tensor([0])
|
||||
elif mode == "asymmetric": index = index / scale_dim
|
||||
elif mode == "pytorch_half_pixel": index = (index + 0.5) / scale_dim - 0.5 if output_dim != 1 else Tensor([-0.5])
|
||||
elif mode == "half_pixel_symmetric": index = input_dim / 2 * (1 - int(output_dim) / output_dim) + (index + 0.5) / scale_dim - 0.5
|
||||
else: raise NotImplementedError(f"invalid {coordinate_transformation_mode=}")
|
||||
return index.clip(0, input_dim-1)
|
||||
axes:list[int]|None=None, coordinate_transformation_mode:str='half_pixel', cubic_coeff_a:float=-0.75, exclude_outside:int=0,
|
||||
extrapolation_value:float=0.0, keep_aspect_ratio_policy:str='stretch', mode:str='nearest', nearest_mode:str='round_prefer_floor'):
|
||||
def _apply_transformation(input_sz, output_sz, scale_dim, mode):
|
||||
index = Tensor.arange(output_sz, requires_grad=False, device=X.device)
|
||||
if mode == "half_pixel": return (index + 0.5) / scale_dim - 0.5
|
||||
if mode == "align_corners": return index * (input_sz - 1) / (output_sz - 1) if output_sz != 1 else Tensor.zeros_like(index)
|
||||
if mode == "asymmetric": return index / scale_dim
|
||||
if mode == "pytorch_half_pixel": return ((index + 0.5) / scale_dim - 0.5) if output_sz != 1 else Tensor.zeros_like(index)
|
||||
if mode == "half_pixel_symmetric":
|
||||
output_dim_scaled = input_sz * scale_dim
|
||||
return (input_sz / 2) * (1 - (output_sz / output_dim_scaled)) + (index + 0.5) / scale_dim - 0.5
|
||||
raise ValueError(f"invalid {coordinate_transformation_mode=}")
|
||||
|
||||
scales, sizes = (None if scales is None else scales[2-(X.ndim-len(scales)):]), (None if sizes is None else sizes[2-(X.ndim-len(sizes)):])
|
||||
# we pre permute the axes and permute back after resize
|
||||
axes, input_shape, = (axes or list(range(X.ndim))), cast(tuple[int, ...], X.shape[2:]),
|
||||
if antialias: raise NotImplementedError("antialias is not implemented")
|
||||
axes = axes or list(range(X.ndim))
|
||||
perm = [a for a in range(len(X.shape)) if a not in axes] + list(axes)
|
||||
# we pre-permute the axes and permute back after resize
|
||||
# the permute aligns X's axes to scales, sizes, and roi
|
||||
X = X.permute(*perm)
|
||||
|
||||
input_shape = cast(tuple[int, ...], X.shape[2:])
|
||||
if scales is not None: assert all(sc==1 for sc in scales[:-len(input_shape)]), "resizing batch_size dim or channel dim not supported"
|
||||
if sizes is not None: assert tuple(sizes[:-2]) == tuple(X.shape[X.ndim-len(sizes):-2]), "resizing batch_size dim or channel dim not supported"
|
||||
assert (scales is not None) ^ (sizes is not None), "only provide one of `scales` or `sizes`"
|
||||
|
||||
scales, sizes = (None if scales is None else scales[-len(input_shape):]), (None if sizes is None else sizes[-len(input_shape):])
|
||||
if sizes is not None:
|
||||
if keep_aspect_ratio_policy in ["not_larger", "not_smaller"]:
|
||||
scale_fxn = min if keep_aspect_ratio_policy == "not_larger" else max
|
||||
scales = [scale_fxn([sizes[i] / input_shape[i] for i in range(len(input_shape)) if i+2 in axes])] * 2
|
||||
sizes = [int((scales[0] * input_shape[i]) + 0.5) if i+2 in axes else input_shape[i] for i in range(X.ndim-2)]
|
||||
else:
|
||||
scales = [size / input_shape for size, input_shape in zip(sizes, input_shape)]
|
||||
else:
|
||||
sizes = [int(sc*sh) for sc, sh in zip(scales, input_shape)]
|
||||
scale = scale_fxn(sz / sh for sz,sh in zip(sizes, input_shape))
|
||||
sizes, scales = [int(scale * sh + 0.5) for sh in input_shape], [scale]*len(input_shape)
|
||||
else: scales = [sz / sh for sz, sh in zip(sizes, input_shape)]
|
||||
else: sizes = [int(sc * sh) for sc, sh in zip(scales, input_shape)]
|
||||
|
||||
if all(sz == sh for sz, sh in zip(sizes, input_shape)): return X.permute(*argsort(perm)) if perm else X
|
||||
|
||||
# NOTE: this transformation makes it so that we can't just call Tensor.interpolate
|
||||
# in Tensor.interpolate, we use indexes without any transformation
|
||||
indexes = []
|
||||
for shape, size, scale in zip(input_shape, sizes, scales):
|
||||
indexes.append(_apply_transformation(Tensor.arange(size), shape, scale, coordinate_transformation_mode))
|
||||
for input_sz, output_sz, scale in zip(input_shape, sizes, scales):
|
||||
indexes.append(_apply_transformation(input_sz, output_sz, scale, coordinate_transformation_mode))
|
||||
|
||||
if mode in ["nearest", "linear"]: indexes = [idx.clip(0, sz-1) for idx, sz in zip(indexes, input_shape)]
|
||||
|
||||
if mode == "nearest":
|
||||
indexes = [_apply_nearest_mode(index, shape, nearest_mode) for (index, shape) in zip(indexes, input_shape)]
|
||||
mode_operations = {
|
||||
"round_prefer_floor": lambda idx: (idx - 0.5).ceil(),
|
||||
"round_prefer_ceil": lambda idx: (idx + 0.5).floor(),
|
||||
"floor": lambda idx: idx.floor(),
|
||||
"ceil": lambda idx: idx.ceil()
|
||||
}
|
||||
if nearest_mode not in mode_operations: raise ValueError(f"invalid {nearest_mode=}")
|
||||
indexes = [mode_operations[nearest_mode](idx).int() for idx in indexes]
|
||||
X = X[(..., *Tensor.meshgrid(*indexes))]
|
||||
|
||||
if mode == "linear":
|
||||
expand = list(X.shape)
|
||||
for i in range(-len(sizes), 0):
|
||||
@@ -757,7 +815,48 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
|
||||
reshape[i] = expand[i] = sizes[i]
|
||||
low, high, perc = [y.reshape(reshape).expand(expand) for y in (index.floor().int(), index.ceil().int(), index - index.floor())]
|
||||
X = X.gather(i, low).lerp(X.gather(i, high), perc)
|
||||
if mode == "cubic": raise NotImplementedError("cubic interpolation is not implemented")
|
||||
|
||||
if mode == "cubic":
|
||||
A = cubic_coeff_a
|
||||
|
||||
def W(x:Tensor):
|
||||
# Keys weights
|
||||
# see piecewise function in: https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm
|
||||
x = x.abs()
|
||||
w0_1 = polyN(x, [A + 2, -(A + 3), 0, 1])
|
||||
w1_2 = polyN(x, [A, -5 * A, 8 * A, -4 * A])
|
||||
return (x <= 1).where(w0_1, (x < 2).where(w1_2, 0))
|
||||
|
||||
expand = list(X.shape)
|
||||
for i in range(-len(sizes), 0):
|
||||
input_sz = X.shape[i]
|
||||
reshape, index = [1] * X.ndim, indexes[i]
|
||||
reshape[i] = expand[i] = sizes[i]
|
||||
|
||||
p = index.floor().int()
|
||||
ratio = index - p
|
||||
|
||||
# Neighbor indices
|
||||
idx0, idx1, idx2, idx3 = [p + d for d in [-1, 0, 1, 2]]
|
||||
# Weights of distance from index and neighbor indices
|
||||
c0, c1, c2, c3 = [W(ratio - d) for d in [-1, 0, 1, 2]]
|
||||
|
||||
if exclude_outside:
|
||||
c0 = ((idx0 >= 0) & (idx0 < input_sz)).where(c0, 0)
|
||||
c1 = ((idx1 >= 0) & (idx1 < input_sz)).where(c1, 0)
|
||||
c2 = ((idx2 >= 0) & (idx2 < input_sz)).where(c2, 0)
|
||||
c3 = ((idx3 >= 0) & (idx3 < input_sz)).where(c3, 0)
|
||||
|
||||
total = c0 + c1 + c2 + c3
|
||||
c0, c1, c2, c3 = c0 / (total + 1e-9), c1 / (total + 1e-9), c2 / (total + 1e-9), c3 / (total + 1e-9)
|
||||
|
||||
# Reshape and expand
|
||||
expanded_indices = [y.clip(0, input_sz - 1).reshape(reshape).expand(expand) for y in [idx0, idx1, idx2, idx3]]
|
||||
expanded_coeffs = [y.reshape(reshape).expand(expand) for y in [c0, c1, c2, c3]]
|
||||
|
||||
# Gather values and apply coefficients
|
||||
gathered_values = [X.gather(i, idx) for idx in expanded_indices]
|
||||
X = sum(v * c for v, c in zip(gathered_values, expanded_coeffs))
|
||||
return X.permute(*argsort(perm)) if perm else X
|
||||
def Upsample(X, scales, mode): return Resize(X=X, scales=scales, mode=mode) # deprecated
|
||||
|
||||
@@ -1050,7 +1149,7 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
|
||||
# ***** Quantization Ops *****
|
||||
def QuantizeLinear(x:Tensor, y_scale:Tensor, y_zero_point:Tensor|int=0, axis:int=1, block_size:int=0, output_dtype:int=0, saturate=1):
|
||||
if isinstance(y_zero_point, Tensor): out_dtype = y_zero_point.dtype
|
||||
elif output_dtype != 0: out_dtype = dtype_parse(output_dtype, "QuantizeLinear op")
|
||||
elif output_dtype != 0: out_dtype = dtype_fallback(OnnxDataType(output_dtype).to_dtype(), "QuantizeLinear op")
|
||||
else: out_dtype = dtypes.uint8
|
||||
y_scale, y_zero_point = _prepare_quantize(x, y_scale, y_zero_point, axis, block_size)
|
||||
if out_dtype == dtypes.uchar:
|
||||
|
||||
@@ -5,9 +5,9 @@ from tinygrad.nn import Linear
|
||||
from tinygrad.tensor import Tensor
|
||||
from tinygrad.nn.optim import Adam
|
||||
from tinygrad.nn.state import get_parameters, get_state_dict, safe_save, safe_load, load_state_dict
|
||||
from tinygrad.opt.search import actions
|
||||
from tinygrad.codegen.opt.search import actions
|
||||
from extra.optimization.helpers import load_worlds, ast_str_to_lin, lin_to_feats, assert_same_lin
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.helpers import getenv
|
||||
|
||||
# stuff needed to unpack a kernel
|
||||
@@ -17,7 +17,7 @@ from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import View
|
||||
from tinygrad.uop.ops import Variable
|
||||
inf, nan = float('inf'), float('nan')
|
||||
from tinygrad.opt.kernel import Opt, OptOps
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps
|
||||
|
||||
INNER = 256
|
||||
class PolicyNet:
|
||||
|
||||
@@ -10,11 +10,11 @@ from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import View
|
||||
from tinygrad.uop.ops import Variable
|
||||
inf, nan = float('inf'), float('nan')
|
||||
from tinygrad.opt.kernel import Opt, OptOps
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps
|
||||
|
||||
# more stuff
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.opt.search import actions
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.search import actions
|
||||
from extra.optimization.helpers import lin_to_feats
|
||||
from extra.optimization.pretrain_valuenet import ValueNet
|
||||
from tinygrad.nn.optim import Adam
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import random
|
||||
from extra.optimization.helpers import load_worlds, ast_str_to_lin
|
||||
from tinygrad.opt.search import actions
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.codegen.opt.search import actions
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.helpers import tqdm
|
||||
|
||||
tactions = set()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# stuff needed to unpack a kernel
|
||||
from tinygrad import Variable
|
||||
from tinygrad.opt.kernel import Opt, OptOps
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps
|
||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo
|
||||
from tinygrad.dtype import dtypes, PtrDType
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
@@ -11,7 +11,7 @@ inf, nan = float('inf'), float('nan')
|
||||
UOps = Ops
|
||||
|
||||
# kernel unpacker
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
def ast_str_to_ast(ast_str:str) -> UOp: return eval(ast_str)
|
||||
def ast_str_to_lin(ast_str:str, opts=None): return Kernel(ast_str_to_ast(ast_str), opts=opts)
|
||||
def kern_str_to_lin(kern_str:str, opts=None):
|
||||
@@ -103,7 +103,7 @@ def lin_to_feats(lin:Kernel, use_sts=True):
|
||||
return ret
|
||||
|
||||
from tinygrad.device import Device, Buffer
|
||||
from tinygrad.opt.search import _ensure_buffer_alloc, _time_program
|
||||
from tinygrad.codegen.opt.search import _ensure_buffer_alloc, _time_program
|
||||
from tinygrad.helpers import to_function_name, CACHELEVEL, diskcache_get, diskcache_put
|
||||
|
||||
def time_linearizer(lin:Kernel, rawbufs:list[Buffer], allow_test_size=True, max_global_size=65536, cnt=3, disable_cache=False, clear_l2=False) -> float: # noqa: E501
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tqdm import tqdm, trange
|
||||
import math
|
||||
import random
|
||||
@@ -14,7 +14,7 @@ from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import View
|
||||
from tinygrad.uop.ops import Variable
|
||||
inf, nan = float('inf'), float('nan')
|
||||
from tinygrad.opt.kernel import Opt, OptOps
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps
|
||||
|
||||
from extra.optimization.helpers import lin_to_feats, MAX_DIMS
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import numpy as np
|
||||
import math, random
|
||||
from tinygrad.tensor import Tensor
|
||||
from tinygrad.nn.state import get_parameters, get_state_dict, safe_save, safe_load, load_state_dict
|
||||
from tinygrad.opt.search import actions, bufs_from_lin, get_kernel_actions
|
||||
from tinygrad.codegen.opt.search import actions, bufs_from_lin, get_kernel_actions
|
||||
from tinygrad.nn.optim import Adam
|
||||
from extra.optimization.extract_policynet import PolicyNet
|
||||
from extra.optimization.helpers import load_worlds, ast_str_to_lin, lin_to_feats, time_linearizer
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import List, Tuple
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.opt.search import get_kernel_actions, actions
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.search import get_kernel_actions, actions
|
||||
|
||||
_net = None
|
||||
def beam_q_estimate(beam:List[Tuple[Kernel, float]]) -> List[Tuple[Kernel, float]]:
|
||||
|
||||
@@ -4,8 +4,8 @@ from extra.optimization.helpers import ast_str_to_lin, time_linearizer
|
||||
from tinygrad import dtypes
|
||||
from tinygrad.helpers import BEAM, getenv
|
||||
from tinygrad.device import Device, Compiled
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.opt.search import beam_search, bufs_from_lin
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.search import beam_search, bufs_from_lin
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -6,8 +6,8 @@ from copy import deepcopy
|
||||
from tinygrad.helpers import getenv, colored
|
||||
from tinygrad.tensor import Tensor
|
||||
from tinygrad.nn.state import get_parameters, get_state_dict, safe_save, safe_load, load_state_dict
|
||||
from tinygrad.opt.search import bufs_from_lin, actions, get_kernel_actions
|
||||
from tinygrad.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.codegen.opt.search import bufs_from_lin, actions, get_kernel_actions
|
||||
from tinygrad.codegen.opt.heuristic import hand_coded_optimizations
|
||||
from extra.optimization.helpers import load_worlds, ast_str_to_lin, lin_to_feats, time_linearizer
|
||||
from extra.optimization.extract_policynet import PolicyNet
|
||||
from extra.optimization.pretrain_valuenet import ValueNet
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from extra.optimization.helpers import load_worlds, ast_str_to_lin, time_linearizer
|
||||
from tinygrad.opt.search import bufs_from_lin, get_kernel_actions
|
||||
from tinygrad.codegen.opt.search import bufs_from_lin, get_kernel_actions
|
||||
|
||||
if __name__ == "__main__":
|
||||
ast_strs = load_worlds()
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ from tinygrad.helpers import getenv, BEAM
|
||||
from tinygrad.engine.jit import TinyJit
|
||||
from tinygrad.engine.realize import CompiledRunner, ExecItem, ScheduleItem, lower_schedule_item, get_program
|
||||
from tinygrad.renderer import ProgramSpec
|
||||
from tinygrad.opt.kernel import Kernel, Opt, OptOps
|
||||
from tinygrad.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.codegen.opt.kernel import Kernel, Opt, OptOps
|
||||
from tinygrad.codegen.opt.heuristic import hand_coded_optimizations
|
||||
import numpy as np
|
||||
|
||||
def move_jit_captured_to_dev(captured, device="DSP"):
|
||||
|
||||
@@ -223,15 +223,18 @@ def max_unpool2d(self:torch.Tensor, indices:torch.Tensor, output_size):
|
||||
|
||||
@torch.library.impl("aten::arange", "privateuseone")
|
||||
def arange(end, dtype=None, device=None, pin_memory=None):
|
||||
return wrap(Tensor.arange(0, end, dtype=_from_torch_dtype(dtype or torch.get_default_dtype())))
|
||||
has_float = isinstance(end, float)
|
||||
return wrap(Tensor.arange(0, end, dtype=_from_torch_dtype(dtype or (torch.get_default_dtype() if has_float else torch.int64))))
|
||||
|
||||
@torch.library.impl("aten::arange.start", "privateuseone")
|
||||
def arange_start(start, end, dtype=None, device=None, pin_memory=None):
|
||||
return wrap(Tensor.arange(start, end, dtype=_from_torch_dtype(dtype or torch.get_default_dtype())))
|
||||
has_float = any(isinstance(x, float) for x in (start, end))
|
||||
return wrap(Tensor.arange(start, end, dtype=_from_torch_dtype(dtype or (torch.get_default_dtype() if has_float else torch.int64))))
|
||||
|
||||
@torch.library.impl("aten::arange.start_step", "privateuseone")
|
||||
def arange_start_step(start, end, step, dtype=None, device=None, pin_memory=None):
|
||||
return wrap(Tensor.arange(start, end, step, dtype=_from_torch_dtype(dtype or torch.get_default_dtype())))
|
||||
has_float = any(isinstance(x, float) for x in (start, end, step))
|
||||
return wrap(Tensor.arange(start, end, step, dtype=_from_torch_dtype(dtype or (torch.get_default_dtype() if has_float else torch.int64))))
|
||||
|
||||
@torch.library.impl("aten::convolution_overrideable", "privateuseone")
|
||||
def convolution_overrideable(input, weight, bias, stride, padding, dilation, transposed, output_padding, groups):
|
||||
@@ -368,6 +371,7 @@ from torch._decomp import get_decompositions
|
||||
decomps = [
|
||||
aten.native_batch_norm, aten.native_batch_norm_backward,
|
||||
aten.native_layer_norm_backward,
|
||||
aten.linalg_cross,
|
||||
aten.addmm,
|
||||
aten.addcmul,
|
||||
aten.addcdiv,
|
||||
|
||||
@@ -135,7 +135,7 @@ class TestTorchBackend(unittest.TestCase):
|
||||
print(c.cpu())
|
||||
|
||||
def test_maxpool2d_backward(self):
|
||||
x = torch.arange(3*3, device=device).reshape(1, 1, 3, 3).requires_grad_(True)
|
||||
x = torch.arange(3*3, dtype=torch.float32, device=device).reshape(1, 1, 3, 3).requires_grad_(True)
|
||||
torch.nn.functional.max_pool2d(x, kernel_size=2, stride=1).sum().backward()
|
||||
np.testing.assert_equal(x.grad.squeeze().cpu().numpy(), [[0, 0, 0], [0, 1, 1], [0, 1, 1]])
|
||||
|
||||
@@ -203,6 +203,12 @@ class TestTorchBackend(unittest.TestCase):
|
||||
b = torch.linalg.det(a)
|
||||
np.testing.assert_equal(b.cpu().numpy(), 120.0)
|
||||
|
||||
def test_linalg_cross(self):
|
||||
a = torch.tensor([[1, 0, 0], [0, 1, 0]], dtype=torch.float32, device=device)
|
||||
b = torch.tensor([[0, 0, 1]], dtype=torch.float32, device=device)
|
||||
cross = torch.linalg.cross(a, b)
|
||||
np.testing.assert_equal(cross.cpu().numpy(), np.array([[0, -1, 0], [1, 0, 0]], dtype=np.float32))
|
||||
|
||||
def test_scalar_assign(self):
|
||||
a = torch.tensor([1, 2, 3], device=device)
|
||||
a[1] = 4
|
||||
|
||||
@@ -26,7 +26,7 @@ setup(name='tinygrad',
|
||||
long_description_content_type='text/markdown',
|
||||
packages = ['tinygrad', 'tinygrad.runtime.autogen', 'tinygrad.runtime.autogen.am', 'tinygrad.codegen', 'tinygrad.nn',
|
||||
'tinygrad.renderer', 'tinygrad.engine', 'tinygrad.viz', 'tinygrad.runtime', 'tinygrad.runtime.support', 'tinygrad.schedule',
|
||||
'tinygrad.runtime.support.am', 'tinygrad.runtime.graph', 'tinygrad.shape', 'tinygrad.uop', 'tinygrad.opt',
|
||||
'tinygrad.runtime.support.am', 'tinygrad.runtime.graph', 'tinygrad.shape', 'tinygrad.uop', 'tinygrad.codegen.opt',
|
||||
'tinygrad.runtime.support.nv', 'tinygrad.apps'],
|
||||
package_data = {'tinygrad': ['py.typed'], 'tinygrad.viz': ['index.html', 'assets/**/*', 'js/*']},
|
||||
classifiers=[
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import random
|
||||
from tinygrad.helpers import getenv
|
||||
from tinygrad.opt.search import beam_search, bufs_from_lin
|
||||
from tinygrad.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.codegen.opt.search import beam_search, bufs_from_lin
|
||||
from tinygrad.codegen.opt.heuristic import hand_coded_optimizations
|
||||
from extra.optimization.helpers import load_worlds, ast_str_to_lin, time_linearizer
|
||||
|
||||
def optimize_kernel(k):
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ if __name__ == "__main__":
|
||||
GlobalCounters.reset()
|
||||
t.softmax(-1, dtype="half", _single_kernel=True).realize()
|
||||
|
||||
from tinygrad.opt.kernel import Kernel, Opt, OptOps
|
||||
from tinygrad.codegen.opt.kernel import Kernel, Opt, OptOps
|
||||
from tinygrad.helpers import get_single_element
|
||||
GlobalCounters.reset()
|
||||
si = get_single_element(t.softmax(-1, dtype="half", _single_kernel=True).schedule())
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
# ruff: noqa: E501
|
||||
from tinygrad.opt.kernel import Kernel, Opt, OptOps
|
||||
from tinygrad.codegen.opt.kernel import Kernel, Opt, OptOps
|
||||
from tinygrad.dtype import dtypes
|
||||
from tinygrad.engine.realize import CompiledRunner, get_program
|
||||
from tinygrad.opt.search import bufs_from_lin
|
||||
from tinygrad.codegen.opt.search import bufs_from_lin
|
||||
from tinygrad.uop.ops import UOp, Ops
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import View
|
||||
|
||||
+2
-2
@@ -4,10 +4,10 @@ os.environ["VALIDATE_HCQ"]="1"
|
||||
|
||||
import unittest, random
|
||||
import numpy as np
|
||||
from tinygrad.opt.kernel import Kernel, KernelOptError
|
||||
from tinygrad.codegen.opt.kernel import Kernel, KernelOptError
|
||||
from tinygrad.device import is_dtype_supported
|
||||
from tinygrad.uop.ops import UOp, Ops
|
||||
from tinygrad.opt.search import Opt, OptOps
|
||||
from tinygrad.codegen.opt.search import Opt, OptOps
|
||||
from tinygrad import Device, dtypes, Tensor
|
||||
from test.external.fuzz_linearizer import compare_linearizer, compare_states, get_fuzz_rawbuf_like
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ from tinygrad.runtime.support.hip_comgr import compile_hip
|
||||
from tinygrad import Tensor
|
||||
from tinygrad.device import Device
|
||||
from tinygrad.engine.schedule import create_schedule
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
|
||||
class TestHIPCompileSpeed(unittest.TestCase):
|
||||
@unittest.skipIf(Device.DEFAULT != "HIP", "only run on HIP")
|
||||
|
||||
Vendored
+2
-2
@@ -2,11 +2,11 @@ import unittest, struct, array, ctypes
|
||||
from tinygrad import Device, dtypes, Tensor
|
||||
from tinygrad.helpers import to_mv
|
||||
from tinygrad.runtime.ops_nv import NVDevice, HWQueue
|
||||
from tinygrad.opt.search import Opt, OptOps
|
||||
from tinygrad.codegen.opt.search import Opt, OptOps
|
||||
from tinygrad.engine.realize import get_runner, CompiledRunner, get_program
|
||||
from test.external.fuzz_linearizer import get_fuzz_rawbufs
|
||||
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.uop.ops import LazyOp, Ops, ReduceOps, BufferOps, MemBuffer
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import View
|
||||
|
||||
+7
-4
@@ -53,6 +53,7 @@ backend_test.exclude('test_dynamicquantizelinear_cpu')
|
||||
backend_test.exclude('test_dynamicquantizelinear_expanded_cpu')
|
||||
|
||||
# BUG: ORT fails these with numerical error but we match ORT numerically
|
||||
# see: https://onnx.ai/backend-scoreboard/onnxruntime_details_stable.html
|
||||
# tested in external_test_onnx_ops.py::TestMainOnnxOps.test_qlinearmatmul_2D_int8_float16
|
||||
backend_test.exclude('test_qlinearmatmul_2D_int8_float16_cpu')
|
||||
# tested in external_test_onnx_ops.py::TestMainOnnxOps.test_qlinearmatmul_3D_int8_float16
|
||||
@@ -65,6 +66,10 @@ backend_test.exclude('test_qlinearmatmul_3D_int8_float32_cpu')
|
||||
backend_test.exclude('test_maxunpool_export_with_output_shape_cpu')
|
||||
# tested in external_test_onnx_ops.py::TestMainOnnxOps.test_averagepool_3d_dilations_large_count_include_pad_is_1_ceil_mode_is_True
|
||||
backend_test.exclude('test_averagepool_3d_dilations_large_count_include_pad_is_1_ceil_mode_is_True_cpu')
|
||||
# tested in external_test_onnx_ops.py::TestMainOnnxOps.test_resize_downsample_scales_linear_align_corners
|
||||
backend_test.exclude('test_resize_downsample_scales_linear_align_corners_cpu')
|
||||
# tested in external_test_onnx_ops.py::TestMainOnnxOps.test_resize_downsample_scales_cubic_align_corners
|
||||
backend_test.exclude('test_resize_downsample_scales_cubic_align_corners_cpu')
|
||||
|
||||
# about different dtypes
|
||||
if not is_dtype_supported(dtypes.float64):
|
||||
@@ -165,10 +170,6 @@ backend_test.exclude('test_deform_conv_*')
|
||||
backend_test.exclude('test_lppool_*')
|
||||
backend_test.exclude('test_scan_*')
|
||||
backend_test.exclude('test_split_to_sequence_*')
|
||||
backend_test.exclude('test_resize_downsample_scales_cubic_*') # unsure how to implement cubic
|
||||
backend_test.exclude('test_resize_downsample_sizes_cubic_*') # unsure how to implement cubic
|
||||
backend_test.exclude('test_resize_upsample_scales_cubic_*') # unsure how to implement cubic
|
||||
backend_test.exclude('test_resize_upsample_sizes_cubic_*') # unsure how to implement cubic
|
||||
backend_test.exclude('test_ai_onnx_ml_tree_ensemble_*') # https://github.com/onnx/onnx/blob/main/onnx/reference/ops/aionnxml/op_tree_ensemble.py#L121
|
||||
|
||||
# rest of the failing tests
|
||||
@@ -178,6 +179,8 @@ backend_test.exclude('test_resize_tf_crop_and_resize_axes_3_2_cpu') # tf_crop_an
|
||||
backend_test.exclude('test_resize_tf_crop_and_resize_extrapolation_value_cpu') # tf_crop_and_resize value not implemented
|
||||
backend_test.exclude('test_resize_downsample_scales_linear_antialias_cpu') # antialias not implemented
|
||||
backend_test.exclude('test_resize_downsample_sizes_linear_antialias_cpu') # antialias not implemented
|
||||
backend_test.exclude('test_resize_downsample_scales_cubic_antialias_cpu') # antialias not implemented
|
||||
backend_test.exclude('test_resize_downsample_sizes_cubic_antialias_cpu') # antialias not implemented
|
||||
backend_test.exclude('test_ai_onnx_ml_label_encoder_tensor_value_only_mapping_cpu') # bad data type string
|
||||
backend_test.exclude('test_ai_onnx_ml_label_encoder_tensor_mapping_cpu') # bad data type string
|
||||
|
||||
|
||||
+43
@@ -75,6 +75,49 @@ class TestMainOnnxOps(TestOnnxOps):
|
||||
outputs = ["y"]
|
||||
self.helper_test_single_op("Gather", inputs, attributes, outputs)
|
||||
|
||||
# NOTE: resize OP is sensitive to numerical errors
|
||||
def _test_resize_scales(self, scale_values, **kwargs):
|
||||
for sc in scale_values:
|
||||
for ct_mode in ["half_pixel", "align_corners", "asymmetric", "pytorch_half_pixel", "half_pixel_symmetric"]:
|
||||
with self.subTest(coordinate_transformation_mode=ct_mode, scale=sc, **kwargs):
|
||||
X = np.array([[[[1, 2, 3, 4],
|
||||
[5, 6, 7, 8],
|
||||
[9,10,11,12]]]], dtype=np.float32)
|
||||
scales = np.array([1.0, 1.0, sc, sc], dtype=np.float32)
|
||||
inputs = {"X": X, "roi": np.array([], dtype=np.float32), "scales": scales}
|
||||
attributes = {"coordinate_transformation_mode": ct_mode, **kwargs}
|
||||
outputs = ["out"]
|
||||
self.helper_test_single_op("Resize", inputs, attributes, outputs)
|
||||
|
||||
def test_resize_linear_mode(self):
|
||||
self._test_resize_scales([0.01, 0.25, 0.5, 0.51, 0.6, 1.0, 1.5, 2.0, 3.5, 20.0], mode="linear")
|
||||
|
||||
def test_resize_nearest_mode(self):
|
||||
# excluded 3.5 because some values divide into slight numerical differences, which when rounded gives wrong results
|
||||
self._test_resize_scales([0.01, 0.25, 0.5, 0.51, 0.6, 1.0, 1.5, 2.0, 20.0], mode="nearest")
|
||||
|
||||
def test_resize_cubic_mode(self):
|
||||
self._test_resize_scales([0.01, 0.25, 0.5, 0.51, 0.6, 1.0, 1.5, 2.0, 3.5, 20.0], mode="cubic", exclude_outside=1)
|
||||
self._test_resize_scales([0.01, 0.25, 0.5, 0.51, 0.6, 1.0, 1.5, 2.0, 3.5, 20.0], mode="cubic", exclude_outside=0)
|
||||
|
||||
def test_resize_downsample_scales_linear_align_corners(self):
|
||||
# https://github.com/onnx/onnx/blob/main/docs/Operators.md#examples-131
|
||||
X = np.array([[[[1, 2, 3, 4], [5, 6, 7, 8]]]], dtype=np.float32)
|
||||
scales = np.array([1.0, 1.0, 0.6, 0.6], dtype=np.float32)
|
||||
inputs = {"X": X, "roi": np.array([], dtype=np.float32), "scales": scales}
|
||||
attributes = {"mode": "linear", "coordinate_transformation_mode": "align_corners"}
|
||||
outputs = ["out"]
|
||||
self.helper_test_single_op("Resize", inputs, attributes, outputs)
|
||||
|
||||
def test_resize_downsample_scales_cubic_align_corners(self):
|
||||
# https://github.com/onnx/onnx/blob/main/docs/Operators.md#examples-131
|
||||
X = np.array([[[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]]], dtype=np.float32)
|
||||
scales = np.array([1.0, 1.0, 0.8, 0.8], dtype=np.float32)
|
||||
inputs = {"X": X, "roi": np.array([], dtype=np.float32), "scales": scales}
|
||||
attributes = {"mode": "cubic", "coordinate_transformation_mode": "align_corners"}
|
||||
outputs = ["out"]
|
||||
self.helper_test_single_op("Resize", inputs, attributes, outputs)
|
||||
|
||||
def test_maxunpool_export_with_output_shape(self):
|
||||
# https://github.com/onnx/onnx/blob/main/docs/Operators.md#examples-91
|
||||
xT = np.array([[[[5, 6], [7, 8]]]], dtype=np.float32)
|
||||
|
||||
+4
-4
@@ -3,7 +3,7 @@ import numpy as np
|
||||
from tinygrad import dtypes, Tensor
|
||||
from tinygrad.uop.ops import Ops
|
||||
from tinygrad.device import is_dtype_supported
|
||||
from extra.onnx import data_types
|
||||
from extra.onnx import OnnxDataType
|
||||
from tinygrad.frontend.onnx import OnnxRunner
|
||||
from hypothesis import given, strategies as st
|
||||
|
||||
@@ -86,8 +86,8 @@ class TestOnnxRunner(unittest.TestCase):
|
||||
output = runner({'inp': Tensor([1])})['output']
|
||||
np.testing.assert_equal(output.numpy(), weights + 1)
|
||||
|
||||
all_dtypes = list(data_types.keys())
|
||||
device_supported_dtypes = {odt for odt, dtype in data_types.items() if is_dtype_supported(dtype)}
|
||||
all_dtypes = list(OnnxDataType)
|
||||
device_supported_dtypes = {odt for odt in OnnxDataType if is_dtype_supported(odt.to_dtype())}
|
||||
|
||||
class TestOnnxRunnerDtypes(unittest.TestCase):
|
||||
"""
|
||||
@@ -95,7 +95,7 @@ class TestOnnxRunnerDtypes(unittest.TestCase):
|
||||
External tensors (inputs) preserve their original dtype - user must ensure compatibility with device.
|
||||
"""
|
||||
def _get_expected_dtype(self, onnx_dtype: int, is_input: bool):
|
||||
true_dtype = data_types[onnx_dtype]
|
||||
true_dtype = OnnxDataType(onnx_dtype).to_dtype()
|
||||
# inputs always preserve their true dtype.
|
||||
if is_input:
|
||||
return true_dtype
|
||||
|
||||
+2
-2
@@ -2,11 +2,11 @@
|
||||
import unittest
|
||||
|
||||
from tinygrad.uop.ops import UOp, Ops
|
||||
from tinygrad.opt.search import Opt, OptOps
|
||||
from tinygrad.codegen.opt.search import Opt, OptOps
|
||||
from tinygrad.dtype import dtypes
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import View
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
|
||||
from test.external.fuzz_linearizer import run_linearizer
|
||||
|
||||
|
||||
Vendored
+19
-26
@@ -1,33 +1,26 @@
|
||||
import random
|
||||
from z3 import Int, Solver, sat
|
||||
from tinygrad import dtypes, Device
|
||||
from tinygrad.uop.ops import UOp, Ops, UPat, graph_rewrite, PatternMatcher
|
||||
from tinygrad.codegen.optional import fast_idiv
|
||||
import z3
|
||||
from tinygrad import dtypes
|
||||
from tinygrad.uop.spec import z3_renderer, z3_cdiv
|
||||
from tinygrad.uop.ops import UOp, graph_rewrite
|
||||
from tinygrad.uop.transcendental import fast_idiv
|
||||
random.seed(42)
|
||||
|
||||
z3_renderer = PatternMatcher([
|
||||
(UPat((Ops.DEFINE_VAR, Ops.SPECIAL), name="x"), lambda x: UOp(Ops.NOOP, arg=x.arg[0])),
|
||||
# Because fast_idiv only works for non-negative integers we can emulate machine arithmetic with modulo operations.
|
||||
(UPat(Ops.SHR, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"(({x.src[0].arg}/(2**{x.src[1].arg}))%{dtypes.max(x.dtype)+1})")),
|
||||
(UPat(Ops.MUL, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"(({x.src[0].arg}*{x.src[1].arg})%{dtypes.max(x.dtype)+1})")),
|
||||
(UPat((Ops.CONST, Ops.VCONST), name="x"), lambda x: UOp(Ops.NOOP, arg=str(x.arg))),
|
||||
(UPat(Ops.CAST, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"{x.src[0].arg}")),
|
||||
])
|
||||
|
||||
def render(self) -> str:
|
||||
ret = graph_rewrite(self.simplify(), z3_renderer)
|
||||
return ret.arg if ret.op is Ops.NOOP else str(ret)
|
||||
|
||||
powers_of_two = [2**i for i in range(64)]
|
||||
if __name__ == "__main__":
|
||||
x = Int('x')
|
||||
for _ in range(10_000):
|
||||
for i in range(10_000):
|
||||
if i % 1000 == 0:
|
||||
print(f"Progress: {i}")
|
||||
dt = random.choice(dtypes.ints)
|
||||
u = UOp(Ops.DEFINE_VAR, dt, arg=('x', 0, random.randint(1, dtypes.max(dt))), src=())
|
||||
u = UOp.variable('x', random.randint(dt.min, 0), random.randint(1, dt.max), dtype=dt)
|
||||
d = random.randint(1, max(1, u.arg[2]))
|
||||
|
||||
expr = fast_idiv(Device[Device.DEFAULT].renderer, u, d)
|
||||
if d in powers_of_two: continue
|
||||
expr = fast_idiv(None, u, d)
|
||||
if expr is None: continue
|
||||
solver = Solver()
|
||||
solver.add(x>=u.arg[1], x<=u.arg[2])
|
||||
if solver.check(eval(render(expr)) != x/d) == sat:
|
||||
assert False, f"Failed: {render(expr)} != x//{d} at x={solver.model()[x]}\nx={u}\nd={d}"
|
||||
|
||||
solver = z3.Solver()
|
||||
z3_sink = graph_rewrite(expr.sink(u), z3_renderer, ctx=(solver, {}))
|
||||
z3_expr, x = z3_sink.src[0].arg, z3_sink.src[1].arg
|
||||
|
||||
if solver.check(z3_expr != z3_cdiv(x, d)) == z3.sat:
|
||||
assert False, f"Failed: {expr.render()} != x//{d} at x={solver.model()}\nx={u}\nd={d}\n{z3_expr=}\n{x/d=}"
|
||||
|
||||
Vendored
+3
-3
@@ -21,9 +21,9 @@ if os.getenv("VALIDATE_HCQ", 0) != 0:
|
||||
|
||||
from tinygrad import Tensor, Device, dtypes
|
||||
from tinygrad.tensor import _to_np_dtype
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.opt.kernel import Opt, OptOps
|
||||
from tinygrad.opt.search import get_kernel_actions, bufs_from_lin
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps
|
||||
from tinygrad.codegen.opt.search import get_kernel_actions, bufs_from_lin
|
||||
from tinygrad.engine.realize import CompiledRunner
|
||||
from tinygrad.helpers import getenv, from_mv, prod, colored, Context, DEBUG, Timing
|
||||
from tinygrad.uop.ops import UOp, Ops
|
||||
|
||||
+10
-3
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# compare kernels created by HEAD against master
|
||||
import os, multiprocessing, logging, pickle, sqlite3, difflib, warnings, itertools, functools, base64, codecs
|
||||
from dataclasses import replace
|
||||
from typing import Callable, Any
|
||||
|
||||
ASSERT_DIFF = int((flag:="[pr]") in os.getenv("COMMIT_MESSAGE", flag) or flag in os.getenv("PR_TITLE", flag))
|
||||
@@ -11,7 +12,9 @@ try:
|
||||
from tinygrad.renderer import Renderer, ProgramSpec
|
||||
from tinygrad.engine.realize import get_program
|
||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo
|
||||
from tinygrad.codegen.opt.kernel import Opt
|
||||
from tinygrad.helpers import VERSION, Context, ContextVar, colored, db_connection, getenv, tqdm
|
||||
from tinygrad.device import Device
|
||||
except ImportError as e:
|
||||
print(repr(e))
|
||||
exit(int(ASSERT_DIFF))
|
||||
@@ -47,9 +50,13 @@ def replay_kernelize(ret:dict[UOp, UOp], big_sink:UOp) -> tuple[str, str, tuple[
|
||||
return "\n".join([f"{len(asts)} kernels", *asts])
|
||||
return to_str(new_sink), to_str(ret[big_sink]), (big_sink,)
|
||||
|
||||
def replay_get_program(p:ProgramSpec, ast:UOp, renderer:Renderer) -> tuple[str, str, tuple[Any, ...]]:
|
||||
input_ast = ast.replace(arg=KernelInfo(opts_to_apply=p.applied_opts, name=p.name)) if ast.arg is None else ast
|
||||
p2 = get_program(input_ast, renderer)
|
||||
def replay_get_program(p:ProgramSpec, ast:UOp, renderer:Renderer|None=None, opts:list[Opt]|None=None) -> tuple[str, str, tuple[Any, ...]]:
|
||||
# NOTE: this always uses the opts_to_apply path
|
||||
sink_arg = ast.arg or KernelInfo(opts_to_apply=p.applied_opts)
|
||||
input_ast = ast.replace(arg=replace(sink_arg, name=p.name))
|
||||
# if no renderer was provided, open the device to get it
|
||||
if renderer is None: renderer = Device[p.device].renderer
|
||||
p2 = get_program(input_ast, renderer=renderer)
|
||||
def to_str(ret:ProgramSpec) -> str:
|
||||
# PYTHON renderer pickles UOps, first unpickle and decode here
|
||||
if p.device.startswith("PYTHON"): return "\n".join([str(x) for x in pickle.loads(base64.b64decode(ret.src))])
|
||||
|
||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
from tinygrad import Device
|
||||
from tinygrad.helpers import getenv, DEBUG, BEAM
|
||||
from tinygrad.opt.search import beam_search, bufs_from_lin
|
||||
from tinygrad.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.codegen.opt.search import beam_search, bufs_from_lin
|
||||
from tinygrad.codegen.opt.heuristic import hand_coded_optimizations
|
||||
from extra.optimization.helpers import load_worlds, ast_str_to_lin, time_linearizer
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ from tinygrad import Device, dtypes
|
||||
from tinygrad.helpers import getenv, colorize_float, DEBUG
|
||||
from extra.optimization.helpers import load_worlds, ast_str_to_lin
|
||||
from test.external.fuzz_linearizer import get_fuzz_rawbufs
|
||||
from tinygrad.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.opt.search import bufs_from_lin
|
||||
from tinygrad.codegen.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.codegen.opt.search import bufs_from_lin
|
||||
from tinygrad.engine.realize import CompiledRunner, get_program
|
||||
from tinygrad.tensor import _to_np_dtype
|
||||
from tinygrad.runtime.ops_amd import AMDDevice
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ from tinygrad import Device, dtypes
|
||||
from tinygrad.helpers import getenv, colorize_float
|
||||
from extra.optimization.helpers import load_worlds, ast_str_to_lin
|
||||
from test.external.fuzz_linearizer import get_fuzz_rawbufs
|
||||
from tinygrad.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.opt.search import bufs_from_lin
|
||||
from tinygrad.codegen.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.codegen.opt.search import bufs_from_lin
|
||||
from tinygrad.engine.realize import CompiledRunner, get_program
|
||||
from tinygrad.tensor import _to_np_dtype
|
||||
import numpy as np
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import itertools
|
||||
from tinygrad import Device
|
||||
from tinygrad.engine.realize import CompiledRunner, get_program
|
||||
from tinygrad.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.codegen.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.helpers import getenv, colorize_float
|
||||
from extra.optimization.helpers import load_worlds, ast_str_to_lin
|
||||
from tinygrad.opt.search import bufs_from_lin
|
||||
from tinygrad.codegen.opt.search import bufs_from_lin
|
||||
from tinygrad.runtime.ops_cuda import PTXCompiler, PTXRenderer, CUDACompiler
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Vendored
+1
-1
@@ -3,7 +3,7 @@ from collections import defaultdict
|
||||
from extra.optimization.helpers import kern_str_to_lin, time_linearizer
|
||||
from test.external.fuzz_linearizer import compare_linearizer
|
||||
from tinygrad.helpers import colored
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
|
||||
# Use this with the LOGKERNS options to verify that all executed kernels are valid and evaluate to the same ground truth results
|
||||
|
||||
|
||||
+2
-2
@@ -3,9 +3,9 @@ import numpy as np
|
||||
from tinygrad import Tensor, GlobalCounters, dtypes, nn, Device, Variable
|
||||
from tinygrad.helpers import CI, Context, getenv
|
||||
from tinygrad.engine.realize import run_schedule
|
||||
from tinygrad.opt.kernel import Opt, OptOps, Kernel, KernelOptError
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps, Kernel, KernelOptError
|
||||
from tinygrad.engine.realize import CompiledRunner, ExecItem, get_program
|
||||
from tinygrad.opt.search import get_kernel_actions
|
||||
from tinygrad.codegen.opt.search import get_kernel_actions
|
||||
from tinygrad.uop.ops import Ops
|
||||
from tinygrad.codegen import apply_rewrites, rewrites_for_views
|
||||
|
||||
|
||||
@@ -290,17 +290,12 @@ class TestMultiConstFolding(unittest.TestCase):
|
||||
np.testing.assert_equal((t + zero).numpy(), np.arange(16))
|
||||
np.testing.assert_equal((t * zero).numpy(), [0] * 16)
|
||||
np.testing.assert_equal((t * one).numpy(), np.arange(16))
|
||||
|
||||
def test_multi_todo_pow(self):
|
||||
ds = tuple(f"{Device.DEFAULT}:{i}" for i in range(4))
|
||||
t = Tensor.arange(16).float().to(ds).realize()
|
||||
zero = Tensor.zeros(16).to(ds).realize()
|
||||
one = Tensor.ones(16).to(ds).realize()
|
||||
|
||||
# TODO: fix pow folding
|
||||
_check_ast_count(0, t ** zero)
|
||||
_check_ast_count(0, t ** one)
|
||||
_check_ast_count(0, one ** t)
|
||||
np.testing.assert_equal((t ** zero).numpy(), [1] * 16)
|
||||
np.testing.assert_equal((t ** one).numpy(), np.arange(16))
|
||||
np.testing.assert_equal((one ** t).numpy(), [1] * 16)
|
||||
|
||||
class TestTautologicalCompare(unittest.TestCase):
|
||||
# without const folding, these would have triggered -Wtautological-compare in clang
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
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()
|
||||
@@ -62,7 +62,6 @@ class TestNaNEdgeCases(unittest.TestCase):
|
||||
class TestEmptyTensorEdgeCases(unittest.TestCase):
|
||||
# we don't need more of these
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_sort_empty(self):
|
||||
# Sorting an empty tensor works in PyTorch and should return empty
|
||||
# values and indices. tinygrad raises an error instead.
|
||||
@@ -219,7 +218,6 @@ class TestAssignIssues(unittest.TestCase):
|
||||
t.shrink(((1, 3), (1, 3))).assign(Tensor.ones(2, 2))
|
||||
np.testing.assert_allclose(t.numpy(), torch_tensor.numpy())
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_assign_broadcast(self):
|
||||
# broadcasting during assign should behave like PyTorch
|
||||
torch_tensor = torch.zeros(3, 5)
|
||||
@@ -258,12 +256,11 @@ class TestEdgeCases(unittest.TestCase):
|
||||
out = Tensor(arr).pad((1, -1, 1, -1), mode='circular')
|
||||
np.testing.assert_equal(out.numpy(), torch_out.numpy())
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_arange_float_step(self):
|
||||
# float steps should match PyTorch exactly
|
||||
torch_out = torch.arange(0, 2, 0.3).numpy()
|
||||
out = Tensor.arange(0, 2, 0.3).numpy()
|
||||
np.testing.assert_allclose(out, torch_out)
|
||||
np.testing.assert_allclose(out, torch_out, atol=1e-7)
|
||||
|
||||
@unittest.skip("this is flaky")
|
||||
@unittest.expectedFailure
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ from tinygrad.runtime.support.hcq import HCQCompiled, HCQBuffer
|
||||
from tinygrad.runtime.autogen import libc
|
||||
from tinygrad.runtime.support.system import PCIIfaceBase
|
||||
from tinygrad.engine.realize import get_runner, CompiledRunner, get_program
|
||||
from tinygrad.opt.kernel import Opt, OptOps
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps
|
||||
from tinygrad import Variable
|
||||
|
||||
MOCKGPU = getenv("MOCKGPU")
|
||||
|
||||
+1
-4
@@ -718,8 +718,6 @@ class TestJitGraphSplit(unittest.TestCase):
|
||||
def ji_xfer(self): return {"type": "xfer"}
|
||||
|
||||
def test_jit_split_simple(self):
|
||||
if Device.DEFAULT == "REMOTE": raise unittest.SkipTest("REMOTE gpu is broken")
|
||||
|
||||
@TinyJit
|
||||
def f(inp):
|
||||
op0 = self.compute(Device.DEFAULT, inp)
|
||||
@@ -792,9 +790,9 @@ class TestJitGraphSplit(unittest.TestCase):
|
||||
multigraph=[self.ji_graph(5)],
|
||||
hcqgraph=[self.ji_graph(5)])
|
||||
|
||||
@unittest.skip("flaky")
|
||||
def test_jit_multidev_xfer(self):
|
||||
if Device.DEFAULT in {"CPU", "LLVM"}: raise unittest.SkipTest("CPU/LLVM is not a valid default device for this test (zero-copies)")
|
||||
if Device.DEFAULT == "METAL" or REAL_DEV == "METAL": raise unittest.SkipTest("Metal is flaky, with multidevice (same as metal llama 4gpu?)")
|
||||
|
||||
try: Device[f"{Device.DEFAULT}:1"]
|
||||
except Exception: raise unittest.SkipTest("no multidevice")
|
||||
@@ -819,7 +817,6 @@ class TestJitGraphSplit(unittest.TestCase):
|
||||
@unittest.skipIf(getenv("MOCKGPU"), "MockGPU does not support parallel copies")
|
||||
def test_jit_multidev_copy(self):
|
||||
if Device.DEFAULT in {"CPU", "LLVM"}: raise unittest.SkipTest("CPU/LLVM is not a valid default device for this test (zero-copies)")
|
||||
if Device.DEFAULT == "REMOTE": raise unittest.SkipTest("REMOTE gpu is broken")
|
||||
|
||||
@TinyJit
|
||||
def f(inp):
|
||||
|
||||
@@ -2,7 +2,7 @@ import numpy as np
|
||||
import unittest
|
||||
from dataclasses import replace
|
||||
|
||||
from tinygrad.opt.kernel import Opt, OptOps, KernelOptError, Kernel, AxisType
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps, KernelOptError, Kernel, AxisType
|
||||
from tinygrad.codegen.gpudims import get_grouped_dims
|
||||
from tinygrad.uop.ops import UOp, Ops, GroupOp, KernelInfo
|
||||
from tinygrad.device import Device, Buffer, is_dtype_supported
|
||||
@@ -10,7 +10,7 @@ from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import View
|
||||
from tinygrad.tensor import Tensor, _to_np_dtype
|
||||
from tinygrad.engine.realize import run_schedule, lower_schedule, CompiledRunner, get_program
|
||||
from tinygrad.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.codegen.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.helpers import prod, Context, getenv, CI, flatten, dedup, AMX, AMD_LLVM
|
||||
from tinygrad.dtype import DType, dtypes, AddrSpace
|
||||
from tinygrad.codegen import apply_rewrites, rewrites_for_views
|
||||
@@ -410,7 +410,7 @@ class TestLinearizer(unittest.TestCase):
|
||||
np.testing.assert_allclose(result, golden_result, atol=0.1, rtol=0.2)
|
||||
|
||||
# check that get_kernel_actions produces all 9 options
|
||||
from tinygrad.opt.search import get_kernel_actions
|
||||
from tinygrad.codegen.opt.search import get_kernel_actions
|
||||
tc_actions = [k for i, k in get_kernel_actions(Kernel(realized_ast), False).items() if k.applied_opts[0].op == OptOps.TC]
|
||||
|
||||
available_tc = len([x for x in Device[Device.DEFAULT].renderer.tensor_cores if x.dtype_in == tc.dtype_in and x.dtype_out == tc.dtype_out])
|
||||
|
||||
@@ -8,8 +8,8 @@ from tinygrad.device import is_dtype_supported
|
||||
from tinygrad.uop.ops import UOp, Ops
|
||||
from tinygrad.helpers import getenv
|
||||
from tinygrad.shape.shapetracker import ShapeTracker, View
|
||||
from tinygrad.opt.search import Opt, OptOps
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.search import Opt, OptOps
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.engine.realize import get_program
|
||||
|
||||
class TestLinearizerDumb(unittest.TestCase):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# ruff: noqa: E501
|
||||
import unittest
|
||||
from tinygrad import dtypes
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.opt.search import Opt, OptOps, bufs_from_lin
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.search import Opt, OptOps, bufs_from_lin
|
||||
from extra.optimization.helpers import time_linearizer
|
||||
|
||||
# stuff needed to unpack a kernel
|
||||
|
||||
@@ -440,6 +440,13 @@ class TestNN(unittest.TestCase):
|
||||
result = layer(a)
|
||||
self.assertEqual(result.shape, shp + (embed_size,))
|
||||
|
||||
def test_embedding_regression(self):
|
||||
# used to fail bounds check
|
||||
with Context(FUSE_ARANGE=1):
|
||||
embedding = Embedding(100, 1024)
|
||||
input_ids = Tensor.empty(16, 16)
|
||||
embedding(input_ids).realize()
|
||||
|
||||
def test_load_state_dict(self):
|
||||
layer = Conv2d(3, 5, kernel_size=3)
|
||||
|
||||
|
||||
+18
-3
@@ -699,6 +699,14 @@ class TestOps(unittest.TestCase):
|
||||
helper_test_op(None, lambda x: x**-0.3, vals=[[0.0]])
|
||||
helper_test_op(None, lambda x: x**-1.0, vals=[[-1.0, 0.0, 1.0]])
|
||||
|
||||
def test_int_pow_const_int(self):
|
||||
helper_test_op(None, lambda x: x**0, vals=[[-2,0,2]], forward_only=True, atol=0)
|
||||
helper_test_op(None, lambda x: x**1, vals=[[-2,0,2]], forward_only=True, atol=0)
|
||||
helper_test_op(None, lambda x: x**2, vals=[[-2,0,2]], forward_only=True, atol=0)
|
||||
helper_test_op(None, lambda x: x**7, vals=[[11,12,13]], forward_only=True, atol=0)
|
||||
helper_test_op(None, lambda x: x**29, vals=[[-2,0,2]], forward_only=True, atol=0)
|
||||
self.helper_test_exception(None, lambda x: x**-2, vals=[[-2,0,2]], forward_only=True, expected=RuntimeError)
|
||||
|
||||
@unittest.skip("not supported")
|
||||
def test_pow_int(self):
|
||||
def _test(base, exponent): helper_test_op(None, lambda x,y: x**y, vals=[base, exponent], forward_only=True)
|
||||
@@ -1093,6 +1101,9 @@ class TestOps(unittest.TestCase):
|
||||
helper_test_op(None, lambda x: x.type(torch.int32).argmin().type(torch.int32), lambda x: x.argmin(), forward_only=True, vals=[[True, False]])
|
||||
|
||||
def test_sort(self):
|
||||
for shape in [(0,), (0,5), (1,), (1,5)]:
|
||||
helper_test_op([shape], lambda x: x.sort(0).values, lambda x: x.sort(0)[0], forward_only=True)
|
||||
helper_test_op([shape], lambda x: x.sort(0).indices.type(torch.int32), lambda x: x.sort(0)[1], forward_only=True)
|
||||
for dim in [-1, 0, 1]:
|
||||
for descending in [True, False]:
|
||||
helper_test_op([(8,8,6)], lambda x: x.sort(dim, descending).values, lambda x: x.sort(dim, descending)[0], forward_only=True)
|
||||
@@ -2739,10 +2750,10 @@ class TestOps(unittest.TestCase):
|
||||
helper_test_op([(2,5,6,5,3,4)], lambda x: x[[[0]]], lambda x: x[[[0]]])
|
||||
helper_test_op([(2,5,6,5,3,4)], lambda x: x[[0],b,c,d,:], lambda x: x[[0],j,k,o,:])
|
||||
helper_test_op([(2,5,6,5,3,4)], lambda x: x[[[[0]]],b,c,d,[[1]]], lambda x: x[[[[0]]],j,k,o,[[1]]])
|
||||
helper_test_op([(2,5,6,5,3,4)], lambda x: x[[1,0],b,c,d,:], lambda x: x[[1,0],j,k,o,:])
|
||||
helper_test_op([(2,5,6,5,3,4)], lambda x: x[[1,0,-1],b,c,d,:], lambda x: x[[1,0,-1],j,k,o,:])
|
||||
helper_test_op([(2,5,6,5,3,4)], lambda x: x[a,b,c,[1,2,3],...], lambda x: x[i,j,k,[1,2,3],...])
|
||||
helper_test_op([(2,5,6,5,3,4)], lambda x: x[a,b,c,[[1],[2],[3]],...], lambda x: x[i,j,k,[[1],[2],[3]],...])
|
||||
helper_test_op([(2,5,6,5,3,4)], lambda x: x[a,[2,1,0],c,[2,1,0],e], lambda x: x[i,[2,1,0],k,[2,1,0],p])
|
||||
helper_test_op([(2,5,6,5,3,4)], lambda x: x[a,[2,1,0],c,[-2,1,0],e], lambda x: x[i,[2,1,0],k,[-2,1,0],p])
|
||||
|
||||
def test_slice_fancy_indexing_tuple_indices(self):
|
||||
a,b,c,d,e,i,j,k,o,p = self._get_index_randoms()
|
||||
@@ -2765,10 +2776,14 @@ class TestOps(unittest.TestCase):
|
||||
a = Tensor.ones(10,11,12)
|
||||
# tensors used as indices must be int tensors
|
||||
with self.assertRaises(IndexError): a[Tensor(1.1)]
|
||||
with self.assertRaises(IndexError): a[Tensor([True, True])]
|
||||
with self.assertRaises(IndexError): a[[1.1]]
|
||||
with self.assertRaises(IndexError): a[Tensor([True, False])]
|
||||
with self.assertRaises(IndexError): a[[True, False]]
|
||||
# shape mismatch, cannot broadcast. either exception is okay
|
||||
with self.assertRaises((IndexError, ValueError)): a[Tensor.randint(3,1,1,1), Tensor.randint(1,4,1,1), Tensor.randint(2,4,4,1)]
|
||||
with self.assertRaises((IndexError, ValueError)): a[Tensor.randint(3,1,1,1), Tensor.randint(1,4,1,1,1)]
|
||||
# this is fine
|
||||
helper_test_op([(5, 6)], lambda x: x[[True, False, 2]])
|
||||
|
||||
def test_gather(self):
|
||||
# indices cannot have gradient
|
||||
|
||||
@@ -2,7 +2,7 @@ import numpy as np
|
||||
import unittest
|
||||
from tinygrad import Tensor
|
||||
from tinygrad.helpers import get_single_element
|
||||
from tinygrad.opt.kernel import Opt, OptOps
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps
|
||||
from tinygrad.engine.realize import CompiledRunner, ExecItem, get_program
|
||||
|
||||
class TestOptGemm(unittest.TestCase):
|
||||
|
||||
@@ -4,9 +4,9 @@ import unittest
|
||||
from dataclasses import replace
|
||||
from tinygrad import Tensor, Context, Device, dtypes
|
||||
from tinygrad.uop.ops import Ops, UOp # noqa: F401 # pylint: disable=unused-import
|
||||
from tinygrad.opt.kernel import Kernel, Opt, OptOps
|
||||
from tinygrad.codegen.opt.kernel import Kernel, Opt, OptOps
|
||||
from tinygrad.engine.realize import CompiledRunner, ExecItem, lower_schedule_item, get_program
|
||||
from tinygrad.opt.search import bufs_from_lin
|
||||
from tinygrad.codegen.opt.search import bufs_from_lin
|
||||
from tinygrad.shape.shapetracker import ShapeTracker, View # noqa: F401 # pylint: disable=unused-import
|
||||
|
||||
N = 512
|
||||
|
||||
@@ -1741,15 +1741,15 @@ class TestIndexing(unittest.TestCase):
|
||||
|
||||
def test_simple_indexing_alt(self):
|
||||
X = Tensor.arange(16).reshape(4, 4)
|
||||
xt = X[[1, 2], [1, 2]]
|
||||
self.check_schedule(xt, 3)
|
||||
np.testing.assert_equal(xt.numpy(), (np.arange(16).reshape(4, 4))[[1, 2], [1, 2]])
|
||||
xt = X[[1, 2], [-1, 2]]
|
||||
self.check_schedule(xt, 1)
|
||||
np.testing.assert_equal(xt.numpy(), (np.arange(16).reshape(4, 4))[[1, 2], [-1, 2]])
|
||||
|
||||
def test_advanced_indexing(self):
|
||||
X = Tensor.arange(10)+1
|
||||
xt = X[[0]]
|
||||
self.check_schedule(xt, 2)
|
||||
np.testing.assert_equal(xt.numpy(), (np.arange(10)+1)[[0]])
|
||||
xt = X[[0, -1]]
|
||||
self.check_schedule(xt, 1)
|
||||
np.testing.assert_equal(xt.numpy(), (np.arange(10)+1)[[0, -1]])
|
||||
|
||||
def test_advanced_indexing_alt(self):
|
||||
X = Tensor.arange(6).reshape(3, 2)+1
|
||||
@@ -1759,8 +1759,8 @@ class TestIndexing(unittest.TestCase):
|
||||
|
||||
def test_advanced_simple_indexing_combined(self):
|
||||
X = Tensor.arange(16).reshape(4, 4)
|
||||
xt = X[1:2, [1, 2]]
|
||||
self.check_schedule(xt, 2)
|
||||
xt = X[1:2, [-1, 2]]
|
||||
self.check_schedule(xt, 1)
|
||||
|
||||
def test_push_through_reshape(self):
|
||||
Tensor.manual_seed(0)
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
import unittest
|
||||
|
||||
from tinygrad.opt.kernel import Opt, OptOps, Kernel
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps, Kernel
|
||||
from tinygrad.uop.ops import UOp, Ops
|
||||
from tinygrad.opt.search import bufs_from_lin, actions, beam_search
|
||||
from tinygrad.codegen.opt.search import bufs_from_lin, actions, beam_search
|
||||
from tinygrad.device import Device
|
||||
from tinygrad.tensor import Tensor
|
||||
from tinygrad.dtype import dtypes
|
||||
@@ -36,7 +36,7 @@ class TestBEAM(unittest.TestCase):
|
||||
|
||||
def test_get_kernel_actions_dedup(self):
|
||||
from test.test_linearizer import helper_realized_ast
|
||||
from tinygrad.opt.search import get_kernel_actions
|
||||
from tinygrad.codegen.opt.search import get_kernel_actions
|
||||
a = Tensor.empty(4, 3)
|
||||
b = Tensor.empty(3)
|
||||
realized_ast, _ = helper_realized_ast(a @ b)
|
||||
@@ -61,7 +61,7 @@ class TestBEAM(unittest.TestCase):
|
||||
@unittest.skipUnless(Device[Device.DEFAULT].renderer.tensor_cores, "test requires tensor cores")
|
||||
def test_search_over_shape(self):
|
||||
from test.test_linearizer import helper_realized_ast
|
||||
from tinygrad.opt.search import get_kernel_actions
|
||||
from tinygrad.codegen.opt.search import get_kernel_actions
|
||||
|
||||
dtype_pairs = [(tc.dtype_in, tc.dtype_out) for tc in Device[Device.DEFAULT].renderer.tensor_cores]
|
||||
multi_shape_dtype_pairs = [dts for dts in dtype_pairs if dtype_pairs.count(dts) > 1]
|
||||
@@ -78,7 +78,7 @@ class TestBEAM(unittest.TestCase):
|
||||
|
||||
def test_get_kernel_actions_preserves_actions_state(self):
|
||||
from test.test_linearizer import helper_realized_ast
|
||||
from tinygrad.opt.search import get_kernel_actions
|
||||
from tinygrad.codegen.opt.search import get_kernel_actions
|
||||
a = Tensor.rand(16, 16)
|
||||
b = Tensor.rand(16, 16)
|
||||
realized_ast, _ = helper_realized_ast(a @ b)
|
||||
|
||||
+12
-1
@@ -14,7 +14,7 @@ from tinygrad.engine.realize import CompiledRunner, get_program
|
||||
from tinygrad.codegen import full_rewrite
|
||||
from tinygrad.uop.symbolic import sym
|
||||
from tinygrad.device import is_dtype_supported
|
||||
from tinygrad.opt.kernel import Opt, OptOps
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps
|
||||
|
||||
def to_uops_list(u:list[UOp], opts=None, skip_check=False) -> list[UOp]: return full_rewrite(UOp.sink(*u), opts)
|
||||
|
||||
@@ -415,6 +415,17 @@ class TestAssembly(unittest.TestCase):
|
||||
uops = program.uops
|
||||
self.assertEqual(len([x.op for x in uops if x.op is Ops.MULACC]), 4)
|
||||
|
||||
def test_use_cmpeq(self):
|
||||
g = UOp(Ops.DEFINE_GLOBAL, dtypes.uint32.ptr(), (), 0)
|
||||
c = UOp(Ops.CONST, dtypes.uint, (), 7)
|
||||
l = UOp(Ops.LOAD, dtypes.uint, (g.index(c),))
|
||||
comp = l.ne(c).ne(True)
|
||||
uops = to_uops_list([comp], opts=Device[Device.DEFAULT].renderer)
|
||||
Device[Device.DEFAULT].renderer.render(uops)
|
||||
ops = [x.op for x in uops]
|
||||
self.assertIn(Ops.CMPEQ, ops)
|
||||
self.assertNotIn(Ops.CMPNE, ops)
|
||||
|
||||
class TestUOpMethod(unittest.TestCase):
|
||||
@unittest.skip("uops lt no longer ordered")
|
||||
def test_compare_alu_same_src_different_arg(self):
|
||||
|
||||
@@ -6,7 +6,7 @@ from tinygrad.renderer import Estimates
|
||||
from tinygrad.codegen import full_rewrite
|
||||
from tinygrad.uop.ops import Ops, UOp
|
||||
from tinygrad.dtype import dtypes
|
||||
from tinygrad.opt.kernel import Opt, OptOps, KernelOptError
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps, KernelOptError
|
||||
from tinygrad.device import Device
|
||||
|
||||
def flops_mem(uops, ignore_indexing=False):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import unittest
|
||||
from tinygrad import Tensor, Context, Device
|
||||
from tinygrad.engine.realize import get_program
|
||||
from tinygrad.opt.kernel import Opt, OptOps
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps
|
||||
from tinygrad.uop.ops import KernelInfo
|
||||
|
||||
class TestLinearizerRewrite(unittest.TestCase):
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import unittest
|
||||
from dataclasses import dataclass, field
|
||||
from tinygrad.uop.ops import PatternMatcher, UOp, graph_rewrite, Ops, UPat, GroupOp, RewriteNotReady
|
||||
|
||||
# we could insert CHILDREN node
|
||||
|
||||
@dataclass
|
||||
class ChildrenContext:
|
||||
children: dict[UOp, list[UOp]]|None = None
|
||||
|
||||
# this is a generic child labeller
|
||||
def extract_children(ctx:ChildrenContext, x:UOp):
|
||||
if ctx.children is not None: return
|
||||
ctx.children = {k:list(v.keys()) for k,v in x.get_children_map().items() if len(v) > 1}
|
||||
|
||||
def mark_children(ctx:ChildrenContext, x:UOp):
|
||||
new_srcs = [(UOp(Ops.CHILD, s.dtype, src=(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}, name="x"), mark_children),
|
||||
])
|
||||
|
||||
@dataclass
|
||||
class TestContext:
|
||||
seen_children: dict[UOp, set[int]] = field(default_factory=dict)
|
||||
ready_children: dict[UOp, set[int]] = field(default_factory=dict)
|
||||
seen_consts:int = 0
|
||||
saved_seen_consts:int = 0
|
||||
exp2_visit_count:int = 0
|
||||
|
||||
# this is a generic pattern
|
||||
def visit_child(ctx:ChildrenContext, x:UOp):
|
||||
if x.src[0] not in ctx.seen_children:
|
||||
ctx.seen_children[x.src[0]] = set()
|
||||
ctx.ready_children[x.src[0]] = set()
|
||||
ctx.seen_children[x.src[0]].add(x.arg[0])
|
||||
if len(ctx.seen_children[x.src[0]]) != x.arg[1]:
|
||||
print(f"visit CHILD {x.arg} bottom up -- not ready {ctx.seen_children[x.src[0]]}")
|
||||
raise RewriteNotReady
|
||||
print(f"visit CHILD {x.arg} bottom up -- READY {ctx.seen_children[x.src[0]]}")
|
||||
ctx.ready_children[x.src[0]].add(x.arg[0])
|
||||
|
||||
pm_child_visitor = PatternMatcher([
|
||||
(UPat(Ops.CHILD, name="x"), visit_child),
|
||||
])
|
||||
|
||||
# this is for the test
|
||||
def see_const(ctx:ChildrenContext, c:UOp): ctx.seen_consts += c.arg
|
||||
def see_exp2(ctx:ChildrenContext): ctx.exp2_visit_count += 1
|
||||
def save_seen_consts(ctx:ChildrenContext, x:UOp): ctx.saved_seen_consts = ctx.seen_consts
|
||||
pm_consts = PatternMatcher([
|
||||
(UPat(Ops.DEFINE_VAR, name="x"), save_seen_consts),
|
||||
(UPat()+UPat.cvar("c"), see_const),
|
||||
(UPat(Ops.EXP2), see_exp2),
|
||||
])
|
||||
|
||||
class TestChildrenRewrite(unittest.TestCase):
|
||||
def test_not_ready_double_simple(self):
|
||||
global_a = UOp.variable("a", 0, 10).exp2()
|
||||
inter = (global_a+global_a).exp2()
|
||||
global_sink = (inter+inter).sink()
|
||||
|
||||
sink = graph_rewrite(global_sink, pm_children, ctx=ChildrenContext(), bottom_up=True)
|
||||
ctx = TestContext()
|
||||
graph_rewrite(sink, pm_consts, ctx=ctx, bottom_up=True)
|
||||
self.assertEqual(ctx.exp2_visit_count, 2)
|
||||
|
||||
def test_not_ready_double(self):
|
||||
global_a = UOp.variable("a", 0, 10).exp2()
|
||||
inter = ((global_a+1000)+(global_a+100)).exp2()
|
||||
global_sink = ((inter+10)+(inter+1)).sink()
|
||||
|
||||
sink = graph_rewrite(global_sink, pm_children, ctx=ChildrenContext(), bottom_up=True)
|
||||
print("test_not_ready_double")
|
||||
ctx = TestContext()
|
||||
graph_rewrite(sink, pm_child_visitor+pm_consts, ctx=ctx, bottom_up=True)
|
||||
self.assertEqual(ctx.exp2_visit_count, 2)
|
||||
self.assertEqual(ctx.seen_consts, ctx.saved_seen_consts)
|
||||
self.assertEqual(ctx.seen_consts, 1111)
|
||||
|
||||
def test_in_srcs_twice(self):
|
||||
global_a = UOp.variable("a", 0, 10).exp2()
|
||||
global_sink = (global_a+global_a).sink()
|
||||
|
||||
ctx = TestContext()
|
||||
graph_rewrite(global_sink, pm_consts, ctx=ctx, bottom_up=True)
|
||||
self.assertEqual(ctx.exp2_visit_count, 1)
|
||||
|
||||
def test_not_ready(self):
|
||||
global_a = UOp.variable("a", 0, 10).exp2()
|
||||
global_sink = ((global_a+2)+(global_a+3)).sink()
|
||||
|
||||
# without children and not ready, we don't see both adds before the DEFINE_VAR
|
||||
ctx = TestContext()
|
||||
graph_rewrite(global_sink, pm_consts, ctx=ctx, bottom_up=True)
|
||||
self.assertNotEqual(ctx.seen_consts, ctx.saved_seen_consts)
|
||||
self.assertEqual(ctx.exp2_visit_count, 1)
|
||||
|
||||
# with children and not ready we do
|
||||
sink = graph_rewrite(global_sink, pm_children, ctx=ChildrenContext(), bottom_up=True)
|
||||
ctx = TestContext()
|
||||
graph_rewrite(sink, pm_child_visitor+pm_consts, ctx=ctx, bottom_up=True)
|
||||
self.assertEqual(ctx.seen_consts, ctx.saved_seen_consts)
|
||||
self.assertEqual(ctx.exp2_visit_count, 1)
|
||||
self.assertSetEqual(list(ctx.ready_children.values())[0], {0,1})
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,8 +1,8 @@
|
||||
import unittest
|
||||
from tinygrad import Tensor, Device
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.device import Buffer
|
||||
from tinygrad.opt.search import get_test_global_size, bufs_from_lin
|
||||
from tinygrad.codegen.opt.search import get_test_global_size, bufs_from_lin
|
||||
from tinygrad.helpers import GlobalCounters
|
||||
from extra.optimization.helpers import time_linearizer
|
||||
from test.test_linearizer import push_views
|
||||
|
||||
@@ -203,9 +203,9 @@ class TestSymbolic(unittest.TestCase):
|
||||
def test_mod_min_max(self):
|
||||
self.helper_test_variable(Variable("x", 0, 10)%Variable("y", 1, 10), 0, 9, "(x%y)")
|
||||
self.helper_test_variable(Variable("x", -10, 0)%Variable("y", 1, 10), -9, 0, "(((x*-1)%y)*-1)")
|
||||
self.helper_test_variable(Variable("x", 0, 10)%Variable("y", -10, -1), 0, 9, "(x%y)")
|
||||
self.helper_test_variable(Variable("x", -10, 0)%Variable("y", -10, -1), -9, 0, "(((x*-1)%y)*-1)")
|
||||
self.helper_test_variable(Variable("x", -10, 10)%Variable("y", -10, -1), -9, 9, "(x%y)")
|
||||
self.helper_test_variable(Variable("x", 0, 10)%Variable("y", -10, -1), 0, 9, "(x%(y*-1))")
|
||||
self.helper_test_variable(Variable("x", -10, 0)%Variable("y", -10, -1), -9, 0, "(((x*-1)%(y*-1))*-1)")
|
||||
self.helper_test_variable(Variable("x", -10, 10)%Variable("y", -10, -1), -9, 9, "(x%(y*-1))")
|
||||
|
||||
# test _min_max directly without the rewrite taking out the sign
|
||||
self.assertEqual((Variable("x", -10, 0)%Variable("y", -10, -1))._min_max, (-9, 0))
|
||||
@@ -296,7 +296,7 @@ class TestSymbolic(unittest.TestCase):
|
||||
def test_neg_mod(self):
|
||||
a = Variable("a", 0, 124)
|
||||
self.helper_test_variable((-a)%4, -3, 0, "((a%4)*-1)")
|
||||
self.helper_test_variable(a%-4, 0, 3, "(a%-4)")
|
||||
self.helper_test_variable(a%-4, 0, 3, "(a%4)")
|
||||
|
||||
def test_distribute_mul(self):
|
||||
self.helper_test_variable(usum([Variable("a", 0, 3), Variable("b", 0, 5)])*3, 0, 24, "((a*3)+(b*3))")
|
||||
|
||||
@@ -97,9 +97,9 @@ class TestViz(BaseTestViz):
|
||||
|
||||
# name can also come from a function that returns a string
|
||||
def test_dyn_name_fxn(self):
|
||||
@track_rewrites(name=lambda a,ret: a.render())
|
||||
def name_from_fxn(s:UOp): return graph_rewrite(s, PatternMatcher([]))
|
||||
name_from_fxn(UOp.variable("a", 1, 10)+1)
|
||||
@track_rewrites(name=lambda *args,ret,**kwargs: ret.render())
|
||||
def name_from_fxn(s:UOp, arg:list|None=None): return graph_rewrite(s, PatternMatcher([]))
|
||||
name_from_fxn(UOp.variable("a", 1, 10)+1, arg=["test"])
|
||||
lst = get_viz_list()
|
||||
# name gets deduped by the function call counter
|
||||
self.assertEqual(lst[0]["name"], "(a+1) n1")
|
||||
|
||||
@@ -11,13 +11,13 @@ from tinygrad.codegen.lowerer import pm_lowerer, get_index
|
||||
from tinygrad.codegen.quantize import pm_quant
|
||||
from tinygrad.codegen.gpudims import pm_add_gpudims
|
||||
from tinygrad.uop.symbolic import sym, symbolic_simple, gep_pushing
|
||||
from tinygrad.uop.optional import get_late_rewrite_patterns
|
||||
from tinygrad.codegen.expander import migrate_indexing, expander
|
||||
from tinygrad.codegen.devectorizer import load_store_folding, load_store_indexing, devectorize, pm_reduce, \
|
||||
ReduceContext, correct_load_store, pm_render
|
||||
from tinygrad.codegen.optional import get_late_rewrite_patterns
|
||||
from tinygrad.codegen.linearize import block_create, pm_blockend_merge, block_merge, pm_finalize, BlockContext
|
||||
from tinygrad.opt import pm_optimize
|
||||
from tinygrad.opt.swizzler import view_left, view_right, fix_kernel_ops
|
||||
from tinygrad.codegen.opt import pm_optimize
|
||||
from tinygrad.codegen.opt.swizzler import view_left, view_right, fix_kernel_ops
|
||||
|
||||
@dataclass
|
||||
class RewriteStep:
|
||||
@@ -82,12 +82,16 @@ def _get_rewrites_for_renderer(opts:Renderer, linearizer:bool, _QUANTIZE, _DEVEC
|
||||
supported_ops = tuple(opts.code_for_op.keys())
|
||||
extra_matcher = opts.extra_matcher if opts.extra_matcher is not None else PatternMatcher([])
|
||||
|
||||
# decompositions
|
||||
pm_decomp = symbolic_simple+get_late_rewrite_patterns(supported_ops, _TRANSCENDENTAL>=2)
|
||||
ret.append(RewriteStep(pm_decomp, name="decompositions"))
|
||||
|
||||
# optional pre matcher
|
||||
if opts.pre_matcher is not None: ret.append(RewriteStep(opts.pre_matcher, name="pre_matcher"))
|
||||
|
||||
# final rules for the renderer (without sym)
|
||||
pm_final_rewrite = symbolic_simple+get_late_rewrite_patterns(supported_ops, _TRANSCENDENTAL>=2)+pm_render+extra_matcher
|
||||
ret.append(RewriteStep(pm_final_rewrite, lambda _: opts, name="final rewrite"))
|
||||
pm_final_rewrite = pm_decomp+pm_render+extra_matcher
|
||||
ret.append(RewriteStep(pm_final_rewrite, lambda _: opts.device, name="final rewrite"))
|
||||
|
||||
# return the list (with optional linearizer)
|
||||
return ret + (rewrites_for_linearizer if linearizer else [])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# opt opinionatedly transforms an ast into an optimized ast using either heuristics or beam search
|
||||
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.uop.ops import UOp, PatternMatcher, UPat, Ops
|
||||
from tinygrad.helpers import NOOPT, BEAM, USE_TC, getenv
|
||||
from tinygrad.renderer import Renderer
|
||||
@@ -24,7 +24,7 @@ def get_optimized_ast(ast:UOp, renderer:Renderer) -> UOp:
|
||||
elif not NOOPT:
|
||||
if not k.apply_tensor_cores(USE_TC.value): k.apply_opts(hand_coded_optimizations(k))
|
||||
if BEAM >= 1:
|
||||
from tinygrad.opt.search import beam_search, bufs_from_lin
|
||||
from tinygrad.codegen.opt.search import beam_search, bufs_from_lin
|
||||
kb = Kernel(ast, opts=renderer)
|
||||
rawbufs = bufs_from_lin(kb, allocate=False)
|
||||
k = beam_search(kb, rawbufs, BEAM.value, bool(getenv("BEAM_ESTIMATE", 1)))
|
||||
@@ -1,5 +1,5 @@
|
||||
import itertools
|
||||
from tinygrad.opt.kernel import Kernel, Opt, OptOps, KernelOptError, AxisType
|
||||
from tinygrad.codegen.opt.kernel import Kernel, Opt, OptOps, KernelOptError, AxisType
|
||||
from tinygrad.helpers import getenv, DEBUG, prod, NOLOCALS
|
||||
from tinygrad.dtype import ImageDType
|
||||
from tinygrad.uop.ops import Ops, resolve
|
||||
@@ -8,13 +8,13 @@ from enum import Enum, auto
|
||||
from tinygrad.uop.ops import GroupOp, KernelInfo, UOp, Ops, can_pad, resolve, Variable, sint, graph_rewrite, AxisType
|
||||
from tinygrad.uop.spec import type_verify, ast_spec
|
||||
from tinygrad.device import Device
|
||||
from tinygrad.opt.tc import TensorCore
|
||||
from tinygrad.codegen.opt.tc import TensorCore
|
||||
from tinygrad.renderer import Renderer
|
||||
from tinygrad.dtype import ImageDType, AddrSpace
|
||||
from tinygrad.helpers import all_same, colored, ansilen, dedup, prod, round_up, to_function_name, unwrap, argfix, DEBUG, TC_SELECT, TC_OPT, AMX
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import strides_for_shape, get_contraction
|
||||
from tinygrad.opt.swizzler import view_left, view_left_through_load
|
||||
from tinygrad.codegen.opt.swizzler import view_left, view_left_through_load
|
||||
|
||||
class OptOps(Enum):
|
||||
TC = auto(); UPCAST = auto(); UNROLL = auto(); LOCAL = auto() # noqa: E702
|
||||
@@ -7,7 +7,7 @@ from tinygrad.device import Device, Buffer, Compiler
|
||||
from tinygrad.helpers import prod, flatten, DEBUG, CACHELEVEL, diskcache_get, diskcache_put, getenv, Context, colored, time_to_str
|
||||
from tinygrad.helpers import IGNORE_BEAM_CACHE, TC_SEARCH_OVER_SHAPE
|
||||
from tinygrad.dtype import ImageDType, PtrDType
|
||||
from tinygrad.opt.kernel import Kernel, Opt, OptOps, KernelOptError
|
||||
from tinygrad.codegen.opt.kernel import Kernel, Opt, OptOps, KernelOptError
|
||||
from tinygrad.tensor import Tensor
|
||||
from tinygrad.engine.realize import CompiledRunner, get_program
|
||||
from tinygrad.renderer import ProgramSpec
|
||||
@@ -180,7 +180,10 @@ def beam_search(lin:Kernel, rawbufs:list[Buffer], amt:int, allow_test_size=True,
|
||||
seen_libs.add(lib)
|
||||
try: tms = _time_program(p, lib, var_vals, rawbufs, early_stop=beam[0][1]*3 if len(beam) else 1.0,
|
||||
allow_test_size=allow_test_size, clear_l2=hasattr(dev, 'invalidate_caches'))
|
||||
except RuntimeError: continue # for runtime issues
|
||||
except Exception as e:
|
||||
if BEAM_DEBUG: print(f"BEAM failed for opts: {acted_lins[i].applied_opts}\n{e}")
|
||||
if isinstance(e, RuntimeError): continue
|
||||
raise
|
||||
timed_lins.append((acted_lins[i], min(tms)))
|
||||
if BEAM_DEBUG > 1: print(f"{time.perf_counter() - st:7.2f}s: {i:5d} {len(cast(list, p.uops)):5d} uops {time_to_str(compile_et, w=12)} compile/{time_to_str(timed_lins[-1][1], w=12)} run {len(timed_lins):4d}/{len(acted_lins):4d} {timed_lins[-1][0].colored_shape()}") # noqa: E501
|
||||
elif DEBUG >= 2: print(f"\r{time.perf_counter() - st:7.2f}s: {time_to_str(timed_lins[-1][1], w=12)} {len(timed_lins):4d}/{len(acted_lins):4d} {timed_lins[-1][0].colored_shape()}\033[K", end="") # noqa: E501
|
||||
+3
-6
@@ -1,10 +1,10 @@
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass, replace, field
|
||||
from dataclasses import dataclass, replace
|
||||
from collections import defaultdict
|
||||
from typing import Any, Generic, TypeVar, Iterator
|
||||
import importlib, inspect, functools, pathlib, os, platform, contextlib, sys, re, atexit, pickle, decimal, time
|
||||
from tinygrad.helpers import CI, OSX, LRU, getenv, diskcache_get, diskcache_put, DEBUG, GlobalCounters, flat_mv, PROFILE, temp, \
|
||||
colored, Context, DISABLE_COMPILER_CACHE, ALLOW_DEVICE_USAGE, cpu_events, ProfileEvent, dedup
|
||||
colored, Context, DISABLE_COMPILER_CACHE, ALLOW_DEVICE_USAGE, cpu_events, ProfileEvent, ProfilePointEvent, dedup
|
||||
from tinygrad.dtype import DType, ImageDType, PtrDType, dtypes, _to_np_dtype
|
||||
from tinygrad.renderer import Renderer
|
||||
|
||||
@@ -55,9 +55,6 @@ atexit.register(lambda: [Device[dn].finalize() for dn in Device._opened_devices]
|
||||
class ProfileDeviceEvent(ProfileEvent):
|
||||
device:str; comp_tdiff:decimal.Decimal=decimal.Decimal(0); copy_tdiff:decimal.Decimal=decimal.Decimal(0) # noqa: E702
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProfilePointEvent(ProfileEvent): device:str; name:str; st:decimal.Decimal; ref:int; arg:dict=field(default_factory=dict) # noqa: E702
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProfileProgramEvent(ProfileEvent): device:str; name:str; lib:bytes|None; base:int|None # noqa: E702
|
||||
|
||||
@@ -69,7 +66,6 @@ class ProfileGraphEvent(ProfileEvent): ents:list[ProfileGraphEntry]; deps:list[l
|
||||
|
||||
# **************** Buffer + Allocators ****************
|
||||
|
||||
|
||||
@dataclass(frozen=True, eq=True)
|
||||
class BufferSpec:
|
||||
# TODO: move device, size, dtype here?
|
||||
@@ -223,6 +219,7 @@ class Allocator(Generic[DeviceType]):
|
||||
def __init__(self, dev:DeviceType):
|
||||
self.dev: DeviceType = dev
|
||||
self.default_buffer_spec: BufferSpec = BufferSpec()
|
||||
self.supports_copy_from_disk: bool = True
|
||||
# overridden in LRUAllocator
|
||||
def alloc(self, size:int, options:BufferSpec|None=None):
|
||||
assert size > 0, f"alloc size must be positive, getting {size}"
|
||||
|
||||
@@ -8,11 +8,11 @@ from tinygrad.device import Device, Buffer
|
||||
from tinygrad.renderer import Renderer, ProgramSpec, Estimates
|
||||
from tinygrad.engine.schedule import ScheduleItem
|
||||
from tinygrad.codegen import full_rewrite
|
||||
from tinygrad.opt.kernel import Opt
|
||||
from tinygrad.codegen.opt.kernel import Opt
|
||||
|
||||
# **************** Program Creation ****************
|
||||
|
||||
@track_rewrites(name=lambda _ast,_renderer,ret: TracingKey(ret.name, (ret.function_name, ret.ast), ret=ret))
|
||||
@track_rewrites(name=lambda *args,ret,**kwargs: TracingKey(ret.name, (ret.function_name, ret.ast), ret=ret), replay=True)
|
||||
def get_program(ast:UOp, renderer:Renderer|None=None, opts:list[Opt]|None=None) -> ProgramSpec:
|
||||
"""
|
||||
Transform an AST into a ProgramSpec. May trigger BEAM search.
|
||||
@@ -77,7 +77,7 @@ class CompiledRunner(Runner):
|
||||
global_size, local_size = self.p.launch_dims(var_vals)
|
||||
if global_size is not None and local_size is None and all_int(self.p.global_size): # type: ignore[arg-type]
|
||||
# TODO: this is copied from get_program
|
||||
from tinygrad.opt.search import optimize_local_size
|
||||
from tinygrad.codegen.opt.search import optimize_local_size
|
||||
local_size = optimize_local_size(self._prg, global_size, rawbufs)
|
||||
global_size = [g//l if g%l == 0 else g/l for g,l in zip(global_size, local_size)]
|
||||
self.p = replace(self.p, global_size=global_size, local_size=local_size)
|
||||
@@ -102,7 +102,7 @@ class BufferCopy(Runner):
|
||||
super().__init__(colored(name, "yellow"), dest_device, Estimates(lds=total_sz, mem=total_sz))
|
||||
def copy(self, dest, src):
|
||||
disk_supports_fast_copyout = src.device.startswith("DISK") and hasattr(src.allocator.dev, 'io_uring') and \
|
||||
getattr(src.allocator.dev, 'fd', None) is not None
|
||||
getattr(src.allocator.dev, 'fd', None) is not None and dest.allocator.supports_copy_from_disk
|
||||
if src.device.startswith("DISK") and hasattr(dest.allocator, 'copy_from_disk') and disk_supports_fast_copyout and src.nbytes >= 4096:
|
||||
dest.allocator.copy_from_disk(dest._buf, src._buf, src.nbytes)
|
||||
elif src.device.startswith("DISK") and hasattr(dest.allocator, '_as_buffer'):
|
||||
|
||||
+5
-2
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
import os, functools, platform, time, re, contextlib, operator, hashlib, pickle, sqlite3, tempfile, pathlib, string, ctypes, sys, gzip, getpass
|
||||
import urllib.request, subprocess, shutil, math, types, copyreg, inspect, importlib, decimal
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from typing import ClassVar, Iterable, Any, TypeVar, Callable, Sequence, TypeGuard, Iterator, Generic, Generator
|
||||
|
||||
T = TypeVar("T")
|
||||
@@ -204,6 +204,9 @@ class ProfileEvent: pass
|
||||
@dataclass
|
||||
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
|
||||
|
||||
cpu_events:list[ProfileEvent] = []
|
||||
@contextlib.contextmanager
|
||||
def cpu_profile(name:str|TracingKey, device="CPU", is_copy=False, display=True) -> Generator[ProfileRangeEvent, None, None]:
|
||||
@@ -218,7 +221,7 @@ def cpu_profile(name:str|TracingKey, device="CPU", is_copy=False, display=True)
|
||||
cache_dir: str = os.path.join(getenv("XDG_CACHE_HOME", os.path.expanduser("~/Library/Caches" if OSX else "~/.cache")), "tinygrad")
|
||||
CACHEDB: str = getenv("CACHEDB", os.path.abspath(os.path.join(cache_dir, "cache.db")))
|
||||
|
||||
VERSION = 21
|
||||
VERSION = 22
|
||||
_db_connection = None
|
||||
def db_connection():
|
||||
global _db_connection
|
||||
|
||||
@@ -6,8 +6,8 @@ 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
|
||||
if TYPE_CHECKING:
|
||||
from tinygrad.opt.tc import TensorCore
|
||||
from tinygrad.opt.kernel import Opt
|
||||
from tinygrad.codegen.opt.tc import TensorCore
|
||||
from tinygrad.codegen.opt.kernel import Opt
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Estimates:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import Literal, Callable, cast
|
||||
import os, math, sys
|
||||
from collections import defaultdict, Counter
|
||||
from tinygrad.opt import tc
|
||||
from tinygrad.codegen.opt import tc
|
||||
from tinygrad.uop.ops import GroupOp, Ops, UOp, PatternMatcher, UPat
|
||||
from tinygrad.helpers import strip_parens, getenv, prod, dedup, AMX
|
||||
from tinygrad.dtype import ImageDType, dtypes, DType, PtrDType, AddrSpace, truncate
|
||||
@@ -103,7 +103,7 @@ class CStyleLanguage(Renderer):
|
||||
Ops.ADD: lambda a,b,dtype: f"({a}+{b})", Ops.SUB: lambda a,b,dtype: f"({a}-{b})", Ops.MUL: lambda a,b,dtype: f"({a}*{b})",
|
||||
Ops.MOD: lambda a,b,dtype: f"({a}%{b})", Ops.IDIV: lambda a,b,dtype: f"({a}/{b})", Ops.CMPNE: lambda a,b,dtype: f"({a}!={b})",
|
||||
Ops.SHR: lambda a,b,dtype: f"({a}>>{b})", Ops.SHL: lambda a,b,dtype: f"({a}<<{b})", Ops.CMPLT: lambda a,b,dtype: f"({a}<{b})",
|
||||
Ops.WHERE: lambda a,b,c,dtype: f"({a}?{b}:{c})" }
|
||||
Ops.WHERE: lambda a,b,c,dtype: f"({a}?{b}:{c})", Ops.CMPEQ: lambda a,b,dtype: f"({a}=={b})"}
|
||||
|
||||
string_rewrite = base_rewrite
|
||||
extra_matcher = extra_pm
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import cast
|
||||
import math, struct, sys
|
||||
from tinygrad.opt import tc
|
||||
from tinygrad.codegen.opt import tc
|
||||
from tinygrad.renderer import Renderer
|
||||
from tinygrad.renderer.cstyle import AMDRenderer
|
||||
from tinygrad.uop.ops import UOp, PatternMatcher, UPat, Ops, GroupOp
|
||||
@@ -59,10 +59,11 @@ def render_wmma_amd(ctx, wmma: UOp, arch: str) -> str:
|
||||
|
||||
# llvm ops, lop[<dtype>][<op>]
|
||||
unsigned_lop = { Ops.ADD: "add", Ops.MUL: "mul", Ops.IDIV: "udiv", Ops.MOD: "urem",
|
||||
Ops.CMPLT: "icmp ult", Ops.CMPNE: "icmp ne", Ops.OR: "or", Ops.AND: "and", Ops.XOR: "xor", }
|
||||
Ops.CMPLT: "icmp ult", Ops.CMPNE: "icmp ne", Ops.CMPEQ: "icmp eq", Ops.OR: "or", Ops.AND: "and", Ops.XOR: "xor",}
|
||||
signed_lop = {**unsigned_lop, Ops.ADD: "add nsw", Ops.CMPLT: "icmp slt", Ops.IDIV: "sdiv", Ops.MOD: "srem"}
|
||||
flags = " nsz arcp contract afn"
|
||||
float_lop = {Ops.ADD: "fadd"+flags, Ops.MUL: "fmul"+flags, Ops.CMPLT: f"fcmp{flags} ult", Ops.CMPNE: f"fcmp{flags} une", Ops.FDIV: "fdiv"+flags}
|
||||
float_lop = {Ops.ADD: "fadd"+flags, Ops.MUL: "fmul"+flags, Ops.CMPLT: f"fcmp{flags} ult",
|
||||
Ops.CMPNE: f"fcmp{flags} une", Ops.CMPEQ: f"fcmp{flags} oeq", Ops.FDIV: "fdiv"+flags}
|
||||
lop = {**{x:unsigned_lop for x in (dtypes.bool,)+dtypes.uints}, **{x:signed_lop for x in dtypes.sints}, **{x:float_lop for x in dtypes.floats}}
|
||||
|
||||
base_rewrite = PatternMatcher([
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import cast, Callable
|
||||
import struct
|
||||
from collections import defaultdict
|
||||
from tinygrad.opt import tc
|
||||
from tinygrad.codegen.opt import tc
|
||||
from tinygrad.uop.ops import Ops, UOp, PatternMatcher, UPat, GroupOp
|
||||
from tinygrad.dtype import dtypes, DType, PtrDType, AddrSpace
|
||||
from tinygrad.renderer import Renderer
|
||||
@@ -26,7 +26,7 @@ asm_for_op: dict[Ops, Callable] = {
|
||||
Ops.AND: lambda d,a,b,dt, name: f"and.pred {d}, {a}, {b};" if dt == dtypes.bool else f"and.b{name[1:]} {d}, {a}, {b};",
|
||||
Ops.OR: lambda d,a,b,dt, name: f"or.pred {d}, {a}, {b};" if dt == dtypes.bool else f"or.b{name[1:]} {d}, {a}, {b};",
|
||||
Ops.IDIV: lambda d,a,b,dt,name: f"div.{name} {d}, {a}, {b};", Ops.MOD: lambda d,a,b,dt,name: f"rem.{name} {d}, {a}, {b};",
|
||||
Ops.MAX: lambda d,a,b,dt,name: f"max.{name} {d}, {a}, {b};",
|
||||
Ops.MAX: lambda d,a,b,dt,name: f"max.{name} {d}, {a}, {b};", Ops.CMPEQ: lambda d,a,b,dt,name: f"setp.eq.{name} {d}, {a}, {b};",
|
||||
Ops.CMPLT: lambda d,a,b,dt,name: f"setp.lt.{name} {d}, {a}, {b};", Ops.CMPNE: lambda d,a,b,dt,name: f"setp.ne.{name} {d}, {a}, {b};",
|
||||
Ops.MULACC: lambda d,a,b,c,dt,name: f"{'fma.rn' if dtypes.is_float(dt) else 'mad.lo'}.{name} {d}, {a}, {b}, {c};",
|
||||
Ops.WHERE: lambda d,a,b,c,dt,name: [f"@{a} mov.{name} {d}, {b};", f"@!{a} mov.{name} {d}, {c};"] if dt == dtypes.bool else \
|
||||
@@ -38,6 +38,7 @@ doesnt_support_half: tuple[Ops, ...] = tuple(op for op in asm_for_op.keys() if o
|
||||
ptx_matcher = PatternMatcher([
|
||||
# bool CMPNE is XOR, bool CMPLT is XOR+AND (universal makes this slow, this is for renderer only)
|
||||
(UPat.var('x', dtype=dtypes.bool).ne(UPat.var('y')), lambda x,y: x^y),
|
||||
(UPat.var('x', dtype=dtypes.bool).alu(Ops.CMPEQ, UPat.var('y')), lambda x,y: (x^y)^True),
|
||||
(UPat.var('x', dtype=dtypes.bool)<UPat.var('y'), lambda x,y: (x^True)&y),
|
||||
# upcast to float32 all the ops that don't support half
|
||||
(UPat(doesnt_support_half, dtype=dtypes.half, name="x"),
|
||||
@@ -91,7 +92,7 @@ string_rewrite = PatternMatcher([
|
||||
f"[{ctx.r[bidx]}+0], {('{' + ', '.join(ctx.r[var]) + '}') if var.dtype.count > 1 else ctx.r[var]};"),
|
||||
(UPat(Ops.SPECIAL, name="x"), lambda ctx,x: f"mov.u32 %{x.arg[0]}, %{'ctaid' if x.arg[0][0] == 'g' else 'tid'}.{chr(120+int(x.arg[0][-1]))};"),
|
||||
(UPat(Ops.DEFINE_GLOBAL, name="x"), lambda ctx, x: f"ld.param.{ctx.types[dtypes.ulong]} {ctx.r[x]}, [data{x.arg}+0];"),
|
||||
(UPat((Ops.CMPLT, Ops.CMPNE), name="x", allow_any_len=True, src=(UPat.var("src0"),)),
|
||||
(UPat((Ops.CMPLT, Ops.CMPNE, Ops.CMPEQ), name="x", allow_any_len=True, src=(UPat.var("src0"),)),
|
||||
lambda ctx, x, src0: ctx.code_for_op[x.op](ctx.r[x], *[ctx.r[v] for v in x.src], src0.dtype, ctx.types[src0.dtype])),
|
||||
(UPat(GroupOp.ALU, name="x"), lambda ctx, x: ctx.code_for_op[x.op](ctx.r[x], *[ctx.r[v] for v in x.src], x.dtype, ctx.types[x.dtype])),
|
||||
(UPat(Ops.BITCAST, name="x", src=(UPat.var("a"),), allow_any_len=True), lambda ctx, x, a: f"mov.b{ctx.types[x.dtype][1:]} {ctx.r[x]}, {ctx.r[a]};"),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user