Status: the authenticated stockflow path is now installed and live-tested on a
CMP 50HX. Full SM and Tensor work is proved. A separate host RT-count patch
also proved that the zero count is an API exposure gate. A read-only live SM
trace proves that the first ray-query machine-code group raises warp error 9,
INVALID_OPCODE, at decode, before FECS stalls and reports Xid 109; the
faulting address latches deterministically at 0x738/0x73c and repeats across
reboot. As of 2026-08-30, real RT execution is assessed as not achievable by
any software or firmware path found in this project. The five exact user-space
DSOs contain a full positive RT compiler path but no proved CMP reject branch;
forcing the alternate GPUCOMP backend changes no saved pipeline-cache or
metadata byte. The best
remaining explanation is a physical RT floorsweep fuse or reset-latched decoder
state below every writable path reached by the driver, GSP-RM, signed Booter,
netlist, FECS override, and user-space compiler. This remains an inference, not
direct electrical proof. See the feasibility assessment below. The compute
path is unaffected.
On 2026-08-13, a board-gated NVIDIA kernel-module change also made the CMP
BAR1 aperture 16 GiB. The state survived a cold boot and passed the compute,
Tensor, and OpenCL checks below.
The same all-feature module now also opens the protected TU102 PCIe policy and
trains the card at PCIe Gen2 x4. A clean package build survived a cold boot,
passed every live check, and doubled measured host-transfer bandwidth without
a compute or VRAM-bandwidth regression.
The target is PCI ID 10de:1e09, a Turing TU102 CMP 50HX with 56 SMs and
3584 CUDA cores. It is not an RTX 2080 die: RTX 2080 uses TU104. The useful
target is normal RTX-class Turing behavior from the enabled TU102 units, with
the stock 10 GB memory layout left unchanged.
packages/cmp50hx-all-610.43.03
is the single supported build path for all proved CMP 50HX changes. It combines
the full instruction issue-rate path, the board-scoped 56 RT-core report, and
the 16 GiB BAR1 ReBAR path, and the board-scoped PCIe Gen2 path in one clean
patch against NVIDIA’s official 610.43.03 source tag. It also checks the
per-patch source and IDA evidence in
packages/cmp50hx-all-610.43.03/patches/README.md.
natural 3584 CUDA and 448 Tensor counts after reboot.
The package checks source and artifact hashes, the running kernel ABI, PCI ID,
PCI class, and the tested 10de:1554 or 1462:371f subsystem before install.
It saves rollback data, does not unload the live driver, and includes one live
verification command. The full remote build passed on kernel
6.8.0-137-generic on 2026-08-13.
This package does not claim real RT execution, a link wider than x4, display, NVDEC, or NVENC. The 56 RT-core value opens the host API only; the physical RT execution fuse remains closed.
On 2026-08-13 the Gigabyte AB350M-DS3H V2 test host was updated to modified F54
firmware with ReBarDxe 0.3. ReBarState=32 enables automatic selection of the
largest size advertised by each device. After reboot, Ryzen 3 2200G Vega BAR0
was 1 GB and BAR2 was 256 MB, both GPU drivers loaded, and Linux reported zero
PCI resource-allocation errors. The image hashes, flash path, and full host
check are in
artifacts/ab350m-ds3h-v2-rebar-f54/VERIFY.md.
The CMP path is now also working, through the NVIDIA kernel module rather than
UEFI. On PCI ID 10de:1e09, module parameter cmp50_rebar_size=8 changes the
TU102 XVE ReBAR selector at BAR0 0x88dcc from 0 to 8 and its size mask at
0x88bbc from 0x400 to 0x7fc00. The normal NVIDIA BAR resize path then
rebuilds the bridge window and assigns a 16 GiB BAR1 aperture. The earlier
PSTRAPS path at 0x101000/0x10100c did not accept the size write; it is not
used by the working patch.
The change was first tested by a live module swap at 128 MiB, then at 16 GiB.
It was installed in initramfs and proved again after a cold boot into kernel
6.8.0-137-generic. Linux reports Region 1 as size=16G; nvidia-smi reports
BAR1 total 16384 MiB; the card stays in P0 and keeps the full issue-rate
override, 3584 CUDA cores, 448 Tensor cores, and the host-side report of 56 RT
cores. The final all-feature nvidia.ko SHA-256 is
cb320f3ffc89892cf9938a569c2f8919d4eee3a4b3c466353ddf0a4580593798.
Its latest package rollback is
/var/lib/cmp50hx-all/backups/20260813T094002Z; the original ReBAR-only
pre-install files remain at /var/tmp/cmp50-rebar-install-backup-20260813T0745Z.
Source, patch, live-swap tool, register proof, and rollback notes are in
experiments/cmp50-rebar-kernel.
This is an R&D project. Each claim uses one of these levels:
An address found in firmware is not, by itself, proof that a host write is safe or that the write changes performance.
This section states the current answer to the project’s RT-execution goal and grades the evidence. Short form: no software-reachable state has been found that makes the SM accept the ray-query opcodes, and every privilege level and state class that could plausibly hold such a gate has now been checked and excluded except one that cannot be tested here. The remaining gate behaves as a physical RT floorsweep fuse sampled by the SM at reset.
INVALID_OPCODE, at instruction decode. It is deterministic: the same SM,
GPC2/TPC0/SM0, latches the same faulting address 0xdf5ea5bc10 on two runs
separated by a reboot (runs/20260812T1240Z-esr-addr,
runs/20260812T1300Z-esr-addr-repeat). A hardware decode rejection cannot be
satisfied by changing any later reporting or exception-handling code.0x21168 reads zero, and both of the only two GSP readers of its
OBJFUSE ID 0xff00008e (0x529b5a0, 0x52bcefc) use it solely to compute a
reported core count. Neither writes SM, clock, power, context, or dispatch
state. Raising the reported count to 56, which this project already does,
therefore exposes the API without enabling execution.0x419bc8, 0x419bf0, 0x419e5c) reach the RT-capable TU102 values
live, but a ten-phase read-only timeline shows exactly how. After GSP init
the tuple is mixed: 00398a2c/844077fe/00005cc1. It stays unchanged through
graphics state load and static-info load. Only
kgraphicsCreateGoldenImageChannel changes the last two words, producing
00398a2c/944077fe/000058c1. These registers are golden-context state, not
the fixed opcode gate. Full evidence is in
experiments/cmp50-rt-register-timeline.1 to 0x21168, performed before GSP-RM starts, read
back zero both immediately and after GSP ready
(experiments/cmp50-rt-fuse-booter).0x409650..0x40966c, which the stockflow
payload already opens and writes for SM issue-rate, is not an RT gate. Its
registers govern issue-rate throttle and FECS-level features; the feature
readout 0x409660 = 0xf3 is consumed by FECS falcon ucode, and GSP-RM reads
it only as the diagnostic the stockflow patch added. It has no literal reader
in the GSP image that branches on it for RT.readFuseById HAL slot. The only RT ID is 0xff00008e, and it feeds the
count reader at 0x529b5a0; the other IDs are unrelated masks. Symbolic
execution of the full OBJFUSE HAL initializer for TU102 through TU117
(HalVarIdx 37..41) found 67 installed slots and no RT/non-RT split.+0xc88 wrapper only changes register-map ID 0x444 after
the common init method. No method controls SM ray-opcode legality.0x72a3 and TU116 target 0x6ffb contain the same byte-for-byte 32-bit
multiply helper. This closes the last apparent FECS code delta as an RT
gate. Full evidence is in experiments/cmp50-rt-opcode-gate.0x21168 or the three RT
netlist words. No RTX-only VBIOS GR init write exists in the decoded path.
Full evidence is in experiments/cmp50-vbios-comparison.sm_75 profile table/mapper
and target register-layout switch, but those use target kinds and feature
words rather than CMP or fuse policy. No supported CMP, PCI-ID,
product-name, mining-card, or RT-fuse reject branch was found in the mapped
host RT path. The last narrow
compiler candidate was tested: RTCORE mode changed from automatic -1 to
forced external GPUCOMP 1, but two runs per mode produced byte-identical
10,099-byte caches and identical compiler metadata. No command buffer or
GPU work was submitted. Full evidence is in
CMP50HX-SHARED-OBJECT-AUDIT.md.
The GPUCOMP finalizer itself has generic unsupported instruction, SASS
generation failed, and unsupported SM version errors, but its visible
off-target success path copies the ELF and retags only its machine field;
it is not proved to translate SASS or unlock RT execution.0x21000+ is a hardware fuse controller; its writable
shadow already rejects a signed-Booter write (point 4), which is the strongest
privilege available here. An override that reaches the SM decoder, if one
exists, would have to outrank that, which no NVIDIA firmware path in the GSP
image does.For a software and firmware unlock, which is the scope of this project, real RT execution on CMP 50HX is assessed as not achievable: the gate is a hardware decode property fixed by a physical fuse the SM samples at reset, below every writable register reached by the driver, GSP-RM, the signed Booter, the netlist, FECS feature override, and tested user-space compiler selector. The positive goal, 56 reported RT cores with successful RT pipeline construction, stops at API exposure and compilation; no RT instruction executes. The only ways past it that remain are outside the proved software paths: a real RTX TU102 reference to disprove the fuse model, or physical fuse work. Neither is a driver change.
This does not weaken the compute result. Full SM speed, 3584 CUDA cores, 448 Tensor cores, and the 10 GB layout are unaffected and still pass.
The test card is a 10de:1e09 CMP 50HX on Linux with the NVIDIA 610.43.03
open kernel module. The V551 stockflow patch and the board-scoped RT-count
override are installed and survive reboot. After a clean boot the card enters
P0, reports full issue rates, and runs at about 1905 MHz during the checks,
with a reported 2100 MHz SM maximum. The post-reboot checks below were made
on 2026-08-11 and the kernel log had no new Xid.
The 2026-08-12 console capture also contains older
POST_FWSEC_COMMIT_FAIL/rm_init_adapter failed lines from warm module-load
attempts. They precede the final successful SIGNED_FULLSPEED_STOCK_RELOCK,
GSP_READY, and DRM initialization lines. A warm load on this host needs a PCI
function reset before loading the module; otherwise those old failures can be
mistaken for the current state. The final No compatible format found and
Cannot find any crtc or sizes messages are display/KMS scanout warnings on
the headless CMP board, not CUDA, Tensor, or RT execution errors.
3584 CUDA cores and 448 Tensor cores.107.1-107.2 TFLOPS through real HMMA instructions.Tensor work is therefore already unlocked. There is no current Tensor feature patch to add. LLM work should use the normal CUDA/Tensor path.
A fresh read-only performance pass was made on the installed all-feature
610.43.03 package. The new low-level tests were built in /var/tmp; no system
library, module, firmware, clock, or power setting changed.
| Path | Fresh result |
|---|---|
| RM issue-rate and core-count gate | PASS_CMP50HX_ISSUE_RATE_AND_COUNTS |
| DP4A issue test | 2782.423 G thread-instructions/s |
| DP2A-pair issue test | 4435.777 G thread-instructions/s |
| FFMA issue test | 2584.518 G thread-instructions/s |
| FMUL+FADD issue test | 4404.997 G thread-instructions/s |
| FP16 Tensor WMMA | 106.819 TFLOPS, exact sample check passed |
| OpenCL FP32 | 13.501 TFLOPS |
| OpenCL coalesced read/write | 504.98 / 474.54 GB/s |
| Pinned CUDA PCIe H2D/D2H | 1.702 / 1.709 GB/s, data check passed |
| Vulkan raster, five long runs | 99.65-100.01 Gpix/s, mean 99.782 |
| Vulkan pixel check | 8192/65536 orange pixels, pass |
All 79 combined-test telemetry samples stayed in P0 at 1905 MHz. Power was
61.43-118.15 W, temperature was 42-46 C, and no new NVRM, Xid, AER, or
PCIe error appeared. The separate OpenCL run also ended cleanly at P0 with no
VRAM allocation left.
The raster source from PR #30 is now a regular optional test under
tools/cmp50/raster. This copy fixes C11 clock visibility and keeps Vulkan
lineWidth=1.0f. The PR’s GA102 0x823xxx register block is unmapped on this
TU102 card, while the corrected test is already near 100 Gpix/s; none of the
PR register writes should be ported.
The verifier was also made quiet. It no longer sends the unsupported SM
throttle-control GET or the ALLENGINES=0xffffffff class-list GET. The parser
never used these optional results. The rebuilt verifier kept the full package
pass and produced zero new NVRM lines.
The IDA check now closes the indirect firmware path. TU102 stores callback
0x528f508 in Graphics+0xb08. Function
kgraphicsApplyInitOverrides_inferred loads that slot and calls it at
0x52a5a4c for RMOverrideSmSpeedSelect, and at 0x52a5a8c for
RMOverrideSmSpeedSelect1. The callback constructs only register-map IDs
0x9664 and 0x966c. A raw RISC-V instruction scan found no matching
0x9670 or 0x9674 construction in this path. The old IDB comment that sent
this TU102 path to the alternate OBJFUSE callback was corrected and saved.
BAR0 0x409670 remains a read-only research point, not an unlock candidate.
There is no checked firmware use or measured speed limit that supports a write.
Raw output and hashes are in
artifacts/cmp50-performance-matrix-20260817.
A short llama.cpp hardware smoke test on 2026-08-12 loaded the 12 GB
Qwen3.6-35B-A3B-UD-IQ2_XXS.gguf model with the fit planner using about
8700 MiB of the 10 GB card. It produced valid output at about 44.5 prompt
tokens/s and 42.6 generation tokens/s. The 240-second wrapper ended only
because llama-cli stayed at its interactive prompt after generation. No
process or new NVRM/Xid error remained, and the card returned to P0 with zero
VRAM use. This is a smoke result, not a standardized model benchmark.
The existing CUDA-enabled llama.cpp tree at /var/tmp/llama.cpp was completed
with the llama-bench target and run on the same card. The benchmark used the
local /var/tmp/Qwen3.6-35B-A3B-UD-IQ2_XXS.gguf model, three repetitions, an
8,192-token fit context, Q8 KV cache, Flash Attention, and 2048/2048
batch/ubatch sizes. Exact command and raw JSON are in
experiments/cmp50-llama-bench/runs/20260812T065300Z.
| Phase | Mean | Spread |
|---|---|---|
| Prompt, 512 tokens | 264.292 t/s |
5.924 t/s |
| Generation, 128 tokens | 48.429 t/s |
0.665 t/s |
Both phases completed with exit code 0. This is higher than the earlier
interactive smoke result because llama-bench excludes tokenization, sampling,
and interactive prompt overhead. The model is about 11.8 GB while the card
has 9798 MiB visible VRAM, so the run uses llama.cpp’s fit planner and is not
an all-GPU 35B measurement. It is nevertheless a valid end-to-end CUDA result
for the current 10 GB CMP configuration.
An initial batch sweep with n_ubatch=512 found the best generation mean at
n_batch=512 (48.960 t/s), while prompt throughput peaked at
n_batch=2048 (266.682 t/s). This is not yet the final setting: larger
ubatches still need a focused comparison. The sweep data is in the session’s
remote /var/tmp/cmp50-llama-batch-stage1-20260812T070000Z.json output.
The GPU stayed healthy: P0 at 1905 MHz, 40 C before and 43 C after, with
zero VRAM use reported after the run. This confirms that the stockflow compute
path supports useful LLM inference throughput. It predates and does not measure
the later PCIe Gen2 unlock.
The downloaded unsloth/Qwen3.5-9B-MTP-GGUF quant was then benchmarked with
the same CUDA-enabled llama-bench target, three repetitions, an 8,192-token
fit context, Q8 KV cache, Flash Attention, and 2048/2048 batch/ubatch sizes.
The exact command and raw evidence are in
experiments/cmp50-llama-bench/runs/20260812T071937Z.
| Phase | Mean | Spread |
|---|---|---|
| Prompt, 512 tokens | 1888.711 t/s |
64.452 t/s |
| Generation, 128 tokens | 68.052 t/s |
0.080 t/s |
The model file is 6135034208 bytes with SHA-256
362f85a2d7dbc0259e926d5ac33ca0d0f17fd3753496d65bfd2106384c929d3f. Relative
to the earlier Qwen3.6 35B.A3B quant at 264.292 prompt t/s and 48.429
generation t/s, this 9B quant is about 7.15x faster for prompt processing
and 1.41x faster for generation. This is a model-size result, not evidence
of an additional RTX 2080 Ti hardware feature.
The card reported 9798 MiB visible VRAM and compute capability 7.5; it
stayed at P0, rose from 43 C to 48 C, and returned to 0 MiB VRAM use
after the run.
To measure an actual user request, llama-cli was run with the exact prompt:
I want to wash my car. The car wash is only 100m away from my house, should i walk or drive?
The final invocation used the Qwen chat template, --single-turn (-st),
reasoning disabled for a concise response, a fixed seed, and a 512-token cap.
The exact transcript and before/after GPU captures are in
experiments/cmp50-llama-bench/runs/20260812T073126Z.
[ Prompt: 244.9 t/s | Generation: 65.6 t/s ]
The model completed its answer and recommended driving because the car must be
present at the wash; it noted walking only makes sense if the user wants a
short exercise or to save a small amount of fuel. The process exited cleanly,
and the card returned from 42 C to 47 C with 0 MiB VRAM use.
-st is essential: -cnv without -st left llama-cli waiting for another
interactive turn. A focused batch sweep for this smaller model remains the
next useful LLM measurement.
The remote host was checked before changing any power setting. The CMP 50HX
reports a 225 W current/default/max power limit range of 100-225 W, and
the baseline llama-cli run peaked at 230.46 W according to the GPU’s
instantaneous power samples while running at up to 1905 MHz SM and 7000 MHz
memory. The run produced 194.7 prompt tokens/s and 72.5 generation
tokens/s, completed with no CUDA stderr, and reached 55 C.
This Linux driver does not expose a direct millivolt or fixed-point voltage
setter through nvidia-smi: -q -d VOLTAGE returns no usable GPU voltage
field. It does expose the older NVML GPC VF offset control on this CMP. A
read-only probe reports the current offset as 0 and an accepted range of
-1000..1000. The offset is a curve-equivalent control; the actual voltage in
millivolts remains hidden. The normal supported headless controls are a power
cap (nvidia-smi -pl), a GPC VF offset, and a locked GPU clock, all reversible
with the documented reset commands.
There is a closer MSI-Afterburner-like Linux path through nvidia-settings
and the NV-CONTROL X extension: Coolbits plus
GPUGraphicsClockOffsetAllPerformanceLevels. The public gpu_undervolt
script uses this method, but requires Ubuntu Desktop/Xorg and Coolbits, and
its tested-card table does not include CMP 50HX. This host is headless, has no
X display, and its installed nvidia-settings cannot start because the GTK
libraries are absent. No X/Coolbits changes were made.
The official OpenCL-Benchmark was used to test a temporary efficiency profile:
GPC VF offset: +200
GPU clock lock: 1905 MHz
GPU power limit: 180 W
Five consecutive runs completed with status 0. The average was 13.536
TFLOPS FP32, 26.954 TFLOPS FP16, and 48.512 TIOPS INT8, with 43.608 s
wall time and 179.29 W peak sampled power. No new NVRM Xid or AER lines
appeared. The helper restored offset 0, unlocked the GPU clock, and restored
the 225 W power limit after the run.
The earlier 225 W OpenCL control averaged 13.460 FP32, 26.632 FP16, and
47.872 INT8 with a 230.46 W peak. This profile therefore cuts about 51 W
from the observed peak without a measured OpenCL throughput loss. It is the
current best tested efficiency profile, but it is not yet part of the
end-user patch: the exact voltage is unknown and the control is runtime state.
A follow-up 170 W / +300 trial did not complete an OpenCL run within the
240 s bound. It produced no benchmark metrics and is rejected as an
end-user profile; the card was restored after the trial.
llama-cli 30-second Pareto sweepThe current power/throughput result uses the exact user workload recorded in
experiments/cmp50-undervolt/llama-cli-power-sweep.sh. Every profile was run
five times. A run is accepted only when the final
[ Prompt: ... t/s | Generation: ... t/s ] line appears; the watchdog is
30 s, with a short cleanup grace period for the normal post-metric exit.
The harness records live power, temperature, and before/after NVRM Xid/AER
counts.
| Profile | Prompt average | Generation average | Peak sampled power | Max temp | Result |
|---|---|---|---|---|---|
Stock 225 W, unlocked |
234.4 t/s |
73.20 t/s |
226.67 W |
60 C |
5/5 |
180 W, +200, 1950 MHz |
235.1 t/s |
73.82 t/s |
182.40 W |
55 C |
5/5 |
170 W, +200, 1980 MHz |
235.9 t/s |
71.86 t/s |
178.12 W |
54 C |
5/5 |
160 W, +200, 1980 MHz |
236.1 t/s |
72.02 t/s |
166.59 W |
50 C |
5/5 |
The recommended stock-level profile is 180 W / +200 / 1950 MHz: its measured
generation result is within normal run-to-run variation of the stock control
while lowering the sampled peak by about 44 W. The power-first profile is
160 W / +200 / 1980 MHz: it lowers the sampled peak by about 60 W and
reduces generation throughput by about 1.6% on this workload. Both profiles
completed five of five runs with no new NVRM Xid/AER line during confirmation.
The reproducible control utility accepts:
sudo /home/xrip/cmp50-vfctl apply # 180 W, +200, 1950 MHz
sudo /home/xrip/cmp50-vfctl set 160 200 1980 # power-first point
sudo /home/xrip/cmp50-vfctl reset # 225 W, offset 0, unlocked clocks
The CMP 50HX/Turing path does not support the NVML memory-lock API. Its
memory overclock control is the memory VF offset API, which accepts an offset
in MHz rather than a fixed memory-clock value. status reads the current
offset and the valid range from the driver.
sudo /home/xrip/cmp50-vfctl status
sudo /home/xrip/cmp50-vfctl mem-offset OFFSET_MHZ
sudo /home/xrip/cmp50-vfctl set 160 200 1980 MEM_OFFSET_MHZ
sudo /home/xrip/cmp50-vfctl set-range 160 200 1800 1980 MEM_OFFSET_MHZ
sudo /home/xrip/cmp50-vfctl reset # also sets memory offset to 0
The apply profile leaves the memory offset untouched. On the tested host the
driver reports a memory-offset range of -2000..6000 MHz. The controls are
runtime state only; reset them after a test.
Raw confirmation roots on the test host are
/home/xrip/cmp50-llama-power-sweep-20260814Tconfirm180,
/home/xrip/cmp50-llama-power-sweep-20260814Tconfirm170, and
/home/xrip/cmp50-llama-power-sweep-20260814Tconfirm160c1980.
An earlier reversible sweep was run with a concise llama-cli request at
200 W, 180 W, and 160 W. The exact command, answer, live power samples,
and before/after GPU captures are in
experiments/cmp50-undervolt/README.md and the linked raw run directories.
| Power limit | Peak sampled power | Prompt | Generation | Max temp | Result |
|---|---|---|---|---|---|
225 W baseline* |
230.46 W |
194.7 t/s |
72.5 t/s |
55 C |
stable |
200 W |
191.20 W |
182.4 t/s |
63.5 t/s |
49 C |
correct answer |
180 W |
173.81 W |
183.6 t/s |
61.8 t/s |
49 C |
correct answer |
160 W |
160.08 W |
183.7 t/s |
60.0 t/s |
48 C |
correct answer |
* The 225 W row used the earlier long-reasoning command and is not directly
throughput-comparable with the concise sweep rows. The three capped rows use
identical commands. All exited cleanly, returned the same drive recommendation,
and had no llama-cli stderr or observed CUDA failure.
For that earlier short test, 180 W was the best practical efficiency point:
it cuts about 56 W from the peak baseline while losing only about 2.7 t/s
generation versus 200 W. 160 W is also stable but costs about 5.5 t/s
generation versus 200 W.
A true fixed 800 mV point still cannot be read back or requested directly on
this headless CMP path. The tested GPC VF offset is the current supported
curve-equivalent route.
For real-use observation, the GPU power limit was changed from 225 W to
180 W with nvidia-smi -pl while the same LLM workload ran. After the
observation period, the power limit was restored to the standard 225 W
value; the card immediately reported 1905 MHz SM and 7000 MHz memory
clocks with no active power-cap throttle reason.
The user’s live-use comparison after restoring the standard limit showed
generation increasing from 76.22 to 85.54 t/s: +9.32 t/s, or about
+12.2%. This confirms that the 180 W cap causes a noticeable serving
throughput loss even though the server remains functional.
For the next real-use test, the GPU was configured with:
nvidia-smi -pl 180
nvidia-smi -lgc 1800,1800
The card reported an 180 W power limit, locked 1800 MHz SM/graphics clock,
and normal 7000 MHz memory. The test has now been closed: the GPU clock lock
is reset and the standard 225 W limit is back. The driver reports no active
power-cap or application-clock override. Rollback was:
nvidia-smi -rgc
nvidia-smi -pl 225
The official ProjectPhysX/OpenCL-Benchmark
was built and run on the remote card on 2026-08-12. The exact source commit,
raw output, and hashes are kept in experiments/cmp50-opencl-benchmark.
Two consecutive device-0 runs compiled all OpenCL kernels and completed without
an OpenCL, NVIDIA, or GPU error:
| Metric | Run 1 | Run 2 |
|---|---|---|
| FP64 | 0.420 TFLOPS |
0.420 TFLOPS |
| FP32 | 13.519 TFLOPS |
13.516 TFLOPS |
| FP16 OpenCL | 26.715 TFLOPS |
26.714 TFLOPS |
| INT64 | 3.418 TIOPS |
3.418 TIOPS |
| INT32 | 13.240 TIOPS |
13.240 TIOPS |
| INT16 | 11.561 TIOPS |
11.668 TIOPS |
| INT8 / DP4A | 48.288 TIOPS |
48.231 TIOPS |
| Coalesced read | 504.02 GB/s |
504.11 GB/s |
| Coalesced write | 472.42 GB/s |
472.80 GB/s |
The same binary, driver, device ID, and command were used after a cold boot with 16 GiB BAR1. Each pass used a 1 GiB buffer, 256 samples per compute and device-memory test, and 16 samples per host transfer. All three passes exited with status 0. Their median is compared with the saved pre-ReBAR run 2:
| Metric | Pre-ReBAR | 16 GiB BAR1 median | Change |
|---|---|---|---|
| FP64 | 0.420 TFLOPS |
0.417 TFLOPS |
-0.71% |
| FP32 | 13.516 TFLOPS |
13.410 TFLOPS |
-0.78% |
| FP16 OpenCL | 26.714 TFLOPS |
26.701 TFLOPS |
-0.05% |
| INT64 | 3.418 TIOPS |
3.391 TIOPS |
-0.79% |
| INT32 | 13.240 TIOPS |
13.232 TIOPS |
-0.06% |
| INT16 | 11.668 TIOPS |
11.563 TIOPS |
-0.90% |
| INT8 / DP4A | 48.231 TIOPS |
47.842 TIOPS |
-0.81% |
| Coalesced read | 504.11 GB/s |
503.97 GB/s |
-0.03% |
| Coalesced write | 472.80 GB/s |
471.31 GB/s |
-0.32% |
| Misaligned read | 418.12 GB/s |
415.57 GB/s |
-0.61% |
| Misaligned write | 124.22 GB/s |
123.37 GB/s |
-0.68% |
| PCIe send | 0.85 GB/s |
0.85 GB/s |
0.00% |
| PCIe receive | 0.85 GB/s |
0.85 GB/s |
0.00% |
| PCIe bidirectional | 0.85 GB/s |
0.85 GB/s |
0.00% |
Every median is within 1% of the saved result. This proves no broad OpenCL
performance regression, but it does not prove a speed gain. This benchmark
moves OpenCL buffers through the driver and is not a direct BAR1 mapping test,
so unchanged PCIe throughput is expected. Raw outputs and the comparison are
in experiments/cmp50-opencl-benchmark/runs/20260813T074922Z-rebar16g.
After the runs, BAR1 was still 16 GiB, the card was healthy in P0, and the
kernel log had no new Xid, AER, BAR-allocation, or GPU error.
The same benchmark binary was run immediately before and after the board-gated kernel Gen2 change. Linux sysfs and NVIDIA RM reported Gen1 x4 before and Gen2 x4 after. Both runs exited with status 0:
| Metric | Gen1 x4 | Gen2 x4 | Change |
|---|---|---|---|
| FP32 | 13.425 TFLOPS |
13.415 TFLOPS |
-0.07% |
| Coalesced read | 504.82 GB/s |
503.79 GB/s |
-0.20% |
| Coalesced write | 472.72 GB/s |
473.25 GB/s |
+0.11% |
| PCIe send | 0.85 GB/s |
1.70 GB/s |
+100.0% |
| PCIe receive | 0.85 GB/s |
1.71 GB/s |
+101.2% |
| PCIe bidirectional | 0.85 GB/s |
1.69 GB/s |
+98.8% |
The final clean-package boot logged two CMP50_GEN2: POLICY_PASS lines and a
first-attempt CMP50_GEN2: RETRAIN_PASS. It also passed the issue-rate/count
verifier, kept BAR1 at 16384 MiB, and had no Xid or AER fault. Exact hashes,
boot ID, values, and remote log paths are recorded in
experiments/cmp50-pcie-gen2/runs/20260813T0932Z-kernel-gen2/RESULT.md.
The endpoint and upstream bridge both reported active 5 GT/s x4 status and a
5 GT/s target. Linux kept the endpoint’s early-boot max_link_speed sysfs
cache at 2.5 GT/s, so that stale maximum field is not used as the live gate.
The package was rebuilt from the clean NVIDIA source archive with all four
ordered patches, installed through manage.sh, rebooted, and verified on the
remote CMP50HX. Three runs of the same OpenCL-Benchmark binary all exited with
status 0. The saved result and raw output are in
experiments/cmp50-opencl-benchmark/runs/20260817T073530-split-install/RESULT.md.
| Metric | Baseline | Split-series median | Change |
|---|---|---|---|
| FP32 | 13.516 TFLOPS |
13.417 TFLOPS |
-0.73% |
| FP16 | 26.714 TFLOPS |
26.719 TFLOPS |
+0.02% |
| Coalesced read | 504.11 GB/s |
503.99 GB/s |
-0.02% |
| Coalesced write | 472.80 GB/s |
473.20 GB/s |
+0.08% |
| Misaligned write | 124.22 GB/s |
123.85 GB/s |
-0.30% |
| PCIe send / receive | 0.85 / 0.85 GB/s |
1.70 / 1.71 GB/s |
+100% / +101% |
Every measured median was no more than 1% below the saved baseline. The
independent PCIe capture reported active Gen2 x4, the GPU returned to P0 with
0 MiB used, and no NVIDIA Xid or PCIe error appeared.
A later direct PROM dump proves that the CMP VBIOS is not the source of the
Gen1 cap. Its init script explicitly changes 0x880a8 low nibble to 2, which
requests Gen2; the RTX 2080 Ti reference has a no-op at the same point. The
stock CMP target later becoming 1 places the cap after VBIOS init, in the
protected policy already opened by the all-feature kernel path. The exact ROM
comparison is in experiments/cmp50-vbios-comparison.
The official project’s RTX 2080 Ti reference is 68 SMs, 4352 cores at
1545 MHz, and 13.448 theoretical FP32 TFLOPS. The CMP 50HX measured
13.516 TFLOPS, or about 100.5% of that FP32 reference. The result is
consistent with the CMP’s higher observed 1905 MHz stockflow clock offsetting
its lower 56-SM count. This is a strong confirmation that the compute path is
already near RTX 2080 Ti scalar OpenCL speed; it does not measure Tensor Core
HMMA, for which the separate CUDA result remains approximately 107.2 FP16
Tensor TFLOPS.
The benchmark printed Gen1 x16 for PCIe in both runs, but its own method
estimates generation while assuming x16 width. That label is not accepted as
PCIe proof; Linux sysfs, NVIDIA RM, and the kernel retrain result are the
authoritative Gen2 x4 evidence.
The post-run card stayed healthy in P0 at about 1905 MHz, 41-42 C, with
zero VRAM use and no compute process left running.
The exact GSP path for RM information index 0x22 reads OBJFUSE ID
0xFF00008E. On TU102 this is BAR0 0x21168, bit 0. The live card reads zero,
so the stock GSP returns zero RT cores.
The test patch in experiments/cmp50-rt-count-override changes only the host
RM static GR cache for the two supported CMP 50HX board IDs. Its live result
was:
| State | RM RT cores | VK_KHR_ray_query |
|---|---|---|
| Stockflow only | 0 |
not exposed |
| Host count override | 56 |
exposed |
CUDA and Tensor counts stayed at 3584 and 448. This proves that at least
one NVIDIA user-mode gate uses the reported RT-core count. The override is not
only a display change. The combined V551-stockflow plus RT-count module now
survives reboot. Its rollback path is recorded in
cmpunlocker-v0.1.28-linux-x64-50hx-stockflow/BACKUP_DIR-610.43.03-RTCOUNT.txt.
The first reboot after installation still reported zero because the initramfs
held the older stockflow-only nvidia.ko. Running update-initramfs for the
active kernel put the new module in the boot image. The second reboot then
reported 56, full issue rates, correct 107.19 TFLOPS Tensor output, and no
new Xid. A persistent module install must therefore verify both the module on
disk and the copy in initramfs.
The exact GSP method is now split from a bad IDA function boundary, typed, and
named kgraphicsGetRtCoreCount_TU102_inferred at 0x529b5a0. TU102 HAL index
37 installs it at KernelGraphics+0xb68. It reads OBJFUSE ID 0xff00008e; if
the bit is set, it calls the active-TPC accessor at KernelGraphics+0x9a8 and
returns twice that count for a non-partitioned TU102. The accessor is named
kgraphicsGetActiveTpcCount_inferred at 0x5252bac.
The exact 610.43.03 executable contains only two functions that construct
OBJFUSE ID 0xff00008e. The TU102 function above builds it with the instruction
pair at 0x529b5d0/0x529b5e8. The alternate function, now named
kgraphicsGetRtCoreCount_FuseOrLegacy_inferred at 0x52bcefc, builds the same
ID at 0x52bcf64/0x52bcf6c. The HAL table selects one of these functions for
the same KernelGraphics+0xb68 slot. A full immediate and call-use scan found
no third GSP reader of this exact fuse ID and no reader that writes RT power,
clock, context, or dispatch state.
An exhaustive scan of the KernelGraphics+0xb68 call pattern found three
consumers in this image:
0x22, which is
NV0080_CTRL_GR_INFO_INDEX_RT_CORE_COUNT.VMIOPD_GRSMINFO structure and store the result in its final
rtCoreCount field at offset 0x20.The only later path that builds this structure outside the GR-information
handler reads maxWarpsPerSM at offset 0x0c; it does not read
rtCoreCount. No context-load, FECS/GPCCS, or ray-dispatch caller of the count
method exists in this GSP image.
This makes two earlier statements compatible: the host count patch is useful
because NVIDIA user mode uses the reported count as an API gate, but the GSP
fuse branch itself is a reporting path. Bypassing the branch in GSP would make
its own report say 56; it is not expected to fix the first-traversal Xid 109.
The count override remains part of the target state as requested, while the
execution fix must be below this method.
The read-only RM probe now sends public control
NV2080_CTRL_CMD_GR_GET_ENGINE_CONTEXT_PROPERTIES (0x2080122d) for all 26
defined engine-context IDs. On the live card, both RT-specific buffers are
valid with or without the count override:
| Context buffer | Size | Alignment | RM status |
|---|---|---|---|
GRAPHICS_RTV |
532480 |
256 |
NV_OK |
GRAPHICS_RTV_CB_GLOBAL |
524288 |
256 |
NV_OK |
The TU102 KernelGraphics::bRtvCbSupported HAL property is also true in the
exact GSP image. The exact 610.43.03 source and disassembly match at the next
level: kgrctxMapGlobalCtxBuffers_IMPL at 0x52dc680 tests that property and
maps GR_GLOBALCTX_BUFFER_RTV_CB (ID 3) into the channel VAS. Its one-buffer
helper is now split and named kgrctxMapGlobalCtxBuffer_IMPL at 0x52dc22c.
The matching unmap helper at 0x52dd13c is named
kgrctxUnmapGlobalCtxBuffer_IMPL. Their real prototypes and useful local
names are applied in the IDB.
A zero-sized buffer, a false TU102 support property, or an omitted high-level RTV global-buffer map is therefore not the current failure. The exact FECS/GPCCS consumption and low-level RT-query state still need proof.
The exact 610.43.03 GSP image has four unpublished internal GR controls that were hidden inside bad IDA function boundaries:
| Command | Handler | Parameter size | Inferred operation |
|---|---|---|---|
0x20800a10 |
0x52332d4 |
0x28 |
Return GFXP control-buffer properties |
0x20800a11 |
0x523342c |
0x10 |
Map caller memory and build the GFXP layout |
0x20800a12 |
0x5233650 |
0x110 |
Enable selected GFXP layout entries |
0x20800a13 |
0x5233880 |
0x114 |
Disable selected GFXP layout entries |
TU102 HAL index 37 installs these Graphics methods:
| Virtual slot | TU102 target | Result |
|---|---|---|
Graphics+0xdb8 |
0x52928ac |
Returns size 1124, alignment 4096 |
Graphics+0xdc0 |
0x5280a0c |
Builds the CPU-visible GFXP control layout |
Graphics+0xdc8 |
0x52810c8 |
Updates its entry masks and active count |
Graphics+0xe88 |
0x5294afc |
Returns the RTV field offsets for local buffer ID 7 |
The +0xdc0 builder gets local context-buffer properties for IDs 3, 4, 5,
6, and 7. Its optional RTV section checks the real
KernelGraphics+0x14f bRtvCbSupported byte. That byte is true for TU102 for
every RM HAL index. None of the three TU102 methods contains a direct CMP,
board-ID, or SKU branch.
The control-block header inside the 1124-byte allocation is now typed in IDA
as NV_CTXSW_GFXP_POOL_CTRL_BLK_TU102_INFERRED. The header is 0x64 bytes.
Its first 0x58 bytes use NVIDIA’s published Pascal GFXP control fields.
TU102 adds three 32-bit fields:
| Offset | Field |
|---|---|
0x58 |
gfxpRtvSize |
0x5c |
globalRtvSize |
0x60 |
rtvOffset |
The new Graphics+0xe88 method accepts only local context-buffer ID 7 and
returns exactly these offsets. The layout builder writes all three fields and
has no CMP test.
The promoted-buffer IDs are now corrected. Public promote IDs 3..12 map to
internal IDs 0..9; therefore public ID 8 is internal ID 5, the real
GFXP_CTRL_BLK. Internal IDs 7 and 8 are the restricted and unrestricted
privilege access maps. The earlier working assumption that internal ID 8 was
the GFXP control block was wrong.
This also closes a false lead at 0x51ea548. Exact 610.43.03 source matching
identifies it as gpuConstructUserRegisterAccessMap_IMPL. The values at
pGpu+0x3fa8, +0x3fb0, and +0x3fb8 are the two register-access-map
pointers and their size, not RT/GFXP templates.
The recovered data path is:
CPU-RM -> internal cmd 0x20800a11 -> GSP handler 0x523342c
-> Graphics+0xdc0 -> write GFXP layout into caller memory
-> CPU-RM fills the control block
-> promote public ID 8 / internal ID 5
-> FECS consumes its offsets and sizes
A small read-only user-client probe is kept at
experiments/cmp50-rt-control-probe/rm_gfxp_static_probe.c. Command
0x20800a10 was run twice on the live 610.43.03 card, once while another GPU
client existed and once with zero VRAM use and zero GPU load. Both calls
returned ioctl success, RM status 0x56, and a zero output structure. The
idle rerun added no Xid and did not allocate GPU memory. This result cannot
separate a normal user-client access rejection from the handler’s own first
property-query failure, because both can report the same status. It does prove
that this unpublished internal control is not a useful direct user-space read
path; the stronger memory-mapping commands were not tried.
The IDB now has evidence-scoped names, prototypes, local names, and comments
for these handlers and methods. _inferred remains in every unpublished name.
This cluster gives no safe CMP bypass to patch: GFXP setup is present and the
TU102 layout path is selected. The next execution gate is more likely in the
CPU-RM data supplied to this path, FECS/GPCCS consumption, or a lower hardware
state transition.
The first FECS comparison also gives a strong negative result. The TU102 FECS
block at IMEM 0x0f56..0x14a3 reads control fields +0x58, +0x5c, and
+0x60. The same block and the same reads exist at the same addresses in the
non-RT TU116 FECS image. The ranges differ in only 18 bytes: nine lcall
targets use TU102 helper 0x72a3 versus TU116 helper 0x6ffb. No other
instruction in that range changes. This means the visible RTV/GFXP block is
common GR context setup, not a TU102-only RT-enable gate.
The full decompressed TU102, TU106, and TU116 netlists were split into all 40 declared regions and compared byte for byte. Most non-code regions are exactly the same on all three chips. Among the register-init payloads, the TU102 versus TU116 difference reduces to three register values:
| BAR0 register | TU102/TU106 | TU116 |
|---|---|---|
0x419bc8 |
0x00398a2c |
0x00398a42 |
0x419bf0 |
0x944077fe |
0x844077fe |
0x419e5c |
0x000058c1 |
0x00005cc1 |
The 0x419bf0 and 0x419e5c entries occur in both region ID 0x05 and the
indexed copy in region ID 0x0a; 0x419bc8 occurs in region ID 0x06.
Region ID 0x12 is one selector word: 0x18 for TU102/TU106 and 0x0c for
TU116. The live CMP 50HX reads exactly the TU102/TU106 values for all three
registers. Thus the card already runs the RT-chip netlist data; replacing or
patching the register-init tables is not a current unlock candidate.
The GPCCS comparison is also almost empty. TU102 and TU116 GPCCS IMEM have the
same 12584-byte size and differ in only three bytes: one compare immediate
at IMEM 0x2f8a (3 versus 1) and one 16-bit subtraction immediate at
0x2fc8 (0x1e00 versus 0x2180). TU102 and TU106 differ in only the latter
immediate. GPCCS DMEM differences are limited to the image header/version text.
There is no missing RT traversal implementation to copy from the RTX GPCCS
image into the CMP path.
A TU102/TU106 FECS path at IMEM 0x48e8..0x4c94 copies nine words from
0x584200..0x584240 to 0x481a00..0x481a40 when FECS DMEM flag 0x11f4 is
enabled. This first looked RT-specific because the direct code block is absent
from TU116. The full data comparison disproves that label: region ID 0x21,
which lists the 0x481aXX registers, is byte-identical on TU102, TU106, and
TU116. A read-only live snapshot also found all nine source and target words
zero. FECS method IDs 0x69 and 0x6a set and clear the 0x11f4 flag, but
their host meaning is not yet named. This path must stay a neutral GPC-state
lead, not an RT patch target.
Together these checks move the remaining gate below normal TU102 netlist selection and below the shared RTV/GFXP allocation path. The best remaining working hypothesis is fuse-derived hardware or protected early-GR state that changes real ray-instruction execution. That hypothesis is not yet proved and does not justify another live ray-query run by itself.
With the count set to 56, Vulkan accepts the device, ray-query extension and
features. BLAS, TLAS, and the compute ray-query pipeline all build. Corrected
tests with 64 rays and with one true invocation both end at vkQueueWaitIdle
with:
Xid 109, CTX SWITCH TIMEOUT, Info 0x4c010
An earlier apparent one-ray pass was invalid: integer dispatch division sent zero workgroups. The probe now uses ceiling division, pushes the exact ray count, and bounds each invocation. The corrected one-ray run fails in about six seconds.
The 2026-08-12 dispatch-isolation test narrows this further:
| Shader case | Important SPIR-V operation | Result |
|---|---|---|
| Control | no ray-query operation | one dispatch completed; no Xid |
| Initialize only | OpRayQueryInitializeKHR, then OpRayQueryGetRayTMinKHR; no OpRayQueryProceedKHR |
device lost and the same Xid 109 in about six seconds |
| Original query | initialize plus OpRayQueryProceedKHR |
same Xid 109 |
All cases use the same BLAS, TLAS, acceleration-structure descriptor, pipeline
layout, and graphics context. The control case proves that object setup and an
ordinary dispatch are safe. Traversal is not required to trigger the fault:
entering ray-query state at OpRayQueryInitializeKHR is enough. The generated
SPIR-V was checked with spirv-dis before execution. Sources and the repeatable
procedure are in experiments/cmp50-rt-dispatch-isolation.
Fresh NVIDIA disk-cache objects were decoded into their NVUC sections. Every
shader that contains OpRayQueryInitializeKHR, including the dead-result and
full-proceed variants, begins with the same four 128-bit SM75 instructions at
offsets 0x10..0x40:
0x10 d0730000 00000000 00000000 00ea0f00
0x20 d4790000 00020000 00000000 00e80f00
0x30 d3730000 00000000 00000000 00e20f00
0x40 d273ff00 00000300 ff040e00 00e20f00
Both the installed CUDA 12 nvdisasm and the official CUDA 13.3 build skip
these four words without naming them. The safe control shader has the same
RayQueryKHR SPIR-V capability and acceleration-structure descriptor, but it
does not contain this instruction group. Its normal second instruction starts
at 0x10. The full-proceed shader starts with the same four words and then
continues into its larger traversal sequence.
The closest proved machine boundary is therefore this initialize prologue, or the hardware state entered by it. We have not yet assigned exact instruction names. It is stronger evidence than the SPIR-V boundary alone, but it does not yet identify which of the four words is rejected.
tools/cmp50/rt_warp_esr_capture.py maps BAR0 read-only and samples the exact
per-SM registers read by the 610.43.03 GSP exception handler. The TU10x aperture
scan found 56 active SM register sets; the other candidate slots returned the
normal 0xbadfxxxx absent-unit value. This independently agrees with the
physical 56-SM topology.
One bounded initialize-only dispatch gave this order:
| Time from capture start | Unit or block | Change |
|---|---|---|
689.631 ms |
GPC2/TPC0/SM0 |
HWW_WARP_ESR=0x00000009 (INVALID_OPCODE), HWW_GLOBAL_ESR=0x00000004, report mask 0x00000000 |
7714.724 ms |
FECS | context-switch status changed to 0x00002001; signal/status changed to 0x00000140 |
| about six seconds after submit | RM | Xid 109, CTX SWITCH TIMEOUT, Info 0x4c010 |
The captured words at 0x728/0x72c were 0x00000104:0x07c12b72. They were
recorded as HWW_WARP_ESR_PC. That label is now disproved; see the next
section. This value must not be mapped to one of the four 128-bit words above.
0x728/0x72c is not a fault-latched PCThe earlier plan was to decode HWW_WARP_ESR_PC and map 0x00000104:0x07c12b72
onto one of the four undecoded SM75 words. Re-reading the saved capture
runs/20260812T1105Z-warp-esr/rt-warp-live.json shows the premise was wrong.
Four facts come from that file alone, with no new hardware run:
warp_esr moved on exactly one SM;warp_esr unchanged;pc=0x00000104:0x07c12b72 before and after esr=0x0->0x9;0x06c12b72, 0x07c12b72, 0x04c1eb72, 0x07c5eb76) and none is
16-byte aligned, which an SM75 instruction address must be.The faulting SM’s own history makes this plain. It changed value seven times before the fault and not at all during it:
420.084 ms 0x00000174:0x06c12b72 -> 0x00000174:0x07c5eb76
428.390 ms 0x00000174:0x07c5eb76 -> 0x00000174:0x06c12b72
513.926 ms 0x00000174:0x06c12b72 -> 0x00000174:0x07c5eb76
519.313 ms 0x00000174:0x07c5eb76 -> 0x00000174:0x06c12b72
546.523 ms 0x00000174:0x06c12b72 -> 0x00000174:0x04c1eb72
552.150 ms 0x00000174:0x04c1eb72 -> 0x00000174:0x06c12b72
676.490 ms 0x00000174:0x06c12b72 -> 0x00000104:0x07c12b72
689.631 ms esr 0x0 -> 0x9, pair unchanged
Four repeating values shared by 56 SMs is per-unit state with a few bit
fields, not a program counter. 0x728/0x72c are therefore unidentified SM
state. tools/cmp50/rt_warp_esr_capture.py now names them sm_state_728 and
sm_state_72c rather than warp_esr_pc_lo/_hi.
0x738/0x73cThe GSP exception handler does not form a PC from 0x728/0x72c at all. It
logs those words raw. It gets a separate 64-bit address from a dedicated helper
at 0x5276138, now named graphicsReadSmExceptionAddrPair_inferred in the
IDB, which reads TPC-relative 0x738 (lo) and 0x73c (hi) at smId * 0x80.
The caller combines them at 0x5288a9c as (hi << 32) + lo. That helper has
exactly one cross-reference: the SM warp-exception handler itself, so it exists
only for this path.
0x738/0x73c were never sampled in the 20260812T1105Z run. The sampler now
reads them as esr_addr_lo/esr_addr_hi.
0xdf5ea5bc10Two runs settled this, a safe one first.
runs/20260812T1230Z-control-esr-addr dispatched the control shader, which
completes and raises no Xid. Over 3083 samples on 56 SMs, 0x738/0x73c never
left zero, while 0x728/0x72c moved 494 and 56 times across every SM and
passed through the exact pair 0x07c12b72:0x00000104 that the earlier run had
recorded as the faulting PC. A value that occurs routinely in fault-free work
cannot be the address of a rejected instruction. An idle read just before it
returned 0x04c1eb72:0x00000174 identically on all 56 SMs, which no per-warp PC
could do, with 0x738/0x73c at zero.
runs/20260812T1240Z-esr-addr then repeated the initialize-only shader. At
1291.302 ms on GPC2/TPC0/SM0:
before after
warp_esr 0x00000000 0x00000009 INVALID_OPCODE
global_esr 0x00000000 0x00000004
esr_addr_hi 0x00000000 0x000000df
esr_addr_lo 0x00000000 0x5ea5bc10
sm_state_728 0x07c12b72 0x07c12b72 unchanged
sm_state_72c 0x00000104 0x00000104 unchanged
The latched address is (0xdf << 32) + 0x5ea5bc10 = 0xdf5ea5bc10. It is
16-byte aligned, as an SM75 instruction address must be. It was zero before the
fault, latched only at the fault, and returned to zero at GR reset at
5903.147 ms. The 0xbadf1201 values that appear on GPC4 at 5898.809 ms are
the absent-unit response as the GR unit collapsed, not additional faults.
So 0x738/0x73c is the fault-latched address pair, and this is the first
captured address of the rejected ray-query instruction. The run reproduced the
same Xid 109, CTX SWITCH TIMEOUT, Info 0x4c010, and needed a host reboot;
after the reboot the card returned to PASS_CMP50HX_FULL_SPEED,
PASS_CMP50HX_ALL_TARGETS_FULL_SPEED, CMP_SKU=1, and 3584/56/448.
0xdf5ea5bc10 cannot be converted to a shader offset from that run alone. The
shader’s base virtual address was not captured at the same time, and a later
allocation cannot be assumed to land at the same address.
The next run has to capture both together: the ioctl and shader-address trace,
which the harness already supports through nvidia_ioctl_trace.so, and this ESR
sampler. Then 0xdf5ea5bc10 - shader_base gives an offset that can be tested
against the four unknown 128-bit words at 0x10, 0x20, 0x30, and 0x40.
Only that comparison names the rejected instruction.
This is a correction of method, not of the failure itself. The SM still rejects
a ray-query instruction with INVALID_OPCODE; only the claim that we held its
address is withdrawn.
The same file also refines the documented order. FECS context-switch status recovered after the fault and only stalled seven seconds later:
687.096 ms ctxsw_status 0x00000002 -> 0x00020002
689.631 ms SM fault, warp_esr 0x0 -> 0x9
1188.015 ms ctxsw_status 0x00020002 -> 0x00000002 (back to normal)
7714.724 ms ctxsw_status 0x00000002 -> 0x00002001,
signal_status 0x00000180 -> 0x00000140 (stall)
The sequence ray instruction -> SM INVALID_OPCODE -> FECS stall -> Xid 109
still holds in time order, but FECS ran normally for about 6.5 seconds in
between. Any explanation that needs the SM fault to wedge FECS directly has to
account for that gap.
This proves the first fault is an SM instruction-decode rejection. FECS and
Xid 109 are later effects. Silencing the GSP exception handler, clearing the
Xid report, or changing only the report mask cannot give the rejected opcode
valid execution meaning. The useful patch must change the fuse-derived or
protected SM/GR state that controls opcode legality. Raw capture files are in
experiments/cmp50-rt-dispatch-isolation/runs/20260812T1105Z-warp-esr.
enableRayQueryColdStartWAR: tested and rejected for this prologueThe exact 610.43.03 libnvidia-glvkspirv.so has SHA-256
ebe38ab3d407ce6a71237006d6d2a9fc0420374700d0a0070a00ee828db5e899.
It contains a real boolean compiler option named
enableRayQueryColdStartWAR. Its options structure stores the byte at
+0x1c2; the stock constructor clears it, and two code-generation paths read
it.
A temporary user-space-only compiler copy was tested. A live uprobe during
pipeline creation showed that the first consumer ran once and the second did
not run. At the first consumer, r10=0, dl=0, cl=0, and r9=0; the zero
dl guard makes the stock code skip the workaround even when the option byte
is forced to one. Forcing the option and both option branches still produced a
byte-identical extracted compiler-text block. A stronger compile-only test
jumped directly to that consumer’s own workaround flag rewrite; the extracted
256-byte text section
still had SHA-256
fbcc1bcf52eb4ee03b7ddb44e4bfa98ae2a21677199fd33d18906f336d7e7c52,
identical to stock.
The helper in tools/cmp50/patch_glvkspirv_coldstart.c records the exact-build
patch and byte checks. It is an experiment, not an install patch. This result
rejects that compiler option as the producer of the four-word prologue for the
current shader. It does not rule out a different hardware cold-start fix.
One run of the ordinary forced-option copy accidentally omitted
--setup-only; it dispatched the known initialize shader and reproduced the
same Xid 109 and Info 0x4c010. The server was rebooted and returned healthy.
The hard-force compiler copy was never dispatched.
Do not repeat the initialize or traversal cases without a new patch or new
tracing; both are now known reset-required failures. The test GPU was rebooted
afterward. The persistent patch returned with P0, full issue rates, 3584
CUDA cores, 448 Tensor cores, and the requested reported count of 56 RT
cores.
The failure is after API exposure and object construction, at the first
ray-query state instruction. The SM first rejects a ray-query instruction as
INVALID_OPCODE; only later does FECS enter the SWITCH form of a GR
context-switch timeout and report 0x0004c010. The exact rejected word and the
state bit that controls its legality remain open. A fuse-derived opcode gate,
protected early-GR state, or missing RT power/context setup can still be the
cause, but the first observed fault is now exact. It is not a Tensor problem
and it is not explained by workload size.
The matching control and initialize-only runs were captured with envytools’
MMT support. tools/cmp50/vulkan_ray_query_bench.c --record-only first gave a
safe check: both cases can build and record the full command buffer without a
submit or an Xid. A later controlled pair submitted each case once; the RT run
reproduced Xid 109 and the machine was rebooted.
mmt_bin2dedma converted both complete traces to text. The local helper
tools/cmp50/mmt_extract_pushbuffer.py then rebuilt the final GPFIFO submit:
d8be8121f5ae35141dd7ce32e6aa8e99e495ae34ec32fdeed05a2c84489554e5;d8be8121f5ae35141dd7ce32e6aa8e99e495ae34ec32fdeed05a2c84489554e5.The final GPU method streams are byte-identical. The RT trace has 118 extra CPU writes before the last doorbell, but they update the user-mode queue and shader-address records; they are not extra GPU class methods. The only proved execution input that changes is the SM75 shader code containing the four unknown ray-query instructions.
This rules out a missing Vulkan-side RT-enable method in the final submit. It does not rule out state made earlier by GSP, FECS/GPCCS, or hardware from the RT fuse. That lower state is now the main target.
The normal host BAR0 write to 0x21168 was already known not to stick. A
second, smaller test used the working signed-Booter stockflow writer before
GSP-RM starts. It wrote 1 to 0x21168 after the protected reset window was
opened, without changing any RT shader or Falcon image.
Two added read-only log points gave the same result:
FULLSPEED_STOCK_RELOCK_WPR_DOWN_HANDOFF_PASS ... RTFUSE=00000000
CMP50_GSP_READY_V551: ... rtfuse=0x00000000
The first read is directly after the signed Booter returns; the second is after GSP is ready. Therefore the write never became visible. GSP did not clear a temporary one. No ray query was submitted with this candidate, no Xid was logged, and the known-good installed modules were restored from RAM.
The repeatable diagnostic patch and builder are in
experiments/cmp50-rt-fuse-booter. This closes the simplest direct-write
override. It does not prove that the fuse has no other override control or
that lower hardware never consumes it.
nvidia-debugdump was taken while the one-ray context was still timed out.
The raw archive is private test data and is not stored in this repository.
The schema-less protobuf register records decode as follows:
| Register | Failure value | Later RM snapshot | Known role |
|---|---|---|---|
0x409b00 |
0x80274824 |
0x802748e5 |
FECS current channel address |
0x409b04 |
0x00274824 |
0x802748e5 |
FECS next channel address |
0x409c00 |
0x00002001 |
0x00000002 |
FECS context-switch state/status, exact bits still open |
0x409400 |
0x00000140 |
0x00000180 |
FECS signal/status |
0x409c18 |
0x00000000 |
0x00000000 |
FECS interrupt status |
The dump also records 0x400704 = 0x80010510 and
0x400708 = 0x00419b48. Envytools’ TU102 register XML identifies these as
PGRAPH.TRAPPED_ADDR and PGRAPH.TRAPPED_DATA_LOW, not as one MMIO method
address plus another register. The trapped address decodes to method 0x510
on subchannel 1; 0x00419b48 is its data. NVIDIA’s Turing compute class names
method 0x510 NVC5C0_SET_FALCON04, while graphics class C597 places that
method at 0x2310. The trace creates both C597 and C5C0 objects, but the exact
subchannel binding has not yet been rebuilt, so the compute-class match is a
strong inference rather than final proof. In either case, 0x419b48 must not
be treated as the missing ray-only MMIO register.
tools/cmp50/protobuf_wire_dump.py makes the decode repeatable without a
protobuf schema:
python tools/cmp50/protobuf_wire_dump.py rm_00.pb \
--path 3.105.2 --registers
python tools/cmp50/protobuf_wire_dump.py error_data.pb \
--path 2.331.3 --registers
The current IDA database is the exact 610.43.03 TU10x GSP/RM RISC-V ELF,
SHA-256 c10c2866e360154e822087957bc4269168e44f8d45922110e67fd751355806f9.
It is already firmware analysis, but it is the high-level controller.
The Xid reporter had a bad IDA function boundary. It is now split, typed, and
named fifoReportCtxswTimeout at 0x511c63c. All four RV64 callers pass
pGpu, a FIFO object, a channel object, and a 32-bit info word. The function
uses the exact string at 0x40af410 and sends Xid 0x6d (109). Its parameters,
channelId, pChannelData, and errorData are named in the IDB.
The TU102-family timeout-status handler at 0x51677c0 is now named
fifoHandleCtxswTimeout_TU102_inferred, typed, and given evidence-based local
names in the IDB. It first reads a hardware timeout-info word from BAR0 at
0x51678cc. For the observed 0x0004c010, the owner field is zero, so the
handler marks it as a reportable channel timeout, extracts channel ID 0x10,
resolves the channel, and only then calls fifoReportCtxswTimeout at
0x5167b44. This is a post-fault reporting path. Removing that final call can
hide Xid 109, but it cannot clear the already timed-out FECS state or make the
shader finish. A useful patch must change the state entered before the timeout,
not only its report.
The upstream SM exception decoder at 0x5288724 is now named
graphicsHandleSmWarpException_inferred. Its RV64 call contract, object
pointers, GPC/TPC/SM arguments, and exception-register locals are named and
typed conservatively in the IDB. It reads TPC-relative 0x728, 0x72c,
0x730, 0x734, and 0x76c. Switch case 9 uses the exact text Illegal
Instruction Encoding; case 11 uses Illegal Instruction Parameter. The live
ray test reached case-9 state in hardware. This function decodes and handles an
error already raised by the SM; it is not the producer of the opcode-enable
state.
IDA had merged several real RV64 functions in the GPCCS ucode-loader region. The corrected boundaries are now:
| Start | End | IDB name | Evidence |
|---|---|---|---|
0x524a0b0 |
0x524a41c |
unchanged | Existing function ends before a fresh frame |
0x524a41c |
0x524aa60 |
gpccsLoadCtxswUcode_reg21xx_inferred |
Fresh frame; GPCCS HAL callback target |
0x524aa60 |
0x524b1ec |
gpccsLoadCtxswUcode_TU102_regF1xx_inferred |
Fresh frame; TU102-selected GPCCS callback |
0x524b1ec |
0x524b9c4 |
gpccsLoadCtxswUcode_reg51xx_inferred |
Fresh frame; GPCCS HAL callback target |
0x524b9c4 |
0x524bcb0 |
sub_524B9C4 |
Fresh frame only; owner and meaning remain open |
The constructor at 0x596e864 is now named and typed
__nvoc_objCreate_GPCCS(GPCCS **, Dynamic *, unsigned int). It allocates and
clears exactly 0x220 bytes. Its metadata points to a class definition with
size 0x220 and class ID 0x4781e8, which matches GPCCS in NVIDIA’s
generated 610.43 source. A conservative GPCCS structure is applied in IDA.
The proved fields are:
| Offset | Field |
|---|---|
0x58, 0x60, 0x68 |
NVOC base-object pointers |
0x70 |
post-load callback |
0x78 |
context-switch ucode loader callback |
0x1d0 |
IMEM image pointer |
0x1d8, 0x1dc |
IMEM aligned and raw byte sizes |
0x1e0 |
DMEM image pointer |
0x1e8, 0x1ec |
DMEM aligned and raw byte sizes |
0x1f4, 0x1f8 |
IMEM and DMEM word sums |
Unknown space remains explicit padding. The exact private NVIDIA method names are not guessed.
The inferred HAL binder at 0x5944de4 reads HalVarIdx from offset 0x10 of
its GpuHalspecOwner argument. TU102 has index 37: group 1 and low-bit index
0x524aa60, stored in GPCCS slot
+0x78 at 0x5945078. This is direct proof that TU102 uses the 0xf1xx
loader, not a nearby implementation.The TU102 loader reads the typed IMEM/DMEM pointers and sizes, honors
RMForceGrUcodeLoad, and writes these register windows:
| Use | Register |
|---|---|
| IMEM control / data / tag | 0xf180 / 0xf184 / 0xf188 |
| DMEM control / data | 0xf1c0 / 0xf1c4 |
The alternate loaders use the same data model with 0x21xx and 0x51xx
windows. Their exact chip owners are not yet proved. Comments at the binder,
constructor, loader starts, and write loops record this evidence in the IDB.
The exact producer of those GPCCS image fields is now mapped. IDA function
kgraphicsParseNetlistImage_inferred at 0x5258cf0 takes OBJGPU *,
KernelGraphics *, and a 32-bit load flag. It walks a pointer table at offset
0xc00 of the object held at pGpu+0x1ee8. NVIDIA’s generated object model and
the use of that table support GraphicsManager as the object’s likely type,
but the OBJGPU field name remains marked as inferred.
Each pointed-to netlist image has this exact layout:
| Offset | Type | Meaning |
|---|---|---|
0x00 |
NvU32 |
image version |
0x04 |
NvU32 |
region count |
0x08 |
array | 12-byte region descriptors |
Each descriptor is three NvU32 values: region ID, data size, and data offset.
The offset is relative to the start of the image. The parser first locates
regions 0x0f and 0x12; the first word of region 0x12 is the image’s
netlist number. The generic parser also has paths for these newer-layout
context-switch microcode regions:
| Region | Destination |
|---|---|
0x3d, 0x3e |
FECS images |
0x3f |
GPCCS DMEM pointer and sizes at +0x1e0/+0x1e8/+0x1ec |
0x40 |
GPCCS IMEM pointer and sizes at +0x1d0/+0x1d8/+0x1dc |
These four IDs are not present in the normal TU102 image found below. The active TU102 image uses the older 40-region layout. The table records real parser paths, but it must not be read as the region list of the TU102 image.
The selected number comes through the GPU HAL slot at +0x328. The common
implementation is named gpuGetNetlistNumber_inferred at 0x51f60e4; the
TU102 wrapper is gpuGetNetlistNumber_TU102_inferred at 0x5218b30. If the
common source returns zero, the TU102 wrapper reads BAR0 0x00100c7c. A set
bit in mask 0x00900000 selects fallback netlist 0x90; clear bits reach an
assertion path.
A new read-only live check found:
| Source | Value |
|---|---|
Host RM GPU-info index 0x12 (NETLIST_REV0) |
0 |
Host RM GPU-info index 0x13 (NETLIST_REV1) |
0 |
GSP-forwarded GPU-info index 0x12 |
0 |
GSP-forwarded GPU-info index 0x13 |
0 |
BAR0 0x00100c7c |
0x00010400 |
0x00100c7c & 0x00900000 |
0 |
The bindata table in this exact GSP ELF is now parsed directly. Its private
record type is 24 bytes: uncompressed size, compressed size, data pointer,
flags, and relative offset. All 495 nonzero records inflate correctly as raw
DEFLATE. Exactly six records have the GR netlist image header and region-table
shape consumed by kgraphicsParseNetlistImage_inferred:
| Chip path | Chip HAL index | Bindata entry / record VA | Size / compressed | Region 0x12 |
Decompressed SHA-256 |
|---|---|---|---|---|---|
| TU102 | 37 | 34 / 0x4bf4408 |
0x1ecc8 / 0xbcc2 |
0x18 |
598f8e529afdcf220d1b20df44fcf3a31047710e119d8c3fb370c28a82eb5ba7 |
| TU104 | 38 | 36 / 0x4bf4438 |
0x20398 / 0xc247 |
0x18 |
24c6b1c2c21af67603c5d953b7aaeb3882aa3ef4e547028b1d731c2100a36288 |
| TU106 | 39 | 32 / 0x4bf43d8 |
0x1ecc0 / 0xbcc3 |
0x18 |
b70c061f6755046dd63ba4dff11be15a779c2210fa33323aa63f72e582f74812 |
| TU116 | 40 | 33 / 0x4bf43f0 |
0x1ea18 / 0xbb0d |
0x0c |
726221cdc1a014e1489e919b4b3e2cda0da836e0e200e4bd168044c65521721d |
| TU117 | 41 | 35 / 0x4bf4420 |
0x1ea18 / 0xbb0e |
0x0c |
13d9bf8bf3c43071fed91f488243979bdfc6728737b3a2ee16822ff961e2376a |
| GA100 | 42 | 37 / 0x4bf4450 |
0x2ce00 / 0x1055e |
0x15 |
28774dc54e9800ef27ce2fdf85ebfc1f1721df82c57e86a45b3cc43605264349 |
The chip choice is also proved in code. Function
chipHalVarIdxFromArchImpl_TU10x_inferred at 0x58e6ab0 maps architecture
0x16 and implementations 2, 4, 6, 8, 7 to HAL indices 37..41. The parent
mapper gives GA100 index 42. The KernelGraphics HAL binder uses index 37 for
TU102 and installs only kgraphicsGetNetlistImage_TU102_inferred at
0x5b12834; the other five main image slots are zero-return stubs.
kgraphicsAcquireNetlistImages_inferred at 0x5231904 walks the six pointer
slots at inferred GraphicsManager+0xc00, calls the selected accessors, passes
each non-null storage record to the exact open-source function
bindataStorageAcquireData, and stores the inflated image pointer. This joins
the bindata records to the parser and proves that they are runtime inputs, not
false-positive data.
For the normal physical TU102 path, entry 34 is therefore the sole non-null
embedded candidate and its netlist number is 0x18. The only embedded
netlist numbers in this firmware are 0x18, 0x0c, and 0x15; there is no
embedded 0x90 image. This explains the failed force-0x90 test without
needing another GPU reset.
The two public revision values do not reveal the active netlist in the stock
host driver. Exact 610.43.03 source in
subdevice_ctrl_gpu_kernel.c::getGpuInfos hard-codes both indices to zero.
IDA shows that GSP’s physical handler is different: index 0x12 calls the
same GPU HAL method at +0x328, and index 0x13 calls its adjacent revision
method. The board-scoped diagnostic patch in
experiments/cmp50-netlist-forward forwards these two GET requests to GSP
instead of replacing their results.
The forwarding patch was built against the exact combined V551-stockflow and
RT-count source and loaded as a temporary module. Both physical GSP results
were still zero. The zero is therefore not only a host-side placeholder. This
does not mean that no netlist is loaded: the image parser can use region
0x0f to choose an image when the getter returns zero, then stores that
image’s region-0x12 value at KernelGraphics+0x1194.
No RMForceNetlistNumber setting was present in the normal live module
parameters or modprobe configuration. A temporary diagnostic load with
RMForceNetlistNumber=0x90 was tested after a PCI function reset. It did not
reach a usable GPU: GSP initialization timed out, the stockflow retry path then
reported Booter error 0x29, and RM ended with
RmInitAdapter failed (0x62:0xffff:2119). The test is rejected and must not be
repeated as an unlock path. Unloading the test module, applying PCI FLR, and
loading the normal installed module restored the card to P0 with 3584 CUDA,
448 Tensor, 56 reported RT cores, and the same on-disk module hash.
The normal TU102 image choice is no longer open: it is entry 34 with number
0x18. A forced 0x18 live load would ask for the same image and would add
little evidence while still requiring a module unload and GPU reset, so it
was not run.
This also removes the earlier CMP-specific-netlist idea as the main Xid 109 cause. The chip HAL key is TU102 architecture/implementation, not CMP SKU, and the same standard TU102 image is selected. FECS/GPCCS may still read RT fuse or context state while running, but there is no separate CMP netlist to replace.
The next main target is below image selection and below the now-closed GSP count path: RT-class state used when the first traversal method reaches GR, plus the exact FECS context field that fails to switch. The persistent host count patch should stay because it is already proved to open the user-mode API gate, but a second patch must fix real execution rather than change either the reported count or the netlist number.
The normal embedded TU102 netlist contains the exact production context-switch firmware used by this driver. The older-layout region mapping is proved by the GSP parser and the corrected TU102 loader:
| Region ID | Payload | Bytes | SHA-256 |
|---|---|---|---|
0x01 |
FECS IMEM | 29373 |
1be861e5ce2eb6349d5a24d91b4280735c09a217941485e9eace9d3e80572cea |
0x00 |
FECS DMEM | 5272 |
a8b25b50bc4bab115f0f17516d5a3331f1a48672db2b00a90af73af0255ec7cd |
0x03 |
GPCCS IMEM | 12584 |
f8a57f19da68a98131dcb736d66ceae7cd145e1de214427866b8036a7b713c4b |
0x02 |
GPCCS DMEM | 2896 |
11a54e23a2443b236e0bc38384792cd5a7ef2902c8c8bdfac33fce31cfc21dbf |
tools/cmp50/extract_gsp_netlist.py makes the extraction repeatable from the
exact GSP ELF and bindata record VA 0x4bf4408. The local IDA 9.4 install has
no Falcon processor, so both exact IMEM images were decoded with envydis as
Falcon generation fuc6. They decode from offset zero with no invalid
instruction marker:
| Exact 610.43.03 image | Decode lines |
|---|---|
fecs_imem_exact_610.43.03.bin |
9623 |
gpccs_imem_exact_610.43.03.bin |
4159 |
The public TU102 images remain useful comparison inputs, but they are different builds:
| Public reference image | Bytes | SHA-256 | Decode lines |
|---|---|---|---|
fecs_inst.bin |
29080 |
1f1b45a2ad4ebbf38a18dff10ef0ad3c32fa93184678c4f8741a2b64036997c1 |
9538 |
gpccs_inst.bin |
12717 |
484632eef86de685e3bd113c074aadbcd20328aedc46b896015c531f383c1624 |
4197 |
They are not safe patch inputs for 610.43.03 because their sizes and hashes do not match the production images above.
The exact 610.43.03 file ucodes_tu10x-610.43.03.bin is not that payload. It
is 12032 bytes, SHA-256
dcbdf512ab09b7f6d946e7f415bcfbc9137f5db1cf0c3c576f4b34b5275acb38, and
contains a 24-byte NVUCODES header with version 1, 496 entries, and
fingerprint 0x8d1990ab6b98ae2b. Every byte after that header is zero. It
therefore contains no FECS or GPCCS instruction image to patch.
The Rust faucon project was also built and tested. Its current main branch
explicitly supports only fuc5. It decodes the Turing GPCCS image through
offset 0x2b, then fails on the fuc6 bclr $flags, $p2 instruction at
offset 0x2e. It is a possible base for future fuc6 tooling, but it is not
an exact disassembler or emulator for this payload today.
All 40 regions were extracted for TU102, TU104, TU106, TU116, and TU117. Only
regions 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x0a, and 0x12
differ. Regions 0x05, 0x06, and 0x0a split exactly into one common value
set for RT-capable TU102/TU104/TU106 and another for TU116/TU117. The entire
register-table difference is three values:
| BAR0 register | TU102/TU104/TU106 | TU116/TU117 |
|---|---|---|
0x00419bc8 |
0x00398a2c |
0x00398a42 |
0x00419bf0 |
0x944077fe |
0x844077fe |
0x00419e5c |
0x000058c1 |
0x00005cc1 |
A read-only live BAR0 check on CMP 50HX returned all three RT-capable values exactly. The card therefore loads and retains RTX-style GR netlist state; a GTX-style netlist is not the missing execution gate.
The proved RT-count fuse shadow at BAR0 0x21168 remains zero. A tightly
scoped volatile host write probe requested bit 0 and read it back immediately.
The value stayed zero and the GPU remained healthy at P0. This proves that an
ordinary host BAR0 write cannot change the shadow after initialization. It
does not rule out a privileged early-Booter override or a different override
register.
After the timeout capture, the same test module was reloaded through FLR with no debug registry keys. The card returned to P0 at about 1905 MHz. The combined stockflow and RT-count module was then installed, added to initramfs, and verified after reboot. No real ray dispatch was repeated because it is a known reset-required failure until the context-switch path changes.
DP4A is present and gives correct results, but public tests show a much lower
issue rate than on RTX Turing. DP2A and separate multiply/add paths can be
faster. This proves an instruction-rate limit, not a missing DP4A unit.
NVIDIA driver 610.57.04 exposes these fuse results through
NV2080_CTRL_CMD_GR_GET_SM_ISSUE_RATE_MODIFIER:
IMLA0FMLA16DPFMLA32FFMAIMLA1 through IMLA4The same header defines full speed and reduced speeds down to 1/64. The GSP firmware reads these fuse values before the host driver reports them.
An early trace found an alternate implementation at 0x52abe9c. It passes
relative OBJFUSE offsets 0x381c and 0x3820, which the TU102 OBJFUSE
resolver would map to BAR0 0x2181c and 0x21820. That address translation is
valid for this function, but a full HAL trace now proves that this function is
not the final TU102 Graphics callback.
For TU102 chip HAL index 37, final Graphics+0xb08 is 0x528f508, named
graphicsWriteSmSpeedSelectOverridesViaRegMap_inferred in the IDB. It writes
register IDs 0x9664 and 0x966c through the register-map object at
Graphics+0x1200. These IDs match the protected FECS registers used by the
working stockflow path: BAR0 0x409664 and 0x40966c.
The old OBJFUSE pair remains useful only as evidence for the alternate implementation:
| Relative offset | TU102 BAR0 |
|---|---|
0x381c |
0x2181c |
0x3820 |
0x21820 |
The same resolver maps relative 0x1378 to published TU102
NV_FUSE_OPT_NVDEC_DISABLE at 0x21378, which is an independent check. The
address translation is proved, but it does not make 0x2181c/0x21820 the
TU102 SM-speed path. A direct write tool based on that old assumption is now
disabled.
The effective TU102 readback format is now separate, proved evidence. The
TU102 KernelGraphics HAL binder installs
kgraphicsGetSmIssueRateModifier_TU102_inferred at 0x528fcf0. It reads a
32-bit word from register offset 0x9668 and expands these fields:
| Field | Readback bits |
|---|---|
IMLA0 |
2:0 |
FMLA16 |
5:3 |
DP |
6 |
FMLA32 |
9:7 |
FFMA |
12:10 |
IMLA1 |
15:13 |
IMLA2 |
18:16 |
IMLA3 |
21:19 |
IMLA4 |
24:22 |
This proves the field order and the effective speed-select values reported by
RM. Together with the corrected TU102 +0xb08 callback, it also joins the GSP
registry override path to the same FECS register family used by stockflow.
Stockflow is still different because it opens the protected FECS PLM through
the signed Booter path before writing the values.
The adjacent FECS registers are now partly identified and are not an RT
shortcut. Envytools names 0x409658
NV_PGRAPH_PRI_FECS_FEATURE_OVERRIDE_ECC. NVIDIA’s published GV100 register
header names 0x409660 NV_PGRAPH_PRI_FECS_FEATURE_READOUT and defines bit
16 as the effective ECC-DRAM enable. The live CMP value 0x000000f3 has bit
16 clear. No GSP TU102 Graphics HAL function in the scoped 0x5280000 through
0x5291000 scan references 0x9654, 0x9658, 0x965c, or 0x9660; the
proved references in that family are the SM-speed override/readback registers
0x9664, 0x9668, and 0x966c. Registers 0x409654 and 0x40965c remain
unnamed, but their location alone is not evidence of an RT enable.
A later full Graphics-HAL scan found four TU102-selected readers of 0x409660
outside that first address range. They test bits 12, 13, 17, and the pair
18:19, with a software override byte for each result. This first looked like
a good RT lead because the binder mask 0xE0 selects chip indices 5, 6, and 7,
which are TU102, TU104, and TU106. The state-owner trace disproves that reading:
0x505f0a0 reads the exact registry keys RMEnableL1ECC, RMEnableSMECC,
RMEnableSHMECC, and RMNoECCFuseCheck, then manages the same feature-byte
cluster used by those readers. The mask groups Graphics ECC implementations;
it is not proof of an RT-capable-die gate.
The IDB now names that owner
graphicsApplyEccRegistryOverrides_inferred. The four TU102 readers at
0x52fd660, 0x52fd8dc, 0x52fd95c, and 0x52fd9dc have bit-specific ECC
names, typed (OBJGPU *, Graphics *) parameters, and comments that keep the
exact ECC unit names unresolved. No byte or live register was patched. This
closes 0x409660 as the next RT experiment while preserving it as useful ECC
documentation.
The fuse-space values at BAR0 0x21804 and 0x21814 are also not proved RT
feature controls on TU102. The exact GSP has one method that reads relative
fuse offset 0x3814, but the OBJFUSE HAL binder selects it only for a different
chip-family index. For TU102 (HalVarIdx 37, low index 5), the same table slot
is now named objfuseHalSlot168NoOp_TU102_inferred, a six-instruction no-op,
while the next slot is the constant-true
objfuseHalSlot170ReturnTrue_TU102_inferred. Both functions and the two binder
stores are named or commented in the IDB. The published
NV_FUSE_FEATURE_READOUT name found in
the 610.43.03 source belongs to GA100 at 0x823814; it does not name TU102
BAR0 0x21814. The read-only BAR0 tool therefore keeps both TU102 addresses
only as unknown observations and no longer labels either one as an RT feature
override.
The exact GPCCS image also contains I/O instructions that name address
0x21800, including a runtime branch at GPCCS IMEM 0x158c to a block at
0x1682 that writes 0x04000000. This is not RT-specific evidence. The
TU116 GPCCS image has the same block byte-for-byte; the complete TU102/TU116
GPCCS images differ at only three later bytes. On the live CMP 50HX, a
read-only BAR0 read of 0x21800 returns zero after GPCCS is running. The
instruction can be a chip/platform branch, a write-only or self-clearing
control, or a Falcon I/O mapping detail. It is therefore recorded in the
probe, but is not a safe RT-write candidate.
The local cmpunlocker-v0.1.28-linux-x64-50hx-stockflow package was compared
with the official project and its
v0.1.28 release:
f113dd5680f4c098e973d15eca59145cf2ace4b2358907b22c8f454039fc464a;LICENSE, NOTICE, and checksum file in
the local folder match the official archive;README.en.md is an extra file from the repository’s newer main
branch, not a file from that release archive;610.43.03 patch applies cleanly in a read-only dry run to NVIDIA’s
source tarball with the exact SHA-256 required by the build script.The package supports device 10de:1e09 only when the subsystem is either
10de:1554 or 1462:371f. The patch contains code for driver versions
580.173.02 and 610.43.03. The newer README also lists a 580.159.03 lane,
but no stockflow patch for that driver is present in this package.
The upstream report says 610.43.03 was tested on one card and on six
1462:371f cards, including repeated reboots. We have now also reproduced the
610.43.03 full-speed result locally, including a persistent install and clean
module reloads.
The patch does not flash the VBIOS. It enlarges the Booter signature allocation
to 0xfa00 bytes and places a crafted ROP payload in it. The payload reuses
code gadgets in NVIDIA’s signed SEC2 Booter. It does not load a new unsigned
Falcon program.
The key ordered stores made by the payload are:
| Step | BAR0 target | Value | Purpose in the patch |
|---|---|---|---|
| 1 | 0x00409650 |
0xffffffff |
Open FECS feature-override PLM |
| 2 | 0x00409664 |
0x88888888 |
Set SM speed override word 0 |
| 3 | 0x0040966c |
0x00000008 |
Set SM speed override word 1 |
| 4 | 0x00409650 |
0xffffff8f |
Put the FECS PLM back |
| 5 | 0x001fa828 |
0x00000000 |
Drop WPR2 high bound |
| 6 | 0x008403c4 |
0x000000ff |
Open the SEC2 reset window |
| 7 | 0x001fa824 |
0x1ffffe00 |
Set the WPR2 low sentinel |
| 8 | 0x008403c4 |
0x000000ff |
Keep the reset window for handoff |
The chain uses the Booter writer gadget at PC 0x10aa. It then releases the
ACR mutex owner token 0xc2 at secure-bus address 0x8e18, clears MAILBOX0,
and enters NVIDIA’s cleanup-and-halt path at PC 0x7c43.
The host patch checks exact readback states, rebuilds the stock signature,
clears SEC2 state, runs the normal FWSEC path, restores the stock WPR2 range,
and then starts normal GSP/RM. One narrow path changes an exact CMP-only GSP RPC
NV_ERR_TIMEOUT result to NV_OK; later health and full-speed gates still run.
This timeout mask is a real risk and must be watched in dmesg.
The package binary is a stripped, static root program. Static strings show module unload/load, process stop, persistent module install, rollback journals, and two undocumented autounlock commands. It has now been run on the test card and its persistent 610.43.03 path is working. Its claim that the card cannot be bricked should still be read only as “no SPI/VBIOS flash”: the ray-query test proved that a protected-state or driver failure can hang a GPU until reset.
The signed Booter image can be recovered without running the package or opening
a GPU device. tools/cmp50/extract_booter.py reads NVIDIA’s generated bindata C
file, checks its declared sizes, decodes raw DEFLATE, and refuses to overwrite
an existing output directory:
python3 tools/cmp50/extract_booter.py \
--source "$NVIDIA_SRC/src/nvidia/generated/g_bindata_kgspGetBinArchiveBooterLoadUcode_TU102.c" \
--output cmp50-booter-610.43.03
For the exact 610.43.03 source pinned by the package, the production image is
0xe700 bytes and has SHA-256
e0f0fc93da097b5b68e5af3adb58b2235ed1fad5fce0b23592d3895af44cb743.
The decoded nine-word header is:
| Field | Value |
|---|---|
| OS code offset / size | 0x0000 / 0x0100 |
| OS data offset / size | 0x8500 / 0x6200 |
| Application count | 1 |
| Application code offset / size | 0x0100 / 0x8400 |
| Application data offset / size | 0x0100 / 0 |
NVIDIA sets bBootFromHs = false for TU102. Its direct loader copies the first
code range to IMEM address 0, then copies the secure range from file offset
0x100 to IMEM address 0x100; both offsets are also used as Falcon tags.
Therefore, for this exact image, Booter PC x maps to uncompressed image and
booter-load-tu102-prod-imem-layout.bin offset x. It also confirms that the
production signature patch is at image offset 0x8700, or DMEM offset 0x200.
There is an important limit. The first 0x100 bytes are a clear non-secure
Falcon loader stub. envydis -m falcon -V fuc6 decodes that range cleanly,
including the initial stack setup, the 0x31 mailbox sentinel, the secure-code
transfer, and the call to PC 0x100. The application range from 0x100 is an
opaque secure payload in the archive. The same disassembler does not decode a
valid instruction stream at stockflow PC 0x10aa. Byte windows at the listed
PCs are therefore archive ciphertext, not runtime gadget bytes.
This independently proves the image identity and address layout, but not the
meaning of any stockflow gadget. NVIDIA also describes HS Falcon state as a
host-inaccessible black box.
A direct IDA import of the extracted production payload would only analyze
ciphertext. Independent gadget proof needs a decrypted runtime IMEM dump or
another matching cleartext image. Until then, PCs 0x10aa, 0x0d27, 0x1cdd,
and 0x7c43 remain stockflow-source claims backed by its reported hardware
result, not IDA-backed findings.
The question was whether the working
cmpunlocker-v0.1.28-linux-x64-50hx-stockflow path should have been used for
the Gen2 experiment. The answer is: it is the right path for the compute
limiter, but it cannot change the PCIe link capability.
The 610.43.03 patch changes the signed TU102 Booter flow. Its relevant
hardware state is:
0x00409650;0x00409664 and 0x0040966c;0x001fa824 and 0x001fa828;0x008403c4.The full-speed values are FECS[0]=0x88888888 and FECS[1]=0x00000008,
followed by the protected handoff and normal GSP/RM startup. This is why the
same card already reaches about 107.2 FP16 Tensor TFLOPS with 3584 CUDA
and 448 Tensor cores.
The patch was searched for PCIe capability/config-space identifiers, including
RMPcieLinkSpeed, NV_XVE, CAP_EXP, and PCIe link-speed/width writes. None
were found. The one 0x880 occurrence is a signed Booter signature-descriptor
offset, not PCIe BAR0 offset 0x88084/0x88088/0x880a8.
Therefore we did not take the wrong route. We tested two separate controls:
stockflow -> signed Booter/SEC2/FWSEC -> FECS/WPR2 -> compute speed
PCIe test -> RMPcieLinkSpeed/RmForceEnableGen2 -> endpoint capability/training
The old stockflow patch alone cannot produce Gen2. The working all-feature patch extends its signed-Booter transaction to open the separate TU102 XP3G policy page, then applies the recovered Turing PCIe controls and asks the upstream bridge to retrain. Compute and PCIe remain separate controls even though one end-user module now carries both.
This section records why registry values alone were insufficient. It is not the current result; the protected kernel path reached Gen2 x4 on 2026-08-13.
The isolated experiment in experiments/cmp50-pcie-gen2 was run on the remote
10de:1e09 CMP 50HX with subsystem 10de:1554 and the installed NVIDIA
610.43.03 open kernel module. The root baseline confirmed:
0000:00:01.1: current Gen1 x4, maximum Gen3 x8;RegistryDwords: empty.The temporary module load accepted exactly:
RMPcieLinkSpeed=0x1;RmForceEnableGen2=0x1
After the load, NVIDIA RM still reported Current 1. Both the GPU endpoint
and the upstream bridge remained at 2.5 GT/s PCIe, x4. nvidia-smi -L
remained healthy. No matching NVIDIA Xid, PCIe AER fault, bus drop, GSP
timeout, or Gen2 training failure was found. The normal module was restored;
the final RegistryDwords value was empty and the link returned to Gen1 x4.
A second root read-only capture checked the raw PCIe capability and BAR0
mirror. The endpoint returned CAP_EXP+0c.l=0x00453d01 (maximum Gen1 x16),
CAP_EXP+10.l=0x10410140 (current Gen1 x4), and
CAP_EXP+30.l=0x00000001 (target Gen1). BAR0 offsets 0x88084, 0x88088,
and 0x880a8 returned the same maximum, current, and target values. The
upstream bridge returned maximum Gen3 x8 and current Gen1 x4.
The exact GSP function init_pcie_registry_settings at 0x4CA3218 reads
RMPcieLinkSpeed and stores its raw DWORD at the PCIe state object offset
+0xA40; the checked disassembly has no PCIe Link Capabilities write. The
open driver source reads RmForceEnableGen2 only to set
PDB_PROP_CL_PCIE_FORCE_GEN2_ENABLE. This makes the result stronger than a
single failed training attempt: the endpoint advertises no Gen2 capability.
A current read-only capture on host 192.168.1.221 checked the same card
again. It adds the PCIe Supported Link Speeds Vector LinkCap2=0x0002
(2.5 GT/s only), the same value through PCI config and the NVIDIA BAR0 mirror,
and CYA_0 bit2 DIS_G2=0 (Gen2 is not disabled by that simple control). The
AMD bridge still reports Gen1-3 support. The 170HX OPT_GEN23/OPT_GEN3
registers are unmapped on TU102, and the XP3G override block is PLM-locked.
The capture is kept in
experiments/cmp50-pcie-gen2/runs/20260812T1317Z-170hx-regset/capture.txt.
One later signed-Booter candidate changed the first FECS PLM target to the
inferred XP3G PLM 0x8e1b0. It read 0xffffff8f before and after a successful
Booter run. A final payload audit shows that the second-exploit branch requested
only 0xffffffff at 0x8e1b0. Its later 0xffffff8f write targets FECS PLM
0x409650, not XP3G. The host read the candidate immediately after the Booter
call and before the stock signature rebuild. This result is write_ignored.
The probe only read LinkCap, LinkCap2, and LinkCtl2; it did not write them or
retrain the link.
A focused IDA pass over gsp_tu10x_610.43.03.elf then checked full constants,
RISC-V split addresses, data xrefs, function-pointer tables, and the relevant
disassembly. It found no direct stock-GSP reference to 0x8e1b0..0x8e1bc, so
the exact PLM name remains inferred. It did find the separate
RMPcieLinkSpeed hardware policy routine at 0x4cb25b8. That routine directly
changes Link Control 2 0x880a8, PRIV_MISC_1 0x8841c, LINK_CONFIG_0
0x8c040, PL_LINK_RATE 0x8c1c0, and CYA_0 0x8c2c0, but has no recovered
XP3G-page access. The full static record is
experiments/cmp50-privilege-map/evidence/20260812-xp3g-gsp-ida-map.md.
Therefore, this historical run proved only that the normal RM registry and
VBIOS-exposed path were insufficient. The later all-feature patch used the
exact TU102 protected path and an explicit upstream-bridge retrain; that path
is now live-proved at Gen2 x4. No lane-width override is part of the solution.
The raw report is kept in
experiments/cmp50-pcie-gen2/runs/20260812T061026Z, and the raw capability
proof is in experiments/cmp50-pcie-gen2/runs/20260812T061656Z; the current
host repeat is in experiments/cmp50-pcie-gen2/runs/20260812T1317Z-170hx-regset.
The corrected Booter evidence and the full target-specific access map are in
experiments/cmp50-privilege-map.
The 610.43.03 open kernel module copies Linux pci_dev->device into
nv->pci_info.device_id in nv_pci_probe() before calling
rm_is_supported_device() and rm_init_private_state(). A guarded,
off-by-default experiment replaced only that copied value with RTX 2080 Ti ID
1e07 on physical CMP 50HX 10de:1e09, subsystem 10de:1554 or
1462:371f. Linux PCI enumeration and configuration space were not changed.
The clean-boot test on subsystem 10de:1554 proved the exact handoff with:
NVRM: CMP50_PCI_ALIAS: physical=10de:1e09 rm=10de:1e07 subsystem=10de:1554
That field is not authoritative for the later product identity. nvidia-smi,
CUDA, lspci, and sysfs all still exposed 1e09 and NVIDIA CMP 50HX. RM
still returned CMP_SKU=1 and DISPLAY_ENABLED=0. Stock CUDA 12.0.146
nvprof still printed the CMP warning and recorded no activity, while stock
Nsight Compute 2022.4.1 still returned ERR_NVCMPGPU with exit 255. Thus the
private CMP classifier is rebuilt later from another RM source or immutable
board data; this early Linux PCI copy is not its controlling input.
The alias itself caused no regression: CUDA exited zero, all issue-rate values
remained full, RM reported 3584 CUDA, 448 Tensor, and 56 RT cores, and ReBAR
plus PCIe Gen2 x4 remained active with no Xid or PCIe AER error. The original
installed modules and alias-off boot configuration were restored afterward.
Because the alias changed no useful result, its code and module parameter were
removed from the all-feature package. The negative evidence remains in
experiments/cmp50-pci-id-alias so this path is not repeated.
CUDA 12.0.146 libcupti.so.12 creates a per-device state and asks the private
driver interface whether the device is CMP. On CMP 50HX it stores 1 at state
offset 0xA38; the same initialization function then returns
CUPTI_ERROR_CMP_DEVICE_NOT_SUPPORTED (42). The public
cuptiDeviceSupported() call still reports the device as supported, so
overriding that public function does not bypass the real gate.
For the Ubuntu library with SHA-256
d846f6110f1ac3a1f58ee531dadce415f6c001f7465183663a45569bb7013249, changing
only file offset 0x12b558 from 01 to 00 stops that flag from being set.
The patched private copy has SHA-256
b0688b349041405449b68b53e4bd5b7ec66988dc56fbadf124bb3564da8cac8f.
nvprof then records the test kernel and CUDA API calls with no CMP warning or
internal profiler error. The card remains healthy and the stock system library
is not modified.
The exact guarded patcher, private-library launcher, smoke test, IDA evidence,
and live result are in packages/cmp50-cupti-12.0.146 and
experiments/cmp50-cupti-unlock.
Nsight Compute 2022.4.1 has a separate copy of the same product policy. Its
libcuda-injection.so stores a private CMP query result at per-device state
offset 0xB2A. The public GPU-support check, profiler session start, and PC
sampling session start all use that shared byte. Replacing only the five-byte
query call at file offset 0x85eb24 with xor eax,eax and NOPs keeps the
normal state setup but stores zero. The guarded private copy changed from
SHA-256 0689cfdd20bf9cb1aabd225cdacdd61726d0368d0e8d89b9ae229d0f35222bc0
to e15f5740e4da6ba3792f38253e8824e207205d710c2bf9a10cdd38a7865a096c.
On the card, ncu then completed one pass and read
sm__cycles_elapsed.avg = 3,859.39 for the test kernel. Patching only later
support or session branches was rejected because it left the shared CMP state
set and made later code fail.
Nsight Systems 2022.4.2 carries its own CUDA 12 CUPTI library. Its matching
one-byte store immediate is at file offset 0x12b4b8. The guarded private
copy changed from SHA-256
0ae68983da177190a2b34c031d599e7a13b42682472644335b1a8de7d20ffb40
to 8a65881f0672e1406ab4cd4673720fb92329c3abec2fed6803bcb076f468ed6d.
The smoke test made a valid .nsys-rep and gpukernsum reported the
cmp50_nsys_probe(float *) kernel. The private tree also links the Ubuntu
host importer into the layout expected by nsys; no installed Nsight file is
changed. An A/B run in the same fixed layout, with only stock CUPTI restored,
made a report with no CUDA kernel data; the direct probe itself still exited
zero.
The version-bound Nsight packages are in
packages/cmp50-nsight-compute-2022.4.1 and
packages/cmp50-nsight-systems-2022.4.2. All three packages require CMP 50HX
PCI device 10de:1e09; their builders check the exact source hashes and their
wrappers check the patched hashes. System files stay unchanged.
| Area | Current view | Proof needed |
|---|---|---|
| CUDA scalar work | Local stockflow full-speed state is stable | Keep correctness and issue-rate regression tests |
| Tensor cores | Working: 448 cores and about 107.2 FP16 Tensor TFLOPS | LLM end-to-end tests |
| RT cores | Count 0 -> 56 exposes ray query; first ray opcode raises SM INVALID_OPCODE, then FECS stalls and Xid 109 follows |
Find the protected opcode-legality state or override |
| 3D APIs | Vulkan device and ray-query feature exposure work with the count patch | Non-RT Vulkan/OpenGL tests; RT waits for the GR fix |
| PCIe width | Working at x4; bridge maximum is x8; no width override is used | Board inspection and a separate width experiment for anything above x4 |
| PCIe speed | Working Gen2 after protected TU102 policy setup and bridge retrain; OpenCL transfer bandwidth doubled | Keep cold-boot, Xid/AER, and transfer-bandwidth regression checks |
| Display | Display fuse and missing board output path are separate | Fuse read, engine list, board check |
| NVENC/NVDEC | May be fused off | Fuse read, engine list, encode/decode test |
| CUPTI/Nsight | Working for nvprof 12.0.146, Nsight Compute 2022.4.1, and Nsight Systems 2022.4.2 with private, hash-gated copies; real API, kernel, and SM-counter records captured | Keep system files unchanged and add separate guarded patterns for newer builds |
| Secure firmware path | Stockflow is locally working; direct code replacement is still signed/blocked | Exact FECS/GPCCS image and context trace |
Exact RTX 2080 Ti equivalence is not possible by software alone: this card
reports 56 SMs / 3584 CUDA cores / 448 Tensor cores and has a 10 GB
memory layout, while the missing SMs and memory are physical configuration.
The useful candidates are therefore:
OpRayQueryInitializeKHR machine-code group raises SM warp error 9,
INVALID_OPCODE; FECS stalls later. The next work is to locate the
fuse-derived or protected SM/GR field that makes these Turing ray opcodes
legal. Patching the later exception or Xid reporter alone is not useful.CMP_SKU=1, but private, hash-gated copies now clear the
product-policy state in CUPTI 12.0.146, Nsight Compute 2022.4.1, and Nsight
Systems 2022.4.2. Live tests captured CUDA APIs, kernels, a real SM metric,
and a valid Nsight Systems report. No installed NVIDIA file is changed.
Other versions still need their own exact byte proof. This is a tool
compatibility unlock; it does not add SMs, RT execution, memory, or
bandwidth. See experiments/cmp50-cupti-unlock and the three
packages/cmp50-* profiler packages.0x9664/0x966c, and stockflow
writes the matching protected BAR0 registers. RM reports all nine fields as
raw: 0 (full), so this path needs no more change for Tensor/LLM work. The
earlier direct OBJFUSE helper is disabled because its TU102 premise was
disproved.The following are currently poor unlock targets: display (DISPLAY_ENABLED=0),
NVDEC (0x21378 & 7 = 7, no NVDEC classes), NVENC (no NVENC classes), extra
SMs, and extra memory. These have a hardware or fuse boundary. PCIe Gen2 is no
longer in this list; it is part of the supported all-feature package.
Assumption: the test card can run Linux. From the repository root:
sudo bash ./tools/cmp50/probe.sh
The script accepts only 10de:1e09. It reads sysfs, PCI configuration,
nvidia-smi, and a fixed set of BAR0 registers. It contains no PCI or BAR0
write operation. Use --skip-bar0 when root BAR access is not wanted.
The two key results are:
<report>/<BDF>/rm-issue-rate.json: the active issue-rate values returned by
NVIDIA RM. raw: 0 means full speed. Other values mean reduced speed; the
file also gives the named ratio where NVIDIA defines one. The same GET-only
probe now reports CMP/display flags, GPU/RT/Tensor core counts, 2D/3D/compute
capability bits, public netlist revision indices, and class lists for
graphics, NVDEC0, NVENC0, DPU, and all engines. It also reports size,
alignment, and RM status for all 26 public GR engine-context buffer IDs,
including the two RT buffers. A failed engine query is kept as an RM status,
not hidden.<report>/<BDF>/bar0.json: raw values for the published display and NVDEC
fuses, the secure-GSP-debug fuse, PCIe state, the separate GSP/OBJFUSE
candidates, and the FECS/WPR2/SEC2 registers used by stockflow. It also
decodes the effective FECS speed readout at 0x409668 and the TU102 netlist
fallback selector at 0x00100c7c.The RM probe must open NVIDIA device files read/write because this is required
by the ioctl interface. It sends only public GET controls. It does not contain
a SET control or a register-write path. A C compiler is needed to build this
small probe. Build errors are saved in rm-issue-rate-build.txt.
Build one test binary and keep it for both the before and after tests:
nvcc -O3 -std=c++14 -arch=sm_75 tools/cmp50/issue_bench.cu -o cmp50-issue-bench
./cmp50-issue-bench 0 | tee cmp50-issue-before.json
The first argument is the CUDA device index. The optional second argument is
the iteration count. The default is 131072. The test accepts only compute
capability 7.5. It gives the best of three runs for:
DP4A per loop;DP2A instructions per loop;FFMA per loop;FMUL and FADD instructions per loop.The float paths use inline PTX, so the compiler cannot turn separate multiply
and add instructions back into FFMA. A good first override must make DP4A
and FFMA faster while keeping correct sample values. Use the same clocks,
power limit, binary, and iteration count for the after test.
The first mutating test should be stockflow’s temporary stockflow-probe, not
a persistent install and not a raw BAR0 write. Run it only after these facts are
recorded and reviewed:
10de:1e09, and the subsystem is exactly
10de:1554 or 1462:371f.580.173.02 or 610.43.03, uses NVIDIA’s open
kernel modules, and has a matching kernel build tree.nvidia-smi
-q, lspci -Dvvnn, and current dmesg have been saved.stockflow-probe temporarily loads the candidate, checks health and full-speed
state, and is designed to restore the stock driver. That restore is not a power
loss guarantee. Keep the stock modules and a recovery path ready.
Do not use stockflow-install, install-autounlock, or any persistent command
for the first test. Static inspection found that install mode writes modules and
/etc/modprobe.d/cmp50hx-v534-no-kms.conf; autounlock also installs a systemd
service and copies the binary to /usr/local/sbin.
The first command, after the exact environment and candidate path are checked, is:
sudo "$BIN" compute50hx-v534 stockflow-probe \
--all-cmp50hx \
--stockflow-candidate "$ART" \
--acknowledge I-ACCEPT-50HX-V534-COMPUTE-UNLOCK
Success must include the package full-speed gate, a clean post-restore driver, the same correct benchmark outputs, and no new Xid or hard Booter/GSP error.
Display, video engines, and 3D class support remain separate experiments. PCIe Gen2 is now live-proved in the all-feature package.
GSP 610.43.03 reads the exact registry keys RMOverrideSmSpeedSelect and
RMOverrideSmSpeedSelect1, stores both 32-bit values, and sends them through
the TU102 Graphics callback that writes FECS register IDs 0x9664 and
0x966c. This IDA-backed path avoids a host-side raw BAR0 write. It is not the
authenticated Booter path used by stockflow and has no isolated hardware result
yet. Its ability to pass the protected FECS write controls remains open.
Keep this as a later, isolated fine-control experiment after a stockflow
baseline is reproducible. Run it only from a headless or remote shell after the
normal NVIDIA modules have been unloaded. Make sure no other modprobe file
supplies NVreg_RegistryDwords; in particular, do not mix PCIe options into
this run.
sudo modprobe nvidia \
NVreg_RegistryDwords='RMOverrideSmSpeedSelect=0x88888888;RMOverrideSmSpeedSelect1=0x00000008'
grep '^RegistryDwords:' /proc/driver/nvidia/params
sudo modprobe nvidia_uvm
The /proc line must contain exactly the two intended keys before any
benchmark is run. Then repeat the RM report and the same instruction binary:
sudo bash ./tools/cmp50/probe.sh \
--skip-bar0 --output cmp50-rm-override
./cmp50-issue-bench 0 | tee cmp50-issue-rm-override.json
sudo dmesg | grep -Ei 'NVRM|nvidia|Xid|GSP' \
| tee cmp50-rm-override-dmesg.txt
Do not change clocks, power, PCIe settings, or the binary between the before
and after runs. A useful result needs lower RM values plus faster DP4A and
FFMA, with the same correct output values. A module parameter shown in
/proc is only proof that RM received text, not proof of a register change.
If module load fails, RM values do not change, or the GPU reports an error,
unload the modules and cold-power-cycle the card. Remove the temporary module
option before the next normal boot. Do not move to raw BAR0 only because the
performance result is negative; first record the RM report, dmesg, and
benchmark output.
experiments/cmp50-uefi-throttle now contains a standalone x64 EFI
application for the proven FECS SM-speed state. This is a first-stage direct
pre-boot test, not yet a port of the full signed SEC2 Booter flow.
The probe build contains no active write routine. It accepts only CMP 50HX PCI
ID 10de:1e09, subsystem 10de:1554 or 1462:371f, PCI display base class
0x03, and successful BAR0 reads. It reports FECS PLM 0x409650, speed words
0x409664 and 0x40966c, and effective readout 0x409668.
The separate apply build also needs --apply. It writes the PLM-open value
first and checks exact readback. If 0xffffffff does not stick, it stops before
either speed word. If it does stick, it sets 0x88888888 and 0x00000008,
restores the original PLM, and checks final state. It has one rollback attempt
for a partial write. It never touches SPI, VBIOS, PCIe capability, UEFI
variables, or Windows files.
Both images build without imports as relocatable PE32+ EFI applications. ABI offset checks cover the small UEFI and PCI I/O interface used by the source. Native mocked-transaction tests cover a blocked PLM, successful apply, partial-write rollback, an already-full state, and an unexpected PLM. The direct hardware test is not run yet. A negative PLM-open result means the next UEFI stage must reproduce the signed Booter setup, WPR2 repair, FWSEC handoff, and stock-signature cleanup; adding only more host BAR0 writes would not help.
tools/cmp50/sm_override.py is kept only to record the old experiment and now
refuses every invocation. The first analysis assumed that alternate function
0x52abe9c was TU102’s final SM-speed callback. Exact HAL execution disproved
that assumption: TU102 selects 0x528f508, which uses the protected FECS path.
The following old gates and confirmation text are retained in source for audit
history, but no direct 0x2181c/0x21820 write is allowed.
10de:1e09;D0;bar0.json belongs to the same BDF and its current register values have not
changed;0xffffffff.Running the tool now fails closed with the corrected reason:
sudo python3 tools/cmp50/sm_override.py \
--bdf 0000:01:00.0 \
--baseline cmp50-baseline/0000_01_00_0
The old direct mode required both --write and this exact text:
CMP50HX-1E09-DIRECT-BAR0-VOLATILE-SM-OVERRIDE-2181C-21820-COLD-RESET
The text no longer enables a write. Use the proved stockflow mechanism for SM speed. Do not revive the direct OBJFUSE experiment unless a later chip-specific HAL trace proves that this exact board selects the alternate implementation.
The known stockflow payload sets every speed field to full. It does not yet give us a safe live tuning interface. The local full-speed result is now stable, so a later R&D step can test one field at a time through the same protected SEC2/FECS path:
dmesg.1/2, 1/4, 1/8, 1/16, 1/32,
and 1/64. The one-bit DP field can only show full or 1/2.This will turn the current all-or-nothing unlock into measured fine control. It should be added only after the exact card baseline and first stockflow probe are available; otherwise we cannot tell a field effect from a boot-path error.
The next RT study will compare this CMP 50HX with a real RTX 2080 Ti. Both use TU102, so this is the best available reference for separating product fuses from common TU102 firmware and driver behavior.
0x21168, display fuse 0x21C04, NVDEC
disable 0x21378, secure-debug state, FECS state, and the three RT netlist
values at 0x419BC8, 0x419BF0, and 0x419E5C.GRAPHICS_RTV and
GRAPHICS_RTV_CB_GLOBAL context buffers, GFXP/RTV offsets, and Vulkan RT
extensions. Keep the CMP count-override result as a separate control.The first comparison is read-only. No BAR0 write, PCIe retrain, VBIOS change, or ray workload on the CMP is part of this plan.
ReBarUEFI/Cmp50Pkg is a separate implementation of the pre-OS part of the
610.43.03 CMP patches. It does not modify generic ReBarDxe and does not use the
old direct OBJFUSE experiment.
The DXE driver matches only 10de:1e09 with subsystem 10de:1554 or
1462:371f. Its default mode is read-only Probe. An armed boot can:
0x88724, 0x88bbc,
0x88dcc) before PCI resource collection;0xffffff8f.The signed TU102 production Booter was checked directly from NVIDIA’s MIT
610.43.03 bindata. Its image, header, signature, patch offset, and metadata are
reproducible with Cmp50Pkg/Tools/generate_booter_asset.py. It cannot safely be
started as a stand-alone UEFI unlock: the stock path enters after FWSEC and
requires a complete live GspFwWprMeta object and its GSP sysmem buffers. DXE
does not own those inputs. Guessing them or starting a second partial GSP can
damage WPR state or hang the card. A locked FECS therefore reports
booter-precondition-missing and performs no FECS write.
This is the key feasibility boundary. ReBAR and Gen2 are feasible in pure DXE. The full compute result is conditional on FECS still being unlocked, and Windows may reset the state after ExitBootServices. If Windows 610.88 restores FECS PLM or link policy, a pure UEFI driver cannot keep the Linux result; that case needs a Windows kernel-side change or a fully proved firmware handoff, not more blind UEFI register writes.
Build, one-shot test, recovery, and Shell command instructions are in
ReBarUEFI/Cmp50Pkg/README.md. RT core count 56 is intentionally outside this
package. The first read-only F54 image and its extraction proof are in
artifacts/cmp50-uefi-f54-20260819/.
The full 610.43.03 IDA and live-host record is in
experiments/cmp50-eglcore-capability-map. It records the exact target
hash, RM control structures, all recovered class-mask table rows, the
RT-count data flow, Vulkan extension and feature predicates, IDB changes, the
2026-08-30 read-only live result, and a cross-check against all earlier RT
experiments.
The extension-record layout was then rechecked from the record base rather
than from the callback-pointer slot. Each record is 0x120 bytes, its string
starts at +4, and its callback pointer is at +0x108. This corrects the
earlier callback labels: every exact consumer in this group belongs to ray
tracing, acceleration structures, or a direct dependency. The full table now
covers KHR acceleration structure, ray query, ray-tracing pipeline,
maintenance and position fetch; EXT opacity micromap, pipeline-library group
handles and invocation reorder; and the related NV extensions.
The key correction is that device-state +0x158 is a graphics-class
capability mask, not framebuffer data. CMP 50HX is selected as generic
C597 TURING_A with TU102 implementation and receives mask
0x00200000; no direct PCI ID 0x1e09 comparison was found in
this path. RT count comes from RM GR-info index 0x22 and is combined
with profile key 0x0e3595 into the proved userspace gate at
+0x269f0.
The same gate selects RT compiler modes, is copied into physical-device shader
state, and enables one compiler-input byte in both the NVVM/ucode and SPIR-V
pipeline paths. The compiler option bit defaults to on (3 & 2), and all live
CMP50 conditions are true, so this is not another closed gate. A separate
off-by-default field at +0x25430 controls only
VK_NV_ray_tracing_validation; NV_ALLOW_RAYTRACING_VALIDATION=1 and profile
key 0xfa85c8 feed that field. Eglcore also has RT task-priority thresholds:
sync 4096 and async 8192. Interface query selector 6 supplies the compared
task value. Defaults select recursive/synchronous work below 4096, an eligible
background-worker band at 4096..8191, and direct interface submit at 8192 or
higher. This is CPU task scheduling, not an RT feature or instruction gate.
Only its compile-time performance effect remains open; it is not patched.
On the current live card, RM reports 56 RT cores and Vulkan reports
accelerationStructure, rayQuery, and
rayTracingPipeline as true. This closes the userspace exposure gate
for the present setup. It does not change the lower execution failure: the
first special ray-query SM instruction is still known to raise
INVALID_OPCODE, stall FECS, and end in Xid 109. A broad patch of
+0x158 is therefore unsafe, and an added eglcore RT-count patch is
redundant on the current live setup.
The expanded read-only run at
experiments/cmp50-eglcore-capability-map/runs/20260830T101217Z-live-read-only
also confirms exposure of opacity micromap, both NV/EXT invocation-reorder
extensions, cluster and partitioned acceleration structures, and their KHR
dependencies. Linear swept spheres, motion blur, and RT validation remain
unexposed for their separate class/state conditions. The probe submitted no
GPU work.
A new live experiment at
experiments/cmp50-rt-unlock-candidate-audit joins the GSP fuse map, the
ten-phase register timeline, and a no-submit Vulkan compiler probe.
The CMP device exposes VK_KHR_pipeline_executable_properties. The probe
created control, initialize-only, and full ray-query compute pipelines with
capture flags, but did not create or submit a command buffer. All three runs
returned zero, and each before/after kernel-log delta was empty. The driver
returned executable statistics but zero internal representations:
| Input | Registers | Reported binary size |
|---|---|---|
| control | 16 | 256 |
| initialize-only | 16 | 256 |
| full ray query | 33 | 6144 |
The core pipeline cache is an NVIDIA CPKV container. Its Zstandard frame at
+0x64 decompresses to NVDANVVMNVuc. The exact NVuc header stores shader
code size at +0x4c and offset at +0x50. The extracted initialize-only and
full ray-query blocks match through byte 0x8f and first differ at 0x90.
CUDA 13.3 nvdisasm -b SM75 rejects these blocks, so they are NVIDIA compiler
ucode, not final SASS. They must not be mapped to the live SM fault address.
This closes direct SASS export through pipeline-executable internal
representations, but opens a safer static path: trace the NVuc boundary from
the exact libnvidia-glvkspirv.so.610.43.03 into eglcore and the final
compiler stack. The library was
copied to decompil, its SHA-256 is
ebe38ab3d407ce6a71237006d6d2a9fc0420374700d0a0070a00ee828db5e899, and its
IDA database is now open. The full candidate matrix, exact code hashes, failed
decode evidence, safety rules, and artifact paths are in the experiment
README.
glvkspirv NVVM-to-NVuc IDA mapThe first static compiler cluster is now mapped and saved in
decompil/libnvidia-glvkspirv.so.610.43.03.i64. The exact function at
0xa1800 owns the NVVMIRToUCode, GLV Convert NVVM IR To Ucode, and
Container Serialization phases. Its only code caller is exported wrapper
_nv002nvvm at 0xa2800. Its supported IDB name and System V contract are:
compileNvvmIrToNvucContainer_inferred(
NvvmCompileContext *pContext,
NvvmCompileState *pState,
NvvmCompileOutput *pOutput) -> unsigned status 0 or 2
The following host-side serialization class was also recovered and typed:
| Address | IDB name | Role |
|---|---|---|
0x6a1730 |
createNvucContainerWriter_inferred |
allocates the 0xa0-byte writer and a 0x68-byte auxiliary index |
0x6a10b0 |
NvucContainerWriter_ctor_inferred |
initializes the typed writer layout and vtable |
0x6a4b10 |
NvucContainerWriter_serialize_inferred |
vtable +0x28; returns serialized pointer and byte size |
0x6a0b20 |
NvucContainerWriter_dtor_inferred |
vtable +0x30; releases owned state |
0x6a0cb0 |
NvucContainerWriter_deletingDtor_inferred |
vtable +0x38; destroys and frees the writer |
0x9588c0 |
g_NvucContainerWriterVtable_inferred |
typed vtable |
This proves the host NVVM-to-ucode and NVuc container-writing path. It does not by itself prove where final SM75 SASS is emitted, where a ray instruction may be rejected, or which hardware state enables RT execution. Full addresses, object offsets, string xrefs, artifact hashes, and negative results are in the experiment README.
The saved IDA pass also checked the internal guards at 0xa1817
(pContext +0x200 == 3) and 0xa1872 (pState +0x115). They are mode/state
and output-fill checks, not proved GPU-target or RT-opcode legality branches.
eglcore, gpucomp, and rtcore post-NVuc mapThe next static pass copied the exact live
libnvidia-gpucomp.so.610.43.03 to decompil. Its size is 110902328 bytes
and SHA-256 is
4c16539192951d9b37c7db7e276560ad4f0a26d9d6c62e2004f0f563a418a8ec.
The remote and local values matched. IDA analysis completed, the supported
changes were read back, and
decompil/libnvidia-gpucomp.so.610.43.03.i64 was saved at 898683228 bytes
after the state-4 packaging helpers were named and typed in IDA.
The newly proved owner of the post-NVuc table is
libnvidia-rtcore.so.610.43.03. The exact live file was also copied to
decompil; it is 44877472 bytes with SHA-256
df846603db891087ff12ae412a1698fdd16094683bf93ec4a139f8d087e82b7d.
The remote and local values match. Its saved, annotated IDA database is
decompil/libnvidia-rtcore.so.610.43.03.i64; it is 284192113 bytes after
writeback. Auto-analysis completed before changes, and affected functions were
force-recompiled and read back before the database was saved.
The exact dispatch record at 0x24f9a00 names CreateInstance and points,
through thunk 0xbd0910, to createVulkanInstance_inferred at 0xca92a0.
Therefore the typed exact 0xb58-byte object is the internal Vulkan instance,
not a separate compiler context. Its VulkanInstance_inferred compiler tail
is:
| Offset | Meaning |
|---|---|
0xab8 |
NVVMCOMP interface returned by gpucomp |
0xac0 |
GLSPIRVC interface returned by gpucomp |
0xae0 |
pRtcoreExportTable, returned by rtcGetExportTable |
0xae8 |
RTCORE setup capacity value; exact object role still open |
0xaf0 |
bRtcoreExportTableReady |
0xaf8 |
GLVLowerToNVVMIR |
0xb00 |
GLVCompileNVVMIRToUCode |
0xb08 |
GLVCompileNVVMBitcodeToUCode |
0xb10 |
GLVSerializeModule |
0xb18..0xb40 |
remaining named GLV serialization and lifetime exports |
0xb50 |
callback from create-info record id 47, version 1 |
compileNvvmIrAndInvokeBackend_inferred at 0xd1f240 is the first proved
post-NVuc boundary. It calls Vulkan instance +0xb00, serializes through
+0xb10, then passes the serialized pointer and size to RTCORE table slot
+0x60. Its caller later invokes the same table at +0x1c0 on the returned
opaque handle before packaging the program.
PipelineCompileContextBase_ctor_inferred at 0xd1df10 propagates the same
internal Vulkan instance pointer through pipeline-context field +0x58.
The public pipeline thunk at 0xbd5310 subtracts 0x50, matching the public
instance handle returned as allocation +0x50. This proves the object identity
used at the post-NVuc call.
The RTCORE table at context +0xae0 is not the NVVMCOMP pointer at
+0xab8, not its vtable at gpucomp 0x6611438, and not the OCGASMCL
table at 0x683aae0. The last table has only twelve proved pointers through
+0x58; +0x60 is null. Therefore gpucomp function 0x254b510, although
it is its own NVVMCOMP slot +0x60, must not be patched as if it were the
live EGL call.
The final address-taking scan resolved the missing writer.
loadRtcoreExportTable_inferred at 0xb82340 opens exact
libnvidia-rtcore.so.610.43.03, requires rtcGetVersion result 0x5c, and
resolves rtcGetExportTable. At 0xb8250e it passes the address of instance
field +0xae0 and UUID bytes
2a b7 62 cf 27 30 51 1e 66 15 c8 3a 4f ba 0f 52. Success writes the RTCORE
export table, uses setup slots +0x08 and +0x2a8, and marks instance byte
+0xaf0 ready. Another proved consumer at 0xbc8471 uses table slot +0x18.
The constructor clear at 0xca91d6 and this nonzero writer complete the
static source chain for +0xae0.
The RTCORE database closes the two live slot gaps. rtcGetVersion at
0x38ef30 returns ABI version 92. rtcGetExportTable at 0x38ee30 accepts
only UUID 2ab762cf2730511e6615c83a4fba0f52; null input returns 1, UUID
mismatch returns 3, and a match returns status 0 with table 0x29c29a0.
The table begins with byte size 0x2d0, so the exact layout is one size field
and 89 function pointers. The next bytes at +0x2d0 begin a zero area. IDA
type RtcoreExportTableV92_inferred matches the proved 720-byte size and
90 members.
Live slot +0x60 is 0x3da750, a thunk to
rtcoreCompileNvucProgramImpl_inferred at 0x3d8d60. The EGL caller at
0xd1f341 proves ten System V arguments: device, backend descriptor,
serialized NVuc pointer and size, target array and count, compile options, two
flags, and the program output pointer. The built-in compiler route is:
0x3d8d60 rtcoreCompileNvucProgramImpl_inferred
-> 0x3ce650 compileRtcoreNvucBackend_inferred
-> 0x1745f0 runInternalRtcoreCompiler_inferred
-> 0x174570 runRtcoreCompilerStateMachine_inferred
-> 0x173f10 advanceRtcoreCompilerState_inferred
-> 0x164620 emitRtcoreFinalCubin_inferred
-> 0x12d7e0 buildRtcoreFinalCubinContainer_inferred
The state-4 function contains exact beforePIC.ll, afterPIC.ll,
afterPICCleanup.ll, rtx, and final.cubin uses. The final container-builder
call is at 0x16648f. Raw call and data flow now classify state 4 and
0x12d7e0 as IR cleanup plus final cubin serialization, relocation, and
container packaging. The strings prove the stage role; they do not prove a
CMP block or the first machine-instruction choice.
The deeper state-4 readback narrows this result. The emitter calls the container
builder with generated buffers, relocation/context data, and callbacks. Its
0x165334 edge reaches 0x226440, now named
rewriteNvConstantSectionWithNumericSuffix_inferred: it finds .nv.constant,
parses a decimal suffix, updates section metadata, and formats the suffix back.
The nearby 0x165359 edge is another generic section helper, and 0x1666a8
is output/package handling after the final.cubin label. These are concrete
ELF/Mercury metadata and packaging operations, not a fixed RT instruction
template or a proved SM75 encoder. The exact RTCORE DSO also has no match for
the four generated SM75 ray-query words or their recorded 64-byte sequence.
The important RT-specific path is earlier, during compiler preparation:
0x1745f0 runInternalRtcoreCompiler_inferred
-> 0x160cc0 compiler-state preparation
-> 0x15ea60 linkRtRuntimeAndPrepareCompilerModule_inferred
-> 0x3c2860 processRtIntrinsicModules_inferred
-> 0x3c23d0 normalizeAndTagRtIntrinsicNames_inferred
-> 0x2029d0 buildAndTagRtIntrinsicRegistry_inferred
-> 0x201fa0 initRtIntrinsicRegistry_inferred
-> 0x1f3410 buildRtIntrinsicDescriptorTable_inferred
-> 0x2028b0 tagRtIntrinsicFunctionsWithTypeSet_inferred
The first link function owns exact phase label afterLinkRuntime.ll. The table
builder creates exact 0xe8-byte RtIntrinsicDescriptor_inferred records.
Supported fields are 32-byte nameStorage at +0x00, pTypeBitSet at
+0x20, and prefix-match byte bPrefixMatch at +0x64; all other bytes stay
explicit unknown storage. The size is exact from both the append stride and
the tag-loop stride at 0x202971.
The table contains exact trace, intersection, terminate, ignore, TTU,
ray-query-derived hit-object, and reorder names. A raw audit of the complete
0xe4a9-byte builder found only 19 conditional jumps. Twelve guard temporary
allocation cleanup and seven control final vector move, sort, and destruction.
None reads PCI id, product, SM target, fuse, or compiler-policy state. The RT
descriptors are registered unconditionally on this path.
nv.rt.ttu.trifetch at 0x1fbb19 carries type codes 0x25 (37) and 0x1e
(30) and is appended at 0x1fbd67 without a product branch. The
with.flags form at 0x1fbd74 and
nv.rt.hitobject.make.from.rayquery. at 0x1fcdd9 use the same positive
construction path. At 0x20295b, the tag pass copies descriptor
pTypeBitSet to matched IR function +0x70.
The later classifier at 0x1a6660, now named
isNvRtIntrinsicNodeWithTypeBits36Or37_inferred, decodes exact prefix
nv.rt. from source 0x29dde20 into runtime storage 0x29dde27. It requires
the function +0x70 set to contain bit 36 or 37. Raw code has two
identical bit-36 calls at 0x1a6701 and 0x1a6712, followed by the bit-37
call at 0x1a6730. TTU type code 37 therefore passes this positive check.
This closes the registry as a hidden CMP deny path. A structured immediate
scan of isTypeBitSet_inferred at 0x70e8e0 found exactly 19 functions that
query type bit 36 and/or 37. A reverse caller search to depth 10 reached
the known RTCORE compiler roots for only these four groups:
0x141ea0 -> 0x146e60 -> 0x16ca50
0x1a6660 -> 0x1a6750 -> 0x2f1390 -> 0x2f22d0
-> 0x30bf60 -> 0x35d7a0 -> 0x16f8f0
0x4cd090 -> 0x164620 state 4
0xa78580 -> 0xa81370 -> 0xa86470 -> 0x4b5de0
-> 0x13b7b0 -> 0x16ca50
The other 15 direct consumers and every recovered bit-call address are in
rtcore-static-map.json. No caller path to the known roots was found within
the bound; unresolved indirect dispatch is still possible.
isIrNodeEligibleForRtcorePass_inferred at 0x141ea0 decodes exact
rtcore.read.const into 0x29da692 and uses it in a generic IR allowlist.
collectRtcoreFunctionReferences_inferred at 0x4cd090 decodes exact prefix
rtcore. into 0x2a66cb8, tests bits 36/37, and collects matching
call-like IR references. cloneIrCallNodeWhenNeeded_inferred at 0xa78580
returns the original node or allocates a 64-byte IR replacement tagged with
value 261. These are positive IR handling paths, not machine selectors.
The state-3 RT classifier use is now exact:
0x172bd0 runRtcoreCompilerState3_inferred
-> 0x16f8f0 buildRtcoreStubsAndRunSplitAtCallSite_inferred
-> 0x35d7a0 runSplitAtCallSitePass_inferred
-> 0x30bf60 analyzeSplitAtCallSiteCandidates_inferred
-> 0x2f22d0 estimateIrNodeCostForSplitWrapper_inferred
-> 0x2f1390 estimateIrNodeCost_inferred
-> 0x1a6750
-> 0x1a6660 positive nv.rt/type-bit classifier
Exact strings SFT-Initial.ll, SFT-AfterSplitAtCallSite.ll,
SFT-AfterDebugPlaceholders.ll, Call Sites, restores, and allocas
support the split-pass role. The large cost function returns finite double
costs or positive infinity for generic IR nodes. The RT match receives a
finite split cost; it is not rejected by card identity.
The split pass also has a corrected System V ABI. RDI is a hidden result
pointer for exact 24-byte SplitAtCallSiteResult_inferred; the function
writes three qwords at +0, +8, and +0x10 and returns RDI in RAX.
RSI is the compiler context and DL is a mode byte. The only caller supplies
a trailing RCX value, but the callee overwrites it before any read, so its
meaning remains open and the IDB calls it unusedTrailingArg.
None of the four connected type-bit groups reads PCI ID, product kind, SM
target, fuse, or device-policy state. A new structural pass then moved the
boundary into state-3 RT intrinsic lowering. It did not use IDA text search.
From root 0x16ca50, direct code refs, direct callee data refs, and the proved
XOR stream (0xa7, then subtract 0x3f per byte) found 150 direct callees.
58 of them directly reference cipher data that decodes with prefix nv.rt.,
rtcore., or nv_rtcore_. The export records 169 references: 28 have a
separately proved decoder length and 141 are bounded printable-prefix
candidates whose last byte or bytes may be outside the real string.
Four RT lowerers are now typed, named, commented, recompiled, read back, and saved in the RTCORE IDB:
0x16ce5d -> 0x1ba620 lowerIntersectionReportIgnoreTerminateIntrinsics_inferred
0x1bbb3a -> 0x1b6820 lowerReportIntersectionIntrinsics_inferred
0x16d51e -> 0x1d48d0 lowerTriangleVertexDataIntrinsics_inferred
0x16d5ef -> 0x1cad30 lowerRayStateReadIntrinsics_inferred
Exact loop bounds prove triangle inputs and helper names
nv.rt.read.triangle.vertex.data, rtcore.read.triangle.vertex.data, and
nv_rtcore_ttu_triangle_vertex_data. The ray-state pass covers 16 unique
world/object origin and direction components plus ray.tmin, ray.tmax,
ray.flags, and ray.mask. The intersection group proves
nv.rt.ignore.intersection, nv.rt.terminate.ray, prefix
nv.rt.report.intersection., and helper nv_rtcore_dispatch_shading_AH.
The report pass builds named IR values for interval checks, committed-hit
state, ignore/terminate state, any-hit addresses/state IDs, and
ray.terminated.
The wider 58-function set also covers payloads, exceptions, launch values, SBT data, instance/primitive/geometry data, transforms, traversables, triangle/sphere/LSS vertex data, trace and continuation calls, hit objects, reorder work, and RT stacks. This corrects the earlier narrow description of state 3: it owns both stub/split work and a large RT IR-lowering family. None of the four fully checked lowerers directly calls a device, PCI, fuse, or driver query. That is a bounded result; a compiler option can still be filled from device policy before the call.
A second structural scan now covers the whole RTCORE .data segment instead
of only direct state-3 callees. It constructs the encoded byte patterns for
nv.rt., rtcore., and nv_rtcore_, scans raw bytes, decodes bounded
printable candidates, and records IDA xrefs. It found 447 data hits, 9
without an xref, and 174 referenced functions. 58 are the known direct
state-3 functions; 116 are outside that set, and 78 of those contain a
bounded ray/TTU/trace/dispatch/stack/hit/call/reorder/traversal term. All
global-map suffixes are marked non-exact until a decoder loop proves their
length.
The first outside cluster is now proved. Exact loop bounds recover both names and underscore aliases:
nv.rt.ttu.trifetch
nv_rt_ttu_trifetch
nv.rt.ttu.trifetch.with.flags
nv_rt_ttu_trifetch_with_flags
advanceRtcoreCompilerState_inferred calls
scanTtuTriFetchUsers_inferred, then lowerTtuTriFetchIntrinsics_inferred.
The latter reaches rewriteCollectedTtuTriFetchCalls_inferred and
lowerOneTtuTriFetchCall_inferred. The plain form supplies one operand; the
with-flags form supplies two more. The lowerer builds internal IR operations
with IDs 0x1499, 0x149b, and 0x149c. These are not proved final SM75 SASS
words. No function in this cluster directly queries target profile, CMP, PCI,
fuse, driver, or RT capability, so this is positive TTU lowering, not a deny
branch.
The next outside cluster is also positive RT work. Exact decoder bounds prove
nv_rtcore_dispatch_shading_CH, nv_rtcore_dispatch_shading_AH, and prefix
nv_rtcore_dispatch_shading_. At 0x3c2e20, stage values 2 and 3 choose
CH or AH and feed a found module into the normal RT intrinsic module path.
buildInternalRtcoreDispatchModules_inferred at 0x3c4120 builds internal
raygen and dispatch modules. 0x3d05c0 selects this builder or the external
GPUCOMP builder. No checked direct callee reads CMP identity, PCI ID, or fuse
state.
The pass also corrected the shared exact-name ABI. findNameHashIndex_inferred
at 0x87fcb0 takes table, data pointer, and length; uses a 33 * hash + byte
open-addressed lookup; and returns an index or 0xffffffff. Wrappers at
0x7d86e0 and 0x7d8870 return payload +0x08, with the latter also requiring
payload byte +0x10 to be clear. This is an IDB contract correction, not an RT
gate.
The nearest compiler gap is now after these proved RT lowerers: the first
function that turns an rtcore.* or nv_rtcore_* result into a
target-specific RT machine operation, and then the first SM75 selector or
encoder. State 4 still collects RT references and packages the final cubin.
The four unknown SM75 words from the separate live shader-cache fault were
searched as four exact 16-byte patterns and as one 64-byte sequence in the
RTCORE DSO. All five searches returned zero. The two partial byte hits were
false: 0x7071b8 is an x86 call displacement and 0x27f3890 is an unrelated
data table. This supports runtime generation, not a fixed embedded SASS
template. It still does not identify the rejected word because the confirmed
ESR address 0xdf5ea5bc10 and shader base were not captured in the same run.
compileRtcoreNvucBackend_inferred has two compiler routes. The process-wide
selector at 0x2ad9fd4 is the current_value +0x54 field of a 0x60-byte
integer-option descriptor at 0x2ad9f80. ELF .bss is zero-filled before
constructors, but initializeRtcoreCompilerBackendOptionDescriptor_inferred
at 0xc0e80 passes -1 to
constructAndRegisterIntOptionDescriptor_inferred at 0x1242a0, which writes
the value indirectly to default_value +0x50 and current_value +0x54.
A safe exact-library dlopen process confirmed runtime value 0xffffffff
after constructors. Eleven direct sites read it; there is no direct address
write because the proved initializer writes through the descriptor pointer.
Mode 0 always selects the built-in compiler. Mode 1 always selects the
external interface. Mode -1 and every other value select it only when the
per-config field is 1. The same mode family controls compile, serialization,
dispatch module building, and helper objects, so changing only one branch
would be wrong.
The recovered 0x28-byte registry stores NVVMCOMP at +0x08 and RTCORECP at
+0x10. loadGpucompCompilerInterfaces_inferred at 0x38f2b0 opens exact
libnvidia-gpucomp.so.610.43.03, resolves nvGetCompilerInterface, and asks
for exact RTCORECP version {3,0}. External compile uses slot +0x18 and
cleanup +0x48; external text-IR work uses +0x20 and cleanup +0x28. No
checked selector, loader, or registry path reads a card, PCI ID, product name,
or RT fuse.
Live slot +0x1c0 is rtcoreSerializeProgram_inferred at 0x40f7c0. The EGL
caller at 0xd237eb proves five System V arguments, although the callee uses
only the program and output pointers. It returns 1 for a null program or
when the exact uint32_t field at program +0xd80 equals 5; otherwise it
calls serializeRtcoreProgramState_inferred at 0x40f510 and returns 0.
The meaning of state 5 remains open and was kept neutral in the IDB.
The exact sm_75, compute_75, -D__CUDA_ARCH__=750, and Turing uses are
in registerNvptxTargetVariants_inferred at 0xcc17e0. That same function
registers newer targets, so this is generic target setup, not a proved CMP50
allow or deny path.
The target record is no longer opaque. createNvptxTargetProfile_inferred at
0xcc1350 allocates the exact 136-byte
NvptxTargetProfile_inferred. It contains kind/LTO/a/f flags, five name or
class pointers, three compatibility sets, a linked compute profile, twelve
still-neutral 32-bit fields at +0x50..+0x7c, and a neutral byte at +0x80.
The related parsed-name object is the exact 12-byte
NvptxTargetSpec_inferred.
SM75 and SM80 have the same kind, LTO, and a/f flags. They also have the
same values at +0x50, +0x54, +0x58, +0x5c, +0x60, +0x64, +0x70,
+0x74, +0x78, and +0x7c. Only two neutral fields differ: SM75 stores
16/32 at +0x68/+0x6c, while SM80 stores 32/64. Their meaning is not
proved. The explicit target-variant flags are separate bytes at +0x04 and
+0x05, and no direct RT consumer of the two differing values was found.
Therefore they must not be patched merely because they differ.
lookupNvptxTargetProfileByName_inferred at 0xcc4020 is the direct profile
lookup. Its direct callers use the result for exact-name validation,
canonicalization, suffix checks, conversion to the compute profile, and
compatibility-set tests. The constructor, registration routine, lookup, and
these direct consumers do not query PCI ID, product type, fuse state, driver
state, or RT capability. This closes the generic target-profile record as a
proved CMP50 RT-disable branch. It does not close a later instruction selector
behind an unresolved indirect compiler edge.
A bounded immediate, byte-pattern, and structured-string
pass found no CMP50 id 0x1e09, CMP90 id 0x220d, NVIDIA vendor/device tuple,
CMP product name, mining-card discriminator, or direct CMP branch in the
mapped compile and serialization paths. This does not rule out opaque config,
serialized input, the optional compiler interface, another export slot,
kernel/GSP policy, or fuse behavior.
The separate NVVMCOMP path is still mapped. It validates the serialized
container, accepts the proved magics de c0 17 0b and 42 43 c0 de, parses
NVuc, and creates an opaque GPU-program object through:
0x254b510 nvvmCompileNvucDefaultMode_inferred
-> 0x254b420 nvvmCompileNvucWithMode_inferred
-> 0x254aef0 validateNvucCompileArgs_inferred
-> 0x254ac60 compileNvucContainerToProgram_inferred
-> 0x254c4d0 createGpuProgramFromNvuc_inferred
No CMP PCI id, TU102 product-name test, or RT-disable branch was proved in that bounded chain.
gpucomp also exposes interface id RTCORECP with version {3,0}. Its object
is at 0x69c8d28 and points to table 0x65ee360. The old 20-slot type was
short: 23 non-null pointers occupy +0x00..+0xb0, for a proved 0xb8-byte
span. +0xb8 and +0xc0 are zero; +0xc8 is unrelated data. RTCORE proves
+0x18 is external compile, +0x20 is text-IR parse/serialization, +0x28
cleans that result, and +0x48 cleans the compile result.
Slot +0x18, now named rtcoreCompileWithInterfaceV3_inferred at 0x61d870,
reaches a real second compiler state machine:
0x61d870 rtcoreCompileWithInterfaceV3_inferred
-> 0x6cc050 runGpucompRtcoreCompilePipeline_inferred
-> 0x6b7c80 initializeGpucompRtcoreCompileState_inferred
-> 0x6cbfe0 runGpucompRtcoreCompileStateMachine_inferred
-> 0x6cba10 runGpucompRtcoreCompileStateStep_inferred
The typed common state header is 0x20 bytes: two neutral pointer fields,
compiler-context pointer +0x10, state +0x18, and status +0x1c. Its loop
ends at state 5 or non-zero status; cancellation is status 13. State 2
walks entry functions, and runGpucompRtcoreCompileState4_inferred at
0x6bcba0 owns exact strings beforePIC.ll, afterPIC.ll,
afterPICCleanup.ll, final.cubin, and rtx.
The GPUCOMP IDB now also names the state destructor, state 1 and 3 handlers, text-IR result cleanup, compile-result cleanup, and helper-object cleanup. The 13 changed functions have checked prototypes and comments; five useful locals and the cancellation callback type were read back. This proves that the external route is not a stub. The later live test proved that forcing it gives the same saved cache and metadata as automatic mode for the tested shader.
The process-local test needs no LD_LIBRARY_PATH. The strict build- and
hash-locked LD_PRELOAD interposer passes mock checks for no opt-in, exact
-1 -> 1, old-value refusal, wrong inode, wrong GNU build ID, and shell
syntax. Its exact RTCORE load-only run changed the constructor-set value
4294967295 (-1) to 1, read it back, returned 0, and added no kernel-log
line. It called no NVIDIA export and created no Vulkan or GPU object.
The same ray-query pipeline was then compiled twice in automatic mode and
twice with forced external mode. All four runs returned 0, created no command
buffer, and submitted no GPU work. Every 10,099-byte pipeline-cache.bin was
byte-identical with SHA-256
45a6fcc782126503e040925451b6bcdce71a7f44189e3aa3a67b63e2e62dcfb7.
Every pipeline-info.json was also byte-identical with SHA-256
6ba6371070fa765c540270520824dcee1c89d978d0ff7bf70e342a91a4e0111c.
Register count stayed 33 and binary size stayed 6,144 bytes. All stderr and
kernel-delta files were empty.
This closes the cache-based RTCORE backend comparison and gives no new unlock
evidence. It does not prove final SM75 SASS is equal because the cache contains
NVuc, not raw SASS. A direct trace of GPUCOMP 0x61d870 can settle which route
automatic mode uses; an exact final-SASS capture is needed to compare the later
code boundary. The full record and stop rules are in
RESEARCH_LOG.md and
backend-selector-preload/BACKEND_COMPARISON_RESULT.md under the experiment.
The complete five-library record and present negative RT conclusion are in
CMP50HX-SHARED-OBJECT-AUDIT.md.
The external state machine reaches the ELF/Mercury finalizer core at 0x2563fd0
(runGpucompElfFinalizerCore_inferred) through wrappers 0x2555e60 and
0x2556320. Its architecture helper at 0x2558350 checks generic source and
target capability bits. The visible off-target fastpath at 0x2564bf2 copies
the input ELF and changes only its machine field. It is a retag/copy path, not
an instruction translator.
The finalizer can report generic unsupported instruction (14), SASS
generation failed (16), and unsupported SM version (22) through
0x479d520. The .nv.merc* parser at 0x479d850 only builds ELF section,
symbol, and relocation metadata. The CAN_FINALIZE_DEBUG helper is called
with its return value discarded in this build. No bounded finalizer path reads
PCI ID, product name, mining state, RT fuse, or CMP identity. Therefore this
new compiler lead strengthens the negative conclusion: the DSO has a real
generic finalizer and error surface, but no proved CMP50HX switch and no
proved path that emits legal SM75 RT code from an off-target ELF.
The deeper finalizer constructor at 0x255ed80 is now named
initGpucompFinalizerHashMaps_inferred. It builds two generic four-byte-key
FNV-1a hash maps with 284 and 295 entries (579 total); helpers at
0x255ead0 and 0x255ed50 only insert entries and return value slots. The
constructor has no CMP, PCI, product, mining, fuse, target-SM, or RT-legality
branch, and the table has not been proved to contain SASS opcodes. It remains
a registry/codec candidate for future consumer tracing, not a patch target.
The software RT-unlock investigation is closed on 2026-08-30. No further
.so patching or RT dispatch testing is justified without new external
evidence (final-SASS comparison, another card, or direct fuse/decoder data).
Most important, the compiler use of exact annotation nvvm.rtcore.entry is a
positive route. At 0x266c2c4 it is tested, and a match reaches 0x266d708,
which allocates an RT-specific object, installs table 0x6613f10, writes tag
0x6101, and appends it to the compiler work vector. It is not a reject path.
The string Target Ray Tracing features is compiler option metadata only.
This pass found no supported gpucomp or mapped RTCORE branch that disables RT
for CMP50HX. The mapped RTCORE registry is a positive route and must not be
patched out. Forcing the alternate compiler route also changed no saved byte.
The live INVALID_OPCODE can still come from a later final machine-code choice,
upload or relocation, hardware decode, or fuse state. This is a priority order,
not proof of one cause. The detailed evidence, all 89 RTCORE
function-pointer addresses, UUIDs, ABIs, IDB changes, bounded negative results,
open indirect edges, and next trace steps are in
experiments/cmp50-rt-unlock-candidate-audit/README.md,
experiments/cmp50-rt-unlock-candidate-audit/RESEARCH_LOG.md,
gpucomp-static-map.json, rtcore-static-map.json, and
state3-rt-prefixed-datarefs.json. The whole-.data discovery map is
global-rt-prefixed-datarefs.json. Its exporters are
tools/export_rtcore_state3_map_ida.py and
tools/export_rtcore_global_rt_map_ida.py; both use structural xrefs and byte
patterns only.
The completed eglcore and glcore IDA passes are now separated from the RT
decode theory.
In exact libnvidia-eglcore.so.610.43.03, only 0xad5f6a and 0xc6500e
emit NVC597_CALL_MME_MACRO(52) (0x20010e68) with argument 0xf0.
0xc6500e, reached from update_pipeline_state_and_emit_mme at 0xc67040,
is the proved live Vulkan bind throttle. 0xad5f6a is capability-gated and
was not reached by that probe. No second MME slot, WAIT_FOR_IDLE,
PIPE_NOP, or supported hidden-throttle candidate was found. The other
inspected 0x2001xxxx writes are ordinary push-buffer state setup.
In exact libnvidia-glcore.so.610.43.03, the same pair appears only at
instruction starts 0xb2085b and 0xdcbb6e. Patching both arguments in
earlier live A/B runs did not change TU102 bind behavior. The active graphics
path also builds typed 24-byte command descriptors and queues their method
words dynamically, so the two literal pairs do not describe the whole path.
Most important, process_bvh_command_stream at 0xe8b640 is a separate
function-table path with a structural bvhdump-*.bin xref. It queues dynamic
descriptors and resets/initializes pipeline method state. This is a real BVH
lead, but no current evidence ties it to the known MME pair, the live CMP bind
path, or a PCI-ID check. The cluster uses feature/class masks and state bytes;
no direct 1e09 or RTX 1f0b compare was proved.
Both IDBs contain the supported names, parameters, comments, and recovered descriptor type; changed functions were force-recompiled, read back, and saved. Exact addresses, rejected method writes, hashes, function names, and open gaps are recorded in the candidate-audit README.