Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/targets/gpu/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ add_library(migraphx_gpu
hsa_chiplet.cpp
kernel.cpp
lower_device_ops.cpp
lower_reshape.cpp
lowering.cpp
loop.cpp
lrn.cpp
Expand All @@ -237,7 +238,6 @@ add_library(migraphx_gpu
prepare_reduce.cpp
pooling.cpp
problem_cache.cpp
propagate_reshape_layout.cpp
rocblas.cpp
schedule_model.cpp
sync_device.cpp
Expand Down
89 changes: 89 additions & 0 deletions src/targets/gpu/include/migraphx/gpu/lower_reshape.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MIGRAPHX_GUARD_GPU_LOWER_RESHAPE_HPP
#define MIGRAPHX_GUARD_GPU_LOWER_RESHAPE_HPP

#include <migraphx/gpu/config.hpp>
#include <string>

namespace migraphx {
inline namespace MIGRAPHX_INLINE_NS {

struct module;

namespace gpu {

/**
* Lower `reshape` into a `reshape_lazy` view, inserting a repacking copy only when the
* input layout does not permit aliasing.
*
* A `reshape` preserves row-major logical element order, so it can be a free view over
* the input buffer whenever the output dims are expressible as a pure restriding of the
* input. Otherwise the bytes have to be moved first.
*
* This pass deliberately runs *after* `eliminate_contiguous`. Lowering inserts a
* `gpu::contiguous` for many ops and `eliminate_contiguous` then drops the redundant
* ones globally, so a reshape's real input layout is not settled until that pass has
* finished. Deciding here means deciding once, with the final layout in hand, instead
* of emitting a pessimistic copy during lowering and undoing it afterwards.
*
* For each `reshape`, in order:
*
* 1. If `reshape_lazy` can alias the input directly and lands on the element count the
* `reshape` declared, emit just the `reshape_lazy`. No copy. The resulting view keeps
* whatever strides aliasing produced, which need not be the ones `reshape` reported.
*
* 2. Otherwise, derive the memory order the input would need in order to be aliasable
* by running the dim mapping backwards (output dims -> input dims), and emit a
* `layout` copy into that order followed by the `reshape_lazy`. The `layout` is
* wrapped in `gpu::precompile_op` and compiles to the existing pointwise copy
* kernel, so this costs the same single kernel as a standardizing contiguous while
* preserving the permutation a following op may want (e.g. NHWC into pooling).
* An identity permutation is declined here and left to case 3, since a `layout`
* that reorders nothing is just a standardizing copy and `gpu::contiguous` has a
* prebuilt code object for it.
*
* 3. Otherwise fall back to a standardizing `gpu::contiguous` followed by the
* `reshape_lazy`.
*
* Cases 1 and 2 need real strides to reason about, so they are skipped for range-based
* dynamic inputs, which have none; those always take case 3. Static inputs are lifted
* to symbolic literals so static and symbolic shapes share one code path.
*
* The 2 input form of `reshape` (`reshape(data, output_buffer)`, where the target shape
* is carried by a runtime-sized output buffer) is not matched and passes through
* un-lowered. No GPU copy op can express it: they all derive their kernel from an index
* space shared by source and destination, which a rank-changing copy does not have.
*/
struct MIGRAPHX_GPU_EXPORT lower_reshape
{
std::string name() const { return "gpu::lower_reshape"; }
void apply(module& m) const;
};

} // namespace gpu
} // namespace MIGRAPHX_INLINE_NS
} // namespace migraphx

#endif // MIGRAPHX_GUARD_GPU_LOWER_RESHAPE_HPP

This file was deleted.

113 changes: 113 additions & 0 deletions src/targets/gpu/lower_reshape.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <migraphx/gpu/lower_reshape.hpp>
#include <migraphx/matcher.hpp>
#include <migraphx/module.hpp>
#include <migraphx/instruction.hpp>
#include <migraphx/make_op.hpp>
#include <migraphx/permutation.hpp>
#include <migraphx/reshape_dims.hpp>
#include <migraphx/sym.hpp>
#include <migraphx/value.hpp>
#include <algorithm>

namespace migraphx {
inline namespace MIGRAPHX_INLINE_NS {
namespace gpu {

namespace {
instruction_ref
insert_precompile_op(module& m, instruction_ref pos, instruction_ref input, const operation& op)
{
auto output_shape = op.compute_shape({input->get_shape()});
auto alloc =
m.insert_instruction(pos, make_op("allocate", {{"shape", to_value(output_shape)}}));
return m.insert_instruction(
pos, make_op("gpu::precompile_op", {{"op", to_value(op)}}), input, alloc);
}

instruction_ref insert_contiguous(module& m, instruction_ref pos, instruction_ref input)
{
const auto& s = input->get_shape();
shape output_shape = s.dynamic() ? shape{s.type(), s.dyn_dims()} : shape{s.type(), s.lens()};
auto alloc =
m.insert_instruction(pos, make_op("allocate", {{"shape", to_value(output_shape)}}));
return m.insert_instruction(pos, make_op("gpu::contiguous"), input, alloc);
}

struct find_reshape : match::supports_dynamic_shapes
{
// Skip reshape(data, output_buffer). Every GPU copy op derives its kernel from one
// index space shared by source and destination, so none of them can change rank.
auto matcher() const { return match::name("reshape")(match::nargs(1)); }

void apply(module& m, const match::matcher_result& r) const
{
auto ins = r.result;
auto dims = ins->get_operator().to_value().at("dims");
auto reshape_op = make_op("reshape_lazy", {{"dims", {dims}}});
auto input = ins->inputs().front();
const auto& s = input->get_shape();

if(not s.dynamic() or s.symbolic())
{
auto expected = ins->get_shape().to_symbolic();
auto output_dims = ins->get_shape().sym_dims();
auto reshaped = reshape_dims(s.to_symbolic(), output_dims, {.lazy = true});
if(reshaped and sym::same_symbol(reshaped->sym_elements(), expected.sym_elements()))
{
m.replace_instruction(ins, reshape_op, {input});
return;
}

auto relayout =
reshape_dims(ins->get_shape().to_symbolic(), s.sym_dims(), {.lazy = true});
if(relayout)
{
auto perm = find_permutation(*relayout);
// An identity permutation means the input only needs standardizing, which is
// what gpu::contiguous already does. Prefer it: lower_device_ops turns it into
// a prebuilt code object, whereas a layout under gpu::precompile_op would pay
// for a jit compile to produce the same copy.
if(not std::is_sorted(perm.begin(), perm.end()))
{
auto layout = insert_precompile_op(
m, ins, input, make_op("layout", {{"permutation", perm}}));
m.replace_instruction(ins, reshape_op, {layout});
return;
}
}
}

auto contiguous = insert_contiguous(m, ins, input);
m.replace_instruction(ins, reshape_op, {contiguous});
}
};
} // namespace

void lower_reshape::apply(module& m) const { match::find_matches(m, find_reshape{}); }

} // namespace gpu
} // namespace MIGRAPHX_INLINE_NS
} // namespace migraphx
29 changes: 0 additions & 29 deletions src/targets/gpu/lowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@
#include <migraphx/op/common.hpp>
#include <migraphx/op/dot.hpp>
#include <migraphx/op/if_op.hpp>
#include <migraphx/op/reshape.hpp>
#include <migraphx/op/quant_dot.hpp>
#include <migraphx/op/reshape_lazy.hpp>

#include <migraphx/gpu/context.hpp>
#include <migraphx/gpu/lowering.hpp>
Expand Down Expand Up @@ -113,7 +111,6 @@ struct miopen_apply
add_nms_op();
add_convolution_backwards_op();
add_select_module_op();
add_reshape_lazy_op();
add_concat_past_present_op();
add_scan_slice_op();
add_fill_op();
Expand Down Expand Up @@ -632,32 +629,6 @@ struct miopen_apply
});
}

/**
* Adds reshape lazy to reshape ops that can be aliased instead of copied.
* `gpu::contiguous` are added before and after the reshape; these contiguous
* instructions can be removed by the eliminate_contiguous pass.
*/
void add_reshape_lazy_op()
{
apply_map.emplace("reshape", [=](instruction_ref ins) {
std::vector<instruction_ref> before_contiguous_args = ins->inputs();
auto before_alloc = insert_allocation(ins, std::prev(ins)->get_shape());
before_contiguous_args.push_back(before_alloc);
auto before_contig =
mod->insert_instruction(ins, make_op("gpu::contiguous"), {before_contiguous_args});

auto new_reshape_lazy = mod->insert_instruction(
ins,
make_op("reshape_lazy", {{"dims", {ins->get_operator().to_value().at("dims")}}}),
before_contig);

std::vector<instruction_ref> after_contiguous_args = {new_reshape_lazy};
auto after_alloc = insert_allocation(new_reshape_lazy, new_reshape_lazy->get_shape());
after_contiguous_args.push_back(after_alloc);
return mod->replace_instruction(ins, make_op("gpu::contiguous"), after_contiguous_args);
});
}

void add_concat_past_present_op()
{
apply_map.emplace("concat_past_present", [=](instruction_ref ins) {
Expand Down
Loading
Loading