Files
wehub-resource-sync 8a852e4b4e
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:14:16 +08:00

441 lines
16 KiB
C++

/* Copyright 2023 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.
==============================================================================*/
#include "tensorflow/tools/proto_splitter/merge.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "riegeli/base/object.h" // from @riegeli
#include "riegeli/bytes/fd_reader.h" // from @riegeli
#include "riegeli/records/record_reader.h" // from @riegeli
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/file_system_helper.h"
#include "tensorflow/tools/proto_splitter/cc/util.h"
#include "tensorflow/tools/proto_splitter/chunk.pb.h"
#include "tsl/platform/errors.h"
#include "tsl/platform/protobuf.h"
#include "tsl/platform/statusor.h"
namespace tensorflow::tools::proto_splitter {
using ::tensorflow::proto_splitter::ChunkedField;
using ::tensorflow::proto_splitter::ChunkedMessage;
using ::tensorflow::proto_splitter::ChunkInfo;
using ::tensorflow::proto_splitter::ChunkMetadata;
using ::tensorflow::proto_splitter::FieldIndex;
using tools::proto_splitter::GetChunkMetadata;
using tools::proto_splitter::GetRiegeliReader;
using tools::proto_splitter::OnlyContainsPb;
using tsl::protobuf::FieldDescriptor;
using tsl::protobuf::Message;
using tsl::protobuf::Reflection;
absl::Status Merger::Merge(const std::vector<std::unique_ptr<Message>>& chunks,
const ChunkedMessage& chunked_message,
Message* merged_message) {
riegeli::RecordReader<riegeli::FdReader<>> null_reader{riegeli::kClosed};
if (chunked_message.has_chunk_index()) {
// Chunks referenced by fields should be merged into the parent chunk.
merged_message->MergeFrom(*chunks[chunked_message.chunk_index()].get());
}
// Use each chunked_field within the chunked_message to merge its
// corresponding chunk into merged_message.
for (const auto& chunked_field : chunked_message.chunked_fields()) {
absl::Status s = ProcessField(chunked_field, merged_message, {}, chunks,
null_reader, MergerOp::MERGE);
if (!s.ok()) return s;
}
return absl::OkStatus();
}
absl::Status Merger::Read(std::string prefix, Message* merged_message) {
uint64_t start_time = Env::Default()->NowMicros();
TF_ASSIGN_OR_RETURN(bool only_contains_pb, OnlyContainsPb(prefix));
if (only_contains_pb) {
return ReadPb(absl::StrCat(prefix, ".pb"), merged_message);
}
// Create riegeli reader for file.cpb
TF_ASSIGN_OR_RETURN(auto reader,
GetRiegeliReader(absl::StrCat(prefix, ".cpb")));
auto read_metadata = GetChunkMetadata(reader);
if (!read_metadata.ok()) {
reader.Close();
return absl::FailedPreconditionError(
absl::StrCat("Couldn't read ChunkMetadata from chunked proto.\n",
read_metadata.status().ToString()));
}
ChunkMetadata chunk_metadata = read_metadata.value();
std::vector<ChunkInfo> chunks_info = std::vector<ChunkInfo>(
chunk_metadata.chunks().begin(), chunk_metadata.chunks().end());
// Read the remaining chunks.
absl::Status s =
ReadFields(chunk_metadata.message(), reader, chunks_info, merged_message);
reader.Close();
uint64_t end_time = Env::Default()->NowMicros();
LOG(INFO) << "Finished reading and merging chunked proto, took "
<< HumanReadableDuration(end_time - start_time) << ".";
return s;
}
absl::Status Merger::ReadPartial(absl::string_view prefix,
const ChunkMetadata& chunk_metadata,
Message* merged_message) {
uint64_t start_time = Env::Default()->NowMicros();
TF_ASSIGN_OR_RETURN(bool only_contains_pb, OnlyContainsPb(prefix));
if (only_contains_pb) {
return absl::FailedPreconditionError(
absl::StrCat("Attempting to read part of a chunked proto .cpb file, "
"but only found a regular proto: ",
prefix, ".pb"));
}
// Create riegeli reader for file.cpb
TF_ASSIGN_OR_RETURN(auto reader,
GetRiegeliReader(absl::StrCat(prefix, ".cpb")));
std::vector<ChunkInfo> chunks_info = std::vector<ChunkInfo>(
chunk_metadata.chunks().begin(), chunk_metadata.chunks().end());
// Read the remaining chunks.
absl::Status s =
ReadFields(chunk_metadata.message(), reader, chunks_info, merged_message);
reader.Close();
uint64_t end_time = Env::Default()->NowMicros();
LOG(INFO) << "Finished reading and merging chunked proto, took "
<< HumanReadableDuration(end_time - start_time) << ".";
return s;
}
absl::Status Merger::ReadChunkedFromString(
absl::string_view data, tsl::protobuf::Message* merged_message) {
uint64_t start_time = Env::Default()->NowMicros();
// Create riegeli string reader.
TF_ASSIGN_OR_RETURN(auto reader, GetRiegeliStringReader(data));
auto read_metadata = GetChunkMetadata(reader);
if (!read_metadata.ok()) {
reader.Close();
return absl::FailedPreconditionError(
absl::StrCat("Couldn't read ChunkMetadata from chunked proto.\n",
read_metadata.status().ToString()));
}
ChunkMetadata chunk_metadata = read_metadata.value();
std::vector<ChunkInfo> chunks_info = std::vector<ChunkInfo>(
chunk_metadata.chunks().begin(), chunk_metadata.chunks().end());
// Read the remaining chunks.
absl::Status s =
ReadFields(chunk_metadata.message(), reader, chunks_info, merged_message);
reader.Close();
uint64_t end_time = Env::Default()->NowMicros();
LOG(INFO) << "Finished reading and merging chunked proto, took "
<< HumanReadableDuration(end_time - start_time) << ".";
return s;
}
absl::Status Merger::ReadPb(const std::string& pb_file,
Message* merged_message) {
uint64_t start_time = Env::Default()->NowMicros();
TF_ASSIGN_OR_RETURN(bool file_exists,
internal::FileExists(Env::Default(), pb_file));
if (!file_exists)
return absl::NotFoundError(absl::StrCat("File not found: ", pb_file));
LOG(INFO) << "Reading binary proto from " << pb_file;
auto ret = ReadBinaryProto(Env::Default(), pb_file, merged_message);
uint64_t end_time = Env::Default()->NowMicros();
LOG(INFO) << "Finished reading binary proto, took "
<< HumanReadableDuration(end_time - start_time) << ".";
return ret;
}
namespace {
bool CompareMapKey(const FieldIndex::MapKey& key1,
const FieldIndex::MapKey& key2) {
if (key1.type_case() != key2.type_case()) {
return key1.type_case() < key2.type_case();
}
switch (key1.type_case()) {
case FieldIndex::MapKey::TypeCase::kS:
return key1.s() < key2.s();
case FieldIndex::MapKey::TypeCase::kBoolean:
return key1.boolean() < key2.boolean();
case FieldIndex::MapKey::TypeCase::kUi32:
return key1.ui32() < key2.ui32();
case FieldIndex::MapKey::TypeCase::kUi64:
return key1.ui64() < key2.ui64();
case FieldIndex::MapKey::TypeCase::kI32:
return key1.i32() < key2.i32();
case FieldIndex::MapKey::TypeCase::kI64:
return key1.i64() < key2.i64();
default:
return false;
}
}
bool CompareFieldIndex(const FieldIndex& tag1, const FieldIndex& tag2) {
if (tag1.kind_case() != tag2.kind_case()) {
return tag1.kind_case() < tag2.kind_case();
}
switch (tag1.kind_case()) {
case FieldIndex::KindCase::kField:
return tag1.field() < tag2.field();
case FieldIndex::KindCase::kIndex:
return tag1.index() < tag2.index();
case FieldIndex::KindCase::kMapKey:
return CompareMapKey(tag1.map_key(), tag2.map_key());
default:
return false;
}
}
} // namespace
template <typename RecordReader>
absl::Status Merger::ReadFields(const ChunkedMessage& chunked_message,
RecordReader& reader,
const std::vector<ChunkInfo>& chunks_info,
tsl::protobuf::Message* merged_message) {
if (chunked_message.has_chunk_index()) {
// Chunks referenced by fields should be merged into the parent chunk.
TF_ASSIGN_OR_RETURN(
std::string chunk,
ReadChunk(reader, chunks_info[chunked_message.chunk_index()]));
if (!merged_message->MergeFromString(chunk)) {
return absl::FailedPreconditionError(
"Couldn't merge chunk into message.");
}
}
// Sort the chunked_fields by depth and index.
// For example, this ensures that GraphDef.library is merged before
// GraphDef.library.function[0], which will be merged before
// GraphDef.library.function[1].
std::vector<ChunkedField> chunked_fields(
chunked_message.chunked_fields().begin(),
chunked_message.chunked_fields().end());
std::sort(
chunked_fields.begin(), chunked_fields.end(),
// Ensure proper strict weak ordering.
[](const ChunkedField& cf1, const ChunkedField& cf2) {
int tag_depth =
std::min(cf1.field_tag().size(), cf2.field_tag().size());
for (int depth = 0; depth < tag_depth; ++depth) {
const FieldIndex& tag1 = cf1.field_tag()[depth];
const FieldIndex& tag2 = cf2.field_tag()[depth];
bool is_equiv = false;
if (tag1.kind_case() == tag2.kind_case()) {
if (tag1.kind_case() == FieldIndex::KindCase::kField) {
is_equiv = (tag1.field() == tag2.field());
} else if (tag1.kind_case() == FieldIndex::KindCase::kIndex) {
is_equiv = (tag1.index() == tag2.index());
} else if (tag1.kind_case() == FieldIndex::KindCase::kMapKey) {
is_equiv = !CompareMapKey(tag1.map_key(), tag2.map_key()) &&
!CompareMapKey(tag2.map_key(), tag1.map_key());
} else {
is_equiv = true; // KIND_NOT_SET
}
}
if (!is_equiv) {
return CompareFieldIndex(tag1, tag2);
}
}
if (cf1.field_tag().size() == cf2.field_tag().size()) {
// If the fields are the same, merge the earlier chunks first.
return cf1.message().chunk_index() < cf2.message().chunk_index();
}
return cf1.field_tag().size() < cf2.field_tag().size();
});
// Use each chunked_field within the chunked_message to merge its
// corresponding chunk into merged_message.
for (const auto& chunked_field : chunked_fields) {
absl::Status s = ProcessField(chunked_field, merged_message, chunks_info,
{}, reader, MergerOp::READ);
if (!s.ok()) return s;
}
return absl::OkStatus();
}
template <typename RecordReader>
absl::Status Merger::ProcessField(
const ChunkedField& chunked_field, Message* merged_message,
const std::vector<ChunkInfo>& chunks_info,
const std::vector<std::unique_ptr<Message>>& chunks, RecordReader& reader,
MergerOp op) {
std::string chunk;
switch (op) {
case MergerOp::READ: {
TF_ASSIGN_OR_RETURN(
chunk, ReadChunk(reader,
chunks_info[chunked_field.message().chunk_index()]));
break;
}
case MergerOp::MERGE: {
chunk =
chunks[chunked_field.message().chunk_index()]->SerializeAsString();
break;
}
}
if (chunked_field.field_tag().empty()) {
// Chunk is not a field within the parent, but instead a portion of the
// parent itself. Needs to be concatenated.
merged_message->MergeFromString(chunk);
return absl::OkStatus();
}
uint64_t field_index;
Message* curr_message = merged_message;
// Use field tags to navigate the merged_message, constructing necessary
// fields along the way.
TF_ASSIGN_OR_RETURN(const std::vector<Field> fields,
GetFieldTypes(chunked_field.field_tag()));
const FieldDescriptor* field_desc = nullptr;
for (const auto& field : fields) {
merged_message = curr_message;
field_desc = merged_message->GetDescriptor()->FindFieldByNumber(
std::get<int>(field.first));
if (field_desc == nullptr) {
return absl::FailedPreconditionError(
absl::StrCat("Field with number ", std::get<int>(field.first),
" not found in message descriptor.",
merged_message->GetDescriptor()->full_name(), ".\n"));
}
auto res = GetMutableField(merged_message, field);
if (!res.ok()) {
if (!absl::IsNotFound(res.status())) return res.status();
// Add missing field.
if (field_desc->is_map()) {
TF_RETURN_IF_ERROR(
AddMapEntry(curr_message, field_desc, field.second.value()));
res = GetMutableField(curr_message, field);
} else {
curr_message->GetReflection()->AddMessage(curr_message, field_desc);
res = GetMutableField(curr_message, field);
}
}
auto [parent, mutable_field, mutable_field_index] = res.value();
if (mutable_field->is_repeated() && mutable_field_index != -1) {
field_index = mutable_field_index;
// Update merged_message to repeated element.
curr_message = parent->GetReflection()->MutableRepeatedMessage(
parent, mutable_field, std::max(0, mutable_field_index));
if (mutable_field->is_map()) {
// messages of map type have the value at field #2
field_desc = mutable_field->message_type()->FindFieldByNumber(2);
merged_message = curr_message;
curr_message = curr_message->GetReflection()->MutableMessage(
curr_message, field_desc);
}
} else if (mutable_field->type() == FieldDescriptor::Type::TYPE_MESSAGE) {
// Update merged_message.
curr_message =
parent->GetReflection()->MutableMessage(parent, mutable_field);
}
}
// merged_message now points to the Message whose field (described by
// field_desc) will be added/set.
const Reflection* reflection = merged_message->GetReflection();
if (field_desc->is_repeated()) {
// field may contain multiple elements
auto message_callback = [&reflection, &merged_message, &field_index, &op,
&chunks, &chunked_field, &reader, &chunks_info,
&field_desc]() -> absl::Status {
for (int _ = reflection->FieldSize(*merged_message, field_desc);
_ <= field_index; _++) {
reflection->AddMessage(merged_message, field_desc);
}
switch (op) {
case MergerOp::MERGE:
TF_RETURN_IF_ERROR(
Merge(chunks, chunked_field.message(),
reflection->MutableRepeatedMessage(
merged_message, field_desc, field_index)));
break;
case MergerOp::READ:
TF_RETURN_IF_ERROR(
ReadFields(chunked_field.message(), reader, chunks_info,
reflection->MutableRepeatedMessage(
merged_message, field_desc, field_index)));
break;
default:
return absl::InternalError("Encountered unknown MergerOp.");
}
return absl::OkStatus();
};
TF_RETURN_IF_ERROR(SetRepeatedFieldElement(
merged_message, field_desc, field_index, chunk, message_callback));
} else {
// regular field
auto message_callback = [&reflection, &merged_message, &op, &chunks,
&chunked_field, &reader, &chunks_info,
&field_desc]() -> absl::Status {
switch (op) {
case MergerOp::MERGE:
TF_RETURN_IF_ERROR(
Merge(chunks, chunked_field.message(),
reflection->MutableMessage(merged_message, field_desc)));
break;
case MergerOp::READ:
TF_RETURN_IF_ERROR(ReadFields(
chunked_field.message(), reader, chunks_info,
reflection->MutableMessage(merged_message, field_desc)));
break;
default:
return absl::InternalError("Encountered unknown MergerOp.");
}
return absl::OkStatus();
};
TF_RETURN_IF_ERROR(
SetFieldElement(merged_message, field_desc, chunk, message_callback));
}
return absl::OkStatus();
}
} // namespace tensorflow::tools::proto_splitter