void validate_tensors_against_specs()

in source/neuropod/backends/neuropod_backend.cc [79:214]


void validate_tensors_against_specs(const NeuropodValueMap &       tensors,
                                    const std::vector<TensorSpec> &specs,
                                    const std::string &            debug_spec_name)
{
    // A vector of all the tensor names in the spec.
    // This is used to check for extra tensors that were provided,
    // but not in the spec
    std::unordered_set<std::string> spec_tensor_names;

    // All instances of a symbol in a specification must
    // resolve to the same value at runtime. See below for more detail
    std::unordered_map<std::string, int64_t> symbol_actual_map;

    for (const auto &spec : specs)
    {
        // Add the item's name to a set used to check for extra tensors
        spec_tensor_names.emplace(spec.name);

        // Try to find a tensor with the same name as this item
        auto tensor_it = tensors.find(spec.name);
        if (tensor_it == tensors.end())
        {
            // For now, all tensors are optional
            // TODO(vip): Fix this once we have a better way of marking items as optional
            continue;
        }

        // Get the tensor
        const auto &tensor = tensor_it->second->as_tensor();

        // Validate data type
        if (tensor->get_tensor_type() != spec.type)
        {
            // Throw an error
            NEUROPOD_ERROR("Tensor '{}' in the {} is expected to be of type {}, but was of type {}",
                           spec.name,
                           debug_spec_name,
                           spec.type,
                           tensor->get_tensor_type());
        }

        // Validate the number of dimensions
        if (tensor->get_dims().size() != spec.dims.size())
        {
            // Throw an error
            NEUROPOD_ERROR("Tensor '{}' in the {} is expected to have {} dimensions, but had {}",
                           spec.name,
                           debug_spec_name,
                           spec.dims.size(),
                           tensor->get_dims().size());
        }

        // Validate the shape
        for (size_t i = 0; i < spec.dims.size(); i++)
        {
            auto dim      = tensor->get_dims()[i];
            auto expected = spec.dims[i];

            if (expected.value == -1)
            {
                // Any value of dim is okay
                continue;
            }
            else if (expected.value > 0) // NOLINT(readability-else-after-return)
            {
                // Check that we have the expected number of items
                if (dim != expected.value)
                {
                    // Throw an error
                    NEUROPOD_ERROR("Dim {} of tensor '{}' in the {} is expected to be of size {}, but was of size {}",
                                   i,
                                   spec.name,
                                   debug_spec_name,
                                   expected.value,
                                   dim);
                }
            }
            else if (expected.value < -1)
            {
                // `expected` is a symbol.
                // Every instance of `expected` should have the same value
                // For example, if a symbol of "num_classes" is used multiple times in the spec,
                // all instances must have the same value
                auto actual_it = symbol_actual_map.find(expected.symbol);
                if (actual_it != symbol_actual_map.end())
                {
                    // We've seen this symbol before
                    auto actual_value = actual_it->second;

                    // Make sure this usage matches the previous value
                    if (dim != actual_value)
                    {
                        // Throw an error
                        NEUROPOD_ERROR(
                            "All dims with symbol '{}' should be the same size. "
                            "Dim {} of tensor '{}' in the {} was expected to be of size {}, but was of size {}",
                            expected.symbol,
                            i,
                            spec.name,
                            debug_spec_name,
                            actual_value,
                            dim);
                    }
                }
                else
                {
                    // This is the first time we're seeing this symbol
                    // Add it to the map so we can check future occurrances of this symbol
                    symbol_actual_map[expected.symbol] = dim;
                }
            }
            else
            {
                // Throw an error
                NEUROPOD_ERROR(
                    "Invalid value of expected shape for item in the {}: {}", debug_spec_name, expected.value);
            }
        }
    }

    // Check for extra tensors that are not included in the spec
    std::vector<std::string> unexpected_tensors;
    for (const auto &item : tensors)
    {
        if (spec_tensor_names.find(item.first) == spec_tensor_names.end())
        {
            unexpected_tensors.emplace_back(item.first);
        }
    }

    if (!unexpected_tensors.empty())
    {
        // Throw an error
        NEUROPOD_ERROR("Tensor name(s) '{}' are not found in the {}", unexpected_tensors, debug_spec_name);
    }
}