Skip to content

VitisAI EP 1.8.0 (XDNA2): running session G makes subsequent runs of session F in the same process return all-NaN (F alone / G alone / separate processes are fine) #402

Description

@tomhda

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions