File size: 144,329 Bytes
ef48c9a
 
 
 
249df7b
ef48c9a
 
 
 
249df7b
ef48c9a
 
 
c9b67bf
 
 
 
 
ef48c9a
 
 
b702eb0
 
 
 
 
 
 
ef48c9a
 
 
 
c9b67bf
ef48c9a
 
 
 
 
 
 
 
c9b67bf
 
ef48c9a
 
 
 
 
 
249df7b
45b77cb
 
c9b67bf
b702eb0
 
 
c9b67bf
 
 
 
 
249df7b
c9b67bf
 
 
b702eb0
 
 
 
45b77cb
b702eb0
 
 
 
 
 
 
 
260098f
c9b67bf
 
 
 
 
249df7b
c9b67bf
 
 
45b77cb
b702eb0
 
45b77cb
b702eb0
 
 
 
 
 
 
 
260098f
 
45b77cb
b702eb0
 
c9b67bf
 
 
 
 
249df7b
c9b67bf
 
 
45b77cb
b702eb0
 
 
c9b67bf
 
 
 
 
 
 
249df7b
c9b67bf
 
 
45b77cb
b702eb0
 
 
45b77cb
b702eb0
 
 
45b77cb
b702eb0
c9b67bf
 
 
 
 
249df7b
c9b67bf
 
 
45b77cb
b702eb0
45b77cb
b702eb0
 
 
 
 
45b77cb
b702eb0
 
 
 
 
45b77cb
b702eb0
c9b67bf
 
 
 
 
 
45b77cb
b702eb0
 
 
 
45b77cb
b702eb0
260098f
c9b67bf
 
 
 
 
249df7b
c9b67bf
 
 
45b77cb
b702eb0
 
45b77cb
b702eb0
 
 
45b77cb
260098f
c9b67bf
 
 
 
 
 
 
45b77cb
 
 
b702eb0
 
 
 
c9b67bf
 
 
 
 
249df7b
c9b67bf
 
 
45b77cb
b702eb0
 
45b77cb
b702eb0
 
 
 
 
 
45b77cb
b702eb0
 
 
45b77cb
c9b67bf
260098f
 
c9b67bf
 
 
 
 
249df7b
c9b67bf
 
 
b702eb0
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
b702eb0
 
 
c9b67bf
 
 
b702eb0
c9b67bf
 
 
b702eb0
 
 
 
c9b67bf
b702eb0
 
c9b67bf
b702eb0
c9b67bf
b702eb0
 
 
c9b67bf
b702eb0
 
 
 
 
 
c9b67bf
 
 
 
 
249df7b
c9b67bf
 
 
45b77cb
b702eb0
 
 
45b77cb
b702eb0
45b77cb
 
 
 
249df7b
45b77cb
 
 
 
b702eb0
 
 
 
c9b67bf
 
 
45b77cb
 
 
 
 
249df7b
45b77cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b702eb0
45b77cb
 
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
45b77cb
 
 
 
 
 
 
b702eb0
45b77cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b702eb0
45b77cb
 
 
 
 
 
 
 
 
 
 
 
 
b702eb0
45b77cb
b702eb0
45b77cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249df7b
ef48c9a
 
c9b67bf
 
 
 
ef48c9a
 
 
 
 
249df7b
ef48c9a
 
 
c9b67bf
 
 
 
 
 
ef48c9a
 
 
 
 
249df7b
ef48c9a
 
 
 
 
 
 
 
 
 
c9b67bf
ef48c9a
 
 
 
 
249df7b
ef48c9a
 
 
 
c9b67bf
45b77cb
b702eb0
 
 
45b77cb
b702eb0
 
 
45b77cb
 
 
 
 
249df7b
45b77cb
 
 
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
 
45b77cb
 
 
 
 
 
 
c9b67bf
 
 
 
45b77cb
c9b67bf
45b77cb
 
 
c9b67bf
45b77cb
 
 
 
 
 
 
 
 
249df7b
45b77cb
 
 
 
b702eb0
 
 
c9b67bf
 
 
 
 
 
 
b702eb0
 
c9b67bf
 
ef48c9a
 
 
 
 
249df7b
45b77cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
45b77cb
 
 
 
 
 
 
b702eb0
c9b67bf
 
45b77cb
 
 
 
b702eb0
 
 
c9b67bf
45b77cb
b702eb0
c9b67bf
 
45b77cb
 
 
 
 
 
 
 
 
 
 
 
249df7b
ef48c9a
 
 
 
 
 
 
 
 
 
 
 
249df7b
ef48c9a
 
b702eb0
ef48c9a
b702eb0
 
 
45b77cb
b702eb0
 
 
 
 
 
 
 
 
45b77cb
 
 
 
 
249df7b
45b77cb
 
 
b702eb0
45b77cb
 
 
b702eb0
45b77cb
c9b67bf
45b77cb
 
 
 
 
 
 
 
 
 
 
b702eb0
45b77cb
c9b67bf
45b77cb
 
b702eb0
c9b67bf
45b77cb
c9b67bf
45b77cb
 
 
 
 
ef48c9a
 
758e46c
 
249df7b
758e46c
 
b702eb0
758e46c
c9b67bf
 
 
758e46c
b702eb0
 
 
758e46c
b702eb0
260098f
 
 
758e46c
b702eb0
 
 
 
 
758e46c
 
b702eb0
 
c9b67bf
 
758e46c
b702eb0
260098f
 
 
b702eb0
 
 
758e46c
b702eb0
 
758e46c
 
b702eb0
758e46c
 
 
 
 
249df7b
758e46c
 
 
b702eb0
758e46c
 
 
 
 
 
 
 
 
 
b702eb0
 
758e46c
 
 
 
b702eb0
758e46c
 
 
 
 
c9b67bf
758e46c
 
b702eb0
 
758e46c
 
 
 
 
 
 
 
 
 
c9b67bf
758e46c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
758e46c
 
 
 
 
 
c9b67bf
758e46c
c9b67bf
 
758e46c
b702eb0
758e46c
b702eb0
c9b67bf
758e46c
 
c9b67bf
 
758e46c
b702eb0
 
758e46c
b702eb0
758e46c
 
 
 
c9b67bf
758e46c
 
b702eb0
758e46c
b702eb0
758e46c
c9b67bf
758e46c
c9b67bf
758e46c
 
 
 
 
 
 
b702eb0
 
249df7b
b702eb0
 
 
 
 
 
 
 
c9b67bf
 
 
b702eb0
 
 
 
 
 
 
 
 
 
 
 
 
260098f
c9b67bf
 
 
 
 
b702eb0
 
ef48c9a
 
 
249df7b
ef48c9a
 
 
b702eb0
 
 
 
 
 
 
c9b67bf
b702eb0
 
 
 
 
 
c9b67bf
b702eb0
 
 
 
 
 
 
 
c9b67bf
b702eb0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
 
 
b702eb0
 
 
 
 
 
 
 
 
c9b67bf
b702eb0
 
 
 
c9b67bf
b702eb0
c9b67bf
b702eb0
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
b702eb0
 
 
 
c9b67bf
 
b702eb0
c9b67bf
b702eb0
 
c9b67bf
 
 
 
 
b702eb0
 
 
 
 
 
 
c9b67bf
 
249df7b
c9b67bf
 
 
 
 
 
 
 
 
 
b702eb0
 
 
249df7b
b702eb0
 
 
 
 
 
ef48c9a
 
 
b702eb0
ef48c9a
 
 
 
 
 
 
249df7b
ef48c9a
 
b702eb0
ef48c9a
b702eb0
 
 
 
ef48c9a
 
 
 
 
 
 
249df7b
ef48c9a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249df7b
260098f
 
 
 
 
c9b67bf
 
 
 
 
 
 
 
 
 
 
 
260098f
 
 
 
 
249df7b
260098f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
260098f
c9b67bf
 
 
 
 
 
 
260098f
 
c9b67bf
 
 
 
 
260098f
 
 
 
 
 
249df7b
ef48c9a
 
b702eb0
ef48c9a
 
 
b702eb0
 
 
 
c9b67bf
 
 
ef48c9a
c9b67bf
 
 
45b77cb
b702eb0
 
45b77cb
 
 
 
 
249df7b
45b77cb
 
 
 
 
 
 
 
 
 
 
 
 
 
b702eb0
45b77cb
 
 
 
c9b67bf
 
45b77cb
 
 
 
ef48c9a
 
c9b67bf
 
249df7b
c9b67bf
 
 
 
 
ef48c9a
 
 
249df7b
ef48c9a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
 
249df7b
c9b67bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ef48c9a
 
 
249df7b
ef48c9a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
 
ef48c9a
 
c9b67bf
ef48c9a
 
 
 
 
 
 
 
c9b67bf
ef48c9a
 
c9b67bf
 
 
ef48c9a
 
 
 
 
 
 
 
 
249df7b
ef48c9a
 
b702eb0
 
 
ef48c9a
 
 
 
249df7b
ef48c9a
 
b702eb0
 
 
ef48c9a
b702eb0
 
 
 
 
 
 
 
ef48c9a
b702eb0
 
45b77cb
c9b67bf
 
 
45b77cb
 
 
 
 
249df7b
45b77cb
 
 
 
 
 
 
c9b67bf
b702eb0
45b77cb
c9b67bf
 
45b77cb
 
 
c9b67bf
 
45b77cb
 
 
 
 
 
 
ef48c9a
 
 
 
 
249df7b
ef48c9a
 
 
 
 
 
 
 
 
 
c9b67bf
ef48c9a
c9b67bf
 
ef48c9a
 
 
b702eb0
ef48c9a
 
 
 
 
 
c9b67bf
 
249df7b
c9b67bf
 
 
 
 
 
 
ef48c9a
 
 
249df7b
ef48c9a
 
 
c9b67bf
 
 
 
 
 
ef48c9a
 
 
 
249df7b
ef48c9a
 
b702eb0
ef48c9a
 
b702eb0
 
ef48c9a
b702eb0
ef48c9a
b702eb0
 
ef48c9a
c9b67bf
 
 
 
 
 
 
ef48c9a
 
 
 
 
249df7b
ef48c9a
 
 
c9b67bf
ef48c9a
c9b67bf
 
 
 
ef48c9a
c9b67bf
 
ef48c9a
 
 
c9b67bf
ef48c9a
 
 
 
 
 
 
 
249df7b
45b77cb
 
c9b67bf
45b77cb
b702eb0
 
 
 
45b77cb
 
 
 
 
249df7b
45b77cb
 
 
c9b67bf
45b77cb
 
c9b67bf
45b77cb
 
 
c9b67bf
45b77cb
 
c9b67bf
45b77cb
 
 
 
c9b67bf
45b77cb
 
 
 
 
 
 
 
 
 
249df7b
ef48c9a
 
c9b67bf
b702eb0
 
 
c9b67bf
 
 
b702eb0
 
 
 
 
c9b67bf
b702eb0
c9b67bf
 
260098f
b702eb0
 
 
c9b67bf
 
 
b702eb0
 
 
 
ef48c9a
 
 
 
 
249df7b
45b77cb
 
 
 
 
 
c9b67bf
45b77cb
c9b67bf
45b77cb
c9b67bf
45b77cb
 
 
 
 
 
 
 
 
 
 
 
 
 
b702eb0
 
 
c9b67bf
45b77cb
c9b67bf
45b77cb
c9b67bf
45b77cb
 
 
 
 
 
 
 
 
 
249df7b
ef48c9a
 
 
 
 
c9b67bf
ef48c9a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
ef48c9a
 
 
 
 
 
 
 
 
 
c9b67bf
ef48c9a
 
 
 
 
 
 
 
 
 
 
249df7b
c9b67bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249df7b
ef48c9a
 
b702eb0
 
 
 
 
 
 
 
 
 
c9b67bf
 
 
 
 
 
b702eb0
 
 
 
 
 
 
c9b67bf
 
b702eb0
 
45b77cb
 
 
 
 
249df7b
45b77cb
 
 
 
 
 
 
 
 
c9b67bf
 
 
 
 
45b77cb
 
 
 
 
 
 
 
c9b67bf
45b77cb
 
c9b67bf
 
 
45b77cb
 
c9b67bf
45b77cb
 
 
 
 
 
ef48c9a
 
 
 
 
249df7b
ef48c9a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
ef48c9a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
ef48c9a
 
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
ef48c9a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249df7b
ef48c9a
 
b702eb0
 
c9b67bf
 
 
 
 
ef48c9a
 
 
 
249df7b
ef48c9a
 
b702eb0
 
c9b67bf
 
 
 
 
b702eb0
 
ef48c9a
 
 
 
 
 
 
249df7b
ef48c9a
 
 
 
 
c9b67bf
260098f
c9b67bf
ef48c9a
 
b702eb0
ef48c9a
 
 
 
 
 
249df7b
c9b67bf
 
 
 
 
 
 
 
 
 
 
 
 
249df7b
ef48c9a
 
b702eb0
ef48c9a
b702eb0
 
 
 
 
 
ef48c9a
c9b67bf
 
 
 
 
 
 
 
ef48c9a
 
 
 
 
249df7b
ef48c9a
 
 
 
 
 
 
 
 
c9b67bf
 
ef48c9a
 
 
 
 
 
 
 
 
 
 
 
c9b67bf
 
 
ef48c9a
 
 
 
c9b67bf
ef48c9a
 
 
 
c9b67bf
 
ef48c9a
 
 
 
 
 
249df7b
ef48c9a
 
 
 
c9b67bf
 
 
ef48c9a
 
 
 
 
 
 
 
 
 
 
 
 
249df7b
ef48c9a
 
c9b67bf
 
 
 
ef48c9a
 
 
 
 
249df7b
ef48c9a
 
 
 
 
 
c9b67bf
 
ef48c9a
 
 
 
c9b67bf
 
ef48c9a
 
c9b67bf
b702eb0
ef48c9a
 
 
 
249df7b
ef48c9a
 
 
b702eb0
 
 
 
 
 
ef48c9a
 
 
 
 
c9b67bf
 
 
 
ef48c9a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "d6dbef8d",
   "metadata": {},
   "source": [
    "# Tiny Storyteller: building a language model from scratch\n",
    "\n",
    "**Author:** singhpratech. **Runs on:** Google Colab (free T4 GPU), or a Mac with Apple Silicon, or CPU.\n",
    "\n",
    "## Purpose\n",
    "Learn how a language model (an \"LLM\") is really built, by building one from nothing.\n",
    "No pretrained weights (dials borrowed from a model someone else trained), no fine-tuning\n",
    "(nudging borrowed dials), no magic. We start with raw PyTorch tensors, build the simplest\n",
    "possible model, then add one idea at a time until we have a small GPT-style transformer that\n",
    "we train ourselves. If those words mean nothing yet, good: the next section explains every\n",
    "one of them.\n",
    "\n",
    "## Use case\n",
    "A **bedtime story starter** for young kids. Give it the first few words, like\n",
    "*\"Once upon a time, a little monkey\"*, and it continues with a short, simple story.\n",
    "\n",
    "## Two characters, one story\n",
    "The whole notebook is told through **Momo**, a young monkey who is learning to tell stories,\n",
    "and **Ella**, an elephant who never forgets and reads to him every night. Momo is the model.\n",
    "Ella is the teacher and the memory. Every idea below, and every clickable picture, uses the\n",
    "same two characters, so nothing has to be re-learned from scratch.\n",
    "\n",
    "## The road map\n",
    "| Part | What we build | Why |\n",
    "|---|---|---|\n",
    "| 0 | Momo and Ella's story, a word list, a live flow diagram, then setup | Every idea and every strange word in plain language |\n",
    "| 1 | Tensors and gradients | The two things every neural net is made of |\n",
    "| 2 | Data: TinyStories | Simple English stories, made for small models |\n",
    "| 3 | A bigram model | The simplest language model that can \"write\" |\n",
    "| 4 | A real tokenizer (BPE) | Words instead of letters |\n",
    "| 5 | Attention, by hand | The idea that makes transformers work |\n",
    "| 6 | A tiny GPT | Stack the pieces into a full model |\n",
    "| 7 | Training | Teach it with a few minutes on a GPU |\n",
    "| 8 | Generate stories | Try the use case |\n",
    "| 9 | Publish on Hugging Face | Save the model, privately at first |\n",
    "| 10 | Load it back, and what we built | Use Momo anywhere |\n",
    "\n",
    "Run every cell top to bottom. Each cell is small on purpose."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52bce3d0",
   "metadata": {},
   "source": [
    "## 0. Before any code: Momo, Ella, and what we are about to build\n",
    "\n",
    "*No code in this section. It is written so that a five-year-old could follow the story and a\n",
    "grown-up could follow the meaning. Every strange word is explained the first time it appears,\n",
    "and all of them are collected in the word list at the end of this section.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "18badc67",
   "metadata": {},
   "source": [
    "### Meet the characters\n",
    "\n",
    "**Momo** is a young monkey. He is quick and curious and wants to learn to tell bedtime\n",
    "stories. Inside Momo's head are millions of tiny **dials**. Right now every dial points\n",
    "somewhere random, so when Momo tries to tell a story it comes out as nonsense.\n",
    "\n",
    "**Ella** is an elephant, and elephants never forget. Every night Ella reads a story to Momo\n",
    "one word at a time. Before each word she stops and lets Momo guess what comes next. Then she\n",
    "tells him how wrong he was. Ella also keeps a shelf where she stores everything already said,\n",
    "so nobody has to work it out twice.\n",
    "\n",
    "In grown-up words: Momo is the **model**. His dials are the **weights**, also called\n",
    "**parameters**: the numbers that get adjusted during learning. Ella's stories are the\n",
    "**training data**. Her \"how wrong were you\" is the **loss**. Her shelf is the **KV cache**.\n",
    "Ella plays two roles on purpose: the teacher while Momo learns (training), and the memory\n",
    "while Momo writes (generation). Each of these gets its own section below."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c6b124e5",
   "metadata": {},
   "source": [
    "### 0.1 What is PyTorch?\n",
    "\n",
    "Momo needs a place to keep his dials and do his sums very fast. That place is **PyTorch**,\n",
    "Momo's workshop. It has three things:\n",
    "\n",
    "- **Boxes for numbers**, called **tensors**. A tensor is a grid of numbers with a shape: a\n",
    "  row of baskets, a tray of baskets, or a stack of trays. Everything in Momo's head is a\n",
    "  tensor: the words coming in, the dials, the guesses going out.\n",
    "- **A very fast counting table**, the **GPU** (graphics processing unit: a chip first built\n",
    "  for video games that can do millions of small sums at the same time). On Colab we borrow\n",
    "  one called a T4.\n",
    "- **A tape recorder**, called **autograd** (automatic gradients). While Momo works it writes\n",
    "  down every step. Afterwards it reads the tape backwards and tells Momo which way to turn\n",
    "  every dial, and by how much. Ordinary programs do not do this, and it is the reason modern AI\n",
    "  is practical at all. Part 1 has a game that shows exactly how the tape is read.\n",
    "\n",
    "PyTorch was built at Meta's AI lab and is behind most famous models (the GPT family, Llama,\n",
    "Stable Diffusion, Whisper). **Hugging Face**, where we publish at the end, is the public\n",
    "library where people share models built this way."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a8557a5e",
   "metadata": {},
   "source": [
    "### 0.2 What is a model, really?\n",
    "\n",
    "A **model** is Momo: a big pile of dials plus a fixed recipe for how to use them. Our Momo\n",
    "has about 5.9 million dials. Famous big models have hundreds of billions. At the start every\n",
    "dial is random. **Training** means turning the dials a tiny bit at a time, thousands of\n",
    "times, until the recipe gives good guesses. Momo never writes down a rulebook. He tunes his\n",
    "instincts. The dials *are* the instincts. (He can still end up repeating sentences he has\n",
    "heard many times, as we will see in Part 8.)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f05878b3",
   "metadata": {},
   "source": [
    "### 0.3 What does a language model actually do?\n",
    "\n",
    "One thing: **given some words, guess the next word.** That is the whole job.\n",
    "\n",
    "Ella reads: \"Once upon a time, a little...\" Momo guesses: \"monkey!\" (or \"girl\", or \"fox\").\n",
    "\n",
    "Telling a whole story is just that guess over and over: guess a word, add it, guess the next\n",
    "word, add it. Chatting with an AI assistant is the same trick. Your question is the words so\n",
    "far, and the answer is a long run of next-word guesses.\n",
    "\n",
    "A **language model** is any machine that does this. An **LLM** is a *large* language model,\n",
    "one with billions of dials. Ours is tiny, but it works the same way."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8774b740",
   "metadata": {},
   "source": [
    "### 0.4 What happens when you ask Momo a question?\n",
    "\n",
    "Say you ask: *\"What is the capital of France?\"* Here is the journey.\n",
    "\n",
    "**Step 1, biting: words become tokens.** Momo cannot swallow a whole sentence. He bites it\n",
    "into pieces: \"What\", \" is\", \" the\", \" capital\", \" of\", \" France\", \"?\". Each kind of piece\n",
    "has a number painted on it. A piece is called a **token**, the biter is the **tokenizer**,\n",
    "and the set of all pieces Momo knows is his **vocabulary** (ours has 4,096 pieces). After\n",
    "this step your question is a short list of numbers.\n",
    "\n",
    "**Step 2, the meaning map: each token becomes a point.** Momo keeps a big map. Every token\n",
    "has a spot on it, and tokens that mean similar things sit close together: \"France\" near\n",
    "\"Spain\", \"capital\" near \"city\". A spot on the map is written as a list of numbers (256 of\n",
    "them in our model). That list is called an **embedding**, and the whole map is learned\n",
    "during training.\n",
    "\n",
    "**Step 3, looking around: attention.** This is the heart of it. Each word looks back at all\n",
    "the words before it (and at itself) and asks \"which of you matter to me right now?\" The word\n",
    "\"capital\" looks around, finds \"France\", and pulls some of France's meaning into itself. Now\n",
    "\"capital\" means \"the capital of France\". This looking-around is called **attention**. It is\n",
    "repeated in several rounds, called **layers**. Picture a tall jungle tree that Momo climbs:\n",
    "each rung is one round of looking around, and the meaning gets sharper at every rung. Our\n",
    "Momo's tree has 6 rungs.\n",
    "\n",
    "**Step 4, the guess.** The last word's sharpened meaning is compared with every token in\n",
    "the vocabulary, giving each a score. The raw scores are called **logits**. They are squashed\n",
    "into percentages that add up to 100, and that squashing is called **softmax**. \"Paris\" gets,\n",
    "say, 92 percent. One token is picked.\n",
    "\n",
    "**Step 5, repeat.** \"Paris\" is added to the words so far and the whole journey runs again for\n",
    "the next token, until Momo picks a special \"the story is over\" token or reaches the length limit. You see words appear one by one\n",
    "on screen because that is literally how they are made."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a0ae3aa",
   "metadata": {},
   "source": [
    "### 0.5 How attention looks things up: the story circle\n",
    "\n",
    "Picture the jungle story circle. Every word in the sentence is an animal sitting in the\n",
    "circle. Each animal holds three cards:\n",
    "\n",
    "- a **query** card: \"here is what I am looking for\" (\"capital\" is looking for a country),\n",
    "- a **key** card: \"here is what I am\" (\"France\" says: I am a country),\n",
    "- a **value** card: \"here is what I will tell you if you pick me\" (France's actual meaning).\n",
    "\n",
    "Every animal holds up its query card and compares it with the key cards of the animals\n",
    "before it, and with its own. A good match scores high, a poor match scores low. The scores\n",
    "are squashed into percentages (softmax again) and the animal collects a blend of everyone's\n",
    "value cards, weighted by those percentages. \"capital\" might take 70 percent from \"France\",\n",
    "20 percent from \"of\", and crumbs from the rest. That blend becomes its new, sharper meaning.\n",
    "\n",
    "One more thing. While Ella is teaching, every animal does this at the same time. While Momo\n",
    "is writing, only the newest animal needs to.\n",
    "\n",
    "Two rules make this work for *writing*:\n",
    "\n",
    "- **No peeking ahead.** When Momo is guessing word 7 he may only look at words 1 to 6. In\n",
    "  code this is a triangle-shaped **mask** that blocks out the future.\n",
    "- **Many heads.** The circle runs several times at once with different cards. Each run is a\n",
    "  **head**. One head might track grammar, another names, another position. Momo has 4 heads.\n",
    "  Big models have dozens."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e786fabb",
   "metadata": {},
   "source": [
    "### 0.6 Ella's shelf: the KV cache\n",
    "\n",
    "Remember Step 5: for each new word the whole journey runs again. Done naively, writing the\n",
    "500th word means re-reading 499 words, and the 501st means re-reading 500. Slower and slower.\n",
    "\n",
    "But the key card and value card of a word already said **never change**. \"France\" holds the\n",
    "same cards whether the story is 7 words or 700 words long. So Ella, who never forgets, keeps\n",
    "every finished word's key and value cards on her shelf. That shelf is the **KV cache** (K for\n",
    "key, V for value; a cache is a place where things are kept for quick reuse). When a new word\n",
    "arrives only *its* cards are made. It compares its query with all the keys on the shelf,\n",
    "collects the values, done. Nothing old is redone.\n",
    "\n",
    "The shelf is why a long chat stays fast, and why long chats need a lot of GPU memory: every\n",
    "token keeps its cards there. A model's **context window** (how much text it can hold at\n",
    "once) is partly how big the shelf is allowed to grow.\n",
    "\n",
    "Part 5 shows the shelf in about a dozen lines of code. Two honest notes: the shelf is emptied at the\n",
    "start of every new story (it is a memory *within* one piece of writing, not between stories),\n",
    "and Momo's own `generate` in Part 6 does not use it, to keep the code short. It redoes the\n",
    "whole journey for every word, which is fine for 200-word stories."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b4e0da60",
   "metadata": {},
   "source": [
    "### 0.7 The strange names, decoded\n",
    "\n",
    "| Word | In Momo's world | Grown-up meaning |\n",
    "|---|---|---|\n",
    "| **Model** | Momo | A pile of dials plus a recipe for using them |\n",
    "| **Weights / parameters** | Momo's dials | The numbers that training adjusts |\n",
    "| **Training data** | Ella's bedtime stories | The text the model learns from |\n",
    "| **Token / tokenizer / vocabulary** | A bite / the biter / all the bites Momo knows | Text pieces, the splitter, the set of pieces |\n",
    "| **Embedding** | A spot on Momo's meaning map | A list of numbers standing for one token |\n",
    "| **Attention** | Looking around the story circle | Each token blending in earlier tokens by relevance |\n",
    "| **Query, key, value** | The three cards each animal holds | What I seek, what I am, what I give |\n",
    "| **Head** | One run of the circle | One attention pattern; several run in parallel |\n",
    "| **Mask** | No peeking ahead | Blocks future tokens |\n",
    "| **Softmax** | Turning scores into shares of 100 | Raise e to the power of each score, then divide so they add up to 1 |\n",
    "| **Logits** | Raw scores before the squash | Unnormalised outputs of the last layer |\n",
    "| **KV cache** | Ella's shelf | Stored keys and values of past tokens |\n",
    "| **Context window** | How long Ella's shelf can get | Maximum tokens the model holds at once |\n",
    "| **GPT** | Momo's kind of model | Generative Pre-trained Transformer: makes text, learned from text first, built from attention |\n",
    "| **Pre-training / fine-tuning** | Ella's bedtime stories / a few extra lessons on one topic | Training from scratch on lots of text / continuing to train an already-trained model on a small special set |\n",
    "| **Transformer** | The tall tree of story-circle rungs | The 2017 design that stacks attention layers |\n",
    "| **Neural network** | Any pile of dials in layers | The general family transformers belong to |\n",
    "| **Layer / block** | One rung of the tree | One attention round plus a think-alone round |\n",
    "| **Feed-forward network** | Thinking alone on a twig after the circle | A small two-layer network applied per token |\n",
    "| **`nn.Linear`** | One tray of dials | Multiplies its input by a weight matrix (the fruit widget in Part 1) |\n",
    "| **LayerNorm** | Ella steadying the volume | Rescales numbers so training stays stable |\n",
    "| **Residual connection** | Adding thoughts instead of replacing them | Each block adds to its input |\n",
    "| **Loss / cross-entropy** | Ella's \"how wrong were you\" | How surprised the model was by the true next token |\n",
    "| **Gradient** | The slope under Momo's feet | For each dial, how the loss changes if the dial moves |\n",
    "| **Gradient descent** | Walking downhill one stride at a time | Repeatedly step every weight against its gradient |\n",
    "| **Autograd** | Momo's tape recorder | PyTorch recording every step so it can replay it backwards |\n",
    "| **Backpropagation** | Reading the tape backwards, the blame game | Chain rule applied from the loss back to every weight |\n",
    "| **Chain rule** | Multiply by how hard each step pushes | The slope through a chain of steps is the product of each step's slope |\n",
    "| **Optimizer / AdamW** | The rule for turning dials | Uses gradients plus a memory of recent slopes |\n",
    "| **Weight decay** (the W in AdamW) | Ella nudging every dial back toward the middle | Shrinks every weight a little each step so none grows wild |\n",
    "| **Learning rate** | Momo's stride | Step size per update |\n",
    "| **Warm-up / cosine decay** | Tiptoe, stride, tiptoe | Learning-rate schedule: small, then big, then small |\n",
    "| **Batch** | How many story pieces per lesson | Examples processed before one update |\n",
    "| **Validation set** | The stories Ella hides for the quiz | Held-out text the model never trains on; shows real learning, not memorising |\n",
    "| **Step / epoch** | One dial turn / one pass over every story | One update / one pass over the data |\n",
    "| **Perplexity** | How many tokens Momo is choosing between | exp(loss); 1 is perfect, vocabulary size is random |\n",
    "| **Temperature / top-k** | How wild Momo is when picking | Sampling knobs at generation time |\n",
    "| **BPE** | How Momo decided where to bite | Byte-pair encoding: merge the most common pairs repeatedly |\n",
    "| **GPU** | The fast counting table | Graphics chip doing millions of sums at once |\n",
    "| **Hugging Face Hub / Colab** | The jungle library / a borrowed workshop | Public model library / Google's free GPU notebooks |\n",
    "| **Access token** | Momo's library card | A secret key that lets you upload; nothing to do with text tokens |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91659f55",
   "metadata": {},
   "source": [
    "### The one-sentence version\n",
    "\n",
    "Momo bites text into tokens, places them on his meaning map, lets each word look around the\n",
    "story circle to sharpen its meaning (attention), guesses the next token, and Ella keeps the\n",
    "cards on her shelf so nothing is redone.\n",
    "\n",
    "Now let's build Momo."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eabf2c36",
   "metadata": {},
   "source": [
    "## Live flow diagram\n",
    "\n",
    "Run the cell below (it only draws, it trains nothing). Press **Play** and watch Momo produce\n",
    "one word at a time: the text is bitten into tokens, each token gets a spot on the meaning\n",
    "map, the newest token looks around the story circle (the orange arcs are attention), the\n",
    "vocabulary is scored, a word is picked, and it joins the text. On the right, Ella's shelf\n",
    "(the KV cache) fills up one card per token, and only the newest token ever needs new cards.\n",
    "One honest note: the diagram shows the shelf because real models use it. Our own short\n",
    "`generate` in Part 6 skips the shelf and redoes the whole journey for every word."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1baea7bb",
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import HTML, display\n",
    "\n",
    "# Everything below is plain HTML + JavaScript, no libraries. It runs inside the notebook output.\n",
    "display(HTML(r\"\"\"<div id=\"llmflow\" style=\"font-family:-apple-system,Segoe UI,Helvetica,Arial,sans-serif;color:#e8e8e8;background:#1e1e1e;border-radius:12px;padding:16px;max-width:980px\">\n",
    "<style>\n",
    "#llmflow .stage{display:inline-block;padding:8px 12px;border-radius:8px;border:2px solid #555;margin:0 4px;min-width:110px;text-align:center;font-size:13px;transition:all .3s;background:#2a2a2a}\n",
    "#llmflow .stage.on{border-color:#ffb347;background:#3a2e1a;box-shadow:0 0 12px #ffb34788}\n",
    "#llmflow .arrow{display:inline-block;color:#888;font-size:18px;vertical-align:middle}\n",
    "#llmflow .chip{display:inline-block;padding:4px 8px;margin:3px;border-radius:6px;background:#2f4f6f;border:1px solid #5a8ab8;font-size:13px;transition:all .3s}\n",
    "#llmflow .chip.new{background:#6a4a1a;border-color:#ffb347}\n",
    "#llmflow .chip.q{outline:2px solid #ffb347}\n",
    "#llmflow .slot{display:inline-block;width:22px;height:30px;margin:2px;border:1px dashed #666;border-radius:4px;vertical-align:middle;font-size:9px;text-align:center;line-height:30px;color:#aaa}\n",
    "#llmflow .slot.full{background:#2f6f4f;border:1px solid #6fcf97;color:#fff}\n",
    "#llmflow .bar{height:14px;background:#5a8ab8;border-radius:3px;margin:2px 0;transition:width .4s}\n",
    "#llmflow button{background:#333;color:#eee;border:1px solid #777;border-radius:6px;padding:6px 12px;margin-right:6px;cursor:pointer;font-size:13px}\n",
    "#llmflow button:hover{background:#444}\n",
    "#llmflow .panel{background:#262626;border-radius:8px;padding:10px 12px;margin-top:10px;font-size:13px;line-height:1.45}\n",
    "#llmflow .lbl{font-size:11px;color:#aaa;text-transform:uppercase;letter-spacing:.05em;margin:8px 0 4px}\n",
    "</style>\n",
    "\n",
    "<div style=\"font-size:15px;font-weight:600;margin-bottom:8px\">How Momo writes a story: live flow</div>\n",
    "<div style=\"font-size:12px;color:#aaa;margin-bottom:10px\">Press <b>Play</b> and watch one word get produced, then the next. Attention weights are illustrative.</div>\n",
    "\n",
    "<div id=\"stages\">\n",
    "  <span class=\"stage\" data-s=\"0\">1. Text so far</span><span class=\"arrow\">&#8594;</span>\n",
    "  <span class=\"stage\" data-s=\"1\">2. Tokenizer<br><small>chop into tokens</small></span><span class=\"arrow\">&#8594;</span>\n",
    "  <span class=\"stage\" data-s=\"2\">3. Meaning map<br><small>token &#8594; vector</small></span><span class=\"arrow\">&#8594;</span>\n",
    "  <span class=\"stage\" data-s=\"3\">4. Attention<br><small>look back</small></span><span class=\"arrow\">&#8594;</span>\n",
    "  <span class=\"stage\" data-s=\"4\">5. Next-word scores</span><span class=\"arrow\">&#8617;</span>\n",
    "  <span class=\"stage\" data-s=\"5\">6. Append word, repeat</span>\n",
    "</div>\n",
    "\n",
    "<div class=\"lbl\">Text so far (each chip is one token)</div>\n",
    "<div id=\"chips\" style=\"min-height:36px\"></div>\n",
    "<svg id=\"arcs\" height=\"70\" style=\"display:block;width:100%\"></svg>\n",
    "\n",
    "<div style=\"display:flex;gap:20px;flex-wrap:wrap\">\n",
    "  <div style=\"flex:1;min-width:280px\">\n",
    "    <div class=\"lbl\">Next-word scores (top 5)</div>\n",
    "    <div id=\"bars\"></div>\n",
    "  </div>\n",
    "  <div style=\"flex:1;min-width:280px\">\n",
    "    <div class=\"lbl\">Ella's shelf (the KV cache): stored key/value cards, one per token</div>\n",
    "    <div id=\"cache\"></div>\n",
    "    <div style=\"font-size:11px;color:#aaa;margin-top:4px\">Only the newest token computes its cards. Old ones are reused.</div>\n",
    "  </div>\n",
    "</div>\n",
    "\n",
    "<div class=\"panel\" id=\"explain\">Press Play.</div>\n",
    "<div style=\"margin-top:10px\">\n",
    "  <button onclick=\"llmPlay()\">&#9654; Play</button>\n",
    "  <button onclick=\"llmStep()\">Step</button>\n",
    "  <button onclick=\"llmReset()\">Reset</button>\n",
    "  <label style=\"font-size:12px;color:#aaa;margin-left:8px\">Speed <input id=\"spd\" type=\"range\" min=\"1\" max=\"5\" value=\"3\" style=\"vertical-align:middle\"></label>\n",
    "</div>\n",
    "</div>\n",
    "\n",
    "<script>\n",
    "(function(){\n",
    "const prompt = [\"Once\",\" upon\",\" a\",\" time\",\",\",\" a\",\" little\",\" monkey\"];\n",
    "const gen = [\n",
    "  {w:\" found\", top:[[\" found\",.41],[\" was\",.22],[\" saw\",.14],[\" lived\",.09],[\" had\",.06]], att:[.05,.03,.04,.06,.02,.08,.22,.50]},\n",
    "  {w:\" a\",     top:[[\" a\",.55],[\" the\",.18],[\" some\",.08],[\" her\",.06],[\" his\",.05]], att:[.03,.02,.05,.03,.02,.06,.12,.27,.40]},\n",
    "  {w:\" shiny\", top:[[\" shiny\",.31],[\" big\",.27],[\" red\",.16],[\" small\",.10],[\" little\",.06]], att:[.02,.02,.03,.02,.01,.04,.18,.33,.10,.25]},\n",
    "  {w:\" ball\",  top:[[\" ball\",.46],[\" stone\",.15],[\" box\",.12],[\" hat\",.08],[\" key\",.05]], att:[.02,.01,.02,.02,.01,.03,.08,.28,.06,.12,.35]},\n",
    "  {w:\".\",      top:[[\".\",.52],[\" in\",.14],[\" and\",.12],[\" on\",.08],[\" that\",.05]], att:[.02,.01,.02,.03,.02,.02,.05,.15,.05,.10,.18,.35]},\n",
    "];\n",
    "let tokens=[], gi=0, stage=-1, timer=null;\n",
    "const $=id=>document.getElementById(id);\n",
    "const txt={\n",
    " 0:\"<b>1. Text so far.</b> Everything written so far, prompt plus generated words, is the input. Nothing else.\",\n",
    " 1:\"<b>2. Tokenizer.</b> The text is chopped into tokens (roughly words, note the leading spaces) and each becomes a number from the model's dictionary.\",\n",
    " 2:\"<b>3. Meaning map.</b> Each token number is swapped for a vector, a long list of values, placing it on a learned map where similar words sit close together.\",\n",
    " 3:\"<b>4. Attention.</b> The newest token (orange outline) looks back at every earlier token. This is Momo looking around the story circle. Arc thickness shows how much it attends to each. It blends their value cards into its own meaning. Future tokens do not exist yet, so there is nothing to peek at. Repeated in 6 layers with 4 heads each.\",\n",
    " 4:\"<b>5. Next-word scores.</b> The refined meaning of the last token is compared with every word in the dictionary. Scores become probabilities; one word is sampled.\",\n",
    " 5:\"<b>6. Append and repeat.</b> The chosen word is added to the text and its key/value cards go onto Ella's shelf, the KV cache. The loop starts again from step 1, but only the new token needs computing.\",\n",
    "};\n",
    "function render(){\n",
    "  document.querySelectorAll('#llmflow .stage').forEach(s=>s.classList.toggle('on',+s.dataset.s===stage));\n",
    "  const c=$('chips'); c.innerHTML='';\n",
    "  tokens.forEach((t,i)=>{const s=document.createElement('span');s.className='chip'+(i===tokens.length-1&&gi>0&&stage<5?' new':'')+(stage===3&&i===tokens.length-1?' q':'');s.textContent=t.replace(/^ /,'␣');c.appendChild(s);});\n",
    "  const k=$('cache'); k.innerHTML='';\n",
    "  const nCached = stage>=5 ? tokens.length : Math.max(0, tokens.length-(gi>0?1:0));\n",
    "  tokens.forEach((t,i)=>{const s=document.createElement('span');s.className='slot'+(i<nCached?' full':'');s.textContent=i<nCached?'KV':'';k.appendChild(s);});\n",
    "  const b=$('bars'); b.innerHTML='';\n",
    "  if(stage>=4 && gi<gen.length){gen[gi].top.forEach(([w,p])=>{b.innerHTML+=`<div style=\"display:flex;align-items:center;gap:8px;font-size:12px\"><span style=\"width:60px;text-align:right\">${w.replace(/^ /,'␣')}</span><div class=\"bar\" style=\"width:${Math.round(p*260)}px\"></div><span style=\"color:#aaa\">${Math.round(p*100)}%</span></div>`;});}\n",
    "  else b.innerHTML='<div style=\"color:#666;font-size:12px\">(appears at step 5)</div>';\n",
    "  drawArcs();\n",
    "  $('explain').innerHTML = stage<0 ? 'Press Play.' : txt[stage];\n",
    "}\n",
    "function drawArcs(){\n",
    "  const svg=$('arcs'); svg.innerHTML='';\n",
    "  if(stage!==3||gi>=gen.length) return;\n",
    "  const chips=[...document.querySelectorAll('#chips .chip')]; if(chips.length<2) return;\n",
    "  const base=$('chips').getBoundingClientRect(); const last=chips[chips.length-1].getBoundingClientRect();\n",
    "  const x1=last.left-base.left+last.width/2; const w=gen[gi].att;\n",
    "  chips.slice(0,-1).forEach((ch,i)=>{const r=ch.getBoundingClientRect();const x0=r.left-base.left+r.width/2;const a=w[i]||0.02;\n",
    "    const p=document.createElementNS('http://www.w3.org/2000/svg','path');\n",
    "    p.setAttribute('d',`M${x1},2 Q${(x0+x1)/2},${20+Math.abs(x1-x0)/6} ${x0},2`);\n",
    "    p.setAttribute('fill','none');p.setAttribute('stroke','#ffb347');p.setAttribute('stroke-width',(0.5+a*10).toFixed(1));p.setAttribute('opacity',(0.25+a*1.5).toFixed(2));\n",
    "    svg.appendChild(p);\n",
    "    const t=document.createElementNS('http://www.w3.org/2000/svg','text');t.setAttribute('x',x0);t.setAttribute('y',60);t.setAttribute('fill','#ffb347');t.setAttribute('font-size','10');t.setAttribute('text-anchor','middle');t.textContent=Math.round(a*100)+'%';svg.appendChild(t);});\n",
    "}\n",
    "window.llmReset=function(){clearInterval(timer);timer=null;tokens=prompt.slice();gi=0;stage=-1;render();};\n",
    "window.llmStep=function(){\n",
    "  if(gi>=gen.length){$('explain').innerHTML='<b>Done.</b> The model wrote: <i>'+tokens.join('')+'</i>. Press Reset to watch again.';clearInterval(timer);timer=null;return;}\n",
    "  stage++;\n",
    "  if(stage===5){tokens.push(gen[gi].w);}\n",
    "  if(stage>5){stage=0;gi++; if(gi>=gen.length){render();window.llmStep();return;}}\n",
    "  render();\n",
    "};\n",
    "window.llmPlay=function(){if(timer){clearInterval(timer);timer=null;return;} const ms=[0,1600,1200,900,600,350][+$('spd').value]; timer=setInterval(window.llmStep,ms); window.llmStep();};\n",
    "window.llmReset();\n",
    "window.addEventListener('resize',drawArcs);\n",
    "})();\n",
    "</script>\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c0f4f61",
   "metadata": {},
   "source": [
    "### Setup (still Part 0)\n",
    "On Colab, go to **Runtime > Change runtime type > T4 GPU** first. Then run the next two cells.\n",
    "The first one fetches the toolboxes we need, one time only (on Colab most are already there, so\n",
    "it is quick). The second picks the fastest counting table available and fixes the dice."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "478042b4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install only the toolboxes that are missing. (On a machine where everything is already installed, this does nothing.)\n",
    "import importlib.util, subprocess, sys\n",
    "missing = [p for p in [\"torch\", \"transformers\", \"datasets\", \"tokenizers\", \"huggingface_hub\"] if importlib.util.find_spec(p) is None]\n",
    "if missing:\n",
    "    subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", *missing])\n",
    "print(\"toolboxes ready\" + (f\" (installed {', '.join(missing)})\" if missing else \"\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "00b89e95",
   "metadata": {},
   "outputs": [],
   "source": [
    "import math, time, json, os\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "# Pick the best device available: NVIDIA GPU (Colab), Apple GPU (Mac), or CPU.\n",
    "device = \"cuda\" if torch.cuda.is_available() else \"mps\" if torch.backends.mps.is_available() else \"cpu\"\n",
    "torch.manual_seed(42)   # fix the dice: every run starts with the same random dials. Change the number for a different Momo.\n",
    "print(\"PyTorch\", torch.__version__, \"| device:\", device)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33dd01e2",
   "metadata": {},
   "source": [
    "## 1. Tensors and gradients: the two building blocks\n",
    "\n",
    "A **tensor** is a grid of numbers with a shape. Picture the baskets in Momo's workshop:\n",
    "\n",
    "- **one row of baskets** is 1-D: shape `(4,)`. Grown-ups call it a **vector**.\n",
    "- **a tray of baskets**, rows and columns, is 2-D: shape `(3, 4)`. A **matrix**.\n",
    "- **a stack of trays**, one per story, is 3-D: shape `(2, 3, 4)`.\n",
    "\n",
    "Everything in Momo's head is one of these: the words coming in (one row per token), the\n",
    "dials (big trays), the guesses going out (one row per position). Click through the\n",
    "dimensions below and hover any basket to see its address."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0b5c0de4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Interactive: 1-D, 2-D and 3-D tensors. Run this cell, then click and hover.\n",
    "from IPython.display import HTML, display\n",
    "display(HTML(r\"\"\"<div id=\"vt\" style=\"font-family:-apple-system,Segoe UI,Helvetica,Arial,sans-serif;color:#e8e8e8;background:#1e1e1e;border-radius:12px;padding:16px;max-width:900px\">\n",
    "<style>\n",
    "#vt button{background:#333;color:#eee;border:1px solid #777;border-radius:6px;padding:6px 12px;margin-right:6px;cursor:pointer}\n",
    "#vt button.on{background:#6a4a1a;border-color:#ffb347}\n",
    "#vt .cell{display:inline-block;width:38px;height:30px;line-height:30px;text-align:center;font-size:12px;border:1px solid #5a8ab8;background:#2f4f6f;margin:1px;border-radius:3px;cursor:crosshair}\n",
    "#vt .cell:hover{background:#ffb347;color:#000}\n",
    "#vt .grid{display:inline-block;padding:6px;background:#262626;border-radius:6px;margin:6px;vertical-align:top}\n",
    "#vt .cap{font-size:13px;line-height:1.5;margin-top:10px;background:#262626;border-radius:8px;padding:10px}\n",
    "</style>\n",
    "<div style=\"font-size:15px;font-weight:600;margin-bottom:6px\">A tensor is a grid of numbers with a shape. Click a dimension, then hover or tap a basket.</div>\n",
    "<div><button id=\"vtB1\" class=\"on\" onclick=\"vtShow(1)\">1-D: vector</button><button id=\"vtB2\" onclick=\"vtShow(2)\">2-D: matrix</button><button id=\"vtB3\" onclick=\"vtShow(3)\">3-D: batch</button></div>\n",
    "<div id=\"vtArea\" style=\"margin-top:10px;min-height:120px\"></div>\n",
    "<div id=\"vtHover\" style=\"font-size:12px;color:#ffb347;height:18px\"></div>\n",
    "<div class=\"cap\" id=\"vtCap\"></div>\n",
    "</div>\n",
    "<script>\n",
    "(function(){\n",
    "const caps={\n",
    "1:\"<b>Shape (4,)</b>: one row of numbers. Picture: one row of Momo's baskets, one number in each. Or one word's meaning as 4 numbers. In our model each token becomes a vector of <b>256</b> numbers.\",\n",
    "2:\"<b>Shape (3, 4)</b>: rows and columns, like Momo's baskets laid out on a tray. Picture: one sentence, one row per token, one column per feature. Our model holds a 256-token story as a (256, 256) matrix.\",\n",
    "3:\"<b>Shape (2, 3, 4)</b>: a stack of trays. Picture: a batch of sentences processed at the same time, which is how GPUs stay busy. Our training batches are 32 windows of 256 tokens: the token ids are shape (32, 256), and after the meaning map each token has 256 numbers, so the stack becomes (32, 256, 256).\"};\n",
    "function grid(r,c,prefix,seed){seed=seed||0;let h='<div class=\\\"grid\\\">';for(let i=0;i<r;i++){h+='<div>';for(let j=0;j<c;j++){const v=(Math.round((Math.sin(i*3+j*7+seed*5)*5)*10)/10).toFixed(1);h+=`<span class=\\\"cell\\\" onmouseover=\\\"vtH('${prefix}${i},${j}', ${v})\\\" onclick=\\\"vtH('${prefix}${i},${j}', ${v})\\\" onmouseout=\\\"vtH('')\\\">${v}</span>`;}h+='</div>';}return h+'</div>';}\n",
    "window.vtH=function(idx,v){document.getElementById('vtHover').textContent=idx?`index [${idx}]  value ${v}`:'';};\n",
    "window.vtShow=function(d){[1,2,3].forEach(k=>document.getElementById('vtB'+k).classList.toggle('on',k===d));\n",
    " const a=document.getElementById('vtArea');\n",
    " if(d===1)a.innerHTML=grid(1,4,'');\n",
    " if(d===2)a.innerHTML=grid(3,4,'');\n",
    " if(d===3)a.innerHTML='<span style=\"font-size:12px;color:#aaa\">sentence 0</span>'+grid(3,4,'0,',1)+'<span style=\"font-size:12px;color:#aaa\">sentence 1</span>'+grid(3,4,'1,',2);\n",
    " document.getElementById('vtCap').innerHTML=caps[d];};\n",
    "window.vtShow(1);\n",
    "})();\n",
    "</script>\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2dfe6dad",
   "metadata": {},
   "source": [
    "### How matrix multiplication actually happens\n",
    "\n",
    "Almost all the work inside Momo is one operation: multiplying two trays of numbers together.\n",
    "Textbooks show the formula first, so nobody understands it. Here is the story instead.\n",
    "\n",
    "Momo and Ella each ate some fruit today. Momo's row says what he ate: 2 bananas, 1 mango,\n",
    "3 coconuts. Ella's row says what she ate. Ella also knows two facts about every fruit, kept as\n",
    "two columns: how many bites it takes, and how many seeds it has. How many bites did Momo take\n",
    "in total? Take Momo's row and the \"bites\" column, multiply the pairs, add them up. That one\n",
    "number is one cell of the answer. Do it for every row and every column and you have the whole\n",
    "answer tray: bites and seeds, for Momo and for Ella. That is all matrix multiplication is.\n",
    "Press **Next cell** and watch.\n",
    "\n",
    "Why Momo cares: a word's embedding is a row like \"2 bananas, 1 mango, 3 coconuts\", and a\n",
    "layer's dials are a tray of columns like \"bites\" and \"seeds\". Multiplying them asks \"how much\n",
    "does this word have of each thing this layer is looking for?\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c94b0555",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Interactive: matrix multiplication, one output cell at a time.\n",
    "from IPython.display import HTML, display\n",
    "display(HTML(r\"\"\"<div id=\"vm\" style=\"font-family:-apple-system,Segoe UI,Helvetica,Arial,sans-serif;color:#e8e8e8;background:#1e1e1e;border-radius:12px;padding:16px;max-width:960px\">\n",
    "<style>\n",
    "#vm button{background:#333;color:#eee;border:1px solid #777;border-radius:6px;padding:6px 12px;margin-right:6px;cursor:pointer}\n",
    "#vm table{border-collapse:separate;border-spacing:3px;display:inline-table;vertical-align:middle;margin:0 6px}\n",
    "#vm td,#vm th{width:46px;height:34px;text-align:center;font-size:13px;border-radius:5px}\n",
    "#vm td{background:#2f4f6f;border:1px solid #5a8ab8;transition:all .25s}\n",
    "#vm th{color:#aaa;font-weight:normal;font-size:11px;background:transparent}\n",
    "#vm td.row{background:#6a4a1a;border-color:#ffb347}\n",
    "#vm td.col{background:#2f6f4f;border-color:#6fcf97}\n",
    "#vm td.out{background:#ffb347;color:#000;font-weight:600}\n",
    "#vm td.empty{background:#262626;border:1px dashed #555;color:#666}\n",
    "#vm .sym{font-size:22px;color:#aaa;vertical-align:middle;margin:0 4px}\n",
    "#vm .cap{font-size:13px;line-height:1.55;margin-top:10px;background:#262626;border-radius:8px;padding:10px;min-height:60px}\n",
    "#vm .work{font-family:monospace;font-size:14px;color:#ffb347;margin-top:6px}\n",
    "</style>\n",
    "<div style=\"font-size:15px;font-weight:600;margin-bottom:4px\">Matrix multiplication, one cell at a time</div>\n",
    "<div style=\"font-size:12px;color:#aaa;margin-bottom:10px\">Story: Momo and Ella each ate some fruit. Ella knows how many bites each fruit takes and how many seeds it has. How many bites, and how many seeds, did each of them have?</div>\n",
    "<div id=\"vmArea\" style=\"overflow-x:auto;white-space:nowrap\"></div>\n",
    "<div class=\"work\" id=\"vmWork\"></div>\n",
    "<div class=\"cap\" id=\"vmCap\"></div>\n",
    "<div style=\"margin-top:10px\"><button onclick=\"vmStep()\">Next cell</button><button onclick=\"vmAuto()\">Play all</button><button onclick=\"vmReset()\">Reset</button></div>\n",
    "</div>\n",
    "<script>\n",
    "(function(){\n",
    "const A=[[2,1,3],[1,4,0]];           // animals x fruits   (bananas, mangoes, coconuts)\n",
    "const B=[[2,1],[3,2],[4,3]];         // fruits x facts      (bites per fruit, seeds per fruit)\n",
    "const fruits=[\"bananas\",\"mangoes\",\"coconuts\"], one=[\"banana\",\"mango\",\"coconut\"], custs=[\"Momo\",\"Ella\"], stalls=[\"bites\",\"seeds\"];\n",
    "const C=[[null,null],[null,null]]; let k=-1, timer=null;\n",
    "function tbl(M,rh,ch,cls){let h='<table><tr><th></th>'+ch.map(c=>`<th>${c}</th>`).join('')+'</tr>';M.forEach((r,i)=>{h+=`<tr><th>${rh[i]}</th>`+r.map((v,j)=>`<td class=\"${cls(i,j,v)}\">${v===null?'?':v}</td>`).join('')+'</tr>';});return h+'</table>';}\n",
    "function render(){const i=k<0?-1:Math.floor(k/2), j=k<0?-1:k%2;\n",
    " document.getElementById('vmArea').innerHTML=\n",
    "  tbl(A,custs,fruits,(r,c)=>r===i?'row':'')+'<span class=\"sym\">&times;</span>'+\n",
    "  tbl(B,fruits,stalls,(r,c)=>c===j?'col':'')+'<span class=\"sym\">=</span>'+\n",
    "  tbl(C,custs,stalls,(r,c,v)=>(r===i&&c===j)?'out':(v===null?'empty':''));\n",
    " if(k<0){document.getElementById('vmWork').textContent='';document.getElementById('vmCap').innerHTML='<b>Left table:</b> how many of each fruit each animal ate (rows = animals, columns = fruits).<br><b>Right table:</b> Ella\\'s two facts about each fruit: bites it takes, seeds it has (rows = fruits, columns = facts).<br>Press <b>Next cell</b>. Each answer cell = one <span style=\"color:#ffb347\">row</span> from the left paired with one <span style=\"color:#6fcf97\">column</span> from the right: multiply matching pairs, add them up.';return;}\n",
    " const terms=A[i].map((a,t)=>`${a}&times;${B[t][j]}`), prods=A[i].map((a,t)=>a*B[t][j]), tot=prods.reduce((x,y)=>x+y,0); C[i][j]=tot;\n",
    " document.getElementById('vmArea').innerHTML=tbl(A,custs,fruits,(r,c)=>r===i?'row':'')+'<span class=\"sym\">&times;</span>'+tbl(B,fruits,stalls,(r,c)=>c===j?'col':'')+'<span class=\"sym\">=</span>'+tbl(C,custs,stalls,(r,c,v)=>(r===i&&c===j)?'out':(v===null?'empty':''));\n",
    " document.getElementById('vmWork').innerHTML=`${custs[i]}, ${stalls[j]}:  ${terms.join(' + ')}  =  ${prods.join(' + ')}  =  <b>${tot}</b>`;\n",
    " document.getElementById('vmCap').innerHTML=`Take <span style=\"color:#ffb347\"><b>${custs[i]}'s row</b></span> (what ${custs[i]} ate) and the <span style=\"color:#6fcf97\"><b>${stalls[j]} column</b></span> (${stalls[j]} per fruit). Walk along both at the same time: ${A[i].map((a,t)=>`${a} ${a===1?one[t]:fruits[t]} &times; ${B[t][j]} ${stalls[j]} each`).join(', ')}. Add up: <b>${tot} ${stalls[j]}</b> for ${custs[i]}. That is one cell of the answer.<br><br>Why it matters: a neural-network layer is exactly this. Left table = your tokens (one row per token, one column per feature). Right table = the layer's weights. The answer = the tokens' new features. GPUs exist to do this billions of times per second.`;}\n",
    "window.vmStep=function(){if(k>=3){return;}k++;render();};\n",
    "window.vmAuto=function(){if(timer)return;timer=setInterval(()=>{if(k>=3){clearInterval(timer);timer=null;return;}vmStep();},1400);};\n",
    "window.vmReset=function(){clearInterval(timer);timer=null;k=-1;C[0]=[null,null];C[1]=[null,null];render();};\n",
    "render();\n",
    "})();\n",
    "</script>\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e39e048b",
   "metadata": {},
   "outputs": [],
   "source": [
    "x = torch.tensor([[1., 2., 3.],\n",
    "                  [4., 5., 6.]])          # shape (2, 3): 2 rows, 3 columns\n",
    "print(\"shape:\", x.shape)\n",
    "print(\"x @ x.T =\\n\", x @ x.T)            # matrix multiply: the core operation of every layer\n",
    "print(\"x * 2 =\\n\", x * 2)                # elementwise ops just work"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3febc443",
   "metadata": {},
   "source": [
    "### The gradient: which way to turn each dial\n",
    "\n",
    "A **gradient** answers one question for one dial: *if I turn you up a tiny bit, does the\n",
    "mistake get bigger or smaller, and how fast?* Every dial gets its own answer. Together those\n",
    "answers are the slope under Momo's feet.\n",
    "\n",
    "PyTorch finds them with **autograd**: we mark a tensor with `requires_grad=True` (switch the\n",
    "tape recorder on), do some math, call `.backward()` (read the tape backwards), and each\n",
    "dial's answer appears in `.grad`.\n",
    "\n",
    "Training is only this, repeated thousands of times: measure the mistake, get the gradients,\n",
    "turn every dial a small step the other way.\n",
    "\n",
    "Start with one dial. Momo's coconut sits on a bowl-shaped hill where height is the mistake.\n",
    "Drag the dial or press **Roll** and watch it find the bottom."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4e72fa2e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Interactive: one dial. Momo's coconut rolls to the bottom of the bowl.\n",
    "from IPython.display import HTML, display\n",
    "display(HTML(r\"\"\"<div id=\"vg\" style=\"font-family:-apple-system,Segoe UI,Helvetica,Arial,sans-serif;color:#e8e8e8;background:#1e1e1e;border-radius:12px;padding:16px;max-width:900px\">\n",
    "<style>#vg button{background:#333;color:#eee;border:1px solid #777;border-radius:6px;padding:6px 12px;margin-right:6px;cursor:pointer}#vg .cap{font-size:13px;line-height:1.5;margin-top:8px;background:#262626;border-radius:8px;padding:10px}</style>\n",
    "<div style=\"font-size:15px;font-weight:600;margin-bottom:6px\">Momo's coconut rolling to the bottom of the bowl. Drag the dial, or let it roll.</div>\n",
    "<svg id=\"vgSvg\" width=\"560\" height=\"260\" style=\"background:#262626;border-radius:8px\"></svg>\n",
    "<div style=\"margin-top:8px\"><label style=\"font-size:13px\">weight w <input id=\"vgW\" type=\"range\" min=\"-4\" max=\"8\" step=\"any\" value=\"7\" style=\"width:260px;vertical-align:middle\" oninput=\"vgDraw(+this.value)\"></label>\n",
    "<button onclick=\"vgStep()\">Take one step</button><button onclick=\"vgAuto()\">Roll (auto)</button></div>\n",
    "<div class=\"cap\" id=\"vgCap\"></div>\n",
    "</div>\n",
    "<script>\n",
    "(function(){\n",
    "const svg=document.getElementById('vgSvg'),W=560,H=260;const lossOf=w=>0.5*(w-2)*(w-2)+1, gradOf=w=>(w-2);\n",
    "const X=w=>40+(w+4)/12*(W-80), Y=l=>H-30-(l/19)*(H-60);\n",
    "let timer=null;\n",
    "window.vgDraw=function(w){document.getElementById('vgW').value=w;let d='';for(let x=-4;x<=8;x+=0.1){d+=(d?'L':'M')+X(x).toFixed(1)+','+Y(lossOf(x)).toFixed(1);}\n",
    " const l=lossOf(w),g=gradOf(w),lr=0.3,wn=w-lr*g;\n",
    " svg.innerHTML=`<path d=\"${d}\" fill=\"none\" stroke=\"#5a8ab8\" stroke-width=\"3\"/>\n",
    " <text x=\"${W/2}\" y=\"${H-6}\" fill=\"#aaa\" font-size=\"12\" text-anchor=\"middle\">dial setting w</text>\n",
    " <text x=\"14\" y=\"${H/2}\" fill=\"#aaa\" font-size=\"12\" transform=\"rotate(-90 14,${H/2})\" text-anchor=\"middle\">loss (how wrong)</text>\n",
    " ${Math.abs(w-2)<0.05?'':`<line x1=\"${X(w)}\" y1=\"${Y(l)}\" x2=\"${X(wn)}\" y2=\"${Y(l)}\" stroke=\"#ffb347\" stroke-width=\"3\" marker-end=\"url(#ar)\"/>`}\n",
    " <defs><marker id=\"ar\" markerWidth=\"8\" markerHeight=\"8\" refX=\"6\" refY=\"4\" orient=\"auto\"><path d=\"M0,0 L8,4 L0,8 z\" fill=\"#ffb347\"/></marker></defs>\n",
    " <circle cx=\"${X(w)}\" cy=\"${Y(l)}\" r=\"9\" fill=\"#ffb347\" stroke=\"#fff\" stroke-width=\"2\"/>\n",
    " <text x=\"${X(2)}\" y=\"${Y(1)+22}\" fill=\"#6fcf97\" font-size=\"12\" text-anchor=\"middle\">best setting (smallest mistake)</text>`;\n",
    " document.getElementById('vgCap').innerHTML=`w = <b>${w.toFixed(2)}</b> &nbsp; loss = <b>${l.toFixed(2)}</b> &nbsp; gradient = <b>${g.toFixed(2)}</b><br>The gradient is the slope under the coconut. Positive slope means \"loss goes up if w grows\", so we move w <i>down</i> by learning rate &times; gradient: ${w.toFixed(2)} &minus; 0.3 &times; ${g.toFixed(2)} = <b>${wn.toFixed(2)}</b>.<br>${Math.abs(w-2)<0.05?'<b>At the bottom.</b> The slope is zero here, so there is nothing left to fix: this is the best setting for this one dial. ':''}Picture Momo in fog feeling which way the ground slopes and stepping downhill. Training does this for all 5.9 million dials at once, thousands of times.`;\n",
    " return wn;};\n",
    "window.vgStep=function(){const w=+document.getElementById('vgW').value;const wn=vgDraw(w);vgDraw(Math.abs(wn-2)<0.03?2:wn);};\n",
    "window.vgAuto=function(){if(timer){clearInterval(timer);timer=null;return;}timer=setInterval(()=>{const w=+document.getElementById('vgW').value;if(Math.abs(w-2)<0.03){clearInterval(timer);timer=null;return;}vgStep();},350);};\n",
    "vgDraw(7);\n",
    "})();\n",
    "</script>\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8c2b93f",
   "metadata": {},
   "source": [
    "### Momo in the foggy jungle: thorn bushes, marks, and the slope\n",
    "\n",
    "The coconut was a toy version. Now Momo himself is the walker, because his position *is*\n",
    "his dials. With millions of dials the hill is not a bowl but a whole jungle valley, and Momo\n",
    "is in fog. Here is the same idea as a story, because this is the part people picture wrongly.\n",
    "\n",
    "**The hill is the mistake.** Height is \"how wrong Momo is right now\". The grove at the\n",
    "bottom is the set of dial settings that makes the fewest mistakes. Momo's position *is* his\n",
    "dials. One step is one dial turn.\n",
    "\n",
    "**A thorn bush is a step that made things worse.** Momo tried a direction and the mistake\n",
    "went up. In the widget he steps back and marks the spot with a red X. Real training is blunter: it\n",
    "never steps back. Momo stays where he landed, reads the slope again, and the slope now points\n",
    "back downhill. The correction comes from the next reading, not from undoing the step.\n",
    "\n",
    "**What the gradient adds.** A real monkey learns a thorn bush is there by walking into it.\n",
    "The gradient is better than that. It is the slope under the feet, felt *before* stepping.\n",
    "Momo gets the slope for all 5.9 million dials in one go, without trying any of them (the\n",
    "blame game, right after this widget, shows how). Most of the time he never touches a bush.\n",
    "Guessing instead would mean testing 5.9 million directions one at a time.\n",
    "\n",
    "**So why does training still sometimes go uphill?**\n",
    "- The stride was too long. That is the **learning rate**. The slope was right, but Momo\n",
    "  overshot the grove and landed on the far wall. The next slope reading points back.\n",
    "- The reading was noisy. Each step uses one **batch** (a handful of story pieces, not the\n",
    "  whole library), so the slope is an estimate.\n",
    "\n",
    "**The marks on the map are real, in two forms.**\n",
    "- *The dials themselves.* Every step is written into the dials. There is no separate map;\n",
    "  after training Momo *is* the map. (Later lessons can partly overwrite earlier ones, which\n",
    "  is why the stories are shuffled and why the last steps are tiny.)\n",
    "- *The optimizer's memory.* The dial-turning rule we use, **AdamW**, keeps a running average\n",
    "  of recent slopes. That is Momo remembering \"the last ten readings all said left, so one odd\n",
    "  reading saying right is probably a bump; keep going left\". One bush does not fool him.\n",
    "\n",
    "**Fastest and most accurate.** Short careful steps at the start (**warm-up**, the ground is\n",
    "unknown), full stride in the middle, tiny steps near the grove (**cosine decay**) so Momo\n",
    "stops on the floor instead of bouncing between the walls.\n",
    "\n",
    "Try both ways of walking below and compare the step and bush counts."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2c8b20dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Interactive: Momo in the foggy jungle. Guess and hit thorn bushes, or read the slope first.\n",
    "from IPython.display import HTML, display\n",
    "display(HTML(r\"\"\"<div id=\"vh\" style=\"font-family:-apple-system,Segoe UI,Helvetica,Arial,sans-serif;color:#e8e8e8;background:#1e1e1e;border-radius:12px;padding:16px;max-width:960px\">\n",
    "<style>\n",
    "#vh button{background:#333;color:#eee;border:1px solid #777;border-radius:6px;padding:6px 12px;margin:0 6px 6px 0;cursor:pointer;font-size:13px}\n",
    "#vh button:hover{background:#444}\n",
    "#vh button.g{border-color:#6fcf97}#vh button.r{border-color:#ff6b6b}\n",
    "#vh .cap{font-size:13px;line-height:1.5;margin-top:8px;background:#262626;border-radius:8px;padding:10px}\n",
    "#vh .log{font-size:12px;background:#262626;border-radius:8px;padding:8px 10px;height:150px;overflow:auto;line-height:1.5}\n",
    "#vh .stat{display:inline-block;padding:4px 10px;border-radius:6px;background:#2a2a2a;border:1px solid #555;margin:2px 6px 2px 0;font-size:12px}\n",
    "</style>\n",
    "<div style=\"font-size:15px;font-weight:600;margin-bottom:4px\">Momo in the foggy jungle: thorn bushes, marks, and the slope under his feet</div>\n",
    "<div style=\"font-size:12px;color:#aaa;margin-bottom:8px\">The map is the mistake score (the loss). Dark blue is low (good), yellow is high (bad). The flag is the banana grove: the best set of dials. Momo cannot see the map; he only feels the ground under his feet.</div>\n",
    "<div style=\"display:flex;gap:16px;flex-wrap:wrap\">\n",
    "  <div><canvas id=\"vhC\" width=\"520\" height=\"360\" style=\"border-radius:8px;background:#262626\"></canvas></div>\n",
    "  <div style=\"flex:1;min-width:300px\">\n",
    "    <div style=\"font-size:12px;color:#aaa;margin-bottom:4px\">TWO WAYS TO WALK</div>\n",
    "    <button class=\"r\" onclick=\"vhBlind()\">Guess a step (may hit a thorn bush)</button>\n",
    "    <button class=\"g\" onclick=\"vhGrad()\">Read the slope, then step</button><br>\n",
    "    <button onclick=\"vhAuto('blind')\">Auto: guessing</button>\n",
    "    <button onclick=\"vhAuto('grad')\">Auto: slope</button>\n",
    "    <button onclick=\"vhReset()\">Reset</button>\n",
    "    <div style=\"margin:6px 0\">\n",
    "      <label style=\"font-size:12px\">Stride (learning rate) <input id=\"vhLr\" type=\"range\" min=\"1\" max=\"20\" value=\"4\" style=\"vertical-align:middle;width:120px\"></label>\n",
    "      <label style=\"font-size:12px;margin-left:8px\"><input id=\"vhMem\" type=\"checkbox\" checked> memory of recent slopes (momentum)</label>\n",
    "    </div>\n",
    "    <div><span class=\"stat\" id=\"vhSteps\">steps: 0</span><span class=\"stat\" id=\"vhTrees\">thorn bushes: 0</span><span class=\"stat\" id=\"vhLoss\">loss: 0</span></div>\n",
    "    <div style=\"font-size:12px;color:#aaa;margin:8px 0 4px\">MOMO'S NOTEBOOK (marks on the map)</div>\n",
    "    <div class=\"log\" id=\"vhLog\">Nothing tried yet.</div>\n",
    "  </div>\n",
    "</div>\n",
    "<div class=\"cap\" id=\"vhCap\"></div>\n",
    "</div>\n",
    "<script>\n",
    "(function(){\n",
    "const C=document.getElementById('vhC'),ctx=C.getContext('2d'),W=C.width,H=C.height;\n",
    "// loss landscape in map coordinates (0..1, 0..1): a tilted bowl with two bumps (the \"trees\" of the terrain)\n",
    "const goal=[0.72,0.68];\n",
    "const loss=(x,y)=>{const dx=x-goal[0],dy=y-goal[1];let l=6*(dx*dx+dy*dy);\n",
    "  l+=1.1*Math.exp(-((x-0.40)**2+(y-0.55)**2)/0.012);l+=0.9*Math.exp(-((x-0.58)**2+(y-0.30)**2)/0.010);return l;};\n",
    "const grad=(x,y)=>{const h=1e-3;return [(loss(x+h,y)-loss(x-h,y))/(2*h),(loss(x,y+h)-loss(x,y-h))/(2*h)];};\n",
    "const px=x=>x*W, py=y=>(1-y)*H;\n",
    "let bg=null,pos,trail,xs,steps,trees,mem,timer=null,mode='';\n",
    "let minL=Infinity;for(let i=0;i<=100;i++)for(let j=0;j<=100;j++){const v=loss(i/100,j/100);if(v<minL)minL=v;}\n",
    "function paintBg(){const img=ctx.createImageData(W,H);let mx=0;const vals=new Float32Array(W*H);\n",
    "  for(let j=0;j<H;j++)for(let i=0;i<W;i++){const v=loss(i/W,1-j/H);vals[j*W+i]=v;if(v>mx)mx=v;}\n",
    "  for(let k=0;k<W*H;k++){const t=Math.min(1,vals[k]/mx);const r=Math.round(30+225*t),g=Math.round(40+170*t*t),b=Math.round(110*(1-t)+40);\n",
    "    img.data[k*4]=r;img.data[k*4+1]=g;img.data[k*4+2]=b;img.data[k*4+3]=255;}\n",
    "  ctx.putImageData(img,0,0);\n",
    "  // contour-ish rings\n",
    "  ctx.strokeStyle='rgba(255,255,255,0.12)';ctx.lineWidth=1;\n",
    "  for(let lv=0.3;lv<4;lv+=0.35){ctx.beginPath();let first=true;for(let a=0;a<=Math.PI*2+0.05;a+=0.05){\n",
    "    // crude: walk outward from goal until loss>=lv\n",
    "    let r=0;for(;r<1.2;r+=0.004){if(loss(goal[0]+r*Math.cos(a),goal[1]+r*Math.sin(a))>=lv)break;}\n",
    "    const x=px(goal[0]+r*Math.cos(a)),y=py(goal[1]+r*Math.sin(a));if(first){ctx.moveTo(x,y);first=false;}else ctx.lineTo(x,y);}ctx.stroke();}\n",
    "  bg=ctx.getImageData(0,0,W,H);}\n",
    "function draw(){ctx.putImageData(bg,0,0);\n",
    "  // flag\n",
    "  ctx.fillStyle='#fff';ctx.fillRect(px(goal[0])-1,py(goal[1])-22,2,22);ctx.fillStyle='#6fcf97';ctx.beginPath();ctx.moveTo(px(goal[0]),py(goal[1])-22);ctx.lineTo(px(goal[0])+14,py(goal[1])-17);ctx.lineTo(px(goal[0]),py(goal[1])-12);ctx.fill();\n",
    "  ctx.font='11px sans-serif';ctx.fillStyle='#fff';ctx.fillText('banana grove (best dials)',px(goal[0])-128,py(goal[1])-26);\n",
    "  // trail\n",
    "  ctx.strokeStyle='#6fcf97';ctx.lineWidth=2;ctx.beginPath();trail.forEach((p,i)=>{i?ctx.lineTo(px(p[0]),py(p[1])):ctx.moveTo(px(p[0]),py(p[1]));});ctx.stroke();\n",
    "  trail.forEach(p=>{ctx.fillStyle='#6fcf97';ctx.beginPath();ctx.arc(px(p[0]),py(p[1]),3,0,7);ctx.fill();});\n",
    "  // trees (bad tries)\n",
    "  xs.forEach(p=>{ctx.strokeStyle='#ff6b6b';ctx.lineWidth=2.5;const x=px(p[0]),y=py(p[1]);ctx.beginPath();ctx.moveTo(x-6,y-6);ctx.lineTo(x+6,y+6);ctx.moveTo(x+6,y-6);ctx.lineTo(x-6,y+6);ctx.stroke();});\n",
    "  // slope arrow (what the gradient says)\n",
    "  const arrived=loss(pos[0],pos[1])<minL+0.015;\n",
    "  const g=grad(pos[0],pos[1]);const n=Math.hypot(g[0],g[1])||1;const ax=-g[0]/n*34,ay=g[1]/n*34;\n",
    "  if(!arrived){ctx.strokeStyle='#ffb347';ctx.lineWidth=3;ctx.beginPath();ctx.moveTo(px(pos[0]),py(pos[1]));ctx.lineTo(px(pos[0])+ax,py(pos[1])+ay);ctx.stroke();\n",
    "  ctx.fillStyle='#ffb347';ctx.beginPath();const ang=Math.atan2(ay,ax);ctx.moveTo(px(pos[0])+ax,py(pos[1])+ay);ctx.lineTo(px(pos[0])+ax-9*Math.cos(ang-0.4),py(pos[1])+ay-9*Math.sin(ang-0.4));ctx.lineTo(px(pos[0])+ax-9*Math.cos(ang+0.4),py(pos[1])+ay-9*Math.sin(ang+0.4));ctx.fill();}\n",
    "  // hiker\n",
    "  ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(px(pos[0]),py(pos[1]),8,0,7);ctx.fill();ctx.fillStyle='#1e1e1e';ctx.font='11px sans-serif';ctx.fillText('\\u{1F435}',px(pos[0])-6,py(pos[1])+4);\n",
    "  const l=loss(pos[0],pos[1]);\n",
    "  document.getElementById('vhSteps').textContent='steps: '+steps;document.getElementById('vhTrees').textContent='thorn bushes: '+trees;document.getElementById('vhLoss').textContent='loss: '+l.toFixed(2);\n",
    "  if(l<minL+0.015){document.getElementById('vhCap').innerHTML='<b>Arrived.</b> Momo is in the banana grove: these dial settings make the fewest mistakes. Steps: <b>'+steps+'</b>, thorn bushes: <b>'+trees+'</b>. Press Reset and try the other way of walking.';if(timer){clearInterval(timer);timer=null;}}\n",
    "}\n",
    "function logLine(html){const L=document.getElementById('vhLog');if(L.textContent.startsWith('Nothing'))L.innerHTML='';L.innerHTML='<div>'+html+'</div>'+L.innerHTML;}\n",
    "const lr=()=>0.01*(+document.getElementById('vhLr').value);\n",
    "window.vhBlind=function(){steps++;const a=Math.random()*Math.PI*2;const d=lr()*2.5;const nx=Math.min(0.98,Math.max(0.02,pos[0]+Math.cos(a)*d)),ny=Math.min(0.98,Math.max(0.02,pos[1]+Math.sin(a)*d));\n",
    "  const before=loss(pos[0],pos[1]),after=loss(nx,ny);\n",
    "  if(after>=before){trees++;xs.push([nx,ny]);logLine('&#10060; tried '+dir(a)+': loss went <b>up</b> '+before.toFixed(2)+' &rarr; '+after.toFixed(2)+'. Thorn bush. Marked it, stepped back.');\n",
    "    document.getElementById('vhCap').innerHTML='<b>Thorn bush.</b> The guess made the mistake worse, so Momo steps back and marks the spot with a red X. Guessing finds the grove eventually, but it pays for every bush. Real training cannot afford this: Momo has 5.9 million dials, and guessing would mean trying each one, one direction at a time.';}\n",
    "  else{pos=[nx,ny];trail.push(pos.slice());logLine('&#10004; tried '+dir(a)+': loss went down '+before.toFixed(2)+' &rarr; '+after.toFixed(2)+'. Kept it.');\n",
    "    document.getElementById('vhCap').innerHTML='<b>Lucky guess.</b> The mistake went down, so the step is kept and the green trail records it.';}\n",
    "  draw();};\n",
    "window.vhGrad=function(){steps++;let g=grad(pos[0],pos[1]);\n",
    "  // momentum: remember recent slopes\n",
    "  if(document.getElementById('vhMem').checked){mem=[0.8*mem[0]+0.2*g[0],0.8*mem[1]+0.2*g[1]];g=mem;}\n",
    "  const n=Math.hypot(g[0],g[1])||1;const d=Math.min(lr()*2.5,lr()*n);const nx=Math.min(0.98,Math.max(0.02,pos[0]-g[0]/n*d)),ny=Math.min(0.98,Math.max(0.02,pos[1]-g[1]/n*d));\n",
    "  const before=loss(pos[0],pos[1]),after=loss(nx,ny);pos=[nx,ny];trail.push(pos.slice());\n",
    "  if(after>=before){trees++;xs.push([nx,ny]);logLine('&#9888; slope said '+dir(Math.atan2(-g[1],-g[0]))+' but loss went <b>up</b> '+before.toFixed(2)+' &rarr; '+after.toFixed(2)+' (stride too long, or noisy reading). Next reading will point back.');\n",
    "    document.getElementById('vhCap').innerHTML='<b>Overshot.</b> The slope was right but the stride was too long, so Momo landed on the far wall. Nothing is lost: the next slope reading points straight back. Shorten the stride (a lower learning rate) and this stops happening. That is why training ends with tiny steps.';}\n",
    "  else{logLine('&#10004; slope said '+dir(Math.atan2(-g[1],-g[0]))+': loss '+before.toFixed(2)+' &rarr; '+after.toFixed(2)+'.');\n",
    "    document.getElementById('vhCap').innerHTML='<b>Read the slope first.</b> The orange arrow is the gradient: which way is downhill, felt through the feet before moving. No bush needed. The blame game (next widget) is how Momo gets this arrow for all 5.9 million dials at once. With memory on, the arrow is the average of recent readings, so one odd bump does not fool Momo.';}\n",
    "  draw();};\n",
    "window.vhAuto=function(m){if(timer){clearInterval(timer);timer=null;if(mode===m)return;}if(mode!==m)mem=[0,0];mode=m;timer=setInterval(()=>{m==='blind'?vhBlind():vhGrad();if(steps>400){clearInterval(timer);timer=null;}},m==='blind'?120:260);};\n",
    "window.vhReset=function(){if(timer){clearInterval(timer);timer=null;}pos=[0.12,0.86];trail=[pos.slice()];xs=[];steps=0;trees=0;mem=[0,0];document.getElementById('vhLog').textContent='Nothing tried yet.';\n",
    "  document.getElementById('vhCap').innerHTML='Momo starts top-left, in fog. Press the <b>red</b> button to guess a step. Press the <b>green</b> button to feel the slope first. Which way reaches the banana grove with fewer steps and fewer thorn bushes? Then push the stride slider to the right and watch what a too-long stride does.';draw();};\n",
    "function dir(a){const d=['east','north-east','north','north-west','west','south-west','south','south-east'];return d[Math.round(((a%(2*Math.PI))+2*Math.PI)%(2*Math.PI)/(Math.PI/4))%8];}\n",
    "paintBg();vhReset();\n",
    "})();\n",
    "</script>\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ea803726",
   "metadata": {},
   "source": [
    "### How does Momo feel the slope without walking? The blame game\n",
    "\n",
    "This is the part most explanations skip, and it is the real trick inside every AI.\n",
    "\n",
    "Momo has a job: pick bananas for Ella. Ella wants 10. Momo's rule has two dials:\n",
    "*bananas = dial A × bunch size + dial B*. Today the bunch size is 2. His dials say A = 1 and\n",
    "B = 3, so he picks 1 × 2 + 3 = 5 bananas. Ella wanted 10. The gap is 5. We score the mistake\n",
    "as the gap times itself, 5 × 5 = 25 (that is what \"squared\" means), so a big gap hurts much\n",
    "more than a small one.\n",
    "\n",
    "Which way should Momo turn dial A? And dial B? He could try: turn A a little, pick again,\n",
    "see if the mistake shrank. That works, but he must try every dial separately. The real Momo\n",
    "has 5.9 million dials. Far too slow.\n",
    "\n",
    "Instead Momo plays the **blame game**. While picking he wrote every step on a tape:\n",
    "\"multiplied A by 2\", \"added B\", \"squared the gap\". Now he reads the tape **backwards** and\n",
    "asks each step: *\"if you went up by one tiny bit, what would the mistake do?\"* Each step\n",
    "answers by taking the answer from the step after it and multiplying by how hard it pushes.\n",
    "At the start of the tape every dial has a number: \"turn me this way, this much\". Those\n",
    "numbers are the gradient, found for all dials in one backward trip.\n",
    "\n",
    "Grown-up words: the tape is **autograd**, reading it backwards is **backpropagation**, and\n",
    "\"multiply by how hard I push\" at each step is the **chain rule**. One detail the game leaves\n",
    "out: when one step feeds *two* later steps (a trick called a residual connection, which we meet\n",
    "in Part 6), the two blames are added together. And the game's mistake is a squared gap; the\n",
    "real Momo scores his mistake differently (cross-entropy, explained in Part 7). Same game,\n",
    "different first step. Press each stage below. The slow-way check lets you see that the\n",
    "backward trip tells the truth."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dc8e8406",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Interactive: the blame game. Pick, pass the blame back, turn the dials.\n",
    "from IPython.display import HTML, display\n",
    "display(HTML(r\"\"\"<div id=\"va\" style=\"font-family:-apple-system,Segoe UI,Helvetica,Arial,sans-serif;color:#e8e8e8;background:#1e1e1e;border-radius:12px;padding:16px;max-width:960px\">\n",
    "<style>\n",
    "#va button{background:#333;color:#eee;border:1px solid #777;border-radius:6px;padding:6px 12px;margin:0 6px 6px 0;cursor:pointer;font-size:13px}\n",
    "#va button:hover{background:#444}\n",
    "#va button.on{border-color:#ffb347;background:#3a2e1a}\n",
    "#va .box{display:inline-block;vertical-align:top;text-align:center;margin:0 2px;min-width:78px}\n",
    "#va .node{border:2px solid #555;border-radius:10px;background:#2a2a2a;padding:8px 6px;font-size:12px;min-height:54px}\n",
    "#va .node.dial{border-color:#5a8ab8}\n",
    "#va .node.lit{border-color:#5a8ab8;box-shadow:0 0 10px #5a8ab888}\n",
    "#va .node.blame{border-color:#ff6b6b;box-shadow:0 0 10px #ff6b6b88}\n",
    "#va .fw{color:#8ec6ff;font-weight:700;font-size:15px;min-height:20px}\n",
    "#va .bw{color:#ff8a8a;font-weight:700;font-size:13px;min-height:18px}\n",
    "#va .op{display:inline-block;vertical-align:top;margin-top:38px;color:#aaa;font-size:18px;width:20px;text-align:center}\n",
    "#va .cap{font-size:13px;line-height:1.55;margin-top:10px;background:#262626;border-radius:8px;padding:10px}\n",
    "#va .knob{width:36px;height:36px;border-radius:50%;border:2px solid #8ec6ff;margin:4px auto;position:relative;background:#1e1e1e}\n",
    "#va .knob i{position:absolute;left:50%;top:2px;width:2px;height:14px;background:#ffb347;margin-left:-1px;transform-origin:1px 16px}\n",
    "#va .lbl{font-size:11px;color:#aaa;text-transform:uppercase;letter-spacing:.05em}\n",
    "</style>\n",
    "<div style=\"font-size:15px;font-weight:600;margin-bottom:4px\">The blame game: how Momo feels the slope without walking (this is autograd)</div>\n",
    "<div style=\"font-size:12px;color:#aaa;margin-bottom:10px\">Momo picks bananas for Ella using two dials in his head. Ella wants <b>10</b> bananas. Blue numbers flow forward while Momo picks. Red numbers are the blame flowing backward.</div>\n",
    "\n",
    "<div id=\"vaRow\" style=\"display:flex;flex-wrap:wrap;align-items:flex-start;padding:4px 0\">\n",
    "  <div class=\"box\"><div class=\"lbl\">bunch size</div><div class=\"node\" id=\"nx\"><div style=\"font-size:11px;color:#aaa\">today</div><div class=\"fw\" id=\"fx\">2</div></div><div class=\"bw\"></div></div>\n",
    "  <div class=\"op\">&times;</div>\n",
    "  <div class=\"box\"><div class=\"lbl\">dial A</div><div class=\"node dial\" id=\"nA\"><div class=\"knob\"><i id=\"kA\"></i></div><div class=\"fw\" id=\"fA\"></div></div><div class=\"bw\" id=\"bA\"></div></div>\n",
    "  <div class=\"op\">=</div>\n",
    "  <div class=\"box\"><div class=\"lbl\">A &times; bunch</div><div class=\"node\" id=\"nm\"><div style=\"font-size:11px;color:#aaa\">step 1</div><div class=\"fw\" id=\"fm\"></div></div><div class=\"bw\" id=\"bm\"></div></div>\n",
    "  <div class=\"op\">+</div>\n",
    "  <div class=\"box\"><div class=\"lbl\">dial B</div><div class=\"node dial\" id=\"nB\"><div class=\"knob\"><i id=\"kB\"></i></div><div class=\"fw\" id=\"fB\"></div></div><div class=\"bw\" id=\"bB\"></div></div>\n",
    "  <div class=\"op\">=</div>\n",
    "  <div class=\"box\"><div class=\"lbl\">Momo's guess</div><div class=\"node\" id=\"ng\"><div style=\"font-size:11px;color:#aaa\">bananas picked</div><div class=\"fw\" id=\"fg\"></div></div><div class=\"bw\" id=\"bg\"></div></div>\n",
    "  <div class=\"op\">vs</div>\n",
    "  <div class=\"box\"><div class=\"lbl\">Ella wants</div><div class=\"node\" id=\"nt\"><div style=\"font-size:11px;color:#aaa\">target</div><div class=\"fw\">10</div></div><div class=\"bw\"></div></div>\n",
    "  <div class=\"op\">&rarr;</div>\n",
    "  <div class=\"box\"><div class=\"lbl\">mistake (loss)</div><div class=\"node\" id=\"nL\"><div style=\"font-size:11px;color:#aaa\">(guess &minus; 10)&sup2;</div><div class=\"fw\" id=\"fL\"></div></div><div class=\"bw\" id=\"bL\"></div></div>\n",
    "</div>\n",
    "\n",
    "<div style=\"margin-top:8px\">\n",
    "  <button id=\"vaB1\" onclick=\"vaCook()\">1. Pick (forward)</button>\n",
    "  <button id=\"vaB2\" onclick=\"vaBlame()\">2. Pass the blame back (backward)</button>\n",
    "  <button id=\"vaB3\" onclick=\"vaTurn()\">3. Turn the dials (one step)</button>\n",
    "  <button onclick=\"vaWiggle()\">Check it the slow way (wiggle dial A)</button>\n",
    "  <button onclick=\"vaAuto()\">Auto: pick, blame, turn</button>\n",
    "  <button onclick=\"vaReset()\">Reset</button>\n",
    "  <span style=\"font-size:12px;color:#aaa;margin-left:6px\">round <b id=\"vaRound\">0</b></span>\n",
    "</div>\n",
    "<div class=\"cap\" id=\"vaCap\"></div>\n",
    "</div>\n",
    "<script>\n",
    "(function(){\n",
    "const x=2,T=10,lr=0.02;let A,B,m,g,L,gA,gB,gg,phase,round,timer=null,bwT=[];\n",
    "const $=id=>document.getElementById(id);\n",
    "const f2=v=>(Math.round(v*100)/100).toFixed(2);\n",
    "function knobs(){$('kA').style.transform='rotate('+(A*60-60)+'deg)';$('kB').style.transform='rotate('+(B*40-120)+'deg)';$('fA').textContent=f2(A);$('fB').textContent=f2(B);}\n",
    "function clearFw(){['fm','fg','fL'].forEach(i=>$(i).textContent='');}\n",
    "function clearBw(){bwT.forEach(clearTimeout);bwT=[];['bA','bm','bB','bg','bL'].forEach(i=>$(i).innerHTML='');document.querySelectorAll('#va .node').forEach(n=>n.classList.remove('blame','lit'));}\n",
    "function lit(ids){document.querySelectorAll('#va .node').forEach(n=>n.classList.remove('lit'));ids.forEach(i=>$(i).classList.add('lit'));}\n",
    "function btn(n){[1,2,3].forEach(k=>$('vaB'+k).classList.toggle('on',k===n));}\n",
    "window.vaCook=function(){m=A*x;g=m+B;L=(g-T)*(g-T);clearBw();$('fm').textContent=f2(m);$('fg').textContent=f2(g);$('fL').textContent=f2(L);lit(['nx','nA','nm','nB','ng','nt','nL']);phase=1;btn(2);\n",
    "  const d=g-T;\n",
    "  $('vaCap').innerHTML=`<b>Pick.</b> Momo follows his rule: dial A &times; bunch size + dial B = ${f2(A)} &times; 2 + ${f2(B)} = <b>${f2(g)}</b> bananas. Ella wanted 10, so the guess is ${Math.abs(d).toFixed(2)} too ${d<0?'low':'high'}. Mistake = (${f2(g)} &minus; 10)&sup2; = <b>${f2(L)}</b>.<br>While picking, Momo wrote every step on a tape: \"I multiplied A by 2\", \"I added B\", \"I squared the gap\". PyTorch does exactly this; the tape is what <code>requires_grad=True</code> switches on.`;};\n",
    "window.vaBlame=function(){if(phase<1){vaCook();}\n",
    "  const d=g-T;gg=2*d;gB=gg*1;gA=gg*x;phase=2;btn(3);\n",
    "  document.querySelectorAll('#va .node').forEach(n=>n.classList.remove('lit'));\n",
    "  const steps=[\n",
    "   ()=>{$('nL').classList.add('blame');$('bL').innerHTML='start: 1';},\n",
    "   ()=>{$('ng').classList.add('blame');$('bg').innerHTML='blame '+f2(gg);},\n",
    "   ()=>{$('nB').classList.add('blame');$('bB').innerHTML='blame '+f2(gB);$('nm').classList.add('blame');$('bm').innerHTML='blame '+f2(gg);},\n",
    "   ()=>{$('nA').classList.add('blame');$('bA').innerHTML='blame '+f2(gA);},\n",
    "  ];\n",
    "  const gap=timer?150:450;bwT=steps.map((s,i)=>setTimeout(s,i*gap));\n",
    "  $('vaCap').innerHTML=`<b>Pass the blame back.</b> Momo reads the tape <i>backwards</i> and asks every step the same question: <i>\"if you went up by one tiny bit, what would the mistake do?\"</i><br>\n",
    "  &bull; <b>Mistake &larr; guess.</b> The gap is ${f2(d)}. Raising the guess by 1 changes (gap)&sup2; by about 2 &times; gap = <b>${f2(gg)}</b>. ${d<0?'Minus means: a higher guess makes the mistake <i>smaller</i>.':'Plus means: a higher guess makes the mistake <i>bigger</i>.'}<br>\n",
    "  &bull; <b>Guess &larr; dial B.</b> Raise B by 1 and the guess rises by exactly 1. So B's blame = ${f2(gg)} &times; 1 = <b>${f2(gB)}</b>.<br>\n",
    "  &bull; <b>Guess &larr; dial A.</b> Raise A by 1 and the guess rises by 2, because A is multiplied by the bunch size. So A's blame = ${f2(gg)} &times; 2 = <b>${f2(gA)}</b>.<br>\n",
    "  Every answer is <i>the answer from the step after me</i> &times; <i>how hard I push</i>. Multiplying along the chain like this is called the <b>chain rule</b>, and reading the tape backwards is called <b>backpropagation</b>. The two red numbers under the dials, ${f2(gA)} and ${f2(gB)}, are the <b>gradient</b>: the slope under Momo's feet in the foggy jungle, for both dials, found in <b>one</b> backward trip and with no walking.`;};\n",
    "window.vaTurn=function(){if(phase<2){vaBlame();bwT.push(setTimeout(vaTurn,timer?700:1900));return;}\n",
    "  const A0=A,B0=B;A=A-lr*gA;B=B-lr*gB;round++;$('vaRound').textContent=round;knobs();clearBw();clearFw();phase=0;btn(1);\n",
    "  $('vaCap').innerHTML=`<b>Turn the dials against their blame.</b> Stride (learning rate) = 0.02.<br>A = ${f2(A0)} &minus; 0.02 &times; (${f2(gA)}) = <b>${f2(A)}</b><br>B = ${f2(B0)} &minus; 0.02 &times; (${f2(gB)}) = <b>${f2(B)}</b><br>Press <b>Pick</b> again and watch the mistake shrink. This is one step of the foggy jungle walk above: the red numbers are the orange arrow. Our real model has 5.9 million dials, and one backward trip gives the blame for all of them at once.`;};\n",
    "window.vaWiggle=function(){if(phase<1)vaCook();const eps=0.1;const L2=((A+eps)*x+B-T)**2;const dA=L2-L;const pred=(phase>=2?gA:2*(g-T)*x)*eps;\n",
    "  $('vaCap').innerHTML=`<b>The slow way, to check.</b> Turn dial A up by ${eps} and pick again: A = ${f2(A+eps)} gives mistake ${f2(L2)} instead of ${f2(L)}, a change of <b>${f2(dA)}</b>.<br>The blame game predicted ${f2(pred/eps)} &times; ${eps} = <b>${f2(pred)}</b>. Same answer (tiny difference because ${eps} is not <i>truly</i> tiny). But the slow way needed one extra picking round for <i>one</i> dial. With 5.9 million dials that is 5.9 million extra rounds per step. The blame game gets all of them from one backward trip. That is why autograd made modern AI possible.`;};\n",
    "window.vaAuto=function(){if(timer){clearTimeout(timer);timer=null;return;}\n",
    "  const tick=()=>{if(phase===0){vaCook();}else if(phase===1){vaBlame();}else{vaTurn();if(L<0.1){timer=null;$('vaCap').innerHTML+='<br><b>Done:</b> Momo now picks '+f2(g)+' bananas, close enough to 10. Dials A = '+f2(A)+', B = '+f2(B)+'. Press Reset to play again.';return;}}\n",
    "    timer=setTimeout(tick,round<2?1600:600);};\n",
    "  timer=setTimeout(tick,0);};\n",
    "window.vaReset=function(){if(timer){clearTimeout(timer);timer=null;}A=1;B=3;round=0;phase=0;$('vaRound').textContent=0;knobs();clearFw();clearBw();btn(1);\n",
    "  $('vaCap').innerHTML='Momo starts with dial A = 1 and dial B = 3. He has no idea which way to turn them. Press <b>Pick</b>, then <b>Pass the blame back</b>, then <b>Turn the dials</b>. Or press <b>Auto</b>.';};\n",
    "vaReset();\n",
    "})();\n",
    "</script>\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fbbc8f63",
   "metadata": {},
   "source": [
    "Now the blame game in three lines of PyTorch. Three dials, one mistake, `backward()`, and\n",
    "each dial gets its blame. Then we turn every dial a small step against its blame. Walking\n",
    "downhill like this, one stride at a time, has a name: **gradient descent**. `torch.no_grad()`\n",
    "means \"switch the tape recorder off while we turn the dials; this part is not a step to be\n",
    "blamed later\". Below you will see three blames, one per dial, and then each dial has moved\n",
    "a little the other way. That is one lesson. Training is thousands of these."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "db819628",
   "metadata": {},
   "outputs": [],
   "source": [
    "w = torch.tensor([1.0, -1.0, 0.5], requires_grad=True)   # three dials; requires_grad switches the tape on\n",
    "loss = ((x @ w) ** 2).sum()                              # the mistake, which depends on the dials\n",
    "loss.backward()                                          # the blame game: read the tape backwards, fill w.grad\n",
    "print(\"loss:\", loss.item())\n",
    "print(\"d(loss)/d(w):\", w.grad)\n",
    "\n",
    "# One step of gradient descent: turn every dial a little against its blame (stride 0.01).\n",
    "with torch.no_grad():\n",
    "    w -= 0.01 * w.grad\n",
    "print(\"w after one step:\", w)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "99aa0e23",
   "metadata": {},
   "source": [
    "## 2. Data: Ella's bedtime stories (TinyStories)\n",
    "\n",
    "Ella needs stories to read. [TinyStories](https://huggingface.co/datasets/roneneldan/TinyStories)\n",
    "is a public **dataset** (a collection of examples to learn from) of short children's stories\n",
    "written in very simple English. It was created to show that *small* models can learn to write\n",
    "real sentences if the language is simple enough.\n",
    "\n",
    "We stream a slice of it so the download stays small."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fc46895d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from datasets import load_dataset\n",
    "\n",
    "N_STORIES = 20_000   # enough for a tiny model; raise it later if you want a better one\n",
    "\n",
    "stream = load_dataset(\"roneneldan/TinyStories\", split=\"train\", streaming=True)\n",
    "stories = [row[\"text\"].strip() for row in stream.take(N_STORIES)]\n",
    "\n",
    "print(f\"{len(stories):,} stories, {sum(len(s) for s in stories)/1e6:.1f}M characters\")\n",
    "print(\"-\" * 60)\n",
    "print(stories[0][:500])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1eca3fe",
   "metadata": {},
   "source": [
    "### Ella's own stories about Momo\n",
    "\n",
    "TinyStories is full of Lily, Tom and their mums. If we want Momo and Ella to show up when the\n",
    "model writes, the model has to *read* about them. So we add about a thousand short stories\n",
    "about our two characters, built from sentence pools in the same simple style (every story has\n",
    "an opening, Ella's introduction, a small problem, Ella helping, a resolution and an ending).\n",
    "We repeat them three times so they are not drowned out by the 20,000 other stories. This is\n",
    "one way a model is taught new material: mix it into the training text. (Grown-ups more often\n",
    "*fine-tune*, which is the same mixing done to a model that has already been trained.)\n",
    "\n",
    "One more thing happens here. Ella hides the last 200 TinyStories and never reads them to\n",
    "Momo. They are her **quiz**, the **validation set**. After each lesson she asks Momo about\n",
    "*those*, so we learn whether he understands the language or has only memorised the stories he\n",
    "heard. The Momo stories go only into the lessons, never into the quiz, so a repeated story can\n",
    "never make the quiz look better than it is."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "215e5fa4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Momo and Ella stories, built from sentence pools. Same code as scripts/momo_stories.py in the repo.\n",
    "import random\n",
    "\n",
    "PLACES = [\"by the river\", \"under the big mango tree\", \"in the green jungle\", \"at the waterfall\",\n",
    "          \"on the sunny hill\", \"near the little pond\", \"in the tall grass\", \"by the old rock\"]\n",
    "THINGS = [\"a shiny red ball\", \"a yellow banana\", \"a big green leaf\", \"a round grey stone\",\n",
    "          \"a blue feather\", \"a little drum\", \"a brown coconut\", \"a soft pink flower\",\n",
    "          \"a long stick\", \"a red kite\"]\n",
    "FRIENDS = [\"Pip the parrot\", \"Tilly the turtle\", \"Bo the bear cub\", \"Lulu the little lion\"]\n",
    "FOODS = [\"bananas\", \"mangoes\", \"sweet berries\", \"coconuts\", \"crunchy leaves\"]\n",
    "\n",
    "OPENINGS = [\n",
    "    \"Once upon a time, there was a little monkey named Momo.\",\n",
    "    \"Momo was a little monkey who lived {place}.\",\n",
    "    \"One day, Momo the monkey woke up very early.\",\n",
    "    \"Momo was a small monkey with a big smile.\",\n",
    "    \"In the jungle there lived a little monkey called Momo.\",\n",
    "    \"Every morning, Momo the monkey jumped out of his tree.\",\n",
    "]\n",
    "ELLA_INTRO = [\n",
    "    \"His best friend was Ella, a big grey elephant.\",\n",
    "    \"Ella the elephant was his best friend. Ella never forgot anything.\",\n",
    "    \"He loved his friend Ella. Ella was an elephant, and she remembered everything.\",\n",
    "    \"Ella the elephant was big and kind and very wise.\",\n",
    "    \"His friend Ella was an elephant with a long trunk and a good memory.\",\n",
    "]\n",
    "# Each scene: (problem, Ella helps, resolution). They always go together.\n",
    "SCENES = [\n",
    "    (\"Momo wanted to play with {thing}, but he could not find it. He looked {place}. It was not there. He felt sad.\",\n",
    "     \"Ella came slowly. \\\"Do not worry, Momo,\\\" she said. \\\"I remember. You left it {place2}.\\\"\",\n",
    "     \"Momo ran and found {thing} right where Ella said. He hugged her trunk.\"),\n",
    "    (\"Momo climbed very high to get {thing}. Then he looked down and felt scared. \\\"I cannot get down!\\\" he cried.\",\n",
    "     \"Ella walked under the tree and lifted her trunk. \\\"I am here,\\\" she said. \\\"You are safe. Come down slowly.\\\"\",\n",
    "     \"Momo climbed down step by step and jumped into Ella's arms.\"),\n",
    "    (\"It started to rain. Momo got wet and cold. He hid {place} and shivered.\",\n",
    "     \"Ella held a big leaf over Momo like an umbrella. \\\"Stay dry, my friend,\\\" she said.\",\n",
    "     \"The rain stopped. The sun came out. Momo and Ella walked home together.\"),\n",
    "    (\"Momo met {friend} {place}. {friend_short} had {thing} and did not want to share. Momo felt sad.\",\n",
    "     \"Ella walked over and said, \\\"Let us all play together. Sharing makes everyone happy.\\\"\",\n",
    "     \"Momo and {friend_short} became friends and played with {thing} all day.\"),\n",
    "    (\"Momo tried to count his {food}. \\\"One, two, three...\\\" Then he forgot the next number.\",\n",
    "     \"Ella said, \\\"After three comes four. I will count with you.\\\" They counted together.\",\n",
    "     \"Momo counted all the way to ten. Ella was very proud.\"),\n",
    "    (\"Momo had a bad dream. He woke up scared and ran to find Ella.\",\n",
    "     \"Ella sat with Momo until he felt better. \\\"Being scared is okay,\\\" she said. \\\"I am right here.\\\"\",\n",
    "     \"Momo fell asleep next to Ella and had happy dreams.\"),\n",
    "    (\"Momo wanted to tell a story, but he did not know how to start. He sat down {place} and thought and thought.\",\n",
    "     \"Ella said, \\\"Start with 'Once upon a time'. Then say what happens next. One word at a time.\\\"\",\n",
    "     \"Momo told his first story. It was about {thing}. Ella clapped her big ears.\"),\n",
    "    (\"Momo saw a big shadow {place}. He thought it was a monster. He hid behind {thing}.\",\n",
    "     \"Ella laughed softly. \\\"That shadow is only me,\\\" she said. Momo peeked out and laughed too.\",\n",
    "     \"They played shadow games until the sun went down.\"),\n",
    "    (\"Momo was sad because he could not swim like {friend}. He sat {place} with his feet in the water.\",\n",
    "     \"Ella said, \\\"Try again, little one. Every time you try, you learn a little more.\\\" She held him up with her trunk.\",\n",
    "     \"Momo kicked and kicked. Soon he could swim all by himself.\"),\n",
    "    (\"Momo and Ella went {place} to look for {food}. They looked and looked, but they found none.\",\n",
    "     \"Ella thought for a moment. Elephants never forget. \\\"I know a secret place,\\\" she said.\",\n",
    "     \"They found lots of {food} and ate them together under the stars.\"),\n",
    "    (\"Momo broke {thing} that belonged to {friend}. He hid it and felt bad all day.\",\n",
    "     \"Ella said, \\\"A true friend says sorry. Go and tell {friend_short}.\\\"\",\n",
    "     \"Momo said sorry. {friend_short} smiled and said, \\\"We can fix it together.\\\"\"),\n",
    "    (\"Momo wanted to be big like Ella. He stood on {thing} and stretched, but he was still small.\",\n",
    "     \"Ella smiled. \\\"You are small, but you can climb and jump and I cannot,\\\" she said. \\\"We are different, and that is good.\\\"\",\n",
    "     \"Momo climbed to the top of the tree and threw {food} down to Ella. They both laughed.\"),\n",
    "]\n",
    "ENDINGS = [\n",
    "    \"The end.\",\n",
    "    \"Momo was happy. Ella was happy. The end.\",\n",
    "    \"And they were best friends forever. The end.\",\n",
    "    \"\\\"Thank you, Ella,\\\" said Momo. \\\"You always remember.\\\" The end.\",\n",
    "    \"That night, Momo dreamed about his friend Ella. The end.\",\n",
    "]\n",
    "\n",
    "\n",
    "def make_story(rng: random.Random) -> str:\n",
    "    friend = rng.choice(FRIENDS)\n",
    "    place, place2 = rng.sample(PLACES, 2)\n",
    "    slots = dict(place=place, place2=place2, thing=rng.choice(THINGS), friend=friend,\n",
    "                 friend_short=friend.split(\" the \")[0], food=rng.choice(FOODS))\n",
    "    problem, helps, resolution = rng.choice(SCENES)\n",
    "    parts = [rng.choice(OPENINGS), rng.choice(ELLA_INTRO), problem, helps, resolution, rng.choice(ENDINGS)]\n",
    "    return \" \".join(p.format(**slots) for p in parts)\n",
    "\n",
    "\n",
    "def momo_stories(n: int = 1000, seed: int = 0) -> list[str]:\n",
    "    # n distinct stories: repeats from the pools are skipped, so \"unique\" really is unique.\n",
    "    rng = random.Random(seed)\n",
    "    out, seen = [], set()\n",
    "    while len(out) < n:\n",
    "        s = make_story(rng)\n",
    "        if s not in seen:\n",
    "            seen.add(s)\n",
    "            out.append(s)\n",
    "    return out\n",
    "\n",
    "momo = momo_stories(1000, seed=0)\n",
    "val_stories = stories[-200:]                    # Ella's hidden quiz: TinyStories only, never trained on\n",
    "train_stories = stories[:-200] + momo * 3       # 19,800 TinyStories + 3,000 Momo stories (1,000 unique, each 3 times)\n",
    "random.Random(42).shuffle(train_stories)        # shuffle so Momo stories are spread through the lessons\n",
    "stories = train_stories                         # from here on, \"stories\" means the lesson stories\n",
    "print(f\"{len(train_stories):,} lesson stories ({len(momo)*3:,} about Momo, {len(set(momo)):,} unique) | {len(val_stories)} quiz stories\")\n",
    "print(\"-\" * 60)\n",
    "print(momo[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "550278c3",
   "metadata": {},
   "source": [
    "## 3. Momo's first trick: the bigram\n",
    "\n",
    "A language model answers one question: **given what came before, what comes next?**\n",
    "\n",
    "Momo's very first trick is to remember only the *last letter*. He keeps a tray: \"after the\n",
    "letter t, how often does h come next? how often does e?\" That tray is a **bigram model**\n",
    "(bi = two: it looks at pairs of letters). In code it is one `nn.Embedding` table of size\n",
    "*vocab × vocab*. An **embedding table** is just a tray where row number *i* belongs to token *i*.\n",
    "In the introduction the embedding was \"a spot on the meaning map\": that map is this kind of\n",
    "tray, one row per token. Here we borrow the same kind of tray for a different job: row *i*\n",
    "holds \"what tends to follow token *i*\". Same box, different contents.\n",
    "\n",
    "First Momo needs a vocabulary. For now, one token = one letter. **Encode** means turning\n",
    "letters into their numbers; **decode** means turning the numbers back into letters. Momo only\n",
    "ever sees the numbers.\n",
    "\n",
    "Play with the idea first. The widget counts, from a few sentences about Momo and Ella, which\n",
    "letter tends to follow which. Click a letter. That tray *is* the whole model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0b4022c7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Interactive: the bigram table, counted live from a few sentences.\n",
    "from IPython.display import HTML, display\n",
    "display(HTML(r\"\"\"<div id=\"vb\" style=\"font-family:-apple-system,Segoe UI,Helvetica,Arial,sans-serif;color:#e8e8e8;background:#1e1e1e;border-radius:12px;padding:16px;max-width:900px\">\n",
    "<style>#vb .k{display:inline-block;width:26px;height:26px;line-height:26px;text-align:center;margin:2px;border-radius:5px;background:#2f4f6f;border:1px solid #5a8ab8;cursor:pointer;font-size:13px}#vb .k.on{background:#ffb347;color:#000}#vb .bar{height:14px;background:#6fcf97;border-radius:3px}#vb .cap{font-size:13px;line-height:1.5;margin-top:8px;background:#262626;border-radius:8px;padding:10px}</style>\n",
    "<div style=\"font-size:15px;font-weight:600;margin-bottom:6px\">Bigram model: click a letter to see what it thinks comes next.</div>\n",
    "<div id=\"vbKeys\"></div>\n",
    "<div id=\"vbOut\" style=\"margin-top:10px;min-height:120px\"></div>\n",
    "<div class=\"cap\">A bigram model is nothing but this table: for every character, how often each other character followed it in the training text. It is counted here live from a few sample sentences. It cannot remember anything further back than one letter, which is why its stories are gibberish. The GPT fixes that with attention.</div>\n",
    "</div>\n",
    "<script>\n",
    "(function(){\n",
    "const corpus=\"once upon a time there was a little monkey called momo. momo liked to play in the jungle with his friend ella the elephant. one day they found a shiny red ball. they were very happy and played all day. the end. ella had a big red balloon. she gave it to momo. momo smiled and said thank you.\";\n",
    "const keys=\"abcdefghijklmnopqrstuvwxyz. \".split('');const counts={};\n",
    "for(let i=0;i<corpus.length-1;i++){const a=corpus[i],b=corpus[i+1];counts[a]=counts[a]||{};counts[a][b]=(counts[a][b]||0)+1;}\n",
    "const kd=document.getElementById('vbKeys');keys.forEach(k=>{const s=document.createElement('span');s.className='k';s.textContent=k===' '?'␣':k;s.onclick=()=>vbShow(k);kd.appendChild(s);});\n",
    "window.vbShow=function(k){[...kd.children].forEach((s,i)=>s.classList.toggle('on',keys[i]===k));const c=counts[k]||{};const tot=Object.values(c).reduce((a,b)=>a+b,0);\n",
    " const all=Object.entries(c).sort((a,b)=>b[1]-a[1]);const rows=all.slice(0,6);const rest=all.slice(6).reduce((s,[,n])=>s+n,0);\n",
    " document.getElementById('vbOut').innerHTML=tot?`<div style=\"font-size:13px;margin-bottom:6px\">After <b>\"${k===' '?'space':k}\"</b> (seen ${tot} times), the next character was:</div>`+rows.map(([ch,n])=>`<div style=\"display:flex;align-items:center;gap:8px;font-size:13px\"><span style=\"width:40px;text-align:right\">${ch===' '?'␣':ch}</span><div class=\"bar\" style=\"width:${Math.round(n/tot*300)}px\"></div><span style=\"color:#aaa\">${Math.round(n/tot*100)}%</span></div>`).join('')+(rest?`<div style=\"display:flex;align-items:center;gap:8px;font-size:13px;color:#888\"><span style=\"width:40px;text-align:right\">other</span><div class=\"bar\" style=\"width:${Math.round(rest/tot*300)}px;background:#555\"></div><span>${Math.round(rest/tot*100)}% (${all.length-6} other letters)</span></div>`:''):`<div style=\"font-size:13px;color:#aaa\">\"${k}\" never appears in the sample text, so the model has no idea.</div>`;};\n",
    "vbShow('t');\n",
    "})();\n",
    "</script>\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc2334a9",
   "metadata": {},
   "source": [
    "Now the same thing in code: first the letter vocabulary and the encode/decode pair."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ebe181d",
   "metadata": {},
   "outputs": [],
   "source": [
    "text = \"\\n\".join(stories)\n",
    "chars = sorted(set(text))\n",
    "char_vocab = len(chars)\n",
    "stoi = {c: i for i, c in enumerate(chars)}   # string -> int\n",
    "itos = {i: c for c, i in stoi.items()}       # int -> string\n",
    "encode_c = lambda s: [stoi[c] for c in s]\n",
    "decode_c = lambda ids: \"\".join(itos[i] for i in ids)\n",
    "\n",
    "print(\"character vocab size:\", char_vocab)\n",
    "print(\"encode('hello') ->\", encode_c(\"hello\"))\n",
    "print(\"decode back     ->\", decode_c(encode_c(\"hello\")))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b5cc8528",
   "metadata": {},
   "source": [
    "Next, Momo's first lessons. Every lesson in this notebook, here and for the big model in\n",
    "Part 7, is the same **five moves**:\n",
    "\n",
    "1. Ella picks a **batch**: a handful of short pieces of text (here 64 pieces of 8 letters),\n",
    "   and for each piece the same text shifted by one letter, which is the list of right answers.\n",
    "2. Momo guesses the next letter at every position.\n",
    "3. Ella scores the guesses with the **loss**.\n",
    "4. The blame game runs backwards: every dial gets its gradient.\n",
    "5. The optimizer turns the dials.\n",
    "\n",
    "Then Momo writes: start from one letter, turn his scores into percentages, spin a wheel whose\n",
    "slices are those percentages to pick the next letter, and repeat. We use only the first two\n",
    "million letters so this toy runs in seconds."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c1c9d084",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Bigram(nn.Module):\n",
    "    def __init__(self, vocab):\n",
    "        super().__init__()\n",
    "        self.table = nn.Embedding(vocab, vocab)   # row i = scores for \"what follows token i\"\n",
    "\n",
    "    def forward(self, idx, targets=None):\n",
    "        logits = self.table(idx)                  # (B, T) -> (B, T, vocab)\n",
    "        loss = None\n",
    "        if targets is not None:\n",
    "            # cross-entropy compares predicted scores with the true next token\n",
    "            loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1))\n",
    "        return logits, loss\n",
    "\n",
    "    @torch.no_grad()\n",
    "    def generate(self, idx, max_new_tokens):\n",
    "        for _ in range(max_new_tokens):\n",
    "            logits, _ = self(idx[:, -1:])                       # only the last token matters\n",
    "            probs = F.softmax(logits[:, -1, :], dim=-1)             # scores -> percentages\n",
    "            idx = torch.cat([idx, torch.multinomial(probs, 1)], dim=1)  # spin a wheel whose slices are the percentages\n",
    "        return idx\n",
    "\n",
    "char_data = torch.tensor(encode_c(text[:2_000_000]), dtype=torch.long)   # first 2 million letters are plenty for a toy\n",
    "\n",
    "def get_char_batch(batch_size=64, block_size=8):\n",
    "    ix = torch.randint(len(char_data) - block_size - 1, (batch_size,))\n",
    "    x = torch.stack([char_data[i:i+block_size] for i in ix])\n",
    "    y = torch.stack([char_data[i+1:i+block_size+1] for i in ix])   # the \"next character\" of each position\n",
    "    return x.to(device), y.to(device)\n",
    "\n",
    "bigram = Bigram(char_vocab).to(device)\n",
    "opt = torch.optim.AdamW(bigram.parameters(), lr=1e-2)   # the dial-turning rule, stride 0.01\n",
    "\n",
    "for step in range(2000):\n",
    "    xb, yb = get_char_batch()                 # move 1: a batch and its right answers\n",
    "    _, loss = bigram(xb, yb)                  # moves 2 and 3: guess, and score with the loss\n",
    "    opt.zero_grad(); loss.backward(); opt.step()   # move 4: blame game; move 5: turn the dials (zero_grad wipes last lesson's blames first)\n",
    "    if step % 500 == 0:\n",
    "        print(f\"step {step:4d}  loss {loss.item():.3f}\")\n",
    "\n",
    "start = torch.tensor([encode_c(\"T\")], device=device)\n",
    "print(\"\\nBigram writes:\\n\", decode_c(bigram.generate(start, 300)[0].tolist()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be9bfbb8",
   "metadata": {},
   "source": [
    "Momo learned *something* (the loss dropped, and he produces word-ish shapes with spaces), but\n",
    "it is gibberish. He only looks one letter back. To tell a story he needs two upgrades:\n",
    "**better bites** and a way to **look further back**. That is the rest of the notebook."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f160514",
   "metadata": {},
   "source": [
    "## 4. A real tokenizer: how Momo decides where to bite\n",
    "\n",
    "Momo bites a sentence into pieces before he can think about it. How big should a bite be?\n",
    "\n",
    "- Bite every letter on its own, and Momo wastes all his effort learning to spell.\n",
    "- Bite whole words, and he needs a piece for every word in the world, and a word he has\n",
    "  never seen breaks him.\n",
    "- **BPE** (byte-pair encoding) is the middle way. Start with single letters. Find the two\n",
    "  pieces that sit next to each other most often (\"t\" and \"h\") and glue them into one piece\n",
    "  (\"th\"). Repeat thousands of times. Common words like \" the\" become one bite. A rare word\n",
    "  like \" hippopotamus\" becomes a few bites (\"hipp\", \"opot\", \"amus\"). Every big model (GPT,\n",
    "  Llama, Qwen) bites this way.\n",
    "\n",
    "We teach Momo his own bites from the stories, 4,096 pieces in total. Each piece gets a\n",
    "number, and from then on Momo only ever sees the numbers.\n",
    "\n",
    "Type a sentence below and watch it get bitten into pieces and turned into numbers. (This\n",
    "widget only pretends, to show the idea. The real biter, trained from our stories, comes in the\n",
    "next cell and may bite differently.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "026dd481",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Interactive: type a sentence, see tokens and ids (illustrative splitter).\n",
    "from IPython.display import HTML, display\n",
    "display(HTML(r\"\"\"<div id=\"vk\" style=\"font-family:-apple-system,Segoe UI,Helvetica,Arial,sans-serif;color:#e8e8e8;background:#1e1e1e;border-radius:12px;padding:16px;max-width:900px\">\n",
    "<style>#vk .chip{display:inline-block;padding:4px 8px;margin:3px;border-radius:6px;background:#2f4f6f;border:1px solid #5a8ab8;font-size:13px}#vk .id{display:inline-block;padding:4px 8px;margin:3px;border-radius:6px;background:#2f6f4f;border:1px solid #6fcf97;font-size:12px;font-family:monospace}#vk input{width:95%;padding:8px;border-radius:6px;border:1px solid #777;background:#262626;color:#eee;font-size:14px}#vk .cap{font-size:13px;line-height:1.5;margin-top:8px;background:#262626;border-radius:8px;padding:10px}</style>\n",
    "<div style=\"font-size:15px;font-weight:600;margin-bottom:6px\">Tokenizer: type anything and watch it get bitten into tokens, then numbers.</div>\n",
    "<input id=\"vkIn\" value=\"Once upon a time, Momo the monkey found a shiny red ball.\" oninput=\"vkRun()\">\n",
    "<div style=\"font-size:12px;color:#aaa;margin:8px 0 2px\">tokens (&#9251; marks a leading space)</div><div id=\"vkTok\"></div>\n",
    "<div style=\"font-size:12px;color:#aaa;margin:8px 0 2px\">pretend token ids (the real ones come from the trained biter in the next cell)</div><div id=\"vkIds\"></div>\n",
    "<div class=\"cap\">A tokenizer splits text into pieces it has seen often. Common words stay whole (\"little\"), rare or long words break into parts (\"unbeli\" + \"evable\"), and punctuation is its own token. Each piece has a fixed number in the dictionary. <i>This demo mimics the idea in JavaScript; the real one is trained from data in Part 4.</i></div>\n",
    "</div>\n",
    "<script>\n",
    "(function(){\n",
    "function hash(s){let h=7;for(const ch of s)h=(h*31+ch.codePointAt(0))%4096;return h;}\n",
    "function split(t){const out=[];const re=/\\s?\\p{L}+|\\s?\\d+|\\s?[^\\s\\p{L}\\d]/gu;let m;while((m=re.exec(t))){let p=m[0];if(p.replace(/^\\s/,'').length>7){const lead=p.startsWith(' ')?' ':'';const w=p.trim();out.push(lead+w.slice(0,Math.ceil(w.length/2)));out.push(w.slice(Math.ceil(w.length/2)));}else out.push(p);}return out;}\n",
    "window.vkRun=function(){const t=document.getElementById('vkIn').value;const toks=split(t);\n",
    " document.getElementById('vkTok').innerHTML=toks.map(p=>`<span class=\"chip\">${p.replace(/^ /,'␣')}</span>`).join('');\n",
    " document.getElementById('vkIds').innerHTML=toks.map(p=>`<span class=\"id\">${hash(p)}</span>`).join('')+`<span style=\"font-size:12px;color:#aaa;margin-left:8px\">${t.length} characters &#8594; ${toks.length} tokens</span>`;};\n",
    "vkRun();\n",
    "})();\n",
    "</script>\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f80df23",
   "metadata": {},
   "outputs": [],
   "source": [
    "from tokenizers import ByteLevelBPETokenizer\n",
    "from transformers import PreTrainedTokenizerFast\n",
    "\n",
    "VOCAB_SIZE = 4096\n",
    "EOS = \"<|endoftext|>\"   # special token that marks where one story ends\n",
    "\n",
    "bpe = ByteLevelBPETokenizer()\n",
    "bpe.train_from_iterator(stories, vocab_size=VOCAB_SIZE, min_frequency=2, special_tokens=[EOS])   # min_frequency: a pair must appear at least twice to be glued\n",
    "\n",
    "# Wrap it in the standard Hugging Face tokenizer class. We reuse the one special marker for\n",
    "# \"story starts\" (bos), \"story ends\" (eos) and \"empty space\" (pad); a tiny model needs no more.\n",
    "tokenizer = PreTrainedTokenizerFast(tokenizer_object=bpe, bos_token=EOS, eos_token=EOS, pad_token=EOS)\n",
    "vocab_size = len(tokenizer)\n",
    "\n",
    "sample = \"Once upon a time, Momo the monkey found a shiny red ball.\"\n",
    "ids = tokenizer(sample)[\"input_ids\"]\n",
    "print(\"vocab size:\", vocab_size)\n",
    "print(f\"{len(sample)} characters -> {len(ids)} tokens\")\n",
    "print(tokenizer.convert_ids_to_tokens(ids))   # 'Ġ' means 'there is a space before this piece'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "48a8b12f",
   "metadata": {},
   "source": [
    "Now every story becomes numbers. We glue all the lesson stories into one long stream of token\n",
    "ids with the \"story is over\" marker between them, and do the same, separately, for Ella's\n",
    "hidden quiz stories."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d96b25f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "def to_ids(story_list):\n",
    "    return torch.tensor(tokenizer(EOS.join(story_list) + EOS)[\"input_ids\"], dtype=torch.long)\n",
    "\n",
    "train_data = to_ids(train_stories)   # Momo's lessons\n",
    "val_data = to_ids(val_stories)       # Ella's quiz, never trained on\n",
    "print(f\"train {len(train_data)/1e6:.2f}M tokens | quiz {len(val_data)/1e3:.0f}K tokens\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c7ab127",
   "metadata": {},
   "source": [
    "## 5. Attention, by hand: the story circle in numbers\n",
    "\n",
    "This is the idea that made modern LLMs possible. Each token gets to **look at the tokens\n",
    "before it** and decide how much to listen to each one. It is the story circle from the\n",
    "introduction, now in code.\n",
    "\n",
    "Every token makes three vectors, its three cards:\n",
    "- a **query** (\"what am I looking for?\"),\n",
    "- a **key** (\"what am I?\"),\n",
    "- a **value** (\"what do I tell you if you pick me?\").\n",
    "\n",
    "A token's output is a weighted blend of the *values* of earlier tokens (and its own). The\n",
    "weights come from how well its *query* matches their *keys*, squashed into percentages by\n",
    "**softmax**. A triangle-shaped **mask** blocks the future, because the future is what Momo is\n",
    "trying to guess.\n",
    "\n",
    "In the code, `nn.Linear` is one tray of dials: it multiplies whatever comes in by that tray,\n",
    "exactly like the fruit widget in Part 1. Three trays make the three cards."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "69abace2",
   "metadata": {},
   "outputs": [],
   "source": [
    "B, T, C = 1, 4, 8                      # batch, time (tokens), numbers per token\n",
    "x_demo = torch.randn(B, T, C)\n",
    "head_size = 4                          # how long each card is\n",
    "q = nn.Linear(C, head_size, bias=False)(x_demo)\n",
    "k = nn.Linear(C, head_size, bias=False)(x_demo)\n",
    "v = nn.Linear(C, head_size, bias=False)(x_demo)\n",
    "\n",
    "scores = q @ k.transpose(-2, -1) / math.sqrt(head_size)  # (B, T, T): how much token i matches token j.\n",
    "                                                          # Dividing by the square root of the card length keeps scores from getting huge when cards get long.\n",
    "mask = torch.tril(torch.ones(T, T))                       # 1 = allowed to look, 0 = future\n",
    "scores = scores.masked_fill(mask == 0, float(\"-inf\"))     # future positions get -inf -> 0 after softmax\n",
    "weights = F.softmax(scores, dim=-1)\n",
    "out = weights @ v                                         # (B, T, head_size)\n",
    "\n",
    "torch.set_printoptions(precision=2, sci_mode=False)\n",
    "print(\"attention weights (row = token, column = who it looks at):\\n\", weights[0])\n",
    "print(\"\\nRow 1 only looks at tokens 0-1, row 3 can see all four. Each row sums to 1.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c24308af",
   "metadata": {},
   "source": [
    "### Ella's shelf (the KV cache), in about a dozen lines\n",
    "\n",
    "When Momo writes, a new token arrives at every step. The keys and values of the tokens\n",
    "already written **never change**, so Ella keeps them on her shelf (the cache) and Momo makes\n",
    "cards only for the newcomer. Below: four tokens are already on the shelf, a fifth arrives,\n",
    "and we check that the shelf gives exactly the same answer as redoing all five from scratch."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "84949030",
   "metadata": {},
   "outputs": [],
   "source": [
    "Wq, Wk, Wv = nn.Linear(C, head_size, bias=False), nn.Linear(C, head_size, bias=False), nn.Linear(C, head_size, bias=False)\n",
    "\n",
    "x_old = torch.randn(1, 4, C)                  # 4 tokens already processed earlier\n",
    "K_cache, V_cache = Wk(x_old), Wv(x_old)       # their key/value cards, stored once: Ella's shelf\n",
    "\n",
    "x_new = torch.randn(1, 1, C)                  # the 5th token arrives\n",
    "q_new, k_new, v_new = Wq(x_new), Wk(x_new), Wv(x_new)   # only ITS cards are computed\n",
    "K_cache = torch.cat([K_cache, k_new], dim=1)  # the shelf grows by one card\n",
    "V_cache = torch.cat([V_cache, v_new], dim=1)\n",
    "\n",
    "att = F.softmax(q_new @ K_cache.transpose(-2, -1) / math.sqrt(head_size), dim=-1)  # 1 query vs 5 stored keys\n",
    "out_cached = att @ V_cache\n",
    "\n",
    "# Check against the slow way: recompute attention for all 5 tokens from scratch, take the last row.\n",
    "x_all = torch.cat([x_old, x_new], dim=1)\n",
    "scores = Wq(x_all) @ Wk(x_all).transpose(-2, -1) / math.sqrt(head_size)\n",
    "scores = scores.masked_fill(torch.tril(torch.ones(5, 5)) == 0, float(\"-inf\"))\n",
    "out_full = (F.softmax(scores, dim=-1) @ Wv(x_all))[:, -1:]\n",
    "\n",
    "print(\"tokens stored in cache:\", K_cache.shape[1])\n",
    "print(\"cached shortcut == full recompute:\", torch.allclose(out_cached, out_full, atol=1e-6))\n",
    "print(\"work done this step: 1 token's cards instead of 5. For a 1,000-word chat, that is 1 instead of 1,000.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "626f6cd8",
   "metadata": {},
   "source": [
    "## 6. A tiny GPT: the whole of Momo\n",
    "\n",
    "**GPT** stands for Generative Pre-trained Transformer. *Generative* because it makes new\n",
    "text. *Pre-trained* because it learns from stories before anyone uses it. *Transformer*\n",
    "because it is built from attention layers stacked on top of each other. Here is the whole of\n",
    "Momo, bottom to top, like the tall jungle tree from the introduction. The rungs come in\n",
    "order, and every token climbs every rung:\n",
    "\n",
    "1. **Token embedding**: each token number becomes a row of `n_embd` numbers, its spot on the\n",
    "   meaning map.\n",
    "2. **Position embedding**: each seat number (1st, 2nd, 3rd...) also gets a row, so Momo knows\n",
    "   word order.\n",
    "3. **Blocks**, repeated `n_layer` times (the 6 rungs of the tree). Each block has:\n",
    "   - **multi-head attention**: the story circle, run 4 times at once with different cards;\n",
    "   - a **feed-forward network**: after listening to everyone, each token sits on its own\n",
    "     twig and thinks alone (a small two-layer network; research suggests many facts end up\n",
    "     stored here);\n",
    "   - **LayerNorm** (Ella keeping the numbers at a steady volume) and **residual connections**\n",
    "     (each block *adds* its thoughts to what came in instead of replacing it, so nothing is\n",
    "     lost on the way up the tree).\n",
    "4. **Final LayerNorm and the scoring tray**: turn each row into a score for every token in\n",
    "   the vocabulary (the logits). Programmers call this tray the \"output head\", a different use\n",
    "   of the word head from the attention heads.\n",
    "\n",
    "We also write `generate`: guess the next token, add it, repeat.\n",
    "\n",
    "Click any layer in the tree below to read what it does and why it is there."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af4363f8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Interactive: the model's layers, bottom to top. Click a layer.\n",
    "from IPython.display import HTML, display\n",
    "display(HTML(r\"\"\"<div id=\"vs\" style=\"font-family:-apple-system,Segoe UI,Helvetica,Arial,sans-serif;color:#e8e8e8;background:#1e1e1e;border-radius:12px;padding:16px;max-width:900px\">\n",
    "<style>#vs .box{display:block;width:300px;margin:4px auto;padding:8px;text-align:center;border-radius:8px;border:2px solid #5a8ab8;background:#2f4f6f;cursor:pointer;font-size:13px;transition:all .2s}#vs .box:hover,#vs .box.on{border-color:#ffb347;background:#3a2e1a}#vs .blk{border:2px dashed #6fcf97;border-radius:10px;padding:8px;width:340px;margin:6px auto;position:relative}#vs .tag{position:absolute;right:-70px;top:40%;color:#6fcf97;font-size:13px;font-weight:600}#vs .ar{text-align:center;color:#888;font-size:16px;line-height:14px}#vs .cap{font-size:13px;line-height:1.5;background:#262626;border-radius:8px;padding:10px;min-height:70px}@media (max-width:760px){#vs .wrap{flex-direction:column-reverse}}</style>\n",
    "<div style=\"font-size:15px;font-weight:600;margin-bottom:6px\">The tiny GPT, bottom to top. Click any layer.</div>\n",
    "<div class=\"wrap\" style=\"display:flex;gap:16px;flex-wrap:wrap\"><div style=\"flex:0 0 420px\">\n",
    "<span class=\"box\" onclick=\"vsX(this,'out')\">next-word scores (4,096 numbers)</span><div class=\"ar\">&#8593;</div>\n",
    "<span class=\"box\" onclick=\"vsX(this,'lnf')\">final LayerNorm + scoring tray (output head)</span><div class=\"ar\">&#8593;</div>\n",
    "<div class=\"blk\"><span class=\"tag\">&times; 6 blocks</span>\n",
    "<span class=\"box\" onclick=\"vsX(this,'res2')\">+ add back (residual)</span><div class=\"ar\">&#8593;</div>\n",
    "<span class=\"box\" onclick=\"vsX(this,'ff')\">feed-forward network</span><div class=\"ar\">&#8593;</div>\n",
    "<span class=\"box\" onclick=\"vsX(this,'ln')\">LayerNorm</span><div class=\"ar\">&#8593;</div>\n",
    "<span class=\"box\" onclick=\"vsX(this,'res1')\">+ add back (residual)</span><div class=\"ar\">&#8593;</div>\n",
    "<span class=\"box\" onclick=\"vsX(this,'attn')\">multi-head attention (4 heads)</span><div class=\"ar\">&#8593;</div>\n",
    "<span class=\"box\" onclick=\"vsX(this,'ln')\">LayerNorm</span></div>\n",
    "<div class=\"ar\">&#8593;</div>\n",
    "<span class=\"box\" onclick=\"vsX(this,'emb')\">token embedding + position embedding</span><div class=\"ar\">&#8593;</div>\n",
    "<span class=\"box\" onclick=\"vsX(this,'tok')\">token ids (up to 256 of them)</span>\n",
    "</div><div style=\"flex:1;min-width:260px\"><div class=\"cap\" id=\"vsCap\">Click a layer to read what it does and why it is there.</div></div></div>\n",
    "</div>\n",
    "<script>\n",
    "(function(){const t={\n",
    "tok:\"<b>Token ids.</b> The input: a list of up to 256 integers from the tokenizer. Picture: the number painted on each of Momo's bite-sized pieces.\",\n",
    "emb:\"<b>Embeddings.</b> Each id is swapped for a 256-number vector (its meaning), and a second vector for its position (1st, 2nd, 3rd...) is added, so the model knows word order. Picture: each animal in the story circle gets a name tag and a seat number.\",\n",
    "ln:\"<b>LayerNorm.</b> Rescales the numbers so they stay in a sensible range. It does no 'thinking'; it keeps training stable. Picture: Ella turning the volume to a steady level before each round.\",\n",
    "attn:\"<b>Multi-head attention.</b> The story circle: each token looks back at earlier tokens (and itself) and blends in what matters. 4 heads run in parallel, each looking for something different (grammar, names, position...). This is the only place tokens talk to each other.\",\n",
    "res1:\"<b>Residual (add back).</b> The attention output is <i>added</i> to the original, not replacing it. The token keeps its own meaning plus what it learned. It also gives gradients a direct path, which is what lets deep networks train at all.\",\n",
    "ff:\"<b>Feed-forward network.</b> A small two-layer network applied to every token on its own, 256 &#8594; 1024 &#8594; 256. Research suggests many facts and patterns end up stored here. Picture: after listening to everyone in the circle (attention), each animal sits on its own twig and thinks alone.\",\n",
    "res2:\"<b>Residual again.</b> Same trick: add the feed-forward result back onto the stream.\",\n",
    "lnf:\"<b>Final LayerNorm + scoring tray.</b> Programmers call this the output head, a different use of the word from the attention heads. One last rescale, then each token's 256-number vector is multiplied against the embedding table (shared weights, 'weight tying') to produce a score for all 4,096 dictionary entries.\",\n",
    "out:\"<b>Scores.</b> 4,096 numbers per position. Softmax turns them into probabilities, and the next word is sampled from them. During training these scores are compared with the real next word to compute the loss.\"};\n",
    "window.vsX=function(el,k){document.querySelectorAll('#vs .box').forEach(b=>b.classList.remove('on'));el.classList.add('on');document.getElementById('vsCap').innerHTML=t[k];};\n",
    "})();\n",
    "</script>\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c54e5e5e",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Config:\n",
    "    vocab_size = vocab_size\n",
    "    block_size = 256     # the context window from the introduction: how many tokens Momo can see at once\n",
    "    n_embd = 256         # size of each token's vector\n",
    "    n_head = 4           # attention heads per block\n",
    "    n_layer = 6          # number of blocks\n",
    "    dropout = 0.0        # tiny model + lots of data -> no dropout needed\n",
    "\n",
    "cfg = Config()\n",
    "\n",
    "class CausalSelfAttention(nn.Module):\n",
    "    def __init__(self, cfg):\n",
    "        super().__init__()\n",
    "        self.n_head = cfg.n_head\n",
    "        self.qkv = nn.Linear(cfg.n_embd, 3 * cfg.n_embd)      # query, key, value for all heads at once\n",
    "        self.proj = nn.Linear(cfg.n_embd, cfg.n_embd)\n",
    "        self.drop = nn.Dropout(cfg.dropout)\n",
    "\n",
    "    def forward(self, x):\n",
    "        B, T, C = x.shape\n",
    "        q, k, v = self.qkv(x).split(C, dim=2)\n",
    "        # reshape so each head works on its own slice: (B, n_head, T, head_size)\n",
    "        q = q.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)\n",
    "        k = k.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)\n",
    "        v = v.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)\n",
    "        # exactly the masked softmax(QK^T/sqrt(d))V from Part 5, done by a fast built-in kernel\n",
    "        y = F.scaled_dot_product_attention(q, k, v, is_causal=True)\n",
    "        y = y.transpose(1, 2).contiguous().view(B, T, C)      # merge the heads back together\n",
    "        return self.drop(self.proj(y))\n",
    "\n",
    "class FeedForward(nn.Module):\n",
    "    def __init__(self, cfg):\n",
    "        super().__init__()\n",
    "        self.net = nn.Sequential(\n",
    "            nn.Linear(cfg.n_embd, 4 * cfg.n_embd),\n",
    "            nn.GELU(),\n",
    "            nn.Linear(4 * cfg.n_embd, cfg.n_embd),\n",
    "            nn.Dropout(cfg.dropout),\n",
    "        )\n",
    "    def forward(self, x):\n",
    "        return self.net(x)\n",
    "\n",
    "class Block(nn.Module):\n",
    "    def __init__(self, cfg):\n",
    "        super().__init__()\n",
    "        self.ln1 = nn.LayerNorm(cfg.n_embd)\n",
    "        self.attn = CausalSelfAttention(cfg)\n",
    "        self.ln2 = nn.LayerNorm(cfg.n_embd)\n",
    "        self.ff = FeedForward(cfg)\n",
    "    def forward(self, x):\n",
    "        x = x + self.attn(self.ln1(x))   # \"x +\" is the residual connection\n",
    "        x = x + self.ff(self.ln2(x))\n",
    "        return x\n",
    "\n",
    "class GPT(nn.Module):\n",
    "    def __init__(self, cfg):\n",
    "        super().__init__()\n",
    "        self.cfg = cfg\n",
    "        self.tok_emb = nn.Embedding(cfg.vocab_size, cfg.n_embd)\n",
    "        self.pos_emb = nn.Embedding(cfg.block_size, cfg.n_embd)\n",
    "        self.blocks = nn.Sequential(*[Block(cfg) for _ in range(cfg.n_layer)])\n",
    "        self.ln_f = nn.LayerNorm(cfg.n_embd)\n",
    "        self.head = nn.Linear(cfg.n_embd, cfg.vocab_size, bias=False)\n",
    "        self.head.weight = self.tok_emb.weight    # weight tying: same matrix in and out, saves parameters\n",
    "        self.apply(self._init)\n",
    "\n",
    "    def _init(self, m):\n",
    "        if isinstance(m, (nn.Linear, nn.Embedding)):\n",
    "            nn.init.normal_(m.weight, mean=0.0, std=0.02)\n",
    "        if isinstance(m, nn.Linear) and m.bias is not None:\n",
    "            nn.init.zeros_(m.bias)\n",
    "\n",
    "    def forward(self, idx, targets=None):\n",
    "        B, T = idx.shape\n",
    "        pos = torch.arange(T, device=idx.device)\n",
    "        x = self.tok_emb(idx) + self.pos_emb(pos)          # (B, T, n_embd)\n",
    "        x = self.ln_f(self.blocks(x))\n",
    "        logits = self.head(x)                              # (B, T, vocab_size)\n",
    "        loss = None\n",
    "        if targets is not None:\n",
    "            loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1))\n",
    "        return logits, loss\n",
    "\n",
    "    @torch.no_grad()\n",
    "    def generate(self, idx, max_new_tokens, temperature=0.8, top_k=40, eos_id=None):\n",
    "        for _ in range(max_new_tokens):\n",
    "            idx_cond = idx[:, -self.cfg.block_size:]       # never feed more than block_size tokens\n",
    "            logits, _ = self(idx_cond)\n",
    "            logits = logits[:, -1, :] / temperature         # lower temperature = safer, more boring\n",
    "            if top_k is not None:\n",
    "                v, _ = torch.topk(logits, top_k)\n",
    "                logits[logits < v[:, [-1]]] = float(\"-inf\")  # keep only the k most likely tokens\n",
    "            probs = F.softmax(logits, dim=-1)\n",
    "            next_id = torch.multinomial(probs, 1)\n",
    "            idx = torch.cat([idx, next_id], dim=1)\n",
    "            if eos_id is not None and (next_id == eos_id).all():   # the model decided the story is over\n",
    "                break\n",
    "        return idx\n",
    "\n",
    "model = GPT(cfg).to(device)\n",
    "n_params = sum(p.numel() for p in model.parameters())\n",
    "print(f\"GPT with {cfg.n_layer} blocks, {cfg.n_head} heads, {cfg.n_embd}-dim vectors -> {n_params/1e6:.1f}M parameters\")\n",
    "print(\"(for scale: GPT-2 small is 124M, Llama-3 8B is 8,000M)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "246a4e91",
   "metadata": {},
   "source": [
    "**Four things in that code we have not talked about.**\n",
    "\n",
    "- `dropout`: during lessons, randomly ignore a few dials so Momo does not lean on any one of\n",
    "  them. Ours is 0, switched off: a tiny model with lots of stories does not need it.\n",
    "- `nn.GELU()`: a gentle bend between the two trays of the feed-forward network. Without a\n",
    "  bend, two trays in a row would be just one bigger tray, and the twig could only multiply.\n",
    "- *Weight tying*, `self.head.weight = self.tok_emb.weight`: the meaning map and the scoring\n",
    "  tray are the same tray used twice (once to go from token to meaning, once to go back), so\n",
    "  Momo needs fewer dials.\n",
    "- `std=0.02`: at the start every dial is set to a small random number. Small, so the first\n",
    "  guesses are gentle; random, so the dials do not all learn the same thing."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31bb615e",
   "metadata": {},
   "source": [
    "## 7. Training: Ella's bedtime lessons\n",
    "\n",
    "Every lesson is the same five moves Momo used for the bigram, just bigger:\n",
    "1. Ella picks a **batch** of story pieces (32 windows of 256 tokens) and, for each, the same\n",
    "   window shifted by one token: the right answers.\n",
    "2. Momo guesses the next token at every position.\n",
    "3. Ella scores the guesses with the **loss** (ours is **cross-entropy**: \"how surprised were\n",
    "   you by the real next word?\").\n",
    "4. The blame game runs backwards: every dial gets its gradient.\n",
    "5. The **optimizer** turns the dials. Ours is **AdamW**, which also keeps a memory of recent\n",
    "   slopes, like Momo remembering his last few readings on the foggy hill. The W is for\n",
    "   **weight decay**: every lesson Ella also nudges every dial a hair back toward zero, so no\n",
    "   dial grows wild.\n",
    "\n",
    "Two words you will see in the code: `model.train()` is lesson mode and `model.eval()` is quiz\n",
    "mode (on our model they behave the same, since dropout is off, but it is good manners).\n",
    "\n",
    "Two extras every real training run uses:\n",
    "- a **learning-rate schedule**: tiny steps first (**warm-up**, the ground is unknown), full\n",
    "  stride in the middle, shrinking steps at the end (**cosine decay**) so Momo lands in the\n",
    "  grove instead of bouncing;\n",
    "- **mixed precision** on NVIDIA GPUs: do most sums with shorter numbers, about 2x faster.\n",
    "\n",
    "About 1,500 lessons takes about two minutes on a T4 or an M-series Mac. Watch the loss fall,\n",
    "on the lessons and on Ella's hidden quiz.\n",
    "\n",
    "Step through the loop below first to see the five moves and a loss curve drawing itself."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0edd20df",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Interactive: the training loop, stage by stage, with a loss curve.\n",
    "from IPython.display import HTML, display\n",
    "display(HTML(r\"\"\"<div id=\"vr\" style=\"font-family:-apple-system,Segoe UI,Helvetica,Arial,sans-serif;color:#e8e8e8;background:#1e1e1e;border-radius:12px;padding:16px;max-width:900px\">\n",
    "<style>#vr button{background:#333;color:#eee;border:1px solid #777;border-radius:6px;padding:6px 12px;margin-right:6px;cursor:pointer}#vr .st{display:inline-block;padding:8px 10px;margin:3px;border-radius:8px;border:2px solid #555;background:#2a2a2a;font-size:13px;min-width:120px;text-align:center;transition:all .2s}#vr .st.on{border-color:#ffb347;background:#3a2e1a}#vr .cap{font-size:13px;line-height:1.5;margin-top:8px;background:#262626;border-radius:8px;padding:10px;min-height:48px}</style>\n",
    "<div style=\"font-size:15px;font-weight:600;margin-bottom:6px\">The training loop. Step through it and watch the loss fall.</div>\n",
    "<div id=\"vrStages\">\n",
    "<span class=\"st\" data-i=\"0\">1. Ella picks a batch<br><small>32 random story windows</small></span><span style=\"color:#888\">&#8594;</span>\n",
    "<span class=\"st\" data-i=\"1\">2. Momo guesses<br><small>the next token, everywhere</small></span><span style=\"color:#888\">&#8594;</span>\n",
    "<span class=\"st\" data-i=\"2\">3. Ella scores<br><small>the loss: how wrong, on average</small></span><span style=\"color:#888\">&#8594;</span>\n",
    "<span class=\"st\" data-i=\"3\">4. blame game<br><small>autograd: a gradient per dial</small></span><span style=\"color:#888\">&#8594;</span>\n",
    "<span class=\"st\" data-i=\"4\">5. optimizer step<br><small>turn all 5.9M dials</small></span><span style=\"color:#888\">&#8617;</span>\n",
    "</div>\n",
    "<div style=\"display:flex;gap:16px;flex-wrap:wrap;margin-top:10px\"><svg id=\"vrSvg\" width=\"460\" height=\"200\" style=\"background:#262626;border-radius:8px\"></svg>\n",
    "<div style=\"flex:1;min-width:240px\"><div class=\"cap\" id=\"vrCap\">Press Step, or Run 20 steps.</div>\n",
    "<div style=\"margin-top:8px\"><button onclick=\"vrStep()\">Step</button><button onclick=\"vrRun()\">Run 20 steps</button><button onclick=\"vrReset()\">Reset</button></div>\n",
    "<div style=\"font-size:12px;color:#aaa;margin-top:6px\">step <b id=\"vrN\">0</b> &nbsp; loss <b id=\"vrL\">-</b></div></div></div>\n",
    "</div>\n",
    "<script>\n",
    "(function(){\n",
    "const txt=[\"<b>Ella picks a batch.</b> 32 random 256-token windows from the stories, plus the same windows shifted one token right: the right answers.\",\"<b>Momo guesses.</b> The batch runs through Momo. Out come scores for every position's next token.\",\"<b>Ella scores.</b> Cross-entropy compares the scores with the true next tokens. One number: average surprise. Lower is better.\",\"<b>The blame game.</b> Autograd reads the tape backwards and gives every one of the 5.9 million dials its gradient: 'turn me this way to lower the loss'.\",\"<b>Turn the dials.</b> AdamW moves every dial a tiny bit against its gradient. Then the loop repeats, 1,500 times.\"];\n",
    "let stage=-1,n=0,pts=[];const svg=document.getElementById('vrSvg');let timer=null;\n",
    "function loss(k){return 1.9+6.5*Math.exp(-k/6)+ (Math.sin(k*1.7)*0.12);}\n",
    "function draw(){const show=pts.slice(-60);let d='';show.forEach((l,i)=>{d+=(i?'L':'M')+(30+i*(410/60)).toFixed(1)+','+(180-(l-1.5)/7.5*160).toFixed(1);});\n",
    " const ticks=[2,4,6,8].map(v=>`<text x=\"26\" y=\"${(180-(v-1.5)/7.5*160+4).toFixed(1)}\" fill=\"#777\" font-size=\"10\" text-anchor=\"end\">${v}</text><line x1=\"30\" x2=\"440\" y1=\"${(180-(v-1.5)/7.5*160).toFixed(1)}\" y2=\"${(180-(v-1.5)/7.5*160).toFixed(1)}\" stroke=\"#333\"/>`).join('');\n",
    " svg.innerHTML=ticks+`<text x=\"240\" y=\"196\" fill=\"#aaa\" font-size=\"11\" text-anchor=\"middle\">training steps${pts.length>60?' (last 60 shown)':''}</text><text x=\"12\" y=\"100\" fill=\"#aaa\" font-size=\"11\" transform=\"rotate(-90 12,100)\" text-anchor=\"middle\">loss</text><path d=\"${d}\" fill=\"none\" stroke=\"#6fcf97\" stroke-width=\"2.5\"/>`+(show.length?`<circle cx=\"${30+(show.length-1)*(410/60)}\" cy=\"${180-(show[show.length-1]-1.5)/7.5*160}\" r=\"4\" fill=\"#ffb347\"/>`:'');\n",
    " document.querySelectorAll('#vr .st').forEach(s=>s.classList.toggle('on',+s.dataset.i===stage));\n",
    " document.getElementById('vrCap').innerHTML=stage<0?'Press Step, or Run 20 steps.':txt[stage];document.getElementById('vrN').textContent=n;document.getElementById('vrL').textContent=pts.length?pts[pts.length-1].toFixed(2):'-';}\n",
    "window.vrStep=function(){stage=(stage+1)%5;if(stage===2){n++;pts.push(loss(n));}draw();};\n",
    "window.vrRun=function(){if(timer)return;let c=0;timer=setInterval(()=>{vrStep();if(++c>=100){clearInterval(timer);timer=null;}},120);};\n",
    "window.vrReset=function(){clearInterval(timer);timer=null;stage=-1;n=0;pts=[];draw();};\n",
    "draw();\n",
    "})();\n",
    "</script>\n",
    "\"\"\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7ed19704",
   "metadata": {},
   "outputs": [],
   "source": [
    "BATCH_SIZE = 32\n",
    "MAX_STEPS = 1500\n",
    "EVAL_EVERY = 250\n",
    "LR = 1e-3\n",
    "WARMUP = 100\n",
    "\n",
    "def get_batch(split):\n",
    "    d = train_data if split == \"train\" else val_data\n",
    "    ix = torch.randint(len(d) - cfg.block_size - 1, (BATCH_SIZE,))\n",
    "    x = torch.stack([d[i:i+cfg.block_size] for i in ix])\n",
    "    y = torch.stack([d[i+1:i+cfg.block_size+1] for i in ix])\n",
    "    return x.to(device), y.to(device)\n",
    "\n",
    "@torch.no_grad()\n",
    "def estimate_loss(iters=20):   # Ella's quiz: average loss over 20 batches of lesson text and 20 of hidden text\n",
    "    model.eval()\n",
    "    out = {}\n",
    "    for split in (\"train\", \"val\"):\n",
    "        losses = torch.zeros(iters)\n",
    "        for i in range(iters):\n",
    "            xb, yb = get_batch(split)\n",
    "            _, loss = model(xb, yb)\n",
    "            losses[i] = loss.item()\n",
    "        out[split] = losses.mean().item()\n",
    "    model.train()\n",
    "    return out\n",
    "\n",
    "def lr_at(step):\n",
    "    if step < WARMUP:\n",
    "        return LR * (step + 1) / WARMUP\n",
    "    progress = (step - WARMUP) / max(1, MAX_STEPS - WARMUP)\n",
    "    return 0.1 * LR + 0.9 * LR * 0.5 * (1 + math.cos(math.pi * progress))\n",
    "\n",
    "# betas: how long AdamW's memory of recent slopes is. weight_decay: the W. (Real runs exempt LayerNorm and biases from decay; we keep it simple.)\n",
    "optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=0.1, betas=(0.9, 0.95))\n",
    "use_amp = device == \"cuda\"\n",
    "scaler = torch.amp.GradScaler(enabled=use_amp)\n",
    "\n",
    "history = []\n",
    "t0 = time.time()\n",
    "model.train()\n",
    "for step in range(MAX_STEPS + 1):\n",
    "    for g in optimizer.param_groups:\n",
    "        g[\"lr\"] = lr_at(step)\n",
    "    if step % EVAL_EVERY == 0 or step == MAX_STEPS:\n",
    "        losses = estimate_loss()\n",
    "        history.append((step, losses[\"train\"], losses[\"val\"]))\n",
    "        print(f\"step {step:5d} | lesson loss {losses['train']:.3f} | quiz loss {losses['val']:.3f} \"\n",
    "              f\"| perplexity {math.exp(losses['val']):6.1f} | {(time.time()-t0)/60:4.1f} min\")\n",
    "    if step == MAX_STEPS:\n",
    "        break\n",
    "    xb, yb = get_batch(\"train\")\n",
    "    with torch.autocast(device_type=\"cuda\", dtype=torch.float16, enabled=use_amp):\n",
    "        _, loss = model(xb, yb)\n",
    "    optimizer.zero_grad(set_to_none=True)\n",
    "    scaler.scale(loss).backward()\n",
    "    scaler.unscale_(optimizer)\n",
    "    torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)   # stops rare huge gradients from wrecking training\n",
    "    scaler.step(optimizer)\n",
    "    scaler.update()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ce14f79b",
   "metadata": {},
   "source": [
    "**How to read the numbers.** The loss is Ella's average \"how surprised were you\" per token.\n",
    "**Perplexity** is `exp(loss)`: roughly, \"between how many tokens is Momo still choosing?\"\n",
    "Random guessing over 4,096 tokens would be perplexity 4,096. Around 10 or below on this data\n",
    "gives real sentences, and our run lands right about there. The quiz loss comes from the 200\n",
    "stories Momo never saw, with no Momo stories in them, so it measures real learning rather\n",
    "than memorised repeats. If the lesson loss kept falling while the quiz loss rose, Momo would\n",
    "be memorising, not learning."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13ba6e8b",
   "metadata": {},
   "source": [
    "## 8. Momo tells a story: the use case\n",
    "\n",
    "Give Momo the first few words and let him continue, one token at a time, as in the live flow\n",
    "diagram at the top (except that our short `generate` redoes the whole journey each step\n",
    "instead of using Ella's shelf). Two knobs control how he picks each token:\n",
    "- **temperature**: very low means he nearly always takes the safest token (dull but\n",
    "  sensible), high means he gambles on unlikely ones (creative but risky);\n",
    "- **top-k**: he only ever picks from the *k* most likely tokens, so the wildest ones are\n",
    "  never chosen.\n",
    "\n",
    "Run it a few times; each run is a fresh story. Try your own prompts."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b8b00a38",
   "metadata": {},
   "outputs": [],
   "source": [
    "def tell_story(prompt, max_new_tokens=200, temperature=0.8):\n",
    "    model.eval()\n",
    "    ids = [tokenizer.eos_token_id] + tokenizer(prompt)[\"input_ids\"]   # start with the story marker, as every training story did\n",
    "    idx = torch.tensor([ids], device=device)\n",
    "    out = model.generate(idx, max_new_tokens, temperature=temperature, eos_id=tokenizer.eos_token_id)\n",
    "    return tokenizer.decode(out[0].tolist(), skip_special_tokens=True)\n",
    "\n",
    "for prompt in [\"Once upon a time, a little monkey\", \"Ella had a red balloon.\", \"One day, Momo went to the river\"]:\n",
    "    print(tell_story(prompt))\n",
    "    print(\"-\" * 60)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc77f782",
   "metadata": {},
   "source": [
    "**Look closely at what we saw.** The Momo stories are almost word for word the sentences we\n",
    "fed him three times each, sometimes with the slots mixed up (he looks for a coconut and finds\n",
    "a feather). That is memorising, not inventing: a few thousand repeats of the same sentences\n",
    "are easy for 5.9 million dials to store. The stories without Momo wander, forget who is\n",
    "speaking, and sometimes change the character's name halfway. Both are honest signs of a tiny\n",
    "model after 1,500 lessons. More stories and more lessons (see the ideas at the end) make him\n",
    "invent more and recite less."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24859a73",
   "metadata": {},
   "source": [
    "## 9. Put Momo's book in the library: the Hugging Face Hub\n",
    "\n",
    "The **Hugging Face Hub** is the public library of models and datasets. A model there is just\n",
    "three things: its **weights** (Momo's dials, the tensors we trained), its **config** (the\n",
    "shape of his head: how many layers, heads and so on), and its **tokenizer** (how he bites).\n",
    "We save all three to a folder, write a short **model card** (the book's cover page: what it\n",
    "is, how it was made, how to use it), and upload the folder as a **repo** (a folder on the\n",
    "Hub) under your account.\n",
    "\n",
    "You need a Hugging Face **access token** with *write* permission (Settings > Access Tokens).\n",
    "This \"token\" has nothing to do with text tokens: it is a secret key, like a library card. On\n",
    "Colab the easiest way is the **Secrets** panel (the key icon on the left): add a secret named\n",
    "`HF_TOKEN`, switch on notebook access, and the cell finds it by itself. Otherwise `login()`\n",
    "asks for it. Locally, put `HF_TOKEN=...` in your shell environment.\n",
    "\n",
    "We create the repo **private** first, so only you can see it. Flip `private=False` in the\n",
    "code when you are ready to share."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7e55d046",
   "metadata": {},
   "outputs": [],
   "source": [
    "REPO_NAME = \"tiny-storyteller\"       # the model will live at huggingface.co/<you>/tiny-storyteller\n",
    "PUSH_TO_HUB = True                   # set False to only save locally\n",
    "\n",
    "save_dir = REPO_NAME\n",
    "os.makedirs(save_dir, exist_ok=True)\n",
    "torch.save(model.state_dict(), f\"{save_dir}/model.pt\")\n",
    "with open(f\"{save_dir}/config.json\", \"w\") as f:\n",
    "    json.dump({k: getattr(cfg, k) for k in [\"vocab_size\", \"block_size\", \"n_embd\", \"n_head\", \"n_layer\", \"dropout\"]}, f, indent=2)\n",
    "tokenizer.save_pretrained(save_dir)\n",
    "\n",
    "final_val = history[-1][2]\n",
    "card = f'''---\n",
    "license: mit\n",
    "language: en\n",
    "datasets: [roneneldan/TinyStories]\n",
    "tags: [tiny, from-scratch, pytorch, educational, text-generation]\n",
    "---\n",
    "# Tiny Storyteller\n",
    "\n",
    "A {n_params/1e6:.1f}M-parameter GPT-style language model trained **from scratch** (random init, no pretrained weights)\n",
    "on {len(train_stories) - 3*len(momo):,} TinyStories plus {3*len(momo):,} short synthetic stories about two characters, Momo the monkey and\n",
    "Ella the elephant ({len(momo):,} unique stories, each repeated 3 times, generated by `momo_stories.py`), as a learning project.\n",
    "Use case: a bedtime story starter for young kids.\n",
    "\n",
    "- Architecture: {cfg.n_layer} transformer blocks, {cfg.n_head} heads, {cfg.n_embd}-dim embeddings, {cfg.block_size}-token context\n",
    "- Tokenizer: byte-level BPE, {vocab_size:,} tokens, trained on the same data\n",
    "- Training: {MAX_STEPS} steps, batch {BATCH_SIZE}, AdamW, cosine schedule\n",
    "- Final validation loss {final_val:.3f} (perplexity {math.exp(final_val):.1f}) on {len(val_stories)} held-out TinyStories (no Momo stories in the validation set)\n",
    "\n",
    "The full build, explained step by step, is in `tiny_llm_from_scratch.ipynb` in this repo.\n",
    "Load with plain PyTorch: `GPT(cfg).load_state_dict(torch.load(\"model.pt\"))`.\n",
    "'''\n",
    "with open(f\"{save_dir}/README.md\", \"w\") as f:\n",
    "    f.write(card)\n",
    "print(\"saved:\", sorted(os.listdir(save_dir)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6cacb9a8",
   "metadata": {},
   "outputs": [],
   "source": [
    "if PUSH_TO_HUB:\n",
    "    from huggingface_hub import HfApi, login, whoami, get_token\n",
    "    if get_token() is None:                        # no HF_TOKEN env var, no Colab secret, no saved login\n",
    "        login()                                    # ask for the access token (nothing is printed)\n",
    "    api = HfApi()\n",
    "    user = whoami()[\"name\"]\n",
    "    repo_id = f\"{user}/{REPO_NAME}\"\n",
    "    api.create_repo(repo_id, repo_type=\"model\", private=True, exist_ok=True)   # private by default; flip to False to share publicly\n",
    "    api.upload_folder(folder_path=save_dir, repo_id=repo_id, repo_type=\"model\")\n",
    "    nb = \"tiny_llm_from_scratch.ipynb\"\n",
    "    if os.path.exists(nb):\n",
    "        api.upload_file(path_or_fileobj=nb, path_in_repo=nb, repo_id=repo_id)\n",
    "    print(\"published at https://huggingface.co/\" + repo_id)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "77a4c8d7",
   "metadata": {},
   "source": [
    "## 10. Loading it back later\n",
    "You, on another machine, can rebuild Momo from the Hub with the `GPT` class from Part 6 and\n",
    "these few lines. The biter (tokenizer) comes along too. While the repo is private, only your\n",
    "own access token can open it; once you set `private=False`, anyone can."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "59455da3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from huggingface_hub import hf_hub_download\n",
    "\n",
    "def load_from_hub(repo_id):\n",
    "    with open(hf_hub_download(repo_id, \"config.json\")) as f:\n",
    "        c = json.load(f)\n",
    "    class C: pass\n",
    "    for k, v in c.items(): setattr(C, k, v)\n",
    "    m = GPT(C()).to(device)\n",
    "    m.load_state_dict(torch.load(hf_hub_download(repo_id, \"model.pt\"), map_location=device))\n",
    "    tok = PreTrainedTokenizerFast.from_pretrained(repo_id)   # the biter travels with the dials\n",
    "    return m.eval(), tok\n",
    "\n",
    "# Example (uncomment after publishing):\n",
    "# model, tokenizer = load_from_hub(\"singhpratech/tiny-storyteller\")\n",
    "# print(tell_story(\"Once upon a time, a little monkey\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bef94f79",
   "metadata": {},
   "source": [
    "## What we built, in one paragraph\n",
    "Ella's stories became numbers through a tokenizer Momo learned himself. A tiny transformer\n",
    "turned those numbers into spots on a meaning map, let each token look around the story\n",
    "circle at the ones before it, and guessed the next token. Ella measured how wrong he was with\n",
    "cross-entropy, the blame game (autograd) found every dial's gradient, and AdamW turned the\n",
    "dials, thousands of times. That is the entire recipe. Big models use the same recipe with more\n",
    "stories, more branches, and more GPUs.\n",
    "\n",
    "## Ideas to try next\n",
    "- Raise `N_STORIES` and `MAX_STEPS` for a noticeably better storyteller.\n",
    "- Change `n_layer` or `n_embd` and watch how parameters, speed and loss trade off.\n",
    "- Lower `temperature` for safer stories, raise it for stranger ones.\n",
    "- Replace TinyStories with your own text (poems, letters, your journal) and the same code\n",
    "  learns to write in that style.\n",
    "- Make Momo invent instead of recite: more unique Momo stories (add scenes to the pools) and\n",
    "  fewer repeats of each."
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "gpuType": "T4",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}