Commit b393d73bb for llama.cpp
commit b393d73bb0448ad101fe84cc77f3eee465fb947d
Author: Masashi Yoshimura <yoshimura.masashi.frbs@gmail.com>
Date: Wed Nov 4 22:46:06 2026 +0900
webgpu: return maxBufferSize from get_max_size and avoid whole-buffer bindings in memset/clear (#31348)
* Fix get_max_size and improve the memset shader
* Fix buffer_memset to handle the buffer size more than max binding size.
diff --git a/ggml/src/ggml-webgpu/ggml-webgpu.cpp b/ggml/src/ggml-webgpu/ggml-webgpu.cpp
index bb2deb9da..ea3d91f3b 100644
--- a/ggml/src/ggml-webgpu/ggml-webgpu.cpp
+++ b/ggml/src/ggml-webgpu/ggml-webgpu.cpp
@@ -182,7 +182,6 @@ struct webgpu_capabilities {
uint32_t subgroup_size = 0;
uint32_t min_subgroup_size = 0;
uint32_t max_subgroup_size = 0;
- size_t memset_bytes_per_thread;
};
// Stores global webgpu members
@@ -256,8 +255,6 @@ struct webgpu_context_struct {
wgpu::ComputePassEncoder active_compute_pass;
bool batch_compute_passes = true;
- size_t memset_bytes_per_thread;
-
#ifdef GGML_WEBGPU_GPU_PROFILE
// Profiling: per-shader GPU time in ms
std::unordered_map<std::string, double> shader_gpu_time_ms;
@@ -672,38 +669,54 @@ static void ggml_backend_webgpu_buffer_memset(webgpu_global_context & ctx,
size_t size) {
std::lock_guard<std::recursive_mutex> lock(ctx->mutex);
- std::vector<uint32_t> params = { (uint32_t) offset, (uint32_t) size, value };
- std::vector<wgpu::BindGroupEntry> entries = { ggml_webgpu_make_bind_group_entry(0, buf, 0, buf.GetSize()) };
- size_t bytes_per_wg =
- ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup * ctx->capabilities.memset_bytes_per_thread;
- uint32_t wg_x = CEIL_DIV(size + 3, bytes_per_wg);
-
- ctx->queue.WriteBuffer(ctx->memset_params_buf, 0, params.data(), params.size() * sizeof(uint32_t));
-
- wgpu::BindGroupEntry params_entry = {};
- params_entry.binding = 1;
- params_entry.buffer = ctx->memset_params_buf;
- params_entry.offset = 0;
- params_entry.size = WEBGPU_PARAMS_BUF_SIZE_BYTES;
- entries.push_back(params_entry);
-
- wgpu::BindGroupDescriptor bind_group_desc;
- bind_group_desc.layout = ctx->memset_pipeline.pipeline.GetBindGroupLayout(0);
- bind_group_desc.entryCount = entries.size();
- bind_group_desc.entries = entries.data();
- bind_group_desc.label = ctx->memset_pipeline.name.c_str();
- wgpu::BindGroup bind_group = ctx->device.CreateBindGroup(&bind_group_desc);
-
- wgpu::CommandEncoder encoder = ctx->device.CreateCommandEncoder();
- wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
- pass.SetPipeline(ctx->memset_pipeline.pipeline);
- pass.SetBindGroup(0, bind_group);
- pass.DispatchWorkgroups(wg_x, 1, 1);
- pass.End();
-
- wgpu::CommandBuffer command = encoder.Finish();
- std::vector<wgpu::CommandBuffer> commands = { command };
- ctx->queue.Submit(commands.size(), commands.data());
+ const size_t max_bind_size = ctx->capabilities.limits.maxStorageBufferBindingSize;
+ const size_t max_wg_size = ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
+
+ while (size > 0) {
+ const size_t align_offset = offset & ~(ctx->capabilities.limits.minStorageBufferOffsetAlignment - 1);
+ const size_t lead = offset - align_offset;
+ const size_t chunk_size = std::min(size, max_bind_size - lead);
+ const size_t bind_size = ROUNDUP_POW2(chunk_size + lead, WEBGPU_STORAGE_BUF_BINDING_MULT);
+
+ std::vector<uint32_t> params = { (uint32_t) lead, (uint32_t) chunk_size, value };
+ std::vector<wgpu::BindGroupEntry> entries = { ggml_webgpu_make_bind_group_entry(0, buf, align_offset,
+ bind_size) };
+ uint32_t wg_x;
+ uint32_t wg_y;
+ uint32_t n_values = CEIL_DIV(chunk_size + lead, sizeof(uint32_t));
+ uint32_t total_wg = CEIL_DIV(n_values, max_wg_size);
+ compute_2d_workgroups(total_wg, ctx->capabilities.limits.maxComputeWorkgroupsPerDimension, wg_x, wg_y);
+
+ ctx->queue.WriteBuffer(ctx->memset_params_buf, 0, params.data(), params.size() * sizeof(uint32_t));
+
+ wgpu::BindGroupEntry params_entry = {};
+ params_entry.binding = 1;
+ params_entry.buffer = ctx->memset_params_buf;
+ params_entry.offset = 0;
+ params_entry.size = WEBGPU_PARAMS_BUF_SIZE_BYTES;
+ entries.push_back(params_entry);
+
+ wgpu::BindGroupDescriptor bind_group_desc;
+ bind_group_desc.layout = ctx->memset_pipeline.pipeline.GetBindGroupLayout(0);
+ bind_group_desc.entryCount = entries.size();
+ bind_group_desc.entries = entries.data();
+ bind_group_desc.label = ctx->memset_pipeline.name.c_str();
+ wgpu::BindGroup bind_group = ctx->device.CreateBindGroup(&bind_group_desc);
+
+ wgpu::CommandEncoder encoder = ctx->device.CreateCommandEncoder();
+ wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+ pass.SetPipeline(ctx->memset_pipeline.pipeline);
+ pass.SetBindGroup(0, bind_group);
+ pass.DispatchWorkgroups(wg_x, wg_y, 1);
+ pass.End();
+
+ wgpu::CommandBuffer command = encoder.Finish();
+ std::vector<wgpu::CommandBuffer> commands = { command };
+ ctx->queue.Submit(commands.size(), commands.data());
+
+ offset += chunk_size;
+ size -= chunk_size;
+ }
}
/** End WebGPU Actions */
@@ -4006,12 +4019,10 @@ static size_t ggml_backend_webgpu_buffer_type_get_alignment(ggml_backend_buffer_
return dev_ctx->webgpu_global_ctx->capabilities.limits.minStorageBufferOffsetAlignment;
}
-// maxBufferSize might be larger, but you can't bind more than
-// maxStorageBufferBindingSize to a single binding.
static size_t ggml_backend_webgpu_buffer_type_get_max_size(ggml_backend_buffer_type_t buft) {
ggml_backend_webgpu_device_context * dev_ctx =
static_cast<ggml_backend_webgpu_device_context *>(buft->device->context);
- return dev_ctx->webgpu_global_ctx->capabilities.limits.maxStorageBufferBindingSize;
+ return dev_ctx->webgpu_global_ctx->capabilities.limits.maxBufferSize;
}
static size_t ggml_backend_webgpu_buffer_type_get_alloc_size(ggml_backend_buffer_type_t buft,
@@ -4176,17 +4187,9 @@ static ggml_guid_t ggml_backend_webgpu_guid(void) {
}
static void ggml_webgpu_init_memset_pipeline(webgpu_global_context & ctx) {
- // we use the maximum workgroup size for the memset pipeline
- size_t max_threads = ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup *
- ctx->capabilities.limits.maxComputeWorkgroupsPerDimension;
- // Size the bytes_per_thread so that the largest buffer size can be handled
- ctx->capabilities.memset_bytes_per_thread =
- CEIL_DIV(ctx->capabilities.limits.maxStorageBufferBindingSize, max_threads);
- std::vector<wgpu::ConstantEntry> constants(2);
+ std::vector<wgpu::ConstantEntry> constants(1);
constants[0].key = "wg_size";
constants[0].value = ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
- constants[1].key = "bytes_per_thread";
- constants[1].value = ctx->capabilities.memset_bytes_per_thread;
ctx->memset_pipeline = ggml_webgpu_create_pipeline(ctx->device, wgsl_memset, "memset", constants);
}
diff --git a/ggml/src/ggml-webgpu/wgsl-shaders/memset.wgsl b/ggml/src/ggml-webgpu/wgsl-shaders/memset.wgsl
index 194d2d6f5..af5a23bc0 100644
--- a/ggml/src/ggml-webgpu/wgsl-shaders/memset.wgsl
+++ b/ggml/src/ggml-webgpu/wgsl-shaders/memset.wgsl
@@ -11,30 +11,33 @@ struct Params {
var<uniform> params: Params;
override wg_size: u32;
-override bytes_per_thread: u32;
@compute @workgroup_size(wg_size)
-fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
- let i = gid.x * bytes_per_thread;
+fn main(@builtin(workgroup_id) wid: vec3<u32>,
+ @builtin(num_workgroups) num_wg: vec3<u32>,
+ @builtin(local_invocation_id) lid: vec3<u32>) {
+
+ let i = (wid.x + wid.y * num_wg.x) * wg_size + lid.x;
let start = params.offset;
let end = params.offset + params.size;
+ let id = (start >> 2u) + i;
+ let id_u32 = id * 4;
+
+ if (id_u32 >= end) {
+ return;
+ }
+
+ let lo = max(start, id_u32) - id_u32;
+ let hi = min(end, id_u32 + 4u) - id_u32;
+ var mask = 0xFFFFFFFFu << (8u * lo);
+ if (hi < 4u) {
+ mask &= (1u << (8u * hi)) - 1u;
+ }
- for (var j: u32 = 0u; j < bytes_per_thread; j += 4) {
- let byte_index = start + i + j;
- if (byte_index + 4 <= end) {
- output_buffer[byte_index >> 2] = params.value;
- } else {
- // Handle tail (unaligned)
- for (var k: u32 = 0; k < 4; k++) {
- let idx = byte_index + k;
- if (idx < end) {
- let word_idx = idx >> 2;
- let bit_offset = (idx & 3) * 8u;
- let mask = ~(0xffu << bit_offset);
- let existing = output_buffer[word_idx];
- output_buffer[word_idx] = (existing & mask) | (params.value & (0xffu << bit_offset));
- }
- }
- }
+ if (mask == 0xFFFFFFFFu) {
+ output_buffer[id] = params.value;
+ } else {
+ let existing = output_buffer[id];
+ output_buffer[id] = (existing & ~(mask)) | (params.value & mask);
}
}