mirror of
https://github.com/tinygrad/tinygrad.git
synced 2026-08-29 10:56:08 +00:00
use HEX_REG_QEMU_INSN_CNT from qemu as a DSP timer [pr] (#8993)
* use HEX_REG_QEMU_INSN_CNT from qemu as a DSP timer [pr] * add quantize test to dsp * fix tests * older onnx * debug, let's see what's happening
This commit is contained in:
@@ -13,6 +13,10 @@ inputs:
|
||||
description: 'Extra dependency groups (comma separated)'
|
||||
required: false
|
||||
default: ''
|
||||
pydeps:
|
||||
description: 'Extra Python dependency groups (space separated)'
|
||||
required: false
|
||||
default: ''
|
||||
opencl:
|
||||
description: "Install OpenCL?"
|
||||
required: false
|
||||
@@ -83,11 +87,11 @@ runs:
|
||||
- name: Install dependencies (with extra)
|
||||
if: inputs.deps != ''
|
||||
shell: bash
|
||||
run: pip install ${{ (runner.os == 'macOS' && '--user') || (runner.os != 'macOS' && '') }} -e ".[${{ inputs.deps }}]" --extra-index-url https://download.pytorch.org/whl/cpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/Triton-Nightly/pypi/simple/
|
||||
run: pip install ${{ (runner.os == 'macOS' && '--user') || (runner.os != 'macOS' && '') }} -e ".[${{ inputs.deps }}]" ${{ inputs.pydeps }} --extra-index-url https://download.pytorch.org/whl/cpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/Triton-Nightly/pypi/simple/
|
||||
- name: Install dependencies (without extra)
|
||||
if: inputs.deps == ''
|
||||
shell: bash
|
||||
run: pip install ${{ (runner.os == 'macOS' && '--user') || (runner.os != 'macOS' && '') }} -e .
|
||||
run: pip install ${{ (runner.os == 'macOS' && '--user') || (runner.os != 'macOS' && '') }} -e . ${{ inputs.pydeps }}
|
||||
|
||||
# **** OpenCL ****
|
||||
|
||||
|
||||
@@ -42,8 +42,7 @@ jobs:
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
deps: docs
|
||||
- name: Install capstone for CLANG disassembly
|
||||
run: pip install capstone
|
||||
pydeps: "capstone"
|
||||
- name: Use as an external package
|
||||
run: |
|
||||
mkdir $HOME/test_external_dir
|
||||
@@ -403,6 +402,9 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: dsp
|
||||
pydeps: "onnx==1.16.0 onnxruntime"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build QEMU Docker with cache
|
||||
@@ -416,6 +418,8 @@ jobs:
|
||||
cache-to: type=gha,mode=min
|
||||
- name: Run test_tiny on DSP
|
||||
run: DEBUG=2 DSP=1 python test/test_tiny.py
|
||||
- name: Test quantize onnx
|
||||
run: PYTHONPATH="." DEBUG=2 DSP=1 python3 test/test_quantize_onnx.py
|
||||
|
||||
testwebgpu:
|
||||
name: Linux (WebGPU)
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import numpy as np
|
||||
import unittest
|
||||
from tinygrad import Tensor, Context, Device
|
||||
from tinygrad.codegen.kernel import Kernel, Opt, OptOps
|
||||
from tinygrad.engine.realize import CompiledRunner, ExecItem
|
||||
|
||||
N = 1024
|
||||
|
||||
def create_gemm_model(model_path:str, in_size=N, out_size=N):
|
||||
import onnx
|
||||
from onnx import helper, numpy_helper, TensorProto
|
||||
# Define input and output
|
||||
input_tensor = helper.make_tensor_value_info("input", TensorProto.FLOAT, [1, in_size])
|
||||
output_tensor = helper.make_tensor_value_info("output", TensorProto.FLOAT, [1, out_size])
|
||||
|
||||
# Create random weights and bias
|
||||
W_data = np.random.randn(in_size, out_size).astype(np.float32)
|
||||
B_data = np.random.randn(out_size).astype(np.float32)
|
||||
|
||||
W_init = numpy_helper.from_array(W_data, name="W")
|
||||
B_init = numpy_helper.from_array(B_data, name="B")
|
||||
|
||||
gemm_node = helper.make_node("Gemm", inputs=["input", "W", "B"], outputs=["output"], alpha=1.0, beta=1.0, transB=0)
|
||||
graph_def = helper.make_graph([gemm_node], "SingleGemmGraph", [input_tensor], [output_tensor], initializer=[W_init, B_init])
|
||||
|
||||
# Create and save the model
|
||||
model_def = helper.make_model(graph_def, producer_name="single_gemm_example")
|
||||
onnx.save_model(model_def, model_path)
|
||||
return model_path
|
||||
|
||||
def sexec(out:Tensor, opts:list[Opt]):
|
||||
si = out.schedule()[-1]
|
||||
k = Kernel(si.ast, opts=Device[Device.DEFAULT].renderer)
|
||||
#opts = [Opt(op=OptOps.UPCAST, axis=0, arg=128)] #, Opt(op=OptOps.UNROLL, axis=0, arg=4)]
|
||||
for opt in opts: k.apply_opt(opt)
|
||||
prg = k.to_program()
|
||||
ei = ExecItem(CompiledRunner(prg), [x.ensure_allocated() for x in si.bufs], si.metadata)
|
||||
for _ in range(3): ei.run(wait=True)
|
||||
|
||||
@unittest.skipIf(Device.DEFAULT != "DSP", "only tests for DSP")
|
||||
class TestQuantizeOnnx(unittest.TestCase):
|
||||
def test_quant(self):
|
||||
from onnxruntime.quantization import quantize_static, QuantFormat, QuantType, CalibrationDataReader
|
||||
from examples.benchmark_onnx import load_onnx_model
|
||||
class FakeDataReader(CalibrationDataReader):
|
||||
def __init__(self): self.cnt = 0
|
||||
def get_next(self) -> dict:
|
||||
self.cnt += 1
|
||||
if self.cnt == 100: return None
|
||||
return {"input": np.random.uniform(size=(1, N)).astype(np.float32)}
|
||||
out_file = "/tmp/test_out.onnx"
|
||||
quantize_static(create_gemm_model("/tmp/test_in.onnx"), out_file,
|
||||
FakeDataReader(), quant_format=QuantFormat.QDQ, per_channel=False,
|
||||
activation_type=QuantType.QInt8, weight_type=QuantType.QInt8,
|
||||
extra_options={"ActivationSymmetric": True})
|
||||
run_onnx_jit, _ = load_onnx_model(out_file)
|
||||
with Context(NOOPT=1):
|
||||
run_onnx_jit(input=Tensor(np.random.uniform(size=(1, N)).astype(np.float32)))
|
||||
|
||||
def test_prequant_conv2d_1x1(self):
|
||||
X = Tensor(np.random.uniform(0, 255, size=(1, 32, 128, 128)).astype(np.uint8))
|
||||
W = Tensor(np.random.uniform(0, 255, size=(64, 32, 1, 1)).astype(np.uint8))
|
||||
out = X.conv2d(W, acc_dtype=X.dtype)
|
||||
opts = [Opt(op=OptOps.UPCAST, axis=1, arg=128), Opt(op=OptOps.UNROLL, axis=0, arg=4)]
|
||||
sexec(out, opts)
|
||||
|
||||
def test_prequant_gemm(self):
|
||||
N = 512
|
||||
# ugh, it's so broken with those casts. need DONT_REALIZE_EXPAND=1 python3 test/test_quantize_onnx.py TestQuantizeOnnx.test_prequant
|
||||
X = Tensor(np.random.uniform(0, 255, size=(N,N)).astype(np.uint8))
|
||||
W = Tensor(np.random.uniform(0, 255, size=(N,N)).astype(np.uint8))
|
||||
out = X.matmul(W, acc_dtype=X.dtype)
|
||||
opts = [Opt(op=OptOps.UPCAST, axis=1, arg=128), Opt(op=OptOps.UNROLL, axis=0, arg=4)]
|
||||
sexec(out, opts)
|
||||
|
||||
def test_prequant_gemv(self):
|
||||
N = 2048
|
||||
# ugh, it's so broken with those casts. need DONT_REALIZE_EXPAND=1 python3 test/test_quantize_onnx.py TestQuantizeOnnx.test_prequant
|
||||
X = Tensor(np.random.uniform(0, 255, size=(1,N)).astype(np.uint8))
|
||||
W = Tensor(np.random.uniform(0, 255, size=(N,N)).astype(np.uint8))
|
||||
#out = X.cast(dtypes.int) @ W.cast(dtypes.int)
|
||||
#out = X @ W
|
||||
out = X.matmul(W, acc_dtype=X.dtype)
|
||||
opts = [Opt(op=OptOps.UPCAST, axis=0, arg=128), Opt(op=OptOps.UNROLL, axis=0, arg=4)]
|
||||
sexec(out, opts)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -411,7 +411,7 @@ class Kernel:
|
||||
elif opt.op is OptOps.UPCAST: # yellow
|
||||
check(axis < self.first_reduce, "upcast is for non-reduce")
|
||||
check(not (self.tensor_core and self.global_dims <= axis < self.global_dims+len(self.tensor_core.get_local_axes())), "can't upcast TC locals")
|
||||
check(amt <= 16, "don't upcast more than 16")
|
||||
check((self.opts is not None and self.opts.device == "DSP") or amt <= 16, "don't upcast more than 16")
|
||||
self.shift_to(axis, amt, insert_before=None)
|
||||
self.upcast()
|
||||
elif opt.op is OptOps.NOLOCALS:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
import ctypes, os, mmap, tempfile, pathlib, array, functools, threading, contextlib, sys, subprocess, time, struct
|
||||
import ctypes, os, mmap, tempfile, pathlib, array, functools, threading, contextlib, sys, subprocess, struct
|
||||
assert sys.platform != 'win32'
|
||||
from tinygrad.device import BufferSpec, Compiled, Allocator, Compiler, MallocAllocator
|
||||
from tinygrad.dtype import dtypes, DType, PtrDType
|
||||
@@ -231,12 +231,14 @@ class MockDSPRenderer(DSPRenderer):
|
||||
def render_kernel(self, function_name:str, kernel:list[str], bufs:list[tuple[str,tuple[DType,bool]]], uops:list[UOp], prefix=None) -> str:
|
||||
ret = ClangRenderer.render_kernel(self, function_name, kernel, bufs, uops, prefix)
|
||||
# https://gpages.juszkiewicz.com.pl/syscalls-table/syscalls.html
|
||||
# control register 21 is HEX_REG_QEMU_INSN_CNT, 0x6a15c000 loads it
|
||||
msrc = ['''static long syscall(long r0, long r1, long r2, long r3, long r4, long r5, long r6) {
|
||||
long retval; __asm__ volatile("r0 = %1; r1 = %2; r2 = %3; r3 = %4; r4 = %5; r5 = %6; r6 = #%7; trap0(#1); %0 = r0" : "=r" (retval)
|
||||
: "r" (r0), "r" (r1), "r" (r2), "r" (r3), "r" (r4), "r" (r5), "i" (r6) : "r0", "r1", "r2", "r3", "r4", "r5", "r6"); return retval; }
|
||||
static int read(int fd, void* buf, int len) {{ return syscall(fd, (long)buf, len, 0, 0, 0, 63); }}
|
||||
static int write(int fd, void* buf, int len) {{ return syscall(fd, (long)buf, len, 0, 0, 0, 64); }}
|
||||
static int exit(int ret) {{ return syscall(ret, 0, 0, 0, 0, 0, 93); }}
|
||||
static unsigned int inscount(void) {{ unsigned int ret; __asm__ volatile(".word 0x6a15c000; %0 = R0" : "=r" (ret) : : "r0"); return ret; }}
|
||||
static void *mmap2(void *addr, unsigned int length, int prot, int flags, int fd, unsigned long offset) {{
|
||||
return (void*)syscall((long)addr, length, prot, flags, fd, offset, 222); }}''', 'void _start(void) {']
|
||||
for i,b in enumerate(bufs):
|
||||
@@ -245,7 +247,9 @@ class MockDSPRenderer(DSPRenderer):
|
||||
msrc.append(f"void *buf{i} = mmap2(0, {sz}, 3, 0x21, -1, 0); read(0, buf{i}, {sz});")
|
||||
else:
|
||||
msrc.append(f"unsigned int val{i}; read(0, &val{i}, 4);")
|
||||
msrc.append("unsigned int st = inscount();")
|
||||
msrc.append(f"{function_name}({', '.join([(f'(void*)buf{i}' if isinstance(b[1][0], PtrDType) else f'val{i}') for i,b in enumerate(bufs)])});")
|
||||
msrc.append("unsigned int et = inscount() - st; write(1, &et, sizeof(et));")
|
||||
for i,b in enumerate(bufs):
|
||||
if isinstance(b[1][0], PtrDType): msrc.append(f"write(1, buf{i}, {b[1][0].size*b[1][0].itemsize});")
|
||||
msrc.append('exit(0); }')
|
||||
@@ -259,13 +263,11 @@ class MockDSPProgram:
|
||||
dsp_lib.flush()
|
||||
os.chmod(dsp_lib.name, 0o0777)
|
||||
# NOTE: this timing includes a docker launch
|
||||
start = time.perf_counter()
|
||||
proc = subprocess.run(["docker", "run", "--rm", "-i", "-v", f"{os.path.abspath(os.path.dirname(dsp_lib.name))}:/work", "-w", "/work",
|
||||
"qemu-hexagon", "-c", f"qemu-hexagon {'-strace' if DEBUG >= 3 else ''} /work/"+os.path.basename(dsp_lib.name)],
|
||||
input=b''.join([bytes(x) for x in bufs] + [struct.pack("I", x) for x in vals]), stdout=subprocess.PIPE, check=True)
|
||||
elapsed = time.perf_counter() - start
|
||||
offset = 0
|
||||
offset = 4
|
||||
for x in bufs:
|
||||
x[:] = proc.stdout[offset:offset+len(x)]
|
||||
offset += len(x)
|
||||
return elapsed
|
||||
return struct.unpack("I", proc.stdout[0:4])[0] / 1e9 # pretend it's 1 Ghz, but this is an inscount, not a time
|
||||
|
||||
Reference in New Issue
Block a user