Skip to content

Commit 8fbc674

Browse files
hehhjiangtensorflower-gardener
authored andcommitted
Let minibenchmark dup the model file descriptor if the model is passed in as a file descriptor.
There is a chance that fd is opened with O_CLOEXEC, and will be closed when starting child process. We dup the input fd, and pass the new fd to the child process. PiperOrigin-RevId: 510979253
1 parent f7c4cfb commit 8fbc674

File tree

5 files changed

+107
-3
lines changed

5 files changed

+107
-3
lines changed

tensorflow/lite/experimental/acceleration/mini_benchmark/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ cc_library(
462462
":runner",
463463
":status_codes",
464464
":validator",
465+
"@com_google_absl//absl/strings",
465466
"@flatbuffers",
466467
"//tensorflow/lite/experimental/acceleration/configuration/c:delegate_plugin",
467468
"//tensorflow/lite:minimal_logging",

tensorflow/lite/experimental/acceleration/mini_benchmark/blocking_validator_runner_test.cc

+34
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,40 @@ TEST_F(BlockingValidatorRunnerTest, SucceedWithEmbeddedValidation) {
106106
}
107107
}
108108

109+
TEST_F(BlockingValidatorRunnerTest, SucceedWithFdCloexecEmbeddedValidation) {
110+
if (!should_perform_test_) {
111+
std::cerr << "Skipping test";
112+
return;
113+
}
114+
115+
options_.model_fd = open(options_.model_path.c_str(), O_RDONLY | O_CLOEXEC);
116+
ASSERT_GE(options_.model_fd, 0);
117+
struct stat stat_buf = {0};
118+
ASSERT_EQ(fstat(options_.model_fd, &stat_buf), 0);
119+
options_.model_size = stat_buf.st_size;
120+
options_.model_offset = 0;
121+
options_.model_path.clear();
122+
123+
BlockingValidatorRunner runner(options_);
124+
ASSERT_EQ(runner.Init(), kMinibenchmarkSuccess);
125+
FlatBufferBuilder fbb;
126+
#ifdef __ANDROID__
127+
fbb.Finish(CreateTFLiteSettings(fbb, Delegate_GPU));
128+
#else
129+
fbb.Finish(CreateTFLiteSettings(fbb));
130+
#endif // __ANDROID__
131+
132+
std::vector<FlatBufferBuilder> results = runner.TriggerValidation(
133+
{flatbuffers::GetRoot<TFLiteSettings>(fbb.GetBufferPointer())});
134+
EXPECT_THAT(results, testing::Not(testing::IsEmpty()));
135+
for (auto& result : results) {
136+
const BenchmarkEvent* event =
137+
GetRoot<BenchmarkEvent>(result.GetBufferPointer());
138+
EXPECT_EQ(event->event_type(), BenchmarkEventType_END);
139+
EXPECT_TRUE(event->result()->ok());
140+
}
141+
}
142+
109143
TEST_F(BlockingValidatorRunnerTest, SucceedWithFdModelCustomValidation) {
110144
if (!should_perform_test_) {
111145
std::cerr << "Skipping test";

tensorflow/lite/experimental/acceleration/mini_benchmark/validator_runner_impl.cc

+59-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ limitations under the License.
2323
#include <utility>
2424
#include <vector>
2525

26+
#include "absl/strings/match.h"
27+
#include "absl/strings/numbers.h"
28+
#include "absl/strings/str_join.h"
29+
#include "absl/strings/str_split.h"
2630
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
2731
#include "tensorflow/lite/experimental/acceleration/configuration/configuration_generated.h"
2832
#include "tensorflow/lite/experimental/acceleration/mini_benchmark/benchmark_result_evaluator.h"
@@ -54,6 +58,56 @@ std::unique_ptr<FlatBufferBuilder> CopyModel(
5458
return copy;
5559
}
5660

61+
// A simple holder for file descriptor that will close the file descriptor at
62+
// destruction time.
63+
class FdHolder {
64+
public:
65+
explicit FdHolder(int fd) : fd_(fd) {}
66+
67+
// Move only.
68+
FdHolder(FdHolder&& other) = default;
69+
FdHolder& operator=(FdHolder&& other) = default;
70+
71+
~FdHolder() {
72+
if (fd_ > 0) {
73+
close(fd_);
74+
}
75+
}
76+
77+
private:
78+
int fd_;
79+
};
80+
81+
// Returns a FdHolder that will close the duped file descriptor when going out
82+
// of scope. If the model is passed in as a file descriptor, update the
83+
// model_path with a duped file descriptor. The original file descriptor may be
84+
// opened with FD_CLOEXEC, and cannot be read from the child process.
85+
std::unique_ptr<FdHolder> UpdateModelPathIfUsingFd(std::string& model_path) {
86+
if (!absl::StartsWith(model_path, "fd:")) {
87+
return nullptr;
88+
}
89+
std::vector<std::string> parts = absl::StrSplit(model_path, ':');
90+
int model_fd;
91+
if (!absl::SimpleAtoi(parts[1], &model_fd)) {
92+
TFLITE_LOG_PROD(TFLITE_LOG_ERROR,
93+
"Failed to parse file descriptor %s from model_path %s",
94+
parts[1].c_str(), model_path.c_str());
95+
return nullptr;
96+
}
97+
int new_fd = dup(model_fd);
98+
if (new_fd < 0) {
99+
TFLITE_LOG_PROD(
100+
TFLITE_LOG_ERROR,
101+
"Failed to dup() file descriptor. Original fd: %d errno: %d", model_fd,
102+
errno);
103+
return nullptr;
104+
}
105+
106+
parts[1] = std::to_string(new_fd);
107+
model_path = absl::StrJoin(parts, ":");
108+
return std::make_unique<FdHolder>(new_fd);
109+
}
110+
57111
} // namespace
58112

59113
MinibenchmarkStatus ValidatorRunnerImpl::Init() {
@@ -143,7 +197,7 @@ void ValidatorRunnerImpl::TriggerValidationAsync(
143197
// error_reporter is not passed in because the ownership cannot be passed to
144198
// the thread.
145199
std::thread detached_thread(
146-
[model_path = fd_or_model_path_, storage_path = storage_path_,
200+
[original_model_path = fd_or_model_path_, storage_path = storage_path_,
147201
data_directory_path = data_directory_path_,
148202
tflite_settings = std::move(tflite_settings),
149203
validation_entrypoint_name =
@@ -157,6 +211,10 @@ void ValidatorRunnerImpl::TriggerValidationAsync(
157211
if (!lock.TryLock()) {
158212
return;
159213
}
214+
215+
std::string model_path = original_model_path;
216+
std::unique_ptr<FdHolder> fd_holder =
217+
UpdateModelPathIfUsingFd(model_path);
160218
for (auto& one_setting : *tflite_settings) {
161219
FlatbufferStorage<BenchmarkEvent> storage(storage_path);
162220
TFLiteSettingsT tflite_settings_obj;

tensorflow/lite/tools/model_loader.cc

+12-1
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,21 @@ bool PathModelLoader::InitInternal() {
5656

5757
bool MmapModelLoader::InitInternal() {
5858
if (model_fd_ < 0 || model_offset_ < 0 || model_size_ < 0) {
59+
TFLITE_LOG_PROD(
60+
TFLITE_LOG_ERROR,
61+
"Invalid model file descriptor. file descriptor: %d model_offset: "
62+
"%d model_size: %d",
63+
model_fd_, model_offset_, model_size_);
5964
return false;
6065
}
6166
if (!MMAPAllocation::IsSupported()) {
67+
TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "MMAPAllocation is not supported.");
6268
return false;
6369
}
6470
auto allocation = std::make_unique<MMAPAllocation>(
6571
model_fd_, model_offset_, model_size_, tflite::DefaultErrorReporter());
6672
if (!allocation->valid()) {
73+
TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "MMAPAllocation is not valid.");
6774
return false;
6875
}
6976
model_ = FlatBufferModel::VerifyAndBuildFromAllocation(std::move(allocation));
@@ -72,6 +79,8 @@ bool MmapModelLoader::InitInternal() {
7279

7380
bool PipeModelLoader::InitInternal() {
7481
if (pipe_fd_ < 0) {
82+
TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "Invalid pipe file descriptor %d",
83+
pipe_fd_);
7584
return false;
7685
}
7786

@@ -89,7 +98,7 @@ bool PipeModelLoader::InitInternal() {
8998
// Close the read pipe.
9099
close(pipe_fd_);
91100
if (read_bytes < 0 || remaining_bytes != 0) {
92-
TFLITE_LOG_PROD(TFLITE_LOG_INFO,
101+
TFLITE_LOG_PROD(TFLITE_LOG_ERROR,
93102
"Read Model from pipe failed: %s. Expect to read %d bytes, "
94103
"%d bytes missing.",
95104
std::strerror(errno), model_size_, remaining_bytes);
@@ -112,6 +121,7 @@ std::unique_ptr<ModelLoader> CreateModelLoaderFromPath(absl::string_view path) {
112121
if (parts.size() != 4 || !absl::SimpleAtoi(parts[1], &model_fd) ||
113122
!absl::SimpleAtoi(parts[2], &model_offset) ||
114123
!absl::SimpleAtoi(parts[3], &model_size)) {
124+
TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "Failed to parse model path: %s", path);
115125
return nullptr;
116126
}
117127
return std::make_unique<MmapModelLoader>(model_fd, model_offset,
@@ -124,6 +134,7 @@ std::unique_ptr<ModelLoader> CreateModelLoaderFromPath(absl::string_view path) {
124134
if (parts.size() != 4 || !absl::SimpleAtoi(parts[1], &read_fd) ||
125135
!absl::SimpleAtoi(parts[2], &write_fd) ||
126136
!absl::SimpleAtoi(parts[3], &model_size)) {
137+
TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "Failed to parse model path: %s", path);
127138
return nullptr;
128139
}
129140
// If set, close the write pipe for the read process / thread.

tensorflow/lite/tools/model_loader.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ namespace tools {
3434
// Class to load the Model.
3535
class ModelLoader {
3636
public:
37-
virtual ~ModelLoader() {}
37+
virtual ~ModelLoader() = default;
3838

3939
// Return whether the model is loaded successfully.
4040
virtual bool Init();

0 commit comments

Comments
 (0)