ნაცნობი სიტუაციაა? ჩვენც აღმოვჩნდით ასეთ მდგომარეობაში. ჩვენი nanoVLM პროექტზე ჩატარებული „საგამოძიებო სამუშაოების“ შემდეგ, აღმოვაჩინეთ, რომ რეალური პრობლემა არც ჩვენი მოდელი იყო და არც აპარატურა; მიზეზი იყო ჩვენი მონაცემთა მილსადენი, რომელიც წარმოუდგენლად არაეფექტურად მუშაობდა. აი, რა აღმოვაჩინეთ: ამ პოსტში ჩვენ ვაშენებთ ეფექტურ მილსადენს ხუთ ეტაპად. თითოეულ ეტაპზე ვამატებთ ან ვაკლებთ წინა ნაბიჯისგან და ვაკეთებთ კომენტარებს იმის შესახებ, თუ რა გამოვიდა სწორად და რა - არა. მონაცემთა მომზადების ამოცანების გასამარტივებლად, ჩვენ შევქმენით ცალკე რეპოზიტორიუმი, რომელიც მხოლოდ მონაცემთა მილსადენზეა ორიენტირებული. ვიმედოვნებთ, რომ ეს ბევრად მარტივი გასაგები იქნება, ვიდრე კოდის წაკითხვა nanoVLM რეპოზიტორიუმთან ინტეგრაციის შემდეგ. გარდა ამისა, ეს შეიძლება გამოსადეგი იყოს სხვა მონაცემთა მილსადენების დასაწყებად! რეპოზიტორიუმი: https://github.com/ariG23498/mmdp იმისათვის, რომ თვალყური ადევნოთ, საკმარისია რეპოზიტორიუმის კლონირება. ის შეიცავს მონაცემთა მომზადების საბოლოო ამოცანებს, მაგრამ შექმნილია იმისათვის, რომ აჩვენოს თითოეული ნაბიჯი. რაიმეს ოპტიმიზაციამდე, უნდა გვესმოდეს, რასთან გვაქვს საქმე. ჩვენი მულტიმოდალური მონაცემთა ნაკრები შეიცავს გამოსახულებებს, ტექსტურ მოთხოვნებს და პასუხებს. ტრენინგის მონაცემებთან გაცნობა გადამწყვეტია წარმატებისთვის. წინა სკრიპტი ყოველ ჯერზე აჩვენებს შემთხვევით ნიმუშს; შეგიძლიათ დააკოპიროთ ნაწყვეტი ნოუთბუქში და გაუშვათ რამდენჯერმე, რათა უკეთ გაეცნოთ მონაცემებს. ჩვენი პირველი ტრენინგის მცდელობისას გამოვიყენეთ აშკარა (და ძალიან ხშირი) მიდგომა: შედეგები მტკივნეული იყო. შეხედეთ ამ ვიზუალიზაციას: ხედავთ ამდენ ნაცრისფერს? ეს არის „padding“ (დაუფიქსირებელი მონაცემები). ეს არის GPU, რომელიც აბსოლუტურად არაფერს ამუშავებს, სანამ თქვენ იხდით გამოთვლითი დროისთვის. ჩვენი ბლოკის დაახლოებით 60% ცარიელ ტოკენებზე იხარჯებოდა. ჩვენი შემდეგი ნაბიჯი მარტივი იყო. დავაყენეთ გლობალური მაქსიმალური სიგრძე და მივყვეთ მას. თუ ნიმუში ძალიან გრძელი იყო, უბრალოდ ვშლიდით მას. როგორც შეამჩნევდით, ბლოკს ახლა ერთი ნიმუშით ნაკლები აქვს. ეს გამოწვეულია ფილტრაციის პროცესით. ამან ხელი შეუწყო, მაგრამ ჩვენ მაინც ვუმატებდით „padding“-ს ყველაფერს ერთსა და იმავე ფიქსირებულ სიგრძეზე, რეალური შინაარსის მიუხედავად. უკეთესი იყო, ვიდრე ადრე, მაგრამ მაინც არაეფექტური. ახლა მზად ვართ, ბლოკების ფორმირება მთლიანად გადავაფასოთ. „Padding“ მტერია, და ჩვენ გვჭირდება სტრატეგია, რომ მისი მინიმიზაცია მოვახდინოთ, ამავდროულად მაქსიმალურად ბევრი მონაცემი ჩავტვირთოთ თითოეულ ბლოკში. აქ შემოდის „ზურგჩანთის პრობლემა“ (knapsack problem), კლასიკური კომპიუტერული მეცნიერების ამოცანა, რომელიც ამისთვის იდეალურია. წარმოიდგინეთ, რომ ლაშქრობისთვის ზურგჩანთას ალაგებთ. მას მხოლოდ გარკვეული წონის ტარება შეუძლია, და თქვენ გსურთ რაც შეიძლება მეტი სასარგებლო ნივთის ჩალაგება. ჩვენს შემთხვევაში: ამ იდეის შესამოწმებლად, ვიწყებთ მარტივი მონაცემთა ნაკრებით: უბრალოდ რიცხვების სია 1-დან 25-მდე, რომელთაგან თითოეული მიმდევრობის სიგრძეს წარმოადგენს. ეს საშუალებას გვაძლევს ექსპერიმენტები ჩავატაროთ გამოსახულებებისა და ტექსტის სირთულის გარეშე. PyTorch-ის მონაცემთა ნაკრებების უმეტესობა map-style-ის ტიპისაა (მათზე წვდომა ხდება dataset[i]-ის საშუალებით). მაგრამ დინამიური ბლოკების შესაქმნელად, გვჭირდება უფრო მოქნილი რამ. ამიტომ, ავაშენეთ iterable-style-ის მონაცემთა ნაკრები torch.utils.data.IterableDataset-ის ქვეკლასირებით. ეს საშუალებას გვაძლევს ბლოკების გენერირებას ოპერატიულად და მონაცემთა დაყოფას რამდენიმე „მუშაკზე“ (workers): მიმდევრობების დაფასოება შეიძლება ნელი იყოს, განსაკუთრებით თუ ვალაგებთ ან ვურევთ. პროცესის დასაჩქარებლად, ვიყენებთ „მწარმოებელი-მომხმარებლის“ (producer-consumer) შაბლონს Python-ის რიგების გამოყენებით: მწარმოებელი ნაკადი (thread) ალაგებს ბლოკებს და ათავსებს მათ რიგში, ხოლო მთავარი ნაკადი საჭიროებისამებრ იღებს მათ. ეს გადაფარვა უზრუნველყოფს მილსადენის შეუფერხებელ მუშაობას. პირველ რიგში, ვცდით მარტივ ხარბ შეფუთვის სტრატეგიას: ეს თანმიმდევრულად გადის მონაცემებში, ამატებს ელემენტებს შეკვრაში, სანამ ის არ შეივსება, შემდეგ კი ახალს იწყებს. ის სწრაფია, მაგრამ არა სრულყოფილი. აი, როგორ გამოიყურება ბლოკები: შეამჩნიეთ, როგორ ხდება მოგვიანებით ბლოკები მეჩხერი? ჩვენ ვტოვებთ ხარვეზებს. ვცადოთ უფრო ჭკვიანი მიდგომა: „bin-packing“ (კერძოდ, First Fit Decreasing): ეს ალაგებს მიმდევრობებს სიგრძის მიხედვით (ყველაზე გრძელი პირველი) და ცდილობს თითოეულის ჩასმას პირველ შეკვრაში, სადაც ადგილია. თუ არცერთი არ ეტევა, იწყებს ახალ შეკვრას. შედეგი? ეს ბლოკები ბევრად უფრო კომპაქტურია, ნაკლები დაკარგული სივრცით. ეს ჰგავს ტეტრისის თამაშს თქვენს მონაცემებთან, ნაწილების მჭიდროდ მორგებას. ახლა კი ნამდვილი საქმე: „ზურგჩანთის შეფუთვის“ (knapsack packing) გამოყენება ჩვენს მულტიმოდალურ მონაცემთა ნაკრებზე. ჩვენ ვუბრუნდებით გამოსახულებებს, მოთხოვნებს და პასუხებს, და გვჭირდება მათი ეფექტურად შეფუთვა, ტოკენების ლიმიტებისა და გამოსახულებების ბიუჯეტების გათვალისწინებით. გამოსახულებების ბიუჯეტირება ხდება იმისთვის, რომ ნიმუშზე გამოსახულებები იყოს დაბალანსებული. გვსურს თავიდან ავიცილოთ ის შემთხვევა, როდესაც ერთ GPU-ს გაცილებით მეტი გამოსახულების დამუშავება მოუწევს, ვიდრე მეორეს. ჩვენი ახალი ConstantLengthDataset კლასი ასრულებს ყველაზე რთულ სამუშაოს. აი, როგორ მუშაობს ის, მე-4 ეტაპთან შედარებით: ConstantLengthDataset ყველაფერს აკეთებს: აი, შედეგი: შეხედეთ! ნაცრისფერი („padding“) მინიმალურია, ხოლო ბლოკები სავსეა სასარგებლო მონაცემებით. ეს ჰგავს ჩემოდნის ისე კარგად ჩალაგებას, რომ მისი ელვაშესაკრავის შეკვრა მაინც შეგიძლიათ მასზე დაჯდომის გარეშე. გამოსახულება შეიძლება თავიდან გაუგებარი მოგეჩვენოთ, მაგრამ მოდით შევხედოთ მას შეზღუდული „padding“-ის მქონე გამოსახულებასთან ერთად. აქ შეამჩნევთ, რომ „knapsack“-ში ნიმუშები უფრო თანაბრად არის განაწილებული. ჩვენ ასევე არ ვხვდებით იმ პრობლემას, რომ ბლოკში ნაკლები ნიმუშია ფილტრაციის გამო. ის, რაც დაიწყო მარტივი კითხვით „რატომ არის ტრენინგი ასეთი ნელი?“, მიგვიყვანა მულტიმოდალური მონაცემების დამუშავების სრულიად გადახედვამდე. მონაცემთა მილსადენისთვის დაბალანსებული „knapsack“ სტრატეგია მომდინარეობს Eagle 2: Building Po…