精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

深入理解GPU內(nèi)存分配:機器學(xué)習(xí)工程師的實用指南與實驗

人工智能 機器學(xué)習(xí)
給定一個模型架構(gòu)、數(shù)據(jù)類型、輸入形狀和優(yōu)化器,你能否計算出前向傳播和反向傳播所需的GPU內(nèi)存量?要回答這個問題,我們需要將流程分解為基本組件,并從底層理解內(nèi)存需求。以下實驗(可以在Google Colab上運行)將幫助你理解核心概念。

給定一個模型架構(gòu)、數(shù)據(jù)類型、輸入形狀和優(yōu)化器,你能否計算出前向傳播和反向傳播所需的GPU內(nèi)存量?要回答這個問題,我們需要將流程分解為基本組件,并從底層理解內(nèi)存需求。以下實驗(可以在Google Colab上運行)將幫助你理解核心概念。

預(yù)留與分配

PyTorch預(yù)留了更多內(nèi)存,但只分配所需的內(nèi)存。這樣做是為了在需要更多內(nèi)存時能夠快速分配,而不是進行昂貴的預(yù)留操作。我們只關(guān)心內(nèi)存分配,而不關(guān)心預(yù)留。

def test_reservation_vs_allocation():
     print(f"Base memory reserved: {torch.cuda.memory_reserved(device_id)}")
     print(f"Base memory allocated: {torch.cuda.memory_allocated(device_id)}")
 
     # Allocate some memory
     x = torch.randn((1024,), dtype=torch.float32, device=device)
     print(f"Memory after allocation (reserved): {torch.cuda.memory_reserved(device_id)}")
     print(f"Memory after allocation (allocated): {torch.cuda.memory_allocated(device_id)}")
 
     # Cleanup
     del x
     print(f"Memory after cleanup (reserved): {torch.cuda.memory_reserved(device_id)}")
     print(f"Memory after cleanup (allocated): {torch.cuda.memory_allocated(device_id)}")
 
     torch.cuda.empty_cache()
     print(f"Memory after empty_cache (reserved): {torch.cuda.memory_reserved(device_id)}")
     print(f"Memory after empty_cache (allocated): {torch.cuda.memory_allocated(device_id)}")
 
 """
 Output:
 
 Base memory reserved: 0
 Base memory allocated: 0
 Memory after allocation (reserved): 2097152
 Memory after allocation (allocated): 4096
 Memory after cleanup (reserved): 2097152
 Memory after cleanup (allocated): 0
 Memory after empty_cache (reserved): 0
 Memory after empty_cache (allocated): 0
 """

當(dāng)刪除變量x或當(dāng)x超出作用域時,x的內(nèi)存被釋放,但仍然為將來使用而預(yù)留。只有在調(diào)用torch.cuda.empty_cache()時,才會釋放預(yù)留的內(nèi)存。

這里的torch.cuda.memory_allocated()將返回PyTorch在此進程上分配的內(nèi)存。如果有另一個進程正在使用一些GPU內(nèi)存,將返回0。為了獲取真實的GPU內(nèi)存使用情況,可以使用以下函數(shù)。

import subprocess
 
 
 def get_gpu_memory_used(gpu_id):
     """
    Returns the amount of memory used on the specified GPU in bytes.
 
    Parameters:
    gpu_id (int): The ID of the GPU (e.g., 0 for "cuda:0", 1 for "cuda:1").
 
    Returns:
    int: The amount of memory used on the GPU in bytes.
    """
     try:
         # Run the nvidia-smi command to get memory usage
         result = subprocess.run(
            ["nvidia-smi", "--query-gpu=memory.used", "--format=csv,nounits,noheader", f"--id={gpu_id}"],
             stdout=subprocess.PIPE,
             text=True
        )
 
         # Get the used memory in MiB from the result
         used_memory_mib = int(result.stdout.strip())
 
         # Convert MiB to bytes (1 MiB = 1024 * 1024 bytes)
         used_memory_bytes = used_memory_mib * 1024 * 1024
 
         return used_memory_bytes
 
     except Exception as e:
         print(f"Error occurred: {e}")
         return None

數(shù)據(jù)類型

float32需要4字節(jié)的內(nèi)存,bfloat16需要2字節(jié),我們可以繪制一些數(shù)據(jù)類型所需的內(nèi)存圖。

圖1:不同數(shù)據(jù)類型的內(nèi)存分配圖1:不同數(shù)據(jù)類型的內(nèi)存分配

def test_dtype_memory_allocation():
     dtypes = [torch.float32, torch.float16, torch.bfloat16, torch.int32, torch.int64, torch.uint8, torch.int8, torch.uint16]
     memories = []
     for dtype in dtypes:
         base_memory = get_gpu_memory_used(device_id)
         x = torch.ones((1024,), dtype=dtype, device=device)
         memory_after_allocation = get_gpu_memory_used(device_id)
         memories.append((memory_after_allocation - base_memory) // 1024)
         del x
         torch.cuda.empty_cache()
     fig = plt.figure(figsize=(7, 4))
     fig.set_tight_layout(True)
     plt.bar([str(d) for d in dtypes], memories)
     plt.xlabel("Data type")
     plt.ylabel("Bytes per element")
     plt.title("Memory allocation for different data types")
     plt.xticks(rotation=45)
     plt.show()

內(nèi)存塊

內(nèi)存以512字節(jié)的塊分配。當(dāng)創(chuàng)建一個張量時,它被分配到下一個可用的塊中。對于形狀為(800,)的float32張量,不是分配800 * 4 = 3200字節(jié),而是分配3584(512 * 7)字節(jié)。

圖2:不同張量大小的內(nèi)存分配。圖2:不同張量大小的內(nèi)存分配。

def test_memory_allocation_relationship():
     """
    For different sizes of tensors, check the memory allocated on GPU.
    """
     memories = []
     sizes = 1050
     for i in tqdm(range(sizes)):
         base_memory = get_gpu_memory_used(device_id)
         x = torch.randn((i,), dtype=torch.float32, device=device)
         memory_after_allocation = get_gpu_memory_used(device_id)
         memories.append(memory_after_allocation - base_memory)
         del x
         torch.cuda.empty_cache()
     plt.plot(memories)
     plt.xlabel("Size of float32 tensor")
     plt.ylabel("Memory allocated (bytes)")
     plt.title("Memory allocation for different tensor sizes")
     plt.show()

可訓(xùn)練參數(shù)(單個線性層前向傳播)

接下來我們將看一個單一的線性層。進行前向傳播,并計算所需的內(nèi)存。

def test_single_linear_layer_forward_allocation():
     # Disable cublas
     # import os; os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":0:0"
 
     print(f"Base memory: {torch.cuda.memory_allocated(device_id)}")
 
     model = nn.Linear(256, 250, device=device, dtype=torch.float32)
     print(f"Memory after model allocation: {torch.cuda.memory_allocated(device_id)}")
 
     x = torch.randn((1, 256,), dtype=torch.float32, device=device)
     print(f"Memory after input allocation: {torch.cuda.memory_allocated(device_id)}")
 
     y = model(x)
     final_memory = torch.cuda.memory_allocated(device_id)
     print(f"Memory after forward pass: {final_memory}")
 
     # Memory calculations
     w_mem = len(model.weight.flatten()) * model.weight.dtype.itemsize
     # Get the higher multiple of 512
     w_mem_as_chunks = (w_mem + 511) // 512 * 512
     print(f"{model.weight.shape=}, {w_mem=}, {w_mem_as_chunks=}")
 
     b_mem = len(model.bias) * model.bias.dtype.itemsize
     b_mem_as_chunks = (b_mem + 511) // 512 * 512
     print(f"{model.bias.shape=}, {b_mem=}, {b_mem_as_chunks=}")
 
     x_mem = (len(x.flatten()) * x.dtype.itemsize + 511) // 512 * 512
     y_mem = (len(y.flatten()) * y.dtype.itemsize + 511) // 512 * 512
     print(f"{x_mem=}, {y_mem=}")
 
     total_memory_expected = w_mem_as_chunks + b_mem_as_chunks + x_mem + y_mem
 
     cublas_workspace_size = 8519680
     memory_with_cublas = total_memory_expected + cublas_workspace_size
     print(f"{total_memory_expected=}, {memory_with_cublas=}")
     
     assert final_memory == memory_with_cublas
 
     del model, x, y
     torch.cuda.empty_cache()
     print(f"Memory after cleanup: {torch.cuda.memory_allocated(device_id)}")
 
     torch._C._cuda_clearCublasWorkspaces()
     print(f"Memory after clearing cublas workspace: {torch.cuda.memory_allocated(device_id)}")
 
 """
 Output:
 Base memory: 0
 Memory after model allocation: 257024
 Memory after input allocation: 258048
 Memory after forward pass: 8778752
 model.weight.shape=torch.Size([250, 256]), w_mem=256000, w_mem_as_chunks=256000
 model.bias.shape=torch.Size([250]), b_mem=1000, b_mem_as_chunks=1024
 x_mem=1024, y_mem=1024
 total_memory_expected=259072, memory_with_cublas=8778752
 Memory after cleanup: 8519680
 Memory after clearing cublas workspace: 0
 """

model有一個形狀為(256, 250)的float32 weight矩陣,占用(256 * 250 * 4) = 256,000字節(jié),這正好是內(nèi)存塊大小512的倍數(shù)(512 * 500 = 256,000)。但是bias有250個float32需要占用(250 * 4) = 1000字節(jié)。而512的更高倍數(shù)是2,(512 * 2) = 1024字節(jié)。x和y是形狀為(256,)的張量,所以它們各占用1024字節(jié)。總內(nèi)存 = weight + bias + x + y

當(dāng)我們將所有內(nèi)容加起來時,應(yīng)該得到259,072字節(jié)(256,000 + 1024 + 1024 + 1024)。但是實際觀察到的大小是8,778,752字節(jié)。這額外的8,519,680字節(jié)來自分配cuBLAS工作空間。

這是為快速矩陣乘法操作預(yù)留的內(nèi)存空間。對于某些matmul操作,會分配一個新的8,519,680字節(jié)的塊。這個大小可能會根據(jù)GPU和Python環(huán)境而變化。當(dāng)調(diào)用torch.cuda.empty_cache()時,cublas內(nèi)存不會消失。它需要torch._C._cuda_clearCublasWorkspaces()來實際清除它。也可以設(shè)置環(huán)境變量os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":0:0"來禁用cublas工作空間。但這可能是一種以犧牲執(zhí)行速度為代價來優(yōu)化內(nèi)存的方法,所以我們使用默認(rèn)就好。

梯度(單個線性層反向傳播)

使用相同的模型,運行l(wèi)oss.backward()。為簡單起見假設(shè)損失為loss = y.sum()。

def test_single_linear_layer_backward_allocation():
     print(f"Base memory: {torch.cuda.memory_allocated(device_id)}")
 
     model = nn.Linear(256, 250, device=device, dtype=torch.float32)
     x = torch.randn((1, 256,), dtype=torch.float32, device=device)
     y = model(x)
 
     print(f"Memory after forward pass: {torch.cuda.memory_allocated(device_id)}")
     y.sum().backward()
     final_memory = torch.cuda.memory_allocated(device_id)
     print(f"Memory after backward pass: {final_memory}")
 
     # Memory calculations
     next_chunk = lambda n: (n + 511) // 512 * 512
     units = model.weight.dtype.itemsize  # 4 bytes for float32
     mem = next_chunk(len(model.weight.flatten()) * units)
     mem += next_chunk(len(model.bias) * units)
     print(f"Excepted model memory: {mem}")
 
     x_mem = next_chunk(len(x.flatten()) * units)
     y_mem = next_chunk(len(y.flatten()) * units)
     print(f"{x_mem=}, {y_mem=}")
     mem += x_mem + y_mem
 
     # Gradient memory
     w_grad_mem = next_chunk(len(model.weight.grad.flatten()) * units)
     b_grad_mem = next_chunk(len(model.bias.grad.flatten()) * units)
     print(f"{model.weight.grad.shape=}, {w_grad_mem=}")
     print(f"{model.bias.grad.shape=}, {b_grad_mem=}")
     mem += w_grad_mem + b_grad_mem
 
     mem += 2 * 8519680  # cublas_size doubled
     print(f"Total memory expected: {mem}")
     assert final_memory == mem
 
     del model, x, y
     torch.cuda.empty_cache()
     print(f"Memory after cleanup: {torch.cuda.memory_allocated(device_id)}")
 
     torch._C._cuda_clearCublasWorkspaces()
     print(f"Memory after clearing cublas workspace: {torch.cuda.memory_allocated(device_id)}")
 
 """
 Output:
 Base memory: 0
 Memory after forward pass: 8778752
 Memory after backward pass: 17555456
 Excepted model memory: 257024
 x_mem=1024, y_mem=1024
 model.weight.grad.shape=torch.Size([250, 256]), w_grad_mem=256000
 model.bias.grad.shape=torch.Size([250]), b_grad_mem=1024
 Total memory expected: 17555456
 Memory after cleanup: 17039360
 Memory after clearing cublas workspace: 0
 """

由于每個具有requires_grad=True的模型參數(shù)都會有一個.grad成員來存儲底層張量的梯度,所以模型的大小會翻倍。

這次分配了2個cublas工作空間內(nèi)存塊,假設(shè)一個用于前向傳播,一個用于反向傳播。此時cublas何時確切地分配新塊還不確定。

中間張量(多層前饋網(wǎng)絡(luò))

當(dāng)模型在推理模式下運行時,沒有自動求導(dǎo)圖,不需要存儲中間張量。所以內(nèi)存量只是簡單地將每一層的內(nèi)存相加。

在需要跟蹤計算圖的訓(xùn)練模式下情況會有所不同。當(dāng)有多個串行應(yīng)用的操作時,比如在前饋網(wǎng)絡(luò)或任何深度網(wǎng)絡(luò)中,自動求導(dǎo)圖需要記住這些操作的中間張量。存儲需求取決于它們的偏導(dǎo)數(shù)操作的性質(zhì)。這些中間張量在反向傳播過程中從內(nèi)存中清除。我們看一些例子:x是輸入,w是需要梯度的參數(shù)(w.requires_grad = True)。

  • x @ w不需要額外的存儲。偏導(dǎo)數(shù)x已經(jīng)存儲。但是當(dāng)x是某個輸出,如x = u * w1時,x也需要被存儲。
  • x + w也不需要存儲,因為對w的偏導(dǎo)數(shù)是0。
  • (x * 2) @ w將需要存儲操作數(shù)x * 2,因為它將用于找到梯度。
  • (((x + 2) @ w1) + 3) * w2是一個有趣的案例,模仿了2層。  
  • 對于關(guān)于w1的偏導(dǎo)數(shù),我們需要存儲x + 2  
  • 對于關(guān)于w2的偏導(dǎo)數(shù),我們需要存儲((x + 2) @ w1) + 3

讓我們看看更深網(wǎng)絡(luò)的實現(xiàn):

def test_multi_layer_forward():
     print(f"Base memory: {torch.cuda.memory_allocated(device_id)}")
 
     inference_mode = False
     n_layers = 1
     model = nn.Sequential(*[
         nn.Sequential(
             nn.Linear(200, 100),
             nn.ReLU(),  # No trainable params
             nn.Linear(100, 200),
             nn.Sigmoid(),  # No trainable params
        )
         for _ in range(n_layers)
    ]).to(device_id)
     batch_size = 5
     x = torch.randn((batch_size, 200), device=device_id)
     with torch.inference_mode(inference_mode):
         y = model(x)
 
     final_memory = torch.cuda.memory_allocated(device_id)
     print(f"Memory after forward pass: {final_memory}")
 
     # Computed memory
     next_chunk = lambda n: (n + 511) // 512 * 512
     mem = 0
     unit = model[0][0].weight.dtype.itemsize
     for block in model:
         for layer in block:
             if isinstance(layer, nn.Linear):
                 mem += next_chunk(len(layer.weight.flatten()) * unit)
                 mem += next_chunk(len(layer.bias) * unit)
                 if not inference_mode:
                     # Gotta store the input
                     mem += next_chunk(layer.in_features * batch_size * unit)
     mem += next_chunk(len(y.flatten()) * unit)
     mem += 8519680  # cublas_size
     if inference_mode:
         mem += next_chunk(len(y.flatten()) * unit)
     print(f"Total memory expected: {mem}")
     assert final_memory == mem

在像BatchNorm1d、LayerNorm、RMSNorm這樣的歸一化層中,在與參數(shù)w相乘之前,有一個對輸入x的操作,如(x — x.mean()) / (x.std() + 1e-6) * w。操作數(shù)(x — x.mean()) / (x.std() + 1e-6)是需要存儲的中間輸出。并且可能還有其他狀態(tài),如running_mean、running_std或forward()方法中的中間張量需要考慮。其中一些中間張量我們無法訪問,所以我們無法確定發(fā)生了什么。當(dāng)包含批量大小時,這變得更加復(fù)雜。

def test_layer_norm():
     print(f"Base memory: {torch.cuda.memory_allocated(device_id)}")
     x = torch.rand((10,), device=device_id)
     w = torch.rand((10,), requires_grad=True, device=device_id)
     # Layer Norm
     y = (x - x.mean()) / (x.std() + 1e-6) * w
     final_memory = torch.cuda.memory_allocated(device_id)
     print(f"Memory after forward pass: {final_memory}")
 
     # Memory calculations
     next_chunk = lambda n: (n + 511) // 512 * 512
     mem = next_chunk(len(x.flatten()) * x.dtype.itemsize)
     mem += next_chunk(len(w.flatten()) * w.dtype.itemsize)
     mem += next_chunk(len(y.flatten()) * y.dtype.itemsize)
     mem += next_chunk(len(x.flatten()) * x.dtype.itemsize)  # intermediate
     print(f"Total memory expected: {mem}")
     assert final_memory == mem

反向傳播非常相似,但有一些變化:

  • 模型大小因梯度存儲而翻倍。
  • 所有中間張量在最后都被清除。
  • 分配了一個新的cublas工作空間。
def test_multi_layer_backward():
     print(f"Base memory: {torch.cuda.memory_allocated(device_id)}")
 
     n_layers = 1
     model = nn.Sequential(*[
         nn.Sequential(
             nn.Linear(200, 100),
             nn.ReLU(),  # No trainable params
             nn.Linear(100, 200),
             nn.Sigmoid(),  # No trainable params
        )
         for _ in range(n_layers)
    ]).to(device_id)
     batch_size = 5
     x = torch.randn((batch_size, 200), device=device_id)
     y = model(x)
     print(f"Memory after forward pass: {torch.cuda.memory_allocated(device_id)}")
     y.sum().backward()
     final_memory = torch.cuda.memory_allocated(device_id)
     print(f"Memory after backward pass: {final_memory}")
 
     # Computed memory
     next_chunk = lambda n: (n + 511) // 512 * 512
     mem = 0
     unit = model[0][0].weight.dtype.itemsize
     for block in model:
         for layer in block:
             if isinstance(layer, nn.Linear):
                 mem += next_chunk(len(layer.weight.flatten()) * unit) * 2   # Weights and gradients
                 mem += next_chunk(len(layer.bias) * unit) * 2               # Biases and gradients
                 # mem += next_chunk(layer.in_features * batch_size * unit) # Intermediate tensors are cleared
     mem += next_chunk(len(y.flatten()) * unit)
     mem += 2 * 8519680                                                      # cublas_size doubled
     mem += next_chunk(len(y.flatten()) * unit)
     print(f"Total memory expected: {mem}")
     assert final_memory == mem

優(yōu)化器(單個線性層反向傳播)

我們觀察一些優(yōu)化步驟的內(nèi)存分配。

def test_single_linear_layer_with_optimizer():
     # Disable cublas
     import os; os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":0:0"
 
     memory_timeline_real = []
     add = lambda e: memory_timeline_real.append({"event": e, "memory": torch.cuda.memory_allocated(device_id)})
     add("baseline")
 
     in_size = 256
     out_size = 250
     batch_size = 100
     model = nn.Linear(in_size, out_size, device=device, dtype=torch.float32)
     add("model_allocation")
 
     optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
     add("optimizer_init")
 
     x = torch.randn((batch_size, in_size,), dtype=torch.float32, device=device)
     add("input_allocation")
 
     def step(n):
         optimizer.zero_grad()
         add(f"optim_zero_grad_{n}")
 
         y = model(x)
         add(f"forward_{n}")
 
         y.sum().backward()
         add(f"backward_{n}")
 
         optimizer.step()
         del y
         add(f"optim_step_{n}")
 
     for i in range(4):
         step(i + 1)
 
     # Bar chart with even name on x-axis and total_memory on y-axis
     fig = plt.figure(figsize=(15, 7))
     fig.set_tight_layout(True)
     plt.ylim((0, 1_300_000))
     plt.bar([event["event"] for event in memory_timeline_real], [event["memory"] for event in memory_timeline_real])
     plt.xlabel("Event")
     plt.ylabel("Total memory allocated (bytes)")
     plt.title(f"Memory allocation during training ({type(optimizer)})")
     plt.xticks(rotation=45)
     plt.show()

圖3:使用SGD優(yōu)化器在訓(xùn)練的各個階段的內(nèi)存分配圖3:使用SGD優(yōu)化器在訓(xùn)練的各個階段的內(nèi)存分配

圖4:使用Adam優(yōu)化器在訓(xùn)練的各個階段的內(nèi)存分配圖4:使用Adam優(yōu)化器在訓(xùn)練的各個階段的內(nèi)存分配

直到backward_1,我們看到內(nèi)存分配如預(yù)期。當(dāng)optimizer.step()結(jié)束時,在這個特定的代碼中刪除了y,所以該內(nèi)存被釋放。在底層優(yōu)化器會獲取額外的內(nèi)存(等于可訓(xùn)練參數(shù)的大小)來更新它們,并在更新后釋放該內(nèi)存。這在圖中沒有顯示。更詳細(xì)的時間圖可以在下圖5中看到。

對于Adam對每個可訓(xùn)練參數(shù)都有一階矩和二階矩。所以它總是在內(nèi)存中保留2倍的模型大小。這是這段代碼中訓(xùn)練最耗費內(nèi)存的部分。

圖5:按毫秒計的內(nèi)存分配時間圖。圖5:按毫秒計的內(nèi)存分配時間圖。

現(xiàn)在讓我們嘗試手動計算這些內(nèi)存需求:

# Memory calculations (continuing from previous code block)
     units = model.weight.dtype.itemsize
     memory_timeline = []
     all_keys = ["trainable_params", "input", "output", "gradient", "intermediate_tensors", "optimizer_state"]
     def update_memory(event: str, update: dict):
         prev_state = memory_timeline[-1] if memory_timeline else {k: 0 for k in all_keys}
         new_state = {k: prev_state.get(k, 0) + update.get(k, 0) for k in all_keys}
         new_state["event"] = event
         memory_timeline.append(new_state)
     next_chunk = lambda n: (n + 511) // 512 * 512
 
     update_memory("baseline", {})
 
     # Model memory
     model_mem = next_chunk(len(model.weight.flatten()) * units)
     model_mem += next_chunk(len(model.bias) * units)
     update_memory("model_allocation", {"trainable_params": model_mem})
     update_memory("optimizer_init", {})
 
     # Input memory
     x_mem = next_chunk(len(x.flatten()) * units)
     update_memory("input_allocation", {"input": x_mem})
     update_memory("optim_zero_grad_1", {})
 
     # Forward
     y_mem = next_chunk(batch_size * out_size * units)
     # Add any intermediate tensors here.
     update_memory("forward_1", {"output": y_mem})  # , "intermediate_tensors": ...})
 
     # Backward
     grad_mem = next_chunk(len(model.weight.grad.flatten()) * units)
     grad_mem += next_chunk(len(model.bias.grad.flatten()) * units)
     # Clear any intermediate tensors here.
     update_memory("backward_1", {"gradient": grad_mem})  # "intermediate_tensors": ...})
 
     # Optimizer memory
     if isinstance(optimizer, torch.optim.SGD):
         # SGD has parameters in memory. They are cleared after each step.
         optimizer_mem = 0
     elif isinstance(optimizer, torch.optim.Adam):
         # Adam has parameters and 2 momentum buffers. Parameters are cleared after each step.
         optimizer_mem = 2 * model_mem
     else:
         raise
     update_memory("optim_step_1", {"optimizer_state": optimizer_mem, "output": -y_mem})
 
     for step in range(2, 5):
         update_memory(f"optim_zero_grad_{step}", {"gradient": -grad_mem})
         update_memory(f"forward_{step}", {"output": y_mem})
         update_memory(f"backward_{step}", {"gradient": grad_mem})
         update_memory(f"optim_step_{step}", {"output": -y_mem})
 
     # Make totals
     for event in memory_timeline:
         event["total"] = sum([v for v in event.values() if isinstance(v, int)])
 
     # Plot memory timeline
     import pandas as pd
     df = pd.DataFrame(memory_timeline, columns=all_keys + ["event"])
     df.set_index("event", inplace=True, drop=True)
     df.plot(kind='bar', stacked=True, figsize=(15, 7), ylim=(0, 1_300_000), xlabel="Event", ylabel="Total memory allocated (bytes)", title=f"Memory allocation expected ({type(optimizer)})")
     plt.tight_layout()
     plt.xticks(rotation=45)
     plt.show()
 
     # Compare the two timelines
     for i, (real, expected) in enumerate(zip(memory_timeline_real, memory_timeline)):
         assert real["memory"] == expected["total"], f"Memory mismatch at {real['event']}: {real['memory']} != {expected['total']}"

圖6:使用SGD優(yōu)化器在訓(xùn)練的不同階段的內(nèi)存使用分段圖6:使用SGD優(yōu)化器在訓(xùn)練的不同階段的內(nèi)存使用分段

圖7:使用Adam優(yōu)化器在訓(xùn)練的不同階段的內(nèi)存使用分段圖7:使用Adam優(yōu)化器在訓(xùn)練的不同階段的內(nèi)存使用分段

在手動計算內(nèi)存分配后,我們的計算與觀察結(jié)果相匹配。這次實際上可以看到內(nèi)存分配到各種張量的分段。例如,Adam的狀態(tài)占用了兩倍的模型大小。梯度(紅色)的不同變化。如果向繼續(xù)測試,還可以嘗試向這個模型添加更多層,添加中間張量并在適當(dāng)?shù)臅r候刪除它們。這應(yīng)該在這些條形圖中創(chuàng)建另一個代表中間張量的分段。

總結(jié)

結(jié)合上面的每個概念我們可以回答主要問題:

  • 可訓(xùn)練參數(shù):固定的模型大小
  • 內(nèi)存塊:它只以512字節(jié)的塊出現(xiàn)
  • Cublas內(nèi)存:前向傳播一個塊,反向傳播一個塊
  • 梯度:與模型大小相同
  • 中間張量:最麻煩的部分,取決于代碼如何編寫
  • 優(yōu)化器:至少分配一倍的模型大小

最后一個問題就是,我們只處理了前饋層,那么CNN、Transformers、RNN等呢?首先CNN是類似前饋層的操作,所以我們可以根據(jù)他的計算規(guī)則進行計算,而Transformers、RNN都基礎(chǔ)操作的組合,我們計算了一個前饋層可以根據(jù)他們的架構(gòu)進行組合計算。我們已經(jīng)掌握了計算前饋層內(nèi)存需求的方法,所以我們可以自己解決這些問題!

責(zé)任編輯:華軒 來源: DeepHub IMBA
相關(guān)推薦

2020-11-04 15:35:13

Golang內(nèi)存程序員

2020-05-27 21:13:27

JavaJVM內(nèi)存

2022-12-28 09:07:41

2015-12-28 11:41:57

JVM內(nèi)存區(qū)域內(nèi)存溢出

2023-11-05 12:05:35

JVM內(nèi)存

2024-08-07 08:24:57

2021-11-26 00:00:48

JVM內(nèi)存區(qū)域

2022-07-06 08:05:52

Java對象JVM

2023-12-31 12:56:02

C++內(nèi)存編程

2021-05-13 21:27:24

ThreadLocal多線程多線程并發(fā)安全

2023-09-19 22:47:39

Java內(nèi)存

2013-06-20 10:25:56

2024-01-11 11:51:51

Rustmap數(shù)據(jù)結(jié)構(gòu)

2022-08-21 16:52:27

Linux虛擬內(nèi)存

2010-03-12 08:55:06

Java內(nèi)省反射

2024-06-28 10:25:18

2016-12-08 15:36:59

HashMap數(shù)據(jù)結(jié)構(gòu)hash函數(shù)

2010-06-01 15:25:27

JavaCLASSPATH

2020-07-21 08:26:08

SpringSecurity過濾器

2010-09-25 14:38:18

Java內(nèi)存分配
點贊
收藏

51CTO技術(shù)棧公眾號

黑人与娇小精品av专区| 在线视频免费在线观看一区二区| 91久久精品一区二区二区| 色狠狠久久av五月综合|| av免费观看在线| 在线视频观看日韩| 国产性猛交xxxx免费看久久| 在线观看免费视频污| 男人天堂亚洲天堂| 国产亚洲一区二区三区四区| 成人免费在线视频网站| 青青操免费在线视频| 成人中文在线| 亚洲国产成人av在线| www.xxx亚洲| 免费看电影在线| 国产色一区二区| 国产福利一区二区三区在线观看| 91麻豆精品在线| 亚洲特级毛片| 久久久99久久精品女同性| chinese麻豆新拍video| 国产精品美女久久久久| 一本大道久久a久久综合婷婷| 一区二区在线观看网站| 日本一卡二卡四卡精品 | av动漫免费看| av免费在线观看网址| 国产日韩在线不卡| 加勒比在线一区二区三区观看| 国产又色又爽又黄又免费| 欧美在线综合| 国内精品久久影院| 久久免费看少妇高潮v片特黄| 九色精品91| 日韩av最新在线| 久久无码专区国产精品s| 成人精品国产亚洲| 色婷婷av久久久久久久| 久久久亚洲精品无码| 免费男女羞羞的视频网站在线观看| 国产精品进线69影院| 日本黄网免费一区二区精品| 性xxxx视频| 成人黄色小视频在线观看| 91网站免费看| 国产视频在线观看视频| 久久69国产一区二区蜜臀| 国产99久久精品一区二区| 久久久久成人精品| 无码无遮挡又大又爽又黄的视频| 国产乱码在线| 一区二区日韩av| 日本老太婆做爰视频| 美女av在线播放| 中文字幕亚洲在| 亚洲资源视频| 日韩免费网站| 亚洲少妇30p| 8x8x华人在线| av2020不卡| 黑人极品videos精品欧美裸| 六月丁香激情网| 性欧美18~19sex高清播放| 福利视频一区二区| 日本www.色| 亚洲一区二区小说| 日韩写真欧美这视频| 日本人妻一区二区三区| jazzjazz国产精品麻豆| 日韩激情av在线播放| 亚洲综合网在线观看| 国产伦精品一区二区三区视频| 亚洲午夜激情免费视频| 亚洲一级理论片| 我不卡手机影院| 欧美多人爱爱视频网站| 日韩欧美亚洲视频| 日本免费新一区视频| 国产一区私人高清影院| www.av导航| 99久精品国产| 婷婷精品国产一区二区三区日韩| 在线观看黄av| 亚洲成人综合视频| 国内外免费激情视频| 国产亚洲欧美日韩精品一区二区三区 | 欧美性受ⅹ╳╳╳黑人a性爽| 亚洲高清一区二区三区| 99蜜桃臀久久久欧美精品网站| 成人亚洲视频| 精品欧美一区二区三区精品久久 | 亚洲天堂2021av| 亚洲欧美校园春色| 日本大胆欧美人术艺术动态| 欧美激情中文网| 丁香六月婷婷综合| 精品一区二区三区蜜桃| 国产精品久久一区二区三区| 国外av在线| 亚洲精品免费播放| 一本久道综合色婷婷五月| 成人豆花视频| 亚洲免费视频观看| 麻豆疯狂做受xxxx高潮视频| 日韩高清不卡在线| 成人免费视频视频在| 2019中文字幕在线视频| 亚洲成人高清在线| 91欧美一区二区三区| 精品影片在线观看的网站| 不卡伊人av在线播放| 69视频免费看| 成人午夜精品一区二区三区| 亚洲电影网站| 国产美女高潮在线| 日韩西西人体444www| 国产99在线 | 亚洲| 亚洲毛片一区| 成人免费在线看片| 91大神xh98hx在线播放| 色偷偷久久一区二区三区| 四虎国产精品永久免费观看视频| 国产精品三级| 2020久久国产精品| 成人久久久精品国产乱码一区二区| 欧美激情综合在线| 国产日韩成人内射视频| 91欧美极品| 欧美成人全部免费| 一级黄色免费看| 国产日韩欧美精品综合| 各处沟厕大尺度偷拍女厕嘘嘘| 51精品国产| 美女少妇精品视频| 97超视频在线观看| 中文字幕日韩欧美一区二区三区| 激情视频综合网| 亚洲国产最新| 欧美在线www| 深爱激情五月婷婷| 亚洲午夜精品一区二区三区他趣| 人人爽人人爽av| 国产韩日影视精品| 精品一区二区三区在线观看| 2018日韩中文字幕| 日韩一级中文字幕| 亚洲成av人片一区二区梦乃| 中文字幕亚洲日本| 欧美激情第8页| 91在线精品观看| 欧美野外wwwxxx| 精品日韩一区二区三区免费视频| 91杏吧porn蝌蚪| 精品中文字幕一区二区小辣椒| 亚洲一一在线| www.久久草.com| 久久精品精品电影网| 亚洲天堂久久久久| 亚洲免费观看在线观看| 亚洲精品一区二区18漫画| 欧美激情日韩| 国产伦精品一区二区三区四区视频| 欧美日韩经典丝袜| 亚洲第一av网站| 毛片毛片女人毛片毛片| 国产欧美一区二区三区在线老狼| 天天影视综合色| 三上亚洲一区二区| 亚洲qvod图片区电影| 色呦呦呦在线观看| 日韩高清人体午夜| 国产成人精品亚洲| 中文字幕综合网| 国产吃瓜黑料一区二区| 国产日韩一区二区三区在线| 欧美视频小说| 91成人小视频| 久久久亚洲网站| 看电影就来5566av视频在线播放| 欧美手机在线视频| 午夜免费激情视频| 26uuu精品一区二区三区四区在线 26uuu精品一区二区在线观看 | 国产精品成人在线观看| 在线成人精品视频| 亚洲欧美bt| 2025韩国大尺度电影| 超碰97久久| 国产精品美女视频网站| 五月花成人网| 亚洲偷熟乱区亚洲香蕉av| 国产毛片久久久久| 精品二区三区线观看| 免费成人深夜蜜桃视频| 国产在线观看a视频| 亚洲精品菠萝久久久久久久| 国产激情视频网站| 免费av网站大全久久| 成人在线视频一区二区三区| 三级精品视频| 91社区国产高清| 女人让男人操自己视频在线观看| 尤物九九久久国产精品的分类| h狠狠躁死你h高h| 色噜噜狠狠色综合中国| 国产黄色片在线免费观看| 久久色成人在线| 国产精品成人免费一区久久羞羞| 秋霞av亚洲一区二区三| 青青在线免费观看| 66视频精品| 色综合电影网| 欧美精品中文字幕亚洲专区| 91亚洲精华国产精华| www.日韩| 午夜精品美女自拍福到在线| 国产婷婷视频在线| 一本大道亚洲视频| 视频一区二区三区在线看免费看| 欧美一区二区三区四区在线观看 | 成年人网站免费在线观看| 国产主播一区二区| 狠狠热免费视频| 中文国产一区| 久久这里只有精品18| 99久久精品费精品国产| 日本午夜精品一区二区| 四虎5151久久欧美毛片| 99在线观看视频| 伊人国产精品| 国产精品视频区1| 亚洲成人短视频| 国产va免费精品高清在线观看| av3级在线| 午夜精品久久久99热福利| 日本性爱视频在线观看| 久久香蕉国产线看观看网| jizz在线免费观看| 在线观看欧美日韩| 成人精品一区二区三区免费 | 欧美网站大全在线观看| 人妻 日韩精品 中文字幕| 婷婷国产v国产偷v亚洲高清| 免费中文字幕在线观看| 一区二区三区自拍| 日韩成人短视频| 亚洲三级免费电影| avtt天堂在线| 亚洲愉拍自拍另类高清精品| 欧美成人合集magnet| 91欧美日韩麻豆精品| 在线亚洲免费视频| 中文字幕手机在线视频| 91极品美女在线| 中国精品一区二区| 欧美精品久久99| 国产日韩欧美视频在线观看| 欧美一区二区视频在线观看 | 日韩欧美中文字幕制服| 精品人妻一区二区三区麻豆91 | 亚洲人成小说网站色在线| 精品视频第一页| 亚洲三级免费观看| 日本老熟俱乐部h0930| 一区二区免费看| 一级片免费网址| 色婷婷综合久久久久中文一区二区 | 午夜免费福利视频在线观看| 另类小说视频一区二区| 午夜一级免费视频| 成人永久免费视频| 91精品国产自产| 中文一区在线播放| 欧美性x x x| 精品国产乱码久久久久久虫虫漫画| 久久国产黄色片| 精品视频在线看| 成人毛片在线免费观看| 国产视频亚洲视频| 日韩av中文| 久久久综合av| 日韩漫画puputoon| 亚洲va久久久噜噜噜久久天堂| 国产精品久av福利在线观看| 欧美黑人xxxxx| 婷婷伊人综合| 国产精品后入内射日本在线观看| 日韩av网站在线观看| 三上悠亚 电影| 国产三级欧美三级日产三级99 | 国产精品成人国产乱一区 | 日本性高潮视频| 亚洲男人天堂av| 欧美黄色一级大片| 日韩欧美在线1卡| 国产网站在线播放| 欧美激情欧美激情| 玖玖精品在线| 国内精品久久国产| 久久精品亚洲人成影院| 久久久久久中文| 欧美 日韩 国产 成人 在线观看| 91色在线porny| 99久久婷婷国产综合| 狠狠躁夜夜躁人人爽天天天天97 | 日韩欧美黄色影院| 久久久久国产精品嫩草影院| 欧美成人精品在线视频| 高清成人在线| 国产精品亚洲不卡a| 国产大片一区| 高清一区二区视频| 99在线精品观看| 婷婷在线精品视频| 欧美吻胸吃奶大尺度电影| 亚洲 欧美 精品| 欧美夫妻性生活视频| 未满十八勿进黄网站一区不卡| 欧美高清性xxxxhd| 亚洲高清电影| 欧美熟妇另类久久久久久多毛| 亚洲国产精品精华液2区45| 国产成人无码一区二区三区在线| 欧美人狂配大交3d怪物一区| 国产一级免费在线观看| 91a在线视频| 高清欧美性猛交xxxx黑人猛| 天天做天天爱天天高潮| 日韩一区精品字幕| 加勒比一区二区| 日韩欧美在线免费观看| 深夜福利免费在线观看| 高清一区二区三区日本久| 免费看一区二区三区| 亚洲综合激情五月| 久久精品国产免费| 精品无码在线观看| 在线免费观看成人短视频| 日本成人一区二区三区| 91精品国产91| 偷拍视屏一区| 免费欧美一级视频| av在线下载| 国产精品欧美一区二区三区奶水| 久久99国产精一区二区三区| 欧美日韩国产精品激情在线播放| 不卡一区二区中文字幕| 久久久久久久久久91| 日韩精品一区二区三区老鸭窝| www国产在线观看| 成人高清在线观看| 亚洲无吗在线| 最近中文字幕无免费| 丰满岳妇乱一区二区三区| 欧美色18zzzzxxxxx| 国产精品91视频| 欧美r级电影| 国产性生活一级片| 一区二区三区在线视频观看58| 91亚洲精华国产精华| 一本久久青青| 欧美日韩在线成人| 中文字幕一区二区在线播放| 99久久国产热无码精品免费| 欧美另类精品xxxx孕妇| 国产精品45p| 亚洲乱码中文字幕久久孕妇黑人| 久久精品夜色噜噜亚洲a∨| 高潮无码精品色欲av午夜福利| 这里只有精品视频| 欧美高清hd| 国产h视频在线播放| 国产午夜精品一区二区三区视频| 在线观看毛片网站| 欧美激情成人在线视频| 亚洲视频分类| 亚洲免费999| 一区二区三区.www| 免费毛片在线| 成人网在线免费观看| 在线欧美福利| 日韩影视一区二区三区| 欧美一级久久久| 欧美xxxhd| 男女啪啪的视频| 99国产欧美久久久精品| 在线观看亚洲国产| 欧美精品久久久久久久久久| 综合国产视频| 日本亚洲一区二区三区| 欧美日韩一区二区精品| 久热国产在线| 免费av在线一区二区| 韩日精品视频一区| 欧美国产成人精品一区二区三区| 色噜噜狠狠狠综合曰曰曰88av| 国产日韩欧美中文在线| 亚洲色欲综合一区二区三区| 自拍偷拍国产精品| 黄色片在线免费观看|