මෙයකඩදාසි වල හඳුන්වා දුන් ශුන්ය-ඩීපී ක්රියාත්මක කිරීමකි ශුන්ය: පුහුණුව සඳහා මතක ප්රශස්තිකරණය ට්රිලියනයක් පරාමිති ආකෘති,
එයප්රශස්තිකරණ තත්වයේ, ශ්රේණියේ සහ පරාමිතීන් බහු උපාංග/නෝඩ් වලට තබා ගනී. එය මුල් ආකෘතියට මතක පරිභෝජනය අඩු කරයි, පරාමිති ගණන කොතැනද, කැබලි ගණන ද , පරාමිතියකට ප්රශස්තිකරණ බයිට් ගණන. බිට් 16-නිරවද්යතාව උපකල්පනය කරන පරාමිතිය සහ ශ්රේණියේ මතකය; එනම් පරාමිතියට බයිට් 2 ක් සහ අනුක්රමික ය. සඳහා ආදම් ප්රශස්තකරණය එය පරාමිතීන්ගේ පිටපතක් සහ fp32 හි පරාමිතියකට අවස්ථා දෙකක් පවත්වා ගෙන යන බැවිනි.
ශුන්ය-ඩීපීහි සන්නිවේදන පරිමාව වේ . සංසන්දනය සඳහා දත්ත-සමාන්තර පුහුණුව සන්නිවේදන පරිමාවක් ඇත.
මෙයනම් කර ඇතත් Zero3
, අප ක්රියාත්මක කර ඇත්තේ එහි ශුන්ය-ඩීපී කොටස පමණක් මිස අවශේෂ මතක පරිභෝජනය ඉලක්ක කරන Zero-R මතක ප්රශස්තිකරණයන් නොවේ. පිටත ක්රියාත්මක කිරීම සඳහා පුහුණුව සඳහා සහාය වන්නේ පරාමිතීන්ගේ උප කුලකයක් පමණි.
මෙමක්රියාත්මක කිරීම Fairscale FSDPවිසින් දේවානුභාවයෙන්.
ZERO-DP මතක ප්රශස්තිකරණය භාවිතා කරමින් GPT Neox මනාව සකස් කිරීම සඳහා ස්ක්රිප්ට් එකක් මෙන්න .
32import functools
33from typing import List, Optional, Tuple
34
35import torch
36import torch.distributed as dist
37from torch import nn
40class Zero3Layer(nn.Module):
සෑමකොටසක්ම පරාමිතීන් chunk
ලැයිස්තුවේ තබා ගනී. පුහුණු කළ හැකි පරාමිතීන් සඳහා chunk[0]
chunk[1]
වන අතර ස්ථාවර පරාමිතීන් සඳහා වේ.
49 chunk: List[nn.Parameter]
chunk
ලැයිස්තුවේ ඇති කුට්ටි වල ප්රමාණය මෙයයි.
51 chunk_size: List[int]
පළමුකුට්ටිය පුහුණු කළ හැකි පරාමිතීන් සඳහා වේ.
53 TRAINING_PARAMS_IDX = 0
පුහුණුකළ හැකි සහ ස්ථාවර පරාමිතීන් ලෙස ලැයිස්තු වලට බෙදී ඇති පරාමිති ලැයිස්තුව මෙයයි.
56 param_refs: List[List[nn.Parameter]]
පරාමිතීන්පිහාටු කිරීමට CUDA ධාරාව
59 fetch_stream: Optional[torch.cuda.Stream]
උපස්ථ/සමුච්චයඵලය අනුක්රමික කිරීමට CDA ඇළ
61 backup_stream: Optional[torch.cuda.Stream]
මෙමස්ථරයට පෙර ස්ථර ලැයිස්තුව
63 prev_layer: List['Zero3Layer']
මෙමස්ථරයට පසුව ස්ථර ලැයිස්තුව
65 next_layer: List['Zero3Layer']
වත්මන්ස්ථරයේ පිහිටීම; ලඝු-සටහන් නිදොස්කරණය සඳහා මෙය භාවිතා කරයි
67 layer_idx: int
පරාමිතීන්ලබා ගෙන තිබේද
70 is_fetched: bool
ස්ථරයේඋපාංගය
73 device: torch.device
ස්ථරයේදත්ත වර්ගය
75 dtype: torch.dtype
එවියයුතු මොඩියුලය
77 module: nn.Module
නෝඩ්/උපාංගගණන දත්ත හරහා තියුණු වේ
79 world_size: int
module
ඔතා ගත යුතු මොඩියුලය. rank
වත්මන් නෝඩයේ නිලය. world_size
දත්ත හරහා තියුණු කර ඇති නෝඩ්/උපාංග ගණන. device
ස්ථරයේ උපාංගය. dtype
ස්ථරයේ දත්ත වර්ගය. 81 def __init__(self, module: nn.Module, rank: int, world_size: int, device: torch.device, dtype: torch.dtype):
89 super().__init__()
ගුණාංගආරම්භ කරන්න
92 self.device = device
93 self.dtype = dtype
94 self.module = module
95 self.prev_layer = []
96 self.next_layer = []
97 self.is_fetched = False
98 self.world_size = world_size
99 self.layer_idx = -1
100 self.fetch_stream = None
101 self.backup_stream = None
102
103 with torch.no_grad():
ස්ථරයේසියලු පරාමිතීන් එකතු කරන්න
105 all_param_refs = [p for p in self.parameters()]
පරාමිතීන්ගේහැඩය ගබඩා කර තබන්න, මන්ද ඒවා ප්රතිනිර්මාණය කිරීමට අපට පසුව අවශ්ය වේ
108 for p in all_param_refs:
109 p._orig_shape = p.shape
සියලුමපරාමිතීන් එකම වර්ගයේ තිබිය යුතුය
112 for p in all_param_refs:
113 assert p.dtype == dtype, "All parameters should have same dtype"
පුහුණුකළ හැකි සහ ස්ථාවර ලෙස වෙනම පරාමිතීන්
116 self.param_refs = [[p for p in all_param_refs if p.requires_grad],
117 [p for p in all_param_refs if not p.requires_grad]]
118 del all_param_refs
මෙම rank = 0
නෝඩය එක් එක් උපාංගය/නෝඩය ගබඩා කළ යුතු ප්රමාණය ගණනය කරනු ඇත, ඒ අනුව පරාමිතීන් බෙදා හරිනු ඇත.
122 if rank == 0:
ඒකාබද්ධකිරීම සහ පෑඩ් පුහුණු කළ හැකි (merged_params[0]
) සහ ස්ථාවර (merged_params[1]
) පරාමිතීන්
124 merged_params = [self._merge_and_pad_params(ps) for ps in self.param_refs]
පුහුණුකළ හැකි සහ ස්ථාවර පරාම් ප්රමාණ ගණනය කරන්න
126 self.chunk_size = [(len(p) // world_size if p is not None else 0) for p in merged_params]
ප්රමාණවිකාශනය කරන්න
128 dist.broadcast(torch.tensor(self.chunk_size, device=device), src=0)
129 else:
ප්රමාණලබා ගැනීම සඳහා හිස් ටෙන්සරයක් සාදන්න
131 chunk_size = torch.tensor([0, 0], device=device)
ප්රමාණලබා ගන්න
133 dist.broadcast(chunk_size, src=0)
134 self.chunk_size = chunk_size.tolist()
පුහුණුකළ හැකි (self.chunk[0]
) සහ ස්ථාවර (self.chunk[1]
) පරාමිතීන් සඳහා වත්මන් උපාංගයේ/නෝඩයේ ගබඩා කිරීම සඳහා පරාමිතීන් සාදන්න
138 self.chunk = [nn.Parameter(self._empty((s,)), requires_grad=i == self.TRAINING_PARAMS_IDX)
139 for i, s in enumerate(self.chunk_size)]
පුහුණුකළ හැකි සහ ස්ථාවර පරාමිතීන් ඒකාබද්ධ කිරීම සඳහා හිස් ටෙන්සරයක්
142 chunk = self._empty((sum(self.chunk_size),))
143
144 if rank == 0:
පුහුණුකළ හැකි සහ ස්ථාවර පරාම් දෙකම සංයුක්ත කරන්න
146 all_params = torch.cat([p.view(world_size, -1) for p in merged_params], dim=-1).view(-1)
147 del merged_params
සියලුමනෝඩ්/උපාංග වෙත ඒවා විසුරුවා හරින්න
150 dist.scatter(chunk, list(all_params.split(sum(self.chunk_size))))
151 del all_params
152 else:
පරාමිතීන්ලබා ගන්න
154 dist.scatter(chunk)
කුට්ටියදත්ත එකතු කරන්න
157 chunk = chunk.split(self.chunk_size)
158 for i, c in enumerate(chunk):
159 self.chunk[i].data[:] = c
160 del chunk
සාමාන්යපරාමිතීන් පිරිසිදු කරන්න
163 self._cleanup_params()
පසුගාමීකොක්කක් එක් කරන්න. මොඩියුලයට සාපේක්ෂව අනුක්රමික ගණනය කළ විට මෙය කැඳවනු ලැබේ.
166 self._backward_hook_ref = self.register_full_backward_hook(self._backward_hook) # type: ignore
world_size
. 168 def _merge_and_pad_params(self, params: List[nn.Parameter]) -> torch.Tensor:
මුළුපරාමිතීන් ගණන
173 size = sum(p.shape.numel() for p in params)
එයබෙදිය නොහැකි නම් world_size
, එය පෑඩ් කරන්න
176 if size % self.world_size != 0:
177 padding_fixed = self.world_size - (size % self.world_size)
එසේනොමැති නම්, පෑඩ් කිරීමට අවශ්ය නැත
179 else:
180 padding_fixed = 0
හිස්පුරවන tensor සාදන්න
182 padding = self._empty((padding_fixed,))
සියලුමපරාමිතීන් සංයුක්ත කර එය පෑඩ් කරන්න
184 return torch.cat([p.view(-1) for p in params] + [padding], dim=0)
186 def get_trainable_chunk(self) -> List[nn.Parameter]:
පුහුණුකළ හැකි පරාමිතීන් නොමැති නම් ආපසු සහ හිස් ලැයිස්තුව
193 if len(self.chunk[self.TRAINING_PARAMS_IDX]) == 0:
194 return []
පුහුණුකළ හැකි කුට්ටිය ලැයිස්තුවක් ලෙස ආපසු එවන්න
197 return [self.chunk[self.TRAINING_PARAMS_IDX]]
199 def _empty(self, shape: Tuple[int, ...]) -> torch.Tensor:
203 return torch.empty(shape, device=self.device, dtype=self.dtype)
205 @torch.no_grad()
206 def _cleanup_params(self):
පරාමිතීන්ලබා ගත නොහැකි බව දැක්වීමට ධජය සකසන්න
214 self.is_fetched = False
සියලුපරාමිතීන් හරහා නැවත
217 for ps in self.param_refs:
218 for p in ps:
නවමෙහෙයුම් වලට පෙර පරාමිතීන්ගේ මෙහෙයුම් සම්පූර්ණ වන තෙක් රැඳී සිටින්න
220 p.data.record_stream(torch.cuda.current_stream())
පරාමිතියවෙනත් කිසිවක් සමඟ ගබඩා බෙදා නොගන්නා බවට වග බලා ගන්න පරීක්ෂා කරන්න
222 assert p.data.storage_offset() == 0, "The tensor is not the sole occupant of the storage."
ගබඩාවවෙනස් කරන්න . මෙය පරාමිතිය භාවිතා කරන මතකය මුදා හරිනු ඇත.
ස්වයංක්රීයප්රස්තාරය ඒ පිළිබඳව සඳහනක් තබා ඇති බැවින් සැකසුම මතකය මුදා p.data
නොහරිනු ඇත.
226 p.data.storage().resize_(0) # This is what actually clears the memory
පරාමිතියටශ්රේණියේ දත්ත නොමැති බවට වග බලා ගන්න
228 assert p.grad is None, 'Gradients should be None'
මෙයසියලු නෝඩ් වලින් සියලු පරාමිති දත්ත ලබා ගන්නා අතර එක් එක් නෝඩයේ පරාමිතීන් නැවත ගොඩනඟනු ඇත.
230 @torch.no_grad()
231 def fetch_params(self):
මඟහැරීම දැනටමත් ලැබී ඇත
239 if self.is_fetched:
240 return
ධජයසකසන්න
243 self.is_fetched = True
ගැනීමට හෝ බෙදා ගැනීමට කිසිවක් නොමැති නම් මඟ හරින්න.
246 if sum(self.chunk_size) == 0:
247 return
සියලුකැබලි වලින් පරාමිතීන් ලබා fetch_stream
ගැනීමට භාවිතා කරන්න
250 with torch.cuda.stream(self.fetch_stream):
පරාමිතීන්ලබා ගැනීම සඳහා හිස් ටෙන්සරයක් සාදන්න
252 buffer = self._empty((self.world_size * sum(self.chunk_size),))
අඛණ්ඩබෆරය නෝඩ් ගණනට බෙදන්න. මෙම බෙදීම් `බෆර්' පිළිබඳ අදහස් වේ.
254 buffers = list(buffer.split(sum(self.chunk_size)))
පුහුණුකළ හැකි සහ ස්ථාවර කුට්ටි දෙකම සංයුක්ත කරන්න
257 chunk = torch.cat(self.chunk, dim=0)
සියලුමනෝඩ්/උපාංග වලින් පරාමිතීන් එක්රැස් කරන්න
260 dist.all_gather(buffers, chunk)
රැස්කරන ලද පරාමිතීන් පුහුණු කළ හැකි සහ ස්ථාවර කුට්ටි වලට බෙදන්න
263 params = buffer.view(-1, sum(self.chunk_size)).split(self.chunk_size, dim=1)
එක්රැස්කිරීමේ මෙහෙයුම සම්පූර්ණ වන තෙක් රැඳී සිටින්න, පසුව අවරෝධක වෙත යොමු කිරීම් ඉවත් කරන්න
265 buffer.record_stream(self.fetch_stream)
266 for b in buffers:
267 b.record_stream(self.fetch_stream)
268 buffer.record_stream(self.fetch_stream)
269 del buffer
270 del buffers
පුහුණුකළ හැකි සහ ස්ථාවර පරාමිතීන් අඛණ්ඩ ආතතීන්ට නැවත සකස් කරන්න
273 params = [p.reshape(-1) for p in params]
තනිපරාමිති ආතතීන් එකතු කරන්න
276 for cont, ps in zip(params, self.param_refs):
පරාමිතීන්නොමැති නම්, මඟ හරින්න
278 if not ps:
279 continue
අඛණ්ඩආතතියෙන් ඕෆ්සෙට්
282 offset = 0
ආදර්ශපරාමිතීන් හරහා නැවත නැවතත් සහ අඛණ්ඩ tensor සිට අගයන් පැවරීම
284 for p in ps:
මුල්පරාමිති හැඩය
286 shape = p._orig_shape # type: ignore[attr-defined]
පරාමිතියේගබඩා ප්රමාණය වෙනස් කරන්න. අපි පරාමිතීන් පිරිසිදු කළ විට මෙය සකස් කරන ලදී.
288 p.data.storage().resize_(shape.numel())
අඛණ්ඩආතතියෙන් අගයන් පැවරීම
290 p.data[:] = cont[offset: offset + shape.numel()].reshape(shape)
වෙනත්මෙහෙයුම් සිදු කිරීමට පෙර මෙහෙයුම් සම්පූර්ණ වන තෙක් රැඳී සිටින්න
292 p.data.record_stream(self.fetch_stream)
ඕෆ්සෙට්යාවත්කාලීන කරන්න
294 offset += shape.numel()
වෙනත්මෙහෙයුම් සිදු කිරීමට පෙර මෙහෙයුම සම්පූර්ණ වන තෙක් රැඳී සිටින්න
297 cont.record_stream(self.fetch_stream)
300 del params
302 def forward(self, *args, **kwargs):
වත්මන්නෝඩයේ සියලු පරාමිතීන් ලබා ගන්න. මෙය පෙර ස්තරය මගින් කැඳවනු ලැබේ, එබැවින් මෙම ඇමතුම පරාමිතීන් ලබා ගත හැකි බවට වග බලා ගැනීම සඳහා පමණි.
309 self.fetch_params()
පරාමිතියසම්පූර්ණ කිරීම සඳහා රැඳී සිටින්න.
312 torch.cuda.current_stream().wait_stream(self.fetch_stream)
ඉදිරියටයන ස්ථරවල පරාමිතීන් ලබා ගැනීම ආරම්භ කරන්න, එවිට වත්මන් ස්තරය එහි ගණනය කිරීම් සිදු කරන ඒවා ලබා ගනු ඇත.
316 for layer in self.next_layer:
317 layer.fetch_params()
ඔටෝග්රාඩ්සක්රීය කර ඇත්නම් වත්මන් ස්ථරයේ පරාමිතීන් සඳහා පසුගාමී කොකු එකතු කරන්න.
320 if torch.is_grad_enabled():
321 self._add_backward_hooks()
වත්මන්ස්ථරයේ ප්රතිදානයන් ගණනය කරන්න
324 res = self.module(*args, **kwargs)
ස්ථරයේපරාමිතීන් පිරිසිදු කරන්න.
ඔටෝග්රාඩ්සක්රීය කර ඇත්නම් පිරිසිදු කිරීම මඟ හරින්න, මෙය ජාලයේ අවසාන ස්තරය වේ, මන්ද පසුගාමී පාස් සඳහා අපට නැවත පරාමිතීන් ලබා ගැනීමට අවශ්ය වනු ඇත.
330 if not torch.is_grad_enabled() or self.next_layer:
331 self._cleanup_params()
332
333 return res
335 def _add_backward_hooks(self):
පසුගාමීකොකු ගණන එකතු කරන ලදි
341 self._backward_hook_handles = 0
වත්මන්ස්ථරයේ පුහුණු කළ හැකි පරාමිතීන් හරහා ලූප්
344 for p in self.param_refs[self.TRAINING_PARAMS_IDX]:
කොක්කක්දැනටමත් එකතු කර නොමැති බවට වග බලා ගන්න
346 assert not hasattr(p, "_hook_handle"), 'Parameter has already been hooked'
අපටබාධා කළ හැකි ස්වයංක්රීය පියවරක් නිර්මාණය expand_as
කිරීමට භාවිතා කරන්න
348 p_tmp = p.expand_as(p)
පසුගාමීකොක්ක එක් කිරීමට හසුරුව ලබා ගන්න. මෙම බ්ලොග් අඩවිය ගැන සාකච්ඡා කරයි grad_acc
.
351 grad_acc = p_tmp.grad_fn.next_functions[0][0]
පසුගාමීකොක්ක එක් කරන්න
353 handle = grad_acc.register_hook(
354 functools.partial(self._post_backward_hook, p))
හසුරුවගැන සඳහනක් තබා ගන්න
356 p._hook_handle = handle
එකතුකරන ලද කොකු ගණන වැඩි කරන්න
358 self._backward_hook_handles += 1
360 def _backward_event(self):
කොකුකවුන්ටරය අඩු කිරීම
368 self._backward_hook_handles -= 1
සියලුමකොකු (මොඩියුල කොක්ක ඇතුළුව) කැඳවා ඇත්නම්, අපට අනුක්රමික උපස්ථ කර පරාමිතීන් පිරිසිදු කළ හැකිය.
372 if self._backward_hook_handles == -1:
373 self._backup_grads()
374 self._cleanup_params()
පෙරස්ථරයේ පරාමිතීන් ලබා ගැනීම ආරම්භ කරන්න, මන්ද ඔටෝග්රාඩ් ඊළඟට එහි අනුක්රමික ක්රියාවලිය කරනු ඇත.
377 for layer in self.prev_layer:
378 layer.fetch_params()
380 def _post_backward_hook(self, p: nn.Parameter, *args):
පරාමිතියෙන්හසුරුව ඉවත් කරන්න
385 p._hook_handle.remove() # type: ignore[attr-defined]
386 delattr(p, "_hook_handle")
පසුගාමීසිදුවීමක් හසුරුවන්න
389 self._backward_event()
391 def _backward_hook(self, *args, **kwargs):
පසුගාමීසිදුවීමක් හසුරුවන්න
396 self._backward_event()
පෙරස්ථරය පරිගණක අනුක්රමික ආරම්භ වනු ඇත. එය පරාම් ලබා ගැනීම අවසන් කර ඇති බවට අප වග බලා ගත යුතුය.
399 torch.cuda.current_stream().wait_stream(self.fetch_stream)
402 return None
404 @torch.no_grad()
405 def _backup_grads(self):
පුහුණුකළ හැකි පරාමිතීන් නොමැති නම් මඟ හරින්න
410 if self.chunk_size[self.TRAINING_PARAMS_IDX] == 0:
411 return
අනුක්රමිකඋපස්ථ කිරීමට උපස්ථ ධාරාව භාවිතා කරන්න
414 with torch.cuda.stream(self.backup_stream):
අනුක්රමිකගබඩා කිරීමට බෆරය
416 buffer = self._empty((self.world_size * self.chunk_size[self.TRAINING_PARAMS_IDX],))
අඛණ්ඩබෆරය නෝඩ් ගණනකට බෙදන්න. මෙම බෙදීම් `බෆර්' පිළිබඳ අදහස් වේ.
418 buffers = list(buffer.split(self.chunk_size[self.TRAINING_PARAMS_IDX]))
අඛණ්ඩබෆරයේ ඕෆ්සෙට්
421 offset = 0
පුහුණුකළ හැකි පරාමිතීන් හරහා නැවත ක්රියාත්මක කරන්න
423 for p in self.param_refs[self.TRAINING_PARAMS_IDX]:
අනුක්රමිකඑකතු
425 shape = p._orig_shape # type: ignore[attr-defined]
426 buffer[offset: offset + shape.numel()] = p.grad.view(-1)
ඕෆ්සෙට්යාවත්කාලීන කරන්න
428 offset += shape.numel()
අනුක්රමිකපිරිසිදු
430 p.grad = None
වත්මන්ෂාර්ඩ් වල අනුක්රමික සමුච්චය කිරීම සඳහා හිස් ටෙන්සරය
433 grad = self._empty((self.chunk_size[self.TRAINING_PARAMS_IDX],))
එක්එක් ශාකයේ අනුක්රමික සමුච්චය කරන්න. එය නෝඩ් හරහා බෆර විසිරී යන අතර සෑම නෝඩයක්ම එයට ලැබෙන ආතතීන් එකතු වේ (අඩු කරයි).
436 dist.reduce_scatter(grad, buffers)
මෙහෙයුමසම්පූර්ණ වන තෙක් රැඳී සිටින්න, පසුව අවරෝධක වෙත යොමු කිරීම් ඉවත් කරන්න
439 for b in buffers:
440 b.record_stream(self.fetch_stream)
441 buffer.record_stream(self.fetch_stream)
442 del buffer
443 del buffers
කුට්ටිඅනුක්රමික සකසන්න. ප්රශස්තකරණය දකින්නේ මෙයයි.
446 self.chunk[self.TRAINING_PARAMS_IDX].grad = grad
447 del grad
Zero3Layer
ස්ථර සඳහා අනුක්රමික මොඩියුලය450class Zero3Sequential(nn.Module):
modules
Zero3Layer
ස්ථර ලැයිස්තුව454 def __init__(self, modules: List[Zero3Layer]):
458 super().__init__()
පරාමිතීන්ලබා ගැනීමට CUDA ධාරාව
461 self.fetch_stream = torch.cuda.Stream()
උපස්ථකිරීමට CUDA ධාරාව (සමුච්චය) අනුක්රමික
463 self.backup_stream = torch.cuda.Stream()
එක්එක් Zero3Layer
ස්ථරයක් සඳහා ධාරාවන් සහ පෙර සහ ඉදිරියට යන ස්ථර සකසන්න
466 for i in range(len(modules)):
ස්ථරදර්ශකය සකසන්න
468 modules[i].layer_idx = i
ධාරාවන්සකසන්න
470 modules[i].fetch_stream = self.fetch_stream
471 modules[i].backup_stream = self.backup_stream
ඉදිරියටයන ස්ථර සකසන්න
473 if i + 1 < len(modules):
474 modules[i].next_layer.append(modules[i + 1])
පෙරස්ථර සකසන්න
476 if i - 1 >= 0:
477 modules[i].prev_layer.append(modules[i - 1])
මොඩියුලගබඩා ලැයිස්තුව
480 self.module_list = nn.ModuleList(modules)
482 def get_trainable_chunk(self):
එක්එක් ස්ථරයෙන් පුහුණු කළ හැකි කුට්ටි ලැයිස්තුව ආපසු ලබා දෙන්න
484 return sum([m.get_trainable_chunk() for m in self.module_list], [])
486 def forward(self, x: torch.Tensor):
ආපසුදක්වා ඵලය අනුක්රමික සම්පූර්ණ බවට වග බලා ගන්න
488 torch.cuda.current_stream().wait_stream(self.backup_stream)
ඉදිරිසාමාර්ථය
491 for m in self.module_list:
492 x = m(x)
495 return x