HomeVideos

Horace He: Building Machine Learning Systems for a Trillion Trillion Floating Point Operations

Now Playing

Horace He: Building Machine Learning Systems for a Trillion Trillion Floating Point Operations

Transcript

1511 segments

0:00

- So it's my great pleasure

0:01

to introduce our speaker tonight, Horace He.

0:04

Horace He graduated from Cornell in 2020,

0:08

and he works at Meta on the PyTorch team,

0:10

specifically at the intersection

0:12

of compilers and machine learning.

0:16

If you've used things like torch.compile,

0:18

which is a thing in PyTorch

0:19

that makes your model goes

0:21

like 2-4x faster in just one line of code.

0:23

Or if you've used FlexAttention,

0:25

which is something that lets researcher

0:27

design fast kernel for attention

0:30

without leaving the world of Python.

0:32

He's the person responsible for both of those things.

0:35

You should also check his blog, which is pretty awesome,

0:37

and in particular the blog post

0:40

"Making Deep Learning go Brrrr From First Principles."

0:43

And without further ado, I'll give it to Horace.

0:46

- Thanks for the intro.

0:47

Today I'm gonna give a talk about

0:49

building machine learning systems

0:50

for a trillion, trillion floating point operations.

0:53

My name is Horace He,

0:54

and I'm on the PyTorch compilers team at Meta.

0:58

So, you know, I think we live in pretty unprecedented times

1:02

in terms of an infrastructure build out,

1:04

which is, I think, you know,

1:05

nicely reflected in NVIDIA's stock price.

1:08

I feel like, you know, basically every month,

1:10

we see like a different headline

1:12

about like a new nuclear power plant

1:13

from like Microsoft, or, you know,

1:15

like a massive 300K GPU data cluster from xAI.

1:19

You know, people like,

1:20

I feel like when Amazon first like built like a,

1:24

or like bought a nuclear power center,

1:25

it was like a big news.

1:27

But now practically like, you know,

1:28

every cool AI company

1:30

has their like own nuclear data center.

1:34

And you know, of course,

1:35

this like kind of really massive (indistinct) build out

1:37

has kind of also resulted in pretty ludicrous fundraisers

1:40

from startups, I think.

1:42

Like, I remember back in like 2016, like,

1:44

you know, if you like made it as a startup,

1:46

it would be like if you were worth a billion dollars, right?

1:49

It was like a unicorn.

1:49

It was like the mark of like really making it

1:51

as a startup beyond your wildest dreams.

1:53

But you know, in 2024, you know,

1:55

you gotta raise a billion dollars just like get started.

1:57

You know, it's like just to play the game, (laughs)

2:00

you need like a billion dollars, you know,

2:02

of which most of it goes to NVIDIA (chuckles).

2:05

(audience laughs)

2:07

And so I think like, it's kind of crazy to think that,

2:11

you know, all of this, like, you know,

2:12

billions of dollars is really just to do

2:14

like absolutely insane amount

2:16

of floating point operations.

2:17

Here's like a nice chart from like Epoch AI

2:20

showing like the growth of compute over time.

2:23

And like, these floating point operations

2:27

are really just like big matmuls

2:28

done like over and over, like, you know,

2:30

for millions of iterations over like months.

2:33

And nowadays,

2:35

like the leading edge models are currently trained

2:37

with about like one E26 floating point operations,

2:40

which is approximately 100 trillion

2:44

trillion floating point operations.

2:45

So like a trillion trillion, like a trillion TeraFLOPS

2:50

worth of floating point operations.

2:52

And so, you know, back, you know, another kind of effect

2:55

that you might've noticed is like back prior to 2016,

2:58

if you search on like Hacker news like ML,

3:00

you'd often get like a lot of people asking about, you know,

3:02

the ML family of languages.

3:05

But nowadays, you know, you search ML on a hacker news

3:08

and you get like a very different like type of article.

3:15

And so I think one of the things

3:16

that kind of is missed here is like, you know,

3:18

with all these like billions of dollars

3:19

and like, you know, yottaflop of operations,

3:21

it's kind of easy to forget that, you know,

3:23

like these operations needed to actually run somehow

3:27

on the like machines.

3:29

And so, you know, a modern stack might involve, you know,

3:33

like, you know, call like an LLM API

3:34

and then it's called like PyTorch, like NCCL, Triton, CUDA

3:37

like NVCC, like all these like different layers in the stack

3:40

that like somebody was involved in writing.

3:44

And I'm often reminded of this like XK CD comic, you know,

3:47

showing like the state of like modern digital infrastructure

3:51

and so, you know, you kind of, you know,

3:52

often have forget about all the infrastructure

3:54

that was involved for you to like get where you are,

3:57

but like really we're just kind of building

3:59

like layers on top of layers.

4:03

And so, you know, if you work in a systems, you know,

4:05

like I do and I suspect many of you do, you kind of,

4:07

I think oftentimes think about your work a little bit like,

4:11

like a benevolent dictator of sorts.

4:14

Where kind of you imagine like what you're doing here

4:15

is that like a lot of people build on top of your work.

4:18

And so if you can kind of, you know,

4:19

give like a small amount improvement to, you know,

4:22

like millions of people, you know,

4:23

your small improvements can lead to like, you know,

4:25

significant impacts on the world.

4:28

And so for example, I kind of imagine like Guido,

4:30

with Python, you just like, you know, kind of sits on top

4:32

of this cloud and you know, eventually like gives us like

4:34

NoGil or like faster CPython or like the Walrus Operator

4:38

and I feel like this is kind of oftentimes like

4:40

how I imagine like infrastructure work did.

4:43

And it's kind of a lot of why I got into infra work

4:46

in the first place.

4:48

And so on the other hand, I feel like if you work in ML,

4:51

things can sometimes feel a little bit different.

4:53

I originally came across this a post on threads

4:55

where this person said, you know, my main gripe

4:57

with like working on top of like LM APIs

5:00

is that you're not really like,

5:01

no one is like engineering anything.

5:03

You're just like chanting like a prayer

5:06

to this like manmade demon deity to like do your bidding.

5:09

And this like very similar to this kind of like

5:11

Shoggoth metaphor that has become like very popular

5:14

in deep learning circles.

5:15

Where the idea here is that like we really like

5:17

put all these like matmuls

5:19

and like computation into producing this like very weird

5:22

alien intelligence.

5:24

and then we like kind of like RLHF it, you know,

5:27

and like provide it in this like nice convenient interface

5:30

to people with like, you know, ChatGPT

5:32

or something like that.

5:34

But you know, I think if you think about it,

5:37

I think we're kind of working with Shoggoths

5:39

like all the way down.

5:42

Like even like if you're working like in systems

5:45

and you're not calling like ML models,

5:48

you still have this kinda like, you know,

5:49

massive amount of infrastructure that you presumably

5:51

don't really understand written by people

5:53

you've probably never talked to.

5:55

And like the end result know they try to expose

5:58

is like some kind of a simple interface

6:00

where you can just like import torch

6:01

and then, you know, hopefully run your code across like,

6:03

you know, a 100K GPUs.

6:08

And so I think as a result there are some,

6:10

I think, interesting ways in which I think ML models

6:12

that feel kind of different from regular systems.

6:16

One of those ways is that like ML models

6:20

are extremely simple and as a result

6:23

we have like very high expectations

6:25

from the performance of like the code.

6:28

I think it kind of like trace this all the way back

6:31

to this very nice article called the "Bitter Lesson,"

6:33

which I'd really recommend reading if you guys

6:35

haven't come across it before.

6:37

Where their main observation here was that like,

6:39

clever ideas in machine learning have basically

6:42

throughout like its 50 year history always lost out

6:45

to just simple ideas that like scaled really well

6:48

with Moore's law.

6:51

And I'm not really like joking here when I say

6:53

that like machine learning logic is exceedingly simple.

6:56

There's like this cool project from someone

6:58

called like Andre Pathy called llama2.c.

7:01

And basically here he's like implemented

7:04

like llama2 in about like 973 lines of C.

7:08

With like no other dependencies.

7:09

Like, you know, every single loop,

7:11

every single like matmul is just implemented from scratch.

7:14

So with 973 lines, you can't like, you know,

7:16

do it very fast, but it does run

7:19

and I think it does kind of indicate just like

7:20

how fundamentally simple the models

7:23

that like we're spending all this compute end up being.

7:27

And so the end result like

7:29

although like the problem themselves are extremely simple

7:31

and like very like easy to optimize in some sense.

7:37

The expectations are very high

7:38

for how well we can optimize these matmuls.

7:41

So one example here is that like the predominant metric

7:44

for measuring your like model's performance in deep learning

7:47

is called model FLOP utilization.

7:50

And so this is basically the like percentage

7:52

of the theoretical max flops that your GPU is able to do.

7:57

And so if you kind of think about this,

7:58

this is actually like a very absurd metric to hit.

8:01

Like if on a CPU, like you measured any of your code

8:04

by this metric, like you can only hit a 100%

8:06

if like at every single time every single core

8:10

of your CPU is always issuing max width SIMD instructions.

8:14

That's like the only way you can hit a 100% utilization.

8:17

And so, you know, if you take a look at any of the code

8:19

that you guys presumably write,

8:21

like almost no CPU code is like anywhere near like,

8:24

you know, a 100% flop.

8:26

It's probably like way under like 1% most of the time.

8:30

On the other hand, in like machine learning

8:31

for like large scale training, we're typically often hitting

8:34

around like 50% of like the peak flops.

8:37

And this is I think like this is kinda like a indicative

8:42

that like even though the overall problem

8:43

is like very simple, the kind of corresponding difficulty

8:46

just goes into like making your like models hit

8:50

like this, like very high perf barrier.

8:54

Another ind of a interesting observation

8:57

in like machine learning is that,

8:59

that has kind of exacerbated this a bit.

9:01

Is that the field has consolidated significantly

9:04

over the last five to 10 years.

9:06

So one of them is that like, you know,

9:08

like maybe 10 years ago you had like a lot more architecture

9:11

is a lot more variance and like different things

9:12

that people were trying.

9:14

But nowadays people really just like transformers are like

9:17

the dominant architecture for everything.

9:18

Like you have transformers for vision, you have transformers

9:20

for language, you have transformers for like, you know,

9:23

audio, it's kind of like all transformers.

9:26

And the other way like things have kind of changed

9:28

is that instead of like, you know, many different people

9:31

training SOTA models.

9:32

You oftentimes just have a few companies

9:34

that are training SOTA models.

9:36

And so we've kind of gone from a bit of like a monopoly

9:38

where like, you know, previously there was this like,

9:41

you know, one person providing the infra

9:42

and like many people using the infra,

9:44

to in some ways it feels a little bit more like monopsony

9:48

where you have like many people trying to provide infra

9:50

and then you know, only one person's actually training

9:51

the job at the end of the day.

9:57

And so as a kind of result, I think that like

10:00

there's kind of two general ways to think about

10:02

like getting performance from like your systems.

10:05

And so one of the ways generally is basically

10:07

like optimizations is like you have a compiler,

10:09

you like make the compiler faster, you know,

10:12

you improve like the performance

10:13

of everybody using your compiler.

10:16

and so the other way though

10:17

is kind of like programming models.

10:19

And so like this is kind of analogous

10:21

as opposed to like a system whose responsibility

10:23

is to like chop down a tree

10:25

and then you know, you're just like optimizing

10:27

how fast you can chop down the tree.

10:28

And the other alternative is like you're providing tools

10:31

for people to cut down the trees themselves.

10:35

And so to kind of like talk a little bit

10:36

about programming models, I think it's like illustrative

10:40

to kind of talk about how like ML frameworks

10:42

have kind of evolved in terms of what program model

10:44

they've exposed to users.

10:47

So originally like I think like 2010,

10:51

like 2011, 2012, the first like ML framework

10:54

that kind of got a lot of popularity was this framework

10:56

called Caffe.

10:57

And the way you like express neural networks in Caffe

10:59

is like a very declarative nature.

11:02

And by that I mean you like edited a protobuf file I think.

11:05

Where like the protobuf like specified all the things

11:08

you needed like care about your neural network.

11:11

And so as you can imagine, you know,

11:12

programming protobufs is like not very fun, you know,

11:14

there's a lot of things you might want to do

11:17

that you can't do in protobufs.

11:18

and so a like natural net thing that people did

11:21

was kind of these kind of graph builder type APIs.

11:24

And so this is kind of, you know, how TensorFlow 1

11:26

kinda of look like where the idea is that oh, you know,

11:28

like programming like, you know, programming protobufs

11:31

or like no human should need to write protobufs by hand.

11:34

And so ideally, you know,

11:35

you should just like write like a DSL of sorts

11:38

that allows you to generate the protobufs

11:40

like from this DSL.

11:43

However, like even this DSL still kind of has

11:46

like a lot of confusion in it.

11:48

Like it is not super clear that like given this DSL

11:50

like how code actually executes on your GPU.

11:54

And so kind of finally like around 2016 or 2017,

11:57

PyTorch started to become like really successful.

12:00

And kind of the like the feature

12:02

that like was most emblematic of PyTorch was basically

12:06

what was called imperative or eager execution.

12:08

And so what this means is that like,

12:13

or like, yeah, so PyTorch you know, was very successful

12:16

and I think it's like worth talking about

12:17

like why eager execution was so successful.

12:20

And I think that the main reason it was successful,

12:22

it just comes down to like what the programming model

12:24

of the execution looked like.

12:25

Where in a imperative slash like eager execution

12:28

is basically like, you know, you call a function,

12:30

the GPU runs a function

12:32

and then the function finishes and you know, that's it.

12:35

Like, you know, like that's basically all you do.

12:37

you call like torch.matmul and this is basically

12:39

the sequence of operations that happens but you know,

12:42

with kind of like a graph mode type approach

12:45

or you know, where kind of this pilot interjects.

12:47

You first like define the function,

12:49

the function gets converted into some like intermediate IR,

12:52

you know, a bunch of who knows what happens to your function

12:55

and then eventually like the function eventually executes

12:58

on the GPU.

13:00

And like the top one was like a very simple

13:03

execution model for people to understand.

13:06

And I think another thing that's kind of interesting

13:08

to notice about this is that this kind of also just,

13:11

like the top half also kind of describes

13:13

how Python executes.

13:16

And I think this is kind of a pretty illustrative to me

13:18

of like why Python has been so successful

13:21

in machine learning.

13:22

It's basically that like, like a funny statement

13:26

that you can make about Python is that like if you tried

13:28

to like train a model today, like you know,

13:32

I gave you like a day to like train a model,

13:34

it would run faster if you ran it in Python

13:36

compared to doing in C++.

13:38

And you might argue that this is like a unfair comparison.

13:42

Because you know, like you know all the infra like,

13:44

you know, all the frameworks that people built

13:45

are in Python.

13:47

But I think the reason why so many

13:48

of these frameworks have been built in Python

13:50

is that Python is like an exceedingly like

13:53

a simple language.

13:54

And so it's also like a very global language.

13:57

And what I mean by like global is that like it's very easy

14:00

for people to build their own infrastructure

14:02

on top of Python without really needing to fight

14:04

with like anything the Python language does

14:06

because the Python language itself does basically nothing.

14:11

Like it, you know, it doesn't do any optimizations for you.

14:13

It just like, you know, takes your function and runs it.

14:16

And so I think that PyTorch basically historically

14:19

is at like a very similar point in the design space.

14:22

Where PyTorch is like execution model is like so very simple

14:27

and although this like doesn't really give you a lot

14:30

in terms of like, it doesn't like automatically

14:31

do a lot of things for you, it does mean that it's very easy

14:34

for people to like build their own infrastructure

14:38

and frameworks on top of PyTorch.

14:46

I think another important detail to realize

14:50

especially about like PyTorch when it first came out.

14:52

Is that like this kind of unoptimized execution

14:55

didn't actually even sacrifice any performance at all.

14:59

Like, you know when people kind of benchmarked

15:00

PyTorch versus like, you know, TensorFlow at Cafe,

15:03

PyTorch oftentimes wasn't even slower than those frameworks.

15:07

And I think there's like a two main reasons for this.

15:09

So the first reason is that back in the day,

15:11

like you know about like 90% plus of your time

15:14

was spent in matmuls.

15:15

And so there's basically nothing else you need to optimize.

15:17

And so matmuls here are like matrix implications

15:20

and so they're often provided by these like vendor libraries

15:22

like cuBLAS or like QDNN.

15:25

And like you know, they're provided by NVIDIA.

15:26

and they're like very hand optimized

15:28

and so, you know, if 90% of your time is spent in matmuls,

15:31

then like what else can you even do to like optimize

15:34

the performance of your neural network?

15:37

And I think another kind of interesting like

15:42

important piece here for like why PyTorch's performance

15:45

was like quite good is that it had this like

15:47

Async execution model.

15:49

Where basically the idea here is that like you kind of have

15:52

like a parallel work queue on your GPU.

15:54

And so what the CPU does is that it's only responsible

15:57

for scheduling work on your work queue

15:59

and then you know, the GPU executes work from the work queue

16:03

and I think of it generally as like this GIF

16:06

is what usually comes to mind and basically you can imagine

16:08

that like the dog is like,

16:10

or Gromit is like Python, you know, they're like, you know,

16:13

trying to put down the train track in front of the train,

16:16

which is the GPU.

16:17

And so, you know, as long as like Gromit is able

16:20

to put down the train tracks faster than the train

16:23

actually rolls along the train tracks,

16:25

you can actually kind of view Python

16:26

as like having zero overhead.

16:28

Like it doesn't provide any extra cost compared to,

16:32

you know, if Python was like in a more efficient language

16:34

like C++.

16:37

And so in this way like you know,

16:39

eager execution not only had like a much easier

16:41

to understand program model for users,

16:43

it was also like basically just as fast

16:46

as like non eager execution.

16:50

Unfortunately, you know, good things that never last.

16:53

And in 2017 like NVIDIA introduced

16:56

what are called like tensor cores.

16:58

And if you guys are unfamiliar with tensor cores,

17:00

they're basically like hardware units on the GPUs

17:03

that only do matmul operations.

17:06

Like, and I don't mean this like figuratively in the sense

17:09

that like people often say that GPUs are like

17:11

well suited for matmuls.

17:13

I mean this like very literally

17:14

in that like there's actually an assembly instruction

17:16

that just does like a mini matmul.

17:18

And this is how you like interact with the tensor cores.

17:21

And so if you look at this like plot of like the amount

17:24

of like matmul flops versus non-matmul flops,

17:28

you can really see like when NVIDIA realized

17:31

that like deep learning was a big deal.

17:33

Because like all, all of a sudden, you know,

17:35

you kind of had this like massive like 10x gap

17:37

and so there's like a log scale.

17:39

And you had this kinda like massive like 10x gap

17:42

between how fast matmuls were on the GPU

17:45

and how fast like literally anything else

17:46

you wanted to run on the GPU was.

17:51

And so the end result is, you know, previously we said

17:53

that like, you know, matmuls took like 90% of the time

17:55

and so if the, if like, you know, NVIDIA has fed up matmuls

17:59

by like 10x but then everything else like stayed

18:01

the same amount of speed,

18:03

then all of a sudden, you know, like you're spending

18:05

a lot more of your time doing like non-matmul operations.

18:10

And so as a result we've kind of gotten like ML compilers

18:15

I think largely due to this change.

18:17

And so one of the, I think the important details

18:18

about ML compilers like, you know,

18:20

in terms of like how they differ from the frameworks

18:22

that came before is that ML frameworks still keep

18:25

like the eager programming model and that like the code

18:28

that you write like logically the program model

18:30

exposed to users is that you're still just writing

18:32

Python code and executes like line by line.

18:35

The only difference now is that instead

18:36

of actually executing line by line,

18:38

we kind of captured into like a graph in some manner.

18:42

And so Torch compile I think actually kind of does this

18:44

in a pretty interesting way.

18:46

And that torch compile actually like intercepts

18:51

at like the Python by code interpreter level.

18:53

Where Python kind of exposes these APIs

18:55

where you can kind of insert

18:56

your like own frame interpreter.

18:58

And so this looks very much just like a traditional

19:00

like you know, Git for like any kind of other VM.

19:03

except this Git is kind of, you know,

19:05

only meant for like PyTorch programs.

19:10

And so if you kind of look at like, you know

19:11

how like things have evolved over time.

19:14

Originally you kind of had like frameworks like TensorFlow

19:17

or Caffe before that.

19:18

Where both the user like program model that users wrote

19:22

was like a graph builder type attraction.

19:25

But then the execution programming model

19:26

was also a graph execution type extraction.

19:29

And then like after that you kind of had PyTorch, you know,

19:32

one type like stuff.

19:34

Where the user program model now switched

19:36

to like an eager style execution, but the execution program

19:39

and the execution program model was also eager where like,

19:42

you know, each operator's executed one at a time.

19:45

But kind of now finally, you know,

19:47

modern ML frameworks like,

19:48

pretty much all ML frameworks nowadays

19:51

use like an imperative eager programming model.

19:54

But almost all ML frameworks now also have some way

19:57

to like capture this program model into a graph of some kind

20:00

so they can perform optimizations.

20:02

And so this is like, you know, JAX.git, Tonnegrad,

20:05

this is like MLX.

20:06

They kind of all have their like different approaches

20:08

for capturing the graph to optimize.

20:15

And so I think next I kinda wanna talk about, you know,

20:18

we've kind of discussed how we've gotten to ML compilers

20:21

in the first place.

20:23

And so I think next I wanna talk about like

20:24

what ML compilers are actually doing for you

20:27

and what kind of optimizations they're performing.

20:31

And so generally speaking, the way I think about

20:33

deep learning performance or like performance on GPUs

20:35

in general is that there's basically three things

20:38

you can be spending your time on.

20:40

The first one is compute, so this is time on our GPU

20:43

competing like actual floating point operations.

20:45

The next one is a memory which is, you know,

20:48

time spent transferring your tensors within a GPU.

20:51

So this is like, you know, across various memory subsystems

20:54

in your GPU.

20:56

And so finally like overhead,

20:57

which is like everything else like you know,

20:59

it's like time your GPU spending idle and so on.

21:03

And so first we're gonna talk about compute.

21:05

And so I think to a sum of approximation you can say

21:09

that all runtime on your GPU is either compute

21:12

or it's a shuffling data.

21:14

And that like, you know, data movement

21:16

is like not like a real operation, right?

21:20

it's like a no op from like the theoretical point of view.

21:23

All it's doing is it's moving data from one place

21:25

where it's convenient to another place

21:26

where it's convenient.

21:28

And so basically a floating point operation

21:29

is like the only real thing a GPU can do.

21:34

But you can actually, I think simplify this even more

21:36

and say that in reality actually nowadays like all runtime

21:39

is either like matmuls or essentially shuffling data.

21:43

And so this is like, because if you look at the actual like

21:46

flop chart on like an Edge 100 like GPU, you can see here

21:50

that like the FP32 flops is like

21:52

you only have 67 teraFLOPS of FB32 compute,

21:56

but you actually have like a 1,00 teraFLOPS

21:58

of a TF32 compute.

22:00

Which is basically like matrix multiplication compute.

22:03

And so what this essentially...

22:05

And sometimes like what this means

22:07

is that if you're not doing measurable (indistinct)

22:09

on your GPU, you're really only like getting like 7%

22:13

of like your peak FLOP utilization.

22:16

And so like, you know by like the metric

22:17

that I mentioned before like model FLOP utilization,

22:20

even if your GPU was fully occupied doing stuff

22:22

that wasn't a matmul you could only ever get like 7%

22:26

of FLOP utilization, which is like much lower than,

22:28

you know, our theoretical peak.

22:35

I did have a brief interlude about like,

22:36

I think an interesting case where like

22:38

these kind of abstractions do break down even more.

22:42

And so I do have like a kind of a fun question.

22:45

Which is like, do the matrix contents

22:47

affect your matrix multiplication performance?

22:49

And so I think, you know, if you kind of like,

22:53

you know, are familiar with like, you know,

22:54

general performance, there are like a lot of things that...

22:57

A lot of ways where like data can impact your performance,

23:00

but in this case matmuls actually avoid a lot of them.

23:02

So for example, they have identical memory access patterns

23:05

regardless of like what data is in your tensor,

23:08

there's like no control flow in the matmul.

23:11

And the GPU also don't have like the denormals.

23:13

So like that's like not a possibility as well.

23:16

So if you like, you know,

23:17

and in this case where like taking three tensors,

23:20

we're initializing them with like all zeros.

23:22

Like a tensor initialized from the Gaussian distribution

23:26

and then a tensor initialized

23:27

from like a uniform distribution,

23:28

which is like from zero to one.

23:30

And so funnily enough, if you benchmark this,

23:33

you actually find that there is a performance difference

23:35

depending on the actual data that is within your tensors.

23:41

And so there's this tweet from some of you guys might know,

23:46

a long time ago that really I think

23:48

like when I first saw this.

23:49

I actually was very much reminded of this tweet,

23:51

where, you know, I thought I knew how like a GPU worked,

23:55

but then I was like very confused about

23:57

what could possibly be causing

23:58

these like performance differences like, you know,

24:01

between the different data.

24:03

And so the actual cause here is something called

24:06

like leakage power or dynamic power where I think, you know,

24:09

most of you're probably familiar that you know,

24:12

like when a CPU or GPU is under load,

24:13

it uses more like power

24:16

and at some point it can like throttle, you know,

24:18

it's like using the maximum amount of power can use

24:20

or the max amount of like a heat it's allowed.

24:24

But the actual thing is that like, you know,

24:26

this power doesn't just like come from nowhere.

24:29

It actually largely comes from what's called

24:31

like dynamic or switching power.

24:33

And what this means is like every time a transistor

24:36

like on the GPU switches from like zero to one

24:39

or like, you know, high to low or low to high,

24:41

it like loses a little bit of this power.

24:43

And so the actual like power usage on your GPU

24:46

is kind of like a like sum across the total amount

24:49

of like switching that goes on in your GPU.

24:53

And so this is why like if you're multiplying

24:55

with all zeros, you can imagine

24:56

that like your GPU ends up not,

24:58

like a lot of transistors don't end up switching at all.

25:00

And so like it doesn't actually consume that much power

25:03

and it's much less a throttle.

25:05

And so if you actually like look at this,

25:08

you can actually get like very different performance

25:10

for like all these different kind of fund distributions.

25:12

Like whether it's like, you know, the normal distribution

25:16

or whether it's like a checkerboard type like pattern

25:18

or you know, it's like sparse or ternary.

25:20

And the reason why is just like,

25:22

it's like this kind of abstract thing

25:25

where these different patterns lead to like more or less

25:27

transistor flips and which leads to like more or less

25:31

of power throttling, which leads to like more or less

25:33

of performance.

25:35

And so I remember actually one time somebody had told me

25:38

a funny story where like they were like training

25:41

the machine learning model and benchmarking performance.

25:43

And then they like at some point their model would nad,

25:47

and then they'd be like wow my performance

25:48

just got way better (chuckles).

25:50

And so I like wrote an article about this

25:53

and they like messaged me and they were like, oh you know,

25:54

that was very illustrative

25:57

because you know, I was really confused

25:58

why my performance would be getting better.

26:00

But that's why, you know, like if all your tensors are NaN,

26:03

your transistors also don't need to do a lot of flipping

26:05

and so you know, you'll measure like a better perf.

26:09

So that's kind of compute.

26:11

And so the next thing that your GPU can be spending

26:14

a lot of your time on is like memory,

26:16

which is essentially a time spent transferring your tensors

26:19

within a GPU.

26:24

And so I think one thing to observe

26:26

from this kind of empirical plot

26:29

from a paper on like a data movement is that although like,

26:35

so this paper kind of breaks down the operations

26:38

on like I think a Bert type model.

26:41

Into what it calls like tensor contractions,

26:42

i.e memory locations.

26:45

And then like, you know, normalization operations

26:47

and element wise operations.

26:48

And so you can see that although major (indistinct)

26:51

are responsible for like 99.8% of your FLOPS,

26:54

they're only responsible for 61% of your runtime.

26:57

And so you know where like why are we spending like,

27:01

you know, 40% of our runtime doing like operations

27:03

like cumulatively only take 0.2% of our FLOPS?

27:09

The kind of a key thing here is what's called

27:12

like a memory bandwidth cost.

27:14

Where the way I typically think about this,

27:16

is that like even like, and so here I'm talking like

27:19

all of your data already lives on the GPU, like, you know,

27:21

it's like, you know, it's like, you know,

27:23

occupying your like GPU's VRAM.

27:28

But the thing is that like your GPU's VRAM

27:30

is not where it like the compute units are located.

27:33

And in order to actually do operations on a GPU,

27:35

you need to move your data from like the VRAM

27:39

to like where the compute units are located,

27:42

which is like your SRAM or like compute units.

27:45

And so usually I kind of think this of like

27:46

as like a factory where you have like a factory

27:49

with like not that much space.

27:50

And then you have a warehouse located like much further away

27:54

and so you have like a lot more space in your warehouse,

27:58

but now in order to do any operations

28:00

like on those like supplies,

28:03

you need to move them from your warehouse

28:05

to your factory and then back.

28:08

And so this cost of like moving data around

28:09

is called the memory bandwidth cost.

28:13

And so this is actually like responsible

28:15

for like a lot of like what your GPU

28:17

is spending its time doing.

28:19

Where if you imagine like let's say we do like, you know,

28:21

three operations like on a GPU,

28:23

so maybe you're doing like an add,

28:24

and then like a, you know, RELU,

28:26

and then like a sign operation.

28:30

Like you can imagine that what actually happens

28:31

when you do these operations.

28:32

Like first the GPU sends the data from like the memory

28:36

to the compute units and then you know,

28:38

turns it from a square to a triangle

28:40

and then it sends it all the way back.

28:42

And then you know, it sends the triangle

28:44

from like the memory units to the compute units again

28:46

where it does like another operation

28:47

and then sends it all the way back.

28:49

And then finally, you know, you guys get the idea,

28:51

it's like sending the circle from the memory units

28:52

to the compute units again

28:54

and then it's like sending into all the way back.

28:56

And so by default whenever you run any operations

28:59

like in PyTorch like let's say you ran like add

29:00

and then multiply and then cosine,

29:03

this is exactly what would be happening on our GPU.

29:06

And so you might think that this is a very like dumb thing

29:09

to do and you would be correct.

29:13

And that, you know, why are we like sending our triangle

29:15

back from like the factory to the warehouse

29:17

just to send the data from like the warehouse

29:19

back to the factory again.

29:21

And so a very like common operation for GPU compilers to do,

29:26

and I'd say what I'd actually call

29:28

like the most important optimization

29:30

in a deep learning compiler by far,

29:31

is called operator fusion.

29:33

And so what an operator fusion does is that instead of like,

29:36

you know, sending the data back and forth so much.

29:38

we like do a single GPU kernel where you send the data once

29:43

to the factory units, you do all of the operations

29:46

and then you send the data back.

29:49

Also, notably, this is also like an issue.

29:53

This optimization is not really something you can do

29:55

in eager mode, right?

29:56

Because in eager mode I was, you know, mentioning

29:58

like the executed model is very simple

30:00

where you like run our operation

30:02

and then it executes the operation.

30:04

And now if we want to do this optimization,

30:06

that program model is like no longer sufficient.

30:11

And so there's actually like a lot of different ways

30:13

to minimize memory movement.

30:15

Although at the end of the day,

30:16

like operator fusion is like the most important thing

30:18

you can do for like a ML compiler.

30:21

There's actually like a lot of decisions

30:23

that go into operator fusion that like kind of enable it

30:26

to be more or less effective.

30:28

One of the kind of examples here

30:30

is kind of these like re computation

30:31

versus reuse trade-offs.

30:33

If you guys are kind of familiar with maybe

30:34

like register allocation type settings,

30:36

you kind of often have a similar issue

30:39

where like if you have a register you can choose

30:41

to either like store it in global memory

30:45

and then load from it later,

30:46

or you can just choose to like recompute

30:48

that value from like values that are ready in the registers.

30:51

And so you kind of have a similar idea here

30:54

where you oftentimes have cases where by doing some amount

30:59

of re computation you can significantly reduce your memory.

31:03

You can significantly reduce your number of memory accesses.

31:06

And so in this way, like the recomputation

31:08

can not only like reduce your like peak memory usage,

31:11

it can also often like improve

31:13

your actual runtime performance.

31:17

And so I think one of the things

31:19

to mention actually about like why.

31:22

So this observation actually ends up being

31:23

like quite important for deep learning performance

31:25

like re computation versus reuse.

31:27

And I think the reason why is that like the shape

31:29

of machine learning programs,

31:31

actually I think it looks quite unusual

31:33

compared to like your typical program that you might have.

31:35

Where like it's generally like a kind of a bit of an axiom

31:39

in like programs that usually most intermediates

31:41

that you have are very short-lived.

31:43

So like, you know, your program generally consists

31:45

of a lot of very short-lived intermediates

31:47

that you know are created

31:48

and they're very shortly destroyed.

31:50

But in machine learning this is actually not the case.

31:53

Because in machine learning, like the typical like model

31:56

that you'll execute will first like run the model

31:59

forward like, you know, layer zero to layer one,

32:01

to layer two, to layer three, to layer four,

32:03

and then initially run what's called the backwards pass,

32:06

which will like run the layers in reverse.

32:09

So then it'll go from like layer four to layer three,

32:11

to layer two, to layer one and to layer zero.

32:13

And in between the forward pass

32:15

and the backwards pass you need to save

32:17

what are called like intermediates or activations.

32:20

And so these are like a lot of, like,

32:23

you have a lot of them.

32:25

And they're like often times like, you know,

32:27

largely responsible for like running into

32:29

like out of memory type errors.

32:31

And so I think this is actually

32:33

like kind of a pretty unusual like program structure

32:37

in machine learning that's caused like, you know,

32:39

back propagation and gradient dissent.

32:45

And so finally, you know, the last thing

32:46

you can be spending your time on is overhead.

32:49

Where you can imagine that,

32:50

you know, if a poor Gromit is not able

32:52

to put down the train tracks faster than the train

32:55

can like go on the train tracks,

32:57

then sometimes a train is gonna be just stuck

32:59

waiting for him to put down like the next train track.

33:02

And so here I have like a profile trace

33:05

where you can see that like the bottom line,

33:07

which is a GPU trace is largely idle

33:10

and it's mostly just idle waiting for the CPU

33:12

to like schedule the next operation.

33:15

And so there are a lot of like ways

33:16

to actually address this nowadays.

33:18

One of the most powerful is called CUDAgraphs,

33:20

which is like an NVIDIA provided API,

33:24

but you also have like other approaches like in a compiler

33:26

for example, like codegenning like a lower overhead wrapper

33:29

or something like this.

33:36

So you know, I've talked about like ML compilers like,

33:41

and you know like you know what they can do to your program

33:43

and how they can be useful.

33:44

But I think kind of like an interesting question

33:47

that you often see like, you know, I talked a lot about

33:49

how like, you know, we have like, you know,

33:51

super massive infra build out

33:53

and the programs are super simple.

33:55

And we've seen like a lot of consolidation

33:57

in terms of like what the architecture looks like.

33:59

And so I think like a reasonable question is like,

34:01

if you only have like one architecture

34:03

and you're spending like billions of dollars to train it,

34:05

why do you even need a compiler?

34:08

You know, like why can't you just like, you know,

34:09

assign some group of people to like optimize it by hand

34:13

instead of like a leveraging a compiler?

34:18

And I'm gonna say some like kind of or sorry,

34:22

and the other thing I'll say about this

34:24

actually is that like in practice a lot of times

34:27

people do not use compilers like for kind of this reason.

34:32

And so this section is gonna be talking a little bit about

34:34

like why I think that's a case.

34:36

And what are kind of some of the challenges

34:38

when it comes to like using compilers in this setting.

34:42

And yeah, so disclaimer, you know,

34:43

I do really like compilers.

34:45

I'm gonna say some kind of mean things about compilers

34:48

in a bit, but you know, as like to establish my credibility,

34:53

you know, I work on a team called Fighter's Compilers.

34:56

And so there are like, to be clear like a lot of reasons

34:58

why compilers can be very useful.

35:00

In particular this kinda like notion of leverage,

35:02

like being able to do the optimization once in the compiler

35:06

and then having everybody be able to like take advantage

35:08

of it.

35:09

And also, you know, compilers are also just like very fun

35:12

to work on.

35:14

That being said, yeah, I'm gonna introduce you.

35:17

You know, my new exciting library,

35:20

Horace's exciting library abbreviated at HEL.

35:23

And so it has a couple of cool features

35:25

that you might be interested in.

35:27

So the first feature it has is that it doesn't always work.

35:30

And you know, to address that,

35:33

it also has no documentation about

35:34

why or when it will work except by reading my library

35:37

as an implementation.

35:39

And in exchange for that, when you update the library,

35:42

it may totally change what code works

35:43

and what code doesn't work.

35:45

I.e. like no backwards compatibility or like guarantees

35:48

on your like, you know, on whether your code works.

35:53

And so are you interested in using my library?

35:57

I guess, you know, most people would probably say no.

36:00

And so I think one thing to note here

36:02

that like if work means like has a desired performance

36:05

and is applying the desired optimizations,

36:08

this is kind of largely describing

36:09

how compiler optimizations work.

36:11

And that compiled optimizations don't always work.

36:15

Often like there's no real documentation

36:17

on when a compiler optimization will trigger and you know,

36:21

when you update your compiler

36:23

it may completely change when it does

36:26

or does not apply these optimizations.

36:29

And so there's like very influential article

36:34

like about this compiler called ISPC.

36:36

And they have this note here called auto vectorization

36:39

is not a programming model.

36:41

Where they note here is that like, you know,

36:43

the problem with an auto vectorizer,

36:44

which is kind of like a compiler...

36:46

Or so the overall like framing of the article

36:48

is that he wrote this compiler called like ISPC,

36:51

which you can think of as like CUDA

36:52

for intel SIMD instructions.

36:56

And he kind of, you know, is constantly like, you know,

36:58

as part of the article, he is constantly trying to fight

37:00

against like the intel compiler team,

37:02

which is like where he works.

37:03

With the intel compiler team,

37:04

wanted to kind of leverage auto vectorization

37:07

to get vectorization done

37:08

instead of introducing like a new program model in ISPC.

37:12

And so he kind of, I think elucidates like what the problem

37:16

with the auto vectoriser.

37:17

Which is that the problem with the auto vectoriser

37:19

is that as long as vectors can fail

37:21

and it will then if you're programmer

37:22

that actually cares about what code the compiler

37:24

generates from your program,

37:25

you need to deeply understand the auto vectoriser.

37:30

Then when it fails to vectorize code,

37:31

you wanna be vectorized, you need

37:33

to either poke it in the right way

37:34

or change your program in the right way

37:36

so that it works for you again.

37:37

And so this is like a very horrible way to program.

37:40

And then, you know, if most of you are,

37:41

or if any of you here are like very like into using

37:44

SIMD instructions, you probably also do not trust

37:47

the auto vectorize at all and you're mostly just, you know,

37:49

writing intrinsics.

37:52

And so the, you know, with the proper program model,

37:55

ideally the user is able to like learn what does

37:58

and does not work without, you know, needing to be tied

38:01

to the implementation and then, you know,

38:03

one compiler implements it

38:04

and then the user can like learn to reliably rely

38:07

on this optimization without needing to understand

38:10

the compiler and only needing to understand

38:12

the programming model.

38:13

And so one I guess way you can phrase this

38:16

is that like a compiler optimization

38:17

that always works is just part of the programming model.

38:20

Like for example, when you're writing like, you know,

38:23

SIMD instructions, you know that as SIMD intrinsic

38:26

will always get mapped to SIMD instruction.

38:29

And so that's like part of your program model

38:30

and not really an optimization that the compiler is doing.

38:36

So I think, you know, to...

38:39

Like when I see like the people kind of complaining

38:42

about like Shoggoths and working with them,

38:45

I kind of am often reminded times of a compiler.

38:48

We can imagine that like a compiler is oftentimes

38:50

it's like large piece of code that you know

38:52

has been worked on by a lot of like very smart people

38:54

and oftentimes has a lot of like tricky details

38:57

and implementation details in it.

39:00

And so ideally when you're doing a compiler,

39:02

like only the program model ends up being exposed

39:04

to the user.

39:05

So like the actual compiler implementation

39:07

ends up being completely hidden.

39:08

And so the user only needs to deal

39:09

with like the nice program model,

39:11

but it's usually just like the language

39:12

that the compiler is compiling from.

39:21

Unfortunately, you know, when compilers fail

39:23

and like, you know, they don't apply the optimizations

39:24

that you want them to apply.

39:26

The kind of entire thing becomes exposed.

39:28

And so, you know, this kind of applies anytime

39:30

that you're like you're wrestling with a compiler

39:33

and you're trying to understand why the compiler

39:34

like did or did not like inline my code or things like this.

39:40

And so to give some examples of like cases

39:42

where like very kind of nuanced details here.

39:45

Can lead to compilers like having,

39:47

like it can lead to compilers struggling a lot.

39:50

One of them here is like numerics and machine learning.

39:52

Where numerics can be like a kind of a,

39:55

or like in general floating point,

39:56

like arithmetic is like a very cursed thing

39:59

to deal with generally speaking.

40:00

And it's just gotten even worse with the fact

40:05

that like everybody in the industry

40:06

keeps on pushing our data types lower

40:08

to lower and lower bits where like on the V100

40:10

they kind of introduce like 16 bit operations

40:13

is kind of the default.

40:15

And on H100 they introduced eight bit operations

40:18

and on the B100, you know, they're now pushing

40:19

for like four bit operation, like four bit floats.

40:22

And operations on four bit floating point numbers.

40:25

I think it's reasonable to question like

40:27

how is this even a floating point number at this point?

40:30

But this is kind of what we're typically dealing with.

40:35

And so as a result of like this kind of like low precision

40:38

and the fact that numerics end up being so subtle,

40:40

the algorithms have like very annoying numerical problems

40:42

to deal with.

40:43

And I think a good example for me

40:44

that was like very frustrating was this kind of a NaN

40:47

in like a FlashAttention implementation.

40:50

Where the underlying cause here is something called an FMA

40:53

or like a fuse multiply accumulate.

40:55

It can actually be like disastrously bad

40:57

when it comes to your numerics.

41:00

So for folks who are unfamiliar with FMA,

41:03

it's basically like it takes like A, B and C

41:05

and it does like a signal operation

41:07

that does A times B plus C.

41:09

And so one of the ways that FMA differs

41:12

from normal operations is that in addition to being faster,

41:16

it's also usually computed

41:17

in what people call like infinite precision internally.

41:20

And what that means is that like the result of your FMA

41:23

is like...

41:27

Like the closest possible representation

41:29

to like the true value of your FMA.

41:32

And so this is different from like if you just did this

41:34

operation separately.

41:35

Where typically after your like multiply operation,

41:38

you would have like a rounding term

41:40

where it becomes a bit different.

41:43

And so in this particular case we're computing exp

41:46

of like a_i times B minus a maximum scale.

41:50

Where max scale is like a singular scaler value.

41:55

That's a result of taking the maximum across a_i times b.

41:58

And so the main idea here is that exp

41:59

is like a very numerically unstable operation.

42:02

And so we want to make sure that

42:03

like we're not taking any very large exp

42:06

and so we're like, we're always,

42:08

we're subtracting out the maximum value

42:09

so that all the exponent, like the maximum size exponent,

42:13

that you can get here is a zero.

42:16

And you know, the compiler in its wisdom

42:20

helpfully rewrites this as like FMA of ai b - max scale.

42:25

Where you know it's smart here,

42:26

it actually realizes that you know,

42:28

it can rewrite the plus into like a plus and minus.

42:32

But then, you know, if you actually look at the numerics

42:34

that are going on here, max scale is like a rounding

42:38

of a_i times b.

42:39

and we actually end up computing this quantity

42:42

instead of like expon of a_i times b minus the rounded value

42:47

of a_i times b, which can be disastrously off.

42:50

And so the end result here is that we NaNed

42:54

with FMAs turned on and you don't NaN with FMAs turned off.

42:58

Even though FMAs are like theoretically

43:01

a precision improving optimization.

43:04

The underlying cause here to summarize

43:06

is basically that our numerical properties

43:09

relied on two separate computations

43:12

of the same value to be exactly identical.

43:15

And in this case with FMAs, like you can apply them

43:18

to only one branch but not the other branch.

43:21

And this leads to like NaNs that in this case.

43:27

Another thing that compilers oftentimes struggle with

43:29

are what are called algebraic rewrites.

43:32

So if you guys are familiar with this optimization

43:34

called FlashAttention.

43:36

Which kind of like fuses attention operators together,

43:39

it actually relies on this like kind of subtle rewrite,

43:44

which people often times call like online softmax.

43:47

Which is that typically softmax is implemented

43:49

with a couple of like global synchronizes I suppose.

43:54

But you can actually rewrite softmax in a way

43:56

that removes one of those global synchronizations.

44:01

But this requires like some amount of math.

44:03

It's not very hard math,

44:04

but like compilers are generally very bad at math

44:07

and so it's kind of difficult for them

44:08

to actually come up with this rewrite by themselves.

44:13

And so I think FlashAttention is like a very good example

44:16

of like a case where you need to think about

44:19

the program model you expose for FlashAttention.

44:22

Where you know, before we had this like fused attention

44:25

kernel came about, people just typically wrote attention

44:28

like this, you know.

44:28

You did like one matmul and then you did a softmax

44:31

and then you did another matmul.

44:32

And you know, this was like not super efficient,

44:34

but it was as efficient as you could do.

44:36

So people were happy with it.

44:38

But unfortunately with FlashAttention we now wanna fuse

44:41

like these three operations into a single kernel.

44:44

And so like the difficulty is like, you know

44:47

what API do we expose for FlashAttention?

44:53

And so one way you might like tackle this

44:55

is a pattern matching.

44:56

So you can like, you know, use a compiler,

45:00

you know, try to find the sequence of matmul

45:02

sort by matmul.

45:03

And then, you know, pattern match it into a like single

45:07

FlashAttention operator.

45:08

And so this is like a reasonable option.

45:12

But the issue here is that it becomes very frustrating

45:14

to debug, like for example, the user might like, you know,

45:17

change how they write softmax like they might reimplement

45:20

softmax with their like own softmax implementation.

45:23

Instead of using your like vendor provided

45:25

softmax implementation.

45:26

And then all of a sudden your pattern like no longer applies

45:29

and you know, they're sad because like the memory

45:32

suddenly blows up and their code is like 3x slower

45:35

and so this is like very frustrating for users to deal with.

45:40

And so instead, you know, what PyTorch did in this case,

45:43

is that we just said, okay, like these three operators

45:47

now need to be fused into one operator.

45:49

We're just gonna directly provide you

45:50

that like single operator as a like, you know,

45:53

a single API that you can call.

45:56

And so this is one way typically that you can deal with

45:57

with like program models is you can just kind of introduce

46:00

a slightly high level thing that like does one,

46:03

a very specific thing for the users,

46:05

but this is also still kind of frustrating.

46:09

And you might have some questions about

46:11

like whether this is actually good enough to do.

46:13

Because you know, when you consolidate multiple

46:15

more primitive APIs in a single more monolithic API,

46:19

you oftentimes run into issues where the monolithic API

46:21

is now no longer able to represent all the things

46:24

that users want to do.

46:27

And so with attention,

46:28

we do see that people kept on coming out

46:30

with like new attention variants like, you know,

46:33

Sliding Window Attention, Alibi, you know, PagedAttention,

46:36

Neighborhood Attention, all this kind of stuff.

46:38

Like, you know, you look on Twitter,

46:40

and like a new four like attention papers

46:43

come out every single week and as a result,

46:46

like they kind of fuse the tender kernels that people have,

46:49

I keep on accumulating like new cords like,

46:52

you know, FlashAttention, like this one's from like

46:54

the FlashAttention repo and now has like a dropout,

46:56

a softmax scale, a causal one, a window size, a soft cap,

47:00

alibi slopes and so on.

47:02

And you know, some of these were like added

47:03

in like the past couple months and you know,

47:05

they just like keep on adding them.

47:06

And once you've added like a core to an API

47:10

or like to a program model,

47:11

you can't remove the core anymore

47:13

because now that's like, you know,

47:14

breaking what users are rely upon.

47:18

And even worse, like even though users like we've...

47:20

People have been kind of aggressive about adding new cores,

47:23

it still doesn't end up being enough.

47:27

And you have like all sorts of users

47:28

who are like constantly complaining that, you know,

47:30

like nobody has implemented FlashAttention

47:32

for their, you know, pet attention variant.

47:35

Like there's no flash attention for prefixLM.

47:38

This bottom one was from like a blog post from somebody

47:41

who used to be at Google who was like complaining

47:43

about the ecosystem outside of Google.

47:46

And so basically the point of here

47:47

is that like a single monolithic operator

47:49

is not actually always sufficient.

47:52

And so we're in a situation where compilers,

47:54

like we don't really want to rely on a compiler

47:57

to generate from scratch,

47:58

but it's also painful to do modifications by hand.

48:01

And so this might kind of seem like a like no win situation.

48:04

Where you kind of must choose like one of them,

48:06

you must choose either like unpredictability

48:09

and you know, doing it as a compile optimization

48:11

or you must choose like a single monolithic API

48:15

that's like difficult for users to like modify.

48:21

But you know, this is kind of where

48:22

you kind of can be clever.

48:24

And come up with like a new program model

48:26

that wasn't either of the program models

48:28

that users had before.

48:29

So like one thing to notice here

48:31

is that this kind of custom kernel that's difficult

48:33

for a compiler to generate from scratch

48:35

can actually be decomposed into like a handwritten

48:38

slash like complicated FlashAttention kernel.

48:40

And a bunch of like trivial modifications from users

48:44

they can actually be like very mechanically generated.

48:49

And so here we have like an API

48:51

that we've introduced recently called Flex Attention

48:53

and I really like FlexAttention.

48:56

And I think we've seen like very positive reception

48:58

from the community, including from I think users

49:01

who like traditionally don't actually like

49:02

use compilers that much.

49:03

And so one of the reasons that FlexAttention is liked.

49:07

Even though like it relies on Torch compile to work

49:10

is that like it's guaranteed to always result

49:12

in a single fused detention kernel

49:13

and it's always guaranteed to have

49:15

like the same memory properties

49:16

as a fused attention kernel.

49:18

And so this means that like the user

49:19

now has a programming model

49:21

that they can rely upon where they can kind of program

49:24

against this programming model

49:25

and like try a bunch of different variants

49:27

that all like fit within this programming model.

49:30

And the user does not need to understand how the,

49:32

like actual API is implemented under the hood.

49:36

So to give you guys like a bit of a like peak

49:41

of how this API looks like.

49:42

If you guys are familiar with like sliding window attention

49:44

or causal attention, here you can kind of implement

49:47

causal attention by checking whether

49:50

like your query position is like greater

49:52

than equal to your KV position.

49:54

And then also checking whether the distance

49:55

between them is like less than your sliding window size.

49:58

And then you can just like, and these masks together

50:01

to get your like sliding window causal attention.

50:05

And so the way we kind of think about like,

50:07

and so this is I think a good example

50:09

of where compilers can provide a lot of value

50:12

for users even in these like super like handcrafted

50:15

scenarios where like previously prior

50:18

to this attention API, you kind of have this situation

50:22

where you had a bunch of these like,

50:24

you had this like big cube of like, you know, like masking

50:27

or like positional biases or whether it supported training

50:30

or whether it was supported inference.

50:32

And you know, a bunch of these dots were like filled in

50:34

with users who had manually implemented attention kernels.

50:37

But now with FlexAttention every single one of these

50:40

like dots are like now filled in.

50:42

And so it's now like consistently users can rely

50:45

upon fuse attention kernels

50:47

regardless of like the tension variant that they're using.

50:51

And it gives them an example of like some stuff

50:53

that users have been doing with a FlexAttention.

50:56

You have on the left, like a bit of a fun attention mask

50:58

from like some guy on Twitter

51:01

where basically in this case he had like a bunch

51:03

of like molecular graphs of like different sizes.

51:09

And he was able to convert this into like an intention mask

51:13

that FlexAttention supported.

51:15

And so this is like a very weird mask that,

51:18

you know, I definitely did not think about

51:19

when developing FlexAttention.

51:21

But when you've developed like I think a good program model

51:23

for users, users are oftentimes able to do things

51:25

that like you never considered doing

51:28

when you developed abstraction.

51:33

Another kind of analogy that I sometimes think about

51:36

when it comes to like,

51:37

you know, optimizations versus program models.

51:40

Is there's kind of like a famous,

51:42

I think quote from Grodicke, about like, you know,

51:44

tackling math problems where he said

51:47

that, you know, when you tackle like a math problem,

51:48

you know, you imagine a math problem as like a walnut.

51:50

And it is like, you know, when you're trying to open

51:52

the walnut, you can either tackle it

51:53

by like just hitting the walnut a bunch

51:55

and like opening it up or you can kind of, you know,

51:58

like soak the walnut in water

52:00

and you know, kind of, you know,

52:01

like establish like new like mental models

52:04

for how to think about the problem until eventually,

52:06

like the walnut just opens by itself

52:08

after being soaked in water.

52:10

And so I think about program models like very similarly.

52:14

Where like, you know, if you think about

52:16

like auto vectorization from like the Intel compiler folks

52:19

perspective, you know, he had this kind of fun anecdote

52:21

where like he said that the Intel people kept on asking

52:24

like, what happens when the CUDA compiler

52:26

fails to vectorize?

52:27

And he was like, you know, absolutely baffled

52:28

about, you know, what happened.

52:29

Like he felt like this was just a thing

52:32

he needed to understand to like, you know,

52:33

understand, you know, how the Intel people

52:35

needed to change their compiler to be more competitive.

52:38

But the kind of like misunderstanding here is that like,

52:41

it is almost a nonsensical question

52:43

to even ask like when does a CUDA compiler fail

52:45

to like make your code parallel?

52:48

Because if you've written your code in CUDA

52:49

and in the CUDA programming model,

52:51

it like must be parallel.

52:53

Like it is kind of like actionmatic

52:55

part of the program model.

52:57

Like there's no way you can write a CUDA program

52:59

that does not execute across multiple cores.

53:04

Like you can write CUDA programs

53:06

that are like incorrect or deadlock,

53:08

but they're still guaranteed to always run

53:10

across multiple cores.

53:11

And this is because the parallelism is inherent

53:14

to the program modeling in CUDA while the parallelism

53:16

is not inherent to the program model of auto vectorization.

53:25

I think, yeah, I think kind of ML compilers

53:31

and kind of, you know, how they apply to like, you know,

53:34

just generally optimizing GPUs I think like is already

53:37

quite difficult, but you have a lot more issues

53:40

when it comes to like making GPUs run at scale.

53:43

And so when you come to like, you know, getting GPUs

53:45

to run a scale, I do think there are a couple

53:47

of like kind of interesting differences

53:49

between distributed ML programs

53:51

versus like traditional distributed systems.

53:55

And so one of the differences

53:56

is that traditional distributed systems

53:57

are oftentimes are just trying to scale a QPS.

54:00

And so when you're like trying to scale QPS,

54:02

you have a lot of like very small interchangeable queries

54:05

and like the performance per hardware

54:07

is not like that critical.

54:09

And then oftentimes you're willing to like double

54:11

your amount of hardware use

54:12

in order to get like fall tolerance

54:14

or like, you know, like higher uptime or things like this.

54:17

On the other hand, in ML systems,

54:19

we just basically have like a single query.

54:22

Like, you know, you're just like training your ML job.

54:24

We have frequent global synchronizations

54:26

across all of our hardware and performance

54:30

is like extremely important,

54:31

you know, so much so that like there's no way

54:33

we could tolerate like a 2x loss in performance

54:36

for basically any reason.

54:40

And so one way to kind of think about

54:42

how you can paralyze programs.

54:44

And this is actually like not specific to ML,

54:46

this is kind of a general,

54:47

like a way to think about paralyzing programs.

54:50

Is that you can think that there's basically a,

54:52

like you have like this cube of computation to do.

54:56

Where one of the dimensions is like the batch dimension.

55:00

So it's like different tasks that you can perform.

55:03

You also have like the within task dimension,

55:05

so like the different operations within a single task.

55:08

And then finally you have like the time dimension,

55:10

which is what your GPU is doing at any given point in time.

55:16

And so you can think of data parallelism

55:17

as basically just splitting along like the batch dimension.

55:20

You can think about like task parallelism

55:22

as splitting across a task dimension.

55:24

And then you think about pipeline parallelism

55:25

as kind of like splitting across the time dimension.

55:31

Yeah, and so for data parallelism,

55:33

like the basic idea I think is like pretty simple,

55:36

which is just that we have like a bunch of different tasks.

55:38

We have a bunch of parallelism,

55:40

and so each GPU just handles a different task.

55:42

And so because of that we just have like...

55:45

We just put one task on each GPU and then run it.

55:48

And this seems like very nice and very trivial,

55:51

but the issue is that like we have a massive synchronization

55:53

of parameters after every step.

55:55

Because after every step that we do like the gradients

55:59

need to be like synchronized across all of your hardware.

56:03

And you can't just like avoid synchronizing

56:06

your parameters because you also have this kind of like,

56:11

mathy constraint from like the ML training side,

56:14

which is that you simply can't train with like too large

56:16

of a batch size or your model will not converge properly.

56:22

There's also kind of another detail here

56:24

where like you can't just like naively

56:25

replicate your parameters.

56:27

People oftentimes use like what's called

56:29

a Fully-Sharded Data Parallel.

56:33

The second kind of parallelism

56:34

that you have is like task parallelism,

56:36

which is commonly known as tensor parallelism.

56:38

And in this case you basically just have two GPUs

56:40

that split the same task.

56:42

And so the main problem that you run into here

56:43

that's kind of specific to ML is that like task parallelism

56:47

because you're like running the same task

56:49

does not oftentimes have like obvious ways

56:51

to overlap your communication with your computation.

56:55

But nevertheless, because the performance is so important,

56:58

we still really want to overlap the communication.

57:00

And so there ends up being like a lot

57:02

of kind of involved things that people do

57:04

to try to improve the overlap.

57:07

One of them here is called like Async Tensor Parallelism.

57:10

Where the general idea here

57:11

is they have like a communication op

57:13

followed by like a computation operation.

57:15

And so usually, you know,

57:16

while you're doing the communication,

57:17

the CPU can't do any computation

57:19

and so this is like, you know, wasted idle time on our GPU.

57:24

But the observation here about Async Tensor Parallelism

57:26

is you can actually kind of like mini pipeline

57:29

your like communication and your matmul.

57:32

Where even within like a single tensor operation,

57:35

there's still oftentimes like a batch dimension

57:37

that you can paralyze along.

57:39

And so by like doing this kinda like micro pipelining

57:41

of your like communication and computation,

57:44

you can actually still enable overlap

57:47

even though like we're doing tensor parallelism.

57:52

Finally, the kind of last kind of parallelism

57:54

that we have is a pipeline parallelism.

57:56

Where the general idea here is that you assign

57:59

like the first part of the task to the first GPU.

58:01

And the second part of the task to the second GPU,

58:04

this kind of differs from tensor parallelism

58:07

or like tensor task parallelism in that

58:09

in this case you do not run on the same task

58:12

with both GPUs at the same time.

58:15

And so in this way you can think of the task

58:17

is actually being sharded across like the time dimension.

58:20

And so there are a couple like additional wrinkles here

58:23

about pipeline parallelism that are kind of unique to ML

58:26

I think.

58:27

So one of them is that, you know,

58:28

once again the frequent massive synchronizations

58:30

prevent us from like filling up the pipeline.

58:33

But the second issue is that like back propagation,

58:35

like the, you know, the forwards and backwards pattern

58:37

that I mentioned before.

58:39

Also adds like a lot of very fun wrinkles

58:41

to your pipeline parallelism schedule.

58:43

And then in this case this is kind of like

58:44

a pipeline like you know, that people have designed

58:48

where the blue boxes are like the forwards pass,

58:50

the cyan boxes are like the backwards pass

58:53

and then the green boxes are like the backwards pass

58:56

split up even more.

58:58

And in this case like the main kind of nuance here

59:02

is that you can see like there's various points here

59:05

where your pipeline can actually choose

59:06

to either run a forwards micro batch

59:10

or like a backwards micro batch.

59:11

And so this kind of like choice is not something

59:14

you need to deal with,

59:15

in like a traditional pipeline parallelism setting.

59:17

And it ends up leading to like a lot of different

59:21

like optimizations that people do with pipeline parallelism.

59:25

And so kind of like putting this all together,

59:27

this is a diagram from the llama3 paper.

59:29

Where they showed like what's kind of being done

59:32

to train llama3.

59:33

And so in this case you can see that we're combining

59:35

like, you know, tensor parallelism, task parallelism,

59:37

with data parallelism with pipeline parallelism.

59:41

And there's also a fourth one thrown in here

59:43

called context parallelism

59:44

If you can think of that as just like another form

59:46

of a task or tensor parallelism.

59:51

Yeah, and so I think again here like compilers

59:55

I think have historically struggled quite a bit

59:57

when it comes to keeping up

59:58

with the distributed optimizations that people do.

60:00

And the main reason is that like compilers are dumb

60:02

and humans are smart.

60:04

And what I mean by this is that for any given set

60:06

of parallelism schemes or like any given set

60:09

of parallelism configs, it often is pretty feasible

60:12

to create like automatic model

60:14

for the compiler to determine how to automatically

60:16

like paralyze your program.

60:19

But the issue here is that actually that like most

60:21

of the innovation of parallelism

60:23

doesn't come from like searching

60:24

within your existing search base.

60:26

It usually comes from like expanding your search base

60:28

along a new dimension.

60:30

And so this is something that compilers

60:31

are not like super great at.

60:34

And it's been one of the struggles when people

60:36

try to like, you know, automate parallelism

60:39

with compilers or like general systems.

60:43

And yeah.

60:47

For example, like one of the digital like kind of wrinkles

60:49

that have kind of shown up like, you know, at certain scales

60:52

is a fault tolerance where you know,

60:55

when you're running like, you know on like, you know,

60:56

10 or 20,000 GPUs like where we're doing these like,

61:00

you know, globally full synchronizations

61:02

across all of our GPUs.

61:04

we have this issue where our GPUs can fail

61:06

for like all sorts of reasons.

61:08

Some of them might be like user related,

61:09

some of them might be like hardware related,

61:11

some of them might be networking related,

61:13

but basically like a single failure

61:15

takes down your entire run and this is like, you know,

61:17

quite problematic.

61:18

And so those tables from like a paper

61:20

that Meta published about like training llama3

61:23

and fault tolerance.

61:26

and like, you know, I think like one kind of example

61:29

of like how this ends up being like a interesting issue

61:32

is that when you're training on like 16,000 GPU hours,

61:35

you're only getting like a error once every 1.8 hours.

61:39

But you know, now if you like scale it up to 131,000 GPUs,

61:43

you now get a failure like every like 15 minutes or so.

61:46

And so like this turns something

61:48

that might not be that problematic

61:49

at 16,000 scale or smaller into something

61:52

that's like very problematic at a 131,000 GPU scale

61:56

or even higher where you can imagine that, you know,

61:58

now you have a situation where you might not be able

62:00

to make even a single step before a single GPU

62:03

in your entire fleet fails.

62:08

So to kind of conclude, you know,

62:09

I think ML has basically become the single most important

62:12

computational workload in the world over the last decade.

62:16

Maybe a bit biased,

62:18

but I think by like the amount of like FLOPS and investment,

62:22

I think you can make a strong case for it.

62:24

But the characteristics I think of the workload

62:27

both from a social POV, like, you know,

62:29

the massive infrastructure required

62:30

as well as the technical POV.

62:32

I think kind of often does mean

62:33

that a lot of the traditional approaches

62:35

to building systems are compilers don't directly apply.

62:38

Like you can't just like, you know, build a compiler

62:40

and then, you know, have people who just optimize a compiler

62:42

for like five years without thinking about, you know,

62:46

how the workloads are evolving on top of them

62:48

affect their compiler.

62:51

And so I think to me

62:52

like the kind of most interesting question

62:54

about building a systems here.

62:55

Isn't really about like building

62:57

the right optimizations for systems.

62:59

It's instead about like coming up

63:00

with the right programming models for expressing

63:03

like your systems and kind of coming up

63:05

with the right programming models to enable people

63:07

to kind of like do their own optimizations

63:10

and kind of like build the next, you know,

63:12

100,000 GPU model.

63:14

Thanks for coming to my talk. Hope you guys liked it.

63:19

(audience applauds)

Interactive Summary

In this talk, Horace He discusses the complexities of building machine learning infrastructure for large-scale model training. He highlights the evolution of programming models in ML, from declarative approaches to PyTorch's successful eager execution, and explains the critical role of compilers—specifically in operator fusion and managing distributed training at scale. The talk emphasizes that instead of just building better automated compilers, the real challenge lies in designing the right programming models that empower users to implement their own high-performance kernels, like in the case of FlexAttention.

Suggested questions

4 ready-made prompts