From 6945a7fe669a84c68b842a894e7759f1773848e0 Mon Sep 17 00:00:00 2001 From: AaronStGeorge Date: Fri, 30 Oct 2026 12:52:36 +0000 Subject: [PATCH 1/2] hrx: keep contiguous F32 duplicates on the device Reuse the copy dispatch for DUP and constrain placement to the supported contiguous F32 case. Cover the active copy in the existing CPU reference test. Assisted-by: Codex (cherry picked from commit 73f49408113923d48bcb7df7602cf43075dc2900) --- ggml/src/ggml-hrx/README.md | 2 ++ .../common/dispatch-copy.cpp | 22 ++++++++++++++++--- ggml/src/ggml-hrx/ggml-hrx.cpp | 17 ++++++++++++++ tests/hrx-ops-test.cpp | 10 ++++++++- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/ggml/src/ggml-hrx/README.md b/ggml/src/ggml-hrx/README.md index 47a530eaf4ef..a6d2dfe4d9fb 100644 --- a/ggml/src/ggml-hrx/README.md +++ b/ggml/src/ggml-hrx/README.md @@ -1,5 +1,7 @@ # HRX Backend +HRX supports `DUP` for contiguous F32 inputs and distinct contiguous outputs, up to 2^30 elements, using its existing copy kernel. This keeps the mixed-embedding input branch on HRX when the scheduler assigns the graph. + ## `hrx-system` backend compatibility `hrx-minimum-commit.txt` records the minimum required commit in ROCm/hrx-system. It contains one full Git commit SHA. The backend's C++ APIs, Loom syntax, and numerical behavior can all depend on that revision. When `GGML_HRX=ON` and `HRX_SOURCE_DIR` is set, CMake requires the HRX checkout's HEAD to equal or descend from the recorded commit. diff --git a/ggml/src/ggml-hrx/dispatch_registration/common/dispatch-copy.cpp b/ggml/src/ggml-hrx/dispatch_registration/common/dispatch-copy.cpp index 5f091f40e564..37c9f1ffe893 100644 --- a/ggml/src/ggml-hrx/dispatch_registration/common/dispatch-copy.cpp +++ b/ggml/src/ggml-hrx/dispatch_registration/common/dispatch-copy.cpp @@ -122,9 +122,17 @@ static bool match_copy_f32_dispatch(const DispatchMatchContext & context, Dispat return true; } -static bool match_cont_f32_dispatch(const DispatchMatchContext & context, DispatchMatch & match) { +static bool match_cont_or_dup_f32_dispatch(const DispatchMatchContext & context, DispatchMatch & match) { const GraphNode * node = context.root_node; - if (node == nullptr || node->op != GGML_OP_CONT || node->inputs.size() != 1) { + if (node == nullptr) { + return false; + } + const bool is_cont = node->op == GGML_OP_CONT; + const bool is_dup = node->op == GGML_OP_DUP; + const bool is_copy = is_cont || is_dup; + const bool has_source = node->inputs.size() == 1; + const bool matches = is_copy && has_source; + if (!matches) { return false; } @@ -216,7 +224,15 @@ void register_copy_dispatch(DispatchRegistryBuilder & registry) { DispatchMatchKind::SingleOp, 0, DispatchSource::Common, - match_cont_f32_dispatch, + match_cont_or_dup_f32_dispatch, + }); + registry.add({ + "common.dup_f32", + GGML_OP_DUP, + DispatchMatchKind::SingleOp, + 0, + DispatchSource::Common, + match_cont_or_dup_f32_dispatch, }); registry.add({ "common.concat_dim0_f32", diff --git a/ggml/src/ggml-hrx/ggml-hrx.cpp b/ggml/src/ggml-hrx/ggml-hrx.cpp index 71b4f9977ae6..0f21047a92d0 100644 --- a/ggml/src/ggml-hrx/ggml-hrx.cpp +++ b/ggml/src/ggml-hrx/ggml-hrx.cpp @@ -1032,6 +1032,23 @@ static bool device_supports_op(ggml_backend_dev_t device, const ggml_tensor * op if (zero_output_elision_supported(op)) { return true; } + if (op->op == GGML_OP_DUP) { + const ggml_tensor * source = op->src[0]; + if (source == nullptr) { + return false; + } + const bool source_f32 = source->type == GGML_TYPE_F32; + const bool output_f32 = op->type == GGML_TYPE_F32; + const bool source_contiguous = ggml_is_contiguous(source); + const bool output_contiguous = ggml_is_contiguous(op); + const bool same_shape = ggml_are_same_shape(source, op); + const bool separate_output = op->view_src == nullptr; + const int64_t elements = ggml_nelements(op); + const bool nonempty = elements > 0; + const bool bounded = elements <= (int64_t{ 1 } << 30); + return source_f32 && output_f32 && source_contiguous && output_contiguous && same_shape && + separate_output && nonempty && bounded; + } const bool supported_binary = supported_binary_f32_tensor(op); if (supported_binary) { return true; diff --git a/tests/hrx-ops-test.cpp b/tests/hrx-ops-test.cpp index e8cb79bac47b..73d5f79efc83 100644 --- a/tests/hrx-ops-test.cpp +++ b/tests/hrx-ops-test.cpp @@ -3639,6 +3639,8 @@ static void run_dynamic_generic_ops_cpu_reference_case() { ggml_tensor * hrx_copy_target = ggml_new_tensor_1d(hrx_ctx, GGML_TYPE_F32, element_count); ggml_tensor * cpu_copy = ggml_cpy(cpu_ctx, cpu_input, cpu_copy_target); ggml_tensor * hrx_copy = ggml_cpy(hrx_ctx, hrx_input, hrx_copy_target); + ggml_tensor * cpu_dup = ggml_dup(cpu_ctx, cpu_input); + ggml_tensor * hrx_dup = ggml_dup(hrx_ctx, hrx_input); ggml_tensor * cpu_concat_lhs = ggml_new_tensor_2d(cpu_ctx, GGML_TYPE_F32, 64, 7); ggml_tensor * cpu_concat_rhs = ggml_new_tensor_2d(cpu_ctx, GGML_TYPE_F32, 32, 7); ggml_tensor * hrx_concat_lhs = ggml_new_tensor_2d(hrx_ctx, GGML_TYPE_F32, 64, 7); @@ -3647,6 +3649,9 @@ static void run_dynamic_generic_ops_cpu_reference_case() { ggml_tensor * hrx_concat = ggml_concat(hrx_ctx, hrx_concat_lhs, hrx_concat_rhs, 0); REQUIRE(cpu_unary != nullptr && hrx_unary != nullptr); REQUIRE(cpu_copy != nullptr && hrx_copy != nullptr); + REQUIRE(cpu_dup != nullptr); + REQUIRE(hrx_dup != nullptr); + REQUIRE(ggml_backend_supports_op(hrx_backend, hrx_dup)); REQUIRE(cpu_concat != nullptr && hrx_concat != nullptr); ggml_cgraph * cpu_graph = ggml_new_graph(cpu_ctx); @@ -3654,13 +3659,15 @@ static void run_dynamic_generic_ops_cpu_reference_case() { REQUIRE(cpu_graph != nullptr && hrx_graph != nullptr); ggml_build_forward_expand(cpu_graph, cpu_unary); ggml_build_forward_expand(cpu_graph, cpu_copy); + ggml_build_forward_expand(cpu_graph, cpu_dup); ggml_build_forward_expand(cpu_graph, cpu_concat); ggml_build_forward_expand(hrx_graph, hrx_unary); ggml_build_forward_expand(hrx_graph, hrx_copy); + ggml_build_forward_expand(hrx_graph, hrx_dup); ggml_build_forward_expand(hrx_graph, hrx_concat); require_kernel_subsequence(scheduled_kernel_sequence(hrx_graph), - { "loom_libs:ggml_unary_f32", "loom_libs:ggml_copy_f32", + { "loom_libs:ggml_unary_f32", "loom_libs:ggml_copy_f32", "loom_libs:ggml_copy_f32", "loom_libs:ggml_concat_dim0_f32" }); for (const char * kernel_name : { "loom_libs:ggml_unary_f32", "loom_libs:ggml_copy_f32", "loom_libs:ggml_concat_dim0_f32" }) { @@ -3687,6 +3694,7 @@ static void run_dynamic_generic_ops_cpu_reference_case() { ggml_backend_synchronize(hrx_backend); require_close(get_f32_tensor(hrx_backend, hrx_unary), get_f32_tensor(cpu_backend, cpu_unary), 0.0f); require_close(get_f32_tensor(hrx_backend, hrx_copy), get_f32_tensor(cpu_backend, cpu_copy), 0.0f); + require_close(get_f32_tensor(hrx_backend, hrx_dup), get_f32_tensor(cpu_backend, cpu_dup), 0.0f); require_close(get_f32_tensor(hrx_backend, hrx_concat), get_f32_tensor(cpu_backend, cpu_concat), 0.0f); ggml_backend_buffer_free(cpu_buffer); From 8a59c2817b8ca6ae33ba0b3fab42c827426e02f1 Mon Sep 17 00:00:00 2001 From: Aaron St George Date: Fri, 30 Oct 2026 13:28:46 -0700 Subject: [PATCH 2/2] Update README.md --- ggml/src/ggml-hrx/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/ggml/src/ggml-hrx/README.md b/ggml/src/ggml-hrx/README.md index a6d2dfe4d9fb..47a530eaf4ef 100644 --- a/ggml/src/ggml-hrx/README.md +++ b/ggml/src/ggml-hrx/README.md @@ -1,7 +1,5 @@ # HRX Backend -HRX supports `DUP` for contiguous F32 inputs and distinct contiguous outputs, up to 2^30 elements, using its existing copy kernel. This keeps the mixed-embedding input branch on HRX when the scheduler assigns the graph. - ## `hrx-system` backend compatibility `hrx-minimum-commit.txt` records the minimum required commit in ROCm/hrx-system. It contains one full Git commit SHA. The backend's C++ APIs, Loom syntax, and numerical behavior can all depend on that revision. When `GGML_HRX=ON` and `HRX_SOURCE_DIR` is set, CMake requires the HRX checkout's HEAD to equal or descend from the recorded commit.