Skip to content

Commit 42e8ae8

Browse files
committed
[Perf] Cache adstack-sizer metadata per task across SPIR-V + LLVM-GPU; per-snode / DeviceAllocation invalidation
1 parent 4240699 commit 42e8ae8

19 files changed

Lines changed: 1346 additions & 399 deletions

quadrants/codegen/llvm/codegen_llvm.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include "llvm/IR/Module.h"
88
#include "llvm/Linker/Linker.h"
99
#include "quadrants/analysis/offline_cache_util.h"
10+
#include "quadrants/ir/analysis.h"
11+
#include "quadrants/ir/snode.h"
1012
#include "quadrants/ir/statements.h"
1113
#include "quadrants/ir/transforms.h"
1214
#include "quadrants/program/extension.h"
@@ -1975,6 +1977,39 @@ void TaskCodeGenLLVM::finalize_offloaded_task_function() {
19751977
current_task->ad_stack.allocas = ad_stack_allocas_info_;
19761978
current_task->ad_stack.size_exprs = ad_stack_size_exprs_;
19771979
current_task->ad_stack.bound_expr = ad_stack_static_bound_expr_;
1980+
// Snodes the task body mutates. Persisted on `OffloadedTask::snode_writes` so the LLVM
1981+
// launcher can invalidate the per-task adstack metadata cache when a kernel that runs in
1982+
// between mutated a SNode an enclosing `size_expr::FieldLoad` reads. Mirrors the SPIR-V
1983+
// analogue in `spirv_codegen.cpp`. Sorted + deduplicated for stable serialisation.
1984+
if (current_offload != nullptr) {
1985+
auto snode_rw = irpass::analysis::gather_snode_read_writes(current_offload);
1986+
current_task->snode_writes.reserve(snode_rw.second.size());
1987+
for (auto *s : snode_rw.second) {
1988+
if (s != nullptr) {
1989+
current_task->snode_writes.push_back(s->id);
1990+
}
1991+
}
1992+
std::sort(current_task->snode_writes.begin(), current_task->snode_writes.end());
1993+
current_task->snode_writes.erase(
1994+
std::unique(current_task->snode_writes.begin(), current_task->snode_writes.end()),
1995+
current_task->snode_writes.end());
1996+
// Ndarray args this task writes to. Same role as `snode_writes` but for ndarray data;
1997+
// covers `size_expr::ExternalTensorRead` invalidation. The first element of each
1998+
// `arg_id_path` key is the kernel-arg slot, which is what `Program::ndarray_data_gen_`
1999+
// is keyed by (via the bound DeviceAllocation).
2000+
auto arr_access = irpass::detect_external_ptr_access_in_task(current_offload);
2001+
for (const auto &kv : arr_access) {
2002+
if ((static_cast<uint32_t>(kv.second) & static_cast<uint32_t>(irpass::ExternalPtrAccess::WRITE)) == 0) {
2003+
continue;
2004+
}
2005+
if (!kv.first.empty()) {
2006+
current_task->arr_writes.push_back(kv.first.front());
2007+
}
2008+
}
2009+
std::sort(current_task->arr_writes.begin(), current_task->arr_writes.end());
2010+
current_task->arr_writes.erase(std::unique(current_task->arr_writes.begin(), current_task->arr_writes.end()),
2011+
current_task->arr_writes.end());
2012+
}
19782013
}
19792014

19802015
// entry_block should jump to the body after all allocas are inserted

quadrants/codegen/llvm/llvm_compiled_data.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,26 @@ class OffloadedTask {
9595
int dynamic_shared_array_bytes{0};
9696
AdStackSizingInfo ad_stack{};
9797

98+
// Snode IDs this task writes to (read-modify-write counts as a write). Computed at codegen time
99+
// by walking the offloaded IR with `gather_snode_read_writes`. Consumed at launch time: each id
100+
// here bumps `Program::snode_write_gen_[id]` so the per-task adstack metadata cache invalidates
101+
// whenever a kernel that ran since the cache was recorded mutated a SNode a downstream
102+
// `size_expr::FieldLoad` may read. Mirrors the SPIR-V `TaskAttributes::snode_writes` field.
103+
std::vector<int> snode_writes;
104+
// Argument arg_ids this task writes to (WRITE bit set in `irpass::detect_external_ptr_access_in_task`).
105+
// Consumed at launch time to bump `Program::ndarray_data_gen_` for the bound DeviceAllocation so
106+
// the per-task adstack metadata cache invalidates when a kernel that ran since the cache was
107+
// recorded mutated an ndarray a downstream `size_expr::ExternalTensorRead` reads. Mirrors the
108+
// SPIR-V `KernelContextAttributes::arr_access` WRITE-bit set, but stored per-task here because
109+
// LLVM codegen does not aggregate `arr_access` to the kernel level.
110+
std::vector<int> arr_writes;
111+
98112
explicit OffloadedTask(const std::string &name = "",
99113
int block_dim = 0,
100114
int grid_dim = 0,
101115
int dynamic_shared_array_bytes = 0)
102116
: name(name), block_dim(block_dim), grid_dim(grid_dim), dynamic_shared_array_bytes(dynamic_shared_array_bytes) {};
103-
QD_IO_DEF(name, block_dim, grid_dim, dynamic_shared_array_bytes, ad_stack);
117+
QD_IO_DEF(name, block_dim, grid_dim, dynamic_shared_array_bytes, ad_stack, snode_writes, arr_writes);
104118
};
105119

106120
struct LLVMCompiledTask {

quadrants/codegen/spirv/kernel_utils.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,15 @@ struct TaskAttributes {
212212
};
213213
AdStackSizingAttribs ad_stack;
214214

215+
// Snode IDs this task writes to (read-modify-write counts as a write). Computed at SPIR-V codegen time
216+
// by walking the offloaded IR with `gather_snode_read_writes`. Consumed by the SPIR-V launcher on every
217+
// `launch_kernel` call: each id here bumps `Program::snode_write_gen_[id]` so the per-task adstack
218+
// metadata cache invalidates whenever a kernel that ran since the cache was recorded mutated a SNode
219+
// a downstream `size_expr::FieldLoad` may read. Stored as raw IDs (not `SNode *`) so the field
220+
// survives offline-cache load-store; the runtime resolves the pointer on demand via
221+
// `Program::get_snode_by_id` only if it ever needs to call into snode-specific APIs.
222+
std::vector<int> snode_writes;
223+
215224
static std::string buffers_name(BufferInfo b);
216225

217226
std::string debug_string() const;
@@ -222,7 +231,8 @@ struct TaskAttributes {
222231
task_type,
223232
buffer_binds,
224233
range_for_attribs,
225-
ad_stack);
234+
ad_stack,
235+
snode_writes);
226236
};
227237

228238
/**

quadrants/codegen/spirv/spirv_codegen.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
#include "quadrants/codegen/spirv/spirv_ir_builder.h"
1818
#include "quadrants/codegen/spirv/detail/spirv_codegen.h"
1919
#include "quadrants/codegen/spirv/spirv_shared_array_retyping.h"
20+
#include "quadrants/ir/analysis.h"
2021
#include "quadrants/ir/transforms.h"
22+
#include "quadrants/ir/snode.h"
2123
#include "quadrants/math/arithmetic.h"
2224
#include "quadrants/codegen/ir_dump.h"
2325

@@ -198,6 +200,23 @@ TaskCodegen::Result TaskCodegen::run() {
198200
task_attribs_.ad_stack.per_thread_stride_float_compile_time = ad_stack_heap_per_thread_stride_float_;
199201
task_attribs_.ad_stack.per_thread_stride_int_compile_time = ad_stack_heap_per_thread_stride_int_;
200202

203+
// Snodes the task body mutates (any `GlobalStore` or `AtomicOp` whose dest resolves to a
204+
// `GlobalPtrStmt`). Persisted on `task_attribs_.snode_writes` so the SPIR-V launcher can bump
205+
// `Program::snode_write_gen_` for each id on every `launch_kernel` call - that is the precise signal
206+
// the per-task adstack metadata cache uses to invalidate when a prior kernel may have changed a
207+
// value an enclosing `size_expr::FieldLoad` reads. Stored as raw int ids (not `SNode *`) so the
208+
// field round-trips through the offline cache without resolving the pointer at serialise time.
209+
auto snode_rw = irpass::analysis::gather_snode_read_writes(task_ir_);
210+
task_attribs_.snode_writes.reserve(snode_rw.second.size());
211+
for (auto *s : snode_rw.second) {
212+
if (s != nullptr) {
213+
task_attribs_.snode_writes.push_back(s->id);
214+
}
215+
}
216+
std::sort(task_attribs_.snode_writes.begin(), task_attribs_.snode_writes.end());
217+
task_attribs_.snode_writes.erase(std::unique(task_attribs_.snode_writes.begin(), task_attribs_.snode_writes.end()),
218+
task_attribs_.snode_writes.end());
219+
201220
Result res;
202221
res.spirv_code = ir_->finalize();
203222
res.task_attribs = std::move(task_attribs_);

0 commit comments

Comments
 (0)