Your first program#
Write it once. Run it on every GPU. Get the same bits.
At the end of this page you will have:
written a simulation in
.vis;run it on every GPU in the machine and confirmed that the result is bit-identical;
produced a receipt and a report, and checked the receipt without any GPU.
The outputs below were copied from a real run on a laptop with an RTX 4050, an Intel UHD and llvmpipe. On your machine the times and the device names change; the computed numbers do not.
1. Write it#
A metal plate with a heat source in the middle. Save it as plate.vis:
// A metal plate with a heat source in the middle: heat spreads to the neighbours.
param steps = 500
param alpha = 0.2 // share exchanged with each neighbour per step (stable below 0.25)
canvas plate[256, 256] edge clamp {
temperature // each channel is one number per pixel, starting at 0
source
}
kernel heat on plate {
if (x - 128)^2 + (y - 128)^2 < 100 {
source = 1
}
}
kernel diffuse on plate {
let neighbours = temperature[x - 1, y] + temperature[x + 1, y]
+ temperature[x, y - 1] + temperature[x, y + 1]
temperature += alpha * (neighbours - 4 * temperature)
if source > 0.5 {
temperature = 100 // the source stays at 100 degrees
}
}
measure average = mean(temperature) on plate
measure hot = count(temperature > 50) on plate
run {
heat
repeat steps { diffuse }
average hot
print average
print hot
save plate.temperature "plate.png"
}
The four parts:
canvasA grid of pixels (here 256 × 256) with channels, the numbers stored in each pixel.
edge clampsays what is read outside the border: the nearest pixel. The other options arezeroandwrap.kernelThe code of one pixel, run on all of them at once on the GPU.
xandyare its coordinates. It can read any neighbour (temperature[x - 1, y]) but only writes to its own pixel. Every pixel reads the state at the start of the step, so there are no data races.measureOne number over the whole canvas (
sum,mean,min,max,count), computed on the GPU in a fixed summation order.runThe order of things.
2. Run it#
vision run plate.vis
device: NVIDIA GeForce RTX 4050 Laptop GPU — DiscreteGPU via Vulkan (driver: NVIDIA 595.99.02)
compiled for the device in 402 ms
average = 1.875969867
hot = 837
saved plate.png
501 kernel runs, 32.8 M pixels updated in 4.1 ms (7966 Mpx/s), 2 measurements
Pick another device with --device intel, --device cpu, --device numpy or the number shown
by vision devices. Change a parameter without editing the file: --param steps=2000.
3. The same result on every device#
vision compare plate.vis
GPUs: time of the 2nd run (the 1st warms up the driver and is discarded)
device time Mpx/s result
numpy (CPU, reference) 332.2 ms 99 reference
NVIDIA GeForce RTX 4050 Laptop GPU 2.6 ms 12406 bit-identical
Intel(R) Graphics (RPL-P) 18.4 ms 1786 bit-identical
llvmpipe (LLVM 20.1.2, 256 bits) 40.7 ms 806 bit-identical
“Bit-identical” means that each of the 65 536 pixels and each measure has the same 32 bits on all four devices. In other GPU languages this does not happen, because every vendor rounds differently; see How determinism works.
4. Real data#
A small terrain as CSV, elevation.csv (semicolons with decimal commas also work):
12;8,5;3
15;9;30
canvas map[3, 2] { elevation flooded }
kernel rain on map {
if elevation < 10 {
flooded = 1
}
}
measure area = count(flooded > 0.5) on map
run {
load map.elevation "elevation.csv"
rain
area
print area
print map.flooded
}
loaded map.elevation <- elevation.csv (CSV)
area = 3
map.flooded (3x2): min 0 | mean 0.5 | max 1
Vision also reads .npy, .asc (the Esri ASCII grid that GDAL exports from any GeoTIFF) and
.png. To look at a file before writing the program:
vision info dem.asc
dem.asc: ASCII grid, cell 30, no data = -9999
resolution: 4 x 3 (width x height): declare canvas name[4, 3]
values: min 7 | mean 11.5 | max 16
2 cells without data: use load canvas.channel "dem.asc" nodata <number>
cell: 30 (unit of the file's coordinate reference system)
origin (corner of the lower-left cell): x = 345000.5, y = 7395000
Vision does not read the coordinate reference system; declare what the model uses, for example: param cell = 30.0
More in Real data.
5. Receipt, report and verification#
vision run plate.vis --receipt receipt.json --report report.html
vision verify receipt.json --device intel
vision verify receipt.json --device numpy
receipt: receipt.json (03568e29693c8bda…)
report: report.html
verifier: Intel(R) Graphics (RPL-P) | receipt made on: NVIDIA GeForce RTX 4050 Laptop GPU
MATCH: 4 outputs (maps and measures) bit-identical to the receipt
verifier: numpy (CPU, reference) | receipt made on: NVIDIA GeForce RTX 4050 Laptop GPU
MATCH: 4 outputs (maps and measures) bit-identical to the receipt
The receipt binds the program, the parameters, the data files, the Vision version and every output into one hash.
The verifier re-runs the whole simulation and compares bit by bit. With
--device numpyit runs on Python and numpy only, without a GPU or a driver: whoever receives the receipt does not need to trust whoever made it.A tampered receipt does not pass.
The report (
--report) is a self-contained HTML page that opens offline and prints to PDF.
To prove who issued the receipt and when, see Receipts, signatures and reports.
Next steps#
The .vis language: everything a kernel can do.
Built-in functions: the 46 built-in functions.
The tensor library: train a neural network with
vision.tensor.