Skip to content
Open
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
8 changes: 4 additions & 4 deletions docs/backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,10 @@ sd-cli -m model.safetensors -p "a cat" --conv-direct off,diffusion=on
- `on` requests direct 2D and 3D convolution.
- `off` uses the existing im2col-based convolution paths.
- `auto` first checks support for a representative F16-weight/F32-input direct
convolution graph. Vulkan then benchmarks direct convolution against im2col
using two small shapes per dimension, with warmup, output validation, and five
interleaved measurements per shape. Direct convolution must improve the sum of
median times by at least 5%; a failed probe defaults to `off`.
convolution graph. On Vulkan, direct 2D convolution is then enabled when its
output on two small shapes matches im2col (a failed probe defaults to `off`),
and direct 3D convolution stays `off`. The decision does not depend on timing,
so the same seed renders the same pixels on a busy and on an idle machine.
Comment on lines +236 to +239

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the performance guide's Vulkan auto rule

This new rule contradicts docs/performance.md:35-36, which still tells users that automatic Vulkan selection uses small benchmarks. Timing now has no effect on the 2D decision and 3D is always disabled, so users following the tuning guide receive an obsolete description of auto; update that relevant guide together with these two documentation changes.

AGENTS.md reference: AGENTS.md:L74-L76

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a2c3b97: docs/performance.md now says Vulkan auto checks direct 2D output against im2col, does not use timing, and keeps direct 3D off.

Other backends retain their implicit GEMM checks without benchmarking:
CUDA 2D requires a compiled Turing-or-newer MMA architecture; HIP 2D requires
the supported CDNA MFMA or RDNA WMMA architectures described below for flash
Expand Down
5 changes: 3 additions & 2 deletions docs/backend_probe.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ policy stays separate: `src/core/probe/flash_attn.cpp`, for example, defines att
shapes and its CPU/CUDA/HIP/Vulkan shortcuts, then calls `sd_run_backend_probe` for unknown
configurations. `src/core/probe/conv_direct.cpp` uses metadata-only probe graphs
to check direct 2D/3D convolution support, then enables known implicit GEMM
paths on non-Vulkan backends. Vulkan compares direct convolution with im2col
using the shared measurement machinery.
paths on non-Vulkan backends. Vulkan runs direct 2D convolution against im2col
through the shared measurement machinery for its output check only (the timing
is logged, not used), and keeps direct 3D convolution off.

For each representative shape, create a `BackendProbeCase`:

Expand Down
5 changes: 3 additions & 2 deletions docs/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ segment count change.
path. Performance depends on GPU architecture, backend, driver, model, and
tensor shapes, so enabling direct convolution can either improve or reduce speed.

Automatic selection uses small benchmarks on Vulkan and implicit GEMM checks
on other backends. It does not guarantee the fastest end-to-end generation.
Automatic selection checks direct 2D convolution output against im2col on
Vulkan (timing is not used; direct 3D stays off there) and uses implicit GEMM
checks on other backends. It does not guarantee the fastest end-to-end generation.
Compare `on` and `off` with the same model and generation settings over
repeated runs, keeping flash attention unchanged and separating startup/probe
time from steady-state generation time. See [backend selection](backend.md)
Expand Down
25 changes: 21 additions & 4 deletions src/core/probe/conv_direct.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ bool sd_probe_conv_direct(ggml_backend_t backend, bool conv3d) {
return false;
}
if (sd_backend_is(backend, "Vulkan")) {
// The decision must not depend on how busy the machine is. The two probe shapes run in well under a
// millisecond, so on a loaded host both paths measure the same submit overhead and the old 5% race
// flipped from run to run, changing the pixels of a fixed seed. Direct 3D convolution stays off: it is
// a tie on NVIDIA in the probe and fails its output check on RADV. Direct 2D convolution is on whenever its
// output matches im2col: measured faster and far smaller in VAE decodes on RADV (gfx1151) and NVIDIA.
if (conv3d) {
LOG_INFO("conv3d direct auto: %s -> off (Vulkan)", name);
return false;
}
std::vector<BackendProbeCase> cases;
for (const auto& shape : {std::pair<int, int>{32, 64}, {16, 128}}) {
BackendProbeCase test;
Expand All @@ -74,14 +83,22 @@ bool sd_probe_conv_direct(ggml_backend_t backend, bool conv3d) {
test.initialize = [conv3d](BackendProbeGraph& graph) { return initialize_conv_probe(graph, conv3d); };
cases.push_back(std::move(test));
}
const auto result = sd_run_backend_probe(backend, 0, cases);
// Only the output check decides, so one timed run each and no time budget a busy host could exhaust.
// No free-memory cutoff either: the probe graphs need a few MB, and the default 128 MiB floor would turn
// direct convolution off whenever another workload holds the VRAM. A device too full to allocate them
// still fails the probe (allocation failure -> off).
BackendProbeOptions options;
options.measurement_runs = 1;
options.time_budget_ms = 1e9;
options.min_free_device_bytes = 0;
const auto result = sd_run_backend_probe(backend, 0, cases, options);
Comment on lines +90 to +94

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove the remaining load-sensitive probe cutoff

When a Vulkan device has less than 128 MiB free at context initialization—for example, because another workload is using VRAM—this call still inherits BackendProbeOptions::min_free_device_bytes = 128 MiB. sd_run_backend_probe then returns insufficient probe memory before checking outputs (backend_probe.cpp:116-123), and this function selects off; the same device with more free memory selects on. Thus machine load can still change the convolution path and fixed-seed pixels despite this change's determinism goal, so the correctness-only probe should remove or substantially lower that blanket threshold.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, fixed in a2c3b97: the Vulkan conv probe now sets min_free_device_bytes = 0. The probe graphs need a few MB, so the only remaining memory dependence is a device too full to allocate them at all, which still fails the probe and selects off. The changed files compile cleanly; I could not rerun the Vulkan leg on this host this round.

if (!result.completed) {
LOG_WARN("conv%dd direct auto: %s -> off (%s)", dims, name, result.error.c_str());
return false;
}
LOG_INFO("conv%dd direct auto: %s -> %s (probe: im2col %.3f ms, direct %.3f ms)",
dims, name, result.candidate_faster ? "on" : "off", result.reference_ms, result.candidate_ms);
return result.candidate_faster;
LOG_INFO("conv%dd direct auto: %s -> on (output check passed; probe: im2col %.3f ms, direct %.3f ms)",
dims, name, result.reference_ms, result.candidate_ms);
return true;
}
if (sd_backend_supports_conv_implicit_gemm(backend, conv3d)) {
LOG_INFO("conv%dd direct auto: %s -> on (implicit GEMM)", dims, name);
Expand Down
Loading