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
1 change: 1 addition & 0 deletions backends/webgpu/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,7 @@ set(WEBGPU_SRCS
runtime/WebGPUBackend.cpp
runtime/WebGPUExecutionOptions.cpp
runtime/WebGPUGraph.cpp
runtime/passes/SwiGLU.cpp
runtime/WebGPUDelegateHeader.cpp
runtime/WebGPUDevice.cpp
runtime/WebGPUQueryPool.cpp
Expand Down
396 changes: 47 additions & 349 deletions backends/webgpu/runtime/WebGPUGraph.cpp

Large diffs are not rendered by default.

16 changes: 7 additions & 9 deletions backends/webgpu/runtime/WebGPUGraph.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -516,6 +516,13 @@ class WebGPUGraph {
return value_types_[id];
}

// Memory-aliasing group id for the tensor's shared buffer, or -1 if it has
// none; fusion passes use this to reject candidates aliased with something
// the planner may reuse outside the fusion's control.
int mem_obj_id(int id) const {
return tensor_mem_obj_ids_[id];
}

public:
// True when the sdpa K/V cache is stored f16-packed (runtime opt-in).
bool kv_f16() const {
Expand DownExpand Up@@ -681,15 +688,6 @@ class WebGPUGraph {
// resize hook.
void add_qkv_fused_dispatch(QkvFusionGroup& g);
void add_qkv_fused_hook(const QkvFusionGroup& g);

// SwiGLU fusion: emit ONE fused elementwise dispatch
// computing out = (gate * sigmoid(gate)) * up, replacing the sigmoid + 2
// muls. `out` is repointed to a private pooled buffer (aliasing guard);
// `gate` is likewise given a private pooled buffer at its producer op by the
// build() walk (the planner reuse-aliases up onto gate's slot, so up_proj
// would stomp gate before the fused reads it). Only used during build(); the
// detection maps are empty (inert) when no SwiGLU triple matches.
void add_swiglu_fused_dispatch(int gate_id, int up_id, int out_id);
};

} // namespace executorch::backends::webgpu
13 changes: 13 additions & 0 deletions backends/webgpu/runtime/WebGPUUtils.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>
Expand All@@ -31,6 +32,18 @@ inline uint64_t numel_of(const std::vector<int64_t>& dims) {
return numel(dims);
}

// fp32, non-null-buffer tensor with byte size matching its element count;
// the dtype/aliasing precondition fusion passes require of every operand.
inline bool is_fp32_tensor(const WebGPUTensor& tensor) {
if (tensor.is_int || tensor.elem_size != sizeof(float) ||
tensor.buffer == nullptr) {
return false;
}
const uint64_t elems = numel_of(tensor.dims);
return elems <= std::numeric_limits<size_t>::max() / sizeof(float) &&
tensor.nbytes == static_cast<size_t>(elems) * sizeof(float);
}

// Clamp workgroup size to device limit (SwiftShader caps at 128).
inline uint32_t clamp_workgroup_size(WGPUDevice device, uint32_t desired) {
WGPULimits limits = {};
Expand Down
15 changes: 7 additions & 8 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused.wgsl
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,14 +7,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
17 changes: 8 additions & 9 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused_wgsl.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@
namespace executorch::backends::webgpu {

// @generated from silu_mul_fused.wgsl - DO NOT EDIT.
// wgsl-sha256: 4b8ede66c5dbc9829ff48f745eb9ad48fa5a5200058baa532fbf34f78ec2f560
// wgsl-sha256: 7ba46c3ec15bfe4ab77a6a3e8e9f81dcb53a82328da953a3a9252fc7d470f461
inline constexpr const char* kSiluMulFusedWGSL = R"(
@group(0) @binding(0) var<storage, read> gate: array<f32>;
@group(0) @binding(1) var<storage, read> up: array<f32>;
Expand All@@ -24,14 +24,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
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
1 change: 1 addition & 0 deletions backends/webgpu/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,7 @@ set(WEBGPU_SRCS
runtime/WebGPUBackend.cpp
runtime/WebGPUExecutionOptions.cpp
runtime/WebGPUGraph.cpp
runtime/passes/SwiGLU.cpp
runtime/WebGPUDelegateHeader.cpp
runtime/WebGPUDevice.cpp
runtime/WebGPUQueryPool.cpp
Expand Down
396 changes: 47 additions & 349 deletions backends/webgpu/runtime/WebGPUGraph.cpp

Large diffs are not rendered by default.

16 changes: 7 additions & 9 deletions backends/webgpu/runtime/WebGPUGraph.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -516,6 +516,13 @@ class WebGPUGraph {
return value_types_[id];
}

// Memory-aliasing group id for the tensor's shared buffer, or -1 if it has
// none; fusion passes use this to reject candidates aliased with something
// the planner may reuse outside the fusion's control.
int mem_obj_id(int id) const {
return tensor_mem_obj_ids_[id];
}

public:
// True when the sdpa K/V cache is stored f16-packed (runtime opt-in).
bool kv_f16() const {
Expand DownExpand Up@@ -681,15 +688,6 @@ class WebGPUGraph {
// resize hook.
void add_qkv_fused_dispatch(QkvFusionGroup& g);
void add_qkv_fused_hook(const QkvFusionGroup& g);

// SwiGLU fusion: emit ONE fused elementwise dispatch
// computing out = (gate * sigmoid(gate)) * up, replacing the sigmoid + 2
// muls. `out` is repointed to a private pooled buffer (aliasing guard);
// `gate` is likewise given a private pooled buffer at its producer op by the
// build() walk (the planner reuse-aliases up onto gate's slot, so up_proj
// would stomp gate before the fused reads it). Only used during build(); the
// detection maps are empty (inert) when no SwiGLU triple matches.
void add_swiglu_fused_dispatch(int gate_id, int up_id, int out_id);
};

} // namespace executorch::backends::webgpu
13 changes: 13 additions & 0 deletions backends/webgpu/runtime/WebGPUUtils.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>
Expand All@@ -31,6 +32,18 @@ inline uint64_t numel_of(const std::vector<int64_t>& dims) {
return numel(dims);
}

// fp32, non-null-buffer tensor with byte size matching its element count;
// the dtype/aliasing precondition fusion passes require of every operand.
inline bool is_fp32_tensor(const WebGPUTensor& tensor) {
if (tensor.is_int || tensor.elem_size != sizeof(float) ||
tensor.buffer == nullptr) {
return false;
}
const uint64_t elems = numel_of(tensor.dims);
return elems <= std::numeric_limits<size_t>::max() / sizeof(float) &&
tensor.nbytes == static_cast<size_t>(elems) * sizeof(float);
}

// Clamp workgroup size to device limit (SwiftShader caps at 128).
inline uint32_t clamp_workgroup_size(WGPUDevice device, uint32_t desired) {
WGPULimits limits = {};
Expand Down
15 changes: 7 additions & 8 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused.wgsl
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,14 +7,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
17 changes: 8 additions & 9 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused_wgsl.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@
namespace executorch::backends::webgpu {

// @generated from silu_mul_fused.wgsl - DO NOT EDIT.
// wgsl-sha256: 4b8ede66c5dbc9829ff48f745eb9ad48fa5a5200058baa532fbf34f78ec2f560
// wgsl-sha256: 7ba46c3ec15bfe4ab77a6a3e8e9f81dcb53a82328da953a3a9252fc7d470f461
inline constexpr const char* kSiluMulFusedWGSL = R"(
@group(0) @binding(0) var<storage, read> gate: array<f32>;
@group(0) @binding(1) var<storage, read> up: array<f32>;
Expand All@@ -24,14 +24,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
1 change: 1 addition & 0 deletions backends/webgpu/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,7 @@ set(WEBGPU_SRCS
runtime/WebGPUBackend.cpp
runtime/WebGPUExecutionOptions.cpp
runtime/WebGPUGraph.cpp
runtime/passes/SwiGLU.cpp
runtime/WebGPUDelegateHeader.cpp
runtime/WebGPUDevice.cpp
runtime/WebGPUQueryPool.cpp
Expand Down
396 changes: 47 additions & 349 deletions backends/webgpu/runtime/WebGPUGraph.cpp

Large diffs are not rendered by default.

16 changes: 7 additions & 9 deletions backends/webgpu/runtime/WebGPUGraph.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -516,6 +516,13 @@ class WebGPUGraph {
return value_types_[id];
}

// Memory-aliasing group id for the tensor's shared buffer, or -1 if it has
// none; fusion passes use this to reject candidates aliased with something
// the planner may reuse outside the fusion's control.
int mem_obj_id(int id) const {
return tensor_mem_obj_ids_[id];
}

public:
// True when the sdpa K/V cache is stored f16-packed (runtime opt-in).
bool kv_f16() const {
Expand DownExpand Up@@ -681,15 +688,6 @@ class WebGPUGraph {
// resize hook.
void add_qkv_fused_dispatch(QkvFusionGroup& g);
void add_qkv_fused_hook(const QkvFusionGroup& g);

// SwiGLU fusion: emit ONE fused elementwise dispatch
// computing out = (gate * sigmoid(gate)) * up, replacing the sigmoid + 2
// muls. `out` is repointed to a private pooled buffer (aliasing guard);
// `gate` is likewise given a private pooled buffer at its producer op by the
// build() walk (the planner reuse-aliases up onto gate's slot, so up_proj
// would stomp gate before the fused reads it). Only used during build(); the
// detection maps are empty (inert) when no SwiGLU triple matches.
void add_swiglu_fused_dispatch(int gate_id, int up_id, int out_id);
};

} // namespace executorch::backends::webgpu
13 changes: 13 additions & 0 deletions backends/webgpu/runtime/WebGPUUtils.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>
Expand All@@ -31,6 +32,18 @@ inline uint64_t numel_of(const std::vector<int64_t>& dims) {
return numel(dims);
}

// fp32, non-null-buffer tensor with byte size matching its element count;
// the dtype/aliasing precondition fusion passes require of every operand.
inline bool is_fp32_tensor(const WebGPUTensor& tensor) {
if (tensor.is_int || tensor.elem_size != sizeof(float) ||
tensor.buffer == nullptr) {
return false;
}
const uint64_t elems = numel_of(tensor.dims);
return elems <= std::numeric_limits<size_t>::max() / sizeof(float) &&
tensor.nbytes == static_cast<size_t>(elems) * sizeof(float);
}

// Clamp workgroup size to device limit (SwiftShader caps at 128).
inline uint32_t clamp_workgroup_size(WGPUDevice device, uint32_t desired) {
WGPULimits limits = {};
Expand Down
15 changes: 7 additions & 8 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused.wgsl
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,14 +7,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
17 changes: 8 additions & 9 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused_wgsl.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@
namespace executorch::backends::webgpu {

// @generated from silu_mul_fused.wgsl - DO NOT EDIT.
// wgsl-sha256: 4b8ede66c5dbc9829ff48f745eb9ad48fa5a5200058baa532fbf34f78ec2f560
// wgsl-sha256: 7ba46c3ec15bfe4ab77a6a3e8e9f81dcb53a82328da953a3a9252fc7d470f461
inline constexpr const char* kSiluMulFusedWGSL = R"(
@group(0) @binding(0) var<storage, read> gate: array<f32>;
@group(0) @binding(1) var<storage, read> up: array<f32>;
Expand All@@ -24,14 +24,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
1 change: 1 addition & 0 deletions backends/webgpu/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,7 @@ set(WEBGPU_SRCS
runtime/WebGPUBackend.cpp
runtime/WebGPUExecutionOptions.cpp
runtime/WebGPUGraph.cpp
runtime/passes/SwiGLU.cpp
runtime/WebGPUDelegateHeader.cpp
runtime/WebGPUDevice.cpp
runtime/WebGPUQueryPool.cpp
Expand Down
396 changes: 47 additions & 349 deletions backends/webgpu/runtime/WebGPUGraph.cpp

Large diffs are not rendered by default.

16 changes: 7 additions & 9 deletions backends/webgpu/runtime/WebGPUGraph.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -516,6 +516,13 @@ class WebGPUGraph {
return value_types_[id];
}

// Memory-aliasing group id for the tensor's shared buffer, or -1 if it has
// none; fusion passes use this to reject candidates aliased with something
// the planner may reuse outside the fusion's control.
int mem_obj_id(int id) const {
return tensor_mem_obj_ids_[id];
}

public:
// True when the sdpa K/V cache is stored f16-packed (runtime opt-in).
bool kv_f16() const {
Expand DownExpand Up@@ -681,15 +688,6 @@ class WebGPUGraph {
// resize hook.
void add_qkv_fused_dispatch(QkvFusionGroup& g);
void add_qkv_fused_hook(const QkvFusionGroup& g);

// SwiGLU fusion: emit ONE fused elementwise dispatch
// computing out = (gate * sigmoid(gate)) * up, replacing the sigmoid + 2
// muls. `out` is repointed to a private pooled buffer (aliasing guard);
// `gate` is likewise given a private pooled buffer at its producer op by the
// build() walk (the planner reuse-aliases up onto gate's slot, so up_proj
// would stomp gate before the fused reads it). Only used during build(); the
// detection maps are empty (inert) when no SwiGLU triple matches.
void add_swiglu_fused_dispatch(int gate_id, int up_id, int out_id);
};

} // namespace executorch::backends::webgpu
13 changes: 13 additions & 0 deletions backends/webgpu/runtime/WebGPUUtils.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>
Expand All@@ -31,6 +32,18 @@ inline uint64_t numel_of(const std::vector<int64_t>& dims) {
return numel(dims);
}

// fp32, non-null-buffer tensor with byte size matching its element count;
// the dtype/aliasing precondition fusion passes require of every operand.
inline bool is_fp32_tensor(const WebGPUTensor& tensor) {
if (tensor.is_int || tensor.elem_size != sizeof(float) ||
tensor.buffer == nullptr) {
return false;
}
const uint64_t elems = numel_of(tensor.dims);
return elems <= std::numeric_limits<size_t>::max() / sizeof(float) &&
tensor.nbytes == static_cast<size_t>(elems) * sizeof(float);
}

// Clamp workgroup size to device limit (SwiftShader caps at 128).
inline uint32_t clamp_workgroup_size(WGPUDevice device, uint32_t desired) {
WGPULimits limits = {};
Expand Down
15 changes: 7 additions & 8 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused.wgsl
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,14 +7,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
17 changes: 8 additions & 9 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused_wgsl.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@
namespace executorch::backends::webgpu {

// @generated from silu_mul_fused.wgsl - DO NOT EDIT.
// wgsl-sha256: 4b8ede66c5dbc9829ff48f745eb9ad48fa5a5200058baa532fbf34f78ec2f560
// wgsl-sha256: 7ba46c3ec15bfe4ab77a6a3e8e9f81dcb53a82328da953a3a9252fc7d470f461
inline constexpr const char* kSiluMulFusedWGSL = R"(
@group(0) @binding(0) var<storage, read> gate: array<f32>;
@group(0) @binding(1) var<storage, read> up: array<f32>;
Expand All@@ -24,14 +24,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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
1 change: 1 addition & 0 deletions backends/webgpu/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,7 @@ set(WEBGPU_SRCS
runtime/WebGPUBackend.cpp
runtime/WebGPUExecutionOptions.cpp
runtime/WebGPUGraph.cpp
runtime/passes/SwiGLU.cpp
runtime/WebGPUDelegateHeader.cpp
runtime/WebGPUDevice.cpp
runtime/WebGPUQueryPool.cpp
Expand Down
396 changes: 47 additions & 349 deletions backends/webgpu/runtime/WebGPUGraph.cpp

Large diffs are not rendered by default.

16 changes: 7 additions & 9 deletions backends/webgpu/runtime/WebGPUGraph.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -516,6 +516,13 @@ class WebGPUGraph {
return value_types_[id];
}

// Memory-aliasing group id for the tensor's shared buffer, or -1 if it has
// none; fusion passes use this to reject candidates aliased with something
// the planner may reuse outside the fusion's control.
int mem_obj_id(int id) const {
return tensor_mem_obj_ids_[id];
}

public:
// True when the sdpa K/V cache is stored f16-packed (runtime opt-in).
bool kv_f16() const {
Expand DownExpand Up@@ -681,15 +688,6 @@ class WebGPUGraph {
// resize hook.
void add_qkv_fused_dispatch(QkvFusionGroup& g);
void add_qkv_fused_hook(const QkvFusionGroup& g);

// SwiGLU fusion: emit ONE fused elementwise dispatch
// computing out = (gate * sigmoid(gate)) * up, replacing the sigmoid + 2
// muls. `out` is repointed to a private pooled buffer (aliasing guard);
// `gate` is likewise given a private pooled buffer at its producer op by the
// build() walk (the planner reuse-aliases up onto gate's slot, so up_proj
// would stomp gate before the fused reads it). Only used during build(); the
// detection maps are empty (inert) when no SwiGLU triple matches.
void add_swiglu_fused_dispatch(int gate_id, int up_id, int out_id);
};

} // namespace executorch::backends::webgpu
13 changes: 13 additions & 0 deletions backends/webgpu/runtime/WebGPUUtils.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>
Expand All@@ -31,6 +32,18 @@ inline uint64_t numel_of(const std::vector<int64_t>& dims) {
return numel(dims);
}

// fp32, non-null-buffer tensor with byte size matching its element count;
// the dtype/aliasing precondition fusion passes require of every operand.
inline bool is_fp32_tensor(const WebGPUTensor& tensor) {
if (tensor.is_int || tensor.elem_size != sizeof(float) ||
tensor.buffer == nullptr) {
return false;
}
const uint64_t elems = numel_of(tensor.dims);
return elems <= std::numeric_limits<size_t>::max() / sizeof(float) &&
tensor.nbytes == static_cast<size_t>(elems) * sizeof(float);
}

// Clamp workgroup size to device limit (SwiftShader caps at 128).
inline uint32_t clamp_workgroup_size(WGPUDevice device, uint32_t desired) {
WGPULimits limits = {};
Expand Down
15 changes: 7 additions & 8 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused.wgsl
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,14 +7,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
17 changes: 8 additions & 9 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused_wgsl.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@
namespace executorch::backends::webgpu {

// @generated from silu_mul_fused.wgsl - DO NOT EDIT.
// wgsl-sha256: 4b8ede66c5dbc9829ff48f745eb9ad48fa5a5200058baa532fbf34f78ec2f560
// wgsl-sha256: 7ba46c3ec15bfe4ab77a6a3e8e9f81dcb53a82328da953a3a9252fc7d470f461
inline constexpr const char* kSiluMulFusedWGSL = R"(
@group(0) @binding(0) var<storage, read> gate: array<f32>;
@group(0) @binding(1) var<storage, read> up: array<f32>;
Expand All@@ -24,14 +24,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
1 change: 1 addition & 0 deletions backends/webgpu/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,7 @@ set(WEBGPU_SRCS
runtime/WebGPUBackend.cpp
runtime/WebGPUExecutionOptions.cpp
runtime/WebGPUGraph.cpp
runtime/passes/SwiGLU.cpp
runtime/WebGPUDelegateHeader.cpp
runtime/WebGPUDevice.cpp
runtime/WebGPUQueryPool.cpp
Expand Down
396 changes: 47 additions & 349 deletions backends/webgpu/runtime/WebGPUGraph.cpp

Large diffs are not rendered by default.

16 changes: 7 additions & 9 deletions backends/webgpu/runtime/WebGPUGraph.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -516,6 +516,13 @@ class WebGPUGraph {
return value_types_[id];
}

// Memory-aliasing group id for the tensor's shared buffer, or -1 if it has
// none; fusion passes use this to reject candidates aliased with something
// the planner may reuse outside the fusion's control.
int mem_obj_id(int id) const {
return tensor_mem_obj_ids_[id];
}

public:
// True when the sdpa K/V cache is stored f16-packed (runtime opt-in).
bool kv_f16() const {
Expand DownExpand Up@@ -681,15 +688,6 @@ class WebGPUGraph {
// resize hook.
void add_qkv_fused_dispatch(QkvFusionGroup& g);
void add_qkv_fused_hook(const QkvFusionGroup& g);

// SwiGLU fusion: emit ONE fused elementwise dispatch
// computing out = (gate * sigmoid(gate)) * up, replacing the sigmoid + 2
// muls. `out` is repointed to a private pooled buffer (aliasing guard);
// `gate` is likewise given a private pooled buffer at its producer op by the
// build() walk (the planner reuse-aliases up onto gate's slot, so up_proj
// would stomp gate before the fused reads it). Only used during build(); the
// detection maps are empty (inert) when no SwiGLU triple matches.
void add_swiglu_fused_dispatch(int gate_id, int up_id, int out_id);
};

} // namespace executorch::backends::webgpu
13 changes: 13 additions & 0 deletions backends/webgpu/runtime/WebGPUUtils.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>
Expand All@@ -31,6 +32,18 @@ inline uint64_t numel_of(const std::vector<int64_t>& dims) {
return numel(dims);
}

// fp32, non-null-buffer tensor with byte size matching its element count;
// the dtype/aliasing precondition fusion passes require of every operand.
inline bool is_fp32_tensor(const WebGPUTensor& tensor) {
if (tensor.is_int || tensor.elem_size != sizeof(float) ||
tensor.buffer == nullptr) {
return false;
}
const uint64_t elems = numel_of(tensor.dims);
return elems <= std::numeric_limits<size_t>::max() / sizeof(float) &&
tensor.nbytes == static_cast<size_t>(elems) * sizeof(float);
}

// Clamp workgroup size to device limit (SwiftShader caps at 128).
inline uint32_t clamp_workgroup_size(WGPUDevice device, uint32_t desired) {
WGPULimits limits = {};
Expand Down
15 changes: 7 additions & 8 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused.wgsl
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,14 +7,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
17 changes: 8 additions & 9 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused_wgsl.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@
namespace executorch::backends::webgpu {

// @generated from silu_mul_fused.wgsl - DO NOT EDIT.
// wgsl-sha256: 4b8ede66c5dbc9829ff48f745eb9ad48fa5a5200058baa532fbf34f78ec2f560
// wgsl-sha256: 7ba46c3ec15bfe4ab77a6a3e8e9f81dcb53a82328da953a3a9252fc7d470f461
inline constexpr const char* kSiluMulFusedWGSL = R"(
@group(0) @binding(0) var<storage, read> gate: array<f32>;
@group(0) @binding(1) var<storage, read> up: array<f32>;
Expand All@@ -24,14 +24,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
1 change: 1 addition & 0 deletions backends/webgpu/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,7 @@ set(WEBGPU_SRCS
runtime/WebGPUBackend.cpp
runtime/WebGPUExecutionOptions.cpp
runtime/WebGPUGraph.cpp
runtime/passes/SwiGLU.cpp
runtime/WebGPUDelegateHeader.cpp
runtime/WebGPUDevice.cpp
runtime/WebGPUQueryPool.cpp
Expand Down
396 changes: 47 additions & 349 deletions backends/webgpu/runtime/WebGPUGraph.cpp

Large diffs are not rendered by default.

16 changes: 7 additions & 9 deletions backends/webgpu/runtime/WebGPUGraph.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -516,6 +516,13 @@ class WebGPUGraph {
return value_types_[id];
}

// Memory-aliasing group id for the tensor's shared buffer, or -1 if it has
// none; fusion passes use this to reject candidates aliased with something
// the planner may reuse outside the fusion's control.
int mem_obj_id(int id) const {
return tensor_mem_obj_ids_[id];
}

public:
// True when the sdpa K/V cache is stored f16-packed (runtime opt-in).
bool kv_f16() const {
Expand DownExpand Up@@ -681,15 +688,6 @@ class WebGPUGraph {
// resize hook.
void add_qkv_fused_dispatch(QkvFusionGroup& g);
void add_qkv_fused_hook(const QkvFusionGroup& g);

// SwiGLU fusion: emit ONE fused elementwise dispatch
// computing out = (gate * sigmoid(gate)) * up, replacing the sigmoid + 2
// muls. `out` is repointed to a private pooled buffer (aliasing guard);
// `gate` is likewise given a private pooled buffer at its producer op by the
// build() walk (the planner reuse-aliases up onto gate's slot, so up_proj
// would stomp gate before the fused reads it). Only used during build(); the
// detection maps are empty (inert) when no SwiGLU triple matches.
void add_swiglu_fused_dispatch(int gate_id, int up_id, int out_id);
};

} // namespace executorch::backends::webgpu
13 changes: 13 additions & 0 deletions backends/webgpu/runtime/WebGPUUtils.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>
Expand All@@ -31,6 +32,18 @@ inline uint64_t numel_of(const std::vector<int64_t>& dims) {
return numel(dims);
}

// fp32, non-null-buffer tensor with byte size matching its element count;
// the dtype/aliasing precondition fusion passes require of every operand.
inline bool is_fp32_tensor(const WebGPUTensor& tensor) {
if (tensor.is_int || tensor.elem_size != sizeof(float) ||
tensor.buffer == nullptr) {
return false;
}
const uint64_t elems = numel_of(tensor.dims);
return elems <= std::numeric_limits<size_t>::max() / sizeof(float) &&
tensor.nbytes == static_cast<size_t>(elems) * sizeof(float);
}

// Clamp workgroup size to device limit (SwiftShader caps at 128).
inline uint32_t clamp_workgroup_size(WGPUDevice device, uint32_t desired) {
WGPULimits limits = {};
Expand Down
15 changes: 7 additions & 8 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused.wgsl
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,14 +7,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
17 changes: 8 additions & 9 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused_wgsl.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@
namespace executorch::backends::webgpu {

// @generated from silu_mul_fused.wgsl - DO NOT EDIT.
// wgsl-sha256: 4b8ede66c5dbc9829ff48f745eb9ad48fa5a5200058baa532fbf34f78ec2f560
// wgsl-sha256: 7ba46c3ec15bfe4ab77a6a3e8e9f81dcb53a82328da953a3a9252fc7d470f461
inline constexpr const char* kSiluMulFusedWGSL = R"(
@group(0) @binding(0) var<storage, read> gate: array<f32>;
@group(0) @binding(1) var<storage, read> up: array<f32>;
Expand All@@ -24,14 +24,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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
1 change: 1 addition & 0 deletions backends/webgpu/CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,7 @@ set(WEBGPU_SRCS
runtime/WebGPUBackend.cpp
runtime/WebGPUExecutionOptions.cpp
runtime/WebGPUGraph.cpp
runtime/passes/SwiGLU.cpp
runtime/WebGPUDelegateHeader.cpp
runtime/WebGPUDevice.cpp
runtime/WebGPUQueryPool.cpp
Expand Down
396 changes: 47 additions & 349 deletions backends/webgpu/runtime/WebGPUGraph.cpp

Large diffs are not rendered by default.

16 changes: 7 additions & 9 deletions backends/webgpu/runtime/WebGPUGraph.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -516,6 +516,13 @@ class WebGPUGraph {
return value_types_[id];
}

// Memory-aliasing group id for the tensor's shared buffer, or -1 if it has
// none; fusion passes use this to reject candidates aliased with something
// the planner may reuse outside the fusion's control.
int mem_obj_id(int id) const {
return tensor_mem_obj_ids_[id];
}

public:
// True when the sdpa K/V cache is stored f16-packed (runtime opt-in).
bool kv_f16() const {
Expand DownExpand Up@@ -681,15 +688,6 @@ class WebGPUGraph {
// resize hook.
void add_qkv_fused_dispatch(QkvFusionGroup& g);
void add_qkv_fused_hook(const QkvFusionGroup& g);

// SwiGLU fusion: emit ONE fused elementwise dispatch
// computing out = (gate * sigmoid(gate)) * up, replacing the sigmoid + 2
// muls. `out` is repointed to a private pooled buffer (aliasing guard);
// `gate` is likewise given a private pooled buffer at its producer op by the
// build() walk (the planner reuse-aliases up onto gate's slot, so up_proj
// would stomp gate before the fused reads it). Only used during build(); the
// detection maps are empty (inert) when no SwiGLU triple matches.
void add_swiglu_fused_dispatch(int gate_id, int up_id, int out_id);
};

} // namespace executorch::backends::webgpu
13 changes: 13 additions & 0 deletions backends/webgpu/runtime/WebGPUUtils.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>
Expand All@@ -31,6 +32,18 @@ inline uint64_t numel_of(const std::vector<int64_t>& dims) {
return numel(dims);
}

// fp32, non-null-buffer tensor with byte size matching its element count;
// the dtype/aliasing precondition fusion passes require of every operand.
inline bool is_fp32_tensor(const WebGPUTensor& tensor) {
if (tensor.is_int || tensor.elem_size != sizeof(float) ||
tensor.buffer == nullptr) {
return false;
}
const uint64_t elems = numel_of(tensor.dims);
return elems <= std::numeric_limits<size_t>::max() / sizeof(float) &&
tensor.nbytes == static_cast<size_t>(elems) * sizeof(float);
}

// Clamp workgroup size to device limit (SwiftShader caps at 128).
inline uint32_t clamp_workgroup_size(WGPUDevice device, uint32_t desired) {
WGPULimits limits = {};
Expand Down
15 changes: 7 additions & 8 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused.wgsl
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,14 +7,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
17 changes: 8 additions & 9 deletions backends/webgpu/runtime/ops/mul/silu_mul_fused_wgsl.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@
namespace executorch::backends::webgpu {

// @generated from silu_mul_fused.wgsl - DO NOT EDIT.
// wgsl-sha256: 4b8ede66c5dbc9829ff48f745eb9ad48fa5a5200058baa532fbf34f78ec2f560
// wgsl-sha256: 7ba46c3ec15bfe4ab77a6a3e8e9f81dcb53a82328da953a3a9252fc7d470f461
inline constexpr const char* kSiluMulFusedWGSL = R"(
@group(0) @binding(0) var<storage, read> gate: array<f32>;
@group(0) @binding(1) var<storage, read> up: array<f32>;
Expand All@@ -24,14 +24,13 @@ struct Params {
}
@group(0) @binding(3) var<uniform> params: Params;

// Fused SwiGLU activation: output = (g * sigmoid(g)) * up, folding the separate
// sigmoid(gate) -> mul(gate,sig)=silu -> mul(silu,up) triple into one dispatch.
// sigmoid + silu are computed in registers (never written to memory), so gate + up
// are read once and one output is written. The sigmoid form (1/(1+exp(-x))) and the
// multiply order match the original ops -> bit-exact.
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
override wg_size: u32 = 64u;

@compute @workgroup_size(wg_size, 1, 1)
fn main(
@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(num_workgroups) num_workgroups: vec3<u32>) {
let idx = gid.x + gid.y * (num_workgroups.x * wg_size);
if (idx >= params.num_elements) {
return;
}
Expand Down
Loading
Loading