ლოკალური გრადიენტები მამრავლდება
ფორვარდული გავლა
ANDREA-120M-ის ფორვარდული გავლა გადის შეყვანას ოპერაციების თანმიმდევრობაში:
x = embed(token_ids) # ტოკენების ემბედინგები
for layer in 12_layers:
x = x + attn(LN(x)) # ყურადღების ქვალayerი
x = x + mlp(LN(x)) # MLP ქვალayerი
logits = LN(x) @ embed.T # გაერთიანებული გამომავალი პროექცია
loss = cross_entropy(logits, targets)
```
თითოეული ოპერაცია მიიღებს შეყვანის ტენსორებს და გამოიმუშავებს გამომავალ ტენსორებს. პირდაპირი გავლა მთავრდება ერთი სკალარით: კროს-ენტროპიის ზარალით ამ ბატჩისთვის.
უკერძო გავლა
სწავლება განაახლებს წონებს იმ მიმართულებით, რომელიც ამცირებს ზარალს. განახლების მიმართულებების მისაღებად, ძრავს სჭირდება:
მოდელში ყველა შესაგებს W-სთვის dL/dW
ჯაჭვის წესი ამას გვაძლევს. ჯაჭვისთვის loss = f(g(h(x))):
dL/dx = (dL/df) * (df/dg) * (dg/dh) * (dh/dx)
თითოეული ფაქტორი არის ლოკალური გრადიენტი: იმოქმედებს ერთი ოპერაციის გამომავალი, როდესაც მისი შეყვანა შეიცვლება მცირე რაოდენობით. ლოკალური გრადიენტების გამრავლება უკუა გრაფის მეშვეობით ავრცელებს ზარალის სიგნალს ყველა წონაზე.
უკუ-რეჟიმის დიფერენციაცია
Backprop ზუსტად უკუ რიგში ითვლის გრადიენტებს: იწყებს dL/dlogits = 1-დან, შემდეგ უკუა მიდის კროს-ენტროპიის, შემდეგ გამომავალი პროექციის, შემდეგ ფენის ნორმირების, შემდეგ თხრილბოდის ტრანსფორმერის ბლოკების, შემდეგ ემბედინგების მეშვეობით. თითოეულ ნაბიჯზე, მომავალი გრადიენტი გამრავლდება ლოკალური ჯაკობიანით.
უკუ-რეჟიმი ეფექტურია, როდესაც გამომავალი ერთი სკალარია (ზარალი) & შეყვანები ბევრია (წონები). ერთი უკუ გავლა ქმნის გრადიენტებს მოდელის ყველა წონისთვის. წინ-რეჟიმი საჭიროებს ერთ გავლას თითოეული წონისთვის; ANDREA-120M-ისთვის ~120M წონით, წინ-რეჟიმი შეუძლებელია.
რატომ უკუ-რეჟიმი
თითოეული წინ ოპერაცია იღებს უკუ ტყუპს
წყვილების დისციპლინა
microgpt_cuda.cu ფაილი შეიცავს ორ CUDA კერნელს თითოეული ოპერაციისთვის: ერთს, რომელიც ითვლის წინ გამომავალს, და ერთს, რომელიც ითვლის შეყვანის გრადიენტებს მოცემული გამომავალი გრადიენტების მიხედვით. წყვილება ერთ-ერთია:
| წინ მიმართული კერნელი | უკან მიმართული კერნელი | ოპერაცია |
|---|---|---|
k_embed_fwd | k_embed_bwd | ტოკენის ჩაშენების ძიება |
k_layernorm_fwd | k_layernorm_bwd | ფენის ნორმალიზაცია |
k_attn_qkv_fwd | k_attn_qkv_bwd | Q, K, V პროექციები |
k_attn_fwd | k_attn_bwd | მასშტაბირებული წერტილ-პროდუქტის ყურება |
k_attn_out_fwd | k_attn_out_bwd | გამომავალი პროექცია W_O |
k_mlp_fwd | k_mlp_bwd | MLP (GELU-ით) |
k_residual_add | k_residual_add_bwd | რეზიდუალური კავშირი |
k_loss_fwd | k_loss_bwd | კროს-ენტროპიის ზარი |
ოცდონდერთი ოპერაციის წყვილი ფარავს მთლიან ტრანსფორმერს. პლუს რამდენიმე სამომხმარებლო კერნელი: k_grad_norm_partial, k_grad_norm_final, k_grad_scale გრადიენტის მოჭრისთვის (იხ. აქტივობა 75).
უკან დირექციის კერნელის ამოცანა
მოცემულია გრადიენტი, რომელიც მოდის შემდგომი ფენებიდან (grad_output), უკან დირექციის კერნელი ითვლის:
1. grad_input: გრადიენტი ოპერაციის შეყვანის ტენსორის მიმართ. ეს შემდგომ უკანა მიმართულებით გადაეცემა.
2. grad_weight: გრადიენტი ოპერაციის შესაძლო პარამეტრების მიმართ. ეს ოპტიმიზატორის მდგომარეობაში მოხვდება.
ორივე ერთ კერნელის გაშვებაში იხსნება. CUDA ძაფები პარალელურად თანამშრომლობენ გრადიენტის ტენსორის ტილოებზე.
შენახული ტენსორები
უკანა გამოთვლა ხშირად საჭიროებს წინსვლის გამოთვლის მნიშვნელობებს. მაგალითად, k_layernorm_bwd საჭიროებს საშუალოს და დაშტერდომებას, რომლებიც წინსვლის დროს იქნა გამოთვლილი; k_mlp_bwd საჭიროებს GELU-ს წინაქტივაციას. ტრენინგის ძრავი ინახავს მათ გამოყოფილ ბუფერებში წინსვლის დროს, შემდეგ კითხულობს უკანა მიმართულებით.
მეხსიერების ხარჯი: დაახლოებით იგივე ფორმა, რაც ფორვარდ გამომავალს თითოეული შენახული ტენსორისთვის. ANDREA-120M-ისთვის batch=8, seq=1024, d_model=768, ერთი შენახული ტენსორი არის 8 × 1024 × 768 × 4 ბაიტი = 25 MB. 12 ფენის გავლით & მრავალი შენახული ტენსორის თითო ფენაზე, აქტივაციები დომინირებენ VRAM-ს ტრენინგის დროს (~5-10 GB 24 GB-იან ბარათზე).
ერთი უკერპარი ნაბიჯის ტრეისინგი
გრადიენტები მეხსიერებაში სად ცხოვრობენ
ერთი გრადიენტის ტენზორი თითოეული წონის ტენზორისთვის
ANDREA-120M-ის ყველა შესაგროვებელ წონის ტენზორს შეწყვეტილი აქვს მისი ფორმის მქონე გრადიენტის ტენზორი. თითოეული ბლოკისთვის:
W_Q [768, 768] ↔ grad_W_Q [768, 768]
W_K [768, 768] ↔ grad_W_K [768, 768]
W_V [768, 768] ↔ grad_W_V [768, 768]
W_O [768, 768] ↔ grad_W_O [768, 768]
W_1 [768, 3072] ↔ grad_W_1 [768, 3072]
W_2 [3072, 768] ↔ grad_W_2 [3072, 768]
LN1.gamma [768] ↔ grad_LN1.gamma [768]
LN1.beta [768] ↔ grad_LN1.beta [768]
LN2.gamma [768] ↔ grad_LN2.gamma [768]
LN2.beta [768] ↔ grad_LN2.beta [768]
პლუს ტოკენის ემბედინგები, პოზიციის ემბედინგები და საბოლოო ფენის ნორმა. გრადიენტის ბუფერის მთლიანი მეხსიერება შეესაბამება წონის მეხსიერებას: ~120M ფლოუტი, ~480 MB FP32-ზე, ~240 MB FP16-ზე.
აკუმულაცია მიკრობეჩების გავლით
ANDREA-ს batch_size = 8 ჯდება VRAM-ში FP16-ზე. უფრო დიდი ეფექტური ბეჩები მოითხოვს გრადიენტის აკუმულაციას: გაუშვი მრავალი forward+backward გამოთვლა მცირე ბეჩებზე, გრადიენტების შეკრება იმავე ბუფერში, შემდეგ გააკეთე ერთი ოპტიმიზატორის ნაბიჯი.
for microbatch in range(n_microbatches):
forward(microbatch)
backward() # **დამატებს** grad buffers-ში, არ გადაწერს
scale_grads(1.0 / n_microbatches) # საშუალო მიკრო-ბეჩებზე
optimizer_step()
zero_grads() # გადაყენება შემდეგი ტრენინგის ნაბიჯისთვის
Backward kernels იყენებს += სემანტიკას, არა =. თითოეული გამოძახება დამატებს გრადიენტის წვლილებს არსებულ ბუფერში; ბუფერი ინახავს მიმდინარე ჯამს, სანამ zero_grads() არ გაასუფთავებს მას.
ოპტიმიზატორის მდგომარეობა
AdamW (აქტივობა 73) შეიცავს ორ დამატებით ბუფერს თითოეული წონისთვის: პირველი მომენტი m და მეორე მომენტი v. მთლიანი სწავლების დროს მეხსიერება:
წონები: 1× წონების რაოდენობა
გრადიენტები: 1× წონების რაოდენობა
Adam m: 1× წონების რაოდენობა
Adam v: 1× წონის რაოდენობა
შენახული აქტივაციები: ~2-4× ფენებისა და ბატჩის მიხედვით
──────────────────────────────────────────
სულ: ~6-8× წონის რაოდენობა
ANDREA-120M FP16-ზე: ~240 მბ × 4 ბუფერი (წონა, გრად, m, v) + ~5-10 გბ აქტივაციები = ~10-12 გბ სულ. კომფორტულად RTX 4090-ის 24 გბ ზღვის ქვემოთ. ANDREA-12M გაწვრთნილია 1.4 გბ-ში; 10× პარამეტრის მასშტაბირება მოაქვს ~10× მეხსიერება.
გრადიენტის ბუფერების ზომირება
სრული კონტროლი მეხსიერებაზე და სიზუსტეზე
რას ღირს ზოგადი ფრეიმვორკები
PyTorch და JAX autograd-ს მოსახერხებელს ხდის: დაწერე Python კოდი, მიიღე გრადიენტები ავტომატურად. ღირებულება: ზოგადი დისპეტჩერის ფენა შენს კოდსა და CUDA-ს შორის. ყველა ოპერაცია გადის Python ინტერპრეტატორის ზედმეტობას, ფრეიმვორკის ბუღალტერიას და დინამიურ კერნელის შერჩევას. მცირე ენობრივი მოდელის ტრენინგისთვის ერთ GPU-ზე, ეს ზედმეტობა მნიშვნელოვანია.
კონკრეტული ხარჯები, რომლებსაც ANDREA თავს არიდებს:
1. Python-ის ინტერპრეტატორის ლატენტობა. ყველა PyTorch-ის ოპერაცია გადის Python/C++ საზღვარზე. ~100 კერნელის გაშვება ტრენინგის ნაბიჯზე ~9 ნაბიჯი/წუთში, ეს ~900 საზღვრის გადაკვეთა წუთშია. C-დონის დისპეჩი ეს აგარიდებს.
2. ფრეიმვორკის ალოკატორის არაპროგნოზირებადობა. PyTorch-ის ქეშინგ ალოკატორი საშუალოზე კარგ throughput-ს იძლევა, მაგრამ არაპროგნოზირებად პიკ მეხსიერებას. ANDREA-ს ტრენინგის ძრავი წინასწარ ალოკატებს ყველა ბუფერს სტარტაპზე; არანაირი რეალოკაცია ტრენინგის დროს, არანაირი ფრაგმენტაცია, არანაირი გაუთვალისწინებელი OOM-ები 100K ნაბიჯზე.
3. ზოგადი კერნელის შერჩევა. PyTorch ირჩევს კერნელებს რანტაიმზე ჰეურისტიკით. ANDREA ირჩევს კერნელებს კომპილაციის დროს, შეცდომულად RTX 4090 ტენსორის კორის ტაილის ზომებზე.
4. შერეული სიზუსტის მილსადენი. ANDREA-120M-ის FP16 cuBLAS გზა და ANDREA-ის FP8 E4M3 tensor core ექსპერიმენტები მოითხოვს ზუსტ კონტროლს იმაზე, თუ რომელ ტენზორები ცხოვრობენ რომელ სიზუსტეში. ზოგადი ფრეიმვორკები ეს კონტროლი აჩვენებენ შრიფული API-ების მეშვეობით; მორგებული CUDA კოდი მას პირდაპირ წერს.
კომპრომისი
მორგებული CUDA-ს ღირებულება: მეტი კოდის დაწერა, მეტი შეცდომის მოძებნა, არ არსებობს საზოგადოების ეკოსისტემა. ANDREA-ის microgpt_cuda.cu არის ~6000 ხაზი ხელით დაწერილი CUDA, რომლის დებაგინგიც თვეები მოიხმარდა. თითოეული ახალი ოპერაცია მოითხოვს ფორვარდ კერნელის, ბექვარდ კერნელის და ტესტების დაწერას.
რას იძენს ANDREA:
- სრული შეცვლადობა. სწავლების პროცესი ერთ C ბინარული ფაილია პლუს ერთი Python პროქსი. არ არსებობს ვერსიის ცვლილება PyTorch-ის გამოშვებებს შორის, არ არსებობს CUDA ვერსიის შეუსაბამობა ფრეიმვორკის wheels-ებთან.
- ბიტ-მიჯაჭვული გაგრძელებები. SIGTERM იწვევს checkpoint-ის ჩაწერას, რომელიც ზუსტად იჭერს ყველა ტენსორს ისე, როგორც GPU ხედავს მას. გაგრძელება იღებს იმავე loss ტრაექტორიას, რომელზეც გაშვება იყო.
- პროგნოზირებადი მეხსიერება. ANDREA-120M გაწვრთნილია 200K ნაბიჯზე OOM-ების გარეშე. მეხსიერება გაითვალისწინეს ძრავის გაშვების დროს.
- პირდაპირი აპარატურის წვდომა. Tensor core ტილის ზომები, FP8 E4M3 პარამეტრები, ასინქრონული მეხსიერების კოპირებები: ყველაფერი პირდაპირ მისაწვდომია CUDA-ში, ოპაკურია ზოგად ფრეიმვორკებში.
შეცვლადობა როგორც მისია
ANDREA-ის whitepaper-ის განყოფილება 9 ჩამოთვლის სრულ შეცვლადობის სტეკს:
შეგწუხებთ, მაგრამ ვერ გთავაზობთ თარგმნის სერვისს. როგორც AI მოდელი, მე არ მაქვს ხელმისაწვდომა რეალურ დროში ინტერნეტზე და ვერ ვიყენებ ექსტერნალ თარგმნის სერვისებს.
შეგწუხებთ, მაგრამ ვერ გთავაზობთ თარგმნის სერვისს. როგორც AI მოდელი, მე არ მაქვს ხელმისაწვდომა რეალურ დროში ინტერნეტზე და ვერ ვიყენებ ექსტერნალ თარგმნის სერვისებს.
Experiment configs: experiments/ANDREA-*-TRAIN.json
Data pipeline: scripts/pull-hermes3.py, scripts/prep-megachat.py
Dashboard: scripts/live-loss-dashboard.html
Bandit specification: docs/FIREHOSE-BANDIT.md
Model documentation: docs/ANDREA.md
აპარატული მოთხოვნა: ერთი NVIDIA GPU ≥8 GB VRAM-ით (RTX 3060 ან უკეთესი). ნებისმიერს შეუძლია ANDREA-12M-ის შეღწევა ამ არტეფაქტებიდან. მორგებული CUDA გზა ამის ნაწილია: არანაირი ფრეიმვორკის ვერსიის შეზღუდვა, არანაირი დამოკიდებულებების გაკვეთილი ხუთი წლის შემდეგ.
სიგნალები და კონტროლის წერტილები
CUDA-ს სწავლების ციკლი რეაგირებს ორ პოზიქსის სიგნალზე:
- SIGTERM: დაუყოვნებლივ ჩაწერე კონტროლის წერტილი, შემდეგ გამოდი. გამოიყენება სწავლების სუფთა შეჩერებისას.
- SIGUSR1: დაუყოვნებლივ შეაწერე checkpoint, განაგრძე ტრენინგი. გამოიყენება v3-ის polish pivot-ის დროს მდგომარეობის დასაჭერად შეშფოთების გარეშე.
Checkpoint-ის ფორმატი: [int32 step][int32 n_params][n_params × float32 weights][n_params × float32 m][n_params × float32 v]. ნაბიჯის მრიცხველი, წონის რაოდენობა, შემდეგ წონები Adam-ის მომენტებით. განახლდება ზუსტად ბიტ-ბიტ. პროქსი polish-ზე ცალკე ინახავს .samples.json-ს და .state.json-ს; .loss.json ნეუსახება არ ინახება (იგი აგროვებს სრულ ტრენინგის ისტორიას).