fix(PackConstants): don't start new storage buffer if current one is still empty (#23927) The PackConstants pass introduces a new buffer when a constant cannot be packed into the current buffer without exceeding the maximum size. However, if a constant is itself larger than the limit, it gets still added, but in a separate buffer. This change fixes a bug in the packing logic when a constant bigger than the limit is encountered when the current buffer is still empty. In this case, the current buffer stayed empty and the big constant got added to a new buffer. The empty buffer caused errors of size 0 buffers not being supported in following passes or in the runtime. This change avoids adding a new buffer is the current one is empty and thus avoids these errors. Signed-off-by: Stefan Schuermans <schuermans@roofline.ai>
diff --git a/compiler/src/iree/compiler/Dialect/Stream/Transforms/PackConstants.cpp b/compiler/src/iree/compiler/Dialect/Stream/Transforms/PackConstants.cpp index e5a1dc7..b17c8b4 100644 --- a/compiler/src/iree/compiler/Dialect/Stream/Transforms/PackConstants.cpp +++ b/compiler/src/iree/compiler/Dialect/Stream/Transforms/PackConstants.cpp
@@ -101,8 +101,13 @@ uint64_t unpaddedLength = slice.getStorageSize(); uint64_t paddedLength = IREE::Util::align( unpaddedLength, resourceConfig.getMinBufferRangeAlignment()); - if (offset + unpaddedLength > resourceConfig.getMaxAllocationSize()) { - // Spilling buffer; make a new one. + // If unpaddedLength is bigger than resourceConfig.getMaxAllocationSize(), + // there no way to stay below the threshold. In this case, make sure that + // no new storage buffer is allocated if the current one is empty, so check + // current offset. + if (offset > 0 && + offset + unpaddedLength > resourceConfig.getMaxAllocationSize()) { + // Spilling non-empty buffer; make a new one. storageBuffers.push_back({UnknownLoc::get(resourceConfig.getContext())}); currentBuffer = &storageBuffers.back(); offset = 0;
diff --git a/compiler/src/iree/compiler/Dialect/Stream/Transforms/test/pack_constants.mlir b/compiler/src/iree/compiler/Dialect/Stream/Transforms/test/pack_constants.mlir index bb9d934..a7002c3 100644 --- a/compiler/src/iree/compiler/Dialect/Stream/Transforms/test/pack_constants.mlir +++ b/compiler/src/iree/compiler/Dialect/Stream/Transforms/test/pack_constants.mlir
@@ -137,3 +137,42 @@ // CHECK: util.return %[[RES0]], %[[RES1]], %[[IF1]]#0 util.return %0#0, %0#1, %0#2 : !stream.resource<constant>, !stream.resource<constant>, !stream.timepoint } + +// ----- + +// Tests that a single constant larger than the max allocation size still gets +// its own buffer (no infinite loop or error). A 256-byte constant exceeds the +// 128 byte limit, but because offset is 0 (buffer is empty) it is not spilled, +// so no empty buffer is created. + +#singleConstantExceedsLimitConfig = #stream.resource_config<{ + max_allocation_size = 128, + min_buffer_offset_alignment = 16, + max_buffer_range = 1073741824, + min_buffer_range_alignment = 16, + index_bits = 32 +}> + +// CHECK-NOT: #composite_of_0b = #util.composite<0x{{[[:alnum:]]+}}, []> +// CHECK: #composite_of_256b = #util.composite<256xi8, [ +// CHECK: dense<42> : tensor<64xi32>, +// CHECK: ]> + +// CHECK-LABEL: @singleConstantExceedsLimit +util.func public @singleConstantExceedsLimit() -> (!stream.resource<constant>, !stream.timepoint) + attributes {stream.resources = #singleConstantExceedsLimitConfig} { + %c256 = arith.constant 256 : index + + // CHECK-NOT: %[[RODATA:.+]] = util.buffer.constant {alignment = {{[:digit:]]+}} : index} : !util.buffer = #composite_of_0b + // CHECK: %[[RODATA:.+]] = util.buffer.constant {alignment = 16 : index} : !util.buffer = #composite_of_256b + // CHECK: %[[DID_MAP:.+]], %[[TRY_MAP:.+]] = stream.resource.try_map %[[RODATA]] + // CHECK: %[[IF:.+]]:2 = scf.if %[[DID_MAP]] + // CHECK: %[[RES:.+]] = stream.resource.subview %[[IF]]#1[%c0] : !stream.resource<constant>{%c256} -> !stream.resource<constant>{%c256} + + %0:2 = stream.resource.constants : + !stream.resource<constant>{%c256} = dense<42> : tensor<64xi32> + => !stream.timepoint + + // CHECK: util.return %[[RES]], %[[IF]]#0 + util.return %0#0, %0#1 : !stream.resource<constant>, !stream.timepoint +}