背景
最近使用大模型进行推理时,首先将大模型 api 部署,然后借助于 request库,发送请求。
在之前写同步的请求,必须等到上一次request响应的结果回来后,才能发送下一次请求,这样导致速度非常慢。
由于大模型部署支持并发,故打算采用多线程方式,让大模型快速处理。
简介
- 多线程
多线程示例代码
python
import concurrent
from concurrent.futures import ThreadPoolExecutor
from tqdm import tqdm
import time
start = time.time()
# 假设我们有一个URL列表,这里用占位符代替
urls = [i for i in range(25)]
# 模拟发送网络请求
def send_request(url):
print(f"发送请求 {url}")
time.sleep(1) # 模拟网络延迟
print(f"收到请求 {url} 的响应")
return url
# 使用ThreadPoolExecutor创建线程池,并限制线程数为 max_workers
with ThreadPoolExecutor(max_workers=5) as executor:
# 使用executor的map方法将urls列表中的每个URL映射到send_request函数
# 这会自动将任务分配给线程池中的线程,而且 results_iterator 收集到的返回值的结果
results_iterator = executor.map(send_request, urls)
end = time.time()
print(f"总共耗时 {end - start} 秒")
python
print(list(results_iterator))
results_iterator 收集到的返回值是有序的
多线程的运行结果如下所示:
bash
发送请求 0
发送请求 1
发送请求 2
发送请求 3
发送请求 4
收到请求 0 的响应收到请求 3 的响应
发送请求 5
收到请求 4 的响应
发送请求 6
发送请求 7
收到请求 1 的响应
发送请求 8
收到请求 2 的响应
发送请求 9
收到请求 5 的响应
发送请求 10
收到请求 6 的响应
发送请求 11
收到请求 7 的响应
发送请求 12
收到请求 9 的响应
发送请求 13
收到请求 8 的响应
发送请求 14
...
收到请求 24 的响应
收到请求 22 的响应
总共耗时 5.021236896514893 秒
关于 核心数选取多少:
在Python中,多线程的性能并不总是随着线程数的增加而线性提升,尤其是在执行CPU密集型任务时。这是因为Python的全局解释器锁(GIL)限制了一次只能有一个线程执行Python字节码。所以,即使你的机器有多个CPU核心,Python的多线程在并行执行CPU密集型任务时可能并不会充分利用所有的核心。
然而,如果你的线程主要执行I/O操作(如网络请求、文件读写等),那么多线程可以有效地利用等待时间,从而提高程序的总体效率。在这种情况下,线程数可以超过CPU核心数,因为线程会在等待I/O操作时自动切换。
对于CPU密集型任务,如果你想要充分利用多核CPU的性能,更好的选择是使用多进程(multiprocessing)而不是多线程。Python的multiprocessing库可以创建多个进程,每个进程都有自己的Python解释器,从而可以并行执行CPU密集型任务。
至于具体应该设置多少线程或进程,这取决于你的具体需求和机器的配置。一般来说,对于I/O密集型任务,线程数可以设置为略多于CPU核心数,以便在等待I/O时充分利用CPU。对于CPU密集型任务,进程数可以设置为等于或略少于CPU核心数,以避免过多的上下文切换开销。但请注意,这只是一个一般性的建议,具体的最佳值可能需要通过实验来确定。
对比 不使用多线程
python
start = time.time()
# 假设我们有一个URL列表,这里用占位符代替
urls = [i for i in range(25)]
# 定义一个函数来发送网络请求
def send_request(url):
print(f"发送请求 {url}")
time.sleep(1) # 模拟网络延迟
print(f"收到请求 {url} 的响应")
return url
ans = []
for url in urls:
ans.append(send_request(url))
end = time.time()
print(f"总共耗时 {end - start} 秒")
bash
发送请求 0
收到请求 0 的响应
发送请求 1
收到请求 1 的响应
发送请求 2
收到请求 2 的响应
发送请求 3
收到请求 3 的响应
发送请求 4
收到请求 4 的响应
发送请求 5
收到请求 5 的响应
发送请求 6
收到请求 6 的响应
发送请求 7
收到请求 7 的响应
发送请求 8
收到请求 8 的响应
发送请求 9
收到请求 9 的响应
发送请求 10
收到请求 10 的响应
发送请求 11
收到请求 11 的响应
发送请求 12
...
收到请求 23 的响应
发送请求 24
收到请求 24 的响应
总共耗时 25.100407123565674 秒
如果不使用多线程的,必须收到上一次请求的响应,才能发送下一个请求;总共耗时25秒,而多线程只需要5秒;