1.bazelԴ?源译????
2.从源码build Tensorflow2.6.5的记录
3.å¦ä½ä½¿ç¨bazel build
4.TF-TRT使用环境搭建
5.实战!用Bazel来管理iOS程序
6.极简入门TensorFlow C++源码
bazelԴ?码编????
针对macOS mojave ..6系统用户在编译tensorflow 2.6版本时遇到的SSE4.1、SSE4.2和AVX指令集问题,源译以下为解决步骤:
首先,码编前往tensorflow源码下载页面,源译下载v2.6.0版本。码编libevent源码解读
然后,源译进入下载后的码编目录,定位至v2.6.0。源译
接下来,码编准备必要的源译软件环境。确保已安装java和minconda。码编
开始编译tensorflow时,源译关键在于使用优化指令集。码编在执行编译命令时,源译加入参数`-march=native`以进行cpu指令集优化。
使用命令行进行编译:`bazelisk build -c opt --copt=-march=native //tensorflow/tools/pip_package:build_pip_package`。
编译完成后,在/tmp/tensorflow_pkg目录下找到生成的wheel文件。使用pip进行安装,即可完成tensorflow 2.6版本的安装。
完成编译与安装后,用户可根据需要下载tensorflow-2.6.0-cp-cpm-macosx___x_.whl文件。提取码为kkli。
从源码build Tensorflow2.6.5的记录
.从源码编译Tensorflow2.6.5踩坑记录,笔者经过一天的努力,失败四次后终于成功。Tensorflow2.6.5是截至.时,能够从源码编译的最新版本。
0 - 前期准备
为了对Tensorflow进行大规模修改并完成科研工作,笔者有从源码编译Tensorflow的需求。平时更常用的防伪溯源码的好处做法是在conda环境中pip install tensorflow,有时为了环境隔离方便打包,会用docker先套住,再上conda + pip安装。
1 - 资料汇总
教程参考:
另注:bazel的编译可以使用换源清华镜像(不是必要)。整体配置流程的根本依据还是官方的教程,但它的教程有些点和坑没有涉及到,所以多方材料了解。
2 - 整体流程
2.1 确定配置目标
官网上给到了配置目标,和对应的版本匹配关系(这张表里缺少了对numpy的版本要求)。笔者最后(在docker中)配置成功的版本为tensorflow2.6.5 numpy1..5 Python3.7. GCC7.5.0 CUDA.3 Bazel3.7.2。
2.2 开始配置
为了打包方便和编译环境隔离,在docker中进行了以下配置:
2. 安装TensorFlow pip软件包依赖项,其编译过程依赖于这些包。
3. Git Tensorflow源代码包。
4. 安装编译工具Bazel。
官网的介绍:(1)您需要安装Bazel,才能构建TensorFlow。您可以使用Bazelisk轻松安装Bazel,并且Bazelisk可以自动为TensorFlow下载合适的Bazel版本。为便于使用,请在PATH中将Bazelisk添加为bazel可执行文件。(2)如果没有Bazelisk,您可以手动安装Bazel。请务必安装受支持的Bazel版本,可以是tensorflow/configure.py中指定的介于_TF_MIN_BAZEL_VERSION和_TF_MAX_BAZEL_VERSION之间的任意版本。
但笔者尝试最快的安装方式是,到Github - bazelbuild/build/releases上下载对应的版本,然后使用sh脚本手动安装。比如依据刚才的配置目标,笔者需要的是Bazel3.7.2,所以下载的恐龙翻天印指标源码文件为bazel-3.7.2-installer-linux-x_.sh。
5. 配置编译build选项
官网介绍:通过运行TensorFlow源代码树根目录下的./configure配置系统build。此脚本会提示您指定TensorFlow依赖项的位置,并要求指定其他构建配置选项(例如,编译器标记)。
这一步就是选择y/N基本没啥问题,其他参考里都有贴实例。笔者需要GPU的支持,故在CUDA那一栏选择了y,其他部分如Rocm部分就是N(直接按enter也可以)。
6.开始编译
编译完成应输出
7.检查TF是否能用
3 - 踩坑记录
3.1 cuda.0在编译时不支持sm_
笔者最初选择的docker是cuda.0的,在bazel build --config=cuda //tensorflow/tools/pip_package:build_pip_package过程中出现了错误。所以之后选择了上面提到的cuda.3的docker。
3.2 问题2: numpy、TF、python版本匹配
在配置过程中,发现numpy、TF、python版本需要匹配,否则会出现错误。
4 - 启示
从源码编译Tensorflow2.6.5的过程,虽然经历了多次失败,但最终还是成功。这个过程也让我对Tensorflow的编译流程有了更深入的了解,同时也提醒我在后续的工作中要注意版本匹配问题。
å¦ä½ä½¿ç¨bazel build
å®è£
å®è£ è¿ç¨è¯·åè: /example
$ cat > src/main/java/com/example/ProjectRunner.java <<EOF
package com.example;
public class ProjectRunner {
public static void main(String args[]) {
Greeting.sayHi();
}
}
EOF
$ cat > src/main/java/com/example/Greeting.java <<EOF
package com.example;
public class Greeting {
public static void sayHi() {
System.out.println("Hi!");
}
}
EOF
Bazeléè¿å·¥ä½åºä¸ææå为 BUILD çæ件æ¥è§£æéè¦æ建ç项ç®ä¿¡æ¯ï¼å æ¤ï¼æ们éè¦å å¨ ~/gitroot/my-project ç®å½å建ä¸ä¸ª BUILD æ建æ件ãä¸é¢æ¯BUILDæ建æ件çå 容ï¼
# ~/gitroot/my-project/BUILD
java_binary(
name = "my-runner",
srcs = glob(["**/*.java"]),
main_class = "com.example.ProjectRunner",
)
BUILDæ件éç¨ç±»ä¼¼Pythonçè¯æ³ãè½ç¶ä¸è½å å«ä»»æçPythonè¯æ³ï¼ä½æ¯BUILDæ件ä¸çæ¯ä¸ªæ建è§åçèµ·æ¥é½è±¡æ¯ä¸ä¸ªPythonå½æ°è°ç¨ï¼èä¸ä½ ä¹å¯ä»¥ç¨ "#" å¼å¤´æ¥æ·»å åè¡æ³¨éã
java_binary æ¯ä¸ä¸ªæ建è§åãå ¶ä¸ name 对åºä¸ä¸ªæ建ç®æ çæ è¯ç¬¦ï¼å¯ç¨ç¨å®æ¥åBazelæå®æ建åªä¸ªé¡¹ç®ãsrcs 对åºä¸ä¸ªæºæ件å表ï¼Bazeléè¦å°è¿äºæºæ件ç¼è¯ä¸ºäºè¿å¶æ件ãå ¶ä¸ glob(["**/*.java"]) 表示éå½å å«æ¯ä¸ªåç®å½ä¸ä»¥æ¯ä¸ª .java 为åç¼åçæ件ãcom.example.ProjectRunner æå®å å«mainæ¹æ³çç±»ã
ç°å¨å¯ä»¥ç¨ä¸é¢çå½ä»¤æ建è¿ä¸ªJavaç¨åºäºï¼
$ cd ~/gitroot/my-project
$ bazel build //:my-runner
INFO: Found 1 target...
Target //:my-runner up-to-date:
bazel-bin/my-runner.jar
bazel-bin/my-runner
INFO: Elapsed time: 1.s, Critical Path: 0.s
$ bazel-bin/my-runner
Hi!
æåï¼ä½ å·²ç»æåæ建äºç¬¬ä¸ä¸ªBazel项ç®äºï¼
æ·»å ä¾èµå ³ç³»
对äºå°é¡¹ç®å建ä¸ä¸ªè§åæ¯å¯ä»¥çï¼ä½æ¯éç项ç®çå大ï¼åéè¦åå«æ建项ç®çä¸åçé¨ä»¶ï¼æç»åç»è£ æ产åãè¿ç§æ建æ¹å¼å¯ä»¥é¿å å 为å±é¨ç»å°çä¿®æ¹å¿å¯¼è´éç°æ建æ´ä¸ªåºç¨ï¼åæ¶ä¸åçæ建æ¥éª¤å¯ä»¥å¾å¥½å°å¹¶åæ§è¡ä»¥æé«æ建æçã
æ们ç°å¨å°ä¸ä¸ªé¡¹ç®æå为两个é¨åç¬ç«æ建ï¼åæ¶è®¾ç½®å®ä»¬ä¹é´çä¾èµå ³ç³»ãåºäºä¸é¢çä¾åï¼æ们éåäºBUILDæ建æ件ï¼
java_binary(
name = "my-other-runner",
srcs = ["src/main/java/com/example/ProjectRunner.java"],
main_class = "com.example.ProjectRunner",
deps = [":greeter"],
)
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
)
è½ç¶æºæ件æ¯ä¸æ ·çï¼ä½æ¯ç°å¨Bazelå°éç¨ä¸åçæ¹å¼æ¥æ建ï¼é¦å æ¯æ建 greeteråºï¼ç¶åæ¯æ建 my-other-runnerãå¯ä»¥å¨æ建æååç«å»è¿è¡ //:my-other-runnerï¼
$ bazel run //:my-other-runner
INFO: Found 1 target...
Target //:my-other-runner up-to-date:
bazel-bin/my-other-runner.jar
bazel-bin/my-other-runner
INFO: Elapsed time: 2.s, Critical Path: 1.s
INFO: Running command line: bazel-bin/my-other-runner
Hi!
ç°å¨å¦æä½ æ¹å¨ProjectRunner.java代ç 并éæ°æ建my-other-runnerç®æ ï¼Greeting.javaæ件å 为没æååèä¸ä¼éç°ç¼è¯ã
使ç¨å¤ä¸ªå ï¼Packagesï¼
对äºæ´å¤§ç项ç®ï¼æ们é常éè¦å°å®ä»¬æåå°å¤ä¸ªç®å½ä¸ãä½ å¯ä»¥ç¨ç±»ä¼¼//path/to/directory:target-nameçååå¼ç¨å¨å ¶ä»BUILDæ件å®ä¹çç®æ ãå设src/main/java/com/example/æä¸ä¸ªcmdline/åç®å½ï¼å å«ä¸é¢çæ件ï¼
$ mkdir -p src/main/java/com/example/cmdline
$ cat > src/main/java/com/example/cmdline/Runner.java <<EOF
package com.example.cmdline;
import com.example.Greeting;
public class Runner {
public static void main(String args[]) {
Greeting.sayHi();
}
}
EOF
Runner.javaä¾èµcom.example.Greetingï¼å æ¤æ们éè¦å¨src/main/java/com/example/cmdline/BUILDæ建æ件ä¸æ·»å ç¸åºçä¾èµè§åï¼
# ~/gitroot/my-project/src/main/java/com/example/cmdline/BUILD
java_binary(
name = "runner",
srcs = ["Runner.java"],
main_class = "com.example.cmdline.Runner",
deps = ["//:greeter"]
)
ç¶èï¼é»è®¤æ åµä¸æ建ç®æ é½æ¯ ç§æ çãä¹å°±æ¯è¯´ï¼æ们åªè½å¨åä¸ä¸ªBUILDæ件ä¸è¢«å¼ç¨ãè¿å¯ä»¥é¿å å°å¾å¤å®ç°çç»èæ´æ¼ç»å ¬å ±çæ¥å£ï¼ä½æ¯ä¹æå³çæ们éè¦æå·¥å 许runneræä¾èµç//:greeterç®æ ãå°±æ¯ç±»ä¼¼ä¸é¢è¿ä¸ªå¨æ建runnerç®æ æ¶éå°çé误ï¼
$ bazel build //src/main/java/com/example/cmdline:runner
ERROR: /home/user/gitroot/my-project/src/main/java/com/example/cmdline/BUILD:2:1:
Target '//:greeter' is not visible from target '//src/main/java/com/example/cmdline:runner'.
Check the visibility declaration of the former target if you think the dependency is legitimate.
ERROR: Analysis of target '//src/main/java/com/example/cmdline:runner' failed; build aborted.
INFO: Elapsed time: 0.s
å¯ç¨éè¿å¨BUILDæ件å¢å visibility = levelå±æ§æ¥æ¹åç®æ çå¯é´èå´ãä¸é¢æ¯éè¿å¨~/gitroot/my-project/BUILDæ件å¢å å¯è§è§åï¼æ¥æ¹ågreeterç®æ çå¯è§èå´ï¼
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
visibility = ["//src/main/java/com/example/cmdline:__pkg__"],
)
è¿ä¸ªè§å表示//:greeterç®æ 对äº//src/main/java/com/example/cmdlineå æ¯å¯è§çãç°å¨æ们å¯ä»¥éæ°æ建runnerç®æ ç¨åºï¼
$ bazel run //src/main/java/com/example/cmdline:runner
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner.jar
bazel-bin/src/main/java/com/example/cmdline/runner
INFO: Elapsed time: 1.s, Critical Path: 0.s
INFO: Running command line: bazel-bin/src/main/java/com/example/cmdline/runner
Hi!
åèææ¡£ ä¸æå¯è§æ§é 置说æã
é¨ç½²
å¦æä½ æ¥ç bazel-bin/src/main/java/com/example/cmdline/runner.jar çå 容ï¼å¯ä»¥çå°éé¢åªå å«äºRunner.classï¼å¹¶æ²¡æä¿æ¤æä¾èµçGreeting.classï¼
$ jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
è¿åªè½å¨æ¬æºæ£å¸¸å·¥ä½ï¼å 为Bazelçrunnerèæ¬å·²ç»å°greeter jaræ·»å å°äºclasspathï¼ï¼ä½æ¯å¦æå°runner.jaråç¬å¤å¶å°å¦ä¸å°æºå¨ä¸è®²ä¸è½æ£å¸¸è¿è¡ãå¦ææ³è¦æ建å¯ç¨äºé¨ç½²åå¸çèªå å«ææä¾èµçç®æ ï¼å¯ä»¥æ建runner_deploy.jarç®æ ï¼ç±»ä¼¼<target-name>_deploy.jar以_deploy为åç¼çåå对åºå¯é¨ç½²ç®æ ï¼ã
$ bazel build //src/main/java/com/example/cmdline:runner_deploy.jar
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner_deploy.jar up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
INFO: Elapsed time: 1.s, Critical Path: 0.s
runner_deploy.jarä¸å°å å«å ¨é¨çä¾èµã
ä¸ä¸æ¥
ç°å¨ï¼æ¨å¯ä»¥å建èªå·±çç®æ 并ç»è£ æç»äº§åäºãæ¥ä¸æ¥ï¼å¯æ¥ç ç¸å ³æç¨ åå«å¦ä¹ å¦ä½ç¨Bazelæ建ä¸ä¸ªæå¡å¨ãAndroidåiOSåºç¨ãä¹å¯ä»¥åè ç¨æ·æåè·å¾æ´å¤çä¿¡æ¯ãå¦ææé®é¢çè¯ï¼å¯ä»¥å° bazel-discuss 论åæé®ã
TF-TRT使用环境搭建
TF-TRT,即TensorFlow与TensorRT的集成,是NVIDIA为加速深度学习推理应用而设计的工具。它简化了TensorFlow用户在GPU上利用TensorRT进行模型推理的流程。本文主要介绍如何在服务器上搭建TF-TRT的使用环境和编写相关代码。
首先,NVIDIA推荐的魔兽世界后台钓鱼源码TF-TRT环境配置基于TensorRT 5.0RC,需要确保NVIDIA驱动程序版本.0以上,CUDA .0以及TensorRT。安装过程建议在Anaconda的虚拟环境中进行,从Tensorflow GitHub上下载1.版本源码,并通过bazel build工具生成pip安装包。在编译时,由于GCC 5.0可能与新版本兼容性问题,需添加特定编译选项。
对于服务器上直接安装,你需按照官方教程安装CUDA、CUDNN、NVIDIA Driver和TensorRT。在Tensorflow的configure文件中,根据你的硬件配置进行相应的调整。然后,通过pip安装生成的.whl文件,安装时需要注意选择nvcc编译器,cudnn 7.3以上版本,以及兼容性的GCC编译选项。
另一种方式是利用Docker容器,Tensorflow .容器需要nvidia driver +版本,并需要获取Nvidia GPU cloud的API密钥。安装完成后,你可以通过Docker拉取tensorflow:.-py3镜像,验证TensorRT与Tensorflow的集成是否成功。
无论是直接安装还是容器化,都需注意选择合适的驱动和软件版本,以确保TF-TRT的稳定运行。安装过程中,还可以根据实际需求在container中安装其他软件,以满足个性化需求。答案资源码怎么使用
实战!用Bazel来管理iOS程序
在探索Bazel作为iOS项目管理工具的过程中,我总结了以下几点原因促使我选择Bazel而非Xcode:
首先,Bazel提供更好的代码审查和依赖管理。Xcode的编译设置分散在多个文件中(xcscheme、pbxproj和xcconfig),使得代码审查变得困难。相反,Bazel的配置统一且可追踪,有利于代码审查流程。
其次,Bazel具有高效的缓存机制和快速增量编译能力。Xcode的编译缓存管理效率低下,即使是细微的设置改动也可能导致全量编译。相比之下,Bazel仅对发生变化的文件或依赖进行编译,切换分支也不会显著影响编译速度。
此外,Bazel支持远程缓存,这在项目规模增大时能显著提高编译效率。它还提供了更多模块化实践的友好环境,支持Swift等语言的大型项目开发。
选择迁移Bazel的时机因工程团队规模和需求而异。在感受到Xcode编译系统的不足(如长时间编译时间、单体大项目或持续集成工具频繁报错)时,迁移可能需要几个月的时间。而对于小型团队,迁移Bazel可能不是当前的首要任务。
配置阶段涉及安装工具(如Bazelisk和Tulsi)和调整文件结构。在源码库中创建WORKSPACE文件以整合规则,使用generate_xcodeproj.sh脚本生成Xcode项目。同时,通过PodToBUILD项目自动将CocoaPods依赖转换为Bazel兼容的格式。
处理CocoaPods依赖时,利用PodToBUILD将现有项目的依赖转换为Bazel格式,然后通过特定命令将依赖复制到源码库中。对于某些依赖,如Google的Protobuf,可以将官方的BUILD文件替换原有的CocoaPods配置。其他依赖通常通过Bazel的编译规则进行导入。
在处理C++支持时,需要手动创建自定义的toolchain并将其集成到本地项目中,以确保使用C++。此外,还需要关注Bridging header的配置、Provisioning文件、entitlement设置、以及手机测试和打包的相关步骤。
在Bazel下进行单元测试时,注意区分Hosted Tests与ios_unit_test的使用场景。对于大多数测试应转换为单元测试,而对于依赖特定环境的测试(如SnapshotTesting),则保持Hosted Tests的使用。
将Bazel集成至持续集成系统中,如Bitrise,可以简化测试和打包流程。通过共享缓存,可以进一步提升持续集成的编译速度。同时,使用Bazel的select_a_variant函数选择性地编译不同版本的程序,如针对Apple的提交版本。
随着项目的深入使用Bazel,可以探索更多的代码生成技术,例如自动将配置文件(如JSON文件)生成固定Swift文件,直接嵌入程序中,减少加载步骤。
迁移Bazel虽然需要一定的前期投入,但相对于之前的切换经验(如从Xcode Workspace到Buck),过程变得更加顺畅。对于大型依赖复杂的项目,迁移工作量也相对较小,通常只需几天时间。
尽管Bazel已提供了良好的基础支持,仍有改进空间,尤其是在工具集成与代码生成的自动化方面。此外,社区的反馈和讨论对于进一步优化迁移过程和利用Bazel的潜力至关重要。
极简入门TensorFlow C++源码
前一段时间,我专注在框架开发上,并偶尔协助业务同学优化使用TensorFlow的代码。在观看dmlc/relay、nnvm的代码时,我发现了它们的有趣之处。我也对TensorFlow的Graph IR、PaddlePaddle的Graph IR产生了兴趣,上周五在阅读代码时,无意间听到了一个数据竞赛群讨论框架的底层实现。几位算法大佬提到了看底层源码可能较为繁琐,因为这类代码通常相对容易理解。在与群内伙伴的交流后,我萌生了撰写一篇关于如何阅读TensorFlow或其他框架底层源码的文章。
选择合适版本的bazel,对于阅读TensorFlow源码至关重要。应使用版本为0..0的bazel来拉取TF2.0代码,因为太高的版本或太低的版本可能影响阅读体验。在安装了合适的bazel版本后,使用clion上的bazel插件进行导入,然后配置编译,导入项目,等待clion编译整个项目。完成编译后,就能愉快地阅读代码,甚至于protobuf生成的文件也能轻松跳转。
使用c++编译模型是TensorFlow的另一面。尝试使用c++编写模型代码,可以深入理解TensorFlow的底层机制。主要函数包括CreateGraphDef、ConcurrentSteps、ConcurrentSessions等。通过这些函数,可以构建计算图,定义节点、常量变量、操作符等。这为理解TensorFlow的逻辑提供了直观的视角。
深入分析代码后,可以了解到TensorFlow的GraphDef机制、Square类的实现、注册到特定op的过程、functor的使用以及最终的实现逻辑。这有助于理解TensorFlow的核心原理,并在阅读源码时进行更深入的思考。
除了阅读源码,还可以通过编写测试用例来增强理解。TensorFlow提供了丰富的测试用例,如在client_session_test.cc中运行测试程序,可以验证代码的正确性。这不仅有助于理解代码,还能提高对TensorFlow框架的掌握程度。
阅读源码只是理解TensorFlow原理的开始,深入行业论文和请教行业专家是进一步深入学习的关键。网络上关于机器学习系统的资料丰富多样,但缺少系统性的课程。希望官方能够分享更多框架的干货,并期待在学习过程中总结和分享更多资源。阅读源码虽然复杂,但其背后蕴含的原理和逻辑十分有趣。
MacOS 下交叉编译的折腾笔记
探索 MacOS 下的交叉编译技巧
本文作为系列 “折腾笔记” 的一部分,旨在以直白的方式展示交叉编译过程中的实际操作,而非追求最佳实践。本教程将为初学者提供一个直观理解交叉编译的基本框架,并在后续篇章中深入探讨基于 Bazel 的交叉编译最佳实践,以及如何在树莓派等目标平台上运行包含深度学习模型的小程序。
值得注意的是,尽管 MacOS 广为人知,但并不等同于 Linux。在 MacOS 上进行交叉编译时,往往面临着一些挑战。例如,某些 TensorFlowLite 提供的交叉编译工具或 Linaro 系列工具仅在 Linux 环境下可用。因此,建议在进行 MacOS 下的交叉编译时,采用 Docker 技术运行 Linux 系统,从而有效绕过这些平台限制。
对于交叉编译的入门理解与实践思路,我们需要明确其本质是利用能将源代码转换为目标平台机器语言的编译器。在进行树莓派等目标平台的交叉编译时,通常需要使用特定于目标架构(如 ARM)的编译器,例如 arm-linux-gnueabihf-gcc。
实际操作中,交叉编译流程可以概括为以下步骤:
1. **依赖环境安装**:利用 Homebrew 等包管理工具安装必要的依赖项。
2. **环境准备**:从树莓派设备上复制相关 gcc 及其配套环境。
3. **环境检查**:确保当前工作目录正确无误。
4. **源代码准备**:编写或获取待编译的源代码文件,如 `hello_cross_comile.cc`。
5. **交叉编译执行**:利用 LLVM 工具链结合 arm-linux-gnueabihf-binutils 进行交叉编译。
6. **构建输出**:运行特定编译脚本(通常封装为 `.sh` 文件)生成目标平台可执行文件(如 `hello`),随后将该文件传输至树莓派等目标平台进行执行。
推荐阅读资源:
4. **[野火]i.MX Linux开发实战指南**:该文档提供了一个全面且详细的交叉编译指南,虽然不直接支持 MacOS,但通过开启 Docker 环境,可以轻松实现 MacOS 下的交叉编译。
Crosstool-ng:尽管这是 MacOS 下公认的交叉编译解决方案,但其操作复杂,且存在系统崩溃风险。对于坚持使用此方案的开发者,可参考他人提供的 Docker 镜像,例如 **Dockfile**,但同样建议考虑使用更易管理的 Linux 操作系统(如 Ubuntu)作为 Docker 容器的基础环境。