- Run
uv sync --all-groups - That's it
- Read existing code in the relevant file(s) first to understand conventions, typing style, and patterns.
- Check neighboring files for imports, library choices, and naming conventions.
- Never add comments to code unless the user explicitly asks.
- This is an educational project — no runtime dependencies are allowed. Only testing/dev tools (pytest, hypothesis, pyrefly, ruff, numpy) may be used.
- This library should be very easily understood and as unbloated as possible. The exception would be the transpiler/runtime, since they make the kernels accessible, and at the python level.
Run all four before submitting changes:
uv run ruff format && uv run ruff check --fix && uv run pyrefly check && uv run pytest --cov --cov-report=term-missing -n autouv run ruff format— must produce no changes (i.e., already formatted)uv run ruff check --fix— must pass with zero errorsuv run pyrefly check— must pass with zero errors (suppressed warnings are OK)uv run pytest --cov— must pass with 100% coverage on all source files
- Functions decorated with
@compile_fnor passed toregister_inlineare transpiled to C — their Python body is never executed. They are automatically excluded from code coverage, as the functions will not be directly used (onlymodule.compiled_functionwill be called) - Cache artifacts live in
~/.cache/simplendarray/— each hash is isolated in its own subdirectory with a per-module.lock.
Compiled C functions are accessible through PythonModule instances via __getattr__ → .so. To make
pyrefly see exact signatures, an importable .py stub file is auto-generated:
- The stub file is named
_{stem}_stubs.pynext to the source module (e.g._element_wise_stubs.pyalongsideelement_wise.py). - At runtime, methods inside the
if TYPE_CHECKING:block are invisible — calls fall through to__getattr__→ C extension. pyrefly reads them statically. - PythonModules will set
stub_path=__file__andstub_var={var_name}to get this desired effect. After the first run, the type stub will be autogenerated.
src/simplendarray/
├── __init__.py # Public API exports
├── array.py # Array class (from_iterable, arange, reshape, relu, __add__, slicing, transpose)
├── buffer.py # Buffer (CPU) — wraps array.array
├── buffer_cuda.py # BufferCuda (GPU) — wraps CUDA device memory
├── dtypes.py # DType classes, get_dtype(), typecode/ctype/cname maps
├── utils.py # contiguous_strides, product, ceildiv
├── kernels/
│ ├── __init__.py # Dispatchers: elem_wise_modules = {"cpu": ..., "gpu": ...}
│ │ # dispatch_arange, dispatch_element_wise_binary/unary, dispatch_reshape_copy
│ ├── cpu/
│ │ └── element_wise.py # CPU compiled kernels (all dtypes)
│ └── cuda/
│ ├── __init__.py # Re-exports buffer_cuda_module, element_wise_module_cuda
│ ├── element_wise_cuda.py # CUDA compiled kernels (__device__, __global__, pybind)
│ ├── buffer_cuda.py # CUDA malloc/free/memcpy wrappers
│ └── helpers.py # dim3, threadIdx, blockIdx, cudaError_t type stubs
└── transpiler/
├── __init__.py
├── transpiler.py # Python → C transpiler
└── runtime.py # PythonModule class, SpecItem, compile_fn, caching, dispatch
tests/
├── test_array.py # Array tests, including dispatch error paths with MagicMock
├── test_buffer.py # Buffer & BufferCuda tests (GPU tests skipif no nvcc)
├── test_transpiler.py
└── test_runtime.py
- Array stores data via
self.data: Buffer | BufferCuda, plus shape/strides/offset (typical strided view). deviceis"cpu"or"gpu", stored on the buffer. Access viaArray.deviceproperty →self.data.device.Buffer(CPU) wrapsarray.array.BufferCuda(GPU) allocates CUDA device memory. Both expose.address(int pointer),.typecode(str like "i"/"f"/"d"/"b"),.device,.num_bytes.- Dispatch via
elem_wise_modules[device].DISPATCH_DICT_{group}[dispatch_key](...). Each compiled module (CPU/GPU) has its own dispatch dicts. Keys aretuple(spec.mapping.items())— e.g.(("T", "int"), ("Op", "_add_int")). - CPU and GPU can have different dispatch keys for the same operation —
dispatch_arangeuses(("T", dt.ctype),)for CPU and(("T", dt.ctype), ("Kernel", "arange_kernel_{cname}"))for GPU. Checkdispatch_reshape_copyfor the pattern. spec.mappingmaps template param names to C type strings. Keys must match exactly between dispatch function and kernel definition.
get_dtype(dtype)wheredtypeis a typecode ("i","f","d","b") or a DType class.- Each DType has:
.typecode(array.array code),.ctype(C type like"int","float"),.cname(short name like"int","float","double","bool"). - Kernel specs iterate over
all_dtypes(all) orall_float_dtypes(float/double only for math ops).
__device__function (e.g._add[T: DType](x: T, y: T) -> T) — single operation, single dtype.__global__kernel (e.g.element_wise_binary_kernel) — readsthreadIdx.x + blockIdx.x * blockDim.x, dispatches to theOp.- pybind=True wrapper (e.g.
element_wise_binary) — computes grid dimensions, launches kernel viaKernel[[[blocks, threads]]](args).
dispatch_element_wise_unaryraisesNotImplementedErrorfor GPU (line 25-26 ofkernels/__init__.py). CUDA unary kernels exist but are never dispatched.Array.relu()andArray.__add__()always create CPUBufferfor output — they don't produce GPU results even when inputs are on GPU.- GPU dispatch keys differ from CPU for
arangeandreshape_copy(GPU adds("Kernel", ...)).dispatch_arangeanddispatch_reshape_copyhave been fixed;dispatch_element_wise_binaryuses matching keys for both. - Coverage skips CUDA kernel lines (
element_wise_cuda.pyat ~87% when nvcc unavailable). Only source files undersrc/simplendarray/are measured, excluding*_stubs.py.
- GPU dispatch paths are tested with
MagicMock+monkeypatch.setitem(_kernels.elem_wise_modules, "gpu", mock_gpu_module)to avoid needing nvcc. - Real GPU integration tests use
@pytest.mark.skipif(not cuda_available, reason="CUDA not available")wherecuda_available = shutil.which("nvcc") is not None. BufferCudamethods tested intest_buffer.pywith skipif pattern.- Hypothesis property-based tests compare against NumPy for slicing, transpose, reshape.