Summary
Two VitisAI sessions in one process: F (front half of a split model,
1 input -> 3 outputs) and G (back half, 3 inputs -> 1 output).
Sequence F1 ok -> F2 ok (G idle) -> G1 ok -> F3 all-NaN on the main output
tensor -> G2 ok. Every later run of the same F session stays NaN; deleting G
does not heal F; recreating F (while G is gone) recovers. F alone (4 runs),
G alone (4 runs), and F/G in separate processes are all clean.
The failing layer (EP / VAIP / XRT / driver) is NOT identified.
Environment
| Item |
Value |
| NPU |
AMD Ryzen AI 7 PRO 350 w/ Radeon 860M (Krackan, 8 columns, BDF 00c4:00:01.1) |
| OS |
Windows 11 10.0.26200 build 26200 |
| SDK |
RyzenAI-SW 1.8.0 (onnxruntime-providers-ryzenai==1.8.0, ryzenai-dynamic-dispatch==1.8.0) |
| ORT |
1.27.0 (onnxruntime-vitisai==1.27.0, onnx==1.22.0, python 3.12.11) |
| EP DLL |
onnxruntime_vitisai_ep.dll 1.8.0.0, providers DLLs 1.27.0.0 |
| XRT / driver / FW |
2.19.0 / 32.0.203.329 / 1.0.21.43 |
| Provider options |
cache_dir=vendor/amd-npu-1.8, cache_key=modelcachekey_r5_front / modelcachekey_r5_back, enable_cache_file_io_in_mem=0 |
Models
F (front_bf16cast.onnx, 1776295828 B, 5799 nodes): input input
[1,3,128,128] -> 3 boundary tensors: main [1,256,64,64], mean [1,3,1,1],
std [1,3,1,1] (all FLOAT). G (back_bf16cast.onnx, 49871980 B, 1802 nodes):
the 3 tensors -> output [1,3,512,512]. Both bf16-cast, each a single
VAIML subgraph (F 4786/4797 ops 99.771%, G 1520/1520 100%, GOPs 100%).
Lineage: AdcSR net_params_200 -> export script -> N5 rewrite -> quark
bf16cast -> onnx.utils.extract_model cut (cut plane A). One-time VAIML
compile ~93 min (F) + ~30 min (G); all runs below are cache hits
(F build ~7-8 s, G build ~0.6-0.9 s). Full hashes in manifest.json.
Minimal reproduction
python repro.py --case same_process (fresh process; fixed .npy inputs;
raw pre-nan_to_num outputs saved; xrt snapshots per stage):
| step |
result |
| F1 |
finite (main err vs CPU fp32: maxabs 8.086e+00, rmse 8.7e-02, cos 0.9983; mean/std err ~1e-3) |
| F2 (G resident, idle) |
finite |
| G1 (saved CPU mids) |
finite, PSNR 51.65 dB vs fp32 reference |
| F3 (after G ran) |
main all-NaN (nan_frac=1.0000, no inf); mean/std still finite, errors unchanged |
| G2 (saved CPU mids) |
finite, PSNR 51.65 dB |
Reproduced in a 2nd fresh process with identical pattern. G's own runs never
degrade (G2 finite after F went NaN), so contamination is one-way G->F.
"Permanent" is limited to: all later runs of the same F session stay NaN.
What we ruled out
- F alone x4 and G alone x4: all finite (PSNR 51.65 dB for G).
- Separate processes (F worker + G worker, overlapping lifetimes, alternating
runs): 3/3 F finite, 4/4 G finite incl. handoff on the live F mid (46.79 dB).
- Input mutation: fresh input copies every run; SHA-256 before/after SAME in
all 52 runs (F 31, G 21).
- Post-hoc output rewrite: F1's 3 output buffers byte-identical after G1 ran.
- Creation order: F created after G ran, or after G's creation but before any
run, stays healthy through later G runs (2 conditions, single observations).
- Recovery: deleting G + gc does NOT heal F; deleting F and recreating it
(G gone) recovers to run-1 statistics; recreating F while the ran G lives
yields NaN from the first run ONLY if a contaminated F existed before in
that process (a 2nd F with a clean lineage is healthy).
- Prior art on the same machine: single-model 2nd-run NaN vs 11 session
options (none helped), cross-process cleanliness, orig<->N5 cross-model
contamination, 100/100 split-process pipe at ~2.05 s/tile.
Observations from xrt-smi
- Both sessions share one HW context per process; submissions==completions at
every stage including NaN runs; errors=0 throughout; ORT verbose log silent.
- Instruction-BO accounting is symmetric and correct (F ~9728 KB, G ~4064 KB;
delete shrinks, recreate restores), yet F stays NaN — the corruption is not
the instruction memory.
- Separate note (different symptom): unthrottled concurrent F+G submission
from two processes times out (ERT_CMD_STATE_TIMEOUT on both sides);
evidence kept separately, not conflated with this NaN issue.
Workaround we use
F and G in separate processes with alternating handoff: 100/100 finite,
~2.05-2.08 s/tile.
Attachments
- Repro package (
repro.py with --case same_process|front_only|back_only| cross_process|order_after_g|recover_*, expected.md, README.md,
manifest.json, env.txt, fixed .npy inputs incl. raw NaN outputs).
- Release with the repro package and large assets (F/G ONNX;
.rai caches on request): https://github.com/tomhda/ultraeasy-upscaler/releases/tag/adcsr-npu-repro-v1
- The models can also be regenerated from the public AdcSR weights with the scripts in this repo:
scripts/adcsr/export_adcsr.py -> scripts/adcsr/rewrite_in_to_n5.py -> quark convert_fp32_to_bf16 --format with_cast -> scripts/adcsr/split_adcsr_npu.py (https://github.com/tomhda/ultraeasy-upscaler).
Related
Summary
Two VitisAI sessions in one process: F (front half of a split model,
1 input -> 3 outputs) and G (back half, 3 inputs -> 1 output).
Sequence F1 ok -> F2 ok (G idle) -> G1 ok -> F3 all-NaN on the main output
tensor -> G2 ok. Every later run of the same F session stays NaN; deleting G
does not heal F; recreating F (while G is gone) recovers. F alone (4 runs),
G alone (4 runs), and F/G in separate processes are all clean.
The failing layer (EP / VAIP / XRT / driver) is NOT identified.
Environment
onnxruntime-providers-ryzenai==1.8.0,ryzenai-dynamic-dispatch==1.8.0)onnxruntime-vitisai==1.27.0,onnx==1.22.0, python 3.12.11)onnxruntime_vitisai_ep.dll1.8.0.0, providers DLLs 1.27.0.0cache_dir=vendor/amd-npu-1.8,cache_key=modelcachekey_r5_front/modelcachekey_r5_back,enable_cache_file_io_in_mem=0Models
F (
front_bf16cast.onnx, 1776295828 B, 5799 nodes): inputinput[1,3,128,128] -> 3 boundary tensors: main [1,256,64,64], mean [1,3,1,1],
std [1,3,1,1] (all FLOAT). G (
back_bf16cast.onnx, 49871980 B, 1802 nodes):the 3 tensors ->
output[1,3,512,512]. Both bf16-cast, each a singleVAIML subgraph (F 4786/4797 ops 99.771%, G 1520/1520 100%, GOPs 100%).
Lineage: AdcSR net_params_200 -> export script -> N5 rewrite -> quark
bf16cast ->
onnx.utils.extract_modelcut (cut plane A). One-time VAIMLcompile ~93 min (F) + ~30 min (G); all runs below are cache hits
(F build ~7-8 s, G build ~0.6-0.9 s). Full hashes in
manifest.json.Minimal reproduction
python repro.py --case same_process(fresh process; fixed.npyinputs;raw pre-
nan_to_numoutputs saved; xrt snapshots per stage):Reproduced in a 2nd fresh process with identical pattern. G's own runs never
degrade (G2 finite after F went NaN), so contamination is one-way G->F.
"Permanent" is limited to: all later runs of the same F session stay NaN.
What we ruled out
runs): 3/3 F finite, 4/4 G finite incl. handoff on the live F mid (46.79 dB).
all 52 runs (F 31, G 21).
run, stays healthy through later G runs (2 conditions, single observations).
(G gone) recovers to run-1 statistics; recreating F while the ran G lives
yields NaN from the first run ONLY if a contaminated F existed before in
that process (a 2nd F with a clean lineage is healthy).
options (none helped), cross-process cleanliness, orig<->N5 cross-model
contamination, 100/100 split-process pipe at ~2.05 s/tile.
Observations from xrt-smi
every stage including NaN runs; errors=0 throughout; ORT verbose log silent.
delete shrinks, recreate restores), yet F stays NaN — the corruption is not
the instruction memory.
from two processes times out (
ERT_CMD_STATE_TIMEOUTon both sides);evidence kept separately, not conflated with this NaN issue.
Workaround we use
F and G in separate processes with alternating handoff: 100/100 finite,
~2.05-2.08 s/tile.
Attachments
repro.pywith--case same_process|front_only|back_only| cross_process|order_after_g|recover_*,expected.md,README.md,manifest.json,env.txt, fixed.npyinputs incl. raw NaN outputs)..raicaches on request): https://github.com/tomhda/ultraeasy-upscaler/releases/tag/adcsr-npu-repro-v1scripts/adcsr/export_adcsr.py->scripts/adcsr/rewrite_in_to_n5.py-> quarkconvert_fp32_to_bf16 --format with_cast->scripts/adcsr/split_adcsr_npu.py(https://github.com/tomhda/ultraeasy-upscaler).Related
related, not claimed identical)