python使用ThreadPoolExecutor、ProcessPoolExecutor进行任务并行

在Python中,可以使用concurrent.futures.ThreadPoolExecutor来实现多线程调用函数。

from concurrent.futures import ThreadPoolExecutor

def my_function(arg1, arg2):
    print(f"Processing {arg1} and {arg2}")

with ThreadPoolExecutor(max_workers=3) as executor:
    for i in range(5):
        executor.submit(my_function, i, i*2)

需要获取返回值,可通过future.result()获取


from concurrent.futures import ThreadPoolExecutor
import time

def calculate_square(number):
    time.sleep(1)  # 模拟耗时操作
    return number * number

with ThreadPoolExecutor(max_workers=3) as executor:
    # 提交任务并保存future对象
    future1 = executor.submit(calculate_square, 2)
    future2 = executor.submit(calculate_square, 3)
    future3 = executor.submit(calculate_square, 4)
    
    # 获取结果(会阻塞直到任务完成)
    print(f"2的平方: {future1.result()}")
    print(f"3的平方: {future2.result()}")
    print(f"4的平方: {future3.result()}")

使用多少线程数合理?对于cpu计算密集型,可以使用cpu核心数作为线程数。对于I/O密集型,可以使用核心数x2。使用如下代码可以获得核心数

import os
cpu_count = os.cpu_count()  # 返回逻辑核心数(含超线程)‌
print(f"CPU核心数: {cpu_count}")

但是对于cpu密集型的任务,更推荐使用多进程的ProcessPoolExecutor,接口和ThreadPoolExecutor一样。但是要说明的是,开启进程、进程间通信和销毁进程要比同样的线程步骤耗时更大,如果要执行的任务不是明显的cpu密集型,那么使用ProcessPoolExecutor并不一定要快于ThreadPoolExecutor

torch.utils.checkpoint模块

当训练模型,出现了显存不足的问题时,可以利用torch.utils.checkpoint模块下的checkpoint函数来减少训练时的显存占用,但是代价是训练过程会变慢,本质上是用速度换取内存。官方文档地址:torch.utils.checkpoint — PyTorch 2.7 documentation

不使用checkpoint时,前向传播的过程中会保存中间的activation,用于后续反向传播时计算梯度。但使用checkpoint,会只保存输入tuple和模型参数,不保存中间的activation,在反向传播时,使用保存的输入重新计算中间的activation,相当于重新跑了一遍正常的前向传播。反向传播完成后,把activation释放。

checkpoint的正确使用方式应该是将网络分为若干的模块,对每个模块应用checkpoint,这样反向传播时,依从后往前的顺序对每个模块计算梯度,但只有在计算到该模块时,该模块才有activation的内存占用,计算完成或没计算到时就没有。checkpoint不应该对网络整体使用,否则就和正常的训练过程一样占用同样的显存。

注意dropout和batch normalization层不能用checkpoint

ipdb调试python代码

在不使用调试模型运行python代码时,有时程序会发生我们意想不到的错误,想要复现错误来调试需要很多时间,或者错误是偶发性的,这时候我们可以借助ipdb这个工具来在运行代码时进行调试。

安装

pip install ipdb

在可能出错的地方,插入语句。一般可以配合try except语句一起使用,在捕获到错误的地方插入语句,程序在运行到这个地方时会进入到断点,然后在命令行里可以交互式调试代码

import ipdb; ipdb.set_trace()

常用调试命令

  • c:继续执行代码,直到遇到下一个断点或程序结束。
  • n:单步执行下一行代码(不会进入函数内部)。
  • s:单步进入下一行代码(如果有函数调用,则进入函数内部)。
  • q:退出调试器并终止程序的执行。
  • l:查看当前位置附近的代码。
  • p:打印变量的值,例如p variable_name
  • h:查看帮助信息,例如h command_name
  • w:查看当前的调用栈。
  • u:向上移动一层调用栈。
  • d:向下移动一层调用栈。