Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
faf57fe
lite: fix memory leak in TFLiteSavedModelConverterV2.convert()
Jul 23, 2026
44f8b8c
Address reviewer feedback: fix test save signature and clarify debug …
Aug 4, 2026
88c4522
Merge branch 'master' into fix/tflite-converter-memory-leak
DakshPrajapati-afk Aug 10, 2026
fc0683d
Fix the CMake build of the Metal delegate after its sources moved to .mm
cocoa-xu Aug 21, 2026
d51d7e3
Fix a flatc race and a source-tree write in the Metal delegate's CMak…
cocoa-xu Aug 21, 2026
fbe17ed
Remove the Metal supplementary libraries that never built
cocoa-xu Aug 21, 2026
d6abed3
Initial upload.
wangw-1991 Jun 26, 2026
71f7bc8
Add test.
wangw-1991 Jul 14, 2026
dc43730
Resolve comments.
wangw-1991 Aug 24, 2026
6314d19
Add an OSS-Fuzz target for the TensorFlow Lite interpreter
elsh04 Aug 26, 2026
446a09e
Check the arena budget before accumulating, not after
elsh04 Aug 26, 2026
397406f
Require the TFLite TRANSPOSE permutation to be a bijection
elsh04 Aug 26, 2026
61020a0
Validate SLICE bounds when the input extent is dynamic
elsh04 Aug 26, 2026
8ac1138
Use a bitmask for the permutation check
elsh04 Aug 26, 2026
e258a88
Add dynamic-input slice tests and order the rank check first
elsh04 Aug 26, 2026
15acef5
Bypass delegates in DynamicInputSliceOpModel
elsh04 Aug 26, 2026
a779e3e
Merge pull request #122082 from wangw-1991:fix_add_overflow
tensorflower-gardener Aug 27, 2026
2acffd7
Merge pull request #123818 from DakshPrajapati-afk:fix/tflite-convert…
tensorflower-gardener Aug 27, 2026
68fc8b2
Implement CpuPjRtCompiler::DeserializeExecutable and remove
pschuh Aug 27, 2026
6db7a6f
[IFRT IR] Catch a new possible error message in invalid programs that…
ICGog Aug 27, 2026
5bae75d
Merge pull request #125809 from cocoa-xu:cx/fix-metal-flatc
tensorflower-gardener Aug 27, 2026
cf6867d
Merge pull request #126109 from endorphin13:tflite-oss-fuzz-target
tensorflower-gardener Aug 27, 2026
327ef3d
[XLA:GPU] Add DegenerateDimensionRewriter to GPU pipeline.
olegshyshkov Aug 27, 2026
d9a8da7
Merge pull request #126177 from endorphin13:slice-validate-dynamic-input
tensorflower-gardener Aug 27, 2026
a433858
Add PjRtExecutable::GetHloModule() which returns a single hlo module for
pschuh Aug 27, 2026
5b9e491
Revert matrix input scaling in EighExpander and TpuEighExpander.
tensorflower-gardener Aug 27, 2026
69792ea
[XLA:CPU] Exclude the patterns that attempt to insert memref.subview.
pifon2a Aug 27, 2026
ce016ec
[XLA:TSL] Add kDimensions and kType stat types to XPlane schema.
tensorflower-gardener Aug 27, 2026
87a1904
[XLA][Verifier]Remove output-to-operand aliasing check for call instr…
tensorflower-gardener Aug 27, 2026
8aaa9f5
Merge pull request #126162 from endorphin13:transpose-validate-permut…
tensorflower-gardener Aug 27, 2026
5c0a1db
[XLA:GPU] Support arbitrary broadcast dimensions in cuDNN convolution…
derdrdirk Aug 27, 2026
b8c608b
Scope async pipelined while loop offset colocation to pipelined while…
amitsabne1 Aug 27, 2026
7711f82
PR #47522: [ROCm] Move CI to rocm 10
draganmladjenovic Aug 27, 2026
c998cc9
Fix synchronization problems in RamFileBlockCache.
tensorflower-gardener Aug 27, 2026
9c87c11
Do not allow batch dimensions to be transposed in a way that interlea…
vwbaker Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ void RamFileBlockCache::UpdateLRU(const Key& key,
// in the cache, and our current block is not block size, this likely means
// we have inconsistent state within the cache. Note: it's possible some
// incomplete reads may still go undetected.
// Read of block->data allowed because block_state==FINISHED here.
if (block->data.size() < block_size_) {
Key fmax = std::make_pair(key.first, std::numeric_limits<size_t>::max());
auto fcmp = block_map_.upper_bound(fmax);
Expand All @@ -105,6 +106,8 @@ void RamFileBlockCache::UpdateLRU(const Key& key,
return TF_SetStatus(status, TF_OK, "");
}

// Attempt to fetch data with the given key into block.
// Return with *status TF_OK only if block->state==FINISHED.
void RamFileBlockCache::MaybeFetch(const Key& key,
const std::shared_ptr<Block>& block,
TF_Status* status) {
Expand All @@ -113,10 +116,12 @@ void RamFileBlockCache::MaybeFetch(const Key& key,
// Perform this action in a cleanup callback to avoid locking mu_ after
// locking block->mu.
if (downloaded_block) {
// downloaded_block == (block->state==FINISHED), so reads of block->data
// are legal here
absl::MutexLock l(mu_);
// Do not update state if the block is already to be evicted.
if (block->timestamp != 0) {
// Use capacity() instead of size() to account for all memory
// Use capacity() instead of size() to account for all memory
// used by the cache.
cache_size_ += block->data.capacity();
// Put to beginning of LRA list.
Expand All @@ -137,6 +142,7 @@ void RamFileBlockCache::MaybeFetch(const Key& key,
// TF_FALLTHROUGH_INTENDED
case FetchState::CREATED:
block->state = FetchState::FETCHING;
// Thread may modify block->data block->state==FETCHING.
block->mu.unlock(); // Release the lock while making the API call.
block->data.clear();
block->data.resize(block_size_, 0);
Expand Down Expand Up @@ -202,6 +208,8 @@ int64_t RamFileBlockCache::Read(const std::string& filename, size_t offset,
}
MaybeFetch(key, block, status);
if (TF_GetCode(status) != TF_OK) return -1;
// At this point, block->state==FINISHED, since MaybeFetch() yielded TF_OK.
// Therefore, it is legal to access block->data.
UpdateLRU(key, block, status);
if (TF_GetCode(status) != TF_OK) return -1;
// Copy the relevant portion of the block into the result buffer.
Expand Down Expand Up @@ -282,10 +290,14 @@ void RamFileBlockCache::Prune() {

void RamFileBlockCache::Flush() {
absl::MutexLock lock(mu_);
block_map_.clear();
lru_list_.clear();
lra_list_.clear();
cache_size_ = 0;
// This code mirrors that in RemoveFile_Locked(),
// but iterates over the entire cache.
auto it = block_map_.begin();
while (it != block_map_.end()) {
auto next = std::next(it);
RemoveBlock(it);
it = next;
}
}

void RamFileBlockCache::RemoveFile(const std::string& filename) {
Expand All @@ -309,7 +321,17 @@ void RamFileBlockCache::RemoveBlock(BlockMap::iterator entry) {
entry->second->timestamp = 0;
lru_list_.erase(entry->second->lru_iterator);
lra_list_.erase(entry->second->lra_iterator);
cache_size_ -= entry->second->data.capacity();

// Adjust the cache_size_ by the size of the block.
// RemoveBlock() can be called by Flush() on blocks that are not yet FINISHED.
// Only finished blocks are counted in cache_size_, and it would be a race
// to read the data member of a block that is not yet FINISHED.
entry->second->mu.lock();
if (entry->second->state == FetchState::FINISHED) {
cache_size_ -= entry->second->data.capacity();
}
entry->second->mu.unlock();

block_map_.erase(entry);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,10 @@ class RamFileBlockCache {
/// Thread safety:
/// The iterator and timestamp fields should only be accessed while holding
/// the block-cache-wide mu_ instance variable. The state variable should only
/// be accessed while holding the Block's mu lock. The data vector should only
/// be accessed after state == FINISHED, and it should never be modified.
/// be accessed while holding the Block's mu lock. Once state==FINISHED,
/// the data vector may be read and may not be written; before
/// state==FINISHED, the data vector may be accessed only by the thread that
/// set state==FETCHING.
///
/// In order to prevent deadlocks, never grab the block-cache-wide mu_ lock
/// AFTER grabbing any block's mu lock. It is safe to grab mu without locking
Expand Down Expand Up @@ -212,13 +214,13 @@ class RamFileBlockCache {
void Prune() ABSL_LOCKS_EXCLUDED(mu_);

bool BlockNotStale(const std::shared_ptr<Block>& block)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_) ABSL_LOCKS_EXCLUDED(block->mu);

/// Look up a Key in the block cache.
std::shared_ptr<Block> Lookup(const Key& key) ABSL_LOCKS_EXCLUDED(mu_);

void MaybeFetch(const Key& key, const std::shared_ptr<Block>& block,
TF_Status* status) ABSL_LOCKS_EXCLUDED(mu_);
TF_Status* status) ABSL_LOCKS_EXCLUDED(mu_, block->mu);

/// Trim the block cache to make room for another entry.
void Trim() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -610,5 +610,34 @@ TEST(RamFileBlockCacheTest, Flush) {
EXPECT_EQ(calls, 2);
}

// This test case reproduces a bug, now fixed, in which
// RamFileBlockCache::Flush() used to clear the cache, invalidating the
// lra_iterator field, without resetting the timestamp fields of the blocks.
// This was a problem for the "reconcile_state" cleanup callback in
// MaybeFetch(), which assumes that if the timestamp field is non-zero, the
// lra_iterator is valid. Later versions of Flush() should handle this case
// correctly.
TEST(RamFileBlockCacheTest, FlushDuringFetch) {
// The delays are sized to exceed the delay in RamFileBlockCache::Prune().
auto delayed_fetcher = [](const std::string& filename, size_t offset,
size_t n, char* buffer,
TF_Status* status) -> int64_t {
memset(buffer, 'x', n);
TF_SetStatus(status, TF_OK, "");
Env::Default()->SleepForMicroseconds(10 * 1000 * 1000);
return n;
};
tf_gcs_filesystem::RamFileBlockCache cache(16, 32, /*max_staleness=*/20,
delayed_fetcher);
std::vector<char> out;
std::unique_ptr<Thread> flush_thread(
Env::Default()->StartThread({}, "delayed_flush", [&cache] {
Env::Default()->SleepForMicroseconds(5 * 1000 * 1000);
cache.Flush();
}));
TF_EXPECT_OK(ReadCache(&cache, "", 0, 16, &out));
Env::Default()->SleepForMicroseconds(5 * 1000 * 1000);
}

} // namespace
} // namespace tensorflow
59 changes: 14 additions & 45 deletions tensorflow/lite/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -424,14 +424,14 @@ if(TFLITE_ENABLE_GPU)
enable_language(OBJCXX)
list(APPEND TFLITE_DELEGATES_METAL_SRCS
${TFLITE_SOURCE_DIR}/delegates/gpu/metal_delegate.mm
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/buffer.cc
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/buffer.mm
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/buffer_convert.mm
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/common.mm
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/compute_task.cc
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/inference_context.cc
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/metal_arguments.cc
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/metal_device.cc
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/metal_spatial_tensor.cc
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/compute_task.mm
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/inference_context.mm
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/metal_arguments.mm
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/metal_device.mm
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/metal_spatial_tensor.mm
)
add_library(metal_delegate STATIC
${TFLITE_DELEGATES_METAL_SRCS}
Expand All @@ -440,6 +440,7 @@ if(TFLITE_ENABLE_GPU)
${CMAKE_BINARY_DIR}/abseil-cpp
${CMAKE_BINARY_DIR}/flatbuffers/include
PRIVATE ${TENSORFLOW_SOURCE_DIR}
PRIVATE ${PROJECT_BINARY_DIR}
)
#
# generate flatbuffers header for inference_context
Expand All @@ -449,54 +450,22 @@ if(TFLITE_ENABLE_GPU)
else()
set(FLATC flatc)
endif()
set(METAL_GENERATED_DIR ${PROJECT_BINARY_DIR}/tensorflow/lite/delegates/gpu/metal)
add_custom_command(
OUTPUT ${TFLITE_SOURCE_DIR}/delegates/gpu/metal/inference_context_generated.h
OUTPUT ${METAL_GENERATED_DIR}/inference_context_generated.h
COMMAND ${CMAKE_COMMAND} -E make_directory ${METAL_GENERATED_DIR}
COMMAND ${FLATC} --scoped-enums
-I ${TENSORFLOW_SOURCE_DIR}
-o ${TFLITE_SOURCE_DIR}/delegates/gpu/metal
-o ${METAL_GENERATED_DIR}
-c ${TFLITE_SOURCE_DIR}/delegates/gpu/metal/inference_context.fbs
DEPENDS ${FLATC_TARGET}
${TFLITE_SOURCE_DIR}/delegates/gpu/metal/inference_context.fbs
)
add_custom_target(
inference_context_cc_fbs
DEPENDS ${TFLITE_SOURCE_DIR}/delegates/gpu/metal/inference_context_generated.h
DEPENDS ${METAL_GENERATED_DIR}/inference_context_generated.h
)
add_dependencies(metal_delegate inference_context_cc_fbs)
#
# supplementary libraries for libmetal_delegate
#
list(APPEND CC_SRCS
buffer
compute_task
inference_context
metal_arguments
metal_device
metal_spatial_tensor
)
SET(METAL_DELEGATE_PATH ${TFLITE_SOURCE_DIR}/delegates/gpu/metal/)

foreach(lib_name ${CC_SRCS})
set_source_files_properties(${METAL_DELEGATE_PATH}${lib_name}.cc PROPERTIES LANGUAGE OBJCXX)
add_library("${lib_name}" STATIC ${METAL_DELEGATE_PATH}${lib_name}.cc)
target_include_directories("${lib_name}" PUBLIC
${CMAKE_BINARY_DIR}/abseil-cpp
${CMAKE_BINARY_DIR}/flatbuffers/include
)
set_target_properties(${lib_name} PROPERTIES LINKER_LANGUAGE OBJCXX)
target_link_libraries(${lib_name})
endforeach()

list(APPEND MM_SRCS
buffer_convert
common
)
foreach(lib_name ${MM_SRCS})
add_library("${lib_name}" STATIC ${METAL_DELEGATE_PATH}${lib_name}.mm)
target_include_directories("${lib_name}" PUBLIC
${CMAKE_BINARY_DIR}/abseil-cpp
${CMAKE_BINARY_DIR}/flatbuffers/include
)
target_link_libraries(${lib_name})
endforeach()
endif()
list(APPEND TFLITE_TARGET_PUBLIC_OPTIONS "-DCL_DELEGATE_NO_GL" "-DEGL_NO_X11")
list(APPEND TFLITE_TARGET_DEPENDENCIES
Expand Down
22 changes: 22 additions & 0 deletions tensorflow/lite/fuzzing/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Fuzzing harnesses for the TensorFlow Lite runtime.

load(
"//tensorflow/security/fuzzing:tf_fuzzing.bzl",
"tf_cc_fuzz_test",
)

package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:private"],
licenses = ["notice"],
)

tf_cc_fuzz_test(
name = "interpreter_fuzz",
srcs = ["interpreter_fuzz.cc"],
deps = [
"//tensorflow/lite:framework",
"//tensorflow/lite/core:framework",
"//tensorflow/lite/kernels:builtin_ops",
],
)
99 changes: 99 additions & 0 deletions tensorflow/lite/fuzzing/interpreter_fuzz.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* Copyright 2026 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

// Fuzzes the TensorFlow Lite interpreter end to end: an arbitrary buffer is
// verified as a flatbuffer model, an interpreter is built for it, tensors are
// allocated, inputs are filled deterministically, and the graph is invoked.
//
// This exercises the builtin kernel implementations, the arena planner and the
// shape-propagation paths, none of which previously had OSS-Fuzz coverage.

#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <string>

#include "fuzztest/fuzztest.h"
#include "tensorflow/lite/core/interpreter.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/model_builder.h"
#include "tensorflow/lite/kernels/register.h"

namespace tflite {
namespace fuzzing {
namespace {

// Keep the fuzzer inside the OSS-Fuzz memory budget. Models and arenas larger
// than this are not interesting: they exercise the allocator, not the kernels.
constexpr size_t kMaxModelBytes = 1 << 20; // 1 MiB
constexpr size_t kMaxArenaBytes = 1 << 26; // 64 MiB

void FuzzInterpreter(const std::string& model_bytes) {
if (model_bytes.size() < 8 || model_bytes.size() > kMaxModelBytes) {
return;
}

// VerifyAndBuildFromBuffer applies tflite::VerifyModelBuffer first, so
// structurally invalid buffers are rejected cheaply.
std::unique_ptr<FlatBufferModel> model =
FlatBufferModel::VerifyAndBuildFromBuffer(model_bytes.data(),
model_bytes.size());
if (model == nullptr) {
return;
}

// Delegates are excluded so the fuzzer exercises the reference and optimized
// CPU kernels rather than a delegate's own implementation.
ops::builtin::BuiltinOpResolverWithoutDefaultDelegates resolver;
std::unique_ptr<Interpreter> interpreter;
if (InterpreterBuilder(*model, resolver)(&interpreter) != kTfLiteOk ||
interpreter == nullptr) {
return;
}

if (interpreter->AllocateTensors() != kTfLiteOk) {
return;
}

size_t total_bytes = 0;
for (const int tensor_index : interpreter->inputs()) {
TfLiteTensor* tensor = interpreter->tensor(tensor_index);
if (tensor == nullptr || tensor->data.raw == nullptr) {
continue;
}
// String tensors own a dynamic buffer with its own layout; writing raw
// bytes into it would corrupt the interpreter rather than the kernel under
// test.
if (tensor->type == kTfLiteString || tensor->type == kTfLiteResource ||
tensor->type == kTfLiteVariant) {
return;
}
// Check before accumulating so the sum itself cannot wrap.
if (tensor->bytes > kMaxArenaBytes ||
total_bytes > kMaxArenaBytes - tensor->bytes) {
return;
}
total_bytes += tensor->bytes;
std::memset(tensor->data.raw, 1, tensor->bytes);
}

interpreter->Invoke();
}
FUZZ_TEST(TfLiteFuzz, FuzzInterpreter);

} // namespace
} // namespace fuzzing
} // namespace tflite
Loading
Loading