UE5 Shader加速分布式编译插件开发

UE Turbo Build Shaders Controller 插件

主要目的是扩展UE的Shader编译器, 扩展到多机可以共享算力.

更新

2021-10-30 目前已经实现可用版本,正在测试效率,如果效率可以打算上架虚幻商城插件方便使用。

原理

  1. 扩展一个Controller 支持Shader 编译
  2. Shader编译线程消费Shader编译任务,启动一个Client进程来批量处理任务
  3. Client进程与编译器线程通过UDP Socket/NamedPipe通信获取编译结果发布到MQ
  4. 编译器接收到编译结果后,调用ShaderCompilerManager报告结果
  5. 分布式多机的Agent通过MQ拉取任务编译

Turbo Build Controller 插件

插件主要扩展消费编译任务(已经是批处理),参考XGE Controller插件

EnqueueTask 处理

接收到一个Batch 任务, 将通过Socket/NamedPipe方式发送到Client进程消费

FetchResult 线程

此线程等待读取Client进程的任务处理结果,将受到的任务报告给Manager (参考XGE Controller)

Client 进程

此进程主要使用Python编写用于快速验证可用原型, 主要用来分发任务TASK 。

Socket/NamedPipe 接受任务线程

从UE插件接受任务, 将任务转换为远程计算工作JOB, 本地独立线程处理或者提交给远程MQ(REDIS)

反馈结果线程

将从MQ读取结果并处理,然后发送给UE Socket/NamedPipe(任务ID), 通知UE检查结果

任务分发

可以本地执行或者远程执行,

  • 接收到的任务按顺序分发给远程, 设置超时时间。
  • 如果超时,则本地执行(由于DistributedShareCompilerThreadRunnable已经实现,可不做处理);
  • 本地空闲状态也可消费,自主决定是否启动Agent进程;

任务包括: 任务工具集, 任务参数(输入,命令行模板)

任务结果 = 工具集(任务参数)

任务环境

任务环境表示任务需要执行准备的环境, 主要是依赖文件。
Client 在初始化时会声明

任务参数

主要是任务输入相关, 包含输入文件, 期望输出文件路径声明,执行命令等

任务分发中心(REDIS)

各个Agent 通过REDIS 获取任务, 执行结果并返回。

Client 上传的任务, Agent 消费的任务回写结果。

任务存储模型

  1. job_pending::list 任务使用List存储(ID,[INPUTS,OUTPUTS],CMD,TOOL_ENV,PATH_MAP)
  2. job_finish::list 任务结果
  3. job_state:: hash (key: code, stdout, stderr, outf:xx1, outf:xx2, inf:xx1, inf:xx2, cmd:xxx, tool_id:xxx, begin_time:xxxx, expired_time:xxxx)
  4. toolset_ctx:hash , 主要是toolid 对应的smb 路径(确保所有client与agent可访问smb)

分发操作方法

  1. Client任务提交

    • Client启动时会初始化任务环境, 会在SMB上创建对应的层次目录, 将工具传输上去 .
      并将工具id 在toolset_ctx 表中登记 : {id:path}
    • Client任务重启也会更新工具<包含引擎和Shader版本号>
    • Client收到插件任务请求时, 构造一个job_state::task_id , 将任务参数填入其中
    • 对于输入文件直接读取存入到REDIS中(假设都是小文件)
    • 然后在task_pending:中插入 task_id
  2. Agent 任务消费

    • Agent 本地空闲时会启动N个进程从REDIS中获取任务;
    • 取出任务时, 首先检查任务环境是否OK,如果没有则调用XCOPY将SMB的环境COPY到本地工作目录(临时目录)
    • 检查任务参数的输入文件,保存到本地文件
    • 设置环境信息,执行命令行程序
    • 程序执行过程中可每10s更新一次结果(stdout,stderr), 将程序返回结果设置到 job_state中
    • 最终程序完成时, 设置job_finish表
  3. Client 任务获取结果
    Client进程中会启动一个进程来缉拿查job_finish 列表, 从job_finish 中获取结果
    每取出一个结果, 检查job_state 的数据,将job_state 的结果写入到文件中.
    然后通过UDP Socket/NamedPipe发送给插件的编译器线程

  4. 插件编译器线程获取结果
    插件编译器线程从UDP Socket/NamedPipe中获取结果, 判断return code 和 输出, 根据情况显示或者报错等。

实现原型

  1. 先在插件中实现本地编译的方法.ok
  2. 实现Client进程本地编译.ok
  3. 实现插件与Client进程通信异步编译.ok
  4. 实现REDIS数据模型操作.ok
  5. 实现同机器REDIS通信Agent消费任务.ok
  6. 实现跨机器验证效率.ok
  7. 增加空闲监测和WEB监控显示.ok
  8. 效率优化.doing