HomeVideos

Let's reproduce GPT-2 (124M)

Now Playing

Let's reproduce GPT-2 (124M)

Transcript

6049 segments

0:00

hi everyone so today we are going to be

0:02

continuing our Zero to Hero series and

0:04

in particular today we are going to

0:06

reproduce the gpt2 model the 124 million

0:09

version of it so when openi released

0:13

gpt2 this was 2019 and they released it

0:16

with this blog post on top of that they

0:19

released this paper and on top of that

0:21

they released this code on GitHub so

0:23

open a/

0:24

gpt2 now when we talk about reproducing

0:27

gpt2 we have to be careful because in

0:29

particular in this video we're going to

0:30

be reproducing the 124 million parameter

0:33

model so the thing to realize is that

0:35

there's always a miniseries when these

0:37

are releases are made so there are the

0:40

gpt2 miniseries made up of models at

0:42

different sizes and usually the biggest

0:45

model is called the

0:46

gpt2 but basically the reason we do that

0:49

is because you can put the model sizes

0:51

on the x-axis of plots like this and on

0:53

the Y AIS you put a lot of uh Downstream

0:55

metrics that you're interested in like

0:57

translation summarization question

0:58

answering and so on and you can chart

1:00

out these scaling laws so basically as

1:03

the model size increases you're getting

1:05

better and better at Downstream metrics

1:07

and so in particular for

1:09

gpt2 if we scroll down in paper there

1:12

are four models in the gpt2 miniseries

1:15

starting at 124 million all the way up

1:18

to 1558 million now the reason my

1:22

numbers the way I say them disagree with

1:23

this table is that this table is wrong

1:25

if you actually go to the uh gpt2 uh

1:29

GitHub repo they sort of say that um

1:32

there was an error in how they added up

1:33

the parameters but basically this is the

1:35

124 million parameter model Etc so the

1:38

124 million parameter had 12 layers in

1:40

the Transformer and it had 768 channels

1:44

in the Transformer 768 dimensions and

1:47

I'm going to be assuming some

1:48

familiarity with what these terms mean

1:50

because I covered all of this in my

1:51

previous video let's build gpt2 uh let's

1:54

build GPT from scratch so I covered that

1:56

in the previous video in this playlist

1:59

now if we do everything correctly and

2:01

everything works out well by the end of

2:03

this video we're going to see something

2:04

like this where we're looking at the

2:06

validation loss which basically um

2:10

measures how good we are at predicting

2:11

the next token in a sequence on some

2:13

validation data that the model has not

2:15

seen during training and we see that we

2:17

go from doing that task not very well

2:20

because we're initializing from scratch

2:22

all the way to doing that task quite

2:23

well um by the end of the training and

2:26

hopefully we're going to beat the gpt2

2:28

uh 124 M model

2:30

now previously when they were working on

2:32

this this is already 5 years ago so this

2:35

was probably a fairly complicated

2:36

optimization at the time and the gpus

2:38

and the compute was a lot smaller today

2:41

you can reproduce this model in roughly

2:42

an hour or probably less even and it

2:45

will cost you about 10 bucks if you want

2:47

to do this on the cloud uh Cloud Compu a

2:49

sort of computer that you can all rent

2:52

and if you pay $10 for that computer you

2:54

wait about an hour or less you can

2:56

actually achieve a model that is as good

2:58

as this model that open ey released and

3:02

uh one more thing to mention is unlike

3:04

many other models open ey did release

3:06

the weights for gpt2 so those weights

3:08

are all available in this repository but

3:11

the gpt2 paper is not always as good

3:14

with all of the details of training so

3:16

in addition to the gpt2 paper we're

3:18

going to be referencing the gpt3 paper

3:20

which is a lot more Concrete in a lot of

3:22

the hyp parameters and optimization

3:24

settings and so on um and it's not a

3:27

huge departure in the architecture from

3:29

the GPT 2 uh version of the model so

3:31

we're going to be referencing both gpt2

3:33

and gpt3 as we try to reproduce gpt2 124

3:36

M uh so let's go so the first thing I

3:40

would like to do is actually start at

3:41

the end or at the Target so in other

3:43

words let's load the GPT to 124 M model

3:47

as it was released by openi and maybe

3:48

take it for a spin let's sample some

3:50

tokens from it now the issue with that

3:52

is when you go into the code base of

3:54

gpt2 and you go into the source and you

3:56

click in on the model. pi you'll realize

3:58

that actually this is using tensorflow

4:01

so the original gpt2 code here was

4:03

written in tensor flow which is

4:06

um you know not let's just say not used

4:09

as much anymore um so we'd like to use

4:12

pytorch uh because it's a lot friendlier

4:14

easier and I just personally like a lot

4:16

more the problem with that is the

4:17

initial code is intenser flow we'd like

4:19

to use pytorch so instead uh to get the

4:21

target we're going to use the hugging

4:23

face Transformers um code which I like a

4:27

lot more so when you go into the

4:28

Transformers source Transformers models

4:30

gpt2 modeling gpt2 Pi you will see that

4:33

they have the gpt2 implementation of

4:35

that Transformer here in this

4:37

file um and it's like medium readable

4:42

but not fully readable um but what it

4:45

does is it did all the work of

4:47

converting all those weights uh from

4:50

tensor flow to pytorch Friendly and so

4:52

it's much easier to load and work with

4:54

so in particular we can look at the

4:56

gpt2 um model here and we can load it

4:59

using hugging face Transformers so

5:01

swinging over this is what that looks

5:03

like from Transformers import the DP GT2

5:07

LM head model and then from pre-train

5:12

gpt2 uh now one awkward thing about this

5:15

is that when you do gpt2 as the model

5:17

that we're loading this actually is the

5:19

124 million parameter model if you want

5:22

the actual the gpt2 the 1.5 billion then

5:25

you actually want to do- XL so this is

5:28

the 12 4 M our Target now what we're

5:32

doing is when we actually get this we're

5:33

initializing the uh pytorch NN module as

5:37

defined here in this

5:38

class from it I want to get just the

5:41

state dict which is just a raw tensors

5:44

so we just have um the tensors of that

5:46

file and by the way here this is a

5:49

jupyter notebook uh but this is jupyter

5:51

notebook running inside vs code uh so I

5:54

like to work with it all in a single

5:56

sort of interface so I like to use vs

5:57

code so this is the jupyter notebook

6:00

extension inside the es

6:03

code so when we get the state dick this

6:06

is just a dict so we can print the key

6:09

and the value which is the tensor and

6:11

let's just look at the shapes so these

6:13

are sort of

6:14

the uh different parameters inside the

6:17

gbt2 model and their shape so the W

6:22

weight for token

6:25

embedding is of size

6:27

50257 by 768 where this is coming from

6:31

is that we have

6:32

50257 tokens in the gpt2 vocabulary um

6:37

and the tokens by the way these are

6:39

exactly the tokens that we spoken about

6:40

in the previous video on my tokenization

6:43

Series so the previous videos just

6:45

before this I go into a ton of detail on

6:47

tokenization gpt2 tokenizer happens to

6:49

have this many tokens for each

6:53

token we have a 768 dimensional

6:56

embedding that is the distributed

6:58

representation that stands in for that

7:01

token so each token is a little string

7:03

piece and then the 768 numbers are the

7:06

vector that represents that

7:08

token and so this is just our lookup

7:10

table for tokens and then here we have

7:13

the lookup table for the positions so

7:16

because gbt2 has a maximum sequence

7:18

length of

7:19

1024 we have up to 1,24 positions that

7:23

each token can be attending to in the

7:25

past and every one of those positions in

7:28

gpd2 has a fixed Vector of

7:31

768 that is learned by

7:33

optimization um and so this is the

7:36

position embedding and the token

7:38

embedding um and then everything here is

7:41

just the other weights and biases and

7:43

everything else of this

7:45

Transformer so when you just take for

7:47

example the positional embeddings and

7:49

flatten it out and take just the 20

7:51

elements you can see that these are just

7:52

the parameters these are weights floats

7:56

just we can take and we can plot them so

7:59

these are the position embeddings and we

8:01

get something like this and you can see

8:03

that this has structure and it has

8:04

structure because what we what we have

8:07

here really is every Row in this

8:10

visualization is a different position a

8:12

fixed absolute position in um the range

8:16

from 0 to

8:17

1024 and each row here is the

8:19

representation of that position and so

8:23

it has structure because these

8:24

positional embeddings end up learning

8:26

these sinusoids and cosiness um that

8:29

sort of like represent each of these

8:31

positions and uh each row here stands in

8:35

for that position and is processed by

8:36

the Transformer to recover all the

8:38

relative positions and uh sort of

8:41

realize which token is where and um

8:44

attend to them depending on their

8:45

position not just their

8:47

content so when we actually just look

8:49

into an individual column inside these

8:53

and I just grabbed three random columns

8:55

you'll see that for example here we are

8:57

focusing on every every single um

9:01

Channel and we're looking

9:03

at what that channel is doing as a

9:07

function of uh position from one from Z

9:11

to

9:12

1223

9:14

really and we can see that some of these

9:15

channels basically like respond more or

9:17

less to different parts of the position

9:19

Spectrum so this green channel uh really

9:22

likes to fire for everything after 200

9:26

uh up to 800 but not less a lot less and

9:30

has a sharp drop off here near zero so

9:33

who knows what these embeddings are

9:34

doing and why they are the way they are

9:36

you can tell for example that because

9:37

they're a bit more Jagged and they're

9:38

kind of noisy you can tell that this

9:40

model was not fully trained and the more

9:43

trained this model was the more you

9:45

would expect to smooth this out and so

9:47

this is telling you that this is a

9:48

little bit of an undertrained model um

9:51

but in principle actually these curves

9:53

don't even have to be smooth this should

9:55

just be totally random noise and in fact

9:57

in the beginning of the optimization it

9:58

is complete random noise because this

10:01

position embedding table is initialized

10:03

completely at random so in the beginning

10:05

you have jaggedness and the fact that

10:07

you end up with something smooth is

10:09

already kind of impressive um that that

10:11

just falls out of the optimization

10:13

because in principle you shouldn't even

10:14

be able to get any single graph out of

10:16

this that makes sense but we actually

10:18

get something that looks a little bit

10:19

noisy but for the most part looks

10:21

sinusoidal like um in the original

10:24

Transformer um in the original

10:26

Transformer paper the attention is all

10:28

you need paper the positional embeddings

10:30

are actually initialized and fixed if I

10:32

remember correctly to sinusoids and

10:34

cosiness of uh different frequencies and

10:37

that's the positional coding and it's

10:38

fixed but in gpt2 these are just

10:40

parameters and they're trained from

10:41

scratch just like any other parameter uh

10:44

and that seems to work about as well and

10:46

so what they do is they kind of like

10:47

recover these sinusoidal like features

10:50

during the

10:52

optimization we can also look at any of

10:54

the other matrices here so here I took

10:57

the first layer of the

11:00

Transformer and looking at like one of

11:02

its weights and just the first block of

11:05

300 by 300 and you see some structure

11:08

but like again like who knows what any

11:10

of this is if you're into mechanistic

11:12

interpretability you might get a real

11:14

kick out of trying to figure out like

11:16

what is going on what is this structure

11:18

and what does this all mean but we're

11:19

not going to be doing that in this video

11:21

but we definitely see that there's some

11:22

interesting structure and that's kind of

11:24

cool what we're mostly interested in is

11:26

we've loaded the weights of this model

11:28

that was released by open Ai and now

11:30

using the hogging face Transformers we

11:33

can not just get all the raw weights but

11:35

we can also get the um what they call

11:39

Pipeline and sample from it so this is

11:42

the prefix hello I'm a language model

11:44

comma and then we're sampling uh 30

11:47

tokens and we getting five sequences and

11:50

I ran this and this is what it produced

11:53

um hell language

11:55

model but what I'm really doing is

11:57

making a human readable document there

11:59

are other languages but those are dot

12:01

dot dot so you can read through these if

12:03

you like but basically these are five

12:05

different completions of the same prefix

12:07

from this uh gbt

12:09

2124m now uh if I go here I took this

12:13

example from here and sadly even though

12:16

we are fixing the seed we are getting

12:18

different Generations from the snippet

12:21

than what they got so presumably the

12:24

code changed um but what we see though

12:28

at this stage that's important is that

12:29

we are getting coherent text so we've

12:32

loaded the model successfully we can

12:34

look at all its parameters and the keys

12:36

tell us where in the model these come

12:39

from and we want to actually write our

12:41

own gpt2 class so that we have full

12:43

understanding of what's happening there

12:44

we don't want to be working with

12:46

something like uh the modeling gpt2 Pi

12:49

because it's just too complicated we

12:50

want to write this from scratch

12:51

ourselves so we're going to be

12:53

implementing the GPT model here in

12:54

parallel and as our first task let's

12:57

load the gpt2 124 M into the class that

13:01

we're going to develop here from scratch

13:04

that's going to give us confidence that

13:06

we can load the open ey model and

13:08

therefore there's a setting of Weights

13:10

that exactly is the 124 model but then

13:13

of course what we're going to do is

13:14

we're going to initialize the model from

13:15

scratch instead and try try to train it

13:18

ourselves um on a bunch of documents

13:20

that we're going to get and we're going

13:22

to try to surpass that model so we're

13:24

going to get different weights and

13:25

everything's going to look different

13:27

hopefully better even um

13:29

but uh we're going to have a lot of

13:31

confidence that because we can load the

13:32

openi model we are in the same model

13:34

family and model class and we just have

13:36

to ReDiscover a good setting of the

13:37

weights uh but from scratch so let's now

13:41

write the gbt2 model and let's load the

13:43

weights and make sure that we can also

13:45

generate text that looks coherent okay

13:48

so let's now swing over to the attention

13:49

is all un need paper that started

13:51

everything and let's scroll over to the

13:53

model architecture the original

13:55

Transformer now remember that gpt2 is

13:57

slightly modified from the or or

13:59

Transformer in particular we do not have

14:02

uh the encoder gpt2 is a decoder only

14:05

Transformer as we call it so this entire

14:07

encoder here is missing in addition to

14:09

that this cross attention here that was

14:12

using that encoder is also missing so we

14:14

delete this entire part everything else

14:18

stays almost the same but there are some

14:20

differences that we're going to uh sort

14:21

of look at here so there are two main

14:26

differences when we go to the gb2 page

14:29

under 2.3 model we notice that first

14:32

there's a reshuffling of the layer Norms

14:34

so they change place and second an

14:38

additional layer normalization was added

14:40

here to the final self detention block

14:43

so basically all the layer Norms here

14:46

instead of being after the MLP or after

14:48

the attention they SN before it and an

14:50

additional layer Norm gets added here

14:52

right before the final

14:54

classifier so now let's Implement some

14:56

of the first sort of skeleton NN module

14:59

modules here in our GPT NN module and in

15:02

particular we're going to try to match

15:04

up this schema here that is used by

15:06

hugging face Transformers because that

15:08

will make it much easier to load these

15:10

weights from this state dict so we want

15:12

something that reflects uh this schema

15:15

here so here's what I came up with

15:19

um basically we see that the main

15:22

container here that has all the modules

15:24

is called Transformer so I'm reflecting

15:26

that with an NN module dict and this is

15:29

basically a module that allows you to

15:30

index into the subm modules using keys

15:34

just like a dictionary uh

15:36

strings within it we have the weights of

15:39

the token embeddings WT and that's an N

15:41

embedding and the weights of the

15:44

position embeddings which is also just

15:45

an N embedding and if you remember n

15:47

embedding is really just a fancy little

15:49

wrapper module around just a single um

15:53

single array of numbers a single uh

15:56

block of numbers just like this it's a

15:58

single tensor and an embedding is a

16:02

glorified um wrapper around a tensor

16:04

that allows you to access its elements

16:07

uh by indexing into the

16:08

rows now in addition to that we see here

16:11

that we have a h and then there's a this

16:14

is index using numbers instead of

16:16

indexed using strings so there's a h. 0

16:19

1 2 Etc all the way up till h. 11 and

16:23

that's because there are 12 layers here

16:26

in this Transformer so to reflect that

16:28

I'm creating also an H I think that

16:31

probably stands for hidden and instead

16:33

of a module dict this is a model list so

16:35

we can index it using integers exactly

16:37

as we see here 01 2 Etc and the modular

16:42

list has a n layer blocks and the blocks

16:46

are yet to be defined in a module in a

16:48

bit in addition to that following the

16:50

gpt2 paper we have we need an additional

16:53

final layer Norm that we're going to put

16:56

in there and then we have the final

16:58

classifier uh the language model head

17:01

which um projects from 768 the number of

17:05

embedding dimensions in this GPT all the

17:08

way to the vocab size which is

17:10

50257 and gpt2 uses no bias for this

17:13

final uh sort of projection so this is

17:16

the skeleton and you can see that it

17:19

reflects this so the wte is the token

17:22

embeddings here it's called output

17:24

embedding but it's really the token

17:26

embeddings the PE is the positional

17:29

codings uh those two pieces of

17:31

information as we saw previously are

17:32

going to add and then go into the

17:34

Transformer the H is the all the blocks

17:37

in Gray and the LNF is this new layer

17:40

that gets added here by the gpt2 model

17:43

and LM head is this linear part here so

17:47

that's the skeleton of the gpt2 we now

17:50

have to implement the block okay so

17:53

let's now recurse to the block itself so

17:55

we want to define the block um so I'll

17:59

start putting them here so the block I

18:02

like to write out like

18:04

this uh these are some of the

18:06

initializations and then this is the

18:07

actual forward pass of what this block

18:09

computes and notice here that there's a

18:12

change from the Transformer again that

18:14

is mentioned in the gpt2 paper so here

18:17

the layer normalizations are after the

18:20

application of attention or feed forward

18:22

in addition to that note that the

18:24

normalizations are inside the residual

18:26

stream you see how feed forward is

18:28

applied and this arrow goes through and

18:30

through the normalization so that means

18:33

that your residual pathway has

18:35

normalizations inside them and this is

18:37

not very good or desirable uh you

18:39

actually prefer to have a single uh

18:42

clean residual stream all the way from

18:44

supervision all the way down to the

18:45

inputs the tokens and this is very

18:48

desirable and nice because the gradients

18:51

that flow from the top if you remember

18:54

from your microad addition just

18:56

distributes gradients during the

18:58

backwards state to both of its branches

19:00

equally so addition is a branch in the

19:04

gradients and so that means that the

19:06

gradients from the top flows straight to

19:08

the inputs the tokens through the

19:10

residual Pathways unchanged but then in

19:13

addition to that the gradient also flows

19:14

through the blocks and the blocks you

19:17

know contribute their own contribution

19:18

over time and kick in and change the

19:20

optimization over time but basically

19:22

clean residual pathway is desirable from

19:25

an optimization perspective and then the

19:28

this is the pre-normalization version

19:30

where you see that RX first goes through

19:32

the layer normalization and then the

19:34

attention and then goes uh back out to

19:38

go to the L ration number two and the

19:40

multia perceptron sometimes also

19:43

referred to as a feed forward Network or

19:44

an FFN and then that goes into the

19:47

residual stream again and the one more

19:50

thing that is kind of interesting to

19:51

note is that recall that attention is a

19:53

communication operation it is where all

19:55

the tokens and there's 1,24 tokens lined

19:58

up in a sequence and this is where the

20:00

tokens communicate this is where they

20:02

exchange information so attention is a

20:06

um aggregation function it's a pooling

20:08

function it's a weighted sum function it

20:12

is a reduce operation whereas MLP this

20:16

uh MLP here happens at every single

20:18

token individually there's no

20:20

information being collected or exchanged

20:21

between the tokens so the attention is

20:24

the reduce and the MLP is the map and

20:27

what you end up with is that the

20:28

Transformer just ends up just being a

20:30

repeated application of map produce if

20:33

you want to think about it that way so

20:36

um this is where they communicate and

20:37

this is where they think individually

20:39

about the information that they gathered

20:41

and every one of these blocks uh

20:43

iteratively refines the um

20:46

representation is at the residual stream

20:48

so this is our block um slightly

20:51

modified from this picture Okay so let's

20:53

now move on to the MLP so the MLP block

20:57

uh I implemented as follows

20:59

it is relatively straightforward we

21:00

basically have two linear projections

21:02

here that are sandwiched in between the

21:05

G

21:06

nonlinearity so nn. G approximate is 10h

21:11

now when we swing on uh swing over to

21:13

the Pyro documentation this is n.g and

21:16

it has this format and it has two

21:18

versions the original version of G which

21:20

we'll step into into in a bit and the

21:22

approximate version of Galo which we can

21:24

request using

21:25

10 so as you can see just as a preview

21:28

here G is a basically like a reu except

21:32

there's no flat exactly Flat Tail here

21:35

at exactly zero but otherwise it looks

21:38

very much like a slightly smoother reu

21:41

it comes from this paper here Gan error

21:43

linear units and uh you can step through

21:46

this paper and there's some mathematical

21:48

calac reasoning that leads to an

21:50

interpretation that leads to the

21:51

specific formulation it has to do with

21:53

stochastic radial risers and the

21:56

expectation of a modification to

21:57

Adaptive dropout so you can read through

21:59

all of that if you'd like here and

22:01

there's a little bit of history as to

22:03

why there is an an approximate version

22:05

of G and that comes from this issue here

22:08

as far as I can tell and in this issue

22:11

Daniel Hendrix mentions that at the time

22:14

when they developed this nonlinearity

22:17

the Earth function which you need to

22:19

evaluate the exact G was very slow in

22:21

tensor flow so they ended up basically

22:23

developing this approximation and this

22:25

approximation that then ended up being

22:27

picked up by Bert and by GP P2 Etc but

22:30

today there's no real good reason to use

22:31

the approximate version you'd prefer to

22:33

just use the exact version um because I

22:36

my expectation is that there's no big

22:38

difference anymore and this is kind of

22:40

like a historical um kind of Quirk um

22:43

but we are trying to reproduce gpt2

22:45

exactly and gpt2 used the 10h

22:49

approximate version so we prefer to

22:51

stick with

22:52

that um now one other reason to actually

22:55

just intuitively use G instead of veru

22:57

is previously in the in videos in the

22:59

past we've spoken about the dead reu

23:02

neuron problem where in this tale of a

23:04

reu if it's exactly flat at zero any

23:07

activations that fall there will get

23:09

exactly zero gradient there's no change

23:11

there's no adaptation there's no

23:13

development of the network if any of

23:15

these activations end in this flat

23:17

region but the G always contributes a

23:20

local gradient and so there's always

23:22

going to be a change always going to be

23:23

an adaptation and sort of smoothing it

23:25

out ends up empirically working better

23:27

in practice as demonstrated in this

23:29

paper and also as demonstrated by it

23:31

being picked up by the bird paper gbt2

23:33

paper and so on so for that reason we

23:35

adopt this nonlinearity uh here in the

23:38

10 in the gbt2 reproduction now in more

23:41

modern networks also like llama 3 and so

23:43

on this nonlinearity also further

23:45

changes uh to swiglo and other variants

23:48

like that uh but for gpt2 they Ed this

23:50

approximate

23:51

G okay and finally we have the attention

23:54

operation so let me paste in my

23:57

attention

24:00

so I know this is a lot so I'm going to

24:02

go through this a bit quickly a bit

24:03

slowly but not too slowly because we

24:05

have covered this in the previous video

24:07

and I would just point you there um so

24:10

this is the attention operation now in

24:12

the previous video you will remember

24:13

this is not just attention this is um

24:16

multi-headed attention right and so in

24:19

the previous video we had this

24:20

multi-headed attention module and this

24:23

implementation made it obvious that

24:25

these heads are not actually that

24:26

complicated uh there's basically

24:28

in parallel inside every attention block

24:32

there's multiple heads and they're all

24:33

functioning in parallel and uh their

24:36

outputs are just being concatenated and

24:38

that becomes the output of the

24:40

multi-headed attention so the heads are

24:42

just kind of like parallel streams and

24:45

their outputs get

24:46

concatenated and so it was very simple

24:48

and made the head be kind of like U

24:51

fairly straightforward in terms of its

24:54

implementation what happens here is that

24:56

instead of having two separate modules

24:58

and indeed many more modules that get

24:59

concatenated all of that is just put

25:01

into a single uh self attention uh

25:04

module and instead I'm being very

25:07

careful and doing a bunch of transpose

25:10

split um tensor gymnastics to make this

25:13

very efficient in pych but fundamentally

25:15

and algorithmically nothing is different

25:17

from the implementation we saw

25:19

before um in this uh give

25:22

repository so to remind you very briefly

25:25

and I don't want to go in this uh into

25:27

this in too many in too much time but we

25:30

have these tokens lined up in a sequence

25:32

and there's 1,20 of them and then each

25:35

token at this stage of the attention

25:37

emits three vectors the query key and

25:40

the value and first what happens here um

25:44

is that the queries and the keys have to

25:46

multiply each other to get sort of the

25:49

attention um amount like how interesting

25:52

they find each other so they have to

25:54

interact multiplicatively so what we're

25:56

doing here is we're calculating the qkv

25:58

we splitting it and then there's a bunch

26:00

of gymnastics as I mentioned here and

26:03

the way this works is that we're

26:04

basically making the number of heads and

26:06

H into a batch Dimension and so it's a

26:10

batch Dimension just like B so that in

26:12

these operations that follow pytorch

26:14

treats B and NH as batches and it

26:18

applies all the operations on all of

26:20

them in parallel in both the batch and

26:22

the

26:23

heads and the operations that get

26:25

applied are number one the queries and

26:27

the keys intera to give us her attention

26:30

this is the autoaggressive mask that

26:32

makes sure that the tokens only attend

26:35

to tokens before them and never to

26:37

tokens in the

26:39

future the softmax here normalizes the

26:41

attention so it sums to one always and

26:45

then recall from the previous video that

26:47

doing the attention Matrix multiply with

26:48

the values is basically a way to do a

26:50

weighted sum of the values of the tokens

26:53

that we found interesting at every

26:55

single token and then the final

26:57

transpose conf VI and view is just

26:59

reassembling all of that again and this

27:02

actually performs the concatenation

27:04

operation so you can step through this

27:06

uh slowly if you'd like um but it is

27:08

equivalent mathematically to our

27:10

previous implementation is just more

27:12

efficient in P torch so that's why I

27:14

chose this implementation

27:16

instead now in addition to that I'm

27:18

being careful with how I name my

27:19

variables so for example cattin is the

27:22

same as seaten and so actually our keys

27:25

should basically exactly follow the

27:27

schema of the hugging face train

27:28

Transformers code and that will make it

27:29

very easy for us to now Port over all

27:32

the weights from exactly this sort of

27:34

naming conventions because all of our

27:36

variables are named the same thing but

27:39

um at this point we have finished the

27:41

gpt2 implementation and what that allows

27:44

us to do is we don't have to basically

27:46

use uh this file from hugging face which

27:48

is fairly long

27:50

um this

27:52

is uh 2,000 lines of code um instead we

27:57

just have a less than 100 lines of code

27:59

and this is the complete uh gpd2

28:01

implementation so at this stage we

28:02

should just be able to take over all the

28:04

weights set them and then do generation

28:07

so let's see what that looks like okay

28:09

so here I've also changed the GPT config

28:11

so that the numbers here the H

28:13

parameters agree with the gpt2 124 M

28:15

model so the maximum sequence length

28:17

which I call block size here is 124 the

28:21

number of tokens is 50250 257 which if

28:25

you watch my tokenizer video know that

28:27

this is 50,000 m merges BP merges 256

28:31

bite tokens the leaves of the BP tree

28:35

and one special end of text token that

28:36

delimits different documents and can

28:38

start generation as well and there are

28:41

12 layers there are 12 heads in the

28:43

attention and the dimension of the

28:45

Transformers was

28:46

768 so here's how we can now load the

28:49

parameters from hugging face to uh our

28:52

code here and initialize the GPT class

28:54

with those parameters so let me just

28:56

copy paste a bunch of code

28:59

here and I'm not going to go through

29:00

this code too slow too quickly too

29:03

slowly because um honestly it's not that

29:07

interesting it's not that exciting we're

29:08

just loading the weights so it's kind of

29:10

dry but as I mentioned there are four

29:12

models in this miniseries of gpt2 this

29:15

is some of the Jupiter code um code that

29:18

we had here on the right I'm just pting

29:20

it over these are the hyper parameters

29:22

of the gpt2 models uh we're creating the

29:24

config object and creating our own model

29:27

and then what's Happening Here is we're

29:28

creating the state dict both for our

29:30

model and for the hugging face

29:33

model um and then what we're doing here

29:36

is we're going over the hugging face

29:38

model keys and we're copying over those

29:42

tensors and in the process we are kind

29:45

of ignoring a few of the buffers they're

29:47

not parameters they're buffers so for

29:49

example attention dobias uh that's just

29:51

used for the autoaggressive mask and so

29:53

we are ignoring some of those masks and

29:56

uh that's it and then then one

29:58

additional kind of annoyance is that

30:00

this comes from the tensorflow repo and

30:02

I'm not sure how this is a little bit

30:04

annoying but some of the weights are

30:05

transposed from what pytorch would want

30:08

and so manually I hardcoded the weights

30:10

that should be transposed and then we

30:12

transpose them if that is so and then we

30:15

return this model so the from

30:18

pre-trained is a

30:20

Constructor or class method in Python

30:23

that Returns the GPT object if we just

30:26

give it the model type which in our case

30:28

is gpt2 the smallest model that we're

30:30

interested in so this is the code and

30:33

this is how you would use it and um we

30:35

can pop open the terminal here in vs

30:38

code and we can python train gbt2 pi and

30:44

fingers

30:46

crossed okay so we didn't crash and so

30:50

we can load the weights and the biases

30:52

and everything else into our Ann module

30:55

but now let's also get additional

30:57

confidence that this is working and

30:58

let's try to actually generate from this

31:00

model okay now before we can actually

31:01

generate from this model we have to be

31:03

able to forward it we didn't actually

31:04

write that code yet so here's the

31:06

forward

31:08

function so the input to the forward is

31:11

going to be our indices our tokens uh

31:13

token indices and they are always of

31:16

shape B BYT and so we have batch

31:19

dimension of B and then we have the time

31:22

dimension of up to T and the T can't be

31:26

more than the block size the block size

31:27

is is the maximum sequence length so B

31:30

BYT indices arranged is sort of like a

31:32

two-dimensional layout and remember that

31:35

basically every single row of this is of

31:37

size up to uh block size and this is T

31:41

tokens that are in a sequence and then

31:43

we have B independent sequences stacked

31:46

up in a batch so that this is

31:48

efficient now here we are forwarding the

31:51

position embeddings and the token

31:52

embeddings and this code should be very

31:54

recognizable from the previous lecture

31:56

so um we basically use uh a range which

31:59

is kind of like a version of range but

32:01

for pytorch uh and we're iterating from

32:04

Z to T and creating this uh positions uh

32:07

sort of uh indices

32:10

um and then we are making sure that

32:12

they're in the same device as idx

32:14

because we're not going to be training

32:15

on only CPU that's going to be too

32:16

inefficient we want to be training on

32:18

GPU and that's going to come in in a

32:20

bit uh then we have the position

32:22

embeddings and the token embeddings and

32:24

the addition operation of those two now

32:26

notice that the position embed are going

32:28

to be identical for every single row of

32:31

uh of input and so there's broadcasting

32:33

hidden inside this plus where we have to

32:36

create an additional Dimension here and

32:38

then these two add up because the same

32:40

position embeddings apply at every

32:41

single row of our example stacked up in

32:44

a batch then we forward the Transformer

32:46

blocks and finally the last layer norm

32:49

and the LM head so what comes out after

32:52

forward is the logits and if the input

32:55

was B BYT indices then at every single B

32:58

by T we will calculate the uh logits for

33:02

what token comes next in the sequence so

33:05

what is the token B t+1 the one on the

33:09

right of this token and B app size here

33:12

is the number of possible tokens and so

33:16

therefore this is the tensor that we're

33:17

going to obtain and these low jits are

33:19

just a softmax away from becoming

33:22

probabilities so this is the forward

33:25

pass of the network and now we can get

33:27

load and so we're going to be able to

33:29

generate from the model

33:30

imminently okay so now we're going to

33:32

try to set up the identical thing on the

33:35

left here that matches hug and face on

33:36

the right so here we've sampled from the

33:39

pipeline and we sampled five times up to

33:42

30 tokens with the prefix of hello I'm a

33:45

language model and these are the

33:46

completions that we achieved so we're

33:48

going to try to replicate that on the

33:49

left here so number turn sequences is

33:51

five max length is 30 so the first thing

33:53

we do of course is we initialize our

33:55

model then we put it into evaluation

33:57

mode now this is a good practice to put

33:59

the model into eval when you're not

34:01

going to be training it you're just

34:02

going to be using it and I don't

34:05

actually know if this is doing anything

34:07

right now for the following reason our

34:09

model up above here contains no modules

34:11

or layers that actually have a different

34:14

uh Behavior at training or evaluation

34:16

time so for example Dropout batch norm

34:18

and a bunch of other layers have this

34:20

kind of behavior but all of these layers

34:22

that we've used here should be identical

34:23

in both training and evaluation time um

34:27

so so potentially model that eval does

34:29

nothing but then I'm not actually sure

34:31

if this is the case and maybe pytorch

34:33

internals uh do some clever things

34:35

depending on the evaluation mode uh

34:36

inside here the next thing we're doing

34:39

here is we are moving the entire model

34:41

to Cuda so we're moving this all of the

34:44

tensors to GPU so I'm sshed here to a

34:47

cloud box and I have a bunch of gpus on

34:49

this box and here I'm moving the entire

34:53

model and all of its members and all of

34:54

its tensors and everything like that

34:56

everything gets shipped off to basically

34:59

a whole separate computer that is

35:01

sitting on the GPU and the GPU is

35:03

connected to the uh CPU and they can

35:05

communicate but it's basically a whole

35:06

separate computer with its own computer

35:08

architecture and it's really well

35:09

catered to parallel processing tasks

35:11

like those of running neural networks so

35:14

I'm doing this so that the model lives

35:16

on the GPU a whole separate computer and

35:19

it's just going to make our code a lot

35:20

more efficient because all of this stuff

35:22

runs a lot more efficiently on the

35:25

gpus so that's the model

35:29

itself now uh the next thing we want to

35:31

do is we want to start with this as the

35:34

prefix when we do the generation so

35:37

let's actually create those prefix

35:39

tokens so here's the code that I've

35:41

written we're going to import the tich

35:43

token library from open Ai and we're

35:45

going to get the gpt2 encoding so that's

35:48

the tokenizer for gpt2 and then we're

35:51

going to encode this string and get a

35:54

list of integers which are the tokens uh

35:57

now these integers here should actually

35:59

be fairly straightforward because we can

36:01

just copy paste this string and we can

36:04

sort of inspect what it is in tick

36:05

tokenizer so just pasting that in these

36:08

are the tokens that are going to come

36:09

out so this list of integers is what we

36:12

expect tokens to become and as you

36:15

recall if you saw my video of course all

36:17

the tokens they're just little string

36:19

chunks right so these are this is the

36:21

chunc of this string into gpt2

36:25

tokens so once we have those tokens it's

36:27

a list of integers we can create a torch

36:30

tensor out of it in this case it's eight

36:32

tokens and then we're going to replicate

36:34

these eight tokens for five times to get

36:36

five rows of eight tokens and that is

36:40

our initial um input X as I call it here

36:45

and it lives on the GPU as well so X now

36:48

is this idx that we can put into forward

36:52

to get our logits so that we know what

36:55

comes as the sixth token

36:58

uh sorry as the ninth token in every one

37:01

of these five rows okay and we are now

37:04

ready to generate so let me paste in one

37:05

more code block

37:07

here um so what's happening here in this

37:09

code block is we have this x which is of

37:12

size B BYT right so batch by time and

37:16

we're going to be in every iteration of

37:18

this loop we're going to be adding a

37:19

column of new indices into each one of

37:22

these rows right and so these are the

37:24

new indices and we're appending them to

37:27

the the sequence as we're sampling so

37:29

with each Loop iteration we get one more

37:31

column into X and all of the operations

37:34

happen in the context manager of torch.

37:36

nograd this is just telling pytorch that

37:38

we're not going to be calling that

37:39

backward on any of this so it doesn't

37:41

have to cach all the intermediate

37:43

tensors it's not going to have to

37:44

prepare in any way for a potential

37:46

backward later and this saves a lot of

37:48

space and also possibly uh some time so

37:52

we get our low jits we get the loow jits

37:54

at only the last location we throw away

37:57

all the other low jits uh we don't need

37:59

them we only care about the last columns

38:01

low jits so this is being wasteful uh

38:04

but uh this is just kind of like an

38:06

inefficient implementation of

38:08

sampling um so it's correct but

38:10

inefficient so we get the last column of

38:13

loow jits pass it through soft Max to

38:14

get our probabilities then here I'm

38:16

doing top case sampling of 50 and I'm

38:18

doing that because this is the hugging

38:20

face default so just looking at the

38:23

hugging face docks here of a pipeline um

38:26

there's a bunch of

38:28

quarks that go into hugging face and I

38:32

mean it's it's kind of a lot honestly

38:34

but I guess the important one that I

38:36

noticed is that they're using top K by

38:38

default which is 50 and what that does

38:41

is that uh so that's being used here as

38:43

well and what that does is basically we

38:45

want to take our probabilities and we

38:47

only want to keep the top 50

38:49

probabilities and anything that is lower

38:51

than the 50th probability uh we just

38:54

clamp to zero and renormalize and so

38:56

that way we are never sampling very rare

38:59

tokens uh the tokens we're going to be

39:01

sampling are always in the top 50 of

39:03

most likely tokens and this helps keep

39:05

the model kind of on track and it

39:07

doesn't blabber on and it doesn't get

39:08

lost and doesn't go off the rails as

39:10

easily uh and it kind of like um sticks

39:13

in the vicinity of likely tokens a lot

39:15

better so this is the way to do it in

39:17

pytorch and you can step through it if

39:18

you like I don't think it's super

39:20

insightful so I'll speed through it but

39:22

roughly speaking we get this new column

39:24

of of tokens we append them on x and

39:27

basically The Columns of X grow until

39:30

this y Loop gets tripped up and then

39:33

finally we have an entire X of size um 5

39:38

by 30 in this case in this example and

39:41

we can just basically print all those

39:43

individual rows so I'm getting all the

39:46

rows I'm getting all the tokens that

39:48

were sampled and I'm using the decode

39:50

function from Tik tokenizer to get back

39:52

the string which we can print and so

39:55

terminal new terminal

39:59

and let me python train

40:08

gpt2 okay so these are the generations

40:11

that we're getting hello I'm a language

40:13

model not a

40:15

program um new line new line Etc hello

40:19

I'm a language model and one of the main

40:21

things that bothers me when they create

40:22

languages is how easy it becomes to

40:23

create something that I me so this will

40:26

just like blabber on right in all these

40:27

cases now one thing you will notice is

40:29

that these Generations are not the

40:31

generations of hugging face here and I

40:35

can't find the discrepancy to be honest

40:37

and I didn't fully go through all these

40:39

options but probably there's something

40:40

else hiding in on addition to the top P

40:43

so I'm not able to match it up but just

40:45

for correctness um down here Below in

40:47

the juper notebook and using the hugging

40:49

face model so this is the hugging face

40:52

model here I was I replicated the code

40:56

and if I do this and I run that then I

40:59

am getting the same results so basically

41:03

the model internals are not wrong it's

41:05

just I'm not 100% sure what the pipeline

41:08

does in hugging face and that's why

41:09

we're not able to match them up but

41:11

otherwise the code is correct and we've

41:13

loaded all the um tensors correctly so

41:16

we're initializing the model correctly

41:18

and everything here works so long story

41:20

short uh We've Port it all the weights

41:22

we initialize the gpt2 this is the exact

41:25

opening gpt2 and it can generate

41:27

sequences and they look sensible and now

41:30

here of course we're initializing with

41:32

gbt2 model weights but now we want to

41:34

initialize from scratch from random

41:36

numbers and we want to actually train a

41:38

model that will give us sequences as

41:40

good as or better than these ones in

41:44

quality and so that's what we turn to

41:46

next so it turns out that using the

41:48

random model is actually fairly

41:49

straightforward because pytorch already

41:51

initializes our model randomly and by

41:53

default so when we create the GPT model

41:58

and the Constructor this is all um all

42:00

of these layers and modules have random

42:03

initializers that are there by default

42:05

so when these linear layers get created

42:07

and so on there's default Constructors

42:10

for example using the Javier

42:11

initialization that we saw in the past

42:13

uh to construct the weights of these

42:15

layers and so creating a random model

42:18

instead of a gpt2 model is actually

42:20

fairly straightforward and we would just

42:22

come here and instead we would create

42:24

model equals GPT and then we want to use

42:28

the default config GPT config and the

42:31

default config uses the 124 M parameters

42:33

so this is the random model

42:35

initialization and we can run

42:42

it and we should be able to get uh

42:46

results now the results here of course

42:48

are total garbage carbal and that's

42:50

because this is random model and so

42:51

we're just getting all these random

42:53

token string pieces chunked up totally

42:55

at random so that's what we have right

42:57

now uh now one more thing I wanted to

42:59

point out by the way is in case you do

43:01

not have Cuda available because you

43:03

don't have a GPU you can still follow

43:04

along with uh with what we're doing here

43:07

uh to some extent uh and probably not to

43:10

the very end because by the end we're

43:11

going to be using multiple gpus and

43:13

actually doing a serious training run uh

43:15

but for now you can actually follow

43:16

along decently okay uh so one thing that

43:19

I like to do in pytorch is I like to

43:20

autod detect the device that is

43:22

available to you so in particular you

43:24

could do that like this

43:28

so here we are trying to detect a device

43:30

to run on that has the highest compute

43:32

capability you can think about it that

43:33

way so by default we start with CPU

43:36

which of course is available everywhere

43:37

because every single computer will have

43:39

a CPU but then we can try to detect do

43:42

you have a GPU you so use a Cuda and

43:44

then if you don't have a Cuda uh do you

43:47

at least have MPS MPS is the back end

43:49

for Apple silicon so if you have a

43:51

Macbook that is fairly new you probably

43:53

have apple silicon on the inside and

43:55

then that has a GPU that is actually

43:57

fairly capable uh depending on which

43:59

MacBook you have and so you can use MPS

44:01

which will be potentially faster than

44:02

CPU and so we can print the device here

44:05

now once we have the device we can

44:07

actually use it in place of Puda so we

44:11

just swap it in and notice that here

44:14

when we call model on X if this x here

44:17

is on CPU instead of GPU then it will

44:21

work fine because here in the forward

44:23

which is where P to will come when we

44:26

create a pose we were careful to use the

44:28

device of idx to create this tensor as

44:31

well and so there won't be any mismatch

44:33

where one tensor is on CPU one is on GPU

44:36

and uh that you can't combine those but

44:38

here we are um carefully initializing on

44:41

the correct device as indicated by the

44:43

input to this model so this will autod

44:47

detect device for me this will be of

44:49

course

44:50

GPU so using device

44:54

Cuda uh but uh you can also run with um

44:58

as I mentioned another device and it's

45:00

not going to be too much slower so if I

45:01

override device here

45:03

oops if I override device equals

45:07

CPU

45:08

then we'll still print Cuda of course

45:11

but now we're actually using CPU one 2 3

45:16

4 5 6 okay about 6 seconds and actually

45:21

we're not using torch compile and stuff

45:22

like that which will speed up everything

45:24

a lot faster as well but you can follow

45:27

even on a CPU I think to a decent extent

45:30

um so that's note on that okay so I do

45:32

want to loop around eventually into what

45:35

it means to have different devices in

45:36

pytorch and what it is exactly that

45:38

pytorch does in the background for you

45:40

when you do something like module. 2

45:43

device or where you take a torch tensor

45:45

and do A2 device and what exactly

45:48

happens and how that works but for now

45:49

I'd like to get to training and I'd like

45:51

to start training the model and for now

45:53

let's just say the device makes code go

45:55

fast um and let's go into how we can

45:58

actually train the model so to train the

46:00

model we're going to need some data set

46:02

and for me the best debugging simplest

46:04

data set that I like to use is the tiny

46:06

Shakespeare data set um and it's

46:09

available at this URL so you can W get

46:11

it or you can just search tiny

46:12

Shakespeare data

46:13

set and so um I have in my file system

46:16

as just LS input.txt

46:18

so I already downloaded it and here I'm

46:22

reading the data set getting the first

46:23

1,000 characters and printing the first

46:26

100

46:27

now remember that gpt2 has uh roughly a

46:30

compression ratio the tokenizer has a

46:32

compression ratio of rly 3 to1 so th000

46:35

characters is roughly 300 tokens here uh

46:37

that will come out of this in the slice

46:39

that we're currently getting so this is

46:42

the first few uh

46:44

characters and uh if you want to get a

46:46

few more statistics on this we can do

46:48

work count on input.txt

46:50

so we can see that this is uh 40,000

46:53

lines about 200,000 words in this data

46:56

set and about 1 million bytes in this

46:59

file and knowing that this file is only

47:01

asky characters there's no crazy unic

47:03

code here as far as I know and so every

47:05

asky character is encoded with one bite

47:08

and so this is uh the same number

47:10

roughly a million characters inside this

47:12

data set so that's the data set size uh

47:15

by default very small and minimal data

47:17

set for debugging to get us off the

47:19

ground in order to tokenize this data

47:21

set we're going to get Tik token

47:23

encoding for gbt2 encode the data uh the

47:27

first um 1,000 characters and then I'm

47:30

only going to print the first 24 tokens

47:33

so these are the tokens as a list of

47:36

integers and if you can read gpt2 tokens

47:38

you will see that 198 here you'll

47:40

recognize that as the slashing character

47:42

so that is a new line and then here for

47:45

example we have two new lines so that's

47:46

198 twice here uh so this is just a

47:49

tokenization of the first 24 tokens so

47:52

what we want to do now is we want to

47:54

actually process these token sequences

47:56

and feed them into a Transformer and in

47:59

particular we want them we want to

48:01

rearrange these tokens into this idx

48:05

variable that we're going to be feeding

48:06

into the Transformer so we don't want a

48:08

single very long onedimensional sequence

48:10

we want an entire batch where each

48:12

sequence is up to uh is basically T

48:16

tokens and T cannot be larger than the

48:18

maximum sequence length and then we have

48:21

these t uh tlong uh sequences of tokens

48:25

and we have B independent examples of

48:27

sequences so how can we create a b BYT

48:30

tensor that we can feed into the forward

48:32

out of these onedimensional

48:34

sequences so here's my favorite way to

48:36

to achieve this uh so if we take torch

48:39

and then we create a tensor object out

48:41

of this list of integers and just the

48:42

first 24 tokens my favorite way to do

48:45

this is basically you do a do view of um

48:49

of uh for example 4x6 which multiply to

48:52

24 and so it's just a two-dimensional

48:54

rearrangement of these tokens and you'll

48:56

is that when you view this

48:57

onedimensional sequence as

48:58

two-dimensional 4x6 here the first six

49:03

uh tokens uh up to here end up being the

49:06

first row the next six tokens here end

49:09

up being the second row and so on and so

49:12

basically it's just going to stack up

49:14

this the um every six tokens in this

49:18

case as independent rows and it creates

49:20

a batch of tokens in this case and so

49:23

for example if we are token 25 in the

49:26

Transformer when we feed this in and

49:28

this becomes the idx this token is going

49:30

to see these three tokens and it's going

49:33

to try to predict that 198 comes

49:35

next so in this way we are able to

49:39

create this two-dimensional batch that's

49:41

that's quite nice now in terms of the

49:44

label that we're going to need for the

49:45

Target to calculate the loss function

49:47

how do we get that well we could write

49:49

some code inside the forward pass

49:51

because we know that the next uh token

49:53

in a sequence which is the label is just

49:55

to the right of us but you'll notice

49:57

that actually we for this token at the

49:59

very end 13 we don't actually have the

50:02

next correct token because we didn't

50:03

load it so uh we actually didn't get

50:07

enough information here so I'll show you

50:09

my favorite way of basically getting

50:11

these batches and I like to personally

50:14

have not just the input to the

50:15

Transformer which I like to call X but I

50:18

also like to create the labels uh tensor

50:21

which is of the exact same size as X but

50:24

contains the targets at every single

50:26

position

50:27

and so here's the way that I like to do

50:28

that I like to make sure that I fetch

50:30

plus one uh token because we need the

50:32

ground Truth for the very last token uh

50:35

for

50:36

13 and then when we're creating the

50:39

input we take everything up to the last

50:41

token not including and view it as 4x6

50:44

and when we're creating targets we do

50:47

the buffer but starting at index one not

50:50

index zero so we're skipping the first

50:52

element and we view it in the exact same

50:54

size and then when I print this

50:58

here's what happens where we see that

51:00

basically as an example for this token

51:02

25 its Target was 198 and that's now

51:05

just stored at the exact same position

51:07

in the Target tensor which is 198 and

51:10

also this last token 13 now has its

51:13

label which is 198 and that's just

51:16

because we loaded this plus one here so

51:19

basically this is the way I like to do

51:20

it you take long sequences you uh view

51:24

them in two- dimensional terms so that

51:26

you get batch of time and then we make

51:29

sure to load one additional token so we

51:31

basically load a buffer of tokens of B *

51:34

t+ one and then we sort of offset things

51:37

and view them and then we have two

51:39

tensors one of them is the input to the

51:41

Transformer and the other exactly is the

51:43

labels and so let's now reorganize this

51:46

code and um create a very simple data

51:50

loader object that tries to basically

51:52

load these tokens and um feed them to

51:55

the Transformer and calculate the loss

51:57

okay so I reshuffled the code here uh

51:59

accordingly so as you can see here I'm

52:01

temporarily overwriting U to run a CPU

52:05

and importing TI token and all of this

52:06

should look familiar we're loading a

52:08

th000 characters I'm setting BT to just

52:10

be 4 and 32 right now just because we're

52:13

debugging we just want to have a single

52:15

batch that's very small and all of this

52:17

should now look familiar and follows

52:19

what we did on the right and then here

52:21

we get the we create the model and get

52:24

the lits and so so here as you see I

52:28

already ran this only runs in a few

52:30

seconds but because we have a batch of

52:32

uh 4X 32 our lits are now of size 4X 32x

52:38

50257 so those are the lit for what

52:40

comes next at every position and now we

52:43

have the labels which are stored in y so

52:46

now is the time to calculate the loss

52:48

and then do the backward pass and then

52:49

the optimization so let's first

52:51

calculate the

52:52

loss okay so to calculate the loss we're

52:55

going to adjust the forward function of

52:56

this NN module in the model and in

52:59

particular we're not just going to be

53:00

returning logits but also we're going to

53:02

return the loss uh and we're going to

53:04

not just pass in the input in thees but

53:06

also the targets uh in y and now we will

53:12

print not Lo just. shape anymore we're

53:14

actually going to print the loss

53:14

function and then c. exit of zero so

53:17

that we skip some of the sampling logic

53:20

so now let's swing up to the forward

53:21

function which gets called there because

53:25

now we also have these optional

53:28

targets and when we get the targets we

53:30

can also calculate uh the loss and

53:32

remember that we want to basically

53:34

return uh log just loss and loss by

53:36

default is none

53:39

but

53:40

um let's put this here if uh targets is

53:45

not none then we want to calculate loss

53:49

and co-pilot is already getting excited

53:51

here and calculating the what looks to

53:53

be correct loss it is using the cross

53:55

entropy loss as is documented here uh so

54:00

this is a function in pytorch under the

54:03

functional now what is actually

54:05

happening here because it looks a little

54:06

bit scary uh basically uh the F that

54:09

cross entropy does not like

54:10

multi-dimensional inputs it can't take a

54:12

b BYT by vocap size so what's happening

54:15

here is that we are flattening out this

54:17

three-dimensional tensor into just two

54:19

Dimensions the First Dimension is going

54:21

to be calculated automatically and it's

54:23

going to be B * T and then the last

54:26

Dimension is vocap size so basically

54:28

this is uh flattening out this

54:30

three-dimensional tensor of logits to

54:32

just be two- dimensional B * T all

54:35

individual examples and vocap size on uh

54:39

in terms of the length of each row and

54:41

then it's also flattening out the

54:42

targets which are also two- dimensional

54:44

at this stage but we're going to just

54:46

flatten them out so they're just a

54:48

single tensor of B * T and this can then

54:51

pass into cross entropy to calculate a

54:52

loss which we return so this should

54:55

basically at this point run because this

54:57

is not too complicated

54:59

so let's run it and let's see if we

55:03

should be printing the

55:09

loss and here we see that we printed 11

55:12

uh roughly and so

55:16

um and notice that this is the tensor of

55:18

a single element which is this number 11

55:21

now we also want to be able to calculate

55:23

a reasonable uh kind of starting point

55:25

for a random rationalized Network so we

55:27

covered this in previous videos but our

55:29

vocabulary size is

55:31

50257 at initialization of the network

55:34

you would hope that um every vocab

55:37

element is getting roughly a uniform

55:40

probability uh so that we're not

55:42

favoring at initialization any token way

55:45

too much we're not confidently wrong at

55:47

initialization so what we're hoping is

55:49

that the probability of any arbitrary

55:51

token is roughly 1 over 50,2 57 and now

55:55

we can sanity check the loss because

55:57

remember that the cross entropy loss is

55:59

just basically the negative um log

56:01

likelihood so if we now take this

56:04

probability and we take it through the

56:06

natural logarithm and then we do the

56:08

negative that is the loss we expect at

56:11

initialization and we covered this in

56:13

previous videos so I would expect

56:15

something around 10.82 and we're seeing

56:17

something around 11 so it's not way off

56:20

this is roughly the probability I expect

56:21

at initialization so that tells me that

56:24

the at initialization or probability

56:26

distribtion is roughly diffused it's a

56:27

good starting point and we can now uh

56:30

perform the optimization and tell the

56:32

network which elements you know should

56:34

follow correctly in what order so at

56:37

this point we can do a l step backward

56:39

calculate the gradients and do an

56:40

optimization so let's get to that okay

56:43

so let's do the optimization now um so

56:46

here we

56:47

have the loss is this is how we get the

56:51

loss but now basically we want a load

56:53

for Loop here so 4 I in range let's do

56:55

50 steps or something like that uh let's

56:58

create an Optimizer object in

57:00

pytorch um and so here we are using the

57:04

atom um Optimizer which is an

57:07

alternative to the stochastic radian

57:08

descent Optimizer SGD that we were using

57:11

so SGD is a lot simpler atom is a bit

57:13

more involved and I actually

57:14

specifically like the atom W variation

57:17

because in my opinion it kind of just

57:19

like fixes a bug um so adom w is a bug

57:22

fix of atom is what I would say when we

57:25

go to the documentation for atom

57:27

W oh my

57:29

gosh we see um that it takes a bunch of

57:32

hyper parameters and it's a little bit

57:34

more complicated than the SGD we were

57:35

looking at before uh because in addition

57:37

to basically updating the parameters

57:39

with the gradient uh scaled by the

57:41

Learning rate it keeps these buffers

57:43

around and it keeps two buffers the m

57:46

and the V which it calls the first and

57:48

the second moment so something that

57:49

looks a bit like momentum and something

57:51

that looks a bit like RMS prop if you're

57:53

familiar with it but you don't have to

57:55

be it's just kind of a normalization

57:57

that happens on each gradient element

57:59

individually and speeds up the

58:00

optimization especially for language

58:02

models but I'm not going to go into the

58:04

detail right here we're going to treat

58:06

it as a bit of a black box and it just

58:08

optimizes um the objective faster than

58:12

SGD which is what we've seen in the

58:13

previous lectures so let's use it as a

58:15

black box in our case uh create the

58:18

optimizer object and

58:21

then go through the optimization

58:28

the first thing to always make sure the

58:30

co-pilot did not forget to zero the

58:32

gradients so um always remember that you

58:35

have to start with a zero gradient then

58:38

when you get your loss and you do a DOT

58:39

backward dot backward adds to gradients

58:42

so it deposits gradients it it always

58:44

does a plus equals on whatever the

58:46

gradients are which is why you must set

58:48

them to zero so this accumulates the

58:50

gradient from this loss and then we call

58:52

the step function on the optimizer to um

58:56

update the parameters and to um decrease

59:00

the

59:00

loss and then we print a step and the

59:03

loss do item is used here because loss

59:06

is a tensor with a single element do

59:08

item will actually uh convert that to a

59:11

single float and this float will live

59:13

not will will live on the CPU so this

59:16

gets to some of the internals again of

59:17

the devices but loss is a is a tensor

59:20

with a single element and it lifts on

59:22

GPU for me because I'm using gpus when

59:25

you call item P torch behind the scenes

59:28

will take that one-dimensional tensor

59:30

ship it back to the CPU uh memory and

59:32

convert it into a float that we can just

59:35

print so this is the optimization and

59:38

this should probably just

59:42

work let's see what

59:45

happens actually sorry let me instead of

59:47

using CPU override let me delete that so

59:50

this is a bit faster for me and it runs

59:52

on Cuda

59:58

oh expected all tensors to be on the

60:00

same device but found at least two

60:02

devices Cuda zero and CPU so Cuda zero

60:06

is the zeroth GPU because I actually

60:07

have eight gpus on this box uh so the

60:10

zeroth GPU in my box and CPU and model

60:14

we have moved to device but when I was

60:17

writing this code I actually introduced

60:18

a bug because buff we never moved to

60:21

device and you have to be careful

60:23

because you can't just do buff dot two

60:25

of

60:26

device um it's not stateful it doesn't

60:30

convert it to be a device it instead uh

60:33

returns pointer to a new memory which is

60:35

on the device so you see how we can just

60:37

do model that two a device that does not

60:39

apply to tensors you have to do buff

60:42

equals

60:44

um b.2 device and then this should work

60:49

okay so what do we expect to see we

60:52

expect to see a reasonable loss in the

60:53

beginning and then we continue to

60:55

optimize just the single batch and so we

60:57

want to see that we can overfit this

60:58

single batch we can we can crush this

61:01

little batch and we can perfectly

61:02

predict the indices on just this little

61:04

batch and indeed that is roughly what

61:06

we're seeing here

61:08

so um we started off at roughly 10.82 11

61:12

in this case and then as we continue

61:14

optimizing on this single batch without

61:16

loading new examples we are making sure

61:17

that we can overfit a single batch and

61:20

we are getting to very very low loss so

61:21

the Transformer is memorizing this

61:24

single individual batch and one more

61:26

thing I didn't mention is uh the

61:28

learning rate here is 3 E4 which is a

61:30

pretty good default for most uh

61:33

optimizations that you want to run at a

61:35

very early debugging stage so this is

61:38

our simple inter Loop and uh we are

61:41

overfitting a single batch and this

61:42

looks good so now what uh what comes

61:45

next is we don't just want to overfit a

61:46

single batch we actually want to do an

61:48

optimization so we actually need to

61:50

iterate these XY batches and create a

61:52

little data loader uh that makes sure

61:54

that we're always getting a fresh batch

61:56

and that we're actually optimizing a

61:57

reasonable objective so let's do that

61:59

next okay so this is what I came up with

62:01

and I wrote a little data loader

62:03

light um so what this data loader does

62:06

is we're importing the token up here

62:08

we're reading the entire text file from

62:10

this single input.txt

62:12

tokenizing it and then we're just

62:14

printing the number of tokens in total

62:17

and the number of batches in a single

62:19

Epoch of iterating over this data set so

62:22

how many unique batches do we output

62:24

before we loop back around the beginning

62:26

of the document and start reading it

62:28

again so we start off at position zero

62:31

and then we simply walk the document in

62:33

batches of B * T so we take chunks of B

62:36

* T and then always Advance by B * T and

62:40

um it's important to note that we're

62:42

always advancing our position by exactly

62:44

B * T but when we're fetching the tokens

62:47

we're actually fetching from current

62:49

position to B * t + 1 and we need that

62:52

plus one because remember uh we need the

62:55

target token

62:56

um for the last token in the current

62:58

batch and so that way we can do um the

63:02

XY exactly as we did it before and if we

63:07

are to um run out of data we'll just

63:09

loop back around to zero so this is one

63:12

way to write a very very simple data

63:13

loader um that simply just goes through

63:16

the file in chunks and is good enough

63:19

for us uh for current purposes and we're

63:21

going to complexify it later and now

63:24

we'd like to come back around here and

63:26

we'd like to actually use our data

63:27

loader so the import Tik token has moved

63:29

up and actually all of this is now

63:32

useless so instead we just want a train

63:35

loader for the training data and we want

63:38

to use the same hyper parameters for

63:39

four so B size was four and time was

63:43

32 and then here we need to get the XY

63:47

for the current batch so let's see if

63:49

copal gets it because this is simple

63:51

enough uh so we call the next batch and

63:53

then we um make sure that we have to

63:57

move our tensors from CPU to the device

64:02

so here when I converted the tokens

64:05

notice that I didn't actually move these

64:06

tokens to the GPU I left them on CPU

64:10

which is the default um and that's just

64:12

because I'm trying not to waste too much

64:14

memory on the GPU in this case this is a

64:16

tiny data set and it would fit uh but

64:19

it's fine to just uh ship it to GPU

64:21

right now for for our purposes right now

64:24

so we get the next batch we keep the

64:26

data loader simple CPU class and then

64:29

here we actually ship it to the GPU and

64:31

do all the computation and uh let's see

64:34

if this runs so python train gbt2 pi and

64:39

what do we expect to see before this

64:41

actually happens what we expect to see

64:43

is now we're actually getting the next

64:44

batch so we expect to not overfit a

64:47

single batch and so I expect our loss to

64:50

come down but not too much and that's

64:54

because I still expect it to come down

64:55

because in the

64:57

50257 tokens many of those tokens never

65:00

occur in our data set so there are some

65:02

very easy gains to be made here in the

65:04

optimization by for example taking the

65:06

biases of all the loits that never occur

65:08

and driving them to negative infinity

65:11

and that would basically just it's just

65:12

that all of these crazy unic codes or

65:14

different languages those tokens never

65:16

occur so their probability should be

65:17

very low and so the gains that we should

65:19

be seeing are along the lines of

65:22

basically deleting the usage of tokens

65:24

that never occur that's probably most of

65:26

the loss gain that we're going to see at

65:28

this scale right now uh but we shouldn't

65:30

come to a zero uh because um we are only

65:35

doing 50 iterations and I don't think

65:37

that's enough to do an eoch right now so

65:39

let's see what we

65:40

got we um we have 338,000

65:44

tokens which makes sense with our 3:1

65:47

compression ratio because there are 1

65:48

million uh characters so one Epoch with

65:52

the current setting of B and T will take

65:55

2, 600 batches and we're only doing 50

65:58

batches of optimization in

66:01

here so we start off in a familiar

66:03

territory as expected and then we seem

66:05

to come down to about 6.6 so basically

66:09

things seem to be working okay right now

66:11

with respect to our expectations so

66:13

that's good okay next I want to actually

66:16

fix a bug that we have in our code um

66:18

it's not a major bug but it is a bug

66:20

with respect to how gpt2 training uh

66:22

should

66:24

happen um

66:26

so the buck is the following we were not

66:28

being careful enough when we were

66:29

loading the weights from hugging face

66:31

and we actually missed a little detail

66:33

so if we come

66:35

here notice that um the shape of these

66:38

two tensors is the same so this one here

66:42

is the token embedding at the bottom of

66:44

the

66:45

Transformer right so and this one here

66:48

is the language modeling head at the top

66:50

of the

66:51

Transformer and both of these are

66:53

basically two-dimensional tensors and

66:55

they shape is identical so here the

66:59

first one is the output embedding the

67:00

token embedding and the second one is

67:02

this linear layer at the very top the

67:04

classifier layer both of them are of

67:07

shape

67:08

50257 X

67:09

768 um this one here is giving us our

67:13

token embeddings at the bottom and this

67:16

one here is taking the 768 channels of

67:18

the Transformer and trying to upscale

67:21

that to 50, 257 to get the Lis for the

67:24

next token so they're both the same

67:27

shape but more than that actually if you

67:29

look at um comparing their elements um

67:33

in pytorch this is an element wise

67:35

equality so then we use do all and we

67:37

see that every single element is

67:39

identical and more than that we see that

67:42

if we actually look at the data pointer

67:44

uh this is what this is a way in pytorch

67:47

to get the actual pointer to the uh data

67:49

and the storage we see that actually the

67:51

pointer is identical so not only are

67:53

these two separate tensors that happen

67:55

to have the same shape and elements

67:57

they're actually pointing to the

67:58

identical tensor so what's happening

68:02

here is that this is a common weight

68:03

tying scheme uh that actually comes from

68:06

the original

68:08

um from the original attention is all

68:10

you need paper and actually even the

68:12

reference before it so if we come

68:16

here

68:19

um eddings and softmax in the attention

68:22

is all you need paper they mentioned

68:24

that in our model we shared the same

68:26

weight Matrix between the two embedding

68:28

layers and the pre softmax linear

68:30

transformation similar to 30 um so this

68:34

is an awkward way to phrase that these

68:36

two are shared and they're tied and

68:38

they're the same Matrix and the 30

68:40

reference is this

68:42

paper um so this came out in

68:45

2017 and you can read the full paper but

68:47

basically it argues for this weight

68:49

tying scheme and I think intuitively the

68:53

idea for why you might want to do this

68:54

comes from from this paragraph here and

68:58

basically you you can observe

69:01

that um you actually want these two

69:04

matrices to behave similar in the

69:07

following sense if two tokens are very

69:10

similar semantically like maybe one of

69:12

them is all lowercase and the other one

69:14

is all uppercase or it's the same token

69:16

in a different language or something

69:17

like that if you have similarity between

69:19

two tokens presumably you would expect

69:21

that they are uh nearby in the token

69:23

embedding space but in the exact same

69:26

way you'd expect that if you have two

69:27

tokens that are similar semantically

69:30

you'd expect them to get the same

69:32

probabilities at the output of a

69:33

transformer because they are

69:35

semantically similar and so both

69:39

positions in the Transformer at the very

69:41

bottom and at the top have this property

69:43

that similar tokens should have similar

69:46

embeddings or similar weights and so

69:49

this is what motivates their exploration

69:51

here and they they kind of you know I

69:53

don't want to go through the entire

69:54

paper and and uh you can go through it

69:57

but this is what they observe they also

69:59

observe that if you look at the output

70:00

embeddings they also behave like word

70:02

embeddings um if you um if you just kind

70:06

of try to use those weights as word

70:08

embeddings um so they kind of observe

70:10

this similarity they try to tie them and

70:13

they observe that they can get much

70:14

better performance in that way and so

70:17

this was adopted and the attention is

70:18

all need paper and then it was used

70:20

again in gpt2 as well

70:24

so I couldn't find it in the

70:26

Transformers implementation I'm not sure

70:28

where they tie those embeddings but I

70:30

can find it in the original gpt2 code U

70:34

introduced by open aai so this is um

70:36

openai gpt2 Source model and here where

70:40

they are forwarding this model and this

70:41

is in tensorflow but uh that's okay we

70:44

see that they get the wte token

70:46

embeddings and then here is the incoder

70:50

of the token embeddings and the

70:52

position and then here at the bottom

70:54

they Ed the WT again to do the lits so

70:58

when they get the loits it's a math Mo

71:00

of uh this output from the Transformer

71:02

and the wte tensor is

71:05

reused um and so the wte tensor

71:08

basically is used twice on the bottom of

71:10

the Transformer and on the top of the

71:12

Transformer and in the backward pass

71:14

we'll get gradients contributions from

71:17

both branches right and these gradients

71:19

will add up um on the wte tensor um so

71:23

we'll get a contribution from the

71:24

classifier list

71:25

and then at the very end of the

71:27

Transformer we'll get a contribution at

71:28

the at the bottom of it float floating

71:31

again into the wte uh tensor so we want

71:35

to we are currently not sharing WT and

71:38

our code but we want to do

71:40

that um

71:44

so weight sharing scheme um and one way

71:48

to do this let's see if goil gets it oh

71:50

it does okay uh so this is one way to do

71:54

it

71:56

uh

71:56

basically relatively straightforward

71:59

what we're doing here is we're taking

72:00

the wte do weight and we're simply uh

72:04

redirecting it to point to the LM head

72:08

so um this basically copies the data

72:11

pointer right it copies the reference

72:14

and now the wte weight becomes orphaned

72:17

uh the old value of it and uh pytorch

72:20

will clean it up python will clean it up

72:23

and so we are only left with a single

72:26

tensor and it's going to be used twice

72:28

in the forward pass and uh this is to my

72:31

knowledge all that's required so we

72:34

should be able to use this and this

72:36

should probably train uh we're just

72:39

going to basically be using this exact

72:40

same sensor twice and

72:44

um we weren't being careful with

72:46

tracking the likelihoods but uh

72:48

according to the paper and according to

72:50

the results you'd actually expect

72:51

slightly better results doing this and

72:53

in addition to that one other reason

72:54

that this is very very nice for us is

72:57

that this is a ton of parameters right

72:59

uh what is the size here it's 768 *

73:03

50257 so This Is 40 million parameters

73:07

and this is a 124 million parameter

73:09

model so 40 divide 124 so this is like

73:12

30% of the parameters are being saved

73:15

using this weight time scheme and so

73:18

this might be one of the reasons that

73:20

this is working slightly better if

73:21

you're not training the model long

73:22

enough because of the weight tying uh

73:25

you don't have to train as many

73:26

parameters and so you become more

73:27

efficient um in terms of the training

73:30

process uh because you have fewer

73:32

parameters and you're putting in this

73:34

inductive bias that these two embeddings

73:36

should share similarities between tokens

73:40

so this is the way time scheme and we've

73:42

saved a ton of parameters and we expect

73:44

our model to work slightly better

73:45

because of the scheme okay next I would

73:47

like us to be a bit more careful with

73:49

the initialization and to try to follow

73:50

the way gpt2 initialized their model now

73:54

unfortunately the gpt2 paper and the

73:55

gpt3 paper are not very explicit about

73:58

initialization so we kind of have to

74:00

read between the lines uh and instead of

74:02

going to the paper which is quite vague

74:04

um there's a bit of information in the

74:07

code that open I released so when we go

74:09

to the model.py we see that when they

74:11

initialize their weights they are using

74:13

the standard deviation of

74:15

0.02 and that's how they they so this is

74:19

a normal distribution for the weights

74:21

and the standard deviation is

74:23

0.02 for the bias they initialize that

74:25

with

74:26

zero and then when we scroll down

74:30

here why is this not scrolling

74:33

um the token embeddings are initialized

74:36

at

74:37

0.02 and position embeddings at 0.01 for

74:40

some reason so those are the

74:42

initializations and we'd like to mirror

74:44

that in

74:45

gpt2 uh in our module here so here's a

74:48

snippet of code that I sort of came up

74:50

with very

74:52

quickly so what's happening here is at

74:55

the end of our initializer for the GPT

74:57

module we're calling the apply function

74:59

of NN module and that iterates all the

75:02

sub modules of this module and uh

75:05

applies in it weights function on them

75:08

and so what's happening here is that

75:11

we're in we're iterating all the modules

75:13

here and if they are an nn. linear

75:16

module then we're going to make sure to

75:17

initialize the weight using a normal

75:19

with the standard deviation of

75:21

0.02 if there's a bias in this layer we

75:24

will make sure to initialize that to

75:25

zero note that zero initialization for

75:28

the bias is not actually the pyto

75:29

default um by default the bias here is

75:33

initialized with a uniform so uh that's

75:36

interesting so we make sure to use zero

75:38

and for the embedding we're just going

75:40

to use 0.02 and um keep it the same um

75:43

so we're not going to change it to 0.01

75:45

for positional because it's about the

75:47

same and then if you look through our

75:49

model the only other layer that requires

75:51

initialization and that has parameters

75:53

is the layer norm and the fighter defer

75:55

initialization sets the scale in the

75:57

layer Norm to be one and the offset in

76:00

the layer Norm to be zero so that's

76:01

exactly what we want and so we're just

76:03

going to uh keep it that way and so this

76:06

is the default initialization if we are

76:09

following the um where is it the uh gpt2

76:14

uh source code that they released I

76:17

would like to point out by the way that

76:19

um typically the standard deviation here

76:21

on this initialization if you follow the

76:23

Javier initialization would be one of

76:24

over the square root of the number of

76:27

features that are incoming into this

76:28

layer but if you'll notice actually 0.02

76:32

is basically consistent with that

76:34

because the the model sizes inside these

76:36

Transformers for gpt2 are roughly 768

76:39

1600 Etc so 1 over the square root of

76:41

for example 768 gives us

76:44

0.03 if we plug in 600 1,600 we get

76:49

0.02 if we plug in three times that

76:52

0.014 Etc so basically 0.02 is roughly

76:56

in the vicinity of reasonable values for

76:59

the for um for these initializations

77:02

anyway so so it's not uh completely

77:05

crazy to be hard coding 0.02 here uh but

77:08

you'd like typically uh some something

77:11

that grows with the model size instead

77:13

but we will keep this because that is

77:15

the gpt2 initialization per their source

77:17

code but we are not fully done yet on

77:19

initialization because there's one more

77:20

caveat here so

77:23

here a mod initialization which accounts

77:26

for the accumulation on the residual

77:27

path with model depth is used we scale

77:30

the weight of residual layers of

77:31

initialization by factor of one over squ

77:33

of n where n is the number of residual

77:35

layers so this is what gbt2 paper says

77:38

so we have not implemented that yet and

77:41

uh we can do so now now I'd like to

77:43

actually kind of like motivate a little

77:44

bit what they mean here I think um so

77:47

here's roughly what they

77:49

mean if you start out with zeros in your

77:52

residual stream remember that each

77:54

residual stream is a is of this form

77:57

where we continue adding to it X is X

78:00

plus something some kind of contribution

78:02

so every single block of the residual uh

78:05

Network contributes some uh amount and

78:09

it gets added and so what ends up

78:11

happening is that the variance of the

78:15

activations in the residual stream grows

78:18

so here's a small example if we start at

78:19

zero and then we for 100 times uh we

78:23

have sort of this residual stream of of

78:25

768 uh zeros and then 100 times we add

78:30

um random which is a normal distribution

78:33

zero mean one standard deviation if we

78:36

add to it then by the end the residual

78:37

stream has grown to have standard

78:39

deviation of 10 and that's just because

78:42

um we're always adding um these numbers

78:47

and so this scaling factor that they use

78:50

here exactly compensates for that growth

78:53

so if we take n and we basically um

78:57

scale down every one of these

78:59

contributions into the residual stream

79:00

by one over theare Ro of n so 1 over

79:03

theun of n is n to the 0.5

79:07

right because n the5 is the square root

79:11

and then one over the square root is n.5

79:14

if we scale it in this way then we see

79:16

that we actually get um

79:20

one

79:21

so this is a way to control the growth

79:24

of of activations inside the residual

79:26

stream in the forward pass and so we'd

79:29

like to initialize in the same way where

79:31

these weights that are at the end of

79:33

each block so this C uh layer uh the gbt

79:38

paper proposes to scale down those

79:40

weights by one over the square root of

79:42

the number of residual

79:43

layers so one crude way to implement

79:46

this is the following I don't know if

79:48

this is uh pyro sanctioned but it works

79:50

for me is we'll do in the

79:53

initialization see that s that do

79:56

special nanog

79:58

GPT uh scale in it is one so we're

80:04

setting um kind of like a flag for this

80:06

module there must be a better way in py

80:08

torch right but I don't

80:11

know okay so we're basically attaching

80:13

this flag and trying to make sure that

80:16

it doesn't conflict with anything

80:17

previously and then when we come down

80:20

here this STD should be 0.02 by default

80:25

but then if

80:27

haat um module of this thing

80:31

then STD *

80:34

equals

80:36

um copal is not guessing correctly uh so

80:39

we want one over the square root of the

80:41

number of layers so

80:44

um the number of residual layers here is

80:47

twice

80:48

times Salt out config layers and then

80:52

this times .5 so we want to scale down

80:57

that standard deviation and this should

80:59

be um correct and Implement that I

81:03

should clarify by the way that the two

81:04

times number of layers comes from the

81:06

fact that every single one of our layers

81:07

in the Transformer actually has two

81:09

blocks that add to the ridal pathway

81:11

right we have the attention and then the

81:13

MLP so that's where the two times comes

81:16

from and the other thing to mention is

81:18

that uh what's slightly awkward but

81:21

we're not going to fix it is that um

81:23

because we are weight sharing the wte

81:26

and the LM head in this iteration of our

81:29

old subm modules we're going to actually

81:31

come around to that tensor twice so

81:33

we're going to first initialize it as an

81:34

embedding with 0.02 and then we're going

81:37

to come back around it again in a linear

81:39

and initialize it again using 0.02 and

81:42

it's going to be 0.02 because the LM

81:44

head is of course not not scaled so it's

81:46

not going to come here it's just it's

81:48

going to be basically initialized twice

81:50

using the identical same initialization

81:52

but that's okay and then scrolling over

81:56

here I added uh some code here so that

81:59

we have

82:00

reproducibility um to set the seeds and

82:03

now we should be able to python train

82:05

gpt2 pi and let this running and as far

82:09

as I know this is the gpt2

82:11

initialization uh in the way we've

82:12

implemented it right now so this

82:16

looks uh reasonable to me okay so at

82:19

this point we have the gpt2 model we

82:21

have some confidence that it's correctly

82:23

implemented we've initialized it

82:24

properly and we have a data loader

82:26

that's iterating through data batches

82:27

and we can train so now comes the fun

82:30

part I'd like us to speed up the

82:31

training by a lot so we're getting our

82:33

money's worth with respect to the

82:34

hardware that we are uh using here and

82:38

uh we're going to speed up the training

82:39

by quite a bit uh now you always want to

82:42

start with what Hardware do you have

82:44

what does it offer and are you fully

82:45

utilizing it so in my case if we go to

82:48

Nvidia

82:49

SMI we can see

82:53

that I have eight gpus and each one of

82:57

those gpus is an a100 sxm 80 gb so this

83:01

is the GPU that I have available to me

83:03

in this box now when I look when I use

83:07

um to spin up these kinds of Boxes by

83:09

the way my favorite place to go to is

83:11

Lambda Labs um they do sponsor my

83:14

development and that of my projects uh

83:17

but I this is my favorite place to go

83:20

and this is where you can spin up one of

83:21

these machines and you pay per hour and

83:23

it's very very simple

83:25

so I like to spin them up and then

83:26

connect vsod to it and that's how I

83:28

develop now when we look at the A1 100s

83:30

that are available here a100 80 GB sxm

83:35

is the um GPU that I have here and we

83:39

have a bunch of numbers here for um how

83:41

many calculations you can expect out of

83:43

this GPU so when I come over here

83:46

and I break in right after here so

83:50

python

83:51

trity so I'm breaking in right after we

83:53

calculate the loit and

83:55

laws and the interesting thing I'd like

83:57

you to note is when I do lit. dtype this

84:02

prints a torch. FL 32 so by default iny

84:06

torch when you create tensors um and

84:08

this is the case for all the activations

84:10

and for the parameters of the network

84:11

and so on by default everything is in

84:13

float 32 that means that every single

84:17

number activation or weight and so on is

84:20

using a float representation that has 32

84:23

bits and uh that's actually quite a bit

84:26

of memory and it turns out empirically

84:27

that for deep learning as a

84:28

computational workload this is way too

84:30

much and deep learning and the training

84:32

of these networks can tolerate

84:34

significantly lower precisions um not

84:37

all computational workflows can tolerate

84:39

small Precision so for example um if we

84:43

go back to to the data sheet you'll see

84:45

that actually these gpus support up to

84:48

fp64 and this is quite useful I

84:50

understand for a lot of um scientific

84:52

Computing applications and there really

84:54

need this uh but we don't need that much

84:56

Precision for deep learning training So

84:59

currently we are here

85:01

fp32 and with this code as it is right

85:04

now we expect to get at at most 19.5

85:08

Tera flops of performance that means

85:10

we're doing 19.5 trillion operations

85:13

floating Point operations so this is

85:15

floating Point multiply add most um most

85:20

likely and so these are the floating

85:23

Point operations

85:25

uh now notice that if we are willing to

85:27

go down in Precision so tf32 is a lower

85:31

Precision format we're going to see in a

85:32

second you can actually get an 8X

85:34

Improvement here and if you're willing

85:36

to go down to float 16 or B float 16 you

85:39

can actually get time 16x performance

85:42

all the way to 312 Tera flops you see

85:45

here that Nvidia likes to site numbers

85:47

that have an asterisk here this asterisk

85:50

uh says with sparsity uh but we are not

85:52

going to be using sparsity in R code and

85:55

I don't know that this is very widely

85:56

used in the industry right now so most

85:58

people look at this number here uh

86:01

without sparcity and you'll notice that

86:03

we could have got even more here but

86:05

this is int 8 and int 8 is used for

86:08

inference not for training uh because

86:11

int 8 has a um it basically has um

86:17

uniform

86:18

spacing um and uh we actually require a

86:21

float so that we get a better match to

86:24

the uh normal distributions that occur

86:28

during training of neural networks where

86:29

both activations and weights are

86:31

distributed as a normal distribution and

86:33

so uh floating points are really

86:35

important to to match that uh

86:38

representation so we're not typically

86:40

using int 8 uh for training but we are

86:42

using it for inference and if we bring

86:45

down the Precision we can get a lot more

86:47

Terra flops out of the tensor course

86:49

available in the gpus we'll talk about

86:51

that in a second but in addition to that

86:53

if all of these numbers have fewer bits

86:56

of representation it's going to be much

86:58

easier to move them around and that's

87:00

where we start to get into the memory

87:02

bandwidth and the memory of the model so

87:04

not only do we have a finite capacity of

87:06

the number of bits that our GPU can

87:08

store but in addition to that there's a

87:11

speed with which you can access this

87:13

memory um and you have a certain memory

87:16

bandwidth it's a very precious resource

87:19

and in fact many of the deep learning uh

87:21

work workloads for training are memory

87:23

bound and what that means is actually

87:25

that the tensor cores that do all these

87:27

extremely fast multiplications most of

87:29

the time they're waiting around they're

87:31

idle um because we can't feed them with

87:34

data fast enough we can't load the data

87:37

fast enough from memory so typical

87:38

utilizations of your Hardware if you're

87:40

getting 60% uh utilization you're

87:43

actually doing extremely well um so half

87:46

of the time in a well-tuned application

87:48

your tensor cores are not doing

87:50

multiplies because the data is not

87:51

available so the memory bandwidth here

87:53

is extremely important as well and if we

87:55

come down in the Precision for all the

87:58

floats all the numbers weights and

88:00

activations suddenly require less memory

88:02

so we can store more and we can access

88:05

it faster so everything speeds up and

88:07

it's amazing and now let's reap the

88:09

benefits of it um and let's first look

88:12

at the tensor float 32

88:14

format okay so first of all what are

88:16

tensor cores well tensor course tensor

88:19

core is just an instruction in the a100

88:22

architecture right so so what it does is

88:25

it does basically a little 4x4 Matrix

88:27

multiply so uh this is just matrix

88:30

multiplication here of 4x4 matrices and

88:35

there are multiple configurations as to

88:38

what Precision any of these matrices are

88:40

it in what Precision the internal

88:42

accumulate happens and then what is the

88:45

output Precision input precisions Etc so

88:47

there's a few switches but it's

88:48

basically a 4x4 multiply and then

88:51

anytime we have any operations that

88:53

require Magic multiplication uh they get

88:55

broken up into these into this

88:58

instruction of little 4x4 multiply and

89:00

so everything gets broken up into this

89:02

instruction because it's the fastest way

89:04

to multiply matrices and it turns out

89:06

that most of the computational work that

89:08

we're doing up above uh all of it really

89:10

is matrix multiplication most of the

89:12

work computationally happens in the

89:14

linear layers um linear linear Etc

89:20

there's a few things sandwiched in

89:21

between so there's some additions in

89:23

residuals there's some G nonlinearities

89:25

there's some layer Norms Etc but if you

89:28

just time them you'll see that these are

89:30

nothing like basically the in

89:32

Transformer is just a bunch of Matrix

89:34

multiplications really um and especially

89:37

at this small scale 124 million

89:39

parameter model actually the biggest

89:42

matrix multiplication by far is the

89:44

classifier layer at the top that is a

89:46

massive Matrix multiply of going from

89:49

768 to

89:50

50257 and that Matrix multiply dominates

89:53

anything else that happens in that

89:55

Network roughly speaking so it's Matrix

89:58

multiplies that become a lot faster

90:00

which are hidden inside our linear

90:02

layers and they're accelerated through

90:05

tensor course now the best reference I

90:07

would say for tensor course is basically

90:09

just go to the um a 100 architecture

90:13

white paper and then it's pretty

90:15

detailed and but I think people it's

90:18

like relatively readable mostly if you

90:20

half understand what's happening um so

90:23

figure 9 tensor float

90:26

32 so this is the explanation basically

90:28

for tf32 and what happens here and you

90:31

see that there's many configuration

90:32

options here available so the input

90:35

operands and what precisions are they in

90:37

the accumulator and um what um basically

90:41

the um the internal representation

90:44

within the instruction when you do the

90:46

accumulate of this matrix

90:48

multiplication so the intermediate plus

90:51

equals um of the intermediate little

90:53

vector multiplies here that all happens

90:55

in

90:57

fp32 and then uh this is an aex

91:00

improvement as I mentioned to the Ops

91:01

that we get so tf32 specifically we're

91:04

looking at this row here and the way

91:06

this works

91:07

is

91:10

um normally fp32 has 32 bits

91:14

tf32 is the exact same bits we have one

91:18

sign bit we have eight exponent bits

91:21

except the mantisa bits get cropped in

91:24

the float and so basically um we end up

91:27

with just 19 bits instead of 32 bits

91:30

because the last 133 bits get truncated

91:33

they get dropped um and all this is

91:36

internal to the instruction so none of

91:38

it is visible to anything in our pytorch

91:41

uh none of our pytorch code will change

91:43

all of the numbers will look identical

91:45

it's just that when you call the tensor

91:47

core um instruction internally in the

91:50

hardware it will crop out these 13 bits

91:54

and that allows it to uh calculate this

91:57

little Matrix multiply significantly

91:59

faster 8X faster now of course this

92:02

speed up comes at a cost and the cost is

92:04

that we are reducing the Precision our

92:07

accumulate is still an fp32 our output

92:09

is fp32 our inputs are fp32 but

92:12

internally things get truncated in the

92:14

operand to perform the operation faster

92:17

and so our results are starting to be a

92:19

bit more approximate but empirically

92:21

when you actually train with this you

92:22

basically can't tell the difference

92:24

so the reason I like tf32 is because if

92:26

you can tolerate a little bit of a

92:28

Precision fudge um then this is free

92:32

like none of your codes sees this it's

92:34

fully internal to the operation and the

92:36

operation to you just go 8X faster and

92:39

it's a bit more approximate and so it's

92:42

a pretty sweet spot I would say in

92:43

optimization and uh let's see what that

92:46

looks like first so I've set up our Cod

92:48

to just time the uh iterations so import

92:51

time I changed the hyper parameters so

92:54

that we have something a bit more that

92:55

reflects uh kind of workload that we

92:57

want to run uh because we want to do a

92:59

fairly large run at the end of this so

93:01

let's use batch size 16 and let's now

93:04

use the actual gpt2 um maximum sequence

93:07

length of 10,24

93:08

tokens uh so this is the

93:11

configuration and then for 50 iterations

93:15

I'm just doing something very lazy here

93:17

I'm doing time. time to get the current

93:19

time and then this is the optimization

93:22

Loop and now I want to time how long

93:24

this takes now one issue with working

93:28

with gpus is that as your

93:32

CPU um when your CPU runs it's just

93:35

scheduling work on GPU it's ordering

93:38

some work right and so it send a request

93:40

and then it continues running and so we

93:43

can actually it can happen sometimes

93:44

that we sort of um speed through this

93:48

and we queue up a lot of kernels to run

93:50

on the GPU and then the CPU sort of like

93:52

gets here and takes time at time but

93:54

actually the GPU is still running

93:56

because it takes it time to actually

93:57

work through the work that was scheduled

94:00

to run and so you're just building up a

94:03

queue for the GPU and so actually if you

94:05

need to you want to wait toat data

94:07

synchronize and this will wait for the

94:10

GPU to finish all the work that was

94:12

scheduled to run up above here and then

94:15

we can actually take the time so

94:17

basically we're waiting for the GPU to

94:19

stop this iteration take time and then

94:22

we're going to just print it so

94:24

so here I'm going to run the training

94:26

Loop and here on the right I'm watching

94:29

Nvidia SMI so we start off at zero um

94:33

we're not using the GPU and then by

94:35

default P will use gpu0 so we see that

94:37

it gets filled up and we're using 35 GB

94:40

out of 80 gabt

94:42

available and then here on the left we

94:45

see that because we've cranked up the

94:47

batch

94:48

size now it's only 20 batches to do a

94:51

single Epoch on our tiny Shakespeare

94:54

and we see that we're seeing roughly a

94:55

th000 milliseconds per iteration here

94:58

right

95:00

so the first iteration sometimes is

95:02

slower and that's because pytorch might

95:04

be doing a lot of initializations here

95:06

on the very first iteration and so it's

95:08

probably initializing all these uh

95:09

tensors and buffers to hold all the

95:11

gradients and I'm not 100% sure all the

95:13

work that happens here but uh this could

95:16

be a slower iteration when you're timing

95:18

your logic you always want to be careful

95:19

with that but basically we're seeing a

95:21

th000 milliseconds per iteration

95:24

um and so this will run for roughly 50

95:26

seconds as we have it right now so

95:29

that's our Baseline in flo 32 one more

95:32

thing I wanted to mention is that if

95:35

this doesn't fit into your GPU and

95:36

you're getting out of memory errors then

95:38

start decreasing your batch size until

95:40

things fit so instead of 16 try eight or

95:42

four or whatever you need to fit um the

95:46

batch into your GPU and if you have a

95:48

bigger GPU you can actually potentially

95:49

get away with 32 and so on uh by default

95:52

you want to basically max out has Max

95:54

Max out the batch size that fits on your

95:56

GPU and you want to keep it nice numbers

95:59

so use numbers that have lots of powers

96:01

of two in them so 16 is a good number 8

96:05

24 32 48 These are nice numbers but

96:09

don't use something like 17 uh because

96:11

that will run very inefficiently on a

96:12

GPU uh and we're going to see that a bit

96:14

later as well so for now let's just

96:17

stick with

96:18

16124 and uh the one thing that I added

96:22

also here and I ran it again is I'm

96:25

calculating a tokens per second

96:27

throughput during training

96:29

because we might end up changing the

96:31

backat size around over time but tokens

96:34

per second is the objective measure that

96:35

we actually really care about how many

96:37

tokens of data are we training on and

96:39

what is the throughput of tokens that

96:41

we're getting in our optimization so

96:43

right now we're processing and training

96:44

on 163,000 tokens per second roughly and

96:48

that's a bit more objective

96:50

metric okay so let's now enable tf32 now

96:53

luckily pytorch makes this fairly easy

96:56

for us and uh to enable tf32 you just

96:59

need to do a single line and is this and

97:02

when we go to the py documentation here

97:04

for this function basically this tells

97:07

pych what kind of kernels to run and by

97:10

default I believe it is highest highest

97:13

Precision for mat M and that means that

97:15

everything happens in float 32 just like

97:18

it did before but if we set it to high

97:20

as we do right now Matrix

97:22

multiplications will not use tensor flow

97:24

32 when it's

97:26

available my GPU is a100 so it's an

97:30

ampere series and therefore tf32 is

97:33

available if you have an older GPU this

97:35

might not be available for you but for

97:38

my GPU it's available and so what I

97:39

expect P to do is that every single

97:41

place where we see an nn. linear inside

97:44

there there's a matrix multiplication

97:46

and I expect that matrix multiplication

97:48

now to be um running on tensor course

97:51

utilizing the TF 32%

97:55

so this is the single line of change

97:58

that is I believe necessary and let's

97:59

rerun this now we saw that um in terms

98:03

of the throughput that is promised to us

98:05

we're supposed to be getting 8X roughly

98:08

so let's see what

98:10

happens and that 8X came from here right

98:15

um 8X and it also came from looking at

98:20

it um here 156 T flops instead of of

98:24

19.5 okay so what actually happened uh

98:27

so we're seeing that our throughput

98:29

roughly 3x not aex so we are going we're

98:35

from 1,000 milliseconds we're going down

98:37

to 300 milliseconds and our throughput

98:39

is now about 50,000 tokens per second so

98:41

we have a roughly 3x instead of 8X so

98:43

what happened and basically What's

98:46

Happening Here is again a lot of these

98:48

workloads are memory bound and so even

98:51

though the

98:52

tf32 offers in principle a lot faster

98:57

throughput all of these numbers

98:59

everywhere are still float 32s and it's

99:01

float 32 numbers that are being shipped

99:03

all over the place through the memory

99:05

system and is just costing us way too

99:07

much time to shuttle around all this

99:08

data and so even though we've made the

99:10

multiply itself much faster uh we are

99:13

memory bound and we're not actually

99:14

seeing the full benefit uh that would

99:16

come from uh this napkin math here uh

99:19

that said we are getting one a 3X faster

99:22

throughput and this is free um single

99:26

line of code in P torch all your

99:28

variables are still float 32 everywhere

99:30

it just runs faster and it's slightly

99:32

more approximate but we're not going to

99:34

notice it basically uh so that's

99:37

tf32 okay so let's now continue so we've

99:41

exercised this row and um we saw that we

99:44

can crop out some of the Precision

99:46

inside the operation itself but we saw

99:49

that we're still memory bound we're

99:50

still moving around all these floats

99:52

right otherwise and we're paying that

99:53

cost because of this so let's now

99:56

decrease the amount of stuff that we're

99:57

going to be moving around and we're

99:59

going to do that by dropping down to B

100:01

float 16 so we're only going to be

100:04

maintaining 16 bits per float and we're

100:07

going to use the B flat 16 and I'll

100:08

explain in a bit uh fp16 difference and

100:12

uh we're going to be in this row so when

100:14

we go back to the documentation here for

100:17

the a

100:18

100 um we see here the precisions that

100:23

are are available and this is the

100:25

original fp32 the tf32 crops out the

100:28

Precision and then here in

100:30

bf16 you see that it is very similar to

100:33

tf32 but it's even more aggressive in

100:36

cropping off of the Precision the

100:38

mantisa of this float so the important

100:40

thing with B float 16 is that the

100:42

exponent bits and the sign bit of course

100:45

remain unchanged so if you're familiar

100:47

with your float numbers and I think this

100:49

should should probably be an entire

100:52

video by itself

100:53

the exponent sets the range that you can

100:56

represent of your numbers and the

100:58

Precision is how much Precision you have

101:00

for your numbers and so the range of

101:04

numbers is identical but we can we have

101:07

fewer possibilities within that range

101:10

because we are truncating the Mena so we

101:12

have less Precision in that

101:14

range what that means is that things are

101:17

actually fairly nice because we have the

101:19

original range of numbers that are

101:21

representable in float but we just have

101:24

less Precision for it and the difference

101:27

with fp16 is that they actually touch

101:29

and change the range so fp16 cannot

101:32

represent the full range of fp32 it has

101:35

a reduced range and that's where you

101:37

start to actually run into issues

101:39

because now you need uh these gradient

101:41

scalers and things like that and I'm not

101:43

going to go into the detail of that in

101:45

this video because that's a whole video

101:48

by itself but fb16 actually historically

101:50

came first that was available in the

101:52

Volta series before Amper and so fp16

101:56

came first and everyone started to train

101:58

in fp16 but everyone had to use all

102:00

these gradient scaling operations which

102:02

are kind of annoying and it's an

102:03

additional source of state and

102:05

complexity and the reason for that was

102:07

because the exponent range was reduced

102:09

in fp16 so that's the i e fp16 spec and

102:13

then they came out with bf16 and the

102:15

Ampere and they made it much simpler

102:18

because we're just truncating manessa we

102:20

have the exact same range and we do not

102:21

need gradient scalers so everything is

102:24

much much simpler now when we do use

102:26

bf16 though we are impacting the numbers

102:30

that we might be seeing in our pytorch

102:32

code these this change is not just local

102:35

to the operation itself so let's see how

102:37

that works

102:39

um there's some documentation here that

102:43

so I think this is probably the best

102:44

best page to explain how to use mixed

102:46

Precision in pytorch um because there

102:49

are many other tutorials and so on even

102:51

within pitor documentation that are a

102:53

lot more confusing and so I recommend

102:55

specifically this one because there's

102:57

five other copies that I would not

102:59

recommend and then when we come

103:02

here ignore everything about everything

103:05

ignore everything about gradient

103:07

scalers and only look at torch.

103:10

AutoCast and basically also this comes

103:13

to a single line of code at the end so

103:15

this is the context manager that we

103:18

want and we want to use that in our

103:21

Network when you click into the torch.

103:25

AutoCast autocasting it has a few more

103:28

uh a bit more guideline for you so it's

103:30

telling you do not call B flat 16 on any

103:34

of your tensors just use AutoCast and

103:36

only surround the uh forward pass of the

103:39

model and the loss calculation and

103:41

that's the only two things that you

103:43

should be surrounding leave the backward

103:45

and the optimizer step alone so that's

103:47

the guidance that comes from the P team

103:49

so we're going to follow that guidance

103:51

and for us because the L calculation is

103:53

inside of the model forward pass for us

103:56

we are going to be doing

103:58

this and then we don't want to be using

104:00

torch Flo 16 because if we do that we

104:02

need to start using gradient scalers as

104:04

well so we are going to be using B float

104:06

16 this is only possible to do an ampere

104:09

uh but this means that the changes are

104:11

extremely minimal like basically just

104:13

this one line of

104:14

code um let me first break

104:19

in to here before we actually run this

104:22

so right after logits I'd like to show

104:25

you that different from the tf32 that we

104:28

saw this is actually going to impact our

104:31

tensors

104:32

so this Lis tensor if we now look at

104:36

this and we look at the dtype we

104:38

suddenly see that this is now B float

104:40

16 uh it's not float 32 anymore so our

104:43

activations have been changed the

104:45

activations tensor is now B FL 16 but

104:48

not everything has changed so model.

104:51

Transformer

104:55

wte uh this is the weight uh token

104:57

embedding table it has a weight inside

105:00

it and the dtype of this weight this

105:02

parameter is still torch float 32 so our

105:06

parameters seem to still be in float 32

105:09

but our activations the loits are now in

105:11

P 16 so clearly this is why we get the

105:14

mixed Precision some things pytorch is

105:16

keeping inlow 32 some things pytorch is

105:19

converting to lower Precision um and

105:23

what gets converted at what point is not

105:26

super clear I remember scrolling

105:30

down is it

105:34

here okay I can't find

105:37

it I I thought it was here okay there we

105:41

go so there are a few docks on when

105:44

you're using this AutoCast what gets

105:46

converted to B FL 16 and and when so for

105:49

example only these Matrix multiply like

105:51

operations get converted to float 16 but

105:54

a lot of operations remain in float 32

105:56

so in particular a lot of normalizations

105:58

like layer norms and things like that

106:00

not all of those layers might be

106:01

converted um so only some layers

106:05

selectively would be running B flat 16

106:07

but things like softmax uh layer Norms

106:10

uh log um log soft Max so loss function

106:14

calculations a lot of those things might

106:15

remain in float 32 because they are more

106:17

susceptible to Precision changes major

106:20

multiplies are fairly um

106:23

robust to Precision changes uh so some

106:26

parts of the network are um impacted

106:29

more or less by the Precision

106:31

change um so basically only some parts

106:34

of the of the model are running in

106:35

reduced Precision let's take it for a

106:38

spin and let's actually see what kind of

106:41

improvement we achieve

106:48

here okay so we used to be 333

106:51

milliseconds we're now 300

106:53

and we used to be somewhere around

106:54

50,000 tokens per second we're now at 55

106:57

so we're definitely running faster but

106:59

maybe not a lot faster and that's

107:02

because there are still many many

107:03

bottlenecks in our gbt2 we're just

107:05

getting started but we have dropped down

107:07

the precision as far as we can with my

107:09

current GPU which is a100 we're using

107:12

pytorch AutoCast unfortunately I don't

107:15

actually exactly know what pytorch

107:17

AutoCast do uh does I don't actually

107:19

know exactly what's in B flat 16 what's

107:22

in float 32

107:23

we could go in and we could start to

107:24

scrutinize it um but these are the kinds

107:27

of rules that pytorch has internally and

107:29

unfortunately they don't documented very

107:31

well uh so we're not going to go into

107:34

that into in too much detail but for now

107:36

we are training in B flow 16 we do not

107:39

need a gradient scaler and the reason

107:40

things are running faster is because um

107:44

we are able to run tensor course in B FL

107:47

16 now that means we are in this row but

107:52

uh we are also paying in Precision for

107:53

this uh so um we expect slightly less

107:57

accurate results with respect to the

107:58

original fp32 but empirically in many

108:01

cases this is a worth it uh kind of

108:04

tradeoff because it allows you to run

108:06

faster and you could for example train

108:07

longer and make up for the uh for that

108:10

Precision decrease so um that's b46 for

108:15

now okay so as we can see we are

108:17

currently at about 300 milliseconds uh

108:19

per iteration and we're now going to

108:21

reach for some really heavy weapons in

108:23

the pie torch Arsenal and in particular

108:25

we're going to introduce torch. compile

108:27

so torch. compile is really quite

108:29

incredible infrastructure from the

108:31

pytorch team and it's basically a

108:32

compiler for neural networks like it's

108:35

almost like GCC for CN C++ code this is

108:38

just this GCC of neural nuts so came out

108:42

a while ago and extremely simple to use

108:46

um the way to use torch compile is to do

108:48

this it's a single line of code to

108:50

compile your model and return it now

108:54

this line of code will cost you

108:55

compilation time but as you might guess

108:57

it's going to make the code a lot faster

108:59

so let's actually run that because this

109:01

will take some time to run but currently

109:03

remember we're at 300 milliseconds and

109:05

we'll see what happens now while this is

109:08

running I'd like to explain a little bit

109:10

of what torch. compile does under the

109:11

hood uh so feel free to read this page

109:15

of P torch but basically there's no real

109:17

good reason for you to not use torch

109:19

compile in your pie torch I kind of feel

109:21

like you should be using almost by

109:23

default if you're not uh unless you're

109:25

debugging and you want your code to run

109:26

really fast and there's one line here in

109:29

torch compile that I found that actually

109:31

kind of like gets to why this is faster

109:33

speed up mainly comes from reducing

109:35

python overhead and GPU read wrs so let

109:38

me unpack that a little bit um okay here

109:41

we are okay so we went from 300

109:43

milliseconds we're now running at 129

109:46

milliseconds so this is uh 300 129 about

109:51

2.3x Improvement from a single line of

109:53

code in py torch uh so quite incredible

109:56

so what is happening what's happening

109:57

under the hood well when you pass the

109:59

model to torch

110:01

compile what we have here in this NN

110:04

module this is really just the

110:05

algorithmic description of what we'd

110:08

like to happen in our Network and torch

110:11

compile will analyze the entire thing

110:14

and it will look at what operations You'

110:15

like to use and with the benefit of

110:18

knowing exactly what's going to happen

110:20

it doesn't have to run in What's called

110:22

the e mode it doesn't have to just kind

110:24

of like go layer by layer like the

110:26

python interpreter normally would start

110:29

at the

110:31

forward and the python interpreter will

110:33

go okay let's do this operation and then

110:36

let's do that operation and it kind of

110:38

materializes all the operations as it

110:40

goes through uh so these um calculations

110:43

are dispatched and run in this order and

110:45

the python interpreter and this code

110:47

doesn't know what kind of operations are

110:49

going to happen later but torch compile

110:51

sees your entire code at the same time

110:53

and it's able to know what operations

110:56

you intend to run and it will kind of

110:58

optimize that process the first thing it

111:00

will do is will it will take out the

111:01

python interpreter from the forward pass

111:03

entirely and it will kind of compile

111:05

this entire neural net as a single

111:07

object with no python interpreter

111:09

involved so it knows exactly what's

111:11

going to run and we'll just run that and

111:12

it's all going to be running in

111:14

efficient

111:15

code uh the second thing that happens is

111:18

uh this read write that they mentioned

111:21

very briefly so a good example of that I

111:23

think is the G nonlinearity that we've

111:25

been looking at so here we use the n and

111:28

G now this here is me uh basically just

111:32

breaking up the inang Galu uh which you

111:35

remember has this formula so this here

111:37

is the equivalent implementation to

111:39

what's happening inside g algorithmic l

111:41

it's

111:42

identical Now by default if uh we just

111:46

we using this instead of ending. G here

111:48

what would happen without torch compile

111:51

well the python interpreter would make

111:52

its way here and then it would be okay

111:54

well there's an input well let me first

111:58

let me raise this input to the third

111:59

power and it's going to dispatch a

112:01

kernel that takes your input and raises

112:03

it to the third power and that kernel

112:05

will run and when this kernel runs what

112:08

ends up happening is this input is

112:11

stored in the memory of the GPU so

112:13

here's a helpful example of the layout

112:16

of what's happening right you have your

112:18

CPU this is in every single computer

112:21

there's a few cores in there and you

112:23

have your uh Ram uh your memory and the

112:26

CPU can talk to the memory and this is

112:28

all well known but now we've added the

112:30

GPU and the GPU is a slightly different

112:32

architecture of course they can

112:33

communicate and it's different in that

112:35

it's got a lot more course than a CPU

112:38

all of those cores are individually a

112:40

lot simpler too but it also has memory

112:43

right this high bandwidth memory I'm

112:47

sorry if I'm botching it hbm I don't

112:49

even know what that stands for I'm just

112:51

realizing that

112:53

but uh this is the memory and it's very

112:54

equivalent to uh RAM basically in the

112:58

computer and what's happening is that

113:00

input is living in the memory and when

113:02

you do input

113:05

cubed this has to travel to the GPU to

113:09

the course and to all the caches and

113:12

registers on the actual chip of this

113:15

GPU and it has to calculate the all the

113:17

elements to the third and then it saves

113:19

the result back to the memory and it's

113:22

this uh travel time that actually causes

113:25

a lot of issues so here remember this

113:28

memory bandwidth we can communicate

113:30

about 2 terabytes per second which is a

113:31

lot but also we have to Traverse this

113:35

link and it's very slow so here on the

113:37

GPU we're on chip and everything is

113:39

super fast within the chip but going to

113:41

the memory is extremely expensive takes

113:43

extremely long amount of time and so we

113:46

load the input do the calculations and

113:48

load back the output and this round trip

113:51

takes a lot of time

113:53

and now right after we do that we

113:54

multiply by this constant so what

113:57

happens then is we dispatch another

113:59

kernel and then the result travels back

114:02

all the elements get multiplied by a

114:03

constant and then the results travel

114:06

back to the memory and then we take the

114:09

result and we add back input and so this

114:12

entire thing again travels to the GPU

114:15

adds the inputs and gets written back so

114:18

we're making all these round trips from

114:20

the memory to actually where the comput

114:22

happens because all the tensor cores and

114:24

alus and everything like that is all

114:26

stored on the chip in the GPU so we're

114:28

doing a ton of round trips and pytorch

114:31

uh without using torch compile doesn't

114:33

know to optimize this because it doesn't

114:36

know what kind of operations you're

114:37

running later you're just telling it

114:39

raise the power to the third then do

114:41

this then do that and it will just do

114:43

that in that sequence but torch compile

114:45

sees your entire code it will come here

114:47

and it will realize wait all of these

114:49

are elementwise operations and actually

114:52

what I'm going to do is I'm going to do

114:53

a single trip of input to the GPU then

114:56

for every single element I'm going to do

114:58

all of these operations while that

115:00

memory is on the GPU or chunks of it

115:04

rather and then I'm going to write back

115:06

a single time so we're not going to have

115:07

these round trips and that's one example

115:09

of what's called kernel fusion and is a

115:11

major way in which everything is sped up

115:14

so basically if you have your benefit of

115:15

onet and you know exactly what you're

115:17

going to compute you can optimize your

115:19

round trips to the memory and you're not

115:21

going to pay the the memory bandwidth

115:23

cost and that's fundamentally what makes

115:25

some of these operations a lot faster

115:27

and what they mean by read writes

115:30

here so let me erase this because we are

115:32

not using it and yeah we should be using

115:36

torch compile and our code is now

115:39

significantly faster and we're doing

115:40

about

115:42

125,000 tokens per second but we still

115:45

have a long way to go before we move on

115:47

I wanted to supplement the discussion a

115:49

little bit with a few more figures uh

115:51

because this is a complic topic but it's

115:53

worth understanding on a high level uh

115:55

what's happening here and I could

115:56

probably spend an entire video of like

115:58

two hours on this but just the preview

116:00

of that basically so this chip here that

116:03

is uh the GPU this chip is where all the

116:06

calculations happen mostly but this chip

116:09

also does have some memory in it but

116:12

most of the memory by far is here in the

116:15

high bandwidth memory hbm and is

116:18

connected they're connected um but these

116:20

are two separate chips basically

116:23

now here this is a zoom in of kind of

116:26

this cartoon diagram of a GPU and what

116:30

we're seeing here is number one you see

116:31

this hbm I I realize it's probably very

116:34

small for you but on the sides here it

116:35

says hbm and so that that's the links to

116:38

the hbm now the hbm is again off chip on

116:42

the chip there are a large number of

116:45

these streaming

116:46

multiprocessors uh every one of these is

116:48

an SM there's 120 of them in total and

116:51

this is where the a lot of the

116:52

calculations happen and this is a zoom

116:54

in of a single individual as it has

116:57

these four quadrants and see for example

116:59

tensor core this is where a lot of the

117:00

Matrix multiply stuff happens but

117:02

there's all these other units to do all

117:04

different kinds of calculations for fp64

117:07

fp32 and for integers and so on now so

117:11

we have all this uh logic here to do the

117:13

calculations but in addition to that on

117:15

the chip there is memory sprinkled

117:17

throughout the chip so L2 cache is some

117:21

amount of memory that lives on the chip

117:23

and then on the SMS themselves there's

117:25

L1 cache I realized it's probably very

117:28

small for you but this blue bar is L1

117:31

and there's also registers um and so

117:34

there is memory stored here but the way

117:36

this memory is stored is very different

117:38

from the way memory is stored in hbm uh

117:41

this is a very different implementation

117:44

uh using um just in terms of like what

117:47

the Silicon looks like it's a very

117:48

different

117:49

implementation um so here you would

117:52

using transistors and capacitors and

117:54

here it's a very different

117:55

implementation uh with SRAM and what

117:57

that looks like but long story short is

118:01

um there is um memory inside the chip

118:05

but it's not a lot of memory that's the

118:07

critical point so this is some C this is

118:09

a example diagram of a slightly

118:11

different GPU just like here where it

118:14

shows that for example typical numbers

118:16

for CPU Dam memory which is this thing

118:19

here you might have one tab of this

118:22

right but it would be extremely

118:23

expensive to access especially for a GPU

118:25

you have to go through the CPU here now

118:28

next we have the hbm so we have tens of

118:30

gigabytes of hbm memory on a typical GPU

118:33

here but it's as I mentioned very

118:35

expensive to access and then on the chip

118:38

itself everything is extremely fast

118:40

within the chip but we only have couple

118:42

10 megabytes of memory collectively

118:45

throughout the Chip And so there's just

118:48

not enough space because the memory is

118:50

very expensive on the chip and so

118:52

there's not a lot of it but it is

118:53

lightning fast to access in relative

118:55

terms and so basically whenever we have

118:58

these kernels um the more accurate

119:01

picture of what's Happening Here is that

119:03

we take these inputs which live by

119:05

default on the global memory and now we

119:08

need to perform some calculation so we

119:10

start streaming the data from the um

119:12

Global memory to the uh chip we perform

119:16

the calculations on the chip and then

119:18

stream it back and store it back to the

119:19

global memory right and so if we are if

119:23

we don't have torch compile we are

119:24

streaming the data through the chip

119:26

doing the calculations and saving to the

119:27

memory and we're doing those round trips

119:29

many many

119:30

times but uh if it's torch compiled then

119:33

we start streaming the memory as before

119:35

but then while we're on the chip we're

119:37

we're we have a chunk of the uh data

119:40

that we're trying to process so that

119:42

chunk now lives on the chip while it's

119:44

on the chip it's extremely fast to

119:46

operate on so if we have kernel Fusion

119:48

we can do all the operations right there

119:49

in an element-wise fashion and those are

119:52

very cheap and then we do a single round

119:54

trip back to the global memory so

119:58

operator Fusion basically allows you to

120:00

keep your chunk of data on the Chip And

120:02

do lots of calculations on it before you

120:04

write it back and that gives huge

120:06

savings and that's why torch compile

120:09

ends up being a lot faster or that's one

120:11

of the major

120:12

reasons uh so again just a very brief

120:14

intro to the memory hierarchy and

120:16

roughly what torch compile does for you

120:19

now torch compile is amazing but there

120:21

are operations torch compile will not

120:23

find and an amazing example of that is

120:26

Flash attention to which we turn next so

120:29

flash attention comes from this paper

120:30

from uh Stanford in

120:33

2022 and it's this incredible algorithm

120:36

for performing attention so um and

120:39

running it a lot faster so flash

120:41

attention will come here and we will

120:44

take out these four

120:46

lines and Flash attention implements

120:48

these four lines really really quickly

120:51

and how does it do that well flash

120:53

attention is a kernel Fusion operation

120:57

so you see here we have um in this

120:59

diagram they're showing P torch and you

121:02

have these four operations uh they're

121:04

including Dropout but we are not using

121:06

Dropout here so we just have these four

121:08

lines of code here and instead of those

121:11

we are fusing them into a single fused

121:13

kernel of flash attention so it's an

121:16

it's a it's a kernel Fusion algorithm

121:19

but it's a kernel Fusion that torch

121:20

compile cannot find

121:22

and the reason that it cannot find it is

121:24

that it um requires an algorithmic

121:26

rewrite of how attention is actually

121:28

implemented here in this case and what's

121:31

remarkable about it is that uh flash

121:33

attention actually if you just count the

121:35

number of flops flash attention does

121:37

more flops than this attention here but

121:41

flash attention is actually

121:42

significantly faster in fact they site

121:45

7. six times faster potentially and

121:48

that's because it is very mindful of the

121:51

memory hierarchy as I described it just

121:53

now and so it's very mindful about

121:55

what's in high bandwidth memory what's

121:57

in the shared memory and it is very

122:00

careful with how it orchestrates the

122:02

computation such that we have fewer

122:04

reads and writes to the high bandwidth

122:06

memory and so even though we're doing

122:08

more flops the expensive part is they

122:10

load and store into hbm and that's what

122:12

they avoid and so in particular they do

122:15

not ever materialize this end byend

122:17

attention Matrix this ATT here a flash

122:21

attention is designed such that this

122:23

Matrix never gets materialized at any

122:25

point and it never gets read or written

122:28

to the hbm and this is a very large

122:30

Matrix right so um because this is where

122:32

all the queries and keys interact and

122:34

we're sort of getting

122:36

um for each head for each batch element

122:40

we're getting a t BYT Matrix of

122:42

attention which is a Million numbers

122:45

even for a single head at a single batch

122:47

index at like so so basically this is a

122:50

ton of memory and and this is never

122:52

materialized and the way that this is

122:54

achieved is that basically the

122:57

fundamental algorithmic rewrite here

122:58

relies on this online softmax trick

123:02

which was proposed previously and I'll

123:03

show you the paper in a bit and the

123:05

online softmax trick coming from a

123:07

previous paper um shows how you can

123:10

incrementally evaluate a soft Max

123:14

without having to sort of realize all of

123:16

the inputs to the softmax to do the

123:18

normalization and you do that by having

123:19

these intermediate variables M and L and

123:22

there's an update to them that allows

123:24

you to evaluate the softmax in an online

123:26

manner um now flash attention actually

123:30

so recently flash attention 2 came out

123:32

as well so I have that paper up here as

123:34

well uh that has additional gains to how

123:36

it calculates flash attention and the

123:38

original paper that this is based on

123:40

basically is this online normalizer

123:42

calculation for softmax and remarkably

123:45

it came out of Nvidia and it came out of

123:46

it like really early 2018 so this is 4

123:50

years before flash attention

123:52

and this paper says that we propose a

123:55

way to compute the classical softmax

123:57

with fewer memory accesses and

123:59

hypothesize that this reduction in

124:00

memory accesses should improve softmax

124:02

performance on actual hardware and so

124:05

they are extremely correct in this

124:08

hypothesis but it's really fascinating

124:10

to me that they're from Nvidia and that

124:12

they had this realization but they

124:13

didn't actually take it to the actual

124:15

flash attention that had to come four

124:18

years later from Stanford so I don't

124:20

fully understand the historical how this

124:22

happened historically um but they do

124:24

basically propose this online update to

124:26

the softmax uh right here and this is

124:29

fundamentally what they reuse here to

124:31

calculate the softmax in a streaming

124:33

Manner and then they realize they can

124:35

actually fuse all the other operations

124:37

with the online sofx calculation into a

124:40

single fused kernel flash attention and

124:42

that's what we are about to use so great

124:45

example I think of being aware of um

124:47

memory hierarchy the fact that flops

124:49

don't matter uh the entire memory access

124:52

pattern matters and that torch compile

124:54

is amazing but there are many

124:55

optimizations that are still available

124:57

to us that potentially torch compile

124:59

cannot find maybe maybe one day it could

125:01

but right now it seems like a lot to ask

125:04

so here's what we're going to do we're

125:05

going to use Flash attention and the way

125:09

to do that basically in pytorch is we

125:11

are going to comment out these four

125:14

lines and we're going to replace them

125:15

with a single line and here we are

125:18

calling this compound operation in

125:20

pytorch called scale that product

125:22

attention and uh pytorch will call flash

125:27

attention when you use it in this way

125:31

I'm not actually 100% sure why torch

125:32

compile doesn't realize that these four

125:34

lines should just call flash attention

125:36

in this exact way we have to do it again

125:38

for it which in my opinion is a little

125:40

bit odd but um here we are so you have

125:46

to use this compound up and uh let's

125:49

wait for a few moments before torch comp

125:51

compile gets around to it and then let's

125:53

remember that we achieved 6.05 661 I

125:58

have it here that's the loss we were

126:00

expecting to see and we took 130

126:03

milliseconds uh before this change so

126:05

we're expecting to see the exact same

126:07

result by iteration 49 but we expect to

126:10

see faster runtime because Flash

126:13

attention is just a an algorithmic

126:14

rewrite and it's a faster kernel but it

126:16

doesn't actually change any of the

126:17

computation and we should have the exact

126:19

same optimization so okay so we're a lot

126:21

faster we're at about 95 milliseconds

126:24

and we achiev

126:28

6.58 okay so they're basically identical

126:31

up to a floating Point fudge Factor so

126:34

it's the identical computation but it's

126:36

significantly faster going from 130 to

126:39

roughly 90

126:40

96 and so this is um 96 divide

126:44

130ish so this is maybe 27 is%

126:48

Improvement um so uh really interesting

126:52

and that is Flash retention okay we are

126:54

now getting to one of my favorite

126:57

optimizations and it is simultaneously

126:59

the dumbest and the most brilliant

127:02

optimization and it's always a little

127:03

bit surprising to me um anyway so

127:06

basically I mentioned a few minutes ago

127:08

that there are some numbers that are

127:10

nice and some numbers that are ugly so

127:13

64 is a beautiful nice number 128 is

127:17

even nicer 256 is beautiful what makes

127:20

these numbers beautiful is that there

127:21

are many powers of two inside them you

127:23

can divide by two many times and uh

127:26

examples of ugly numbers are like 13 and

127:28

17 and something like that prime numbers

127:30

numbers that are not even and so on and

127:32

so pretty much you always want to use

127:34

nice numbers in all of your code that

127:36

deals with neural networks or Cuda

127:38

because everything in Cuda Works in sort

127:40

of like powers of two and lots of

127:42

kernels are written in terms of powers

127:45

of Two And there are lots of blocks of

127:47

sizes 16 and uh 64 and so on so

127:50

everything is written in those terms and

127:52

you always have special case handling

127:54

for all kinds of uh logic that U when

127:57

your inputs are not made of nice numbers

128:00

so let's see what that looks like

128:01

basically scan your code and look for

128:03

ugly numbers is roughly theistic so

128:06

three times is kind of ugly um I'm not

128:10

100% sure maybe this can be improved but

128:12

this is uh this is ugly and not

128:15

ideal um four times is nice so that's uh

128:20

that's nice

128:22

1024 is very nice that's a power of two

128:25

12 is a little bit suspicious um not too

128:28

many powers of two 768 is great 50, 257

128:32

is a really really ugly number um it's

128:36

first of all it's odd so uh and there's

128:38

no not too many powers of two in there

128:40

so this is a very ugly number and it's

128:43

highly suspicious and then when we

128:45

scroll down all these numbers are nice

128:48

and then here we have mostly nice

128:50

numbers except for 25 so in this

128:53

configuration of gpt2 XL a number of

128:55

heads is 25 uh that's a really ugly

128:57

number that's an odd number and um

129:00

actually this did cause a lot of

129:01

headaches for us recently when we're

129:02

trying to optimize some kernels uh to

129:04

run this fast um and required a bunch of

129:07

special case handling so basically these

129:10

numbers are we have some ugly numbers

129:12

and some of them are easier to fix than

129:13

others and in particular the voap size

129:15

being 50257 that's a very ugly number

129:18

very suspicious and we want to fix it

129:20

now when you when you fix these things

129:23

uh one of the easy ways to do that is

129:24

you basically um increase the number

129:27

until it's the nearest power of two that

129:29

you like so here's a much nicer number

129:32

it's

129:33

50304 and why is that because 50304 can

129:37

be divided by 8 or by 16 or by 32

129:43

64 it can even be divided by 128 I think

129:46

yeah so it's a very nice number um so

129:49

what we're going to do here is the GPT

129:51

config and you see that we initialized B

129:53

cap size to

129:54

50257 Let's override just

129:58

that um element to be

130:01

50304 okay so everything else stays the

130:05

same we're just increasing our

130:06

vocabulary size so we're adding it's

130:09

almost like we're adding fake tokens uh

130:12

so that book up size has powers of two

130:14

inside it now actually what I'm doing

130:16

here by the way is I'm increasing the

130:18

amount of computation that our network

130:19

will be doing if you just count the the

130:21

flops on like do the math of how many

130:23

flops we're doing we're going to be

130:25

doing more flops and we still have to

130:27

think through whether this doesn't break

130:30

anything but if I just run this uh let's

130:33

see what we get uh currently this ran in

130:35

maybe

130:38

96.5 milliseconds per step I'm just kind

130:41

of like eyeballing it and let's see what

130:43

kind of a result we're going to

130:46

get uh while this is compiling let's

130:49

think through whether our code actually

130:51

works okay when we increase the vocap

130:53

size like this let's look at where vocap

130:55

size is actually

130:57

used so we swing up to the inet and we

131:00

see that it's used inside the embedding

131:01

table of course so all the way at the

131:03

bottom of the Transformer and it's used

131:05

at the classifier layer all the way at

131:06

the top of the Transformer so in two

131:08

places and let's take a look and we're

131:11

running at 93 so 93 milliseconds instead

131:14

of

131:15

96.5 so we are seeing a roughly yeah 4%

131:19

Improvement here uh by doing more

131:22

calculations and the reason for this is

131:25

we fixed we've made an ugly number into

131:28

a nice number let's I'm going to come

131:30

into the explanation for that a little

131:32

bit again but for now let's just

131:34

convince ourselves that we're not

131:35

breaking anything when we do this so

131:36

first of all we've made the the wte the

131:39

embedding table for the tokens we've

131:41

made it larger it's almost like we

131:43

introduced more tokens at the bottom and

131:46

these tokens are never used because the

131:48

gbt tokenizer only has tokens up to

131:50

$50,000

131:51

256 and so we'll never index into the

131:55

rows that we've added so we're wasting a

131:57

little bit of space here by creating

131:59

memory that's never going to be accessed

132:01

never going to be used Etc now that's

132:03

not fully correct because this wte

132:06

weight ends up being shared and ends up

132:08

being used in the classifier here at the

132:10

end so what is that doing to the

132:13

classifier right here well what what

132:15

that's doing is we're predicting

132:16

additional Dimensions at the classifier

132:18

now and we're predicting probabilities

132:20

for tokens that will of course never be

132:21

present in the training set um and so

132:25

therefore the network has to learn that

132:27

these probabilities uh have to be driven

132:29

to zero and so the logits that the

132:31

network produces have to drive those

132:33

dimensions of the output to negative

132:35

Infinity but it that's no different from

132:38

all the other tokens that are already in

132:39

our data set um or rather that are not

132:42

in our data set so Shakespeare only

132:45

probably uses let's say a th000 tokens

132:46

out of 50,000 to 57 tokens so most of

132:49

the tokens are already being driven to

132:51

zero probability by the optimization we'

132:53

just introduced a few more tokens now

132:55

that in a similar manner will never be

132:57

used and have to be driven to zero in

132:59

probability um so functionally though

133:02

nothing breaks we're using a bit more

133:05

extra um memory but otherwise this is a

133:08

harmless operation as far as I can tell

133:11

but and we're adding calculation but

133:12

it's running faster and it's running

133:14

faster because as I mentioned in Cuda so

133:17

many kernels use uh block tiles and

133:21

these block towels are usually nice

133:22

numbers uh so powers of two so

133:25

calculations are done in like chunks of

133:26

64 or chunks of 32 and when your um when

133:31

your desired calculation doesn't neatly

133:32

fit into those block tiles um there are

133:36

all kinds of boundary kernels that can

133:38

kick in to like do the last part so

133:42

basically in a lot of kernels they will

133:44

chunk at up your input and they will do

133:46

the nice part first and then they have a

133:47

whole second second phase where they

133:50

come back to any that like uh remains uh

133:54

and then they process the remaining part

133:56

and the kernels for that could be very

133:57

inefficient and so you're basically um

134:00

spinning up all this extra compute and

134:02

is extremely inefficient so you might as

134:04

well pad your inputs and um make it fit

134:07

nicely and usually that empiric lens up

134:10

actually running faster um so this is

134:13

another example of a 4% Improvement that

134:16

we've added and this is something that

134:18

also torch compile did not find for us

134:21

you would hope that torch compile at

134:22

some point could figure an optimization

134:24

like this out uh but for now uh this is

134:27

it and I also have to point out that

134:28

we're using pytorch nightly so that's

134:30

why we're only seeing 4% if you're using

134:33

pytorch 2.3.1 or earlier you would

134:36

actually see something like 30%

134:37

Improvement just from this change from

134:39

changing it to from 50,000 to 57 to

134:43

50304 so again one of my favorite

134:47

examples also of having to understand

134:49

the under the hood and how it all works

134:51

and to know what kinds of things to

134:52

Tinker with to push the performance of

134:54

your code okay so at this point we have

134:56

improved the performance by about 11x

134:58

right because we started at about 1,000

135:00

milliseconds per step and we're now down

135:02

to like 93 milliseconds so that's uh

135:05

quite good and we're uh doing a much

135:08

better job of utilizing our GPU

135:09

resources so I'm going to now turn to

135:12

more algorithmic changes uh and

135:14

improvements to the actual optimization

135:16

itself and what we would like to do is

135:18

we would like to follow the hyper

135:19

parameters that are mentioned in the GP

135:20

G2 or gpt2 gpt3 paper now sadly gpt2 is

135:26

uh doesn't actually say too much it's

135:28

very nice of them that they released the

135:30

model weights and the code but the paper

135:32

itself is extremely vague as to the

135:33

optimization details uh the code itself

135:36

that they released as well the code

135:38

we've been looking at this is just the

135:40

inference code so there's no training

135:41

code here and very few hyp parameters so

135:44

this doesn't also tell us too much so

135:46

for that we have to turn to the gpt3

135:48

paper and um in the depending of the

135:51

gpt3 paper um they have a lot more hyper

135:55

parameters here for us to use and the

135:57

gpt3 paper in general is a lot more

135:59

detailed as to uh all of the you know

136:02

small details that go into the model

136:04

training but gpt3 U models were never

136:07

released so gbt2 we have the weights but

136:10

no details and gpt3 we have lots of

136:11

details but no weights so um but roughly

136:15

speaking gpt2 and gpt3 architectures are

136:17

very very similar and um basically there

136:21

are very few changes the context length

136:23

was expanded from 1024 to 2048 and

136:25

that's kind of like the major change uh

136:28

and some of the hyper parameters around

136:29

the Transformer have changed but

136:31

otherwise they're pretty much the same

136:32

model it's just that gpt3 was trained

136:34

for a lot longer on a bigger data set

136:36

and uh has a lot more thorough

136:38

evaluations uh and the gpt3 model is 175

136:42

billion instead of 1.6 billion um in the

136:46

gpt2 so long story short we're going to

136:49

go to gp3 paper to follow along some the

136:51

hyper parameters so to train all the

136:54

versions of gpt3 we use atom with beta 1

136:56

beta 2 of9 and .95 so let's swing over

137:00

here and make sure that the betas

137:02

parameter which you can see here

137:04

defaults to 0.9 and

137:06

999 is actually set to 0.9 and

137:11

.95 and then the Epsilon parameter uh

137:14

you can see is the default is 1 in8 and

137:17

this is also one in8 let's just uh put

137:19

it in so that works

137:22

expit uh now next up they say we clip

137:25

the gra Global Norm of the gradient at

137:27

1.0 so what this is referring to is that

137:30

once we calculate the gradients right

137:32

after l. backward um we basically have

137:35

the gradients at all the parameter

137:37

tensors and what people like to do is

137:40

basically uh clip them to have some kind

137:42

of a maximum Norm so in pytor this is

137:45

fairly easy to do uh it's one line of

137:48

code here that we have to insert right

137:50

after we calcul Cal the gradients and

137:52

what this utility function is doing is

137:55

um it's calculating the global Norm of

137:58

the parameters so every single par um

138:01

gradient on all the parameters you

138:03

square it and you add it all up and you

138:05

take a big square root of that and

138:07

that's the norm of the parameter V

138:10

Vector basically it's the it's the

138:12

length of it if you if you'd like to

138:14

look at it that way and we are basically

138:16

making sure that its length is no more

138:18

than 1.0 and we're going to clip it

138:21

and the reason that people like to use

138:23

this is that uh sometimes you can get

138:25

unlucky during your optimization maybe

138:27

it's a bad data batch or something like

138:28

that and if you get very unlucky in the

138:31

batch you might get really high loss and

138:33

really high loss could lead to a really

138:35

high gradient and this could basically

138:38

uh shock your model and shock the

138:40

optimization so people like to use a

138:42

gradient Norm clipping uh to prevent the

138:45

model from um basically getting too big

138:49

of shocks in terms of the gradient

138:50

magnet ude and uh the upper bound it in

138:53

this way it's a bit of a hacky solution

138:55

it's about like a patch on top of like

138:57

deeper issues uh but uh people still do

139:00

it fairly frequently now the clip grad

139:03

Norm Returns the norm of the gradient

139:05

which I like to always visualize uh

139:08

because um it is useful information and

139:11

sometimes you can look at the norm of

139:13

the gradient and if it's well behaved

139:15

things are good if it's climbing things

139:17

are bad and they're destabilizing during

139:19

training sometimes you could get a spike

139:21

in the norm and that means there's some

139:22

kind of an issue or an instability so

139:25

the norm here will be a

139:28

norm uh and let's do a uh 4f or

139:33

something like

139:34

that and I believe this is just a float

139:37

and so we should be able to uh print

139:40

that uh so that's Global gradient

139:44

clipping now they go into the details of

139:46

the learning rate uh scheduler so they

139:49

don't just use a fixed learning rate

139:51

like we do here for 3 E4 but there's

139:54

actually basically a cosine DK learning

139:57

rate schedule um it's got a warm-up and

140:00

it's got a cosine DEC to 10% over some

140:04

Horizon

140:06

um and so we're going to implement uh

140:09

this in a second I just like to see Norm

140:11

printed here okay there we go so what

140:14

happened here is the norm is actually

140:16

really high in the beginning 30 or so

140:19

and you see that as we continue training

140:21

it kind of like

140:22

stabilizes um at values below one um and

140:27

this is not that crazy uncommon for the

140:30

norm to be high in the very first few

140:31

stages basically What's Happening Here

140:33

is the model is completely random and so

140:35

there's a ton of learning happening very

140:37

early in the network but that learning

140:39

is kind of like um you know it's mostly

140:41

learning the biases of the output tokens

140:44

and so it's a bit of an unstable time uh

140:46

but the network usually stabilizes in a

140:48

very few iterations so this looks very

140:50

relatively reasonable to me except

140:52

usually I would expect this looks a

140:54

little bit funky that we go from 28 to 6

140:56

to 2 and then to 10 um it's not

140:59

completely insane but it's just kind of

141:01

a little bit

141:02

funky um okay so let's now get to the

141:05

learning rate schuer so the learning

141:07

rate schedule that's used here in gpt3

141:09

is what's called a cosine Decay learning

141:12

schedule with warmup and the way this

141:14

looks is that the learning rate is

141:17

basically starts right at around zero

141:19

linearly rank s up over some amount of

141:21

time and then comes down with this

141:24

cosine sort of form and comes down to

141:27

some kind of a minimum learning rate

141:28

that's up to you so here the minimum

141:30

learning rate is zero but uh here in the

141:33

paper they said that they use cosine

141:35

Decay for learning rate down to 10% of

141:37

its value over the first 260 billion

141:40

tokens and then training continues 10%

141:43

after and there's a linear warmup over

141:46

the first 375 million tokens so that's

141:50

about the learn R so let's now implement

141:52

this uh so I already implemented it here

141:55

and the way this works is let me scroll

141:58

down first here I changed our training

142:00

Loop a little bit so this was a 4i in

142:02

Max steps I just change it to step now

142:04

so that we have the notion of a step is

142:07

a single optimization step in the in the

142:09

for Loop and then here I get the LR for

142:13

this step of the optimization using a

142:15

new function I call get LR and then in

142:18

pytorch to set the learning rate I think

142:20

this is is the way to set the learning

142:21

rate it's a little bit gnarly um because

142:24

you have to basically there's a notion

142:25

of different par parameter groups that

142:27

could exist in the optimizer and so you

142:28

actually have to iterate over them even

142:30

though we currently have a single param

142:32

group only um and you have to set the LR

142:34

in this for Loop kind of style is is my

142:37

impression right now so we have this

142:39

look of LR we set the learning rate and

142:42

then on the bottom I'm also printing it

142:45

uh so that's all the changes I made to

142:47

this Loop and then of course the get LR

142:49

is my scheduler now it's worth pointing

142:51

out that pytorch actually has learning

142:53

rate schedulers and you can use them and

142:55

I believe there's a cosine learning rate

142:57

schedule in pytorch I just don't really

142:59

love using that code because honestly

143:02

it's like five lines of code and I fully

143:06

understand what's happening inside these

143:07

lines so I don't love to use

143:09

abstractions where they're kind of in

143:11

screwable and then I don't know what

143:13

they're doing so personal style so the

143:16

max learning rate here is let's say 3 E4

143:19

but we're going to see that in gpt3

143:22

here they have a table of what the

143:25

maximum learning rate is for every model

143:28

size so um for for this one basically 12

143:34

12 layer 768 gpt3 so the gpt3 small is

143:37

roughly like a GPT

143:40

2124m we see that here they use a

143:42

learning rate of 6 E4 so we could

143:44

actually go higher um in fact we may

143:46

want to try to follow that and just set

143:48

the max LR here at six

143:51

uh then the that's the maximum learning

143:53

rate the minum learning rate is uh 10%

143:55

of that per description in the paper

143:58

some number of steps that we're going to

144:00

warm up over and then the maximum steps

144:02

of the optimization which I now use also

144:05

in the for Loop down here and then you

144:07

can go over this code if you like it's

144:09

not U it's not terribly inside Flor

144:11

interesting I'm just uh modulating based

144:13

on the iteration number which learning

144:16

rate uh there should be so this is the

144:18

warm-up region um

144:21

this is the region after the

144:22

optimization and then this is the region

144:24

sort of in between and this is where I

144:26

calculate the cosine learning rate

144:28

schedule and you can step through this

144:29

in detail if you'd like uh but this is

144:32

basically implementing this

144:33

curve and I ran this already and this is

144:38

what that looks

144:40

like um so when we now run we start at

144:45

um some very low number now note that we

144:47

don't start exactly at zero because that

144:49

would be not useful to update with a

144:50

learning rate of zero that's why there's

144:52

an it+ one so that on the zeroth

144:54

iteration we are not using exactly zero

144:57

we're using something very very low then

144:59

we linearly warm up to maximum learning

145:02

rate which in this case was 34 when I

145:04

ran it but now would be 6 E4 and then it

145:07

starts to decay all the way down to um 3

145:11

E5 which was at the time 10% of the

145:14

original learning rate now one thing we

145:16

are not following exactly is that they

145:18

mentioned that um

145:21

let me see if I can find it

145:23

again we're not exactly following what

145:26

they did

145:28

because uh they mentioned that their

145:30

training Horizon is 300 billion tokens

145:33

and they come down to 10% of the initial

145:35

learning rate of at 260 billion and then

145:37

they train after 260 with 10% so

145:41

basically their Decay time is less than

145:43

the max steps time whereas for us

145:45

they're exactly equal so it's not

145:47

exactly faithful but it's um it's an

145:51

okay um this is okay for us and for our

145:53

purposes right now and um we're just

145:57

going to use this ourselves I don't

145:58

think it makes too too big of a

146:00

difference honestly I should point out

146:02

that what learning rate schedule you use

146:04

is totally up to you there's many

146:05

different types um coign learning rate

146:08

has been popularized a lot by gpt2 and

146:10

gpt3 but people have come up with all

146:12

kinds of uh other learning rate

146:14

schedules um and this is kind of like an

146:16

active area of uh research as to which

146:18

one is the most effective at train these

146:20

networks okay next up the paper talks

146:23

about the gradual batch size increase so

146:26

there's a ramp on the batch size that is

146:29

linear and you start with very small

146:31

batch size and you ramp up to a big

146:32

batch size over time uh we're going to

146:35

actually skip this and we're not going

146:36

to work with it and the reason I don't

146:38

love to use it is that it complicates a

146:41

lot of the arithmetic because you are

146:42

changing the number of tokens that

146:43

you're processing at every single step

146:45

of the optimization and I like to keep

146:47

that math very very simple also my

146:49

understanding is that that this is not

146:50

like a major um Improvement and also my

146:54

understanding is that this is not like

146:55

an algorithmic optimization Improvement

146:57

it's more of a systems and speed

146:59

Improvement and roughly speaking this is

147:02

because uh in the early stages of the

147:05

optimization uh again the model is in a

147:07

very atypical setting and mostly what

147:10

you're learning is that um you're mostly

147:13

learning to ignore the tokens uh that

147:15

don't come up in your training set very

147:16

often you're learning very simple biases

147:19

and and that kind of a thing and so

147:23

every single example that you put

147:24

through your network is basically just

147:26

telling you use these tokens and don't

147:28

use these tokens and so the gradients

147:30

from every single example are actually

147:31

extremely highly correlated they all

147:33

look roughly the same in the in the OR

147:36

original parts of the optimization

147:38

because they're all just telling you

147:39

that these tokens don't appear and these

147:40

tokens do appear and so because the

147:43

gradients are all very similar and

147:45

they're highly correlated then why are

147:46

you doing batch sizes of like Millions

147:49

when if you do a batch size of 32k

147:51

you're basically getting the exact same

147:53

gradient early on in the training and

147:55

then later in the optimization once

147:57

you've learned all the simple stuff

148:00

that's where the actual work starts and

148:01

that's where the gradients become more

148:02

decorrelated per examples and that's

148:04

where they actually offer you sort of

148:07

statistical power in some sense um so

148:10

we're going to skip this just because it

148:12

kind of complicates things and we're

148:14

going to go

148:15

to uh data are sampled without

148:18

replacement during training um so until

148:21

an Epoch boundary is reached so without

148:23

replacement means that they're not

148:24

sampling from some fixed pool and then

148:27

uh take a sequence train on it but then

148:31

also like return the sequence to the

148:32

pool they are exhausting a pool so when

148:34

they draw a sequence it's it's gone

148:37

until the next Epoch of training uh so

148:39

we're already doing that because our

148:41

data loader um iterates over chunks of

148:44

data so there's no replacement they

148:47

don't become eligible to be drawn again

148:49

until the next P so we're basically

148:51

already doing

148:53

that um all models use a weight decay of

148:56

0.1 to provide a small amount of

148:59

regularization so let's Implement a

149:01

weight Decay and you see here that I've

149:03

already kind of made the changes and in

149:04

particular instead of creating the

149:06

optimizer right here um I I'm creating a

149:10

new configure optimizers function inside

149:12

the model and I'm passing in some of the

149:14

hyper parameters instead so let's look

149:17

at the configure optimizers which is

149:18

supposed to return the optimizer

149:24

object okay so it looks complicated but

149:27

it's actually really simple and it's

149:29

just um we're just being very careful

149:31

and there's a few settings here to go

149:32

through the most important thing with

149:34

respect to this line is that you see

149:36

there's a weight Decay parameter here

149:38

and I'm passing that

149:41

into um well I'm passing that into

149:44

something called optim groups that

149:46

eventually ends up going into the addom

149:47

W Optimizer um and the weight Decay

149:50

that's by default used in Addam W here

149:53

is 0.01 so it's it's u 10 times lower

149:57

than what's used in gpt3 paper here um

150:01

so the weight dek basically ends up

150:02

making its way into the ADD and W

150:04

through the optimizer groups now what

150:05

else is going on here in this uh

150:07

function so the two things that are

150:09

happening here that are important is

150:10

that I'm splitting up the parameters

150:12

into those that should be weight decayed

150:14

and those that should not be weight

150:15

decayed so in particular it is common to

150:18

not weight decay uh biases and any other

150:22

sort of one-dimensional tensors so the

150:25

one-dimensional tensors are in the no

150:27

Decay prams and these are also things

150:30

like uh layer Norm scales and biases it

150:33

doesn't really make sense to weight

150:34

Decay those you mostly want to weight

150:36

Decay uh the weights that participate in

150:39

Matrix multiplications and you want to

150:41

potentially weight Decay the

150:43

embeddings and uh We've covered in

150:46

previous video why it makes sense to

150:47

Decay the weights because you can sort

150:49

of the it as a regularization because

150:51

when you're pulling down all the weights

150:53

you're forcing the optimization to use

150:55

more of the weights um and you're not

150:57

allowing any one of the weights

150:59

individually to be way too large um

151:02

you're forcing you're forcing the

151:03

network to kind of like distribute the

151:05

work across more channels because

151:07

there's sort of like a pull of gravity

151:09

on the weights

151:11

themselves um so that's why we are

151:13

separating it in those ways here we're

151:16

only decaying the embeddings and the

151:18

mmal participating ways

151:21

uh we're printing the number of uh

151:22

parameters that we decaying and not most

151:24

of the parameters will be decayed and

151:26

then one more thing that we're doing

151:27

here is I'm doing another optimization

151:31

here and previous add and W did not have

151:34

this option but later parts of pytorch

151:37

introduced it and that's why I'm

151:38

guarding it with an inspect do signature

151:41

which is basically checking if this

151:43

fused um quar is present inside atom W

151:48

and then if it is present I'm going to

151:50

end up using it and passing it in here

151:53

because some earlier versions do not

151:55

have fused equals so here's adamw fused

151:58

equals it did not used to exist and it

152:00

was added later and there's some docks

152:03

here for what's happening and basically

152:05

they say that by default they do not use

152:07

fused because it is relatively new and

152:10

we want to give it sufficient big time

152:12

so by default they don't use fused but

152:13

fused is a lot faster when it is

152:15

available and when you're running on

152:17

Cuda and what that does is in instead of

152:20

iterating in a for Loop over all the

152:22

parameter tensors and updating them that

152:25

would launch a lot of kernels right and

152:27

so a fused just means that it's a um all

152:30

those kernels are fused into a single

152:31

kernel you get rid of a lot of overhead

152:34

and you a single time on all the

152:36

parameters call a uh kernel that updates

152:39

them and so it's just basically a kernel

152:42

Fusion for the atom W update instead of

152:44

iterating over all the

152:47

tensors so that's the configure

152:48

optimizers function that I like to use

152:51

and we can rerun and we're not going to

152:53

see any major differences from what we

152:55

saw before but we are going to see some

152:57

prints uh coming from here so let's just

153:00

take a look at what they look

153:01

like so we see that number of Decay

153:04

tensors is 50 and it's most of the

153:06

parameters and number of non- deay

153:08

tensors is 98 and these are the biases

153:10

and the layer Norm parameters mostly and

153:13

that's there's only 100,000 of those so

153:15

most of it is decayed and then we are

153:18

using the fused implementation of ATM W

153:20

which will be a lot faster so if you

153:22

have it available I would advise you to

153:24

use it I'm not actually 100% sure why

153:26

they don't default to it it seems fairly

153:28

benign and

153:29

harmless and also because we are using

153:31

the fused implementation I think this is

153:34

why we have dropped um notice that the

153:37

running time used to be 93 milliseconds

153:39

per step and we're now down to 90

153:41

milliseconds per step because of using

153:43

the fused atom W Optimizer so in a

153:46

single commit here we are introducing

153:48

fused atom getting improvements on the

153:51

time and we're adding or changing the

153:54

weight Decay but we're only weight

153:56

decaying the two dimensional parameters

153:58

the embeddings and the matrices that

154:00

participate in linear so that is this

154:03

and we can take this out and uh yeah

154:06

that is it for this line one more quick

154:10

note before we continue here I just want

154:11

to point out that the relationship

154:13

between weight Decay learning rate batch

154:15

size the atom parameters beta 1 beta 2

154:18

the Epsilon and so on these are very

154:20

complicated uh mathematical

154:22

relationships in the optimization

154:24

literature and um for the most part I'm

154:27

in this video I'm just trying to copy

154:29

paste the settings that open AI used but

154:31

this is a complicated topic uh quite

154:33

deep and um yeah in this video I just

154:36

want to copy the parameters because it's

154:38

a whole different video to really talk

154:39

about that in detail and give it a

154:41

proper Justice instead of just high

154:42

level

154:43

intuitions uh now the next thing that I

154:45

want to move on to is that uh this

154:48

paragraph here by the way we're going to

154:49

turn back around to when we improve our

154:51

data loader for now I want to swing back

154:54

around

154:56

to this

155:01

table where you will notice that um for

155:04

different models we of course have

155:06

different U hyper parameters for the

155:08

Transformer that dictate the size of the

155:10

Transformer Network we also have a

155:12

different learning rate so we're seeing

155:13

the pattern that the bigger networks are

155:14

trained with slightly lower learning

155:16

rates and we also see this batch size

155:20

where in in the small networks they use

155:22

a smaller batch size and in the bigger

155:23

networks they use a bigger batch size

155:26

now the problem with for us is we can't

155:28

just use 0.5 million batch size because

155:31

uh if I just try to come in here and I

155:33

try to set uh this uh B where is my

155:38

b

155:40

um b

155:44

equals where where do I call the DAT

155:46

okay b equal 16 if I try to set um

155:51

well well we have to be careful it's not

155:52

0.5 million because this is the badge

155:54

size in the number of tokens every

155:56

single one of our rows is24 tokens so

156:00

0.5 E6 1 million divide 1024 this would

156:04

need about a

156:06

488 match size so the problem is I can't

156:09

come in here and set this to 488 uh

156:12

because my GPU would explode um this

156:15

would not fit for sure and so but we

156:18

still want to use this batch size

156:20

because again as I mentioned the batch

156:22

size is correlated with all the other

156:24

optimization hyper parameters and the

156:26

learning rates and so on so we want to

156:28

have a faithful representation of all

156:29

the hyper parameters and therefore we

156:31

need to uh use a bat size of .5 million

156:34

roughly but the question is how do we

156:37

use .5 million if we only have a small

156:39

GPU well for that we need to use what's

156:41

called gradient accumulation uh so we're

156:44

going to turn to that next and it allows

156:46

us to simulate in a Serial way any

156:48

arbitrary batch size that we set and so

156:51

we can do a batch size of .5 million we

156:54

just have to run longer and we have to

156:56

process multiple sequences and basically

156:59

add up all the gradients from them to

157:02

simulate a batch size of .5 million so

157:04

let's turn to that next okay so I

157:05

started the implementation right here

157:07

just by adding these lines of code and

157:09

basically what I did is first I set the

157:12

total batch size that we desire so this

157:14

is exactly .5 million and I used a nice

157:17

number a power of two uh because 2 to

157:19

the 19 is 524 288 so it's roughly .5

157:23

million it's a nice number now our micro

157:26

batch size as we call it now is 16 so

157:29

this is going to be we still have B BYT

157:32

in the SE that go into the Transformer

157:34

and do forward backward but we're not

157:36

going to do an update right we're going

157:38

to do many forward backwards we're going

157:40

to and those gradients are all going to

157:42

plus equals on the parameter gradients

157:44

they're all going to add up so we're

157:46

going to do forward backward grad akum

157:48

steps number of times and then we're

157:50

going to do a single update once all

157:52

that is

157:53

accumulated so in particular our micro

157:55

batch size is just now controlling how

157:58

many tokens how many rows we're

157:59

processing in a single go over a forward

158:02

backward so um here we are doing 16 *

158:06

124 we're doing 16

158:09

384 um tokens per forward backward and

158:14

we are supposed to be doing 2 to the 19

158:17

whoops what am I doing 2 to the

158:20

19 in total so the grat Aon will be

158:26

32 uh so therefore gr AUM here will work

158:28

out to 32 and we have to do 32 forward

158:32

backward um and then a single update now

158:35

we see that we have about 100

158:37

milliseconds for a singer forward

158:38

backward so doing 32 of them will be

158:41

will make every step roughly 3 seconds

158:44

just napkin

158:46

math so that's grum steps but now we

158:48

actually have to Implement that so we're

158:50

going to swing over to our training Loop

158:54

because now this part

158:56

here and this part here the forward and

158:59

the backward we have to now repeat this

159:01

32 times before we do everything else

159:04

that follows so let's uh see how we can

159:06

Implement that so let's come over here

159:09

and actually we do have to load a new

159:10

batch every single time so let me move

159:12

that over here and now this is where we

159:14

have the inner loop so for micro step in

159:18

range graum

159:20

steps we do this and remember that l.

159:24

backward always deposits gradients so

159:26

we're doing inside losta backward

159:27

there's always a plus equals on the

159:29

gradients so in every single L of

159:31

backward gradients will add up on the

159:33

gradient

159:35

tensors um so we lost that backward and

159:38

then we get all the gradients over there

159:41

and then we normalize and everything

159:43

else should just follow um so we're very

159:47

close but actually there's like subtle

159:50

and deep issue here and this is actually

159:52

incorrect so invite I invite you to

159:54

think about why this is not yet

159:56

sufficient um and uh let me fix it then

159:59

okay so I brought back the jupyter

160:01

notebook so we can think about this

160:02

carefully in a simple toy setting and

160:05

see what's happening so let's create a

160:07

very simple neural nut that takes a 16

160:10

Vector of 16 numbers and returns a

160:11

single

160:12

number and then here I'm creating some

160:15

random uh examples X and some targets uh

160:19

y Y and then we are using the mean

160:21

squared loss uh here to calculate the

160:25

loss so basically what this is is four

160:28

individual examples and we're just doing

160:30

Simple regression with the mean squared

160:31

loss over those four

160:34

examples now when we calculate the loss

160:36

and we lost that backward and look at

160:38

the gradient this is the gradient that

160:40

we

160:41

achieve now the loss objective here

160:44

notice that in MSE loss the default for

160:46

the loss function is reduction is mean

160:49

so we're we're calculating the average

160:52

mean loss um the the mean loss here over

160:56

the four examples so this is the exact

160:59

loss objective and this is the average

161:02

the one over four because there are four

161:03

independent examples here and then we

161:06

have the four examples and their mean

161:08

squared error the squared error and then

161:11

this makes it the mean squared error so

161:14

therefore uh we are we calculate the

161:16

squared error and then we normalize it

161:18

to make it the mean over the examples

161:20

and there's four examples here so now

161:22

when we come to the gradient

161:24

accumulation version of it this uh this

161:28

here is the gradient accumulation

161:30

version of it where we have grad acum

161:32

steps of four and I reset the gradient

161:35

we've grum steps of four and now I'm

161:38

evaluating all the examples individually

161:39

instead and calling L that backward on

161:41

them many times and then we're looking

161:43

at the gradient that we achieve from

161:44

that so basically now we forward our

161:47

function calculate the exact same loss

161:49

do a backward and we do that four times

161:52

and when we look at the gradient uh

161:54

you'll notice that the gradients don't

161:57

match so here we uh did a single batch

162:00

of four and here we did uh four gradient

162:03

accumulation steps of batch size one and

162:06

the gradients are not the same and

162:08

basically the the reason that they're

162:09

not the same is exactly because this

162:11

mean squared error gets lost this one

162:14

quarter in this loss gets lost because

162:16

what happens here is the loss of

162:19

objective for every one of the loops is

162:22

just a mean squ error um which in this

162:25

case because there's only a single

162:26

example is just this term here so that

162:28

was the loss in the zeroth eration same

162:30

in the first third and so on and then

162:33

when you do the loss. backward we're

162:35

accumulating gradients and what happens

162:38

is that accumulation in the gradient is

162:40

basically equivalent to doing a sum in

162:43

the

162:45

loss so our loss actually here is this

162:49

without the factor of one quarter

162:51

outside of it so we're missing the

162:54

normalizer and therefore our gradients

162:56

are off and so the way to fix this or

162:58

one of them is basically we can actually

163:00

come here and we can say loss equals

163:02

loss divide

163:04

4 and what happens now is that we're

163:07

introducing we're we're scaling our loss

163:09

we're introducing a one quarter in front

163:11

of all of these

163:14

places so all the individual losses are

163:17

now scaled by one quarter and and then

163:19

when we backward all of these accumulate

163:22

with a sum but now there's a one quarter

163:24

inside every one of these components and

163:26

now our losses will be

163:28

equivalent so when I run this you see

163:32

that the U gradients are now identical

163:35

so long story short with this simple

163:37

example uh when you step through it you

163:39

can see that basically the reason that

163:41

this is not correct is because in the

163:44

same way as here in the MSE loss the

163:46

loss that we're calculating here in the

163:50

model is using a reduction of mean as

163:54

well uh so where's the loss after that

163:57

cross

163:58

entropy and by default the reduction uh

164:01

here in Cross entropy is also I don't

164:03

know why they don't show it but it's the

164:05

mean uh the mean uh loss at all the B

164:08

BYT elements

164:10

right so there's a reduction by mean in

164:13

there and if we're just doing this

164:15

gradient accumulation here we're missing

164:16

that and so the way to fix this is to

164:19

simply compensate for the number of

164:21

gradient accumulation steps and we can

164:23

in the same way divide this loss so in

164:25

particular here the number of steps that

164:26

we're doing is loss equals loss divide

164:31

gradient accumulation steps so even uh

164:33

co-pilot s gets the modification but in

164:36

the same way exactly we are scaling down

164:38

the loss so that when we do loss that

164:40

backward which basically corresponds to

164:42

a sum in the objective we are summing up

164:45

the already

164:46

normalized um loss and and therefore

164:49

when we sum up the losses divided by

164:51

grum steps we are recovering the

164:53

additional normalizer uh and so now

164:56

these two will be now this will be

164:59

equivalent to the original uh sort of

165:01

optimization because the gradient will

165:03

come out the same okay so I had to do a

165:05

few more touch-ups and I launched

165:07

launched the optimization here so in

165:09

particular one thing we want to do

165:10

because we want to print things nicely

165:13

is well first of all we need to create

165:15

like an accumulator over the loss we

165:16

can't just print the loss because we'd

165:18

be printing only the final loss at the

165:20

final micro step so instead we have loss

165:22

ofon which I initialize at zero and then

165:25

I accumulate a uh the loss into it and

165:28

I'm using detach so that um uh I'm

165:31

detaching the tensor uh from the graph

165:35

and I'm just trying to keep track of the

165:36

values so I'm making these Leaf nodes

165:38

when I add them so that's lakum and then

165:42

we're printing that here instead of loss

165:43

and then in addition to that I had to

165:46

account for the grum steps inside the

165:48

tokens processed because now the tokens

165:50

processed per step is B * T * gradient

165:54

accumulation so long story short here we

165:57

have the optimization it looks uh

165:59

reasonable right we're starting at a

166:00

good spot we calculated the grum steps

166:03

to be

166:04

32 and uh we're getting about 3 seconds

166:07

here

166:08

right

166:10

um

166:12

and so this looks pretty good now if

166:14

you'd like to verify that uh your

166:16

optimization and the implementation here

166:18

is correct and your working on a side

166:20

well now because we have the total patch

166:21

size and the gradient accumulation steps

166:24

our setting of B is purely a performance

166:26

optimization kind of setting so if you

166:29

have a big GPU you can actually increase

166:31

this to 32 and you'll probably go a bit

166:33

faster if you have a very small GPU you

166:35

can try eight or four but in any case

166:37

you should be getting the exact same

166:38

optimization and the same answers up to

166:41

like a floating Point error because the

166:43

gradient accumulation kicks in and um

166:46

and can um handle everything serially as

166:48

an

166:49

Neary so uh that's it for gradient

166:51

accumulation I think okay so now is the

166:53

time to bring out the heavy weapons uh

166:56

you've noticed that so far we've only

166:57

been using a single GPU for training but

167:00

actually I am paying for eight gpus here

167:02

and so uh we should be putting all of

167:04

them to work and in particular they are

167:06

going to collaborate and uh you know

167:09

optimize over tokens at the same time

167:12

and communicate so that um uh they're

167:15

all kind of collaborating on the

167:16

optimization for this we are going to be

167:18

using the distributed data parallel from

167:20

pytorch there's also a legacy data

167:22

parallel which I recommend you not use

167:24

and that's kind of like you know Legacy

167:27

distributed data parallel Works in a

167:28

very simple way we have eight gpus so

167:31

we're going to uh launch eight processes

167:35

and each process is going to be assigned

167:36

to GPU and for each process the training

167:40

Loop and everything we've worked on so

167:41

far is going to look pretty much the

167:42

same H GPU as far as it's concerned is

167:45

just working on exactly what we've built

167:47

so far but now Secret L there's eight of

167:49

them and they're all going to be

167:51

processing slightly different parts of

167:52

the data and we're going to add one more

167:56

part where once they all calculate their

167:58

gradients there's one more part where we

168:00

do a average of those

168:03

gradients and so that's how they're

168:05

going to be collaborating on uh the

168:07

computational workload here so to use

168:10

all eight of them we're not going to be

168:12

launching our script anymore with just

168:14

um pytorch train

168:16

gbt2 piy we're going to be running it

168:19

with a special command called torrun in

168:21

pytorch we'll see that in a bit and

168:23

torrun uh when it runs our python script

168:26

we'll actually make sure to run eight

168:28

eight of them in parallel and it creates

168:32

these environmental variables where each

168:34

of these processes can look up which uh

168:37

basically which one of the processes it

168:40

is so for example torron will set rank

168:43

local Rank and World size environmental

168:46

variables and so this is a bad way to

168:48

detect whether uh DDP is running so if

168:51

we're using torch run if DDP is

168:54

running then uh we have to make sure

168:57

that K is available because I don't know

168:58

that you can run this on CPU anymore or

169:01

that that makes sense to do um this is

169:05

some um setup code here the important

169:07

part is that there's a world size which

169:10

for us will be eight that's the total

169:11

number of processes running there's a

169:14

rank which is um each process will

169:17

basically run the ex exact same code at

169:19

the exact same time roughly but all the

169:22

process the only difference between

169:24

these processes is that they all have a

169:26

different dtp rank so the um gpu0 will

169:30

have DDP rank of zero GPU 1 will have uh

169:33

rank of one Etc so otherwise they're all

169:36

running the exact same script it's just

169:38

that DDP rank will be a slightly

169:40

different integer and that is the way

169:42

for us to coordinate that they don't for

169:44

example run on the same data we want to

169:46

we want them to run on different parts

169:47

of the data and so on

169:49

now local rank is something that is only

169:52

used in a multi- node setting we only

169:54

have a single node with ag gpus and so

169:57

local rank is the rank of the GPU on a

170:00

single node so from 0 to seven as an

170:04

example but for us we're mostly going to

170:06

be running on a single box so the things

170:08

we care about are Rank and World size

170:10

this is eight and this will be whatever

170:12

it is depending on the GPU uh that uh

170:15

that this particular instantiation of

170:17

the script runs on

170:19

now here we make sure that according to

170:23

the local rank we are setting the device

170:27

to be Cuda colon and colon indicates

170:30

which GPU to use if there are more than

170:32

one gpus so depending on the local rank

170:36

of this process it's going to use just

170:39

the appropriate GPU so there's no

170:40

collisions on which GPU is being used by

170:42

which

170:43

process and finally there's a Boolean

170:45

variable that I like to create which is

170:47

the DDP rank equ equal Z so the master

170:50

process is arbitrarily process number

170:53

zero and it does a lot of the printing

170:55

logging checkpointing Etc and the other

170:57

processes are thought of mostly as a

170:59

compute processes that are assisting and

171:01

so Master process zero will have some

171:03

additional work to do all the other

171:05

processes will uh will mostly just be

171:06

doing forward

171:08

backwards and if we're not using DDP and

171:10

none of these variables are set we

171:12

revert back to single GPU training so

171:14

that means that we only have rank zero

171:16

the world size is just one uh and and we

171:19

are the master process and we try to

171:21

autodetect the device and this is world

171:24

as

171:25

normal so so far all we've done is we've

171:27

initialized

171:28

DDP and uh in the case where we're

171:31

running with torrun which we'll see in a

171:33

bit there's going to be eight copies

171:35

running in parallel each one of them

171:37

will have a different Rank and now we

171:39

have to make sure that everything

171:41

happens uh correctly afterwards so the

171:44

tricky thing with running multiple

171:45

processes is you always have to imagine

171:48

that there's going to be eight processes

171:50

running in parallel so as you read the

171:52

code now you have to imagine there's

171:54

eight you know eight python interpreters

171:57

running down these lines of code and the

171:59

only difference between them is that

172:01

they have a different DDP rank so they

172:03

all come here they all pick the exact

172:05

same seed they all make all of these

172:08

calculations completely unaware of the

172:10

other copies running roughly speaking

172:12

right so they all make the exact same

172:14

calculations and now we have to adjust

172:16

these calculations to take into account

172:19

that there's actually like a certain

172:21

world size and certain ranks so in

172:24

particular these micro batches and

172:26

sequence lengths these are all just per

172:28

GPU right so now there's going to be num

172:31

processes of them running in parallel so

172:34

we have to adjust this right because the

172:36

grum steps now is going to be total B

172:39

size divide B * T time U DDP R

172:43

size because each um process will will

172:48

do B * T and there's this many of

172:51

them and so in addition to that we we

172:54

want to make sure that this fits nicely

172:56

into total batch size which for us it

172:58

will because 16 * 124 * 8 8 gpus is

173:04

131 uh K and so

173:08

524288 this means that our gratum will

173:10

be four with the current settings right

173:13

so there's going to be 16 * 124 process

173:16

on each GPU and then there's a GP pus so

173:18

we're going to be doing

173:20

131,000 tokens in a single forward

173:23

backward on the 8

173:26

gpus so we want to make sure that this

173:28

fits nicely so that we can derive a nice

173:30

gradient accumulation

173:32

steps and uh yeah let's just adjust the

173:36

comments here times uh DDP World size

173:41

okay so each GPU calculates this now

173:45

this is where we start to get run into

173:46

issues right so we are each process is

173:49

going to come by a print and they're all

173:51

going to print so we're going to have

173:53

eight copies of these prints so one way

173:56

to deal with this is exactly this master

173:58

process variable that we have so if

174:00

Master process then guard this and

174:03

that's just so that we just print this a

174:05

single time because otherwise all the

174:07

processes would have computed the exact

174:08

same variables and there's no need to

174:10

print this eight

174:11

times um before getting into the data

174:14

loader and we're going to have to

174:15

refactor it obviously maybe at this

174:18

point is uh we should do some prints and

174:21

uh just take it out for a spin and exit

174:23

at this point so import

174:26

sis and S start exit and print IM

174:33

GPU um DDP

174:38

rank IM GPU DDP Rank and that um

174:43

print

174:46

by so uh so now let's try to run this

174:49

and just see how this works so let's

174:51

take it for a spin just so we see what

174:52

it looks like so normally we use to

174:54

launch python train gpd2 P like this now

174:57

we're going to run with torch run and

174:59

this is what it looks like so torch run

175:02

Standalone number of processes for

175:04

example is eight for us because we have

175:05

eight gpus uh and then change of2 Pi so

175:09

this is what the command would look like

175:11

and torch run again we'll run eight of

175:13

these so let's just see what happens so

175:16

first

175:18

it gets a little busy so there's a lot

175:20

going on here so first of all there's

175:22

some warnings from distributed and I

175:24

don't actually know that these mean

175:26

anything I think this is just like the

175:28

code is setting up and the processes are

175:29

coming online and we're seeing some

175:31

preliminary failure to collect while the

175:33

processes come up I'm not 100% sure

175:36

about that but we start to then get into

175:39

actual prints

175:41

so all the processes went down and then

175:44

the first print actually comes from

175:46

process 5 uh just by chance and then it

175:50

printed so process 5 basically got here

175:52

first it said I'm process on GPU 5 buy

175:56

and then this these prints come from the

176:00

master

176:01

process so process 5 just finished first

176:04

for whatever reason it just depends on

176:05

how the operating system scheduled the

176:07

processes to run uh then gpu0 ended then

176:10

GPU 3 and two and then uh probably

176:14

process 5 or something like that has uh

176:17

exited and and DDP really doesn't like

176:19

that because we didn't properly dispose

176:21

of uh the multi-gpus um setting and so

176:27

process group has not been destroyed

176:28

before we destruct uh so it really

176:31

doesn't like that and in an actual

176:33

application we would want to call

176:34

destroy process group uh so that we

176:37

clean up DDP properly and so it doesn't

176:40

like that too much and then the rest of

176:41

the gpus finish and that's it so

176:45

basically we can't guarantee when these

176:46

processes are running it's totally

176:48

but they are running in parallel we

176:50

don't want them to be printing um and

176:54

next up let's erase

176:57

this next up we want to make sure that

176:59

when we create data loader light we need

177:01

to now make it aware of this

177:03

multi-process um setting because we

177:06

don't want all the processes to be

177:07

loading the exact same data we want

177:10

every process to get its own chunk of

177:11

data so that they're all working on

177:13

different parts of the data set of

177:14

course so let's adjust that so one

177:17

particular particularly simple and a

177:19

naive way to do this is we have to make

177:21

sure that we pass in the rank and the

177:23

size to the data

177:25

loader and then when we come up here we

177:28

see that we now take Rank and processes

177:29

and we save them now the current

177:32

position will not be zero uh because

177:35

what we want is we want to stride out

177:37

all the processes so one way to do this

177:40

is we basically take S.B times salt. T

177:43

and then multiply it by the process

177:46

rank so proc process rank 0 will start

177:49

at zero but process rank one now starts

177:52

at B * T process rank two is starts at 2

177:55

* B * D Etc so that is the

177:59

initialization now we still they still

178:01

do this identically but now when we

178:04

advance we don't Advance by B * T we

178:06

advance by B * T times number of

178:10

processes right so basically um the

178:14

total number of tokens that we're um

178:16

consuming is B * T * number processes

178:19

and they all go off to a different Rank

178:23

and the position has to advance by the

178:24

entire

178:26

chunk and then here B * T time uh s. num

178:30

processes + one would be to exceed

178:33

number of tokens then we're going to

178:35

Loop and when we Loop we want to of

178:37

course Loop in the exact same way so we

178:39

sort of like reset back uh so this is

178:42

the simplest change that I can uh find

178:45

for kind of a very simple distributed

178:47

data Lo light and um you can notice that

178:50

if process rank is zero and non

178:52

processes is one then uh the whole thing

178:54

will be identical to what we had before

178:56

but now we can have actually multiple

178:58

processes uh running and this should

179:00

work

179:01

fine um so that's the data loader okay

179:05

so next up once they've all initialized

179:07

the data loader they come here and they

179:09

all create a GPT model uh so we create

179:13

eight GPT models on eight processes but

179:15

because the seeds are fixed here they

179:17

all create the same identical model they

179:20

all move it to the device of their Rank

179:22

and they all compile the model and

179:25

because the models are identical there

179:26

are eight identical compilations

179:28

happening in parallel but that's okay

179:31

now none of this uh changes because that

179:33

is on a per step basis and we're

179:34

currently working kind of within step

179:36

because we need to um just uh all the

179:39

all the changes we're making are kind of

179:41

like a within step

179:42

changes now the important thing here is

179:44

when we construct the M model we

179:47

actually have a bit of work to to do

179:48

here get loits is deprecated so uh

179:50

create

179:52

model we need to actually wrap the model

179:55

into the distributed data parallel

179:58

container so um this is how we wrap the

180:01

model into the DDP container and these

180:04

are the docs for DDP and they're quite

180:07

extensive and there's a lot of caveats

180:09

and a lot of things to be careful with

180:10

because everything complexifies times 10

180:12

when multiple processes are involved but

180:15

roughly speaking this device IDs I

180:17

believe has to be passed in now

180:18

unfortunately the docs for what device

180:20

IDs is is is extremely unclear uh so

180:24

when you actually like come here this

180:26

comment for what device IDs is is

180:29

roughly

180:30

nonsensical um but I'm pretty sure it's

180:33

supposed to be the DDP local rank so not

180:35

the DDP rank the local rank uh so this

180:39

is what you pass in here this wraps the

180:41

model and in particular what DDP does

180:43

for you is in a forward pass it actually

180:45

behaves identically so um my

180:48

understanding of it is nothing should be

180:49

changed in the forward pass but in the

180:51

backward pass as you are doing the

180:53

backward pass um in the simpl setting

180:56

once the backp passes over on each

180:59

independent GPU each independent GPU has

181:02

the gradient for all the parameters and

181:05

what DDP does for you is once the

181:06

backward pass is over it will call

181:09

what's called all reduce and it

181:11

basically does an average across all the

181:14

uh ranks of their gradients and and then

181:18

it will deposit that average on every

181:20

single rank so every sing Single rank

181:22

will end up with the average on it and

181:25

so basically that's the communication it

181:27

just synchronizes and averages the

181:28

gradients and that's what DDP offers you

181:31

now DDP actually is a little bit more um

181:34

it is a little bit more involved than

181:35

that because as you are doing the

181:37

backward pass through the layers of the

181:38

Transformer it actually can dispatch

181:41

Communications for the gradient while

181:43

the backward pass is still happening so

181:45

there's overlap of the uh communication

181:47

of the gradient and the synchronization

181:48

of them and uh the backward pass and uh

181:52

this is just more efficient and um uh to

181:55

do it that way so that's what DDP does

181:57

for you um forward is unchanged and

182:00

backward is mostly unchanged and we're

182:02

tacking on this average as we'll see in

182:04

a bit okay so now let's go to the uh

182:08

optimization nothing here changes let's

182:11

go to the optimization here the inner

182:12

loop and think through the

182:13

synchronization of uh these gradients in

182:15

the DP so basically by default what

182:18

happens as I mentioned is when you do l.

182:20

backward here it will do the backward

182:22

pass and then it will synchronize the

182:24

gradients um the problem here is because

182:28

of the gradient accumulation steps Loop

182:30

here we don't actually want to do the

182:33

synchronization after every single La

182:35

step backward because we are just

182:37

depositing gradients and we're doing

182:39

that serially and we just want them

182:40

adding up and we don't want to

182:42

synchronize every single time that would

182:44

be extremely wasteful so basically we

182:46

want to add them up and then on the the

182:48

very last uh it's only on the very last

182:50

step when micro when micro step becomes

182:53

gratak steps minus one only at that last

182:55

step do we want to actually do the

182:58

alberu uh to average up the gradients so

183:02

to do that we come here and um the

183:05

official sanctioned way by the way is to

183:07

do this no sync context manager so

183:10

pytorch says this is a context manager

183:13

to disable gradient synchronization

183:14

across DDP processes So within this

183:17

context gradient will be

183:19

accumulated and basically when you do no

183:21

sync there will be no communication so

183:24

they are telling us to do with DDP no

183:26

sync uh do the gradient accumulation

183:29

accumulate grats and then they are

183:30

asking us to do DDP again with another

183:32

input and that backward and I just

183:35

really don't love this I I just really

183:37

don't like it uh the fact that you have

183:39

to copy paste your code here and use a

183:40

context manager and this is just super

183:42

ugly so when I went to this source code

183:45

here you can see that when you enter

183:48

you simply toggle this variable this

183:51

require backward grat sync and this is

183:54

uh being toggled around and changed and

183:58

this is the variable that basically uh

184:01

if you step through it is being toggled

184:03

to determine if the gradient is going to

184:05

be synchronized so I actually just kind

184:07

of like to use that directly uh so

184:10

instead what I like to do is the

184:13

following right here before the L back

184:15

backward if we are using the DDP then um

184:20

then basically we only want to

184:23

synchronize we only want this variable

184:25

to be true when it is the final

184:28

iteration in all the other iterations

184:31

inside the micr steps we want to be

184:33

false so I just toggle it like this so

184:36

required backward graph sync should only

184:38

turn on when the micro step is the last

184:41

step and so I'm toggling this variable

184:44

directly and I hope that that impacts

184:47

last St backwards

184:48

and this is a naughty thing to do

184:49

because you know they could probably

184:51

change the DDP and this variable will go

184:53

away but for now I believe this this

184:55

works and it allows me to avoid the use

184:57

of context managers and code duplication

185:00

I'm just toggling the variable and then

185:01

Lop backward will not synchronize most

185:03

of the steps and it will synchronize the

185:04

very last step and so once this is over

185:08

uh and we come out every single um rank

185:13

will suddenly magically have the average

185:17

of all the gradients that were stored on

185:20

all the ranks so now we have to think

185:22

through whether that is what we want and

185:24

also um if this suffices and whether how

185:29

it works with the loss and what is loss

185:31

AUM so let's think through through that

185:33

now and the problem I'm getting at is

185:35

that we've averaged the gradients which

185:37

is great but the loss AUM has not been

185:40

impacted yet and the and this is outside

185:43

of the DDP container so that is not

185:45

being averaged um and so here when when

185:47

we are printing Los AUM well presumably

185:49

we're only going to be printing on the

185:51

master process uh rank zero and it's

185:53

just going to be printing the losses

185:55

that it saw on its process but instead

185:57

we want it to print the loss over all

186:00

the processes and the average of that

186:02

loss because we did average of gradients

186:04

so we want the average of loss as well

186:06

so simply here after this uh this is the

186:09

code that I've used in the past um and

186:13

instead of LF we want

186:15

Lum so if

186:18

DDP again then this is a p torch

186:22

distributed I import it where do I

186:24

import

186:26

it uh oh gosh so this file is starting

186:30

to get out of control huh so if uh so

186:33

import torch. distributed as dist

186:36

so dist.

186:38

ALU and we're doing the average on Lum

186:42

and so this lakum tensor exists on all

186:44

the ranks when we call all use of

186:46

average it creates the average of those

186:48

numbers and it deposits that average on

186:51

all the ranks so all the ranks after

186:53

this um call will now contain L AUM uh

186:57

averaged up and so when we print here on

187:00

the master process the L AUM is

187:02

identical in all the other ranks as well

187:04

so here if Master process

187:07

oops we want to print like this okay and

187:10

finally we have to be careful because

187:12

we're not processing even more tokens so

187:15

times DDP World size

187:18

that's number of tokens that we've

187:19

processed up

187:21

above

187:24

and everything else should be fine uh

187:27

the only other thing to be careful with

187:29

is as I mentioned you want to destroy

187:31

the process group so that we are nice to

187:33

nickel and it's not going to uh to uh to

187:35

DDP and it's not going to complain to us

187:38

uh when we exit

187:40

here so that should be it let's try to

187:43

take it for a spin okay so I launched

187:44

the script and it should be uh printing

187:46

here imminently we're now training with

187:48

8 gpus at the same time so the gradient

187:51

accumulation steps is not 32 it is now

187:53

divide 8 and it's just four uh so um

187:58

otherwise this is what the optimization

187:59

now looks like and wow we're going

188:01

really fast so we're processing 1.5

188:04

million tokens uh per second now so

188:09

these are some serious numbers and the

188:11

tiny shakespare data set is so tiny that

188:12

we're just doing like so many Epoch over

188:15

it most likely but this is roughly what

188:17

looks like um one thing that I had to

188:20

fix by the way is that this was model.

188:23

configure optimizers which Now doesn't

188:25

work because model now is a DDP model so

188:27

instead this has to become raw

188:29

model. configure optimizers where raw

188:32

model is something I create here so

188:35

right after I wrap the model into DDP uh

188:38

I have to create the raw model which in

188:40

the case of DDP is a model. module is

188:43

where it stores the raw and then module

188:46

of gpt2 as we have it which contains the

188:49

uh configure optimizers function that we

188:51

want to call so that's one thing that I

188:53

have to fix otherwise this seems to run

188:56

now one thing you'll notice is that when

188:57

you actually compare this run and the

188:59

numbers in it to the just running a

189:01

single GPU you'll notice that this is

189:04

single GPU run with 32 gratum the

189:06

numbers won't exactly match

189:09

up and uh that's kind of a boring reason

189:11

for why that happens uh the reason for

189:13

that is that in the data loader we're

189:15

basically just iterating through batches

189:17

and slightly different way because now

189:18

we're looking for an entire page of data

189:21

and if that page uh for all the gpus if

189:24

that chunk exceeds the number of tokens

189:26

we just Loop and so actually the single

189:29

GPU and the H GPU process will end up um

189:33

resetting in a slightly different Manner

189:35

and so our batches are slightly

189:36

different and so we get slightly

189:38

different numbers but one way to

189:39

convince yourself that this is okay it

189:42

just make the total batch size much

189:43

smaller and the b and a t and then um

189:48

so I think I used uh 4 * 124 * 8 so I

189:52

used 32768 as a total patch size and

189:55

then um so I made sure that the single

189:57

GPU will do eight creting accumulation

190:00

steps and then the multi-gpu and then

190:02

you're reducing the boundary effects of

190:04

the data loader and you'll see that the

190:06

numbers match up so long story short

190:08

we're now going really really fast the

190:10

optimization is mostly consistent with

190:12

gpt2 and three hyper parameters and uh

190:16

we have outgrown our tiny Shakespeare

190:18

file and we want to upgrade it so let's

190:20

move to next to that next so let's now

190:22

take a look at what data sets were used

190:23

by gpt2 and gpt3 so gbt2 used this web

190:27

Text data set that was never released um

190:30

there's an attempt at reproducing it

190:32

called open web text uh so basically

190:34

roughly speaking what they say here in

190:35

the paper is that they scraped all

190:37

outbound links from Reddit and then uh

190:41

with at least three Karma and that was

190:43

kind of like their starting point and

190:44

they collected all the web P all the web

190:45

pages and all the text in them and so

190:48

this was 45 million links and this ended

190:50

up being 40 GB of text so uh so that's

190:54

roughly what gpt2 says about its data

190:57

set so it's basically outbound links

190:58

from Reddit now when we go over to gpt3

191:01

there's a training data set section and

191:03

that's where they start to talk about um

191:05

common coll which is a lot more uh used

191:09

actually I think even gpt2 talked about

191:11

common coll um but basically it's not a

191:14

very high quality data set all by itself

191:16

because it is extremely noisy this is a

191:18

completely random subset of the internet

191:20

and it's much worse than you think so

191:22

people go into Great Lengths to filter

191:24

common craw because there's good stuff

191:26

in it but most of it is just like ad

191:27

spam random tables and numbers and stock

191:30

tickers and uh it's just total mess

191:35

so that's why people like to train on

191:38

these data mixtures that they curate and

191:41

uh are careful with so a large chunk of

191:44

these data mixtures typically will be

191:45

common C like for example 50% of the

191:47

tokens will be comic but then here in

191:50

gpt3 they're also using web text to from

191:52

before so that's Reddit outbound but

191:54

they're also adding for example books

191:56

and they're adding Wikipedia there's

191:58

many other things you can decide to add

192:00

now this data set for gpt3 was also

192:02

never released so today some of the data

192:05

sets that I'm familiar with that are

192:06

quite good and would be representative

192:08

of something along these lines are

192:10

number one the red pajama data set or

192:12

more specifically for example the slim

192:14

pajama subset of the red pajama data set

192:17

which is a cleaned and D duplicated

192:19

version of it and just to give you a

192:21

sense again it's a bunch of common crawl

192:24

um C4 which is also as far as I know

192:27

more common craw but processed

192:28

differently and then we have GitHub

192:30

books archive Wikipedia stack exchange

192:33

these are the kinds of data sets that

192:35

would go into these data mixtures now

192:37

specifically the one that I like that

192:38

came out recently is called Fine web

192:41

data set uh so this is an attempt to

192:43

basically collect really high quality

192:45

common coll data and filter it in this

192:48

case to 15 trillion tokens and then in

192:51

addition to that more recently

192:52

huggingface released this fine web edu

192:55

subset which is 1.3 trillion of

192:58

educational and 5.4 trillion of high

193:01

educational content so basically they're

193:03

trying to filter common C to very high

193:06

quality educational subsets and uh this

193:09

is the one that we will use there's a

193:11

long uh web page here on fine web and

193:14

they go into a ton of detail about how

193:16

they process the data which is really

193:17

fascinating reading by the way and I

193:19

would definitely recommend if you're

193:20

interested into Data mixtures and so on

193:22

and how data gets processed at these

193:24

scales a look at this uh page and more

193:27

specifically we'll be working with the

193:28

fine web edu I think and it's basically

193:32

educational content from the

193:34

internet uh they show that training on

193:36

educational content in in their metrics

193:39

um uh works really really well and we're

193:43

going to use this sample 10 billion

193:46

tokens subsample of it because we're not

193:49

going to be training on trillions of

193:50

tokens uh we're just going to train on

193:52

uh 10 billion sample of the fine web edu

193:56

because empirically in my previous few

193:58

experiments this actually suffices to

194:00

really get close to gpt2 Performance and

194:02

it's um simple enough to work with and

194:04

so let's work with the sample 10 uh BT

194:07

so our goal will be to download it

194:10

process it and make sure that our data

194:12

loader can work with it so let's get to

194:15

that okay so I introduced another um

194:18

file here that will basically download

194:21

Fine web edu from huging face data sets

194:24

it will pre-process and pre- tokenize

194:26

all of the data and it will save data

194:28

shards to a uh folder on um local disk

194:34

and so while this is running uh just

194:38

wanted to briefly mention that you can

194:40

kind of look through the data set viewer

194:41

here just to get a sense of what's in

194:43

here and it's kind of interesting I mean

194:45

it's a it basically looks like it's

194:47

working fairly well like it's talking

194:48

about nuclear energy in France it's

194:51

talking

194:52

about Mexican

194:54

America some mac PJs Etc so actually it

194:58

seems like their filters are working

194:59

pretty well uh the filters here by the

195:01

way were applied automatically using um

195:04

llama 370b I believe and so uh basically

195:08

llms are judging which content is

195:10

educational and that ends up making it

195:11

through the filter uh so that's pretty

195:13

cool now in terms of the script itself

195:16

I'm not going to go through the full

195:17

script because it's not as interesting

195:19

and not as llm Centric but when you run

195:22

this basically number one we're going to

195:24

load the data set uh which this is all

195:26

huging face code running this you're

195:28

going to need to uh pip install data

195:31

sets um so it's downloading the data set

195:35

then it is tokenizing all of the

195:37

documents inside this data set now when

195:39

we tokenize the documents you'll notice

195:42

that um to tokenize a single document uh

195:46

we first

195:47

start the tokens with the end of text

195:49

token and this is a special token in the

195:51

gpt2 tokenizer as you know so

195:54

50256 is the ID of the end of text and

195:57

this is what begins a document even

195:59

though it's called end of text but this

196:01

is uh the first token that begins a

196:03

document then we extend with all of the

196:06

tokens of that document then we create a

196:08

numpy array out of that we make sure

196:11

that all the tokens are between

196:14

oh okay let me debug this

196:17

okay so apologies for that uh it just

196:19

had to do with me using a float division

196:21

in Python it must be integer division so

196:23

that this is an INT and everything is

196:25

nice um okay but basically the

196:28

tokenization here is relatively

196:29

straightforward returns tokens in mp.

196:32

un6 uh we're using .16 to save a little

196:35

bit of space because 2 to the 16us 1 is

196:39

65,000 so the gpt2 max token ID is well

196:43

below that and then here there's a bunch

196:45

of multiprocessing code and it's

196:47

honestly not that exciting so I'm not

196:48

going to step through it but we're

196:50

loading the data set we're tokenizing it

196:52

and we're saving everything to shards

196:55

and the shards are numpy files uh so

196:58

just storing a numpy array and uh which

197:01

is very very similar to torch

197:03

tensors and the first Shard 0000 is a

197:07

Val a validation Shard and all the other

197:09

shards are uh training shards and as I

197:12

mentioned they all have 100 million

197:14

tokens in them exactly um and and that

197:17

just makes it easier to work with as to

197:20

Shard the files because if we just have

197:22

a single massive file sometimes they can

197:24

be hard to work with on the disk and so

197:26

sharting it is just kind of um nicer

197:28

from that

197:30

perspective and uh yeah so we'll just

197:32

let this run this will be probably um

197:36

30ish minutes or so and then we're going

197:38

to come back to actually train on this

197:39

data and we're going to be actually

197:41

doing some legit pre-training in this

197:42

case this is a good data set we're doing

197:45

lots of tokens per second we have 8 gpus

197:48

the code is ready and so we're actually

197:50

going to be doing a serious training run

197:52

so let's get P it back in a bit okay so

197:54

we're back so uh if we LS edu fine web

197:58

we see that there's now 100 charts in it

198:02

um and that makes sense because each

198:03

chart is 100 million tokens so 100

198:06

charts of that is 10 billion tokens in

198:08

total now swinging over to the main file

198:11

I made some adjustments to our data

198:12

loader again and that's because we're

198:14

not running with uh Shakespeare anymore

198:17

we want to use the fine web shards and

198:20

so you'll see some code here that

198:21

additionally basically can load these

198:23

shards uh we load the um un6 numpy file

198:28

we convert it to a torch. long tensor

198:30

which is what a lot of the layers up top

198:32

expect by default and then here we're

198:35

just enumerating all the shards I also

198:38

added a split to data load of light so

198:40

we can uh load the split train but also

198:42

the split Val uh the zero

198:44

split and then we can load the shards

198:47

and then here we also have not just the

198:49

current position now but also the

198:51

current Shard so we have a position

198:53

inside A Shard and then when we uh run

198:55

out of tokens in A Single Shard we first

198:58

Advance The Shard and loop if we need to

199:01

and then we get the tokens and readjust

199:03

the position so this data loader will

199:06

now iterate all the shards as well so I

199:09

Chang that and then the other thing that

199:11

I did while uh the data was processing

199:14

is our train loader now has split train

199:17

of course and down here I set up some I

199:20

set up some numbers

199:21

so we are doing 2 to the

199:24

9 uh tokens per uh per um per step and

199:31

we want to do roughly 10 billion tokens

199:35

um because that's how many unique tokens

199:36

we have so if we did 10 billion tokens

199:39

then divide that by 29 we see that this

199:41

is 1973 steps so that's where that's

199:44

from and then the GPT three paper says

199:47

that they warm up the learning rate over

199:49

375 million tokens so I came here and

199:53

375 E6 tokens divide uh 2 to the

199:57

19 is 715 steps so that's why warm-up

200:01

steps is set to 715 so this will exactly

200:04

match um the warm-up schedule that gpt3

200:07

used and I think 715 by the way is very

200:10

uh mild and this could be made

200:12

significantly more aggressive probably

200:13

even like 100 is good enough um

200:17

but it's okay let's leave it for now so

200:18

that we have the exact hyper parameters

200:20

of gpt3 so I fix that and then um that's

200:25

pretty much it we can we can run so we

200:28

have our script

200:29

here and we can

200:32

launch and actually sorry let me do one

200:34

more

200:38

thing excuse

200:40

me for my GPU I can actually fit more

200:43

batch size and I believe I can fat I can

200:45

fit 60 4 on my GPU as a micro bash size

200:50

so let me try

200:54

that I could be misremembering but that

200:57

means 64 * 124 per GPU and then we have

201:00

a gpus so that means we would not even

201:02

be doing gradient accumulation if this

201:04

fits because uh this just multi

201:06

multiplies out to uh the full total bat

201:09

size so no gradient

201:12

accumulation and that would run pretty

201:14

quickly if that fits

201:26

let's go let's go I mean if this works

201:29

then this is basically a serious

201:31

pre-training run um we're not logging

201:34

we're not evaluating the validation

201:35

split we're not running any evaluations

201:37

yet so it's not we haven't crossed our

201:39

te's and dotted our eyes but uh if we

201:42

let this run for a while we're going to

201:44

actually get a pretty good model and the

201:46

model that might even be on par with or

201:49

better than gpt2 124 M okay so it looks

201:54

like everything is going great we're

201:55

processing 1.5 million tokens per

201:58

second uh everything here looks good

202:03

we're doing 330 milliseconds per

202:06

iteration and we have to do a total

202:09

of uh where are we printing that 1973 so

202:13

19073 times 0.33

202:17

is this many seconds this many minutes

202:20

so this will run for 1.7

202:24

hours uh so one and a half hour run uh

202:28

like this and uh we don't even have to

202:30

use gradient accumulation which is nice

202:31

and you might not have that luxury in

202:33

your GPU in that case just start

202:35

decreasing the batch size until things

202:37

fit but keep it to nice

202:39

numbers um so that's pretty exciting

202:42

we're currently warming up the learning

202:43

rate so you see that it's still very low

202:45

one4 so this will ramp up over the next

202:48

few steps all the way to 6 e

202:50

Nega uh 4

202:53

here very cool so now what I'd like to

202:56

do is uh let's cross the T and do our

202:58

eyes let's evaluate on the validation

203:00

split and let's try to figure out how we

203:02

can run evals how we can do logging how

203:05

we can visualize our losses and all the

203:07

good stuff so let's get to that before

203:09

we actually do the run okay so I've

203:11

adjusted the code so that we're

203:13

evaluating on the validation split so

203:15

creating the Val loader just by passing

203:17

in Split equals Val that will basically

203:19

create a data loader just for the uh

203:21

validation

203:22

Shard um the other thing I did is in the

203:25

data loader I introduced a new function

203:27

reset which is called at init and it

203:29

basically resets the data loader and

203:31

that is very useful because when we come

203:34

to the main training Loop now so this is

203:37

the code that I've added and basically

203:39

every 100th iteration including the

203:41

zeroth iteration we put the model into

203:44

evaluation mode we reset the Val loader

203:47

and then um no gradients involved we're

203:50

going to

203:52

basically accumulate the gradients over

203:54

say 20 steps and then average it all up

203:58

and print out the validation loss and so

204:01

that basically is the exact same logic

204:03

as the training Loop roughly but there's

204:06

no loss that backward it's only

204:07

inference we're just measuring the loss

204:09

we're adding it up everything else

204:11

otherwise applies and is exactly as

204:13

we've seen it before and so this will

204:15

print the validation laws

204:16

um every 100th iteration including on

204:19

the very first

204:20

iteration uh so that's nice that will

204:23

tell us some amount some a little bit

204:25

about how much we're overfitting that

204:27

said like uh we have roughly Infinity

204:29

data so we're mostly expecting our train

204:31

and Val loss to be about the same but

204:33

the other reason I'm kind of interested

204:35

in this is because we can take the GPT

204:36

2124m as openi released it we can

204:39

initialize from it and we can basically

204:41

see what kind of loss it achieves on the

204:43

validation loss as well and that gives

204:45

us kind of an indication as to uh how

204:47

much that model would generalize to 124

204:49

M but it's not an sorry to fine web edu

204:52

validation split that said it's not a

204:55

super fair comparison to gpt2 because it

204:57

was trained on a very different data

204:58

distribution but it's still kind of like

205:00

an interesting data point and in any

205:02

case you would always want to have a

205:03

validation split in a training run like

205:06

this so that you can make sure that you

205:08

are not um overfitting and this is

205:11

especially a concern if we were to make

205:13

more Epoch in our training data um so

205:16

for example right now we're just doing a

205:18

single Epoch but if we get to a point

205:20

where we want to train on 10 epochs or

205:21

something like that we would be really

205:23

careful with maybe we are memorizing

205:26

that data too much if we have a big

205:28

enough model and our validation split

205:30

would be one way to tell whether that is

205:32

happening okay and in addition to that

205:34

if you remember at bottom of our script

205:36

we had all of this orphaned code for

205:37

sampling from way back when so I deleted

205:40

that code and I moved it up um to here

205:43

so once in a while we simply value

205:45

validation

205:46

once in a while we sample we generate

205:49

samples and then uh we do that only

205:52

every 100 steps and we train on every

205:55

single step so that's how I have a

205:56

structure right now and I've been

205:58

running this for 10,000 iterations so

206:00

here are some samples on neration

206:02

1,000

206:05

um hello I'm a language model and I'm

206:07

not able to get more

206:09

creative I'm a language model and

206:10

languages file you're learning about

206:12

here is or is the beginning of a

206:14

computer

206:16

okay so this is all like pretty uh this

206:19

is still a garble uh but we're only at

206:21

ration 1,000 and we've only just barely

206:24

reached maximum learning rate uh so this

206:26

is still learning uh we're about to get

206:28

some more samples coming up in

206:32

1,00 okay

206:35

um okay this is you know the model is

206:38

still is still a young baby okay so uh

206:42

basically all of this sampling code that

206:44

I've put here everything should be

206:45

familiar with to you and came from

206:47

before the only thing that I did is I

206:49

created a generator object in pytorch so

206:52

that I have a direct control over the

206:54

sampling of the random numbers don't

206:56

because I don't want to impact the RNG

206:58

state of the random number generator

207:00

that is the global one used for training

207:02

I want this to be completely outside of

207:04

the training Loop and so I'm using a

207:07

special sampling RNG and then I make

207:09

sure to seed it that every single rank

207:12

has a different seed and then I pass in

207:14

here where we sort of consumer in the

207:17

numbers in multinomial where the

207:18

sampling happens I make sure to pass in

207:20

the generator object there otherwise

207:22

this is identical uh now the other thing

207:25

is um you'll notice that we're running a

207:27

bit slower that's because I actually had

207:29

to disable torch. compile to get this to

207:32

sample and um so we're running a bit

207:34

slower so for some reason it works with

207:36

no torch compile but when I torch

207:37

compile my model I get a really scary

207:39

error from pytorch and I have no idea

207:41

how to resolve it right now so probably

207:43

by the time you see this code released

207:45

or something like that maybe it's fixed

207:47

but for now I'm just going to do end

207:49

false um and I'm going to bring back

207:51

toor compile and you're not going to get

207:54

samples and I I think I'll fix this

207:56

later uh by the way um I will be

207:59

releasing all this code and actually

208:01

I've been very careful about making get

208:03

commits every time we add something and

208:05

so I'm going to release the entire repo

208:07

that starts completely from scratch all

208:09

the way to uh now and after this as well

208:12

and so everything should be exactly

208:13

documented in the git commit history um

208:16

um and so I think that will be nice so

208:19

hopefully by the time you go to GitHub

208:20

uh this is removed and it's working and

208:22

I will have fixed the bug okay so I have

208:24

the optimization running here and it's

208:26

stepping and we're on step 6,000 or so

208:28

so we're about 30% through training now

208:31

while this is training I would like to

208:32

introduce one evaluation that we're

208:34

going to use to supplement the

208:35

validation set and that is the H swag

208:38

eval so hos swag comes from this paper

208:42

back in 2019 so it's a 5-year-old eval

208:44

now and the way H swag works is there is

208:47

basically a sentence completion data set

208:50

so it's a multiple choice for every one

208:52

of these questions we have uh basically

208:54

a shared context like a woman is outside

208:57

with a bucket and a dog the dog is

208:59

running around trying to avoid bath she

209:02

a Rises the bucket off with soap and

209:04

blow dry the dog's head B uses a hose to

209:08

keep it from getting soapy C gets the

209:11

dog wet and it runs away again or D gets

209:14

into a bathtub with the dog

209:16

and so basically the idea is that these

209:19

multiple choice are constructed so that

209:22

one of them is a natural continuation of

209:25

the um sentence and the others are

209:30

not and uh the others might not make

209:32

sense like uses the host to keep it from

209:34

getting soaped that makes no sense and

209:36

so what happens is that models that are

209:38

not trained very well are not able to

209:40

tell these apart but models that have a

209:43

lot of World Knowledge and can tell uh

209:45

which um and can tell a lot about the

209:48

world will be able to create these

209:50

completions and these sentences are

209:52

sourced from activity net and from Wiki

209:55

how and at the bottom of the uh

210:00

paper there's kind of like a cool chart

210:03

of the kinds of domains in Wiki house so

210:05

there's a lot of sentences from

210:07

computers and electronics and Homes and

210:09

Garden and it has kind of a broad

210:11

coverage of the kinds of things you need

210:13

to know about the world in order to find

210:15

the most likely completion and um the

210:19

identity of that of that completion one

210:22

more thing that's kind of interesting

210:23

about H swag is the way it was

210:25

constructed is that the incorrect um

210:28

options are deliberately um

210:32

adversarially sourced so they're not

210:34

just random sentences they're actually

210:37

sentences generated by language models

210:39

and they're generated in such a way that

210:41

language models basically find them

210:42

difficult but humans find them easy and

210:45

so they mentioned that humans have a 95%

210:47

accuracy on this set but at the time the

210:49

state-of-the-art language models had

210:51

only 48% and so at the time this was a

210:54

good Benchmark now you can read the

210:57

details of this paper to to learn more

210:59

um the thing to point out though is that

211:01

this is 5 years ago and since then what

211:03

happened to H swag is that it's been

211:05

totally just uh

211:08

um solved and so now the language models

211:11

here are 96% so basically the 4% the

211:14

last 4% is probably errors in the data

211:16

set or the questions are really really

211:18

hard and so basically this data set is

211:20

kind of crushed with respect to language

211:22

models but back then the best language

211:23

model was only at about 50% uh but this

211:27

is how far things got but still the the

211:30

reason people like H swag and it's not

211:33

used by the way in gpt2 but in gpt3

211:37

there is H swag eval and lots of people

211:39

use H

211:41

swag and so for gpt3 we have results

211:45

here

211:46

that are cited so we know what percent

211:48

accuracies gpt3 um attains at all these

211:51

different model checkpoints for H swag

211:54

eval and the reason people like it is

211:56

because H swag is a smooth eval and it

211:59

is an eval that offers quote unquote

212:01

early signal uh so early signal means

212:04

that even small language models are

212:06

going to start at the random chance of

212:08

25% but they're going to slowly improve

212:11

and you're going to see 25 26 27 Etc and

212:15

uh you can see slow Improvement even

212:17

when the models are very small and it's

212:19

very early so it's smooth it has early

212:23

signal and um it's been around for a

212:26

long time so that's why people kind of

212:28

like this

212:29

eval uh now the way that we're going to

212:32

evaluate this is as

212:34

follows as I mentioned we have a shared

212:37

context and this is kind of like a

212:39

multiple choice task but instead of

212:41

giving the model a multiple choice

212:42

question and asking it for A B C or D uh

212:46

we can't do that because these models

212:47

when they are so small as we are seeing

212:49

here the models can't actually do

212:51

multiple choice they don't understand

212:53

the concept of associating a label to

212:55

one of the options of multiple choice uh

212:58

they don't understand that so we have to

212:59

give it to them in a native form and the

213:01

native form is a token completion so

213:05

here's what we do we construct a batch

213:06

of four rows and uh T tokens whatever

213:10

that t happens to be then the shared

213:13

context that is basically the context

213:15

for the for choices the tokens of that

213:17

are shared across all of the rows and

213:20

then we have the four options so we kind

213:22

of like lay them out and then only one

213:25

of the options is correct in this case

213:26

label three option three and so um this

213:30

is the correct option and option one two

213:32

and for are

213:33

incorrect now these options might be of

213:36

different lengths so what we do is we

213:38

sort of like take the longest length and

213:40

that's the size of the batch B BYT and

213:42

then some of these uh here are going to

213:45

be pded Dimensions so they're going to

213:47

be unused and so we need the tokens we

213:51

need the correct label and we need a

213:53

mask that tells us which tokens are

213:55

active and the mask is then zero for

213:58

these uh padded areas so that's how we

214:01

construct these batches and then in

214:04

order to get the language model to

214:05

predict A B C or D the way this works is

214:08

basically we're just going to look at

214:10

the tokens their probabilities and we're

214:12

going to pick the option that gets the

214:15

lowest or the highest average

214:18

probability for the token so for the

214:22

tokens because that is the most likely

214:25

completion according to the language

214:27

model so we're just going to look at the

214:29

um probabilities here and average them

214:33

up across the options and pick the one

214:35

with the highest probability roughly

214:38

speaking so this is how we're going to

214:40

do H swag

214:42

um and this is I believe also how uh

214:46

gpt3 did it um this is how gpt3 did it

214:50

as far as I know but you should note

214:52

that some of the other evals where you

214:54

might see H swag may not do it this way

214:57

they may do it in a multiple choice

214:58

format where you sort of uh give the the

215:00

context a single time and then the four

215:02

completions and so the model is able to

215:05

see all the four options before it picks

215:07

the best possible option and that's

215:08

actually an easier task for a model

215:11

because you get to see the other options

215:12

when you're picking your choice um but

215:15

unfortunately models at our size can't

215:17

do that only models at a bigger size are

215:20

able to do that and so our models are

215:22

actually slightly handicapped in this

215:23

way that they are not going to see the

215:25

other options they're only going to see

215:27

one option at a time and they just have

215:29

to assign probabilities and the correct

215:31

option has to win out in this metric all

215:34

right so let's now implement this very

215:36

briefly and incorporate it into our

215:38

script okay so what I've done here is

215:40

I've introduced a new file called hell

215:42

swag. py that you can take a look into

215:45

and I'm not going to to step through all

215:46

of it because uh this is not exactly

215:48

like deep code deep code it's kind of

215:51

like a little bit tedious honestly

215:53

because what's happening is I'm

215:54

downloading hsac from GitHub and I'm

215:56

rendering all of its examples and there

215:58

are a total of 10,000 examples I am

216:00

rendering them into this format um and

216:04

so here at the end of this render

216:07

example function you can see that I'm

216:09

returning the

216:11

tokens uh the tokens of this um 4xt

216:16

uh array of Tokens The Mask which tells

216:19

us which parts are the options and

216:21

everything else is zero and the label

216:24

that is the correct label and so that

216:26

allows us to then iterate the examples

216:28

and render them and I have an evaluate

216:30

function here which can load a um gpt2

216:33

from huging face and it runs the eval

216:36

here um and it basically just calculates

216:40

uh just as I described it predicts the

216:42

option that has the lowest or the

216:45

highest prob ility and the way to do

216:47

that actually is we can basically

216:48

evaluate the cross entropy loss so we're

216:51

basically evaluating the loss of

216:53

predicting the next token in a sequence

216:55

and then we're looking at the row that

216:57

has the lowest average loss and that's

217:01

the uh option that we pick as the

217:04

prediction and then we do some stats and

217:06

prints and stuff like that so that is a

217:08

way to evaluate L swag now if you go up

217:11

here I'm showing that for GPT 2124m if

217:14

you run this script you're going to see

217:16

that H swag gets

217:19

29.5% um so that's the performance we

217:22

get here now remember that random Chan

217:23

is 25% so we haven't gone too far and

217:27

gpt2 XL which is the biggest the gpt2

217:31

gets all the way up to 49% roughly so uh

217:34

these are pretty low values considering

217:36

that today's state-ofthe-art is more

217:37

like 95% uh so these are definitely

217:40

older models by now and then there's one

217:42

more thing called Uther harness which is

217:44

a very piece of infrastructure for

217:46

running evals for language models and

217:48

they get slightly different numbers and

217:50

I'm not 100% sure what the discrepancy

217:52

is for these um it could be that they

217:54

actually do the multiple choice uh

217:57

instead of just the completions and that

217:59

could be the um uh the discrepancy but

218:02

I'm not 100% sure about that i' have to

218:04

take a look but for now our script

218:06

reports 2955 and so that is the number

218:08

that we'd like to beat if we are

218:10

training a GPD 2124m from scratch and

218:13

ourselves um

218:16

so now I'm going to go into actually

218:19

incorporating this eval into our main

218:22

training script and um and basically

218:26

because we want to evaluate it in a

218:28

periodic manner so that we can track H

218:30

swag and how it evolves over time and

218:32

see when when and if we cross uh this

218:36

2955 um sort of region so let's now walk

218:41

through some of the changes to train

218:42

gpt2 thatp the first thing I did here is

218:45

I actually made use compile optional

218:47

kind of and I disabled it by default and

218:51

the problem with that is the problem

218:53

with compile is that unfortunately it

218:55

does make our code faster but it

218:56

actually breaks the evaluation code and

218:58

the sampling code it gives me a very

219:00

gnarly message and I don't know why so

219:02

hopefully by the time you get to the

219:04

codebase when I put it up on GitHub uh

219:06

we're going to fix that by then but for

219:07

now I'm running without torch compile

219:09

which is why you see this be a bit

219:11

slower so we're running without torch

219:13

compile I also create cre a log

219:15

directory log where we can place our

219:18

log.txt which will record the train loss

219:22

validation loss and the H swag

219:23

accuracies so a very simple text file

219:25

and we're going to uh open for writing

219:28

so that it sort of starts empty and then

219:30

we're going to append to

219:32

it I created a simple variable that um

219:36

helps tell us when we have a last step

219:39

and then basically periodically inside

219:40

this Loop every 250th iteration or at

219:44

the last step we're going to evaluate

219:46

the validation loss and then every 250th

219:50

iteration um we are going to evaluate H

219:53

swag but only if we are not using

219:56

compile because compile breaks it so I'm

219:59

going to come back to this code for

220:01

evaluating H swag in a second and then

220:04

every 250th iteration as well we're also

220:06

going to sample from the model and so

220:08

you should recognize this as our ancient

220:10

code from way back when we started the

220:12

video and we're just sampling from the

220:13

model

220:15

and then finally here um these are if

220:18

we're not after we validate sample and

220:21

evaluate hell swag we actually do a

220:23

training step here and so this is one

220:26

step of uh training and you should be

220:28

pretty familiar with all of what this

220:30

does and at the end here once we get our

220:32

training laws we write it to the file so

220:35

the only thing that changed that I

220:37

really added is this entire section for

220:38

H swag eval and the way this works is

220:41

I'm trying to get all the gpus to

220:43

collaborate on the H swag and so we're

220:45

iterating all the examples and then each

220:48

process only picks the examples that

220:52

assigned to it so we sort of take I and

220:54

moded by the world size and we have to

220:56

make it equal to rank otherwise we

220:58

continue and then we render an example

221:01

put it on the GPU we get the low jits

221:04

then I create a helper function that

221:05

helps us basically predict the option

221:08

with the lowest loss so this comes here

221:10

the prediction and then if it's correct

221:12

we sort of keep count and then if

221:15

multiple processes were collaborating on

221:17

all this then we need to synchronize

221:18

their stats and so the way one way to do

221:21

that is to package up our statistics

221:23

here into tensors which we can then call

221:26

this. alberon and

221:29

sum and then here we sort of um unwrap

221:33

them from tensors so that we just have

221:35

ins and then here the master process

221:37

will print and log the hellis swag

221:40

accuracy

221:41

so that's kind of the that's kind of it

221:45

and that's what I'm running right here

221:47

so you see this optimization here and uh

221:50

we just had a generation and this is

221:52

Step 10,000 out of about 20,000 right so

221:55

we are halfway done and these are the

221:58

kinds of samples that uh we are getting

221:59

at this stage so let's take a look hello

222:02

I'm a language model so I'd like to use

222:04

it to generate some kinds of output

222:07

hello I'm a language model and I'm a

222:08

developer for a lot of

222:10

companies Al language

222:12

model uh let's see if I can find fun

222:17

one

222:28

um I don't know you can go through this

222:30

yourself but certainly the predictions

222:32

are getting less and less random uh it

222:34

seems like the model is a little bit

222:35

more self-aware and using language uh

222:38

that is a bit

222:39

more uh specific to it being language

222:43

model hello I'm a language model and

222:45

like how the language is used to

222:46

communicate I'm a language model and I'm

222:48

going to be speaking English and German

222:52

okay I don't know so let's just wait

222:53

until this optimization finishes and uh

222:56

we'll see what kind of samples we get

222:57

and we're also going to look at the

222:59

train Val and the hway accuracy and see

223:03

how we're doing with respect to

223:06

gpt2 okay good morning so focusing For a

223:09

Moment On The jupyter Notebook here on

223:11

the right I created a new cell that

223:13

basically allows us to visualize the the

223:15

train Val and Hela and um the hel score

223:19

and you can step through this it

223:21

basically like parses the log file that

223:22

we are writing and um a lot of this is

223:25

just like boring ma plot lip code but

223:28

basically this is what our optimization

223:30

looks like

223:32

so we ran for

223:38

19,731 billion tokens which is whoops oh

223:41

my gosh which is one Epoch of the sample

223:44

10B of webd on the left we have the loss

223:48

and the in blue we have the training

223:50

loss in Orange we have the validation

223:52

loss and red as a horizontal line we

223:55

have the opening IG gpt2 124 M model

223:58

checkpoint when it's just evaluated on

224:00

the validation set of um of this fine

224:04

web edu uh so you can see that we are

224:06

surpassing this orange is below the red

224:09

so we're surpassing the validation set

224:11

of this data set and like I mentioned

224:13

the data set distribution is very

224:15

different from what gpt2 trained on so

224:16

this is not an exactly fair comparison

224:19

but it's a good cross check uh to uh to

224:22

look at now we would ideally like

224:25

something that is withheld and

224:27

comparable and somewhat standard um and

224:30

so for us that is helis swag and so on

224:33

here we see the H swag progress we made

224:35

from 25% all the way here in red we see

224:39

the open gpt2 124 M model in red so it

224:44

achieves this h bag here and the the

224:47

gpt3 model 124 M which was trained on

224:50

300 billion tokens achieves green so

224:54

that's over here so you see that we

224:56

basically surpassed the gbt2 24m uh

225:00

model right here uh which is uh really

225:03

nice now interestingly we were able to

225:07

do so with only training on 10 billion

225:08

tokens while gpt2 was trained on 100

225:11

billion tokens so uh for some reason we

225:14

were able to get away with significantly

225:16

fewer tokens for training there are many

225:18

possibilities to as to why we could

225:21

match or surpass this accuracy um with

225:24

only 10 million training so number one

225:27

um it could be that opening gbt2 was

225:30

trained on a much wider data

225:32

distribution so in particular fine web

225:34

edu is all English it's not multilingual

225:38

and there's not that much math and code

225:40

um and so math and code and multilingual

225:43

could have been stealing capacity from

225:45

the original gpt2 model and um basically

225:50

that could be partially the reason why

225:52

uh this is not working out there's many

225:54

other reasons um so for example the H

225:57

swag eval is fairly old uh maybe 5 years

225:59

or so it is possible that aspects of H

226:02

swag in some way or even identically

226:04

have made it into the training Set uh of

226:07

fine web we don't know for sure but if

226:10

that was the case then we are basically

226:11

looking at the training curve instead of

226:12

the validation curve so long story short

226:15

this is not a perfect eval and there's

226:16

some caveats here uh but at least we

226:19

have some confidence that that we're not

226:20

doing something completely wrong and

226:23

um and uh it's probably the case that

226:26

when people try to create these data

226:27

sets they try to make sure that test

226:29

sets that are very common are not part

226:31

of the training set for example uh when

226:33

hugging face created the fine web BDU

226:35

they use H swag as an eval so I would

226:37

hope that they make sure that they D

226:39

duplicate and that there's no hella swag

226:41

in the training set but we can't be sure

226:45

uh the other thing I wanted to address

226:46

briefly is look at this loss curve this

226:48

looks really this looks really wrong

226:50

here I don't actually know 100% what

226:52

this is and I suspect it's because the

226:55

uh 10 billion sample of fine web edu was

226:58

not properly shuffled um and there's

227:01

some issue here uh with the data that I

227:04

don't fully understand yet and there's

227:06

some weird periodicity to it um and

227:08

because we are in a very lazy way sort

227:10

of serializing all the tokens and just

227:12

iterating all them from scratch without

227:14

doing any permutation or any random

227:16

sampling ourselves I think we're

227:18

inheriting some of the ordering that

227:21

they have in the data set so uh this is

227:24

not ideal but hopefully by the time you

227:26

get to this repo uh some of these things

227:28

by the way will hopefully be fixed and I

227:32

will release this build n GPT repo and

227:35

right now it looks a little ugly and

227:37

preliminary uh so hopefully by the time

227:39

you get here it's nicer but down here

227:41

I'm going to show aada and I'm going to

227:44

talk about about some of the things that

227:45

happened after the video and I expect

227:48

that we will have fixed uh the small

227:50

issue uh but for now basically this

227:52

shows that uh our training is not uh

227:55

completely wrong and it shows that uh

227:58

we're able to surpass the accuracy with

228:00

only 10x the token budget um and

228:03

possibly it could be also that the data

228:05

set may have improved so uh the original

228:08

uh gpt2 data set was web text it's

228:11

possible that not a lot of care and

228:12

attention went into the data set this

228:14

was very early in llms whereas now

228:17

there's a lot more scrutiny on good

228:18

practices around uh D duplication

228:20

filtering uh quality filtering and so on

228:23

and it's possible that the data that

228:24

we're training on is just of higher

228:25

quality per token and that could be

228:27

giving us a boost as well so a number of

228:30

cave has to think about but for now uh

228:32

we're pretty happy with this um and yeah

228:36

now the next thing I was interested in

228:37

is as you see it's a morning now so

228:39

there was an overnight and I wanted to

228:41

basically see how far I could push the

228:43

result so uh to do an overnight run I

228:46

basically did instead of one Epoch which

228:48

took roughly two hours I just did a

228:50

times four so that that would take eight

228:52

hours while I was sleeping and so we did

228:54

four Epoch or roughly 40 billion uh

228:56

tokens of training and I was trying to

228:59

see how far we could get um and so this

229:01

was the only change and I reran the

229:03

script and when I point uh and read the

229:05

log file at uh at the 40b uh this is

229:08

what the curve look

229:10

like okay so to narrate this number one

229:13

we are seeing this issue here here with

229:15

the periodicity through the different

229:17

Epoch and something really weird with

229:19

the fine web edu data set and that is to

229:22

be determined uh but otherwise we are

229:25

seeing that the H swag actually went up

229:27

by a lot and we almost we almost made it

229:31

uh to the GPT 324m accuracy uh up here

229:35

uh but not quite so uh it's too bad that

229:37

I didn't sleep slightly longer um and uh

229:41

I think if this was an uh five Epoch run

229:44

we may have gotten here now one thing to

229:47

point out is that if you're doing multi

229:49

Epoch runs uh we're not actually being

229:51

very careful in our data loader and

229:53

we're not um I this data loader goes

229:56

through the data in exactly the same

229:59

format and exactly the same order and

230:01

this is kind of suboptimal and you would

230:03

want to look into extensions where you

230:05

actually permute the data uh randomly

230:08

you permute the documents around in

230:10

Every Single Shard on every single new

230:12

Epoch um and po even permute the

230:16

shards and that would go a long way into

230:18

decreasing the pricity and it's also

230:20

better for the optimization so that

230:22

you're not seeing things ident in the

230:23

identical format and you're introducing

230:25

some of the some uh Randomness in how

230:28

the documents follow each other because

230:29

you have to remember that in every

230:31

single row these documents follow each

230:33

other and then there's the end of text

230:34

token and then the next document so the

230:36

documents are currently glued together

230:39

in the exact same identical manner but

230:41

we actually want to break break up the

230:43

documents and shuffle them around

230:45

because the order of the documents

230:46

shouldn't matter and they shouldn't um

230:49

basically we want to break up that

230:50

dependence because it's a kind of a

230:51

spous correlation and so our data lad is

230:54

not currently doing that and that's one

230:56

Improvement uh you could think of

230:58

making um the other thing to point out

231:01

is we're almost matching gpt3 accuracy

231:03

with only 40 billion tokens gpt3 trained

231:06

on 300 billion tokens so again we're

231:08

seeing about a 10x um Improvement here

231:11

with respect to learning efficiency uh

231:14

the other thing I wanted to and I don't

231:16

actually know exactly what to attribute

231:18

this to other than some of the things

231:19

that I already mentioned previously for

231:21

the previous run uh the other thing I

231:23

wanted to briefly mention is uh the max

231:26

LR here I saw some people already play

231:29

with this a little bit in a previous

231:31

related repository um and it turns out

231:33

that you can actually almost like three

231:35

xas so it's possible that the maximum

231:37

learning rate can be a lot higher and

231:39

for some reason the gpt3 hyper

231:40

parameters that we are inheriting are

231:42

actually extremely conservative and you

231:44

can actually get away with a Higher

231:45

Learning rate and it would train faster

231:47

so a lot of these hyper parameters um

231:50

are quite tunable and feel free to play

231:52

with them and they're probably not set

231:54

precisely correctly and um it's possible

231:59

that you can get away with doing this

232:01

basically and if you wanted to exactly

232:03

be faithful to gpt3 you would also want

232:07

to make the following difference you'd

232:10

want to come here and the sequence

232:11

length of gpt3 is 2x it's 20 48 instead

232:15

of 1,24 so you would come here change

232:17

this to 248 for T and then if you want

232:20

the exact same number of tokens uh half

232:22

a million per iteration or per step you

232:25

want to then decrease this to 32 so they

232:28

still multiply to half a mil so that

232:31

would give your model sequence length

232:33

equal to that of gpt3 and in that case

232:36

basically the

232:37

um the models would be roughly identical

232:40

as far as I'm as far as I'm aware

232:42

because again gpt2 and gpt3 are very

232:44

very similar models now we can also look

232:47

at some of the samples here from the

232:48

model that was trained overnight so this

232:51

is

232:52

the optimization and you see that here

232:55

we stepped all the way to

232:57

76290 also or so and these are the hos

233:02

mag we achieved was 33.2 4 and these are

233:06

some of the samples from the model and

233:08

you can see that if you read through

233:10

this and pause the video briefly you can

233:11

see that they are a lot more coherent uh

233:14

so

233:15

um and they're actually addressing the

233:17

fact that it's a language model almost

233:21

so uh hello I'm a language model and I

233:24

try to be as accurate as

233:27

possible um I'm a language model not a

233:29

programming

233:31

language I know how to communicate uh I

233:34

use

233:35

Python

233:37

um I don't know if you pause this and

233:40

look at it and then compare it to the

233:41

one to the model that was only trained

233:43

for 10 billion uh you will see that

233:45

these are a lot more coherent and you

233:47

can play with this uh

233:48

yourself one more thing I added to The

233:50

Code by the way is this chunk of code

233:52

here so basically right after we

233:54

evaluate the validation loss if we are

233:56

the master process in addition to

233:58

logging the validation loss every 5,000

234:01

steps we're also going to save the

234:02

checkpoint which is really just the

234:04

state dictionary of the model and so

234:07

checkpointing is nice just because uh

234:09

you can save the model and later you can

234:11

uh use it in some way if you wanted to

234:13

resume the optimiz ation then in

234:15

addition to saving the model we have to

234:17

also save the optimizer State dict

234:20

because remember that the optimizer has

234:21

a few additional buffers because of adom

234:24

so it's got the m and V and uh you need

234:28

to also resume the optimizer properly

234:30

you have to be careful with your RNG

234:31

seeds uh random number generators and so

234:33

on so if you wanted to exactly be able

234:35

to resume optimization you have to think

234:37

through the state of the of the training

234:40

process but if you just want to save the

234:41

model this is how you would do it and

234:43

one one nice reason why you might want

234:45

to do this is because you may want to

234:47

evaluate the model a lot more carefully

234:50

so here we are only kind of like winging

234:52

the hell swag eval but you may want to

234:54

use something um nicer like for example

234:57

the Luther uh Luther evaluation hardness

235:01

evaluation hardness hardness um so this

235:06

is a way to also evaluate language

235:08

models and um so it's possible that um

235:13

you may want to use basically different

235:15

infrastructure to more thoroughly

235:17

evaluate the models on different um

235:20

evaluations and compare it to the

235:21

opening gbt2 model on many other um

235:25

tasks like for example that involve math

235:26

code or different languages and so on so

235:29

this is a nice functionality to have as

235:30

well

235:32

um and then the other thing I wanted to

235:34

mention is that everything we've built

235:36

here this is only the pre-training step

235:39

so um the GPT here is a it dreams

235:42

documents it just predicts the next to

235:44

you can't talk to it like you can talk

235:46

to chat GPT uh chat GPT if you wanted to

235:49

talk to the model we have to fine-tune

235:51

it into the chat format and it's not

235:54

actually like that complicated if you're

235:55

looking at supervised fine-tuning or sft

235:58

really what that means is we're just

235:59

swapping out a data set into a data set

236:01

that is a lot more conversational and

236:03

there's a user assistant user assistant

236:04

kind of structure and we just fine-tune

236:06

on it and then we um we basically fill

236:09

in the user tokens and we sample the

236:11

assistant tokens it's not a lot more

236:13

deeper than that uh but basically we

236:15

swap out the data set and continue

236:17

training uh but for now we're going to

236:19

stop at uh pre-training one more thing

236:21

that I wanted to briefly show you is

236:23

that of course what we've built up today

236:25

was building towards nanog GPT which is

236:27

this repository from earlier uh but also

236:30

there's actually another nanog GPT

236:32

implementation and it's hiding in a more

236:34

recent project that I've been working on

236:36

called llm Doc and lm. C is a pure Cuda

236:41

implementation of gpt2 or gpt3 training

236:44

and it just directly uses uh Cuda and is

236:47

written as Cuda now the nanog gbt here

236:51

acts as reference code in pytorch to the

236:53

C implementation so we're trying to

236:55

exactly match up the two but we're

236:57

hoping that the C Cuda is faster and of

236:59

course currently that seems to be the

237:01

case um because it is a direct optimized

237:04

implementation so train gpt2 Pi in LL

237:06

M.C is basically the nanog GPT and when

237:10

you scroll through this file you'll find

237:12

a lot of things that very much look like

237:16

um things that we've built up in this

237:19

lecture and then when you look at train

237:21

gpt2 docu uh this is the C Cuda

237:25

implementation so there's a lot of MPI

237:27

nickel GPU Cuda

237:30

cc++ and you have to be familiar with

237:32

that but uh um when this is built up we

237:37

can actually run the two side by side

237:39

and they're going to produce the exact

237:40

same results but lm. C actually runs

237:43

faster so let's see that so on the left

237:45

I have pytorch a nanog GPT looking thing

237:49

on the right I have the llmc call and

237:52

here I'm going to launch the

237:54

two both of these are going to be

237:55

running on a single GPU and here I'm

237:57

putting the lm. C on GPU 1 and this one

238:00

will grab uh gpu0 by default and

238:05

then we can see here that lm. c

238:08

compiled and then allocate space and

238:11

it's

238:12

stepping so

238:15

basically uh meanwhile P torch is still

238:17

compiling because torch compile is a bit

238:19

slower here than the lm. C nbcc Cuda

238:24

compile and so this program has already

238:26

started running and uh we're still

238:28

waiting here for torch compile now of

238:30

course uh this is a very specific

238:33

implementation to gpt2 and 3 a pytorch

238:35

is a very general neural network

238:37

framework so they're not exactly

238:38

comparable but if you're only interested

238:39

in training gpt2 and 3 lm. C is very

238:43

fast it takes less space it's faster to

238:46

start and it's faster per

238:49

step and so P started to Stepping here

238:53

and as you can see we're running at

238:54

about 223,000 tokens per second here and

238:57

about 185,000 tokens per second here um

239:03

so quite a bit slower but I don't have

239:05

full confidence that I exactly squeezed

239:08

out all the juice from the pytorch

239:09

implementation but the important thing

239:11

here is notice that if I Aline up the

239:14

steps you will see that the losses and

239:16

Norms that are printed between these two

239:18

are

239:19

identical so on the left we have the pie

239:21

torch and on the right this C

239:24

implementation and they're the same

239:25

except this one runs faster uh so that's

239:28

kind of I wanted to show you also

239:30

briefly lm. C and this is a parallel

239:33

implementation and it's also something

239:35

that you may want to uh play with or

239:37

look at and um it's kind of interesting

239:39

okay so at this point I should probably

239:40

start wrapping up the video because I

239:42

think it's getting way longer than I

239:44

anticipated uh but we did Cover a lot of

239:46

ground and we built everything from

239:48

scratch so as a brief summary we were

239:50

looking at the gpt2 and GPT 3

239:55

papers we were looking at how you set up

239:57

these training runs uh and all the

239:59

considerations involved we wrote

240:01

everything from scratch and then we saw

240:03

that over the duration of either a

240:04

2-hour training run or an overnight run

240:07

we can actually match the 124 million

240:09

parameter checkpoints of gbt2 and gpt3

240:12

uh to a very large extent

240:14

um in principle the code that we wrote

240:16

would be able to train even bigger

240:18

models if you have the patients or the

240:19

Computing resources uh and so you could

240:21

potentially think about training some of

240:23

the bigger checkpoints as well um there

240:26

are a few remaining issues to address

240:28

what's happening with the loss here

240:30

which I suspect has to do with the fine

240:31

web edu data sampling uh why can't we

240:34

turn on Torch compile uh it currently

240:36

breaks generation and H swag what's up

240:39

with that in the data loader we should

240:41

probably be permuting our data when we

240:43

reach boundaries so there's a few more

240:45

issues like that and I expect to be

240:47

documenting some of those over time in

240:49

the uh build n GPT repository here which

240:53

I'm going to be releasing with this

240:55

video if you have any questions or like

240:57

to talk about anything that we covered

240:59

please go to discussions tab uh so we

241:02

can talk here uh or please go to issues

241:04

or pull request pull requests um

241:07

depending on what you'd like to

241:08

contribute or also have a look at the uh

241:11

Zero to Hero Discord and uh I'm going to

241:14

be hanging out here on N GPT

241:17

um otherwise for now I'm pretty happy

241:20

about where we got um and I hope you

241:23

enjoyed the video and I will see you

241:25

later

Interactive Summary

This video, part of the "Zero to Hero" series, details the reproduction of OpenAI's GPT-2 124 million parameter model in PyTorch, departing from the original TensorFlow implementation. It covers the model's architecture, including its decoder-only Transformer structure and the use of GELU nonlinearity in MLP blocks. The video walks through initializing the model with specific weight tying and residual scaling, then delves into training from scratch. Significant emphasis is placed on performance optimizations such as using TF32, BFloat16 with torch.autocast, torch.compile for kernel fusion, Flash Attention, and padding the vocabulary size for GPU efficiency. Distributed training techniques like gradient accumulation and Distributed Data Parallel (DDP) across multiple GPUs are also implemented. The training progresses from a small "Tiny Shakespeare" dataset to a larger "FineWebEdu" dataset, with evaluation on a validation split and the HellaSwag benchmark. The speaker demonstrates that their reproduced model surpasses the original GPT-2 124M's HellaSwag accuracy using less training data, attributing this to potentially better data quality or architectural refinements, while also briefly comparing its performance to a pure CUDA implementation.

Suggested questions

14 ready-made prompts