ARCH/X86: Rework AVX2 NT buffer-transfer kernels and dispatch - #11895
Conversation
Route transfers above nt_dest_threshold through the existing NT destination kernel regardless of the source hint. Remove the specialized combined path and its non-temporal source prefetches. Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Handle the destination prefix, streaming body, cache-line drain, and tail inside the AVX2 NT destination kernel. Send chunks below 3072 bytes to memcpy before entering the always-streaming implementation. Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Handle the source prefix, vector body, cache-line drain, and tail inside the AVX2 copy-out kernel. Remove non-temporal source prefetches and the external tail-copy contract. Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Set the automatic AMD NT destination threshold to three quarters of L3 so large transfers switch to streaming stores earlier. Report its strict comparison as sizes above the configured threshold. Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Use the shared ERMS helper for eligible NT source copies and retain the vectorized implementation below the built-in memcpy threshold. Exercise both dispatcher branches in the existing NT source test. Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Close the outer built-in memcpy window when NT_BUFFER_TRANSFER_MIN is set explicitly so the NT dispatcher selects between ERMS and the vector implementation. Document the configuration effect. Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
|
🤖 Starting review — findings will be posted here when done. |
|
@roiedanino @tvegas1 please check. |
Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Explain that an explicit total-length NT threshold closes the preceding fragment-length ERMS window so eligible fragments reach NT dispatch. Clarify that ERMS remains selectable inside that dispatcher. Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
|
🤖 Starting review — findings will be posted here when done. |
| * total transfer length. Close the outer window so fragments of an | ||
| * eligible transfer cannot bypass the explicit NT threshold. | ||
| */ | ||
| ucs_global_opts.arch.builtin_memcpy_max = 0; |
There was a problem hiding this comment.
NT_BUFFER_TRANSFER_MIN also zeroes builtin_memcpy_max, which disables ERMS in ucs_memcpy_relaxed() for all copy sizes, not just NT transfers — including on Intel where NT is off. A user tuning only the NT threshold loses ERMS for regular copies. Can we scope this so it only affects the NT dispatch path, or gate it on the NT vendor/threshold being active?
There was a problem hiding this comment.
Correct, an explicit NT_BUFFER_TRANSFER_MIN intentionally closes the outer ERMS window for all sizes. Transfers below the configured NT threshold therefore use memcpy(), while eligible transfers reach ucs_x86_nt_buffer_transfer(), where ERMS remains available internally for NT_SOURCE.
This is needed because outer ERMS selects on fragment len before NT eligibility is evaluated on total_len. Retaining or clamping the outer window would allow an eligible transfer’s fragments to return through ERMS before its explicit NT policy and copy hint are considered. On Intel, the automatic NT threshold remains disabled; setting it explicitly opts into this behavior.
|
The rewrite removes the aocl-libmem attribution comment ( |
Yes, this is intentional. The attribution applied to the previous ucs_x86_copy_bytes_le_128()-based implementation. Its leading-zero switch and special len <= 128 dispatch were removed. The current AVX2 kernels were reworked using the AVX-512 kernel design from the original PR #11696 as their structural reference: a 64-byte-aligned prefix, explicit 256-byte body, 64-byte drain, and sub-cache-line tail. The AVX-512 implementation is not part of this PR; it is planned for the third PR in the split series. The rewritten AVX2 code does not retain AOCL-LibMem’s control flow or vector-loop implementation. The remaining overlapping head/tail handling for a short tail is a standard bounded-copy technique |
|
🤖 CI Triage Agent — TL;DR: This job did not fail on any code error — the Azure Pipelines agent was killed mid-compile ("The agent has received a shutdown signal"), so the ASAN Full analysisSummary: The "AddressSanitizer roce on worker 0" job was cancelled by the Azure DevOps agent host during the Root cause: Infrastructure, not code. The log shows a clean, uninterrupted build: Checking for a hang: the largest gap between consecutive log lines is ~39 s (10:30:36 → 10:31:15, the The signature is an agent-side termination: agent VM preemption/reclamation, agent service restart, or Azure auto-cancelling the run because a newer commit was pushed to PR #11895 superseding commit Implicated commit: none — File: n/a — no source file is implicated (last compiled unit Suggested fix: Re-queue the "UCX PR" pipeline for PR #11895 (or push an empty commit) to get a clean signal on the ASAN roce job. Do not raise any timeout — the step was killed at ~7 minutes with continuous progress, so a time-limit change would have no effect. If this recurs across multiple PRs on the same agent pool, escalate to CI infrastructure to check for agent preemption/host restarts on the Related: PR #11895 (this build). No matching issues found for the "agent has received a shutdown signal" signature in the openucx/ucx tracker.
|
Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
|
🤖 Starting review — findings will be posted here when done. |
|
Residual coverage gap: no test exercises a fragmented transfer where Residual coverage gap: the ERMS NT_SOURCE test only runs where |
|
🤖 CI Triage Agent — TL;DR: The Full analysisSummary: Azure job "Tests gpu on worker 0" ( Root cause: Two-part, neither related to PR #11895 (which is an AVX2 NT-copy dispatch rework and touches no CUDA/parser code):
Implicated commit: unknown — the failing commit 4f35d6e ( File: Suggested fix:
Related: PR #11895 (build under test, unrelated to the failure); no existing issue found for the fragile first-line parsing in |
Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
|
🤖 Starting review — findings will be posted here when done. |
|
Process/coverage note: the previously reported test-coverage gaps still hold — fragmented transfer with |
Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Remove the redundant field-specific threshold guard and rely on fixture restoration. Initialize the derived NT destination threshold explicitly so source-path tests do not depend on an uncloned value. Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
Signed-off-by: Arun Chandran <Arun.Chandran@amd.com>
The existing guarded A second behavioral test through Under production AMD settings, |
|
🤖 Starting review — findings will be posted here when done. |
| * uses total transfer length. Close the outer window so fragments | ||
| * of an eligible transfer cannot bypass the explicit NT threshold. | ||
| */ | ||
| ucs_global_opts.arch.builtin_memcpy_max = 0; |
There was a problem hiding this comment.
Setting builtin_memcpy_max to 0 disables ERMS for every copy, including transfers with total_len < nt_buffer_transfer_min that never reach the NT dispatcher. On Intel the default window is 1K..8M, so UCX_NT_BUFFER_TRANSFER_MIN=<size> silently drops ERMS for all copies below that size, and the user's BUILTIN_MEMCPY_MAX value is discarded. Can we gate the outer window on total_len in ucs_memcpy_relaxed() instead of overwriting the config?
There was a problem hiding this comment.
This is intentional and is the same trade-off discussed in the earlier
NT_BUFFER_TRANSFER_MIN thread.
A finite explicit threshold opts into transfer-level NT selection, so the
outer fragment-length ERMS window is closed to prevent fragments of an
eligible logical transfer from returning before the total_len policy is
evaluated. Explicit NT_BUFFER_TRANSFER_MIN=inf now preserves the configured
BUILTIN_MEMCPY_MAX because no transfer can be NT-eligible.
Gating the outer window on total_len would add another global load and
comparison before every copy in the latency-sensitive Intel ERMS range. I
prefer to keep that path unchanged and apply the explicit opt-in policy during
initialization. The consequence is intentional: a finite explicit NT
threshold disables the outer ERMS window globally, including for
below-threshold transfers.
| */ | ||
| const size_t min_nt_buffer_transfer_size = 3072; | ||
|
|
||
| if (ucs_likely(len < min_nt_buffer_transfer_size)) { |
There was a problem hiding this comment.
This skips NT stores for any fragment below 3072 even when total_len is far above nt_dest_threshold; previously fragments above 128 B still used streaming stores. For a transport whose segment size is under 3K the NT_DEST hint becomes a no-op — is that intended?
There was a problem hiding this comment.
Yes, this is intentional. The reworked kernels use 3072 bytes as a
per-fragment crossover, independently of total_len and nt_dest_threshold,
to avoid their vector and streaming-store setup for smaller fragments. A
transport that always segments below 3K therefore uses libc memcpy() for
those fragments even when the logical transfer carries NT_DEST. This is a
deliberate change from the old implementation's 128-byte cutoff.
| return; | ||
| } | ||
|
|
||
| /* |
There was a problem hiding this comment.
minor: this 24-line comment repeats what is documented at ucs_cpu_nt_bt_thresh_min() and in the NT_BUFFER_TRANSFER_MIN help string. pls condense to the "why" only (docs/CodeStyle.md: usually one line, document once in the most relevant area).
There was a problem hiding this comment.
This repeats the already resolved
NT_SOURCE documentation thread.
I prefer to keep this documentation because the implicit NT_SOURCE
fallthrough and the interaction between the outer and inner ERMS gates are not
obvious from the code. Fragments below 3072 still reach this function and
return from its initial memcpy() check.
| static UCS_F_ALWAYS_INLINE | ||
| size_t ucs_x86_nt_dst_buffer_transfer(void *dst, const void *src, size_t len, | ||
| size_t total_len) | ||
| /* 256B NT store (8x YMM). */ |
There was a problem hiding this comment.
minor: the four 256 B helpers differ only in load/store flavor. Can we generate them with a macro (or pass the load/store choice) to cut ~100 lines of duplication? The same applies to the two kernels, which differ only in stream vs store plus the final sfence.
There was a problem hiding this comment.
This is the same trade-off discussed in the earlier
explicit AVX2 helpers thread.
I prefer to keep the helpers and kernels explicit so the aligned versus
unaligned loads, regular versus streaming stores, and destination fence remain
directly visible and auditable. A macro would reduce source lines but would
not reduce generated code.
| len -= 128; | ||
| /* (2) ascending 256B NT-stream body to last full 256B. */ | ||
| src_aligned = ((UCS_PTR_BYTE_DIFF(dp, sp) & 31u) == 0u); | ||
| if (ucs_unlikely(src_aligned)) { |
There was a problem hiding this comment.
minor: ucs_unlikely(src_aligned) hints the wrong way here — (dp-sp) & 31 is 0 whenever both buffers are 64 B-aligned, which is the common case in the copy paths. Also the NT_SOURCE twin at L287 has no hint, so the two kernels disagree.
There was a problem hiding this comment.
The hint is intentional. On the shared-memory AM bcopy path, the
receive-descriptor base is cache-line aligned, but UCP writes its protocol
header first and packs the payload at hdr + 1. Common tag-eager header sizes
are 8, 16, and 24 bytes, so an aligned user source and aligned descriptor base
do not produce relatively aligned payload pointers modulo 32. The
destination-prefix copy advances both pointers equally, leaving the source
unaligned when the streaming body begins.
This preserves the pre-existing AVX2 implementation's
ucs_likely(source_is_unaligned) expectation. The unhinted NT_SOURCE branch
does not make the NT_DEST hint incorrect, so I prefer to retain it.
| } | ||
| } | ||
|
|
||
| #define ucs_x86_nt_dst_buffer_transfer ucs_x86_nt_dst_avx2_buffer_transfer |
There was a problem hiding this comment.
minor: there is only one implementation, so can we name the kernels ucs_x86_nt_dst_buffer_transfer/ucs_x86_nt_src_buffer_transfer directly and drop these aliases?
There was a problem hiding this comment.
This is the same request as the already resolved
ISA bridge thread.
I prefer to keep the aliases because the stacked AVX-512 follow-up uses them
as the compile-time bridge between AVX2 and AVX-512 while cpu.c keeps
identical dispatcher call sites.
|
|
||
| #include <ucs/arch/global_opts.h> | ||
| #include <ucs/config/parser.h> | ||
| #include <ucs/sys/string.h> |
There was a problem hiding this comment.
minor: ucs/sys/string.h looks unused — ucs_config_sprintf_memunits() comes from config/parser.h and UCS_MEMUNITS_* from config/types.h.
There was a problem hiding this comment.
ucs/sys/string.h is required here because this file uses
UCS_MEMUNITS_INF, which is defined in that header.
ucs_config_sprintf_memunits() comes from config/parser.h, but
config/types.h does not define UCS_MEMUNITS_INF, so I prefer to keep the
direct include.
What?
Rework the AVX2 NT buffer-transfer kernels to handle edges and tails internally, simplify dispatch, update AMD thresholds, and select ERMS for eligible NT source copies. Extend coverage for both vectorized and ERMS paths.
Why?
Feature: splitting as per comments from #11696
How?
#11696 is broken to logical PRs, this is the second in the series