| // 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_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); | |
| return exit_code; | |
| } |