概述
整个编译打包过程的总体思路,是参照在linux
下的编译流程,配置环境,执行编译命令,根据编译器/链接器反馈的错误,修改相应的源码或者相关库文件的存放路径,编译出windows
平台下静态库和二进制执行文件。
TIP
:在碰到很多编译错误的时候,适当避开某些不好解决的第三方依赖库(libevent,gflag,glog
),把流程走通,再回头解决外部依赖库的问题。
windows编译tensorflow serving流程图
Windows编译环境搭建
说明:本教程中部分路径的展示沿用了linux
教程中正斜杠的用法,未予修改。凡是非linux
沿用命令及输出信息中的路径,均采用windows
平台下的反斜杠。两种分隔方式除特别说明,在windows
平台下可以混用。
安装Bazel
下载bazel
的windows的安装包,版本要求>=3.7.2。在页面中找到 bazel--windows-x86_64.exe
,如bazel-3.7.2-windows-x86_64.exe
改名为bazel.exe
,并将该文件所在目录放到%PATH%
目录,打开命令行窗口,可以搜索到bazel
即可。
D:\TFServing\serving>bazel --versionbazel 3.7.2
安装Visual Studio 2019
进入visual studio 2019安装包下载界面,选择社区版,按提示安装即可。可参考安装教程。
安装Python及附属包
选择python3.9
,进入下载界面,找到对应版本,参考教程。在安装时将pip
一起安装,后需用pip
安装其他工具包比如numpy
等。
其他工具
这里主要是git,msys2,patch,git
的安装方式可参考教程。
msys2
是在windows
上模拟linux
环境,安装后可以在window
上执行linux
上的程序。安装教程可参考资料。
pacman -Syupacman -Supacman -S patch unzip
配置环境变量
将上面安装的所有工具的二进制包添加到环境变量:
在系统变量中添加变量名BAZEL_SH,BAZEL_VC,BAZEL_VS
,并设置相应的路径。
D:\msys64\usr\bin\bash.exe #BAZEL_SHD:\Program Files\Microsoft Visual Studio\2019\Community\VC #BAZEL_VCD:\Program Files\Microsoft Visual Studio\2019\Community #BAZEL_VS
%PATH%
中需添加:
D:\Bazel #bazel.exe所在目录D:\ProgramData\Python\Python39 #python39.exe所在目录D:\msys64\usr\bin#msys2中安装的软件所在目录
下载源码
下载tensorflow serving
的源码:
git clone https://github.com/tensorflow/serving.git cd serving
通过分析依赖层级结构,获取所有依赖库源码:
bazel fetch --experimental_repo_remote_exec tensorflow_serving/model_servers:tensorflow_model_server
注意:fetch
可以通过分析依赖层级关系,下载对应的工程文件,可通过--repository_cache d:\BazelCache
选项,指定目录存放所有下载的源码文件,也可参考下一节中的bazel
配置文件修改存放位置。如遇到下载不成功,重复执行即可。直到显示
D:\TFServing\serving>bazel fetch --experimental_repo_remote_exec tensorflow_serving/model_servers:tensorflow_model_server Starting local Bazel server and connecting to it... DEBUG: D:/bazelcache/z4avbakx/external/org_tensorflow/third_party/repo.bzl:109:14: Warning: skipping import of repository 'icu' because it already exists. INFO: All external dependencies fetched successfully. Loading: 301 packages loaded
表示全部工程文件下载完毕。
源码修改
tensorflow serving
的源码文件是通过git clone
命令下载到本地,而所有依赖库文件包括tensorflow
, libevent
等都存放在指定的bazel
缓存目录。这里以进入serving
根目录下开始
cd serving
Bazel
编译脚本的修改
- 打开
.bazelrc
文件,在文末加入
startup --output_user_root=D:\BazelCache
- 表示将所有依赖的外部库源码下载到这个目录,且编译中生成的中间文件,缓存文件,静态库以及二进制文件均存放此目录。此操作与在
bazel
命令中使用--repository_cache d:\BazelCache
效果相同 。如不改变,则会默认在C盘中存放上述文件(不推荐)。 - 在
.\tensorflow_serving\workspace.bzl
中注释掉com_goolge_sentencepiece
及com_google_glog
的下载请求。此操作可规避sentencepiece
以及glog
等产生的平台关联性报错。 - 在
.\third_party\libevent\BUILD
文件中注释掉58~72行libevent
的编译语句(genrule
部分)。该编译语句适用于linux
平台,windows平台上采用官方提供cmake
方式编译,并将头文件和库文件放到指定搜索目录[1]。 - 进入
.\tensorflow_serving\model_servers\BUILD
文件中注释掉参数linkstamp
,此操作可避免调用java
运行时的异常 [2]。
cc_library( name="tensorflow_model_server_main_lib", srcs= [ "main.cc", ], hdrs= [ "version.h", ], visibility= [ ":tensorflow_model_server_custom_op_clients", "//tensorflow_serving:internal", ], ... )
TensorFlow
的源码修改
如上文所述,tensorflow
的工程源码存放在自定义的D:\BazelCache
所在目录。修改tensorflow
的源码,需进入tensorflow
工程所在的根目录,例如:
cd D:\BazelCache\z4avbakx\external\org_tensorflow
- 在文件
.\tensorflow\core\framework\registration\registration.h
中找到
- 改为:
- 此操作用来解决
msvc
对__VA_ARGS__
的支持问题[3]。同理需在文件.\tensorflow\core\framework\op_kernel.h
中将
- 改为:
TIP
: 查找目录及子目录下所有包含指定字符串的文件,linux
下可使用
grep"findStr"-r ./
- 分别在源码文件
.\tensorflow\core\lib\random\random_distributions.h.\tensorflow\cc\gradients\math_grad.cc.\tensorflow\compiler\xla\client\lib\prng.cc.\tensorflow\compiler\mlir\tensorflow\transforms\lower_tf.cc.\tensorflow\compiler\xla\client\lib\math.cc
- 中适当位置(建议在头文件包含之后)加入
- 可解决函数中找不到该宏定义的错误。
- 在文件
.\tensorflow\core\platform\windows\error_windows.cc.\tensorflow\core\platform\cloud\gcs_dns_cache.cc
- 中将
window.h
和winsock2.h
的包含顺序调换[4]。 - 在
.\tensorflow\core\platform\path.cc
中将
boolIsAbsolutePath(StringPiecepath) { return!path.empty() &&path[0] =='/';//linux中的路径第一个字符为‘/’为绝对路径}
- 改为:
boolIsAbsolutePath(StringPiecepath) { return!path.empty() &&path[1] ==':';//windows中的路径第二个字符为‘:’为绝对路径}
- 来支持在windows平台上的正常执行。
TensorFlow Serving
的源码修改
进入到tensorflow serving
工程主目录,即
cdserving
- 在文件
.\tensorflow_serving\util\net_http\server\public\response_code_enum.h
中注释掉ERROR=500
UNAVAILABLE_LEGAL=451, // Unavailable For Legal Reasons (RFC 7725)CLIENT_CLOSED_REQUEST=499, // Client Closed Request (Nginx)// Server Error//ERROR = 500, // Internal Server ErrorNOT_IMP=501, // Not ImplementedBAD_GATEWAY=502, // Bad Gateway...
- 同时修改
.\tensorflow_serving\model_servers\http_server.cc
中涉及到HTTPStatusCode::ERROR
的部分。该操作可让编译继续进行,但可能会导致调试信息缺失的问题,请谨慎操作。 - 在
.\tensorflow_serving\util\net_http\server\internal\evhttp_server.cc
中
//#include <netinet/in.h>//替换为
- 这是由于在
windows
平台没有netinet/in.h
文件,故用windows
平台下相关文件替换[5]。同时,在该文件中
voidInitLibEvent() { //if (evthread_use_pthreads() != 0) { //pthread是linux平台的线程库if (evthread_use_windows_threads() !=0) { //替换成windows平台对应的接口NET_LOG(FATAL, "Server requires pthread support."); }
- 在
.\tensorflow_serving\model_servers\main.cc
中注释掉TF_Version
以及TF_Serving_Version
的函数
if (!tensorflow::Flags::Parse(&argc, argv, flag_list)) { std::cout<<usage; return-1; } // if (display_version) {// std::cout << "TensorFlow ModelServer: " << TF_Serving_Version() << "\n";// << "TensorFlow Library: " << TF_Version() << "\n";// return 0;// }tensorflow::port::InitMain(argv[0], &argc, &argv); ...
- 由于最开始注释掉了
linkstamp
,导致version.cc
内的版本信息没有被加入执行文件,故相关的函数缺失。该操作不会影响程序的主要功能。
libevent
编译配置
由于tensorflow serving
工程中对libevent
编译脚本不适合windows
平台。故选择手动下载并采用cmake
的方式编译[1],将头文件和生成的库文件放入指定目录。在libevent
工程的根目录中新建libevent
目录,即
D:\BazelCache\z4avbakx\external\com_github_libevent_libevent\libevent
中所有头文件放入include\
目录,将生成的库文件放入lib\
目录。
编译执行
bazelbuild--experimental_repo_remote_exectensorflow_serving/model_servers:tensorflow_model_server--action_envPYTHON_BIN_PATH=D:\ProgramData\Python\Python39\python.exe--local_ram_resources=200--local_cpu_resources=10>log.txt
参数中--action_env PYTHON_BIN_PATH
用来指定python
的安装路径,若不指定,系统将无法正确找到python.exe
,导致错误[6]。--local_ram_resources=200 --local_cpu_resources=10
用来控制bazel
内存使用和cpu
核心使用。可有效解决编译过程中出现的堆空间分配不足的问题[7]。采用 >log.txt
可将低级别的警告信息保存至文件,方便排查错误类型。当出现类似下列输出
... INFO: Analyzed target //tensorflow_serving/model_servers:tensorflow_model_server (276 packages loaded, 23004 targets configured). INFO: Found 1 target... Target //tensorflow_serving/model_servers:tensorflow_model_server up-to-date: bazel-bin/tensorflow_serving/model_servers/tensorflow_model_server.exe INFO: Elapsed time: 27.595s, Critical Path: 7.34s INFO: 1 process: 1 internal. INFO: Build completed successfully, 1 total action
表明编译成功,且生成了对应的执行文件。
运行及调试
在serving
的根目录下以管理员权限执行以下命令[8]
bazel-bin\tensorflow_serving\model_servers\tensorflow_model_server.exe --model_base_path=D:\TFServing\serving\tensorflow_serving\servables\tensorflow\testdata\saved_model_half_plus_two_cpu --model_name=half_plus_two --rest_api_port=8501
当出现类似下列输出
... 2021-10-1316:21:11.190345: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: half_plus_two version: 123} 2021-10-1316:21:11.192614: I tensorflow_serving/model_servers/server_core.cc:486] Finished adding/updating models 2021-10-1316:21:11.192739: I tensorflow_serving/model_servers/server.cc:133] Using InsecureServerCredentials 2021-10-1316:21:11.192771: I tensorflow_serving/model_servers/server.cc:383] Profiler service is enabled 2021-10-1316:21:11.195258: I tensorflow_serving/model_servers/server.cc:409] Running gRPC ModelServer at 0.0.0.0:8500 ... [evhttp_server.cc : 249] NET_LOG: Entering the event loop ... 2021-10-1316:21:11.197798: I tensorflow_serving/model_servers/server.cc:430] Exporting HTTP/REST API at:localhost:8501 ...
表明windows上的服务端正常运行。可通过客户端命令
curl-XPOST http://localhost:8501/v1/models/half_plus_two:predict -d"{\"instances\":[1.0, 2.0, 5.0]}"
测试,如出现类似下列输出
{ "predictions": [2.5, 3.0, 4.5 ] }
表明客户端的请求被正确处理,服务器模型正常运行。可用于测试其他模型。
其他
内存问题
在编译时如果出现类似
LINK : fatal error LNK1102: 内存不足
的问题,可在命令行中
setPreferredToolArchitecture=x64
msvc
默认采用32位的编译工具包括cl.exe
以及link.exe
,这里采用64位的编译工具编译,解决虚拟内存不足的问题[9]。
耗时问题
编译tensorflow
的某些源码文件时会遇到耗时特别长的问题,目前还不清楚是什么问题导致。如果不中断,可能需要1~3个小时才能完成某一个文件的目标码的生成。通过切换到较低版本可能会改善时长问题(待验证)。
下一步工作
- 目前编译打包的版本没有
cuda
的支持,后续将根据模型的运行时间添加。 - 关于
libevent
对windows
平台支持不好的问题,如有需要将替换成更友好的libuv
库。 - 已经注释掉未参加编译的
sentencepiece
,gflag
库,如果影响程序的正常运行,将添加支持。 - 对于通过注释
linkstamp
去掉的版本信息,将按需添加。 - 分析
Serving
工程的依赖层次结构,去掉业务实战中用不到的功能,使执行文件轻量化。
参考文档
[6] python
环境配置错误
[7] bazel
限制内存使用
[9] 编译器堆空间不足问题
[7] bazel
限制内存使用
[9] 编译器堆空间不足问题