Spaces:
Runtime error
Runtime error
/* | |
* Copyright 2021 Google LLC | |
* | |
* 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. | |
*/ | |
// Various utility functions related to reading and writing files, vectors, etc. | |
// Would be much simpler if Android supported File. | |
namespace csrblocksparse { | |
template <typename T> | |
void unzip(int64_t st_size, std::vector<T>* array) { | |
ZLib z; | |
z.SetGzipHeaderMode(); | |
if (z.HasGzipHeader(reinterpret_cast<char*>(array->data()), st_size)) { | |
const std::size_t kMaxBufferSize = 1 << 27; // 128MB | |
Bytef* dest; | |
uLongf dest_len = kMaxBufferSize; | |
CHECK_EQ(z.UncompressGzipAndAllocate(&dest, &dest_len, | |
(Bytef*)array->data(), st_size), | |
Z_OK); | |
CHECK_EQ(dest_len % sizeof(T), 0); | |
array->assign(reinterpret_cast<T*>(dest), | |
reinterpret_cast<T*>(dest + dest_len)); | |
free(dest); | |
} else { | |
CHECK_EQ(st_size % sizeof(T), 0); | |
} | |
} | |
// Reads a file that contains an array of a single POD type. Eventually we | |
// will replace serializiation with protos, but for now this is the easiest way | |
// to interface with the rest of the pipeline. | |
// | |
// StatusOr might be preferred but does not compile on ARM. | |
// |DiskType| and |ElemType| template types have no effect in this function | |
// version and are only used to handle fixed_type disk storage. | |
template <typename T, typename DiskType = T, typename ElemType = T> | |
typename std::enable_if<!csrblocksparse::IsFixed16Type<DiskType>::value, | |
absl::Status>::type | |
ReadArrayFromFile(const std::string& file_name, std::vector<T>* array, | |
const std::string& path = "/data/local/tmp/") { | |
int64_t length = 0; | |
const absl::Status status = | |
detail::ReadArrayIfstream(file_name, path, array, &length); | |
if (!status.ok()) { | |
return status; | |
} | |
unzip(length, array); | |
return absl::OkStatus(); | |
} | |
// If the metatype |DiskType| is of fixed16_type, we load int16_ts from disk and | |
// construct |ElemType| from them. |ElemType| is necessary because we need to | |
// know the mantissa/exponent bit split before casting to float. We need a | |
// separate function template for fixed rather than an if block because the | |
// compiler will complain bfloat not having an int16_t constructor. | |
template <typename T, typename DiskType, typename ElemType> | |
typename std::enable_if<std::is_same<T, float>::value && | |
csrblocksparse::IsFixed16Type<DiskType>::value, | |
absl::Status>::type | |
ReadArrayFromFile(const std::string& file_name, std::vector<T>* array, | |
const std::string& path = "/data/local/tmp/") { | |
std::vector<int16_t> disk_values; | |
SPARSE_MATMUL_RETURN_IF_ERROR( | |
ReadArrayFromFile(file_name, &disk_values, path)); | |
array->resize(disk_values.size()); | |
std::transform( | |
disk_values.begin(), disk_values.end(), array->begin(), | |
[](int16_t disk_value) { return static_cast<T>(ElemType(disk_value)); }); | |
return absl::OkStatus(); | |
} | |
// Writes a vector to a binary file. Eventually serialization will be handled | |
// with protos. | |
template <typename T> | |
absl::Status WriteArrayToFile(const std::vector<T>& array, | |
const std::string& file_name, | |
std::string path = "/data/local/tmp/") { | |
path = (ghc::filesystem::path(path) / file_name).string(); | |
FILE* fp = fopen(path.c_str(), "wb"); | |
if (fp == nullptr) | |
return ErrnoToCanonicalStatus(errno, | |
absl::Substitute("Error opening $0", path)); | |
size_t write_count = fwrite(array.data(), sizeof(T), array.size(), fp); | |
if (write_count != array.size()) { | |
return ErrnoToCanonicalStatus( | |
errno, | |
absl::Substitute( | |
"Error writing array, only wrote $0 of $1 elements for file $2", | |
write_count, array.size(), path)); | |
} | |
SPARSE_MATMUL_RETURN_IF_ERROR(ErrnoToCanonicalStatus( | |
fclose(fp), absl::Substitute("Error closing $0", path))); | |
return absl::OkStatus(); | |
} | |
// Reads an entire layer that consists of weights, bias and mask as a | |
// SparseLinearLayer. Eventually this serialization will be handled with | |
// protos, but the rest of the system currently does naive serialization. | |
// | |
// StatusOr might be preferred but does not compile on ARM. | |
// | |
// Here |DiskWeightType| is the metatype used to store the weights, usually | |
// fixed16_type, float, or bfloat. | |
// For |DiskWeightType| = fixed16_type specialization, this loads a file with a | |
// "fixed16_weights.raw" suffix which stores int16_ts as its element datatype. | |
// The disk elements should match fixed16<WeightType::kExponentBits>. This cuts | |
// down disk storage of weights by | |
// >= half. For all other types it reads the weights as floats. | |
template <typename WeightType, typename RhsType, | |
typename DiskWeightType = float> | |
absl::Status LoadGenericLayer( | |
const std::string& prefix, bool zipped, const std::string& path, | |
float default_bias, | |
SparseLinearLayer<WeightType, RhsType>* sparse_linear_layer) { | |
std::string fixed_prefix = | |
csrblocksparse::IsFixed16Type<DiskWeightType>::value ? "fixed16_" : ""; | |
std::string extension = zipped ? ".gz" : ""; | |
std::string weight_name = | |
absl::StrCat(prefix, fixed_prefix, "weights.raw", extension); | |
std::string mask_name = absl::StrCat(prefix, "mask.raw", extension); | |
std::string bias_name = absl::StrCat(prefix, "bias.raw", extension); | |
std::vector<float> weight_vector; | |
std::vector<float> mask_vector; | |
std::vector<float> bias_vector; | |
const auto status = ReadArrayFromFile<float, DiskWeightType, WeightType>( | |
weight_name, &weight_vector, path); | |
SPARSE_MATMUL_RETURN_IF_ERROR(status); | |
SPARSE_MATMUL_RETURN_IF_ERROR( | |
ReadArrayFromFile(mask_name, &mask_vector, path)); | |
SPARSE_MATMUL_RETURN_IF_ERROR( | |
ReadArrayFromFile(bias_name, &bias_vector, path)); | |
CHECK(weight_vector.size() == mask_vector.size()) | |
<< "Weight and mask must be" | |
<< " the same size, weights: " << weight_vector.size() | |
<< " mask: " << mask_vector.size(); | |
CHECK(weight_vector.size() % bias_vector.size() == 0) | |
<< "Weights size must " | |
"be a multiple of the bias size. Weights: " | |
<< weight_vector.size() | |
<< " " | |
"bias: " | |
<< bias_vector.size() | |
<< " remainder: " << weight_vector.size() % bias_vector.size(); | |
int rows = bias_vector.size(); | |
int cols = weight_vector.size() / rows; | |
MaskedSparseMatrix<float> weights_masked(rows, cols, mask_vector.data(), | |
weight_vector.data()); | |
weights_masked.template CastWeights<WeightType>(); | |
using csrmatrix = CsrBlockSparseMatrix<WeightType, RhsType>; | |
csrmatrix weights(weights_masked); | |
// If the weights were not a multiple of the block size in rows, we need to | |
// expand the bias vector to match using the provided default_bias value. | |
bias_vector.resize(weights.rows(), default_bias); | |
using BiasType = typename TypeOfProduct<WeightType, RhsType>::type; | |
CacheAlignedVector<BiasType> bias(bias_vector); | |
*sparse_linear_layer = std::move(SparseLinearLayer<WeightType, RhsType>( | |
std::move(weights), std::move(bias))); | |
return absl::OkStatus(); | |
} | |
template <typename WeightType, typename RhsType, | |
typename DiskWeightType = float> | |
absl::Status LoadSparseLayer( | |
const std::string& prefix, bool zipped, | |
SparseLinearLayer<WeightType, RhsType>* sparse_linear_layer, | |
const std::string& path = "/data/local/tmp/") { | |
return LoadGenericLayer<WeightType, RhsType, DiskWeightType>( | |
prefix, zipped, path, 0.0f, sparse_linear_layer); | |
} | |
template <typename WeightType, typename RhsType, | |
typename DiskWeightType = float> | |
absl::Status LoadLogitLayer( | |
const std::string& prefix, bool zipped, const std::string& path, | |
SparseLinearLayer<WeightType, RhsType>* sparse_linear_layer) { | |
return LoadGenericLayer<WeightType, RhsType, DiskWeightType>( | |
prefix, zipped, path, std::numeric_limits<float>::lowest(), | |
sparse_linear_layer); | |
} | |
// Reads an entire layer that consists of weights, bias and mask as a | |
// MaskedLinearLayer. Eventually this serialization will be handled with | |
// protos, but the rest of the system currently does naive serialization. | |
// | |
// StatusOr might be preferred but does not compile on ARM. | |
template <typename T> | |
absl::Status LoadMaskedLayer(const std::string& prefix, bool zipped, | |
MaskedLinearLayer<T>* masked_sparse_matrix, | |
const std::string& path = "/data/local/tmp/") { | |
std::string extension = zipped ? ".gz" : ""; | |
std::string weight_name = absl::StrCat(prefix, "weights.raw", extension); | |
std::string mask_name = absl::StrCat(prefix, "mask.raw", extension); | |
std::string bias_name = absl::StrCat(prefix, "bias.raw", extension); | |
std::vector<float> weight_vector; | |
std::vector<float> mask_vector; | |
std::vector<float> bias_vector; | |
SPARSE_MATMUL_RETURN_IF_ERROR( | |
ReadArrayFromFile(weight_name, &weight_vector, path)); | |
SPARSE_MATMUL_RETURN_IF_ERROR( | |
ReadArrayFromFile(mask_name, &mask_vector, path)); | |
SPARSE_MATMUL_RETURN_IF_ERROR( | |
ReadArrayFromFile(bias_name, &bias_vector, path)); | |
CHECK(weight_vector.size() == mask_vector.size()) | |
<< "Weight and mask must be" | |
<< " the same size, weights: " << weight_vector.size() | |
<< " mask: " << mask_vector.size(); | |
CHECK(weight_vector.size() % bias_vector.size() == 0) | |
<< "Weights size must " | |
"be a multiple of the bias size. Weights: " | |
<< weight_vector.size() | |
<< " " | |
"bias: " | |
<< bias_vector.size() | |
<< " remainder: " << weight_vector.size() % bias_vector.size(); | |
int rows = bias_vector.size(); | |
int cols = weight_vector.size() / rows; | |
MaskedSparseMatrix<T> weights_masked(rows, cols, mask_vector.data(), | |
weight_vector.data()); | |
CacheAlignedVector<T> bias(bias_vector); | |
*masked_sparse_matrix = | |
MaskedLinearLayer<T>(std::move(weights_masked), std::move(bias)); | |
return absl::OkStatus(); | |
} | |
// Load a vector of POD into a CacheAlignedVector. | |
// | |
// StatusOr might be preferred but does not compile on ARM. | |
template <typename T> | |
absl::Status LoadVector(const std::string& file_name, | |
CacheAlignedVector<T>* cache_aligned_vector, | |
const std::string& path = "/data/local/tmp/") { | |
std::vector<float> values; | |
SPARSE_MATMUL_RETURN_IF_ERROR(ReadArrayFromFile(file_name, &values, path)); | |
*cache_aligned_vector = std::move(CacheAlignedVector<T>(values)); | |
return absl::OkStatus(); | |
} | |
// Loads a 2D vector from a file. One of rows or cols can optionally be | |
// -1 to indicate that dimension should be inferred. | |
template <typename T> | |
absl::Status LoadFatVector(const std::string& file_name, int rows, int cols, | |
FatCacheAlignedVector<T>* fat_cache_aligned_vector, | |
const std::string& path = "/data/local/tmp/") { | |
// neither can be zero | |
CHECK(rows != 0 && cols != 0); | |
// only one can be -1 | |
CHECK(rows != -1 || cols != -1); | |
// otherwise must be positive | |
CHECK(rows >= -1 && cols >= -1); | |
CacheAlignedVector<T> values; | |
SPARSE_MATMUL_RETURN_IF_ERROR(LoadVector(file_name, &values, path)); | |
if (rows > 0) | |
CHECK_EQ(values.size() % rows, 0); | |
else | |
rows = values.size() / cols; | |
if (cols > 0) | |
CHECK_EQ(values.size() % cols, 0); | |
else | |
cols = values.size() / rows; | |
*fat_cache_aligned_vector = std::move(FatCacheAlignedVector<T>(values, rows)); | |
return absl::OkStatus(); | |
} | |
// Return all files in a given directory | |
// If only File worked on Android and Windows... | |
absl::Status FilesInDirectory(const std::string& path, | |
const std::string& must_contain, | |
std::vector<std::string>* result); | |
} // namespace csrblocksparse | |