Adding a Custom Filesystem Plugin(添加自定义文件系统插件)
Adding a Custom Filesystem Plugin
背景
TensorFlow框架经常用于多进程和多机器环境,如Google数据中心,Google云计算机学习,亚马逊网络服务(AWS)和现场分布式集群。为了共享和保存由TensorFlow生成的某些类型的状态,该框架假定存在可靠的共享文件系统。这个共享文件系统有很多用途,例如:
- 通常将状态检查点保存到分布式文件系统以获得可靠性和容错性。
- 培训过程通过将事件文件写入TensorBoard监视的目录与TensorBoard进行通信。即使TensorBoard运行在不同的进程或机器上,共享文件系统也可以使此通信正常工作。
在现实世界中共享或分布式文件系统有很多不同的实现,所以TensorFlow为用户提供了一个能够实现可以在TensorFlow运行时注册的自定义FileSystem
插件的功能。当TensorFlow运行时试图通过FileSystem
接口写入文件时,它使用一部分路径名来动态选择应该用于文件系统操作的实现。因此,添加对自定义文件系统的支持需要实现一个FileSystem
接口,构建一个包含该实现的共享对象,并在运行时加载该对象,以便无论哪个进程需要写入该文件系统。
请注意,TensorFlow已经包含许多文件系统实现,例如:
- 标准的POSIX文件系统
注意:
NFS文件系统通常作为POSIX接口挂载,因此标准的TensorFlow可以在NFS挂载的远程文件系统上运行。
- HDFS - Hadoop文件系统
- GCS - Google云端存储文件系统
- 一个“内存映射文件”文件系统
本指南的其余部分介绍了如何实现自定义文件系统。
实现一个自定义的文件系统插件
要实现自定义文件系统插件,您必须执行以下操作:
- 实现的子类
RandomAccessFile
,WriteableFile
,AppendableFile
,和ReadOnlyMemoryRegion
。
- 将
FileSystem
接口作为子类来实现。
FileSystem
用适当的前缀模式注册实现。
- 将文件系统插件加载到想要写入该文件系统的进程中。
FileSystem界面
该FileSystem
接口是file_system.h中定义的抽象C ++接口。FileSystem
接口的实现应该实现接口定义的所有相关方法。实现该接口要求限定,例如,创建动作RandomAccessFile
,WritableFile
和实施标准的文件系统操作,例如FileExists
,IsDirectory
,GetMatchingPaths
,DeleteFile
,等。这些接口的实现通常涉及将函数的输入参数转换为委托给在您的自定义文件系统中实现等效功能的已有库函数。
例如,该PosixFileSystem
实现DeleteFile
使用POSIX unlink()
函数实现; CreateDir
只需打电话mkdir(
GetFileSize涉及
调用stat()文件
,然后返回stat对象返回所报告的文件大小。同样,对于HDFSFileSystem实现
中,这些调用直接委托给libHDFS类似
的功能,如实现hdfsDelete了的
DeleteFile
。
我们建议查看这些代码示例,以了解不同的文件系统实现如何调用其现有的库。例子包括:
文件接口
除了允许查询和操作文件系统中的文件和目录的操作外,该FileSystem
接口还要求您实现返回抽象对象实现的工厂,例如RandomAccessFile,WritableFile
TensorFlow代码以及读写该FileSystem
实现中的文件。
要实现 RandomAccessFile
,必须实现一个名为Read()
的接口,其中实现必须提供一种方法来读取指定文件中的偏移量。
例如,下面是POSIX文件系统的RandomAccessFile的实现,它使用pread()
随机访问POSIX函数来实现读取。请注意,特定实现必须知道如何重试或传播底层文件系统中的错误。
class PosixRandomAccessFile : public RandomAccessFile {
public:
PosixRandomAccessFile(const string& fname, int fd)
: filename_(fname), fd_(fd) {}
~PosixRandomAccessFile() override { close(fd_ }
Status Read(uint64 offset, size_t n, StringPiece* result,
char* scratch) const override {
Status s;
char* dst = scratch;
while (n > 0 && s.ok()) {
ssize_t r = pread(fd_, dst, n, static_cast<off_t>(offset)
if (r > 0) {
dst += r;
n -= r;
offset += r;
} else if (r == 0) {
s = Status(error::OUT_OF_RANGE, "Read less bytes than requested"
} else if (errno == EINTR || errno == EAGAIN) {
// Retry
} else {
s = IOError(filename_, errno
}
}
*result = StringPiece(scratch, dst - scratch
return s;
}
private:
string filename_;
int fd_;
};
为了实现WritableFile顺序书写的抽象,一个必须实现的几个接口,如Append()
,Flush()
,Sync()
,和Close()
。
例如,下面是POSIX文件系统的WritableFile的实现,它FILE
在其构造函数中使用一个对象,并在该对象上使用标准的posix函数来实现该接口。
class PosixWritableFile : public WritableFile {
public:
PosixWritableFile(const string& fname, FILE* f)
: filename_(fname), file_(f) {}
~PosixWritableFile() override {
if (file_ != NULL) {
fclose(file_
}
}
Status Append(const StringPiece& data) override {
size_t r = fwrite(data.data(), 1, data.size(), file_
if (r != data.size()) {
return IOError(filename_, errno
}
return Status::OK(
}
Status Close() override {
Status result;
if (fclose(file_) != 0) {
result = IOError(filename_, errno
}
file_ = NULL;
return result;
}
Status Flush() override {
if (fflush(file_) != 0) {
return IOError(filename_, errno
}
return Status::OK(
}
Status Sync() override {
Status s;
if (fflush(file_) != 0) {
s = IOError(filename_, errno
}
return s;
}
private:
string filename_;
FILE* file_;
};
有关更多详细信息,请参阅这些接口的文档,并查看示例实现以获取灵感。
注册并加载文件系统
一旦你FileSystem
为你的自定义文件系统实现了实现,你需要在“scheme”下注册它,以便将以该模式为前缀的路径指向你的实现。为此,请致电REGISTER_FILE_SYSTEM
:
REGISTER_FILE_SYSTEM("foobar", FooBarFileSystem
当TensorFlow尝试对其路径以文件开头的文件进行操作时foobar://
,它将使用该FooBarFileSystem
实现。
string filename = "foobar://path/to/file.txt";
std::unique_ptr<WritableFile> file;
// Calls FooBarFileSystem::NewWritableFile to return
// a WritableFile class, which happens to be the FooBarFileSystem's
// WritableFile implementation.
TF_RETURN_IF_ERROR(env->NewWritableFile(filename, &file)
接下来,您必须构建一个包含此实现的共享对象。cc_binary
在这里可以找到使用bazel 规则的例子,但是你可以使用任何构建系统来做到这一点。有关类似说明,请参阅构建op库的部分。
构建此目标的结果是.so
共享对象文件。
最后,您必须在此过程中动态加载此实施。在Python中,您可以调用该tf.load_file_system_library(file_system_library)
函数,将路径传递给共享对象。在您的客户端程序中调用此函数会加载进程中的共享对象,从而将您的实现注册为可用于通过FileSystem
接口进行的任何文件操作。例如,您可以看到test_file_system.py。
什么通过这个接口?
几乎所有TensorFlow核心C ++文件操作使用FileSystem
界面,比如CheckpointWriter
,EventsWriter
和许多其他工具。这意味着实现一个FileSystem
实现允许大部分TensorFlow程序写入共享文件系统。
在Python中,gfile
和file_io
类通过SWIG与`FileSystem实现绑定在一起,这意味着一旦你加载了这个文件系统库,你就可以:
with gfile.Open("foobar://path/to/file.txt") as w:
w.write("hi")
当你这样做时,包含“hi”的文件将出现在共享文件系统的“/path/to/file.txt”中。