একাধিক জিপিইউ জুড়ে বিতরণ করা এআই সম্পর্কে একটি সিরিজের অংশ:
- পার্ট 1: হোস্ট এবং ডিভাইস প্যাটার্ন বোঝা (এই নিবন্ধ)
- পার্ট 2: পয়েন্ট-টু-পয়েন্ট এবং যৌথ অপারেশন (শীঘ্রই আসছে)
- পার্ট 3: GPU কিভাবে যোগাযোগ করে (শীঘ্রই আসছে)
- পার্ট 4: সিকোয়েন্সিয়াল ক্যাশে এবং ডিস্ট্রিবিউটেড ডেটা প্যারালেলিজম (DDP) (শীঘ্রই আসছে)
- পার্ট 5: শূন্য (শীঘ্রই আসছে)
- পার্ট 6: টেনসর সমান্তরালতা (শীঘ্রই আসছে)
ভূমিকা
এই নির্দেশিকাটি একটি CPU এবং বিচ্ছিন্ন গ্রাফিক্স কার্ড (GPU) একসাথে কীভাবে কাজ করে তার প্রাথমিক ধারণাগুলি ব্যাখ্যা করে। এটি একটি উচ্চ-স্তরের ভূমিকা যা আপনাকে হোস্ট-ডিভাইস প্যারাডাইমের একটি মানসিক মডেল তৈরি করতে সাহায্য করার জন্য ডিজাইন করা হয়েছে। আমরা বিশেষভাবে NVIDIA GPU-তে ফোকাস করব, যেগুলি এআই ওয়ার্কলোডের জন্য সবচেয়ে বেশি ব্যবহৃত হয়।
ইন্টিগ্রেটেড জিপিইউগুলির জন্য, যেমন অ্যাপল সিলিকন চিপগুলিতে পাওয়া যায়, আর্কিটেকচারটি কিছুটা আলাদা, এবং এই পোস্টে কভার করা হবে না।
বড় ছবি: হোস্ট এবং ডিভাইস
বোঝার সবচেয়ে গুরুত্বপূর্ণ ধারণা হল মধ্যে সম্পর্ক হোস্ট এবং এই সরঞ্জাম.
- হোস্ট: এটা তোমার সিপিইউ. এটি অপারেটিং সিস্টেম চালায় এবং লাইন দ্বারা আপনার পাইথন স্ক্রিপ্ট লাইন চালায়। হোস্ট হল সেনাপতি; এটি সামগ্রিক যুক্তির দায়িত্বে রয়েছে এবং ডিভাইসটিকে কী করতে হবে তা বলে।
- পরামর্শ: এটা তোমার gpu. এটি একটি শক্তিশালী কিন্তু বিশেষায়িত কোপ্রসেসর যা ব্যাপকভাবে সমান্তরাল গণনার জন্য ডিজাইন করা হয়েছে। ডিভাইসটি একটি অ্যাক্সিলারেটর; হোস্ট এটিকে একটি টাস্ক না দেওয়া পর্যন্ত এটি কিছুই করে না।
আপনার প্রোগ্রাম সবসময় CPU-তে শুরু হয়। আপনি যখন GPU একটি কাজ সম্পাদন করতে চান, যেমন দুটি বড় ম্যাট্রিক্স গুণ করা, CPU নির্দেশাবলী এবং ডেটা GPU-তে পাঠায়।
CPU-GPU মিথস্ক্রিয়া
হোস্ট একটি সারিবদ্ধ সিস্টেমের মাধ্যমে ডিভাইসের সাথে কথা বলে।
- CPU কমান্ড শুরু করে: আপনার স্ক্রিপ্ট, CPU-তে চলমান, GPU-এর উদ্দেশ্যে কোডের একটি লাইনের সম্মুখীন হয় (উদাহরণস্বরূপ,
tensor.to('cuda')) - অর্ডার সারিবদ্ধ: CPU অপেক্ষা করে না। এটি এই কমান্ডটিকে GPU-এর জন্য একটি বিশেষ কার্য তালিকায় রাখে যাকে বলা হয় a চুদা স্রোত – পরবর্তী বিভাগে এই বিষয়ে আরো.
- অ্যাসিঙ্ক্রোনাস এক্সিকিউশন: CPU প্রকৃত অপারেশন সম্পূর্ণ করার জন্য GPU এর জন্য অপেক্ষা করে না, হোস্ট আপনার স্ক্রিপ্টের পরবর্তী লাইনে চলে যায়। এটা বলা হয় অ্যাসিঙ্ক্রোনাস মৃত্যুদন্ডএবং এটি উচ্চ কর্মক্ষমতা অর্জনের চাবিকাঠি। GPU যখন সংখ্যা ক্রাঞ্চ করতে ব্যস্ত থাকে, তখন CPU অন্যান্য কাজে কাজ করতে পারে, যেমন ডেটার পরবর্তী ব্যাচ প্রস্তুত করা।
চুদা স্রোত
ক চুদা স্রোত GPU হল অপারেশনের একটি অর্ডারকৃত সারি। একটি একক প্রবাহে জমা দেওয়া অপারেশনগুলি সম্পাদিত হয়৷ ক্রমেএকের পর এক। তবে অপারেশনাল ক্রস আলাদা স্ট্রীম চালাতে পারে এই সঙ্গে – GPU একই সময়ে একাধিক স্বাধীন কাজের চাপ পরিচালনা করতে পারে।
ডিফল্টরূপে, প্রতিটি PyTorch GPU অপারেশন সারিবদ্ধ বর্তমান সক্রিয় প্রবাহ (এটি সাধারণত ডিফল্ট স্ট্রিম যা স্বয়ংক্রিয়ভাবে তৈরি হয়)। এটি সহজ এবং অনুমানযোগ্য: প্রতিটি অপারেশন শুরু করার আগে পূর্ববর্তী অপারেশন শেষ হওয়ার জন্য অপেক্ষা করে। বেশিরভাগ কোডের জন্য, আপনি কখনই এটিতে মনোযোগ দেবেন না। কিন্তু যখন আপনার কাজ থাকে তখন এটি কর্মক্ষমতাকে আটকে রাখে পারে ওভারল্যাপ
একাধিক স্ট্রীম: সমসাময়িক
একাধিক স্ট্রীমের জন্য ক্লাসিক ব্যবহারের ক্ষেত্রে হল ডেটা স্থানান্তরের সাথে ওভারল্যাপিং গণনা. GPU ব্যাচ N প্রক্রিয়া করার সময়, আপনি একই সাথে CPU RAM থেকে GPU VRAM-এ ব্যাচ N+1 কপি করতে পারেন:
Stream 0 (compute): [process batch 0]────[process batch 1]───
Stream 1 (data): ────[copy batch 1]────[copy batch 2]───
এই পাইপলাইনটি সম্ভব কারণ GPU এর ভিতরে পৃথক হার্ডওয়্যার ইউনিটে গণনা এবং ডেটা স্থানান্তর ঘটে, যা সত্যিকারের সমান্তরালতা সক্ষম করে। PyTorch-এ, আপনি প্রসঙ্গ পরিচালকদের সাথে স্ট্রীম তৈরি করেন এবং সেগুলিতে কাজের সময়সূচী করেন:
compute_stream = torch.cuda.Stream()
transfer_stream = torch.cuda.Stream()
with torch.cuda.stream(transfer_stream):
# Enqueue the transfer on transfer_stream
next_batch = next_batch_cpu.to('cuda', non_blocking=True)
with torch.cuda.stream(compute_stream):
# This runs concurrently with the transfer above
output = model(current_batch)
নোট নিন non_blocking=True পতাকা উত্তোলন .to(). এটি ছাড়া, স্থানান্তর এখনও সিপিইউ থ্রেডকে ব্লক করবে এমনকি যদি আপনি এটিকে অ্যাসিঙ্ক্রোনাসভাবে চালাতে চান।
স্ট্রীম মধ্যে সিঙ্ক্রোনাইজেশন
যেহেতু স্ট্রিমগুলি স্বাধীন, তাই আপনাকে স্পষ্টভাবে নির্দেশ করতে হবে যখন একটি অন্যটির উপর নির্ভর করে। ভোঁতা যন্ত্র হল:
torch.cuda.synchronize() # waits for ALL streams on the device to finish
একটি আরো অস্ত্রোপচার পদ্ধতি ব্যবহার করে CUDA উন্নয়ন. একটি ইভেন্ট একটি স্ট্রিমে একটি নির্দিষ্ট বিন্দু চিহ্নিত করে, এবং অন্য একটি স্ট্রীম CPU থ্রেড বন্ধ না করে এটির উপর অপেক্ষা করতে পারে:
event = torch.cuda.Event()
with torch.cuda.stream(transfer_stream):
next_batch = next_batch_cpu.to('cuda', non_blocking=True)
event.record() # mark: transfer is done
with torch.cuda.stream(compute_stream):
compute_stream.wait_event(event) # don't start until transfer completes
output = model(next_batch)
এটা আরও বেশি কার্যকরী stream.synchronize() কারণ এটি শুধুমাত্র GPU পাশের নির্ভরশীল স্ট্রীমকে থামায় – CPU থ্রেড সারিবদ্ধ কাজ চালিয়ে যাওয়ার জন্য বিনামূল্যে থাকে।
দৈনিক PyTorch প্রশিক্ষণ কোডের জন্য আপনাকে ম্যানুয়ালি স্ট্রিম পরিচালনা করতে হবে না। কিন্তু বৈশিষ্ট্য মত DataLoader(pin_memory=True) এবং প্রিফেচিং হুডের নীচে এই প্রক্রিয়াটির উপর অনেক বেশি নির্ভর করে। স্ট্রীম বোঝা আপনাকে সেই সেটিংস কেন বিদ্যমান তা শনাক্ত করতে সহায়তা করে এবং যখন সেগুলি উপস্থিত হয় তখন সূক্ষ্ম কার্যক্ষমতার বাধাগুলি নির্ণয় করার জন্য আপনাকে সরঞ্জাম দেয়৷
pytorch tensor
PyTorch একটি শক্তিশালী ফ্রেমওয়ার্ক যা অনেক বিশদ বিবরণকে বিমূর্ত করে, কিন্তু এই বিমূর্ততা কখনও কখনও হুডের নীচে কী ঘটছে তা অস্পষ্ট করতে পারে।
আপনি যখন একটি PyTorch টেনসর তৈরি করেন, তখন এর দুটি অংশ থাকে: মেটাডেটা (যেমন এর আকার এবং ডেটা টাইপ) এবং প্রকৃত সংখ্যাসূচক ডেটা। তাই যখন আপনি এই মত কিছু চালান t = torch.randn(100, 100, device=device)টেনসরের মেটাডেটা হোস্টের র্যামে সংরক্ষিত থাকে, যখন এর ডেটা GPU-এর VRAM-এ সংরক্ষিত থাকে।
এই পার্থক্য গুরুত্বপূর্ণ। যখন আপনি দৌড়ান print(t.shape)CPU এই তথ্যটি অবিলম্বে অ্যাক্সেস করতে পারে কারণ মেটাডেটা ইতিমধ্যেই তার নিজস্ব RAM-তে রয়েছে। কিন্তু আপনি যদি দৌড়ান তাহলে কি হবে print
হোস্ট-ডিভাইস সিঙ্ক্রোনাইজেশন
CPU থেকে GPU ডেটা অ্যাক্সেস করা ট্রিগার হতে পারে হোস্ট-ডিভাইস সিঙ্ক্রোনাইজেশনএকটি সাধারণ কর্মক্ষমতা বাধা. এটি তখন ঘটে যখন CPU-এর GPU থেকে ফলাফলের প্রয়োজন হয় যা এখনও CPU-এর RAM-এ উপলব্ধ নয়।
উদাহরণস্বরূপ, লাইন বিবেচনা করুন print(gpu_tensor) যা একটি টেনসর প্রিন্ট করে যা এখনও GPU দ্বারা গণনা করা হচ্ছে। GPU চূড়ান্ত ফলাফল পাওয়ার জন্য সমস্ত গণনা সম্পূর্ণ না করা পর্যন্ত CPU টেনসরের মানগুলি মুদ্রণ করতে পারে না। যখন স্ক্রিপ্ট এই লাইনে পৌঁছায়, তখন CPU বাধ্য হয় ব্লকএর মানে এটি থামে এবং GPU শেষ হওয়ার জন্য অপেক্ষা করে। GPU তার কাজ শেষ করার পরে এবং তার VRAM থেকে CPU এর RAM-এ ডেটা কপি করার পরেই CPU এগিয়ে যেতে পারে।
অন্য উদাহরণ হিসাবে, মধ্যে পার্থক্য কি torch.randn(100, 100).to(device) এবং torch.randn(100, 100, device=device)? প্রথম পদ্ধতিটি কম কার্যকর কারণ এটি CPU-তে ডেটা তৈরি করে এবং তারপর GPU-তে স্থানান্তর করে। দ্বিতীয় পদ্ধতিটি আরও কার্যকর কারণ এটি সরাসরি GPU-তে টেনসর তৈরি করে; CPU শুধুমাত্র সৃষ্টি কমান্ড পাঠায়।
এই সিঙ্ক্রোনাইজেশন পয়েন্টগুলি কার্যকারিতাকে গুরুতরভাবে প্রভাবিত করতে পারে। কার্যকরী জিপিইউ প্রোগ্রামিং হোস্ট এবং ডিভাইস উভয়কে যতটা সম্ভব ব্যস্ত রাখতে তাদের ক্ষুদ্রকরণ করে। সব পরে, আপনি আপনার GPU যেতে চান brrrrr.

স্কেলিং আপ: ডিস্ট্রিবিউটেড কম্পিউটিং এবং র্যাঙ্ক
বৃহৎ মডেল, যেমন বড় ভাষা মডেল (LLM) প্রশিক্ষণের জন্য প্রায়শই একটি GPU প্রদান করতে পারে তার চেয়ে বেশি কম্পিউটিং শক্তির প্রয়োজন হয়। একাধিক জিপিইউ জুড়ে কাজ সমন্বয় করা আপনাকে বিতরণ করা কম্পিউটিং জগতে নিয়ে আসে।
এই প্রসঙ্গে, একটি নতুন এবং গুরুত্বপূর্ণ ধারণা উদ্ভূত হয়: পোস্ট.
- সবাই পদমর্যাদা একটি সিপিইউ প্রক্রিয়া রয়েছে যা একটি একক ডিভাইস (GPU) এবং একটি অনন্য আইডি বরাদ্দ করা হয়। আপনি যদি দুটি জিপিইউ জুড়ে একটি প্রশিক্ষণ স্ক্রিপ্ট চালু করেন, আপনি দুটি প্রক্রিয়া তৈরি করবেন: এক
rank=0এবং অন্যের সাথেrank=1.
এর মানে হল যে আপনি আপনার পাইথন স্ক্রিপ্টের দুটি পৃথক উদাহরণ চালু করছেন। একাধিক GPU (একক নোড) সহ একটি একক মেশিনে, এই প্রক্রিয়াগুলি একই CPU-তে চলে কিন্তু মেমরি বা অবস্থা ভাগ না করেই স্বাধীন থাকে। Rank 0 আপনার নির্দিষ্ট GPU-তে কমান্ড দেয় (cuda:0), যেখানে Rank 1 অন্য GPU কমান্ড (cuda:1) যদিও উভয় র্যাঙ্ক একই কোড চালায়, আপনি একটি ভেরিয়েবলের সুবিধা নিতে পারেন যা প্রতিটি জিপিইউতে বিভিন্ন কাজ বরাদ্দ করার জন্য র্যাঙ্ক আইডি ধারণ করে, যেমন প্রতিটি ডেটার একটি ভিন্ন অংশ প্রক্রিয়া করে (আমরা এই সিরিজের পরবর্তী ব্লগ পোস্টে এর উদাহরণগুলি দেখব)।
উপসংহার
শেষ পর্যন্ত পড়ার জন্য অভিনন্দন! এই পোস্টে, আপনি শিখেছেন:
- হোস্ট/ডিভাইস সম্পর্ক
- অ্যাসিঙ্ক্রোনাস মৃত্যুদন্ড
- CUDA স্ট্রীম এবং কিভাবে তারা সমসাময়িক GPU কাজ সক্ষম করে
- হোস্ট-ডিভাইস সিঙ্ক্রোনাইজেশন
পরবর্তী ব্লগ পোস্টে, আমরা পয়েন্ট-টু-পয়েন্ট এবং সমষ্টিগত ক্রিয়াকলাপগুলির গভীরে অনুসন্ধান করব, যা বিতরণ করা নিউরাল নেটওয়ার্ক প্রশিক্ষণের মতো জটিল কর্মপ্রবাহের সমন্বয় করতে একাধিক GPU-কে সক্ষম করে।