Metadata-Version: 2.4
Name: vulpy
Version: 0.1.0
Summary: Cross-platform Vulkan compute with a NumPy-flavored Python front end and inspectable generated GLSL
Author: Sydney McConnell
License:                                  Apache License
                                   Version 2.0, January 2004
                                http://www.apache.org/licenses/
        
           TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
        
           1. Definitions.
        
              "License" shall mean the terms and conditions for use, reproduction,
              and distribution as defined by Sections 1 through 9 of this document.
        
              "Licensor" shall mean the copyright owner or entity authorized by
              the copyright owner that is granting the License.
        
              "Legal Entity" shall mean the union of the acting entity and all
              other entities that control, are controlled by, or are under common
              control with that entity. For the purposes of this definition,
              "control" means (i) the power, direct or indirect, to cause the
              direction or management of such entity, whether by contract or
              otherwise, or (ii) ownership of fifty percent (50%) or more of the
              outstanding shares, or (iii) beneficial ownership of such entity.
        
              "You" (or "Your") shall mean an individual or Legal Entity
              exercising permissions granted by this License.
        
              "Source" form shall mean the preferred form for making modifications,
              including but not limited to software source code, documentation
              source, and configuration files.
        
              "Object" form shall mean any form resulting from mechanical
              transformation or translation of a Source form, including but
              not limited to compiled object code, generated documentation,
              and conversions to other media types.
        
              "Work" shall mean the work of authorship made available under
              the License, as indicated by a copyright notice that is included in
              or attached to the work (an example is provided in the Appendix below).
        
              "Derivative Works" shall mean any work, whether in Source or Object
              form, that is based on (or derived from) the Work and for which the
              editorial revisions, annotations, elaborations, or other modifications
              represent, as a whole, an original work of authorship. For the purposes
              of this License, Derivative Works shall not include works that remain
              separable from, or merely link (or bind by name) to the interfaces of,
              the Work and Derivative Works thereof.
        
              "Contribution" shall mean, as submitted to the Licensor for inclusion
              in the Work by the copyright owner or by an individual or Legal Entity
              authorized to submit on behalf of the copyright owner. For the purposes
              of this definition, "submitted" means any form of electronic, verbal,
              or written communication sent to the Licensor or its representatives,
              including but not limited to communication on electronic mailing lists,
              source code control systems, and issue tracking systems that are managed
              by, or on behalf of, the Licensor for the purpose of discussing and
              improving the Work, but excluding communication that is conspicuously
              marked or designated in writing by the copyright owner as "Not a
              Contribution."
        
              "Contributor" shall mean Licensor and any Legal Entity on behalf of
              whom a Contribution has been received by the Licensor and incorporated
              within the Work.
        
           2. Grant of Copyright License. Subject to the terms and conditions of
              this License, each Contributor hereby grants to You a perpetual,
              worldwide, non-exclusive, no-charge, royalty-free, irrevocable
              copyright license to reproduce, prepare Derivative Works of,
              publicly display, publicly perform, sublicense, and distribute the
              Work and such Derivative Works in Source or Object form.
        
           3. Grant of Patent License. Subject to the terms and conditions of
              this License, each Contributor hereby grants to You a perpetual,
              worldwide, non-exclusive, no-charge, royalty-free, irrevocable
              (except as stated in this section) patent license to make, have made,
              use, offer to sell, sell, import, and otherwise transfer the Work,
              where such license applies only to those patent claims licensable
              by such Contributor that are necessarily infringed by their
              Contribution(s) alone or by combination of their Contribution(s)
              with the Work to which such Contribution(s) was submitted. If You
              institute patent litigation against any entity (including a cross-claim
              or counterclaim in a lawsuit) alleging that the Work or a Contribution
              incorporated within the Work constitutes direct or contributory patent
              infringement, then any patent licenses granted to You under this License
              for that Work shall terminate as of the date such litigation is filed.
        
           4. Redistribution. You may reproduce and distribute copies of the
              Work or Derivative Works thereof in any medium, with or without
              modifications, and in Source or Object form, provided that You
              meet the following conditions:
        
              (a) You must give any other recipients of the Work or Derivative
                  Works a copy of this License; and
        
              (b) You must cause any modified files to carry prominent notices
                  stating that You changed the files; and
        
              (c) You must retain, in the Source form of any Derivative Works
                  that You distribute, all copyright, patent, trademark, and
                  attribution notices from the Source form of the Work,
                  excluding those notices that do not pertain to any part of
                  the Derivative Works; and
        
              (d) If the Work includes a "NOTICE" text file as part of its
                  distribution, You must include a readable copy of the
                  attribution notices contained within such NOTICE file, in
                  at least one of the following places: within a NOTICE text
                  file distributed as part of the Derivative Works; within
                  the Source form or documentation, if provided along with the
                  Derivative Works; or, within a display generated by the
                  Derivative Works, if and wherever such third-party notices
                  normally appear. The contents of the NOTICE file are for
                  informational purposes only and do not modify the License.
                  You may add Your own attribution notices within Derivative
                  Works that You distribute, alongside or in addition to the
                  NOTICE text from the Work, provided that such additional
                  attribution notices cannot be construed as modifying the License.
        
              You may add Your own license statement for Your modifications and
              may provide additional grant of rights to use, copy, modify, merge,
              publish, distribute, sublicense, and/or sell copies of the
              Derivative Works, as a separate agreement, consistent with the
              rights granted herein. However, you shall not state terms of rights
              that conflict with this License or remove or modify any rights
              granted herein.
        
           5. Submission of Contributions. Unless You explicitly state otherwise,
              any Contribution intentionally submitted for inclusion in the Work
              by You to the Licensor shall be under the terms and conditions of
              this License, without any additional terms or conditions.
              Notwithstanding the above, nothing herein shall supersede or modify
              the terms of any separate license agreement you may have executed
              with Licensor regarding such Contributions.
        
           6. Trademarks. This License does not grant permission to use the trade
              names, trademarks, service marks, or product names of the Licensor,
              except as required for reasonable and customary use in describing the
              origin of the Work and reproducing the content of the NOTICE file.
        
           7. Disclaimer of Warranty. Unless required by applicable law or
              agreed to in writing, Licensor provides the Work (and each
              Contributor provides its Contributions) on an "AS IS" BASIS,
              WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
              implied, including, without limitation, any warranties or conditions
              of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
              PARTICULAR PURPOSE. You are solely responsible for determining the
              appropriateness of using or reproducing the Work and assume any
              risks associated with Your exercise of permissions under this License.
        
           8. Limitation of Liability. In no event and under no legal theory,
              whether in tort (including negligence), contract, or otherwise,
              unless required by applicable law (such as deliberate and grossly
              negligent acts) or agreed to in writing, shall any Contributor be
              liable to You for damages, including any direct, indirect, special,
              incidental, or exemplary damages of any character arising as a
              result of this License or out of the use or inability to use the
              Work (including but not limited to damages for loss of goodwill,
              work stoppage, computer failure or malfunction, or all other
              commercial damages or losses), even if such Contributor has been
              advised of the possibility of such damages.
        
           9. Accepting Warranty or Liability. While redistributing the Work or
              Derivative Works thereof, You may choose to offer, and charge a fee
              for, acceptance of support, warranty, indemnity, or other liability
              obligations and/or rights consistent with this License. However, in
              accepting such obligations, You may offer such obligations only on
              Your own behalf and on Your sole responsibility, not on behalf of
              any other Contributor, and only if You agree to indemnify, defend,
              and hold each Contributor harmless for any liability incurred by,
              or claims asserted against, such Contributor by reason of your
              accepting any warranty or additional liability.
        
           END OF TERMS AND CONDITIONS
        
           Copyright 2026 Sydney McConnell
        
           Licensed under the Apache License, Version 2.0 (the "License");
           you may not use this file except in compliance with the License.
           You may obtain a copy of the License at
        
               http://www.apache.org/licenses/LICENSE-2.0
        
           Unless required by applicable law or agreed to in writing, software
           distributed under the License is distributed on an "AS IS" BASIS,
           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
           See the License for the specific language governing permissions and
           limitations under the License.
        
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Requires-Dist: vulkan>=1.3
Provides-Extra: progress
Requires-Dist: tqdm>=4.0; extra == "progress"
Provides-Extra: dev
Requires-Dist: tqdm>=4.0; extra == "dev"
Dynamic: license-file

# VulPy

**Write Python. Run on any GPU. Inspect the GLSL it generates.**

VulPy is a Vulkan compute runtime with a Python front end. You write Python, with access to any architectural pattern the language supports, and VulPy's AST transpiler builds it into GLSL, compiles to SPIR-V, and dispatches on whatever Vulkan can reach: an RTX 4090, an Intel Iris Xe, a phone's Adreno 730 in Termux. No CUDA, no vendor lock-in, no separate backends.

The generated GLSL is always inspectable. If you want to see what a persistent kernel looks like explicitly in shader code, or what a zero-copy memory channel compiles to, it's right there, readable and modifiable. VulPy can be a prototyping layer you eventually leave, or a runtime you stay in. Either way you end up understanding what your code actually does on the GPU.

CuPy had the right idea making GPU compute feel like NumPy. VulPy follows the same instinct but targets Vulkan. And because GLSL and SPIR-V are already the common language every major vendor builds tooling around — the one GPU IR whose drivers stay maintained on every platform because the games industry funds them — the output isn't just inspectable, it's portable twice over. Share the `.vul.py` file and anyone with `pip install vulpy` runs it on whatever Vulkan device they have. Or run `python -m vulpy emit-glsl kernel.vul.py` and share the GLSL itself: readable, compilable, zero VulPy dependency. Working in an Nvidia shop? Transliterate to CUDA. AMD? ROCm. The generated shader is a working starting point, not a blank page.

```python
import numpy as np
import vulpy as vp

x = np.linspace(0.0, 1.0, 1_000_000, dtype=np.float32)
y = vp.sin(x) + vp.exp(x)   # GPU-accelerated, returns NumPy array
```

---

## Install

```bash
pip install vulpy
```

Requires `glslangValidator` on PATH for runtime GLSL → SPIR-V compilation.

### Linux (Ubuntu / Debian)

```bash
sudo apt install glslang-tools
pip install vulpy
```

### Android / Termux

```bash
pkg install python-numpy glslang
pip install vulkan vulpy vulkan-tools
pkg uninstall vulkan-loader-generic
pkg install vulkan-loader-android
```

Use `pkg install python-numpy` rather than `pip install numpy`, because `pip` throws a fit and crashes building the wheel on android.

### macOS / Windows

Expected to work via MoltenVK (macOS) or the LunarG Vulkan SDK (Windows), but I don't have a mac and I haven't run Windows in a year. Please let me know if you get it working, the steps you took, and if anything breaks. 

---

## Tested Hardware

| Device | GPU | Platform |
|---|---|---|
| Laptop | NVIDIA RTX 4050 Mobile | Ubuntu Linux |
| Laptop | Intel Iris Xe (iGPU, same machine) | Ubuntu Linux |
| Vivo X Fold | Adreno 730 | Android / Termux |
| Nubia RedMagic 8 | Adreno 740 | Android / Termux |

Any device with Vulkan compute support should work. Android phones don't need root, just Termux, Python, and the correct Vulkan driver package.

---

## Memory Model

Currently, VulPy exposes four memory kinds, corresponding directly to Vulkan memory property flags:

| Kind | GPU speed | CPU readable | Use for |
|---|---|---|---|
| `"device"` | Full VRAM bandwidth | No | Large working arrays, anything the GPU owns entirely |
| `"host"` | PCIe bandwidth | Yes (write-combining — fast write, slow read) | Staging, control buffers, write-only channels |
| `"cached"` | PCIe bandwidth | Yes (full DDR bandwidth) | Read-back, inter-GPU data bridges |
| `"rebar"` | Full VRAM bandwidth | Yes (limited) | Small buffers that need occasional CPU access |

On integrated GPUs and phones (Unified Memory Architecture), `"device"` and `"host"` collapse to the same physical DRAM. The CPU and GPU are on the same die sharing the same memory bus; the distinction disappears at the hardware level.

### Zero-copy: one dataset, multiple devices

VulPy can allocate frame buffers backed by page-aligned host DRAM and import those physical pages into multiple GPU address spaces simultaneously via `VK_EXT_external_memory_host`. The GPU can write directly to system DRAM. A NumPy array pointing at the same pages can then see the result immediately. No copy, no transfer, no staging buffer.

For instance, on a system with both a discrete GPU and an integrated GPU:

1. The dGPU can run compute against its VRAM-resident buffers at full GPU memory bandwidth
2. A dedicated workgroup can then write selected results to the shared host pages, once, over PCIe
3. The iGPU can import those same physical pages into its own address space and run against them
4. A NumPy view on the CPU reads from those same DRAM cells
5. Anything else on the CPU reads from that NumPy view

One write. One dataset. Three concurrent possible readers. In persistent mode there are no dispatch signals, no shader reloads, no handshakes. The only traffic on the PCIe bus is data you explicitly committed. That's why the transfer is zero cost in the "persistent shader" architecture outlined below and in the tutorials.

On UMA hardware, step 2 is a same-die write, so PCIe doesn't exist to cross.

---

## Features

VulPy has several features ranging from drop-in NumPy-like statements to full persistent GPU-side programs. The goal is to provide several ways to interact with your graphics cards, ranging from in-line VulPy statements to entire `.vul.py` kernels. 

### Convenience mode

Accepts NumPy arrays, returns NumPy arrays. No setup required.

```python
x = np.linspace(0.0, 1.0, 1_000_000, dtype=np.float32)
y = vp.sin(x) + vp.exp(x)
```

### Resident mode

Keeps arrays in Vulkan-owned memory across dispatches. `numpy_view()` returns a NumPy array whose data pointer is the persistently mapped address of the Vulkan allocation. Same physical bytes, no copy.

```python
xv = vp.asarray(x)
yv = vp.empty_like(xv)
vp.sin(xv, out=yv)
result = yv.numpy_view()   # NumPy data pointer == Vulkan buffer pointer
```

### Fusion mode

`vp.jit` traces a Python lambda and emits one shader for the whole expression.

```python
f = vp.jit(lambda a, b: vp.where(a > b, vp.sin(a), vp.cos(b)))
out = f(xv, xv * 0.5)
```

### Kernel decorator mode

Write a kernel as an annotated Python function. VulPy transpiles the body to GLSL.

```python
@vp.kernel(local_size_x=64)
def push_particles(
    pos: vp.Buffer[vp.vec4],
    vel: vp.Buffer[vp.vec4],
    n:   vp.uint32,
    dt:  vp.float32,
):
    i    = vp.global_id()
    x    = vp.var(pos[i].xyz)
    v    = vp.var(vel[i].xyz)
    x[:] = x + v * dt
    pos[i] = vp.vec4(x, pos[i].w)

push_particles(pos_v, vel_v, dispatch=N, n=N, dt=0.001)
```

### `.vul.py` source mode

Write GPU source in a `.vul.py` file. Python syntax compiled to GLSL/SPIR-V by VulPy's AST transpiler. The generated shader is always inspectable.

```python
# my_kernel.vul.py
import vulpy as vp

class PushConstants:
    N:  uint32
    dt: float32

def main(pos: vp.Buffer[vp.float64], vel: vp.Buffer[vp.float64]):
    i: uint32 = vp.global_id()
    if i >= pc.N:
        return
    pos[i] = pos[i] + vel[i] * pc.dt
```

```python
shader = vp.compile("my_kernel.vul.py", local_size_x=256, context=ctx)
shader.allocate(pos=pos_array, vel=vel_array)   # sizes the buffers — no data moves
shader.upload("pos", pos_array)
shader.upload("vel", vel_array)
shader.run(dispatch=N, N=N, dt=0.001)
```

```python
# inspect the shader
python -m vulpy emit-glsl my_kernel.vul.py
```

### Raw GLSL mode

Pass handwritten GLSL source directly for full manual control.

```python
k = vp.kernel(
    GLSL_SOURCE,
    buffers=2,
    push=[("n", "uint32"), ("dt", "float32")],
)
k(pos, vel, dispatch=N, n=N, dt=0.001)
```

### Control-buffer mode

Resident `uint32` buffers that both CPU and GPU can read and write atomically. Use for flags, counters, GPU-written event queues, and adaptive kernel control.

```python
control = vp.ControlBuffer(["mode", "hot_count"], words=16)
control["hot_count"] = 0
# GPU kernel: atomicAdd(control[hot_count], 1)
print(control["hot_count"])
```

### Memory Sockets (MemSock)

A typed, directional memory channel between GPU and CPU. WebSocket-like semantics
for data transfer between VRAM and DRAM.

This is the answer to a problem the persistent model creates: when one dispatch
runs for seconds or hours, the natural observation points of launch-by-launch
code disappear and the run becomes a black box you wait on. Getting sight back
normally means hand-rolling pinned host memory, a ring layout, and a commit
protocol; MemSock packages that machinery as a first-class object. Paired with
the control buffer it works in both directions — stream meaning out, write
intent in — so a long-running kernel can be watched live *and* steered
mid-flight without ever stopping it.

```python
# host .py
pipe = vp.MemSock(
    dtype       = vp.float32,
    frame_shape = (ny, nx),
    payload     = {"snap_idx": vp.uint32},
)

@pipe.on("message")
def on_frame(frame, idx):
    imshow.set_data(frame)   # frame is a zero-copy numpy view into host DRAM

pipe.open(ctx, capacity=n_snapshots, ctrl_view=ctrl_arr, ctrl_offset=CTRL_PIPE)
```

```python
# .vul.py — coordinator side
def pipe_body(snap_idx: vp.uint32):
    pipe[snap_idx * pc.nx + p] = efield[p]

if t % pc.snap_interval == 0:
    pipe.fire(pipe_body, snap_idx=t)   # transpiler wraps this in the epoch protocol
```

For a full working example including iGPU hot-cell overlay, see [Lesson 11: 3D FDTD Scatter](docs/tutorials/lesson-11-fdtd-scatter.md).

### Persistent GPU-side programs

A single kernel dispatch that runs like an application on your GPU, managing its own scheduling on-device. The CPU launches it, and then begins an async event loop that listens and reacts to events emitted by the GPU over MemSocks.

The reference implementation is the [3D FDTD tutorial](docs/tutorials/lesson-11-fdtd-scatter.md), but the pattern applies broadly: long-running simulations, adaptive solvers, live visualization, and any workload where the GPU should own its compute loop rather than being called one dispatch at a time.

These are established techniques — persistent threads, software work queues, GPU-initiated I/O — the same architecture as D3D12 Work Graphs and the persistent megakernels in modern LLM inference engines, usually learned from research papers and production kernels. The tutorial series teaches them as a ladder of runnable steps and [maps each piece to its literature](docs/tutorials/lesson-11-fdtd-scatter.md#where-this-sits).

---

## Device Selection

```python
vp.list_devices()                    # list all Vulkan devices
ctx = vp.Context()                   # default device
ctx = vp.Context(device_index=1)     # by index
ctx = vp.Context(kind="dgpu")        # discrete GPU
ctx = vp.Context(kind="igpu")        # integrated GPU
```

---

## Requirements

- Python ≥ 3.11
- NumPy ≥ 1.24
- `vulkan` Python package
- `glslangValidator` on PATH
- A Vulkan-compatible GPU with Vulkan drivers installed

---

## Documentation

- [API Reference](https://github.com/HvwtSvwce/vulpy/blob/main/docs/API.md)
- [Architecture](https://github.com/HvwtSvwce/vulpy/blob/main/docs/architecture.md)
- [Tutorial Series](https://github.com/HvwtSvwce/vulpy/blob/main/docs/tutorials/README.md) — 12 lessons from no-kernel array math to a 3D FDTD simulation demonstrating the persistent shader architecture, one new concept per lesson; stop at any rung and you have a complete toolkit
- [Hardware and Platform Notes](https://github.com/HvwtSvwce/vulpy/blob/main/docs/hardware.md)

---

## Current Limitations

- Main convenience-mode compute path is `float32`. `float64` is available in `.vul.py` kernels when the device reports `shaderFloat64` support (desktop GPUs; not phones).
- Resident outputs must be C-contiguous.
- Axis-aware reductions, fancy indexing, and bool dtype are not implemented.
- macOS and Windows are untested.
