If GPUs Are So Good, Why Do We Still Use CPUs at All?
codingstuff.substack.com
codingstuff.substack.com
Maybe to a layperson this is an interesting question, but surely on this site the answer is obvious.
I skimmed through anyway, just on the off chance he's talking about some new GPU development I didn't know about. But no, it's just a clickbaity title for a basic "pros and cons of parallelizing" article of mediocre quality.
Why don't you write one with good quality (if not great) that is understandable to the audience of laypersons interested in, having or asking this question.
Teddy Wahle did a good job explaining it to non-professionals who are using gaming laptops/PCs in their daily lives for content creation and such.
If I can think this question, imagine thousands (if not millions) of people who have or will have similar questions.
Freight trains carry a lot, but are really bad at branching. Same with GPUs.
GPUs are massively parallel limited purpose CPUs. You're always waiting for the program counter to get to the end, and getting data to/from Ram.
My project (since the 1980s) is to put compute and memory together, so you can either store 16 bits, or do a 4 bit look up with the same data in every cell (which has 4x16 bits total). Having no memory-compute barrier should allow for pushing data through a program, in a physical sense. Like an FPGA, but easier to program.
Imagine such a chip at a few Ghz, delivering one output per clock cycle for a given task.
Therefore I can't yet imagine how to easier onboard people who learned on a "classical CPU". Can you go a bit deeper in what makes your system easier to program?
By making each cell identical you remove most of the floor planning concerns. By latching the data from each cell, you remove timing concerns, and deliver deterministic performance.
This has other benefits like being possible to route around a bad cell, or to build a "wall" around a given section of code.
The emulator will always give correct output baring hardware failure.
I hope to get through tiny Tapeout and have a chip before the end of the year.
As for programming, the code has to compile to a directed acyclic graph of boolean logical operations. I suspect that it would be fairly easy to use as a backend for tinygrad or perhaps LLVM as a stretch goal.
The XC6200 became news once as they[1] used an evolutionary algorithm to create a configuration that can detect a single frequency tone. The resulting configuration was determined that it was impossible that it could work, but it did. Placing the configuration in a different part of the FPGA broke it.
It seemed that algorithm used the analog properties of the FPGA in that specific location to get to a smaller result.
[1] https://www.idi.ntnu.no/emner/tdt22/2011/Thompsonieeeehw.pdf
In the same sense, parallelization (and computing on a GPU) are great when you need to perform the same computation on a huge number of data. When each computation is more independent, it might make more sense to perform it through the CPU.
CPUs are actually hugely parallel today. Every CPU, even the "small cores" (E-Cores from Intel or little cores from ARM) are out-of-order superscalar cores. This means that every CPU can execute 2, 3, 4, 6, 8 or more instructions per clock tick.
What a CPU is, fundamentally, is a device that takes a classically written sequential program and *DISCOVERS* parallelism for you. And then executes that discovered parallelism.
A GPU, and SIMD in general, is the opposite. SIMD requires that the programmer writes the program in a grossly parallel manner before it even executes on the GPU. Naturally, GPUs lead to more parallelism, not due to magic but simply because the programmer thought about parallelism in a more scalable way.
For that example, couldn't you do exactly that? Multiplication is commutative
avg(a, b) = avg(b, a)
but avg(avg(a, b), c) != avg(a, avg(b, c)) rock vs paper = paper vs rock
but (rock vs paper) vs scissors != rock vs (paper vs scissors)Trying to decompose a problem in this way ranges from hard to impossible.
There are at least four main ways to break apart a problem into parallel work units:
Search-type problems, where you want to do a lot of computations and discard most of them, are easy to parallelize. The extreme case is a crypto key tester. A Bitcoin miner and the WWII "bombe" are examples. The amount of data that comes out is tiny compared to the work done.
Then there are problems which decompose spatially. Space is what keeps everything from being in the same place. Fluid flow, weather prediction, and hydrodynamics of nuclear explosions are all like that. The space is divided into regions and the regions talk to their neighbors. So most communication is local. Those are classic supercomputer problems. Machine learning, of course. Units are connected to local neighbors. So that, is in a sense, spatial.
Graphics rendering does large numbers of triangles in parallel. The combining operation has to be associative and commutative. Z-buffers make that happen for opaque and transparent surfaces. Translucent is harder, but there are tricks to pull that off without a depth sort. See "order independent transparency".
There are assembly-line approaches, where data progresses forward through multiple stages, with little or no data moving backwards. Audio and video processing work like that. So do some kinds of vision systems. This is the oldest approach - it's how large punched card tabulator shops worked, with trays of punched cards being carried from machine to machine.
Most things you can do on a GPU seem to fit into one of those four patterns. If you can hammer a problem into one of those four patterns, a GPU might help. If not, well...
Did I miss any core patterns?
https://en.wikipedia.org/wiki/Kahan_summation_algorithm#Alte...
Raytracing (which is a tree traversal) is a notable exception because there's so many millions of rays that get launched, that it becomes efficient to group them back together and then parallelize the traversals all together.
So if you have "enough" of them going at once, its back to GPU Superiority. But a *SINGLE* linked list or *SINGLE* tree search will always be best done on a CPU.
+ Efficient decoding of one instruction stream for many parallel cores.
+ Efficient parallel access to lots of data in regular layout with address offsets relative to core index.
- Lots of instruction streams quickly require serialization, given only one instruction stream can be handled per block of co-working cores.
- Only 1 or a few parallel calculations per stream, leaves rest of co-workers unused.
- Irregular data layouts, force parallel memory hardware to act sequentially, with each core getting access while the others wait their turn, until all cores access is complete.
- Lots of branching stalls cores, as cores that take one branch execute, while the other cores stall, and then vice versa.
CPU cores:
+ Efficient decoding of different instruction streams per core.
+ Efficient with different memory layouts, due to per core caching.
+ Highly optimized for branching.
- Parallel calculations across cores suffer from redundant instruction decoding overhead for every core.
- Highly parallel calculations suffer, from the relatively low number of cores.
GPU and CPU:
+ Both optimized for sequential data access across sequentially offset addresses.
CPU Bonuses:
+ They handle overall system initialization, power & device management, and other system tasks.
+ SIMD instructions let each CPU core perform a bit like a little GPU core group.
Perhaps a better question is does our current CPU architecture is the most suitable for data processing and the likely answer is no.
There are much better architecture for data processing tasks namely dataflow CPU architecture [1].
There's a new trend moving toward dataflow based architecture for data processing and AMD is one of them [2].
I think a combination of dataflow architecture optimized for array computation is the best for data processing because it generalized almost all common data processing techniques including spreadsheets, databases, graphs and matrices [3].
[1] Dataflow architecture:
https://en.wikipedia.org/wiki/Dataflow_architecture
[2] AIEBLAS: Open-Source Expandable BLAS Implementation for AMD/Xilinx Versal Device:
https://github.com/atlarge-research/AIE-BLAS
[3] Mathematics of Big Data: Spreadsheets, Databases, Matrices, and Graphs:
https://mitpress.mit.edu/9780262038393/mathematics-of-big-da...
I think the comparison makes a lot more sense when you consider a GPU compared with a system on the chip where the SOC can do the basic work that you would do. To the average gamer, it looks like the only part of a “CPU“ that is improving is the graphics.
So we might as well put everything on the GPU at this point. This goes hand-in-hand with the most showing games rendering via AI, storage being added to graphics cards, and AI processing in general.
He helps build This notion that maybe things wouldn’t be bottlenecked by the CPU anymore if we switched to a new paradigm.
I think maybe if the switch to ARM happens for PCs, we will see more development on that idea of an SOC bundled with high end GPUs.
It would be nice to be able to take random bits of code and actually look at what you win / lose by running (unoptimized) on GPU vs. CPU.
Hummm various models seem to think the problem is nobody has done the work of figuring out how stuff like memory allocation and function control flow would work.
I'm surprised there isn't a fun github with a minimalist proof of concept or that none of the big players have implemented (even very inefficient) instructions in their GPU to allow general purpose computing.
There's nothing particularly special about graphics programs. It's just that until Vulkan and Metal, the pipelines were more or less defined for you (with OpenGL, DX, etc).
Just like we have for loops and threads that work on all CPU architectures and cores.
Instead we have a mess of various CPU instructions (SSE, AVX, NEON, SVE) along with incompatible GPU frameworks (CUDA, rocm?).
Java is trying to do that with Vector api but only for stuff on CPUs, so GPUs are not included [0].
If we had a unified way to take advantage of CPU and GPU, I’m sure devs would do it more.
#pragma openmp simd
for(int i=0; i<1000; i++){
z[i] = x[i] + y[i]; // Look ma, I'm parallel!
}
Relies very heavily on the autovectorizer of GCC / LLVM, but it works somewhat well if you know what you're doing. Also, I'm pretty sure #pragma openmp simd doesn't actually do anything but maybe makes the "failed to vectorize" warnings a bit more obvious??I dunno, I don't play with the auto-vectorizer that much actually.
Say you can readily get CPUs that are 1/100th the TFLOPS of the GPUs.
Could you use 10000 of the CPUs and get performance on LLM training similar to what the 100 GPUs would have given?
To use a gross analogy, why do we use Otto cycle engines when (emissions aside), gas turbines exist?
Don't forget: as Intel was floundering with Itanium, AMD was just making SSE and SSE2 instructions faster (AMD 3dNow! never became popular, but they continued to push that). It seems like SIMD was the historically correct choice over VLIW.
Today: every high-speed program in Desktop/Server is either SIMD-accelerated CPU (NEON or AVX512 or SVE), or full-blown SIMD GPU.
It's just that execution units are so small relative to everything else that you might as well expend some extra silicon for them.
SIMD is only really wins for very specific workloads (memory copy and 3D graphics, for example).
I do wonder if today VLIW machines with modern compilers could be a thing. Compiler technology is so much better now, maybe we are in the era where VLIW could be renewed. But on the other hand, CUDA shows that SIMD-compilers / SIMD-languages have improved as well.
Though I expect the lines between hardware paradigms to get a bit blurry in the near future.
Who ever claimed that?