向Python multiprocessing.Process传递Keras模型报错问题
问题根因
- Windows环境下Python
multiprocessing默认采用spawn模式创建子进程,所有传给子进程的参数都会先执行pickle序列化,再在子进程侧反序列化还原。 - Keras模型内置的pickle序列化逻辑不会直接把模型权重写入序列化字节流,而是临时将模型存储到当前进程独占的内存文件系统(即报错中
ram://开头的路径),反序列化阶段再从该路径读取模型文件完成加载。 - 内存文件系统是进程级隔离的,子进程没有权限访问父进程
ram://路径下的临时文件,因此反序列化时会抛出找不到变量文件的错误。通过clone_model得到的模型副本序列化逻辑和原模型完全一致,同样会触发该问题。移除模型传参后不会触发Keras模型的序列化/反序列化流程,因此多进程逻辑可正常运行。
可行解决方案
按稳定性从高到低排序:
- 方案1:子进程内完成模型初始化(全平台兼容,最稳定)
不要跨进程传递Keras模型对象,只传递可正常序列化的模型配置、numpy格式的权重数组,子进程启动后自行构建模型结构、加载权重。该方案完全避开了Keras模型跨进程序列化的坑,也不会触发TensorFlow会话、CUDA上下文跨进程错乱的问题,是实际生产环境优先选择的方案。
参考修改代码:from multiprocessing import Process from tensorflow import keras from tensorflow.keras import layers def dumb_fun(x, model_weights): # 子进程内部独立构建模型结构 input_layer = layers.Input(shape=(120,160,2)) x = layers.Conv2D(32, (3, 3), activation='relu')(input_layer) x = layers.MaxPool2D((2, 2))(x) x = layers.Conv2D(64, (3, 3), activation='relu')(x) x = layers.MaxPool2D((2, 2))(x) x = layers.Conv2D(128, (3, 3), activation='relu')(x) x = layers.MaxPool2D((2, 2))(x) x = layers.Flatten()(x) x = layers.Dense(64, activation='relu')(x) x = layers.Dense(32, activation='relu')(x) actions = layers.Dense(5, activation='softmax')(x) model = keras.Model(input_layer, actions) # 加载传入的numpy格式权重 model.set_weights(model_weights) print(x) print(model) if __name__ == '__main__': input_layer = layers.Input(shape=(120,160,2)) x = layers.Conv2D(32, (3, 3), activation='relu')(input_layer) x = layers.MaxPool2D((2, 2))(x) x = layers.Conv2D(64, (3, 3), activation='relu')(x) x = layers.MaxPool2D((2, 2))(x) x = layers.Conv2D(128, (3, 3), activation='relu')(x) x = layers.MaxPool2D((2, 2))(x) x = layers.Flatten()(x) x = layers.Dense(64, activation='relu')(x) x = layers.Dense(32, activation='relu')(x) actions = layers.Dense(5, activation='softmax')(x) actor = keras.Model(input_layer, actions, name="actor") numbers = [1,1,2,3,5,8,13] num_procs = len(numbers) processes = [] for i in range(num_procs): # 仅传递numpy格式的权重数组,不传递模型对象 processes.append(Process(target=dumb_fun, args=(numbers[i], actor.get_weights()))) for proc in processes: proc.start() for proc in processes: proc.join() - 方案2:切换多进程启动模式为fork(仅支持Linux/macOS,Windows不可用)
fork模式下子进程会直接复制父进程的完整内存空间,不需要对传入参数做pickle序列化,自然不会触发内存文件系统访问的问题。但TensorFlow对fork模式的兼容性一般,GPU环境下容易出现CUDA上下文错乱的问题,仅建议CPU场景下临时使用。
启用方式:在主程序入口、创建进程前添加如下代码import multiprocessing as mp mp.set_start_method('fork', force=True) - 方案3:通过磁盘文件中转模型(全平台兼容,性能较差)
如果不想在子进程重复写模型构建逻辑,可以先将父进程的模型保存到实体磁盘路径,子进程只接收磁盘路径字符串,启动后自行调用keras.models.load_model加载模型。该方案本质和方案1逻辑一致,但会产生额外的磁盘IO开销,进程数量较多时启动速度慢。
内容的提问来源于stack exchange,提问作者Giuseppe Boezio
相关产品推荐
相关产品推荐

