Experimental CUDA LLVM to OptiX IR
The CUDA backend has an opt-in path from its optimized LLVM module directly to binary OptiX IR. Ray-tracing shaders can use this path; ordinary CUDA compute shaders continue through LLVM’s PTX emitter. Building the feature does not change the default shader compiler.
The pipeline is:
DSL / AST -> shared XIR normalization -> LLVM 22 or 23 optimization
-> OptiX IR compatibility lowering
-> NVVM kernel annotation transplant
-> in-tree LLVM 7 bitcode writer -> level-2 OptiX IR container
-> OptiX moduleCreate
This is an in-process container writer. It does not invoke CICC, NVRTC, or libNVVM for this ray-tracing path. The existing AST/NVRTC and LLVM/PTX paths remain available.
Build and select the path
Configure with both experimental CMake options enabled. Both default to OFF:
# Run from an MSVC developer PowerShell. Use a full LLVM 22 or 23 development package.
cmake -S . -B build-msvc-llvm -G Ninja `
-DCMAKE_BUILD_TYPE=Release `
-DCMAKE_CXX_COMPILER=cl `
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded `
-DLUISA_COMPUTE_ENABLE_CUDA=ON `
-DLUISA_COMPUTE_ENABLE_EXPERIMENTAL_CUDA_LLVM_CODEGEN=ON `
-DLUISA_COMPUTE_ENABLE_EXPERIMENTAL_CUDA_OPTIX_IR=ON `
-DLLVM_DIR="C:/deps/llvm-23/lib/cmake/llvm"
cmake --build build-msvc-llvm
The pinned in-tree downgrader accepts LLVM 22 and 23. It compiles as a static helper against the same LLVM package used by the CUDA backend; installing LLVM 7 or a second host LLVM is unnecessary. Use separate build directories when switching host LLVM major versions. The existing Apple LLVM 14/AIR serialization path retains its own writer.
On Windows, obtain the full clang+llvm-<version>-x86_64-pc-windows-msvc
development archive, including LLVM headers, static libraries, and
lib/cmake/llvm/LLVMConfig.cmake. The CLI/toolchain installer alone is not a
substitute for these development files. The official LLVM 23.1.2 archive used
here targets the MSVC ABI, uses /MT, and disables RTTI and exceptions. Match
the package’s CRT/RTTI configuration; CMake uses /FI for the legacy writer shim
and matches LLVM’s disabled RTTI. The application and backend remain compiled
with MSVC; the archive’s own build compiler does not select the project compiler.
That LLVM 23 SDK exports real ZLIB::ZLIB and zstd::libzstd_static link
dependencies without bundling their development libraries. Provide compatible
MSVC static libraries and headers instead of removing those dependencies or
creating empty imported targets. For example, append these arguments to the
configuration above after installing Zlib and Zstd into C:/deps/llvm-support:
-DZLIB_INCLUDE_DIR="C:/deps/llvm-support/include" `
-DZLIB_LIBRARY_RELEASE="C:/deps/llvm-support/lib/zlibstatic.lib" `
-DZLIB_USE_STATIC_LIBS=ON `
-Dzstd_INCLUDE_DIR="C:/deps/llvm-support/include" `
-Dzstd_LIBRARY="C:/deps/llvm-support/lib/zstd_static.lib" `
-Dzstd_STATIC_LIBRARY="C:/deps/llvm-support/lib/zstd_static.lib"
The local dependency setup builds official Zlib 1.3.1 and Zstd 1.5.7 sources with
MSVC /MT and retains source/library hashes and COFF CRT checks in
.deps/llvm23/dependencies/provenance.json. The full SDK and its local allocator
adaptation have separate provenance under .deps/llvm23/sdk/. These local
records describe the validation environment; they are not required repository
files or a substitute for obtaining compatible dependencies.
Set both environment variables before starting the application:
$env:LUISA_EXPERIMENTAL_LLVM_CODEGEN = "1"
$env:LUISA_CUDA_LLVM_OPTIX_IR = "1"
# Launch the application with its normal CUDA backend arguments.
These runtime switches also default to off. They are read when the backend is loaded, so changing them after device creation is not a supported switch between formats in one process.
Runtime selection |
Ray-tracing shader |
Ordinary compute shader |
|---|---|---|
Both switches unset |
AST/NVRTC to PTX |
AST/NVRTC to PTX |
LLVM codegen |
LLVM to PTX |
LLVM to PTX |
Both switches |
LLVM to OptiX IR |
LLVM to PTX |
The OptiX IR switch alone does not enable LLVM generation. Existing kernels that require LLVM, such as autodiff kernels, retain their automatic LLVM selection. Requesting OptiX IR for an LLVM ray-tracing kernel without the CMake feature produces an explicit error.
Cache and AOT artifacts
OptiX IR is binary. Preserve its explicit length, including embedded zero bytes; do not pass it through a text writer or append a PTX terminator. The OptiX API receives the byte buffer and its exact size. An OptiX IR compilation error is reported directly and never retried with PTX text-version patching.
Generated cache entries separate PTX from OptiX IR by format and extension.
Metadata serializes CODE_FORMAT PTX or CODE_FORMAT OPTIX_IR, includes the
format in equality, and validates the code envelope before loading. A legacy
sidecar without CODE_FORMAT means PTX. OptiX IR is accepted only with
RAY_TRACING metadata.
For AOT generation, use the usual ShaderOption::compile_only flow and give
ShaderOption::name an explicit .optixir extension. Keep the matching
.optixir.metadata sidecar. Use an explicit .ptx or .optixir name when loading
artifacts with the same base name. For backward compatibility, an extensionless
load checks the .ptx artifact first and uses .optixir only if that PTX file is
absent. Loading follows the saved metadata; the runtime environment switches
select generation, not the interpretation of an existing artifact.
Fast-math denormal mode
OptiX IR fast-math generation adds container scalar tag 13 = 1 for
flush-to-zero, in addition to the LLVM denormal function attributes.
A CPU-only CUDA 13.4 NVRTC comparison of --optix-ir with and without
--ftz=true produced byte-identical decoded bitcode; the sole difference
was this four-byte container scalar and its resulting payload offsets.
LLVM attributes alone did not enable FTZ in the ray-query AH/IS runtime
regression on the validation driver. No other observed NVRTC fast-math
container options are copied. Precise generation retains the previous
header exactly, and ordinary compute shaders still emit PTX.
The host-only test_cuda_shader_metadata checks both headers, lengths,
payload offsets, and identical seed/encoded bitcode. Device tests in
test_cuda_llvm_ray_query_bounds check runtime subnormal inputs and
results through ordinary compute, any-hit, and intersection stages.
Compatibility boundary
The container follows the supplied proof of concept’s observed CUDA 13.3
level-2 layout. Its format fields and NVIDIA’s
nvvm.annotations_transplanted marker are not a public cross-version contract.
Success on one driver/CUDA/OptiX combination does not establish compatibility
with another. LLVM 7-compatible bitcode serialization alone also does not prove
that the target OptiX compiler accepts every intrinsic in that module.
The C++ wrapper luisa::compute::llvm_downgrade_to_7 consumes an already
optimized std::unique_ptr<llvm::Module> and returns in-memory bitcode bytes.
The overlay is pinned to upstream downgrader commit 4244e2a1, with source and
patch fingerprints checked before creating a build-directory mirror for the
selected LLVM major. It conditionally handles the LLVM 23 branch, module-assembly,
and attribute APIs while preserving the existing AIR typed-pointer adaptations.
Unsupported input fails explicitly; the embedded helper uses fatal diagnostics
instead of letting third-party exceptions escape its noexcept boundary.
LLVM 23 represents denormal modes with DenormalFPEnv rather than the two legacy
string attributes. Before legacy serialization, the writer translates both the
default and f32 modes back to denormal-fp-math and denormal-fp-math-f32,
preserving each mode’s output/input order. This is required by old bitcode
readers and does not replace the OptiX IR container FTZ setting described above.
The changed writer is separated by OptiX IR cache revision 10; it does not
change the LLVM/PTX writer path.
LLVM 23 also represents numeric vector splats as ConstantInt/ConstantFP.
Legacy writers must emit a vector aggregate referring to the scalar constant,
with the scalar registered by the value enumerator. Treating these objects as
scalar records produced malformed bitcode: the dynamic image fixture was
rejected by OptiX, and LLVM’s own reader reported Invalid float const record.
The adaptation preserves each lane’s bits, including wide integer and floating
types, without changing arithmetic permissions or the PTX code-generation path.
The adapter transfers recognized kernel annotation meaning before adding the consumed marker and keeps the PTX kernel calling convention. Unsupported NVVM annotation semantics are rejected. Optimization finishes before typed-pointer reconstruction; running another optimizer between reconstruction and writing would invalidate the legacy writer’s no-op casts.
The compatibility lowering runs after the normal host optimization pipeline (O2/O3 according to the selected level), only when emitting OptiX IR. LLVM/PTX does not run these adaptations:
Floating-point atomic add, subtract, minimum, and maximum become integer compare-exchange loops. They retain the original address space, alignment, ordering, synchronization scope, volatility, and returned old value. Retry compares integer bits, so a stored NaN cannot cause an endless floating-point equality retry. The replacement arithmetic has no newly introduced fast-math permissions; minimum/maximum retain
minnum/maxnumsemantics. Exchange uses an integer atomic exchange with bitcasts. The adapter handles 32- and 64-bit IR, but the public DSL and current runtime regression cover float32 only.Generic
rintuses rounding-to-nearest-even instructions without flushing subnormals; half values pass through float.fabsclears the sign bit. Signed and unsigned integer min/max use comparisons and selects. Floating vector reductions preserve the explicit start value and lane order, carrying only the original operation’s fast-math permissions.Packed boolean mask comparisons with zero or all ones become scalar bitwise all/any reductions. This avoids the observed OptiX compiler crash for
<3 x i1>bitcast toi3, while retaining poison propagation from each lane.2D/3D zero-boundary surface loads and stores use inline surface instructions with explicit memory effects. The adapter handles scalar, two-lane, and four-lane operations with 8-, 16-, or 32-bit components. This works around an observed compiler failure in the validation environment: a native NVRTC OptiX IR probe without ray queries also produced misaligned local/shared accesses, while its PTX counterpart passed. The runtime regression validates four-channel BYTE4, HALF4, and FLOAT4 images and volumes, including half signed zero and exactly representable finite values. Scalar/two-channel execution is not covered by that regression.
Before legacy serialization, fast-math flags are also removed from calls that
return aggregates: LLVM 7 does not encode those permissions on aggregate calls.
This removal does not change the call’s defined result. The wrapper still
rejects freeze, scalable vectors, bfloat16, AMX, target-extension types, and
LLVM 23’s byte type; other unsupported instructions or intrinsics remain an
explicit compatibility boundary. These adaptations and limits apply to OptiX IR,
not to LLVM/PTX.
Initial adapter validation (5b28304a3, LLVM cache v12)
This record describes the initial OptiX IR adapter at commit 5b28304a3, using
LLVM cache revision v12. Its PTX byte-identity and compilation measurements
predate the later shared fast-math FTZ and ray-query payload changes; they are
not byte-identity or performance claims for those subsequent revisions.
The following results were collected on Windows with MSVC/Ninja, LLVM 22.1.8, CUDA 13.4.92, OptiX 9.0, NVIDIA driver 617.14, and an RTX 4060 Laptop GPU. This work validates that environment; it does not establish compatibility with other LLVM versions or change either default-off switch.
Check |
Evidence / result |
|---|---|
Full MSVC/Ninja build with the opt-in gate |
Passed; |
Focused regression suite |
22/22 passed: 11 LLVM/PTX tests, 10 OptiX IR tests, and one host metadata test; |
Metadata, legacy sidecar, and malformed-envelope tests |
Passed: 136 assertions in 16 host cases |
Generated OptiX IR accepted and executed |
Passed; IR tests require an actual |
Ray-query and tracing correctness |
Passed: triangle/procedural hits, misses, visibility and build modes, curves, motion, world/object rays, termination, captured state, and mixed filter/general handlers |
Additional reader compatibility regressions |
Passed: float32 atomic results/old values/concurrency, all bool2/3/4 patterns and negations (97 assertions per route), and 2D/3D texture read/write bits (18 assertions per route) |
Explicit and extensionless AOT load behavior |
Passed for PTX and OptiX IR artifacts in the payload-boundary fixture, with the same strict readback oracle as the compiled shader |
PTX isolation |
Passed: PTX tests reject IR generation/consumption; the ordinary-compute cache fixture passes through PTX |
Cold cutout LLVM/PTX output |
Byte-identical to the pre-OptiX-IR baseline: 33,699 bytes, SHA-256 |
Cold cutout compile-time comparison |
Completed; OptiX IR has a faster LLVM generation stage but a slower total shader compilation in this run; medians below |
Cutout image comparison at 64 spp |
99.28455% of pixels identical; mean absolute channel error 0.0139923 on a 0–255 scale. Outputs are not bit-identical across the two compiler paths |
Images Compute Sanitizer |
Passed: exit 0, all 18 image/volume assertions, |
Cutout Compute Sanitizer |
Passed: OptiX IR, 64 spp, exit 0 with a saved PNG and |
Interactive rendering on the final adapter |
Passed: cutout and procedural, 4,096 spp each, exit 0 with saved PNGs; both logs confirm |
Additional Compute Sanitizer workloads |
The ray-query integration fixture passed on both PTX and OptiX IR after payload sizing; see the v14 results below |
Large captured-state Compute Sanitizer |
Passed: 65,536 rays, exit 0, |
GPU execution-time comparison |
Not measured by the compile-time experiment below |
The cold PTX comparison uses
.deps/optixir-compile-ab-20260930-005337/0-ptx/.cache/kernel_ed984c0d76c0a790.llvm-v12.ptx.
It proves unchanged output for this cutout kernel, not byte identity for every
possible shader. The original AOT checks loaded artifacts emitted by named
compilation. The subsequent v15 tests also exercise a compile_only producer
before creating any consumer for that kernel.
Interactive results are recorded under build-msvc-llvm/test-results/ in
cutout-llvm-gui-cutout-query-normal-20260930-010110-105 and
clean-procedural-llvm-20260930-010152. Each directory contains the process
result, rendering log, and PNG. These completion checks are separate from the
subsequent GPU throughput comparisons below.
The cold-cache cutout run records three processes per route in
.deps/optixir-compile-ab-20260930-005337/results.json. Its medians are:
Compilation stage |
LLVM/PTX |
LLVM/OptiX IR |
|---|---|---|
Host LLVM generation |
81.5722 ms |
72.9058 ms |
OptiX |
164.089 ms |
217.289 ms |
Total shader compilation |
270.549 ms |
318.592 ms |
The LLVM generation stage is approximately 10.6% faster, but total shader
compilation is approximately 17.8% slower with OptiX IR in this sample. The
first PTX moduleCreate took 1,330 ms; comparing against that initialization
outlier would give a misleading speedup. The table reports route medians,
including that process in the sample. These results support retaining the
explicit opt-in rather than changing the default compiler path.
Generated OptiX IR with CUDA LLVM CodeGen identifies an uncached generation.
OptiX moduleCreate: format=..., bytes=..., attempt=..., duration=... ms measures
the CPU module-creation call. Neither log is a GPU execution-time measurement;
record device, driver, shader/cache state, workload, and separate dispatch timing
when comparing performance.
General-query state follow-up
Cache v15 replaces load/select/store of the previous committed hit with a conditional update. Valid distances write the new hit directly; invalid ones preserve every field, including an earlier accepted hit. Ordered bounds checks remain in place. The full MSVC build and all 22 focused tests passed, including compile-only artifact production and invalid replacement preservation.
An eight-process ABBA comparison used the five-query mixed surface/procedural
benchmark with mutable captures: 65,536 rays, 512 warmup dispatches, 512 dispatches
per sample and nine samples. Median throughput was 2,173.43 Mqueries/s for v14
and 2,196.79 Mqueries/s for v15 (+1.07%). The process ranges overlap; this
does not establish a repeatable speedup. PTX input shrank from 33,546 to 30,736
bytes. Results are in
build-msvc-llvm/test-results/ray-query-guarded-commit-v15-20260930-020109/.
The corresponding eight-process cutout comparison produced identical PNGs and
all five generated PTX files were byte-identical. Registers remained raygen 70,
any-hit/intersection 65, with raygen continuation stack 96 bytes and spills
236 bytes. Median throughput was 638.9738 versus 641.7760 spp/s; the two ABBA
groups had inconsistent directions. This establishes unchanged cutout code,
resources and images for this workload, without a throughput improvement claim.
Results are in
build-msvc-llvm/test-results/cutout-runtime-abba-20260930-020205-349878/.
Cache v16 further reduces the private query state from 112 to 88 bytes:
it retains the acceleration handle and original instance-table pointer, removes
the unused binding count/padding and query flags, and preserves public resource
bindings and captured-reference identity. Full build and all 22 regressions
passed. In another eight-process ABBA comparison, median mixed-query throughput
increased from 2,205.40 to 2,289.92 Mqueries/s (+3.83%); both groups improved
(+2.73% and +5.14%). PTX input decreased from 30,736 to 29,913 bytes. Every
numerical check passed. GPU clock states still differed, including 7,001/8,001
MHz memory states, so these are workload-specific observations rather than a
guaranteed speedup. The record is
build-msvc-llvm/test-results/ray-query-compact-query-v16-20260930-021321/.
The v16 cold cutout renders retained the earlier route-specific image hashes.
All five default PTX artifacts remained byte-identical, including the 35,366-byte
ray-query shader (b7c9478e16a16506addbc6ec2b5c81ec7b2456d6ce4d7484b54fcd34d8472d9c).
The record is .deps/optixir-compile-ab-20260930-021409/.
A final four-process AST/LLVM ABBA comparison of the mixed-query benchmark
measured 2,520.10 Mqueries/s for AST and 2,240.775 Mqueries/s for LLVM/PTX
(median of each route’s process medians). LLVM remains about 11.1% behind
AST on this workload. The cutout and mixed-query results should not be generalized
to each other. All four numerical checks passed; the record is
build-msvc-llvm/test-results/ray-query-v16-final-ast-llvm-20260930-021937/.
Direct traversal operands (OptiX IR revision 9)
Direct IR now passes its nine native LLVM float operands directly to the OptiX traversal intrinsic, matching the SDK form. The existing typed-register workaround remains in the PTX path. The fixed intrinsic signature, active payload count and memory clobber are unchanged; only the OptiX IR cache revision changes.
Eight cold-cache ABBA processes preserved the image hash in every run and reduced
the cutout module from 21,516 to 20,664 bytes. Median host generation was
75.454 to 69.605 ms, driver module creation 315.879 to 259.686 ms, and total
compilation 417.772 to 349.289 ms. The groups moved in opposite directions, so
these aggregate medians do not establish a stable compile-time speedup.
Registers, stack and spills were unchanged. The record is
.deps/optixir-runtime-abba-20260930-022011-1932967/.
All 11 OptiX IR/metadata regressions passed. Both LLVM/PTX and OptiX IR also
passed Compute Sanitizer with 38 assertions and zero memory errors, covering
the ray-query integration fixture, including large captures, compile-only
production, cache reuse and AOT loading. Logs are in
.deps/final-query-memcheck-v16-optixir9-{0,1}/.
Final build-gate validation
The selected MSVC/Ninja tree was reconfigured with the OptiX IR CMake option
OFF, fully rebuilt using cmake --build, and passed 12/12 PTX/metadata
tests. Inspection of the actual CUDA compile/link rules confirmed that the
IR encoder, legalizer, LLVM 7 writer and enable macro were absent. CUDA, Fallback,
SIMD, Remote, GUI and LLVM remained enabled. The option was then restored ON,
the full tree rebuilt again, and 22/22 combined tests passed. The final local
build includes OptiX IR, while runtime selection remains explicitly opt-in.
Records are .deps/optixir-cmake-gate-{off,on}.json,
.deps/optixir-gate-{off,on}-build.log, .deps/optixir-gate-off-tests.log and
.deps/optixir-final-all-tests.log. All five cutout PTX shaders and the PTX PNG
were byte-identical between the OFF build and the final ON build; final OptiX IR
artifacts and images also matched the earlier revision-9 comparison exactly.
The final six-process cold-cache check (three per route) measured these medians:
Compilation stage |
LLVM/PTX |
LLVM/OptiX IR |
|---|---|---|
Host LLVM generation |
80.9134 ms |
70.3120 ms |
OptiX |
166.917 ms |
181.499 ms |
Total shader compilation |
302.3605 ms |
272.3857 ms |
Individual-stage medians need not sum to the median total. Host generation was
13.1% faster in this sample; total compilation also favored IR here, but earlier
comparisons reversed that result. These measurements do not establish a stable
end-to-end compile-time improvement. All six renders succeeded and reproduced
their route-specific hashes. The record is
.deps/optixir-compile-ab-20260930-022827/results.json.
Reconstructing resource captures (cache v17)
The shared XIR analyze_unique_resource_origins analysis proves which resource
arguments forward an unchanged kernel argument through every ordinary-call and
ray-query callback edge. Kernel roots map to themselves. Multiple roots,
computed descriptors, unknown function uses and cyclic dependencies remain
unproven. The analysis does not assume that resource contents are immutable.
CUDA LLVM excludes proven resource descriptors from both register captures and scratch contexts. AH and IS reload the complete descriptor from the corresponding constant launch-parameter field, including buffer view offset and length. Bound texture storage assumptions use the original kernel argument index. Ordinary captured values retain their snapshot semantics, and captured references retain their addresses. Explicit per-capture field mappings preserve callback argument order when resource and non-resource captures are interleaved.
The 32-word budget is computed after removing these descriptors. General queries still reserve two words for the query pointer and one for the pipeline ID; captures beyond the remaining 29 words use private scratch and a two-word context pointer, for five payload words total. This change does not yet decompose the general query’s mutable state into bidirectional payloads.
The full MSVC build and 23/23 focused tests passed, including the new shared
analysis test and AH/IS resource fixtures with offset views, live lengths,
conflicting callable roots, and both direct and oversized captures. Both output
routes passed Compute Sanitizer with 42 assertions and zero memory errors.
The records are .deps/rq-resource-v17-tests.log and
.deps/final-query-memcheck-resource-v17-{0,1}/.
The mixed-query benchmark’s active payload decreased from 12 to 8 words.
OptiX reported registers falling from 74 to 70 in raygen, 83 to 78 in AH
and 84 to 80 in IS. Raygen continuation stack/spills remained 160/12 bytes;
IS direct stack remained 8 bytes. PTX input shrank from 29,913 to 26,197
bytes. Optimized LLVM IR confirms descriptor reloads from @params in the
callbacks; see .deps/rq-resource-v17-dump/. These code-size and resource-count
changes do not by themselves establish a throughput improvement.
The subsequent eight-process v16/v17 LLVM comparison changed only the CUDA DLL.
Raw process-median throughput was 1,718.810 versus 1,113.680 Mqueries/s;
the two ABBA groups moved +5.63% and -35.21%, respectively. GPU-utilization
samples at or above 90% had per-process median graphics clocks ranging from
825 to 1,665 MHz, and the CSVs do not isolate measured phases. All eight numerical
oracles passed. This unstable result does not establish a throughput improvement;
no slow runs were removed. The complete record is
build-msvc-llvm/test-results/ray-query-resource-v17-long-20260930-093030-124997/.
A longer v16 AST/LLVM baseline used 1,048,576 rays, 1,000 warmup dispatches,
512 dispatches per sample and nine samples across eight ABBA processes. Identical
LLVM code ranged from 921.91 to 2,627.28 Mqueries/s. Active GPU clock states varied
substantially; the clock CSVs include compilation and validation, without exact
sample boundaries. These results do not establish a stable relative throughput
or a precise cause of the variation. All eight runs and numerical checks remain
in build-msvc-llvm/test-results/ray-query-v16-ast-llvm-long-20260930-085618-997821/.
Recovering native traversal results (cache v18)
Qualified AH/IS callbacks now use the implicit OptiX hit object instead of passing the caller’s query address through traversal. AH supports candidate-hit reads, triangle commits, and termination after a commit in the same block. IS supports candidate-hit/ray reads and procedural commits. Invocation-local state preserves bound tightening and invalid-then-valid or valid-then-invalid commit behavior; LLVM inlining/SROA removes unused fields. Final hit kind, distance, instance, primitive and barycentrics are decoded after traversal, with guarded miss/triangle/curve/custom getters and zero custom barycentrics.
Intermediate committed-hit/state observations, query aliases, AH ray-bound observations and procedural termination retain the general stateful path. This is a conservative qualification, not a claim that every query needs no software state. The native-result layout still reserves two null query-pointer words in v18; removing that padding is a separate ABI change.
The full MSVC build and 23/23 focused tests passed. Added fixtures exercise
accepted surface termination and procedural commit recovery with exact bounds,
including NaN/Inf in precise mode. Both PTX and OptiX IR passed query integration
under Compute Sanitizer with 43 assertions and zero errors. Evidence:
.deps/rq-hardware-v18-{build,tests,memcheck}.log.
For test_procedural, optimized LLVM no longer has a query alloca. PTX input
decreased 16,305 -> 14,366 bytes, raygen continuation stack 128 -> 32 bytes,
and AH registers 73 -> 67; raygen stayed at 67 registers and IS increased
72 -> 74, with zero spills. The dumps are in
.deps/procedural-hardware-v18-dump/.
The initial 1024-spp comparison correctly stopped on differing PNG hashes.
All 1,207 changed pixels were black in v17 and colored in v18; AST, Fallback
and SIMD were nonblack at all those positions. Fallback, SIMD and v18 each
passed the existing gallery reference. Inspection found the old fast-math
IS range checks can accept a NaN into software hit state even when native
reporting does not accept it. The renderer can produce that NaN when subtracting
nearly equal squared distances before a square root. This is not a finite-input
query regression; precise-mode NaN rejection remains covered separately.
The failed exact comparison and subsequent CPU image analyses are preserved in
procedural-hardware-v18-abba-20260930-094553-000023/ under the test-results
directory and .deps/procedural-hardware-v18-*-image-analysis.json.
A separate eight-process v17/v18 ABBA run required every process to pass the
unmodified gallery reference and exact PNG reproducibility within each version.
Median throughput was 1,141.27 -> 1,189.42 spp/s (+4.22%), with group ratios
+3.91% and +4.18%. It includes blit and final readback; laptop clocks were
unlocked, so this is a local observation rather than a general speed guarantee.
The v18 AST/LLVM four-process comparison measured 1,211.28 / 1,205.53 spp/s.
Full records are procedural-hardware-v18-reference-abba-20260930-095720-629103/
and procedural-hardware-repeat-abba-20260930-095053-182962/ under test-results.
A single-instruction diagnostic on a private v17 PTX cache changed only the
perpendicular-distance rejection from setp.gt to setp.gtu, rejecting NaN.
It removed all 1,207 reported black pixels (matching v18 there exactly) and
43 additional bright-region pinholes still present in v18. Every originally
nonblack pixel was unchanged. Thus native-result recovery is not by itself a
complete fix for NaN capture side effects. The untouched baseline, exact patch,
cache hashes and output analysis are preserved in
.deps/procedural-v17-nan-diagnostic/; no reference image was regenerated.
Compact native-result payload prefix (cache v19)
Cache v19 removes the unused query-pointer prefix from qualified native-result
pipelines. Every pipeline places its ID in payload p0. General stateful queries
place their original query pointer in p1/p2; native-result callbacks need no
caller query pointer. Captures retain their snapshot or reference identity, and
proven kernel resource descriptors are still reconstructed from launch parameters.
Pipeline path |
Direct captures |
Direct capture budget |
Oversized/unsupported capture fallback |
|---|---|---|---|
Qualified native result |
Start at |
31 words |
|
General stateful query |
Start at |
29 words |
|
The host-declared capacity remains 2–32 words and is the maximum required by any pipeline in the shader. A capture-free native-result pipeline uses one meaningful word but retains the minimum two-word declaration. Direct captures that fill either budget use all 32 words. Mixed general/native-result handlers share one consistent active count; the private OptiX intrinsic still has a fixed 32-word signature with unused tail operands. Large capture aggregates use the context fallback rather than exceeding that signature.
RAY_QUERY_PAYLOAD_COUNT continues to round-trip through cache and AOT metadata.
Legacy metadata without the field defaults to 2. Cache v19 distinguishes the
new generated ABI; existing explicit AOT files retain their own saved module
and count and are not reinterpreted as newly generated code. This prefix change
applies to LLVM/PTX and LLVM/OptiX IR and leaves the AST/NVRTC two-word ABI intact.
With LLVM 22.1.8, both output routes passed the expanded query integration
fixture with 73 assertions per route and Compute Sanitizer
ERROR SUMMARY: 0 errors. The logs are
.deps/final-query-memcheck-compact-v19-{0,1}/{stdout,sanitizer}.log.
This includes direct/fallback payload boundaries, mixed handlers, resource
reconstruction, and cache/AOT readback. No throughput conclusion is attached to
this layout validation.
Both LLVM 22.1.8 and 23.1.2 completed the full MSVC/CMake build and passed
31/31 focused CTest cases on the RTX 4060 Laptop GPU with CUDA/driver 13.4.
The cases cover both CUDA output routes, dynamic image/volume storage, ray-query
bounds and FTZ behavior, CPU fast-math environments, resource reconstruction,
and the host-only test_llvm_downgrade70 writer regression. That regression
checks 27 numeric vector patterns in globals, nested aggregates and local
returns, plus asymmetric default/f32 denormal modes, through the actual legacy
writer and the matching SDK reader. Logs are in
.deps/final-validation-logs/{00-llvm23,01-llvm22}-focused.log.
Support for both host LLVM API versions does not establish compatibility with arbitrary LLVM, CUDA, OptiX, or driver versions; the runtime OptiX IR path remains explicitly opt-in. These Windows results do not constitute native ARM testing.
Final validation on both SDKs completed eight Compute Sanitizer memcheck
runs: test_cuda_llvm_ray_query and test_cuda_llvm_ray_query_bounds, each
through LLVM/PTX and LLVM/OptiX IR. All processes exited successfully with
zero errors, passing 73 and 266 assertions respectively. Logs confirm the
requested RTX module format, including OptiX IR without a PTX fallback.
All ten 1024-spp procedural renders (Fallback, SIMD, AST/NVRTC, LLVM/PTX
and LLVM/OptiX IR on each SDK) passed the unchanged gallery reference, with
RGB PSNR 44.89–45.25 dB. Saved PNG hashes matched their records. The two
historical diagnostic masks, containing 1,207 affected positions and 43
residual pinhole positions, contained zero all-black RGB pixels in every
render. This checks those known artifacts in this scene; it does not establish
pixel identity across routes or absence of artifacts in arbitrary scenes.
Manifests, process results and pinhole-analysis.json are retained under
.deps/final-dual-llvm-22-final-20260930-121642-6149630/ and
.deps/final-dual-llvm-23-final-20260930-121959-0258811/.
Local performance measurements after LLVM 22/23 validation
On 2026-09-30, each SDK ran two serial ABBA groups per scene on the RTX 4060 Laptop GPU. Each fresh process used a private shader cache and disabled the OptiX disk cache; the runtime files were identical within each SDK comparison. Only the OptiX IR environment switch changed. Logs verified the requested module format and fresh LLVM generation. All 32 renders passed their original gallery references, and PNGs were repeatable within each SDK/scene/route.
Cutout used 4096 spp and three iterations, with 64 spp per dispatch and no register cap. Its reported throughput excludes the first iteration and uses the median elapsed time of the remaining two. Procedural used 1024 spp; its post-compile interval includes first launch, blit and final readback. These are application timings, not isolated GPU kernel timings. The table contains medians across four processes per route; times are milliseconds.
LLVM |
Scene |
PTX spp/s |
OptiX IR spp/s |
RTX LLVM generation, PTX / IR |
OptiX module creation, PTX / IR |
|---|---|---|---|---|---|
22.1.8 |
Cutout |
499.38 |
473.87 |
138.88 / 235.04 |
217.43 / 1419.48 |
22.1.8 |
Procedural |
1219.76 |
1188.10 |
107.09 / 96.56 |
121.67 / 129.33 |
23.1.2 |
Cutout |
558.13 |
400.13 |
182.31 / 304.33 |
567.78 / 763.62 |
23.1.2 |
Procedural |
1210.78 |
1217.20 |
112.63 / 85.31 |
115.51 / 142.89 |
These measurements do not establish a stable OptiX IR speedup. Cutout had large within-process and between-process variation, and compilation had long tails on both routes. For example, the first four LLVM 22 Cutout runs had median graphics clocks of 1320, 1680, 1005 and 1560 MHz among samples with at least 90% GPU utilization. Clock logs cover entire processes rather than exact measured phases. The machine was reported idle; no clocks or power settings were changed, and no slow runs were discarded. IR/PTX throughput ratios for the two groups were 0.994/0.898 (LLVM 22 Cutout), 0.879/0.979 (LLVM 22 Procedural), 0.868/0.735 (LLVM 23 Cutout), and 1.042/1.004 (LLVM 23 Procedural). Skipping LLVM’s PTX emission did not demonstrate lower end-to-end compilation latency in these runs. OptiX IR remains opt-in.
Full manifests, per-process timings, compiler properties, clock samples and
images are in
build-msvc-llvm/test-results/final-llvm22-ptx-ir-abba-20260930-122459-716262/
and
build-msvc-llvm23/test-results/final-llvm23-ptx-ir-abba-20260930-123142-158800/.
A separate LLVM 22 v18/v19 comparison changed only the CUDA backend DLL. Cutout’s payload fell from 3 to 2 words and AH/IS registers from 65 to 64; raygen stayed at 70 registers with a 96-byte continuation stack and 236 bytes of continuation spills. Its two ABBA throughput ratios were 0.651 and 1.372, so this does not establish a throughput change. Procedural measured 1208.23 / 1206.54 spp/s with group ratios 1.021 and 0.975, also without an established speedup. All same-scene v18/v19 PNGs were identical.
The separate Cutout AST/LLVM comparison still showed a performance gap:
602.66 / 429.82 spp/s, with LLVM/AST group ratios 0.682 and 0.697. LLVM
raygen used 70 registers versus AST’s 64; continuation spills were 236
versus 232 bytes. These statistics do not measure occupancy or establish
the cause of the gap. LLVM process medians ranged from 267.66 to 560.82
spp/s, so the aggregate difference is not a stable estimate of its size.
This comparison and the v18/v19 Cutout comparison take the median of all
three iterations, unlike the PTX/IR comparison above; their aggregate
numbers must not be compared directly. Evidence is in
build-msvc-llvm/test-results/cutout-{v18-v19,ast-v19}-final/ and
build-msvc-llvm/test-results/procedural-v18-v19-final-abba-20260930-124733-476831/.
October 1, 2026 checkpoint (LLVM cache v21)
The following Cutout measurements are a new checkpoint, not a reinterpretation
of the historical results above. They use LLVM 22.1.8, MSVC/CMake/Ninja,
CUDA 13.4 and OptiX 9 on the RTX 4060 Laptop GPU. Both LLVM 22.1.8 and
23.1.2 completed full builds and passed the callback comparator regressions,
including differing GEP inbounds/nuw/nusw flags. The final LLVM 22 focused
suite passed 32/32. LLVM 23 also passed an earlier 32-case suite; its final
suite had one ray-query timeout during recorded Modern Standby, followed by
a passing standalone rerun in 5.69 s. The failed log is retained. These
API/correctness checks do not extend the performance measurements to LLVM 23.
Cache v21 shares equivalent, capture-free hardware-result callback dispatch bodies within one stage. Per-query trace flags and IDs, general captured-state paths, and numerical bounds checks are preserved. The comparison checks function attributes and ABI, rejects unsupported metadata/identity features, and compares instruction flags along matching control-flow edges in addition to LLVM’s structural comparator.
Separate 1024-spp diagnostic renders prove one shared surface dispatch target on both routes. OptiX reports smaller any-hit bodies and whole modules; these counts are compiler instructions, not SASS instructions:
Compiler / module statistic |
LLVM/PTX v20 → v21 |
LLVM/OptiX IR v20 → v21 |
|---|---|---|
Basic blocks |
19 → 11 |
17 → 10 |
Instructions |
60 → 39 (−35.0%) |
55 → 36 (−34.5%) |
Module bytes |
35,135 → 34,398 |
20,636 → 20,420 |
The payload remains two words. Physical AH/IS allocation remains 64 registers with no stack or spills. Raygen is also unchanged: PTX uses 70 registers, 96-byte continuation stack and 236-byte continuation spills; OptiX IR uses 68 registers, 8-byte direct stack, 128-byte continuation stack and 232-byte continuation spills. These properties do not establish occupancy or a cause of any runtime difference.
All timed runs use 4096 spp × three iterations, 64 spp per dispatch and no
register cap. A run’s warm throughput uses the median elapsed time of
iterations 2/3; table entries are medians across processes. These application
timings include AS updates, accumulation and stream completion. Cold total
measures the separately logged RTX shader compilation interval. Processes
start suspended, receive verified affinity 0x15400 (four distinct P cores),
and use independent copy2 runtimes and fresh shader caches, with OptiX disk caching
disabled. Source/binary hashes and actual module formats are checked.
Each v20/v21 comparison uses two ABBA groups, four processes per version. Both snapshots use the exact frozen v20 executable; only the CUDA backend DLL differs. The two output formats were measured in separate experiments:
Route |
v20 / v21 spp/s |
v21/v20 change |
Per-group change |
Cold RTX total v20 / v21, ms |
|---|---|---|---|---|
LLVM/PTX |
904.69 / 904.08 |
−0.07% |
+0.63%, −0.79% |
274.31 / 266.86 |
LLVM/OptiX IR |
831.23 / 837.68 |
+0.78% |
+0.98%, +0.75% |
274.20 / 292.67 |
PTX is effectively unchanged; the small IR improvement is comparable to its roughly 1% run variability. Warm-window graphics-clock medians match within each version comparison: 2325 MHz for PTX and 2280 MHz for IR. IR v21 still has 14 P3 samples and a transient 7001 MHz memory clock. No frequency correction is applied, and the instruction reduction is not a demonstrated large speedup.
Two further comparisons each use one ABBA group, two processes per route, with one common current-v21 runtime and executable snapshot:
Comparison, first / second |
Warm spp/s |
Second/first change |
Cold RTX total, ms |
Warm graphics MHz |
|---|---|---|---|---|
LLVM/PTX / LLVM/OptiX IR |
910.22 / 847.86 |
−6.85% |
270.05 / 272.33 |
2340 / 2295 |
AST/NVRTC / LLVM/PTX |
906.03 / 902.12 |
−0.43% |
1101.26 / 283.66 |
2310 / 2325 |
In the direct-format comparison, IR reduces RTX LLVM generation from 92.05 to 75.50 ms, but OptiX module creation rises from 157.90 to 180.47 ms. It does not reduce total compilation time. LLVM/PTX is near AST’s rendering throughput in this single group while taking 25.8% of its total compilation time. Warm clock samples cover logged iterations 2/3 with utilization ≥80%; both direct comparisons remain in P0 at 8001 MHz memory. Their sample size and different graphics clocks preclude a general speed claim. OptiX IR remains opt-in.
All 24 timed renders pass the unchanged gallery, with exact PNG repeatability within each route/version. v20/v21 images are byte-identical within each format. Direct PTX/IR and AST/LLVM images differ slightly (RGB PSNR 69.49 and 66.47 dB, respectively; maximum channel difference 5/255, unchanged alpha); cross-route identity is not required. The four diagnostic renders also pass their gallery.
Local evidence is retained in .deps/oct01-cutout-v20-v21-{ptx,optixir}-abba/,
.deps/oct01-cutout-v20-v21-codegen-proof/,
build-msvc-llvm/test-results/oct01-v21-ptx-optixir-direct-abba-20261001-145507-168414/
and .deps/oct01-cutout-ast-v21-ptx-abba/. These ignored packets contain exact
commands, hashes, cold/warm timings, clock samples and images. Correctness logs
are .deps/oct01-final-build-msvc-llvm{,23}-focused.log,
.deps/oct01-rq21-focused23.log and .deps/oct01-rq21-timeout-recheck23.log.