Skip to content

Commit 3a1f8d2

Browse files
davidlin54lind
andauthored
[AOSP] add file descriptor support to file_data_loader (#6611)
Summary: For Google's ODP, the model will execute in a process that does not have access to the file system. Instead, the caller must open the file then pass ownership of the FD to the model executor. 1. Added support for reading files by FD passed by the prefix "fd:///" (copied from tensorflow https://cs.android.com/android/platform/superproject/main/+/main:external/federated-compute/fcp/tensorflow/file_descriptor_filesystem.cc;l=41;bpv=1;bpt=1) into `file_data_loader`. 2. Added test cases by opening file first and passing the FD to the data loader Test Plan: 1. run C++ tests 2. use script with following instruction: ``` $ cd aosp $ source build/envsetup.sh $ lunch aosp_arm64-trunk_staging-userdebug $ m executor_runner $ FILENAME="<model_path>" $ exec {FD}<${FILENAME} # open file for read, assign descriptor $ echo "Opened ${FILENAME} for read using descriptor ${FD}" $ out/host/linux-x86/bin/executor_runner --model_path fd:///${FD} --is_fd_uri=true $ exec {FD}<&- # close file ``` result: ``` Opened out/host/linux-x86/bin/add.pte for read using descriptor 10 I 00:00:00.000256 executorch:executor_runner.cpp:94] Model file fd:///10 is loaded. I 00:00:00.000279 executorch:executor_runner.cpp:103] Using method forward I 00:00:00.000282 executorch:executor_runner.cpp:150] Setting up planned buffer 0, size 48. I 00:00:00.000294 executorch:executor_runner.cpp:173] Method loaded. I 00:00:00.000310 executorch:executor_runner.cpp:183] Inputs prepared. I 00:00:00.000336 executorch:executor_runner.cpp:192] Model executed successfully. I 00:00:00.000345 executorch:executor_runner.cpp:196] 1 outputs: Output 0: tensor(sizes=[1], [2.]) ``` --------- Co-authored-by: lind <[email protected]>
1 parent e1a9f91 commit 3a1f8d2

File tree

4 files changed

+205
-16
lines changed

4 files changed

+205
-16
lines changed

examples/portable/executor_runner/executor_runner.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222

2323
#include <gflags/gflags.h>
2424

25+
#include <fcntl.h>
26+
#include <unistd.h>
27+
2528
#include <executorch/extension/data_loader/file_data_loader.h>
2629
#include <executorch/extension/evalue_util/print_evalue.h>
2730
#include <executorch/extension/runner_util/inputs.h>
@@ -36,6 +39,10 @@ DEFINE_string(
3639
model_path,
3740
"model.pte",
3841
"Model serialized in flatbuffer format.");
42+
DEFINE_bool(
43+
is_fd_uri,
44+
false,
45+
"True if the model_path passed is a file descriptor with the prefix \"fd:///\".");
3946

4047
using executorch::extension::FileDataLoader;
4148
using executorch::runtime::Error;
@@ -66,7 +73,12 @@ int main(int argc, char** argv) {
6673
// DataLoaders that use mmap() or point to data that's already in memory, and
6774
// users can create their own DataLoaders to load from arbitrary sources.
6875
const char* model_path = FLAGS_model_path.c_str();
69-
Result<FileDataLoader> loader = FileDataLoader::from(model_path);
76+
const bool is_fd_uri = FLAGS_is_fd_uri;
77+
78+
Result<FileDataLoader> loader = is_fd_uri
79+
? FileDataLoader::fromFileDescriptorUri(model_path)
80+
: FileDataLoader::from(model_path);
81+
7082
ET_CHECK_MSG(
7183
loader.ok(),
7284
"FileDataLoader::from() failed: 0x%" PRIx32,

extension/data_loader/file_data_loader.cpp

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ namespace extension {
4343

4444
namespace {
4545

46+
static constexpr char kFdFilesystemPrefix[] = "fd:///";
47+
4648
/**
4749
* Returns true if the value is an integer power of 2.
4850
*/
@@ -74,25 +76,36 @@ FileDataLoader::~FileDataLoader() {
7476
::close(fd_);
7577
}
7678

77-
Result<FileDataLoader> FileDataLoader::from(
78-
const char* file_name,
79-
size_t alignment) {
79+
Result<int> getFDFromUri(const char* file_descriptor_uri) {
80+
// check if the uri starts with the prefix "fd://"
8081
ET_CHECK_OR_RETURN_ERROR(
81-
is_power_of_2(alignment),
82+
strncmp(
83+
file_descriptor_uri,
84+
kFdFilesystemPrefix,
85+
strlen(kFdFilesystemPrefix)) == 0,
8286
InvalidArgument,
83-
"Alignment %zu is not a power of 2",
84-
alignment);
87+
"File descriptor uri (%s) does not start with %s",
88+
file_descriptor_uri,
89+
kFdFilesystemPrefix);
8590

86-
// Use open() instead of fopen() to avoid the layer of buffering that
87-
// fopen() does. We will be reading large portions of the file in one shot,
88-
// so buffering does not help.
89-
int fd = ::open(file_name, O_RDONLY);
90-
if (fd < 0) {
91-
ET_LOG(
92-
Error, "Failed to open %s: %s (%d)", file_name, strerror(errno), errno);
93-
return Error::AccessFailed;
94-
}
91+
// strip "fd:///" from the uri
92+
int fd_len = strlen(file_descriptor_uri) - strlen(kFdFilesystemPrefix);
93+
char fd_without_prefix[fd_len + 1];
94+
memcpy(
95+
fd_without_prefix,
96+
&file_descriptor_uri[strlen(kFdFilesystemPrefix)],
97+
fd_len);
98+
fd_without_prefix[fd_len] = '\0';
99+
100+
// check if remaining fd string is a valid integer
101+
int fd = ::atoi(fd_without_prefix);
102+
return fd;
103+
}
95104

105+
Result<FileDataLoader> FileDataLoader::fromFileDescriptor(
106+
const char* file_name,
107+
const int fd,
108+
size_t alignment) {
96109
// Cache the file size.
97110
struct stat st;
98111
int err = ::fstat(fd, &st);
@@ -119,6 +132,47 @@ Result<FileDataLoader> FileDataLoader::from(
119132
return FileDataLoader(fd, file_size, alignment, file_name_copy);
120133
}
121134

135+
Result<FileDataLoader> FileDataLoader::fromFileDescriptorUri(
136+
const char* file_descriptor_uri,
137+
size_t alignment) {
138+
ET_CHECK_OR_RETURN_ERROR(
139+
is_power_of_2(alignment),
140+
InvalidArgument,
141+
"Alignment %zu is not a power of 2",
142+
alignment);
143+
144+
auto parsed_fd = getFDFromUri(file_descriptor_uri);
145+
if (!parsed_fd.ok()) {
146+
return parsed_fd.error();
147+
}
148+
149+
int fd = parsed_fd.get();
150+
151+
return fromFileDescriptor(file_descriptor_uri, fd, alignment);
152+
}
153+
154+
Result<FileDataLoader> FileDataLoader::from(
155+
const char* file_name,
156+
size_t alignment) {
157+
ET_CHECK_OR_RETURN_ERROR(
158+
is_power_of_2(alignment),
159+
InvalidArgument,
160+
"Alignment %zu is not a power of 2",
161+
alignment);
162+
163+
// Use open() instead of fopen() to avoid the layer of buffering that
164+
// fopen() does. We will be reading large portions of the file in one shot,
165+
// so buffering does not help.
166+
int fd = ::open(file_name, O_RDONLY);
167+
if (fd < 0) {
168+
ET_LOG(
169+
Error, "Failed to open %s: %s (%d)", file_name, strerror(errno), errno);
170+
return Error::AccessFailed;
171+
}
172+
173+
return fromFileDescriptor(file_name, fd, alignment);
174+
}
175+
122176
namespace {
123177
/**
124178
* FreeableBuffer::FreeFn-compatible callback.

extension/data_loader/file_data_loader.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,27 @@ namespace extension {
2626
*/
2727
class FileDataLoader final : public executorch::runtime::DataLoader {
2828
public:
29+
/**
30+
* Creates a new FileDataLoader that wraps the named file descriptor, and the
31+
* ownership of the file descriptor is passed. This helper is used when ET is
32+
* running in a process that does not have access to the filesystem, and the
33+
* caller is able to open the file and pass the file descriptor.
34+
*
35+
* @param[in] file_descriptor_uri File descriptor with the prefix "fd:///",
36+
* followed by the file descriptor number.
37+
* @param[in] alignment Alignment in bytes of pointers returned by this
38+
* instance. Must be a power of two.
39+
*
40+
* @returns A new FileDataLoader on success.
41+
* @retval Error::InvalidArgument `alignment` is not a power of two.
42+
* @retval Error::AccessFailed `file_name` could not be opened, or its size
43+
* could not be found.
44+
* @retval Error::MemoryAllocationFailed Internal memory allocation failure.
45+
*/
46+
static executorch::runtime::Result<FileDataLoader> fromFileDescriptorUri(
47+
const char* file_descriptor_uri,
48+
size_t alignment = alignof(std::max_align_t));
49+
2950
/**
3051
* Creates a new FileDataLoader that wraps the named file.
3152
*
@@ -79,6 +100,11 @@ class FileDataLoader final : public executorch::runtime::DataLoader {
79100
void* buffer) const override;
80101

81102
private:
103+
static executorch::runtime::Result<FileDataLoader> fromFileDescriptor(
104+
const char* file_name,
105+
const int fd,
106+
size_t alignment = alignof(std::max_align_t));
107+
82108
FileDataLoader(
83109
int fd,
84110
size_t file_size,

extension/data_loader/test/file_data_loader_test.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,103 @@ class FileDataLoaderTest : public ::testing::TestWithParam<size_t> {
4040
}
4141
};
4242

43+
TEST_P(FileDataLoaderTest, InBoundsFileDescriptorLoadsSucceed) {
44+
// Write some heterogeneous data to a file.
45+
uint8_t data[256];
46+
for (int i = 0; i < sizeof(data); ++i) {
47+
data[i] = i;
48+
}
49+
TempFile tf(data, sizeof(data));
50+
51+
int fd = ::open(tf.path().c_str(), O_RDONLY);
52+
53+
// Wrap it in a loader.
54+
Result<FileDataLoader> fdl = FileDataLoader::fromFileDescriptorUri(
55+
("fd:///" + std::to_string(fd)).c_str(), alignment());
56+
ASSERT_EQ(fdl.error(), Error::Ok);
57+
58+
// size() should succeed and reflect the total size.
59+
Result<size_t> size = fdl->size();
60+
ASSERT_EQ(size.error(), Error::Ok);
61+
EXPECT_EQ(*size, sizeof(data));
62+
63+
// Load the first bytes of the data.
64+
{
65+
Result<FreeableBuffer> fb = fdl->load(
66+
/*offset=*/0,
67+
/*size=*/8,
68+
DataLoader::SegmentInfo(DataLoader::SegmentInfo::Type::Program));
69+
ASSERT_EQ(fb.error(), Error::Ok);
70+
EXPECT_ALIGNED(fb->data(), alignment());
71+
EXPECT_EQ(fb->size(), 8);
72+
EXPECT_EQ(
73+
0,
74+
std::memcmp(
75+
fb->data(),
76+
"\x00\x01\x02\x03"
77+
"\x04\x05\x06\x07",
78+
fb->size()));
79+
80+
// Freeing should release the buffer and clear out the segment.
81+
fb->Free();
82+
EXPECT_EQ(fb->size(), 0);
83+
EXPECT_EQ(fb->data(), nullptr);
84+
85+
// Safe to call multiple times.
86+
fb->Free();
87+
}
88+
89+
// Load the last few bytes of the data, a different size than the first time.
90+
{
91+
Result<FreeableBuffer> fb = fdl->load(
92+
/*offset=*/sizeof(data) - 3,
93+
/*size=*/3,
94+
DataLoader::SegmentInfo(DataLoader::SegmentInfo::Type::Program));
95+
ASSERT_EQ(fb.error(), Error::Ok);
96+
EXPECT_ALIGNED(fb->data(), alignment());
97+
EXPECT_EQ(fb->size(), 3);
98+
EXPECT_EQ(0, std::memcmp(fb->data(), "\xfd\xfe\xff", fb->size()));
99+
}
100+
101+
// Loading all of the data succeeds.
102+
{
103+
Result<FreeableBuffer> fb = fdl->load(
104+
/*offset=*/0,
105+
/*size=*/sizeof(data),
106+
DataLoader::SegmentInfo(DataLoader::SegmentInfo::Type::Program));
107+
ASSERT_EQ(fb.error(), Error::Ok);
108+
EXPECT_ALIGNED(fb->data(), alignment());
109+
EXPECT_EQ(fb->size(), sizeof(data));
110+
EXPECT_EQ(0, std::memcmp(fb->data(), data, fb->size()));
111+
}
112+
113+
// Loading zero-sized data succeeds, even at the end of the data.
114+
{
115+
Result<FreeableBuffer> fb = fdl->load(
116+
/*offset=*/sizeof(data),
117+
/*size=*/0,
118+
DataLoader::SegmentInfo(DataLoader::SegmentInfo::Type::Program));
119+
ASSERT_EQ(fb.error(), Error::Ok);
120+
EXPECT_EQ(fb->size(), 0);
121+
}
122+
}
123+
124+
TEST_P(FileDataLoaderTest, FileDescriptorLoadPrefixFail) {
125+
// Write some heterogeneous data to a file.
126+
uint8_t data[256];
127+
for (int i = 0; i < sizeof(data); ++i) {
128+
data[i] = i;
129+
}
130+
TempFile tf(data, sizeof(data));
131+
132+
int fd = ::open(tf.path().c_str(), O_RDONLY);
133+
134+
// Wrap it in a loader.
135+
Result<FileDataLoader> fdl = FileDataLoader::fromFileDescriptorUri(
136+
std::to_string(fd).c_str(), alignment());
137+
ASSERT_EQ(fdl.error(), Error::InvalidArgument);
138+
}
139+
43140
TEST_P(FileDataLoaderTest, InBoundsLoadsSucceed) {
44141
// Write some heterogeneous data to a file.
45142
uint8_t data[256];

0 commit comments

Comments
 (0)