debug
Skill1.1k starsChanged 35 days agoApache-2.0View on GitHub
Debug crashes and test failures via stack-traces, host/device logging, DSL buffer inspection, and add_debug_checks kernel restrict-mode guards.
192 other projects use a version of this.
Trending
Quiet since July.thin dataabout the whole repository
Most recently taken up by
- PeanutGT/NutsSkillstheir own version, first seen Nov 8, 2026
- jskswamy/skillstheir own version, first seen Nov 8, 2026
- Firzus/agent-skillstheir own version, first seen Nov 8, 2026
- domengabrovsek/agent-configtheir own version, first seen Nov 8, 2026
- domengabrovsek/claudetheir own version, first seen Nov 8, 2026
- microsoft/veronatheir own version, first seen Nov 8, 2026
Who's talking about LuisaGroup/LuisaCompute
- Stack Overflow“c++ error: ‘struct std::atomic_flag’ has no member named ‘test’”Stack Overflow · Dec 26, 2022 · 1 votes
- Tencent/ncnn“optimize multi-head attention (increased shared-memory tile size, add fused kernel)”GitHub · Jul 10, 2026
- bazelbuild/bazel-central-registry“wanted: [LuisaGroup/LuisaCompute]”GitHub · Nov 8, 2025
- MuGdxy/SymEigen“Try to add support for luisa-compute”GitHub · Sep 15, 2025
- LuisaGroup/LuisaRender“Remove `util/u64.h` and use `Var<ulong>` instead”GitHub · Jun 2, 2024
The biggest 5 of 5 mentions linking to the repository that our crawl found. Each line is their own headline.
Used with
Projects with it also use add-discord, add-slack and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
MicrosoftSkilladd-discord
Add Discord bot channel integration via Chat SDK.
Also in 11 projects that use this
MicrosoftSkilladd-slack
Add Slack channel integration via Chat SDK.
Also in 11 projects that use this
- +10 more from microsoft
Other versions192 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 192
- Yeachan-Heo/oh-my-claude-sisyphus★ 41ksame name
- Yeachan-Heo/oh-my-claudecode★ 41ksame name
- anthropics/knowledge-work-plugins★ 35ksame name
- gavrielc/nanoclaw★ 31ksame name
- nanocoai/nanoclaw★ 31ksame name
- qwibitai/nanoclaw★ 31ksame name
- openai/symphony★ 27ksame name
- FalkorDB/FalkorDB★ 10ksame name
- NVIDIA/TensorRT-Model-Optimizer★ 5.6ksame name
- NVIDIA/Model-Optimizer★ 5.6ksame name
182 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository22 other files it gives its agents.
- .github/copilot-instructions.mdCopilot instructions
- astSkill
- backend-architectureSkill
- cmakeSkill
- cpp-styleSkill
- gitSkill
- glslangSkill
- hlslSkill
- ir-pipelineSkill
- lc_apiSkill
- lc_native_shaderSkill
- lc_optimizeSkill
- llvm-spirvSkill
- lspSkill
- project_structureSkill
- renderdocSkill
- spirv_codegenSkill
- spv-optSkill
- testSkill
- xir-passesSkill
- xmakeSkill
- yyjsonSkill
The file
We split this skill into the rules it gives an agent
12 rules. 0 are common, 6 are distinctive, and 6 are very specific to this project.
0
Common
Most of the 175 edited versions we read keep these.
6
Distinctive
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 10
When a crash or LUISA_ERROR is emitted, capture the full console output first.
- Distinctive: line 24
Read the innermost frame (first after the crash header).
- Distinctive: line 26
If the trace ends inside a driver/shared library, suspect (a) invalid resource usage (out-of-bounds buffer/image access), or (b) …
- Very specific: line 60
Run with LUISA_ENABLE_VALIDATION=1: Context::create_device wraps the backend device in the luisa-validation-layer plugin, so API misuse …
- Very specific: line 65
Read CMakeLists.txt or xmake.lua near the failing target to locate the executable source file and its main().
- Distinctive: line 85
If the failure happens during a kernel dispatch, move to device-side logging (Section 4).
- Distinctive: line 86
If the failure is a wrong numerical result, move to buffer read-back (Section 5).
- Very specific: line 137
Never route a device message to luisa::log_error/LUISA_ERROR from the callback: it is [[noreturn]], prints a host backtrace and calls …
- Very specific: line 145
When you need to inspect many values or avoid per-thread log flooding, write results into a Buffer and read back on the host.
- Very specific: line 297
Use LUISA_DUMP_SOURCE=1 when you suspect a code-generation bug (wrong instruction, missing binding, incorrect type).
- Distinctive: line 372
After the fix, re-run under the monitor: peak private memory should be bounded (hundreds of MiB for small device tests) and the timeline …
- Distinctive: line 379
Always plan: before editing; record each failed attempt so it is not repeated.
“Common” is measured: kept by at least 6 in 10 of the 175 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Debugging LuisaCompute
1. Interpreting Stack-Traces
When a crash or LUISA_ERROR is emitted, capture the full console output first.
What to look for:
- Top frames — the actual fault (null dereference, assertion, backend error).
- LuisaCompute frames — functions prefixed with
luisa::, especiallyluisa::compute::and the DSL inline namespaceluisa::compute::dsl::. - Backend frames — plugins load as
luisa-backend-<name>; the installed backends arecuda,dx,fallback,hip,metal,metal4,simd,vk. There is nocpubackend — the CPU paths arefallback(native C++/JIT) andsimd. - Last log line — often the preceding
LUISA_INFO/LUISA_VERBOSEshows the dispatch or shader name that triggered the bug.
What the trace itself looks like (src/core/platform.cpp, include/luisa/core/stl/format.h):
LUISA_ERROR→luisa::log_errorappends the frames to the error message itself (NN [0xADDR]: module :: symbol + offset), then flushes and callsstd::abort()— so expect a second dump from the abort handler.- Windows installs
SetUnhandledExceptionFilter,std::set_terminateand aSIGABRThandler (staticStackTracerInit), which print!!! Unhandled structured exception !!!,!!! std::terminate called (uncaught exception) !!!or!!! SIGABRT / std::abort() called !!!, followed by----- Stack Trace (N frames) -----rows of[ii] symbol at module+0xoff. - Frames come from
luisa::backtrace()— dbghelp (CaptureStackBackTrace+SymFromAddr) on Windows,::backtrace/backtrace_symbolson Unix. On Windows this whole path is#ifndef NDEBUG: a Release build yields no frames at all, so reproduce under-m debugor-m releasedbgfirst.
Action:
- Read the innermost frame (first after the crash header). This is the immediate cause.
- Walk upward until you hit a recognizable LuisaCompute API call (e.g.,
Device::compile,Buffer::copy_to/copy_from,Stream::synchronize, or theshader(args...).dispatch(...)command built byShaderInvoke<N>::dispatch). That is the call-site. - If the trace ends inside a driver/shared library, suspect (a) invalid resource usage (out-of-bounds buffer/image access), or (b) backend-specific limitation.
2. Plan Before Fixing
Once the stack-trace points to a file/line or API call, write a debug plan in this order:
- Hypothesis — state what you believe caused the failure in one sentence.
- Verification — describe the smallest code change or log addition that can confirm/disprove the hypothesis.
- Fix strategy — if verified, what exactly will you change.
- Rollback marker — note the original state so you can undo cleanly.
If the fix fails:
- Save the failed attempt with memory.
- Re-read the stack-trace and the saved steps. Do not repeat a failed hypothesis.
- Pick the next most likely cause and repeat from step 1.
3. When There Is No Stack-Trace
Silent failures (hang, wrong result, test timeout) provide no trace.
Distinguish hang types by CPU usage first:
- High-CPU "hang" (process burns a core indefinitely) is usually a spin on
corrupted state, e.g. a
spin_mutexinside a freed object — a UAF that looks like a hang. Treat it as a memory bug: audit recent lifetime changes and destructors that unlink cross-object references. - Zero-CPU hang is a genuine wait (GPU fence, deadlocked mutex).
For multi-test binaries, isolate by running each test alone (Boost.UT takes
positional name patterns after the backend arg: bin/debug/test_basic_types.exe vk test_vector_construction, i.e.
xmake run test_basic_types vk test_vector_construction). When a
fix candidate emerges, A/B it with git stash — a failure that persists with
the fix stashed is pre-existing and out of scope; do not chase it.
Cheapest ways to make a silent failure talk:
- Run with
LUISA_ENABLE_VALIDATION=1:Context::create_devicewraps the backend device in theluisa-validation-layerplugin, so API misuse turns into a loggedLUISA_ERROR(with trace) instead of driver-side corruption. - Build with safe mode —
lc_safe_mode(xmake) orLUISA_COMPUTE_ENABLE_SAFE_MODE=ON(CMake), which definesLUISA_ENABLE_SAFE_MODE. Resource constructors thenLUISA_ERRORon an invalid handle at the creation call (e.g.include/luisa/runtime/buffer.h,src/runtime/stream.cpp) instead of failing later. - Remember that a Release (
NDEBUG) Windows binary cannot print frames at all (Section 1) — 'no stack trace' may just be the build mode.
Find the entry point:
- Read
CMakeLists.txtorxmake.luanear the failing target to locate the executable source file and itsmain(). - Identify the test harness (e.g.,
test_device.h,boost::ut) and how the device is created.
Add host-side logging:
#include <luisa/core/logging.h>
// In host code (C++ runtime)
LUISA_VERBOSE("Entering {}::{}", __FILE__, __func__);
LUISA_INFO("Buffer size = {}", buf.size());
LUISA_VERBOSE_WITH_LOCATION("Dispatching kernel X");Set log level early (before Context creation if possible):
luisa::log_level_verbose(); // or log_level_info()Progressive narrowing:
- Log at the start of
main()and at every major phase (context → device → stream → compile → dispatch). - If the failure happens during a kernel dispatch, move to device-side logging (Section 4).
- If the failure is a wrong numerical result, move to buffer read-back (Section 5).
4. DSL / Device-Side Logging
Inside kernels, use device_log (include/luisa/dsl/stmt.h, lowered to FunctionBuilder::print_) to emit per-thread messages. They are collected by the stream and flushed to the host callback (Stream::set_log_callback); with no callback installed the backends print them through the host logger as LUISA_INFO("[DEVICE] {}", ...).
Basic usage:
#include <luisa/dsl/syntax.h>
#include <luisa/dsl/sugar.h>
Kernel2D k = [&]() noexcept {
UInt2 coord = dispatch_id().xy();
$if (coord.x == 1) {
device_log("hello {} {}", coord, make_float3x3());
};
};Custom log callback on the stream:
Stream stream = device.create_stream();
stream.set_log_callback([](luisa::string_view message) {
LUISA_INFO("device: {}", message);
});
stream << shader().dispatch(128u, 128u) << synchronize();Structured severity prefixes (for custom routing):
// Severity prefixes + dispatch-id pattern: src/tests/unit/runtime/test_printer_custom_callback.cpp
// (it also emits a 'V' prefix and appends `$dispatch_id` to every message)
#define DEVICE_INFO(FMT, ...) \
device_log(luisa::format("I" FMT) __VA_OPT__(, ) __VA_ARGS__)
#define DEVICE_WARNING(FMT, ...) \
device_log(luisa::format("W" FMT) __VA_OPT__(, ) __VA_ARGS__)
#define DEVICE_ERROR(FMT, ...) \
device_log(luisa::format("E" FMT) __VA_OPT__(, ) __VA_ARGS__)
stream.set_log_callback([](luisa::string_view msg) {
if (!msg.empty()) {
switch (msg.front()) {
case 'I': luisa::log_info("{}", msg.substr(1)); break;
case 'W': luisa::log_warning("{}", msg.substr(1)); break;
case 'E': luisa::log_warning("device error: {}", msg.substr(1)); break;
default: luisa::log_verbose("{}", msg); break;
}
}
});Never route a device message to luisa::log_error/LUISA_ERROR from the callback: it is [[noreturn]], prints a host backtrace and calls std::abort() (include/luisa/core/logging.h), so one device-side 'E' line kills the host process mid-flush.
No build switch or environment variable is needed for device logging: device_log is lowered to FunctionBuilder::print_, each backend ships the format strings with the compiled shader (printers), and the host decodes the log buffer in format_shader_print (src/backends/common/shader_print_formatter.h). There is no enable_logging, logging_buffer, or buffer_printf API in this codebase.
Important: Device logs are asynchronous. Always synchronize() the stream before assuming all logs have arrived. If a kernel hangs, the callback may never fire for logs buffered inside the failing dispatch.
5. Using Buffer for DSL Debug
When you need to inspect many values or avoid per-thread log flooding, write results into a Buffer and read back on the host.
Buffer-based inspection:
#include <luisa/core/stl/vector.h>
#include <luisa/dsl/syntax.h>
#include <luisa/dsl/sugar.h>
Buffer<float4> debug_buf = device.create_buffer<float4>(1024);
Kernel1D k = [](BufferVar<float4> out) noexcept {
UInt idx = dispatch_id().x;
Float4 v = make_float4(cast<float>(idx),
cast<float>(idx) * 2.0f,
cast<float>(idx) * 3.0f,
0.0f);
out.write(idx, v);
};
auto shader = device.compile(k);
stream << shader(debug_buf).dispatch(1024)
<< synchronize();
// Read back
luisa::vector<float4> host(1024);
stream << debug_buf.copy_to(luisa::span{host}) << synchronize();
for (size_t i = 0; i < 8; ++i) {
LUISA_INFO("host[{}] = {}", i, host[i]);
}Counter + slot pattern for conditional values:
- Allocate a
Buffer<uint>(e.g.counter_buf) plus a payloadBuffer<T>of the size you want to capture. - In the kernel, take a slot atomically and write through it:
auto slot = counter_buf.atomic(0u).fetch_add(1u); debug_buf.write(slot, payload);—BufferVarexposesread(i)/write(i, v)/atomic(i), notoperator[](include/luisa/dsl/resource.h,include/luisa/dsl/atomic.h; live example:src/tests/unit/coro/test_coro_wavefront.cpp:1071). - Guard the slot (
if (slot < N)) so a flooded kernel cannot write past the buffer. - This captures the first N interesting threads without over-allocating.
6. Kernel Restrict Mode: add_debug_checks
TEMPORARY DEBUG SCAFFOLDING — DELETE IT BEFORE SHIPPING.
add_debug_checksrebuilds the whole function (and every custom callable it uses) with a guard around every operation, and a guard is a printed diagnostic, never a free assertion. Expect an extremely huge performance cost, far beyond a few percent:
- each guarded op adds comparisons/branches plus size-query and diagnostic plumbing, and forces device printing on (
requires_printing()), which defeats vectorization, unrolling and dead-code elimination on the instrumented path;- error propagation adds a trailing
reference<uint>out-parameter to every transformed callable and a check at every call site (propagate_callable_errors);- a faulting thread additionally pays for building and flushing a device log line (asynchronous, so a flooded kernel also floods the log buffer). Real dispatches can slow down by orders of magnitude, so an instrumented build is unusable for timing or throughput work. Therefore: instrument only to localize a bug, then remove every
add_debug_checks(...)call (plusLUISA_AST_DEBUG_KERNEL=1, the debug kernel type, and its extra shader/dispatch) as soon as the bug is found — never commit it, never measure performance with it, and re-run the normal uninstrumented kernel to confirm the fix. The GPU-side cost is identical in Debug and Release, so do not expect a release build to make it cheap.
add_debug_checks(kernel) (include/luisa/dsl/func.h:736-749, pulled in by luisa-compute.h) rewrites a kernel and, transitively, every custom callable it uses so that every operation that can fail at runtime is guarded: on failure the guard prints a diagnostic through the device printer and the thread stops early (return_). A failure inside a callable is reported to its caller through a generated trailing reference<uint> error out-parameter (0 = ok, 1 = failed), so one bad value stops the whole kernel thread. This is the kernel restrict mode — use it to turn a silently corrupted, out-of-bounds or NaN-producing dispatch into an explicit device-side message plus a provable early stop. Implementation: src/ast/function_builder_debugger.cpp (its header comment is the definitive operator-by-operator check table, kept in sync with include/luisa/ast/op.h); options: include/luisa/ast/function_builder_debugger.h:15-48.
Canonical usage (from src/tests/integration/runtime/test_function_debugger.cpp):
#include <luisa/luisa-compute.h>
auto kernel = add_debug_checks(Kernel1D{[&](BufferVar<float> in, BufferVar<uint> out) noexcept {
auto i = dispatch_id().x;
auto v = in->read(i * 100u); // BUFFER_READ bounds guard
auto y = v / 0.0f; // float-div-by-zero + NaN/Inf guard
out->write(i, 1u); // sentinel: must not run for a faulting thread
static_cast<void>(y);
}});
// Guards call BUFFER_SIZE/BYTE_BUFFER_SIZE/ACCEL_SIZE, so compile with debug
// info (required on dx, harmless elsewhere).
auto shader = device.compile(kernel, {.enable_debug_info = true});
// Guards print through the device printer -> capture them on the stream.
stream.set_log_callback([](luisa::string_view message) { LUISA_INFO("device: {}", message); });
stream << shader(input, output).dispatch(4u) << synchronize(); // input/output are the host buffersGuards emit lc-debug: <what> in <kernel> (...) lines. The callback receives the raw print payload (no severity prefix is added by the backends); match on a substring, e.g. "buffer index out of range" or "NaN/Inf result". (The reference test's helper drops the first byte of every message, a leftover from test_printer_custom_callback.cpp where the kernel prepends its own severity letter — so it deliberately never matches lc-debug: itself.)
What is checked by default (detail::DebugKernelOptions):
| Guard | Fires on | Message contains |
| Integer div/mod | x / 0, x % 0 (int/uint) | integer division/modulo by zero in <fn> |
| Float div | float divisor == 0 | float division by zero in <fn> |
| NaN/Inf result | NaN/Inf result of float ADD/SUB/MUL/DIV/MOD, and math domain errors (SQRT/RSQRT negative, LOG* <= 0, ACOS/ASIN |x| > 1, ACOSH < 1, ATANH |x| > 1, POW, ATAN2(0,0), EXP* overflow, NORMALIZE zero-length, INVERSE singular) | NaN/Inf result in <fn> |
| Shift amount | SHL/SHR amount >= bit width or negative | shift amount out of range in <fn> |
| Buffer bounds | BUFFER_READ/WRITE (+volatile) index >= buffer.size(); BYTE_BUFFER_* range (offset + sizeof(T) > size) | buffer index out of range in <fn>, byte-buffer range out of bounds in <fn> |
| Bindless bounds | bindless element index vs BINDLESS_BUFFER_SIZE (slot validity is a host-encoder contract) | bindless element index out of range |
| Accel instance index | RAY_TRACING_INSTANCE_* / SET_INSTANCE_* index vs accel.size() (needs CallOp::ACCEL_SIZE: dx/vk/cuda) | accel instance index out of range in <fn> |
| Callee error | a transformed callable failed at a CUSTOM call site | callee <name> failed in <fn> |
Opt-in / not checked:
check_texture_bounds{false}—TEXTURE_READ/WRITEcoordinates vsTEXTURE_SIZE(writes are clamped on some backends);check_ray_validity{false}— NaN/Inf ray origin/direction.- Never guarded: atomics (the
AtomicRefsits on an already-bound buffer), size queries (BUFFER_SIZE/TEXTURE_SIZE/ACCEL_SIZE), texture samplinguv/coord(hardware-clamped), int overflow (MINUS/ADD/SUB/MUL, off in v1), accel motion keys, and loopfor-init/cond/update +$whileprovenance expressions (the AST has no per-iteration statement position — documented v1 limitation). - Rejected outright with a clear error: autodiff (
BACKWARD/GRADIENT), raster-stageRASTER_*, and cooperative/async/expert ops (COOPERATIVE_*,ASYNC_COPY,PIPELINE_*,MBARRIER_*, …). - Zero overhead when there is nothing to check: a function whose ops are all safe is rebuilt statement-for-statement identical (the test asserts equal statement counts for a plain shared-memory kernel and for
acc->intersectwithout instance access).
Tune the checks. add_debug_checks always uses the default DebugKernelOptions; for per-check control call the detail entry point and rebuild the kernel type:
namespace detail = luisa::compute::detail;
detail::DebugKernelOptions opts;
opts.check_accel_instance_index = false; // backend without CallOp::ACCEL_SIZE
opts.check_texture_bounds = true; // opt in
Kernel1D<BufferVar<float>, BufferVar<uint>> checked{
detail::debug_function(*plain_kernel.function(), opts)};Zero-touch variant: LUISA_AST_DEBUG_KERNEL=1 (also true/on; read once per process, src/ast/function_builder_debugger.cpp:1404-1408) makes FunctionBuilder::define_kernel run the generator on every kernel defined afterwards — non-kernel builders pass through unchanged (include/luisa/ast/function_builder.h:289-297). Fastest way to check a whole app without touching source — but it is process-wide, so unset it (or restart the shell) when done: as long as it is set, every kernel defined afterwards carries the guards and pays the huge cost described above.
Pre-flight sanity: a guarded kernel sets requires_printing() and advertises the size queries it needs via propagated_builtin_callables() (CallOp::BUFFER_SIZE / BYTE_BUFFER_SIZE / ACCEL_SIZE) — assert both when wiring a new backend, and skip the check whose query that backend does not implement.
Reference test: src/tests/integration/runtime/test_function_debugger.cpp (xmake test_proj, CMake luisa_compute_add_test) is the end-to-end spec of every guard; run xmake run test_function_debugger <backend> (the accel-structure cases need cuda/dx/vk/metal). It checks both the message and that faulting threads stop before their sentinel write — copy that pattern to reproduce a suspected OOB/NaN kernel.
7. Environment Variables for Backend Diagnosis
177 distinct LUISA_* names are read from the environment in src/ outside src/tests (114 of them LUISA_SIMD_* codegen toggles), plus the system MTL_*/METAL_* variables; enumerate them with grep -rn '"LUISA_' src/. The table lists the diagnosis-relevant subset. Booleans are read through luisa::compute::detail::env_flag (src/backends/common/env_flag.h:11-17: 1, true, TRUE, on, ON), while a few checks compare against exactly "1" (noted below). Set flags to 1 and you are always safe.
| Variable | Effect | Evidence |
LUISA_DUMP_SOURCE=1 | Dump generated shader sources/bytecode (must be exactly 1 on the DX/Vulkan/CUDA paths). | src/backends/common/backend_print_code.h:8-10 |
LUISA_LOG_LEVEL = verbose / info / warning / error | Startup log level (case-insensitive); same effect as luisa::log_level_verbose(). Invalid values warn and keep the default (debug in non-NDEBUG, else info). | src/core/logging.cpp:52-78 |
LUISA_ENABLE_VALIDATION=1 | Context::create_device wraps the backend device in the luisa-validation-layer plugin (must be exactly 1). | src/runtime/context.cpp:155,372-391 |
LUISA_OPTIX_VALIDATION=1 | Full OptiX device-context validation on CUDA (large slowdown; warns when on). | src/backends/cuda/cuda_device.cpp:324-329,1576-1579 |
LUISA_VULKAN_VALIDATION=1 | Force the Vulkan instance validation layers on (they are already on by default in NDEBUG-off builds). | src/backends/vk/device.cpp:220-232 |
LUISA_VULKAN_REQUIRE_NATIVE_XIR_SPIRV=1 | Fail instead of silently falling back to HLSL→SPIR-V for a user compute kernel. | src/backends/vk/device.cpp:71-74,3346 |
LUISA_DISABLE_COMMAND_REORDER=1 | Process-wide kill switch for DX/Vulkan command reordering → strict submission order (A/B a reorder bug). | src/backends/common/command_reorder_switch.h:17-36 |
LUISA_DUMP_XIR=1 (+ LUISA_DUMP_XIR_DIR) | Dump XIR (kernel.<hash>.xir, .opt.xir, …); CUDA/XIR, Metal4, fallback. | src/backends/fallback/fallback_shader.cpp:66-71, src/backends/cuda/cuda_device.cpp:79-100 |
LUISA_DUMP_LLVM_IR=1 / LUISA_DUMP_ASM=1 | Dump LLVM IR (kernel.<hash>.ll, .opt.ll) / assembly (.s) — fallback JIT and the LLVM codegen paths. | src/backends/fallback/fallback_shader.cpp:73-85,766,845,856 |
LUISA_DUMP_SPV=1 | Dump the pre-validation SPIR-V binary (hard-coded path /tmp/opencode/kernel_<hash>.spv). | src/backends/common/spirv/spirv_codegen/entry.cpp:440-444 |
LUISA_XIR_TRACE_PASSES=1 | Per-pass tracing while planning the XIR→SPIR-V pipeline. | src/backends/common/spirv/spirv_codegen/utils.cpp:218-219 |
LUISA_VULKAN_PROFILE_COMPILATION=1 | Per-phase compile timing on the native SPIR-V path. | src/backends/common/spirv/spirv_codegen/entry.cpp:21-24, optimizer.cpp:586 |
LUISA_XIR_NORMALIZE_CFG=1 / LUISA_XIR_ELIMINATE_EARLY_RETURN=1 | Per-backend XIR CFG pass toggles (CUDA, HIP, fallback). CUDA LLVM accepts plain CFG and PHIs; LUISA_XIR_RESTRUCTURE_CFG applies only to backends that still restructure CFG. | src/backends/cuda/cuda_device.cpp, src/backends/fallback/fallback_shader.cpp |
LUISA_SINGLE_THREADING=1 | Fallback backend: run every thread of a dispatch serially on one host thread (deterministic device-side debugging). | src/backends/fallback/fallback_command_queue.cpp:247-257 |
LUISA_CUDA_FALLBACK_RTX=1 / LUISA_DX_FALLBACK_RTX=1 / LUISA_VK_FALLBACK_RTX=1 | Force the software ray-tracing fallback instead of OptiX/DXR/native Vulkan RT. | src/backends/cuda/cuda_device.cpp:398, src/backends/dx/DXApi/LCDevice.cpp:65, src/backends/vk/device.cpp:1894 |
LUISA_SIMD_WARP_WIDTH=…, LUISA_SIMD_WORKER_COUNT=…, LUISA_SIMD_REPORT_OPTIMIZATIONS=1, LUISA_SIMD_DUMP_ASSEMBLY_DIR=… | SIMD (CPU) backend overrides/diagnostics; LUISA_SIMD_* also covers dozens of DISABLE_*/FORCE_* codegen toggles. | src/backends/simd/runtime/simd_device.cpp:124,152, src/backends/simd/simd_compiler.cpp:898 |
LUISA_METAL_SHADER_INFO=1, LUISA_METAL_COMMAND_BUFFER_PROFILE=1, LUISA_DUMP_METAL_LIBRARY=1 | Metal/Metal4 compile-info and command-buffer staging profile; metallib dump. | src/backends/metal/metal_shader.cpp:81,102, src/backends/metal/metal_compiler.cpp:239 |
MTL_DEBUG_LAYER=1, MTL_SHADER_VALIDATION=1, MTL_ENABLE_CAPTURE=1, METAL_CAPTURE_ENABLED=1 | System Metal env vars the backends forward/force when deciding to keep debug info. | src/backends/metal4/metal_compiler.cpp:45-49,452-456 |
LUISA_HIP_PROFILE=1, LUISA_HIP_WAVE64=1, LUISA_DUMP_HIP_ISA=<dir>, LUISA_DUMP_PTX=1, LUISA_DUMP_AMDGPU=1 | HIP/CUDA codegen-path diagnostics. | src/backends/hip/hip_stream.cpp:114, src/backends/hip/hip_device.cpp:623, src/backends/cuda/llvm_codegen/cuda_codegen_llvm.cpp:19 |
| remaining families | LUISA_SPIRV_OPT_LEVEL/_PASSES/_PASS_FLAGS/_MAX_ITERATIONS/_SROA_LIMIT/_MAX_ID_BOUND/_PRESERVE_BINDINGS (bisect spv-opt), LUISA_XIR_DISABLE_OPTIMIZATION, LUISA_XIR_ENABLE_SCALARIZER, LUISA_VK_DUMP_FALLBACK_HLSL, LUISA_EXPERIMENTAL_LLVM_CODEGEN (CUDA LLVM route; autodiff always requires LLVM), LUISA_CORO_* coroutine-pass dumps, LUISA_GUI_RASTER*, LUISA_REMOTE_TOKEN. | src/backends/common/spirv/spirv_codegen/optimizer.cpp:72-586, .../utils.cpp:491-505, src/backends/vk/device.cpp:3157, src/backends/cuda/cuda_device.cpp:65-86, src/xir/passes/coro_alloca_scope.cpp:546 |
(luisa::log_level_verbose() etc. are the programmatic equivalents, include/luisa/core/logging.h:136-142. Build-time switches are not env vars: CMake LUISA_COMPUTE_* options and xmake lc_* options.)
Use LUISA_DUMP_SOURCE=1 when you suspect a code-generation bug (wrong instruction, missing binding, incorrect type).
Where to find the dumps:
- DirectX:
hlsl_output_<name>.hlslin the current working directory. - Vulkan user compute (XIR→SPIR-V path):
spv_code_<name>.spvasmin the current working directory. - Vulkan user compute (LLVM→SPIR-V path):
spv_code_llvm_<name>.spvasm. - Vulkan internal HLSL consumers: backend builtins/raster may dump
hlsl_output_<name>.hlsl; ordinaryDevice::compile(Function)compute shaders must not. - CUDA:
.cusource in the runtime.cachedirectory (write_shader_source); PTX +.metadataalso land in.cache(write_shader_cache) — only explicitly named/AOT bytecode goes to the data directory itself (write_shader_bytecode). The.datadirectory holds internal shaders, not user PTX. - Metal:
.metalsource in the runtime.cachedirectory; Metal4 dumps.metallibthere. - Fallback: does not read
LUISA_DUMP_SOURCE; setLUISA_DUMP_XIR=1/LUISA_DUMP_LLVM_IR=1/LUISA_DUMP_ASM=1to getkernel.<hash>.xir,.opt.xir,.ll,.opt.ll,.sin the current working directory.
The runtime directories are printed by LUISA_INFO at context creation; they default to the executable directory. When running under xmake run, dumps written directly to the current working directory will appear in the project root.
8. Decision Checklist
| Symptom | First Action | Next Action |
| Crash with stack-trace | Read innermost + first Luisa frame | Hypothesize → plan → fix |
| Crash with no frames | Rebuild -m debug/-m releasedbg (Windows backtrace is #ifndef NDEBUG) | Attach scripts/debugger.py (Section 9) |
| Silent wrong result | Add LUISA_INFO at host entry points | Use buffer read-back to inspect values |
| Suspected kernel OOB / NaN / div-by-zero | Wrap the kernel with add_debug_checks + enable_debug_info (Section 6) | Read the lc-debug: messages on the stream log callback; faulting threads stop early |
| Kernel dispatch hangs | Check synchronize() and stream callback | Add minimal device_log at start of kernel |
| Hang burning CPU | Suspect spin on freed memory (UAF masquerading as hang) | Isolate per-test; audit destructor unlink paths |
| Unsure if failure is yours | Stash changes and re-run the same binary path | Persistent failure = pre-existing, out of scope |
| Backend compilation error | Set LUISA_DUMP_SOURCE=1 (fallback: LUISA_DUMP_XIR=1) | Inspect generated .spvasm or .hlsl |
| Suspected API/resource misuse | Set LUISA_ENABLE_VALIDATION=1 | Re-run and read validation messages |
| Test timeout | Read build file for target entry | Narrow phase with host logging |
9. Windows Crash Debugging with scripts/debugger.py
A lightweight Python debugger using Windows Debug API + DbgHelp.dll (pure ctypes, no third-party packages) to launch an x64 executable under the debugger, print a symbolic stack trace for every exception event, and stop at the second-chance one. Output: [!] Caught exception: header (code/address/FirstChance), then === C++ stack trace (with PDB symbols) === rows #NN 0xADDR func followed by file:line when line info exists.
Usage:
python scripts/debugger.py <path_to_exe> [pdb_search_path] [-- <args>...]- Arguments after
--are forwarded to the target executable. - Symbols come from
SRV*C:\Symbols*https://msdl.microsoft.com/download/symbols;<pdb_search_path>, so the PDB must sit next to the EXE or inpdb_search_path(system DLLs are fetched from the Microsoft symbol server). - Prints a trace for first-chance exceptions too (breakpoints, MSVC C++ EH
0xE06D7363) and keeps going; only a second-chance exception ends the session. - Works on Windows x64 with Python 3.x (64-bit recommended).
Example:
# tests take the backend as argv[1] (Boost.UT name patterns follow it) — no gtest here
python scripts/debugger.py bin/debug/test_runtime.exe -- dx10. Tracking Memory Growth with scripts/mem_monitor.py
A cross-platform (Windows/Linux) Python wrapper that launches a process, samples its memory at a fixed interval, writes a timeline log, and kills the process tree when private memory exceeds a threshold — essential when a runaway test could exhaust machine RAM before you can observe it.
- Windows: Private Bytes / Working Set via
GetProcessMemoryInfo; kills the tree withtaskkill /F /T. - Linux: private memory =
Private_Clean + Private_Dirtyfrom/proc/<pid>/smaps_rollup(fallbackRssAnon + VmSwap), working set =VmRSS; kills the child's whole process group withSIGKILL(the child is started in its own session).
Usage:
python scripts/mem_monitor.py [--kill-gb N] [--interval S] [--log FILE] -- <exe> [args...]--kill-gb N(default 20): hard kill threshold in GiB of private memory (Private Bytes on Windows,smaps_rollupprivate on Linux). Pick a value that leaves the machine usable (e.g. total free RAM minus headroom).--interval S(default 0.25): sampling interval in seconds.--log FILE(defaultmem_monitor.log): timeline output, onetime_s private_mb working_set_mbrow per sample.- Arguments after
--are forwarded to the target executable. - On exit it prints a summary: exit code, whether it killed the process, peak private memory, and the first/last samples.
Example (run a device test, kill if it exceeds 6 GiB):
python scripts/mem_monitor.py --kill-gb 6 --interval 0.25 --log mem.log -- bin/debug/test_runtime.exe dxWorkflow for a suspected leak / memory blow-up:
- Run the full repro under the monitor with a safe
--kill-gb. A straight-line, ever-growing timeline (constant MiB/s) usually means an unbounded accumulation loop (e.g. appending commands to aCommandListthat never terminates), not a classic leak; stepwise growth synchronized with test phases suggests per-phase leaks. - Isolate the phase: run individual tests (Boost.UT: positional test-name patterns after the backend arg, e.g.
test.exe <backend> my_test) and compare peak memory and timeline shape. - A phase that terminates with flat memory is innocent; a phase whose memory grows without plateau owns the bug. Then read that phase's code for loops whose trip count can make zero progress (e.g.
n = min(remaining, k)withk == 0). - After the fix, re-run under the monitor: peak private memory should be bounded (hundreds of MiB for small device tests) and the timeline flat.
Note: GPU/device memory is mostly invisible to host-side private memory; this tool tracks host-side growth. Pair with backend allocator logging if device-side exhaustion is suspected.
Summary
- Stack-traces → innermost frame = cause; upward walk = call-site.
- Always plan before editing; record each failed attempt so it is not repeated.
- No trace → read
CMakeLists.txt/xmake.lua, addLUISA_INFO/LUISA_VERBOSE, thendevice_log. - DSL values → prefer
Bufferwrite + host read-back for bulk inspection; usedevice_logfor targeted per-thread messages. - Kernel OOB / NaN / div-by-zero / bad shift →
add_debug_checks(kernel)+enable_debug_info(Section 6), orLUISA_AST_DEBUG_KERNEL=1for the whole app: guards printlc-debug:messages and stop the faulting thread. - Backend/codegen issues → set
LUISA_DUMP_SOURCE=1to inspect generated shaders andLUISA_ENABLE_VALIDATION=1to catch API/resource misuse. - Memory blow-up / suspected leak → run under
scripts/mem_monitor.pywith--kill-gb(Windows/Linux); constant-rate growth = unbounded accumulation loop, stepwise growth = per-phase leak.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

