Rust SIMD on the GPU (vectorware.com)
189 points by sagacity 17 hours ago
O3marchnative 15 hours ago
The author mentions Rust's portable SIMD library [0]. The only issue with portable SIMD is it's only available on nightly. I used it in my FFT crate, but we had to switch to the fearless_simd crate in order to get a portable SIMD solution that works on stable [1].
jonkoops 13 hours ago
Pretty common for Rust to cook things in nightly for a very long time; I wouldn't consider it a bad thing, tbh.
LoganDark 7 hours ago
It's been annoying to me as an end user that so many basic things require nightly. I use nightly as my main toolchain, but enabling unstable features makes a project nightly-only, which is undesired for crates that don't already revolve around the unstable feature.
I most often encounter unstable features when I reach for a basic common-sense utility method and discover that it's not stable. Like just earlier today I would have reached for bool::toggle which not only is unstable, but is also newly added as of like a month ago! but some unstable methods have been sitting around for years.
And now that IntelliJ-Rust is proprietary, I can't even make a feature request anymore for the ability to exclude unstable features from the autocomplete. So they will taunt me forever, perfect little helpers just locked away.
stymaar 4 hours ago
6r17 15 hours ago
My heard hurts - i was stupid enough to think that SIMD was a CPU only thing - I don't understand why it would be ported to GPU - huge kudos to managing to surprise me
MindSpunk 9 hours ago
It's not really obvious unless you go in depth of the details on modern GPU architecture. GPUs aren't really SIMD, they're SIMT (single instruction multiple thread). The silicon looks a lot like SIMD, but the programming model is different.
If you go look at AMD's ISA docs (they're public) you'll see you don't have the equivalent of a __mm256 register like on x86. Each 'thread' just deals with single scalar values like int32 of float32. The hardware, however, groups 32 or 64 threads together which all run the same program and runs them together. Each 'thread' loosely maps to a SIMD lane. The SIMD is implicit, not explicit.
The main difference is that the 'SIMD' execution is somewhat opaque to the program. You just write plain scalar code and the hardware model dispatches it efficiently to SIMD execution units. It's not really an abstraction because to extract maximum performance you have to understand how it works. You can use this kind of programming model on a CPU too, Intel did it with [0] ISPC. It's a C-like language that has execution semantics similar to GPU shader languages but compiles to regular CPU code, and maps threads to your CPUs SIMD lanes like a GPU.
mathisfun123 6 hours ago
> GPUs aren't really SIMD, they're SIMT (single instruction multiple thread)
False. If they were threads they'd have their own PC. They do not - only the warp has a PC.
> You just write plain scalar code and the hardware model dispatches it efficiently to SIMD execution units.
Absolutely not. If you don't write coalesced loads, bank-conflict free, predication-free, cooperative code you will get worse than CPU performance.
MindSpunk 6 hours ago
amdivia 3 hours ago
penteract 2 hours ago
monocasa 14 hours ago
GPU "cores" are basically what a CPU would call SIMD lanes. So a GPU with 1024 'CUDA cores' might be structured as 16 relatively independent pieces that a CPU might call a core, each with a 64 wide SIMD unit.
mathisfun123 13 hours ago
32 wide - only AMD has a 64 wide mode
y1n0 10 hours ago
64 what? Bits/bytes/something bigger?
corysama 10 hours ago
chlorion 15 hours ago
GPUs work on vectors and matrices very often, that's what they are good at, so it makes a lot of sense that they can operate with SIMD I think!
ismailmaj 14 hours ago
There is something very SIMD-coded in GPU programming which is coalesced stores/loads, if a warp (32 threads) handles contiguous memory, it will create ~4 transactions instead of 32.
hingler36 15 hours ago
Welcome to the lucky 10,000! SIMD is actually a pretty integral part of how GPUs are able to work efficiently, it's part of why there's such a strong focus on branchless programming in the field.
grokcodec 12 hours ago
I would love to have an open source Rust SIMD library with the scope and maturity that https://github.com/google/highway brings to C++.
raphlinus 10 hours ago
This is basically the goal of fearless_simd, but of course achieving the same level of maturity will take time.
camel-cdr 14 hours ago
I love how ever example of portable SIMD isn't portable.
They specifies a constant SIMD width so it's non-portable. Well, not performance portable, but why are we using SIMD again?
jandrewrogers 9 hours ago
The capabilities of various SIMD ISAs don't have enough intersection to be portable outside of relatively trivial cases. Many of the somewhat unique capabilities are load-bearing, so you want to use them on architectures that support them. Taken in whole, someone who cares about performance would be using different data structures and algorithms depending on the specific SIMD architecture and that is nearly impossible to abstract in a library. Too many important but complex details are idiosyncratic to the implementation.
Another way of looking at it is that our programming environments are not sufficiently powerful and expressive to create the necessary abstractions to make SIMD truly portable.
zamadatix 12 hours ago
It should really be read/advertised as "portabler SIMD". It beats hoping the compiler autovectorizes everything well forever or writing architecture specific code manually again but is going to compromise on average performance vs platform specific SIMD.
pjmlp 5 hours ago
.NET and Java have three levels of SIMD support, Go's ongoing efforts, and does the upcoming C++ standard.
Autovectorization, depending on compiler's cleverness, really portable SIMD operations, and then the CPU specific SIMD ones.
So this should be perfectly doable in crate that advertises as portable, while leaving the non portable stuff to another crate.
exDM69 4 hours ago
> They specifies a constant SIMD width so it's non-portable.
This is incorrect, you can use vectors wider than native SIMD width and the compiler will break them down to register size of the target cpu.
In fact it's sometimes better to used wider than native width, in some applications I see 20% better throughput with f32x16 (512 bits) on an AVX2 CPU (256 bits). It is kinda like loop unrolling it.
camel-cdr 4 hours ago
Except you can't use this in actual code, because either, as is the case in this example with f32x32, you run out of registers and spill all over the place. Or you aren't using your full vector register or could've gotten better performance by "unrolling" more often for the larger vectors.
If you use f32x16 (the avx-512 wisth), SSE now effectively has 4 registers to work with and will spill when doing anything beyond the most simple stuff.
The default should imo be relative to the native register width, so you can do 1x, 2x or sometimes 4x the native width, depensing on your register preasure.
exDM69 2 hours ago
tyho 14 hours ago
Go's implementation is vector size independant https://pkg.go.dev/simd@master
IshKebab 12 hours ago
Sure but there's no real way to use that in a portable way, at least not a way that maximises performance on every CPU you run it on. That's pretty much impossible at the moment.
krapht 11 hours ago
MomsAVoxell 13 hours ago
Why should it be portable? Honest question.
SIMD seems to me, to be very platform specific. Maybe there are times one SIMD unit is not anothers' SIMD unit?
camel-cdr 13 hours ago
The create is called portable_simd.
There is no reason a portable_simd relu_dot implemention should need to specify the SIMD width.
But the design and documentation of portable_simd makes the fixed size syntactically easy/the default and the width agnostic code harder.
dwattttt 12 hours ago
exDM69 4 hours ago
Counter question: why shouldn't it be portable?
It's definitely a 80% solution where you occasionally need to drop down to intrinsics (at zero runtime perf cost) for CPU specific instructions.
But just having vector types, arithmetic, swizzling, loads and stores will go a long way for basic tasks.
And with generics you can write code that is type and width agnostic. No need to rewrite your code of you want to go from SSE to AVX512, just change from f32x4 to f32x16 (or use generics) and you are done.
nynx 15 hours ago
Do you have examples of complex algorithms running on the gpu with rust with competative performance? Radix sort might be a good one to start with
LegNeato 16 hours ago
Author here, AMA.
lbhdc 15 hours ago
What is vectorware's business model? Are you planning to sell support/consulting to companies using your stack? Or are you looking to sell licenses to your tool? Or something else?
LegNeato 15 hours ago
The tentative plan is to open source all the compiler and `std` bits with our products built on top (compilers are not good businesses). More about our products coming in the next couple of months!
lbhdc 14 hours ago
jcranmer 15 hours ago
The post is kind of vague on the IR you're targeting. Can you give some examples of what the SIMD-ized IR looks like, and how it maps to the target PTX?
LegNeato 15 hours ago
Didn't want to go into crazy detail in the post.
Each family of operations is a trait parameterized by the operation itself:
pub trait EvaluateReduction<Operation, T>: LaneEvaluator {
/// Reduce one distributed definition to an ordinary uniform scalar.
fn evaluate_reduction(&self, value: LaneValue<Self, role::Distributed, T>) -> T;
}
Call sites name the operation: let one = evaluator.splat::<Splat, _>(1_u32);
let two = evaluator.splat::<Splat, _>(2_u32);
let three = evaluator.binary::<Add, _>(one, two);
let total = evaluator.reduce::<Sum, u32>(three); // a uniform u32
let running = <Executor as EvaluateScan<Scan<Sum, Exclusive>, u32>>::scan(&evaluator, three);
Operations like Sum, Max, ReduceXor, Inclusive, and Exclusive are all distinct types.As mentioned in the post, execution shape is typed too. A static shuffle takes its control as a type-level constant, and the shuffle mode constrains which controls are expressible:
// Shift down one lane, keeping our own value where the source is inactive.
let down = <Executor as EvaluateShuffle<Shuffle<Down>, DownOrSelf<1>, u32>>::shuffle(&ev, v);
// Broadcast from lane zero.
let bcast = <Executor as EvaluateShuffle<Shuffle<Broadcast>, WarpLane<0>, u32>>::shuffle(&ev, down);
// Butterfly exchange with the neighbor one bit away.
let bfly = <Executor as EvaluateShuffle<Shuffle<Xor>, Butterfly<1>, u32>>::shuffle(&ev, bcast);
For an example of errors caught, a warp-scoped executor for a device-scoped barrier is a compile error: <ScopedWarpExecutor<'_, WarpUniform> as EvaluateBarrier<Barrier<Device>>>::barrier(evaluator)
// error[E0277]: the trait bound `Device: NvptxBarrierScope` is not satisfied
// help: the trait `NvptxBarrierScope` is implemented for `Warp`
Strip mining is typed on the amount of work and the lane capacity, and it hands back one chunk at a time along with the predicate saying which lanes live in that chunk: // Six work items across four active lanes: two chunks, based at 0 and 4.
<Executor as EvaluateStripMine<StripMine, (WorkItems, ActiveLanes<StripMined<4>>), i32>>::
for_each_strip_mined(
&evaluator,
(WorkItems::new(6)?, ActiveLanes::new(4)?),
|index, active| {
// ...
},
);
Hopefully that gives the flavor of it.the__alchemist 15 hours ago
I'm confused too. How does this fit between these approaches for paraellization:
- CUDA kernels and Tiles (e.g. Cudarc, cuda-oxide, rust-gpu etc) - SIMD on the GPU. (E.g. as in the title...)
- CPU SIMD using avx or SSE instructions (And probably thin wrappers for vectors so you can have sane syntax). Or the maybe-upcoming core simd which should abstract over architecture-specific instructions. Magic floats etc which do 4-16 computations at once, but are a bit clumsy to work with
- Rayon thread pools - arbitrary parallel computations, including SIMD, one per CPU core.
It looks like from the code samples like maybe a cleaner syntax for writing code on the GPU than CUDA kernels? E.g. without mucking with serialization, host and device by abstracting over it? And inspired by core::simd. (Good choice if so, in the interest of standardizing on syntax; I did this for my x86 SIMD vector/quaternion lib as well)lbhdc 15 hours ago
This is really cool! It sounds like y'all have a compiler fork that you are using to make this work. I wanna tinker with this, is your compiler available?
LegNeato 15 hours ago
It is not currently available but we intend to make it available after we launch our products.
bbminner 12 hours ago
If you have to express your computation using an "array programming DSL" with things like scan and gather anyways - why not opt to use torch/tensorflow/jax or anything else that targets MLIR? An example of writing a relu using an embedded array DSL is really not helping your case either - that's exactly the problem that these other solutions mentioned above are successfully solving for the past ~15y (starting with theano etc). Not sure what this brings to the table - doing that AoT instead of at runtime?
LegNeato 11 hours ago
The goal of this work is to run existing unmodified CPU libraries (which may use core::simd) on the GPU. If you are manually writing ML-shaped workloads, it doesn't add any value over writing with tech like torch/tensorflow/jax which are custom built for those use-cases (except maybe familiarity if you are a CPU programmer).
Eridrus 15 hours ago
Given the massive demand for GPUs for LLMs, what sorts of work do you expect to economically benefit from utilizing GPUs more?
LegNeato 15 hours ago
Part of our thesis is that decent GPUs are in every shipping device and most software doesn't use them and should.
Eridrus 14 hours ago
shay_ker 13 hours ago
Hm is the intent to one day replace the CPU?
LegNeato 13 hours ago
The goal is to use similar abstractions and code across both the CPU and GPU where it makes sense.
PoignardAzur 14 hours ago
Any thoughts about SIMD-related crates?
max-m 12 hours ago
How was your day?
melodyogonna 10 hours ago
Very interesting. But GPU programming gets complicated when you start doing 3d computation on very large data, will be interesting to see how tensor abstraction is built on top of this. Another point is that this is using fixed-width SIMD vectors; unless there is a way to compute this statically based on available GPU info, performance will always be left on the table.
frollogaston 8 hours ago
I've noticed a lot of articles about SIMD on the HN front page. That's cool, but just wondering, is there some reason this is more in focus lately?
vatsachak 7 hours ago
SIMD is actually underrated still. Programmers should always be thinking about it. It's a free 4x in a lot of cases
pjmlp 5 hours ago
The main problem with SIMD is it is a complex subject, even if not that good, autovectorization wins over what most common devs know about SIMD.
Well now you could in theory AI generate SIMD, which will be vibe coded, as those devs have no idea of its correctness.
frollogaston 7 hours ago
I did see the article about that too. Don't know about "always" since there are applications like web backends where you're never going to add arrays of floats or something. Even if it's data science stuff, if that's in Python, Numpy is doing the SIMD for you.
skitsofrandom 7 hours ago
I’m wondering if AI has made SIMD intrinsics much more approachable for many and so there are just more people working on abstractions for their workflow of choice right now. There’s probably a lot of code out there that could benefit from SIMD but the effort to actually use it was too high for the return.
LoganDark 7 hours ago
Maybe things being on the front page reminds others? After seeing something, sometimes you can have ideas relating to it for a while.
samuell 7 hours ago
Yes, this kind of thing seems to happen quite often. Popular posts spurring further posts on a theme.
dev_dan_2 12 hours ago
Really exciting work and great write up, thanks a lot and all the best to your startup!
`core` instead of `std` is great too!
This will become useful in one of my sideproject where I use bitmaps to speed up pathfinding, exited to try it out!
efnx 16 hours ago
Congrats to the Rust-GPU folks! Nice to see the good work flowing.
nperez19 13 hours ago
Love the pendantic mode setting on the website
minraws 13 hours ago
I don't get what's the value of it not being enabled by default what does the toggle get us, really? Maybe I don't understand web design and it makes it harder to read for some, I am dyslexic and never had any issues.
LegNeato 12 hours ago
It's just a way for us to add minutia and details that most don't care / need to know about. There are three audiences we try to make the posts accessible for: Rust people who don't know about GPUs, GPU people who don't know about Rust, and non-Rust non-GPU people. The toggle lets knowledgable readers go "wait, what about..." and hopefully the toggle answers it.
minraws 11 hours ago
the__alchemist 15 hours ago
Hey - this is probably off-topic/meta, but what is going on with the comments here? Is it bots?
dev_l1x_be 15 hours ago
No idea, but it seems HN needs POW challenges.
lukan 15 hours ago
Could also just be trolls attracted by the Rust topic.