|  | // Copyright 2023 The IREE Authors | 
|  | // | 
|  | // Licensed under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  |  | 
|  | // Converts one or more parameter files into a single IREE Parameter Archive. | 
|  | // Allows for stripping and renaming parameters as basic editing features. | 
|  |  | 
|  | #include <ctype.h> | 
|  | #include <stdio.h> | 
|  |  | 
|  | #include "iree/base/api.h" | 
|  | #include "iree/base/internal/file_io.h" | 
|  | #include "iree/base/internal/flags.h" | 
|  | #include "iree/hal/api.h" | 
|  | #include "iree/io/formats/irpa/irpa_builder.h" | 
|  | #include "iree/io/parameter_index.h" | 
|  | #include "iree/io/scope_map.h" | 
|  | #include "iree/io/stream.h" | 
|  |  | 
|  | //===----------------------------------------------------------------------===// | 
|  | // Parameter builder logic | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | IREE_FLAG_LIST( | 
|  | string, splat, | 
|  | "Declares a splat parameter in the form `name=shapextype=pattern`.\n" | 
|  | "Splat parameters have no storage on disk and can be used to mock real\n" | 
|  | "parameters that besides load-time performance and contents behave the\n" | 
|  | "same as if real parameters had been used. The shape and type are used to\n" | 
|  | "calculate the parameter size in the index and the value is interpreted\n" | 
|  | "based on the type. Note that splat parameters cannot be mutated at\n" | 
|  | "runtime and can only be used for constant values.\n"); | 
|  |  | 
|  | // TODO(benvanik): support @file.bin syntax, numpy files, etc. | 
|  | IREE_FLAG_LIST( | 
|  | string, data, | 
|  | "Declares a data parameter in the form `name=shapextype=pattern`.\n" | 
|  | "Data parameters have a storage reservation in the final archive with\n" | 
|  | "either zeroed contents or a specified repeating pattern value.\n" | 
|  | "The shape and type are used to calculate the parameter size in the index\n" | 
|  | "and the value is interpreted based on the type. Omitting the value\n" | 
|  | "will leave the parameter with zeroed contents on disk."); | 
|  |  | 
|  | // TODO(benvanik): support external entries (--external=file.bin#128-512). | 
|  |  | 
|  | IREE_FLAG(int32_t, alignment, IREE_IO_PARAMETER_ARCHIVE_DEFAULT_DATA_ALIGNMENT, | 
|  | "Storage data alignment relative to the header."); | 
|  |  | 
|  | typedef struct { | 
|  | iree_string_view_t name; | 
|  | uint64_t storage_size; | 
|  | uint64_t element_count; | 
|  | iree_hal_element_type_t element_type; | 
|  | struct { | 
|  | uint8_t pattern[16]; | 
|  | uint8_t pattern_length; | 
|  | } splat; | 
|  | } iree_io_parameter_info_t; | 
|  |  | 
|  | // Parses a `name=shapextype[=value]` flag string. | 
|  | static iree_status_t iree_io_parameter_info_from_string( | 
|  | iree_string_view_t parameter_value, iree_io_parameter_info_t* out_info) { | 
|  | memset(out_info, 0, sizeof(*out_info)); | 
|  |  | 
|  | iree_string_view_t spec; | 
|  | iree_string_view_split(parameter_value, '=', &out_info->name, &spec); | 
|  |  | 
|  | iree_string_view_t shape_type, contents; | 
|  | iree_string_view_split(spec, '=', &shape_type, &contents); | 
|  |  | 
|  | iree_host_size_t shape_rank = 0; | 
|  | iree_hal_dim_t shape[16] = {0}; | 
|  | IREE_RETURN_IF_ERROR(iree_hal_parse_shape_and_element_type( | 
|  | shape_type, IREE_ARRAYSIZE(shape), &shape_rank, shape, | 
|  | &out_info->element_type)); | 
|  |  | 
|  | if (IREE_UNLIKELY(iree_hal_element_bit_count(out_info->element_type) == 0) || | 
|  | IREE_UNLIKELY( | 
|  | !iree_hal_element_is_byte_aligned(out_info->element_type))) { | 
|  | return iree_make_status( | 
|  | IREE_STATUS_INVALID_ARGUMENT, | 
|  | "opaque and sub-byte aligned element types cannot currently be used as " | 
|  | "splats; use a bitcast type (2xi4=1xi8, etc)"); | 
|  | } | 
|  |  | 
|  | out_info->storage_size = | 
|  | iree_hal_element_dense_byte_count(out_info->element_type); | 
|  | out_info->element_count = 1; | 
|  | for (iree_host_size_t i = 0; i < shape_rank; ++i) { | 
|  | out_info->storage_size *= shape[i]; | 
|  | out_info->element_count *= shape[i]; | 
|  | } | 
|  |  | 
|  | // TODO(benvanik): support external files and such; for now we just assume | 
|  | // either empty string (0 splat) or a splat value. | 
|  |  | 
|  | if (iree_string_view_is_empty(contents)) { | 
|  | out_info->splat.pattern_length = 1; | 
|  | memset(out_info->splat.pattern, 0, sizeof(out_info->splat.pattern)); | 
|  | } else { | 
|  | iree_device_size_t byte_count = | 
|  | iree_hal_element_dense_byte_count(out_info->element_type); | 
|  | if (byte_count > sizeof(out_info->splat.pattern)) { | 
|  | return iree_make_status( | 
|  | IREE_STATUS_OUT_OF_RANGE, | 
|  | "element type size for %.*s out of range of splat patterns", | 
|  | (int)shape_type.size, shape_type.data); | 
|  | } | 
|  | out_info->splat.pattern_length = (uint8_t)byte_count; | 
|  | IREE_RETURN_IF_ERROR(iree_hal_parse_element( | 
|  | contents, out_info->element_type, | 
|  | iree_make_byte_span(out_info->splat.pattern, 16))); | 
|  | } | 
|  | return iree_ok_status(); | 
|  | } | 
|  |  | 
|  | // Declares parameter metadata for all parameters specified by flags. | 
|  | static iree_status_t iree_tooling_declare_parameters( | 
|  | iree_io_parameter_archive_builder_t* builder) { | 
|  | IREE_TRACE_ZONE_BEGIN(z0); | 
|  |  | 
|  | // Metadata-only parameters first; they have no storage and the fact that they | 
|  | // are not in-order with any data parameters means they don't impact runtime | 
|  | // access locality behavior. | 
|  | for (iree_host_size_t i = 0; i < FLAG_splat_list().count; ++i) { | 
|  | iree_io_parameter_info_t info; | 
|  | IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
|  | z0, | 
|  | iree_io_parameter_info_from_string(FLAG_splat_list().values[i], &info)); | 
|  | IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
|  | z0, | 
|  | iree_io_parameter_archive_builder_add_splat_entry( | 
|  | builder, info.name, /*metadata=*/iree_const_byte_span_empty(), | 
|  | info.splat.pattern, info.splat.pattern_length, info.storage_size)); | 
|  | } | 
|  |  | 
|  | // Data parameters follow and will appear in storage in the order they were | 
|  | // declared with flags. | 
|  | for (iree_host_size_t i = 0; i < FLAG_data_list().count; ++i) { | 
|  | iree_io_parameter_info_t info; | 
|  | IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
|  | z0, | 
|  | iree_io_parameter_info_from_string(FLAG_data_list().values[i], &info)); | 
|  | IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
|  | z0, iree_io_parameter_archive_builder_add_data_entry( | 
|  | builder, info.name, /*metadata=*/iree_const_byte_span_empty(), | 
|  | FLAG_alignment, info.storage_size)); | 
|  | } | 
|  |  | 
|  | IREE_TRACE_ZONE_END(z0); | 
|  | return iree_ok_status(); | 
|  | } | 
|  |  | 
|  | // Defines parameter storage for those that require it. | 
|  | static iree_status_t iree_tooling_define_parameters( | 
|  | iree_io_parameter_index_t* target_index, | 
|  | iree_io_physical_offset_t target_file_offset, | 
|  | iree_io_stream_t* target_stream) { | 
|  | IREE_TRACE_ZONE_BEGIN(z0); | 
|  |  | 
|  | for (iree_host_size_t i = 0; i < FLAG_data_list().count; ++i) { | 
|  | iree_io_parameter_info_t info; | 
|  | IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
|  | z0, | 
|  | iree_io_parameter_info_from_string(FLAG_data_list().values[i], &info)); | 
|  | const iree_io_parameter_index_entry_t* target_entry = NULL; | 
|  | IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
|  | z0, | 
|  | iree_io_parameter_index_lookup(target_index, info.name, &target_entry)); | 
|  | IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
|  | z0, iree_io_stream_seek( | 
|  | target_stream, IREE_IO_STREAM_SEEK_SET, | 
|  | target_file_offset + target_entry->storage.file.offset)); | 
|  | IREE_RETURN_AND_END_ZONE_IF_ERROR( | 
|  | z0, iree_io_stream_fill(target_stream, info.element_count, | 
|  | info.splat.pattern, info.splat.pattern_length)); | 
|  | } | 
|  |  | 
|  | IREE_TRACE_ZONE_END(z0); | 
|  | return iree_ok_status(); | 
|  | } | 
|  |  | 
|  | //===----------------------------------------------------------------------===// | 
|  | // main | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | IREE_FLAG(bool, quiet, false, | 
|  | "Silences additional stdout output when not needed."); | 
|  |  | 
|  | // TODO(benvanik): add --append= to chain archive headers. | 
|  | IREE_FLAG(string, output, "", "Output .irpa file path."); | 
|  |  | 
|  | static void iree_io_file_handle_release_mapping( | 
|  | void* user_data, iree_io_file_handle_primitive_t handle_primitive) { | 
|  | iree_file_contents_free((iree_file_contents_t*)user_data); | 
|  | } | 
|  |  | 
|  | static iree_status_t iree_tooling_open_output_parameter_file( | 
|  | iree_io_physical_offset_t archive_offset, | 
|  | iree_io_physical_size_t archive_length, iree_allocator_t host_allocator, | 
|  | iree_io_file_handle_t** out_file_handle) { | 
|  | iree_file_contents_t* file_contents = NULL; | 
|  | IREE_RETURN_IF_ERROR(iree_file_create_mapped( | 
|  | FLAG_output, archive_offset + archive_length, archive_offset, | 
|  | (iree_host_size_t)archive_length, host_allocator, &file_contents)); | 
|  | iree_io_file_handle_release_callback_t release_callback = { | 
|  | .fn = iree_io_file_handle_release_mapping, | 
|  | .user_data = file_contents, | 
|  | }; | 
|  | iree_status_t status = iree_io_file_handle_wrap_host_allocation( | 
|  | IREE_IO_FILE_ACCESS_WRITE, file_contents->buffer, release_callback, | 
|  | host_allocator, out_file_handle); | 
|  | if (!iree_status_is_ok(status)) { | 
|  | iree_file_contents_free(file_contents); | 
|  | } | 
|  | return status; | 
|  | } | 
|  |  | 
|  | int main(int argc, char** argv) { | 
|  | IREE_TRACE_APP_ENTER(); | 
|  | IREE_TRACE_ZONE_BEGIN(z0); | 
|  |  | 
|  | iree_allocator_t host_allocator = iree_allocator_system(); | 
|  | int exit_code = EXIT_SUCCESS; | 
|  |  | 
|  | // Parse command line flags. | 
|  | iree_flags_set_usage( | 
|  | "iree-create-parameters", | 
|  | "Creates IREE Parameter Archive (.irpa) files. Provide zero or more\n" | 
|  | "parameter value declarations and an output file with\n" | 
|  | "`--output=file.irpa` to produce a new file with zeroed or patterned\n" | 
|  | "contents.\n" | 
|  | "\n" | 
|  | "Parameter declarations take a shape and type in order to calculate the\n" | 
|  | "required storage size of the parameter at rest and at runtime. The\n" | 
|  | "shape and type need not match what the consuming program expects so\n" | 
|  | "long as the storage size is equivalent; for example, if the program\n" | 
|  | "expects a parameter of type `tensor<8x2xi4>` the parameter declaration\n" | 
|  | "can be `8xi8`, `1xi64`, `2xf32`, etc.\n" | 
|  | "\n" | 
|  | "Example creating a file with two embedded data parameters that have\n" | 
|  | "zeroed contents and one with a repeating pattern:\n" | 
|  | "  iree-create-parameters \\\n" | 
|  | "    --data=my.zeroed_param_1=4096xf32 \\\n" | 
|  | "    --data=my.zeroed_param_2=2x4096xi16 \\\n" | 
|  | "    --data=my.pattern_param_2=8x2xf32=2.1 \\\n" | 
|  | "    --output=output_with_storage.irpa\n" | 
|  | "\n" | 
|  | "Example creating a file with splatted values (no storage on disk):\n" | 
|  | "  iree-create-parameters \\\n" | 
|  | "    --splat=my.splat_param_1=4096xf32=4.1 \\\n" | 
|  | "    --splat=my.splat_param_2=2x4096xi16=123 \\\n" | 
|  | "    --output=output_without_storage.irpa\n"); | 
|  | iree_flags_parse_checked(IREE_FLAGS_PARSE_MODE_DEFAULT, &argc, &argv); | 
|  |  | 
|  | iree_io_parameter_archive_builder_t builder; | 
|  | iree_io_parameter_archive_builder_initialize(host_allocator, &builder); | 
|  |  | 
|  | // Declare parameters based on flags, populating the builder with the metadata | 
|  | // for each parameter without yet writing any data. | 
|  | iree_status_t status = iree_tooling_declare_parameters(&builder); | 
|  |  | 
|  | // Open a file of sufficient size (now that we know it) for writing. | 
|  | iree_io_physical_offset_t target_file_offset = 0; | 
|  | iree_io_physical_offset_t archive_offset = iree_align_uint64( | 
|  | target_file_offset, IREE_IO_PARAMETER_ARCHIVE_HEADER_ALIGNMENT); | 
|  | iree_io_physical_size_t archive_length = | 
|  | iree_io_parameter_archive_builder_total_size(&builder); | 
|  | iree_io_file_handle_t* target_file_handle = NULL; | 
|  | if (iree_status_is_ok(status)) { | 
|  | status = iree_tooling_open_output_parameter_file( | 
|  | archive_offset, archive_length, host_allocator, &target_file_handle); | 
|  | } | 
|  |  | 
|  | // Wrap the target file in a stream. | 
|  | iree_io_stream_t* target_stream = NULL; | 
|  | if (iree_status_is_ok(status)) { | 
|  | status = | 
|  | iree_io_stream_open(IREE_IO_STREAM_MODE_WRITABLE, target_file_handle, | 
|  | target_file_offset, host_allocator, &target_stream); | 
|  | } | 
|  |  | 
|  | // Allocate an index we'll populate during building to allow us to get the | 
|  | // storage ranges of non-metadata parameters. | 
|  | iree_io_parameter_index_t* built_index = NULL; | 
|  | if (iree_status_is_ok(status)) { | 
|  | status = iree_io_parameter_index_create(host_allocator, &built_index); | 
|  | } | 
|  |  | 
|  | // Commit the archive header to the file and produce an index referencing it. | 
|  | // This will allow us to know where to copy file contents. | 
|  | if (iree_status_is_ok(status)) { | 
|  | status = iree_io_parameter_archive_builder_write( | 
|  | &builder, target_file_handle, target_file_offset, target_stream, | 
|  | built_index); | 
|  | } | 
|  |  | 
|  | // Define non-metadata-only parameters that use the data storage segment. | 
|  | if (iree_status_is_ok(status)) { | 
|  | status = iree_tooling_define_parameters(built_index, target_file_offset, | 
|  | target_stream); | 
|  | } | 
|  |  | 
|  | // Dump the new index ala iree-dump-parameters to show the final file. | 
|  | if (iree_status_is_ok(status) && !FLAG_quiet) { | 
|  | status = iree_io_parameter_index_fprint(stdout, iree_string_view_empty(), | 
|  | built_index); | 
|  | } | 
|  |  | 
|  | iree_io_stream_release(target_stream); | 
|  | iree_io_file_handle_release(target_file_handle); | 
|  | iree_io_parameter_archive_builder_deinitialize(&builder); | 
|  | iree_io_parameter_index_release(built_index); | 
|  |  | 
|  | fflush(stdout); | 
|  | if (!iree_status_is_ok(status)) { | 
|  | iree_status_fprint(stderr, status); | 
|  | iree_status_free(status); | 
|  | exit_code = EXIT_FAILURE; | 
|  | } | 
|  | fflush(stderr); | 
|  |  | 
|  | IREE_TRACE_ZONE_END(z0); | 
|  | IREE_TRACE_APP_EXIT(exit_code); | 
|  | return exit_code; | 
|  | } |