sanmonga22 commited on
Commit
7d2d537
Β·
verified Β·
1 Parent(s): a6bb395

Publish parse Hexagon-NPU bundle (v81) + model card

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ v81/doc_sample.png filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ license_name: nvidia-open-model-license
4
+ license_link: https://huggingface.co/nvidia/NVIDIA-Nemotron-Parse-v1.2
5
+ tags:
6
+ - qhexrt
7
+ - hexagon
8
+ - npu
9
+ - qnn
10
+ - vlm
11
+ - ocr
12
+ - document-understanding
13
+ base_model: nvidia/NVIDIA-Nemotron-Parse-v1.2
14
+ pipeline_tag: image-text-to-text
15
+ ---
16
+
17
+ # Nemotron-Parse-v1.2 β€” Hexagon NPU (QHexRT)
18
+
19
+ Document OCR + layout parse (`nvidia/NVIDIA-Nemotron-Parse-v1.2`) compiled to run on the Qualcomm
20
+ Hexagon NPU through the [QHexRT](https://github.com/) runtime. Give it a document image; it returns the
21
+ text plus layout markup (`<class_Section-header>` / `<class_Text>` …) and bounding-box tokens.
22
+
23
+ It runs as a QHexRT **AED-VLM** plan: a RADIO ViT-H vision encoder feeds an MBart attention-encoder-decoder
24
+ that greedily decodes the page. Only the two heavy graphs (vision, decoder) run on the NPU; the glue is host C++.
25
+
26
+ ## Arch
27
+
28
+ - **Hexagon v81** (`dsp_arch=v81`, `soc_model=87`). Bundle dir: `v81/`.
29
+ - **Arch-pinned:** a context binary bakes in `dsp_arch` + `soc_model`, so these `.bin`s load **only** on v81.
30
+ A different Hexagon arch needs a re-compile into a sibling `v79/` / `v83/` dir (load failure, not wrong output).
31
+ - Precision: **fp16** on both graphs (no quantization yet; W8A16 weight-only is the future perf/memory lever).
32
+
33
+ ## Pipeline β€” what runs where
34
+
35
+ | stage | where | detail |
36
+ |---|---|---|
37
+ | RADIO preprocess | host C++ | CLIP mean/std normalize, `im_to_patches` 16Γ—16, embedder 768β†’1280, 8 prefix (cls/register) tokens, CPE pos-embed β†’ `embed[3336,1280]` |
38
+ | **vision encoder** | **NPU graph** `radio_enc_1024` | C-RADIOv2-H, 32-block ViT-H (1280-dim, 16 heads), full attention. `embed[3336,1280] β†’ vit_feat[3336,1280]`, fp16, 2D I/O |
39
+ | neck | host C++ | drop 8 prefix β†’ conv1β†’LN β†’ window conv2β†’LN + summary token β†’ **833 cross-attn memory tokens** `mem[833,1024]` |
40
+ | cross-KV | host C++ | project `mem` by each decoder layer's `encoder_attn.{k,v}_proj` β†’ the cross-attention K/V, bound once |
41
+ | **decoder step** | **NPU graph** `parse_decoder_step` | MBart AED, 10 layers, d1024/16 heads. Single-step **fixed-MDL=512 KV cache**, in-graph int32 embed + in-graph untied lm-head β†’ `logits[52352]`, fp16. No `position_ids` (MBart has no positional embedding) |
42
+ | decode loop | host C++ | greedy argmax, self-KV ping-pong, seed = eos = `decoder_start_token_id` = 2, stop on predicted eos β†’ detok β†’ text + layout markup |
43
+
44
+ No custom op-package (RADIO ViT + AED decoder are native QNN ops) β†’ **no `ADSP_LIBRARY_PATH` needed**.
45
+
46
+ ## Files (`v81/`)
47
+
48
+ | file | role | size |
49
+ |---|---|---|
50
+ | `nemotron-parse.json` | QHexRT manifest (the declarative run plan) | 2.4 KB |
51
+ | `radio_enc_1024.bin` | vision encoder context binary (RADIO ViT-H, fp16) | 1329.1 MB |
52
+ | `parse_decoder_step.bin` | MBart AED decoder-step context binary (KV-cache + in-graph lm-head, fp16) | 512.7 MB |
53
+ | `parse_radio_preproc.bin` | host blob: input-conditioner + patch-generator weights (CLIP norm, embedder 768β†’1280, prefix, CPE pos) | 21.0 MB |
54
+ | `parse_neck.bin` | host blob: neck weights (conv1, ln1, conv2, ln2, sum_proj, ln3) | 44.3 MB |
55
+ | `parse_cross_proj.bin` | host blob: `encoder_attn.{k,v}_proj` weight+bias Γ—10 layers (cross-KV projection) | 84.0 MB |
56
+ | `tokenizer.json` | tokenizer (vocab 52352) | 4.0 MB |
57
+ | `doc_sample.png` | device-gate fixture document | 0.1 MB |
58
+
59
+ Total β‰ˆ **2.0 GB**. The QNN runtime libs (`libQnnHtp.so`, `libQnnSystem.so`, the v81 HTP skel) come from the
60
+ QAIRT SDK β€” they are **not** in this repo.
61
+
62
+ ## Run (on-device, over adb)
63
+
64
+ Stage the QAIRT aarch64 runtime libs + the `qhx_generate` tool into `/data/local/tmp/wq` once (see the QHexRT
65
+ `docs/DEPLOY.md`), then:
66
+
67
+ ```bash
68
+ hf download runanywhere/nemotron_parse_HNPU --local-dir nemotron_parse_HNPU
69
+ adb push nemotron_parse_HNPU/v81 /data/local/tmp/wq/nemotron_parse # PowerShell + native paths on Windows
70
+
71
+ adb shell "cd /data/local/tmp/wq && LD_LIBRARY_PATH=. \
72
+ ./qhx_generate nemotron_parse/nemotron-parse.json libQnnHtp.so libQnnSystem.so nemotron_parse \
73
+ 512 '' nemotron_parse/doc_sample.png"
74
+ ```
75
+
76
+ Tool arg order is invariant: `<tool> <manifest> libQnnHtp.so libQnnSystem.so <artifacts_root> <max_new> "<prompt>" [image]`.
77
+ The **image is the input**; the text prompt is ignored (the host-op runs its own greedy decode to eos). `max_new`
78
+ caps the decode at the MDL (512 β†’ 511 tokens β‰ˆ a paragraph / simple page).
79
+
80
+ ## Device validation (Hexagon v81, S25 reference device)
81
+
82
+ Validated per the QHexRT three-gate rubric (offline oracle β†’ on-device raw-I/O parity β†’ end-to-end over multiple inputs):
83
+
84
+ - **Vision, on-device raw-I/O parity:** `embed[3336,1280]` F16 β†’ `vit_feat`, cosine **0.999030** vs the offline
85
+ reference at RES=1024 (3336-token ViT). (Offline oracle cos 1.0007 vs `vit.forward_features`.)
86
+ - **Decoder:** offline fixed-MDL(512) step cosine **1.000001** vs the full-seq `NemotronParseDecoder`; device-functional
87
+ (loads, emits `logits[52352]` + all self-KV out β€” the KV ring works).
88
+ - **End-to-end** on `doc_sample.png` β†’ reads the document text **exactly** vs the pure-greedy gold (65-token chain,
89
+ seed 2 β†’ eos 2): title + body text + `<class_Section-header>`/`<class_Text>` layout markup + bbox tokens. Confirmed
90
+ on **3 additional synthetic documents**.
91
+ - Text content is **exact**; bounding-box y-coordinates drift ~0.03 (fp16 coordinate-regression precision, a
92
+ `greedy_tol`-class tolerance β€” not a text error).
93
+
94
+ ### Measured performance (v81)
95
+
96
+ | phase | time |
97
+ |---|---|
98
+ | vision encoder (3336-wide self-attention) | ~1986 ms |
99
+ | decode | ~14 ms/token |
100
+ | end-to-end (`doc_sample.png`, 65 tokens) | ~15.7 s |
101
+
102
+ ## Caveats
103
+
104
+ - **Fixed device resolution 1024Γ—832.** The processor resizes by longest edge then white-pads; any-aspect document
105
+ is letterboxed to 1024Γ—832 on the host (patch grid stays 64Γ—52). Other resolutions need a re-export.
106
+ - **Output cap β‰ˆ 511 tokens** (MDL=512) β€” a paragraph or a simple page. A full multi-page document needs MDL raised
107
+ and the decoder re-exported.
108
+ - **Memory:** the 3336-wide vision self-attention spills ~5.5 GB DDR, so a ~12 GB device (S25) is required.
109
+ - **fp16, not quantized.** Bounding-box coordinates are the only place fp16 shows (~0.03 drift); text is exact.
110
+ - Greedy (temp=0) is the parity metric; the source config's `repetition_penalty=1.1` is a sampler-quality knob,
111
+ identical to pure greedy on short documents.
112
+
113
+ ## License
114
+
115
+ Governed by the source model's license (`nvidia/NVIDIA-Nemotron-Parse-v1.2`, NVIDIA Open Model License). This repo
116
+ ships only the compiled Hexagon context binaries + host weights + manifest, not the original checkpoints.
v81/doc_sample.png ADDED

Git LFS Details

  • SHA256: b671fb2b04b6db350eac178c0974267ef039183944010db9efd5fa5e3986d59a
  • Pointer size: 131 Bytes
  • Size of remote file: 101 kB
v81/nemotron-parse.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": 1,
3
+ "_comment": "NB-9 NVIDIA-Nemotron-Parse-v1.2 document OCR / layout-parse as a QHexRT AED-VLM family (image -> RADIO ViT-H vision -> host neck -> MBart AED decoder -> autoregressive greedy). The nemotron_parse_generate host-op runs the whole pipeline: host RADIO preprocess (LongestMaxSize-resize + white-pad to 1024x832, /255, OPENAI-CLIP norm, patch_generator im_to_patches 16x16 -> embedder 768->1280 + 8 prefix + CPE pos_embed) -> NPU vision graph radio_enc_1024 (32 RADIO blocks, 2D I/O embed[3336,1280] -> vit_feat[3336,1280], fp16) -> host neck (drop 8 prefix -> conv1/LN1 -> window conv2/LN2 + summary token -> 833 cross-attn memory tokens[833,1024]) -> host cross-KV (mem @ decoder.encoder_attn.{k,v}_proj per layer) -> NPU decoder step graph parse_decoder_step (single-step fixed-MDL=512 KV cache: input_ids[1] int32 in-graph embed, attention_mask[512], k/v_cache_self_N_in/out[512,1024], k/v_cache_cross_N[833,1024], in-graph lm-head -> logits[52352]) greedy decode loop (asr_whisper pattern: seed=eos=decoder_start_token_id=2, right-aligned mask, self-KV ping-pong, NO position_ids). No custom op-package (RADIO ViT + AED decoder are native QNN ops) -> no ADSP_LIBRARY_PATH needed (like Whisper). Host weight blobs (parse_radio_preproc.bin, parse_neck.bin, parse_cross_proj.bin) + tokenizer.json in fixture_dir. Offline-gated: preprocess cos 1.0008, neck cos 1.000066, cross-KV cos 1.000000; both graphs device-functional (vision Gate-B cos 0.999030, decoder loads + emits logits). Graph names are the compiled binary's real internal names (radio_enc_1024_f16 / parse_decoder_step_f16 from build_f16.sh); confirm with qhx_meta after compile.",
4
+ "model": { "name": "nemotron-parse", "family": "vlm", "dsp_arch": "v79" },
5
+ "params": {
6
+ "vocab": 52352,
7
+ "n_layers": 10,
8
+ "hidden": 1024,
9
+ "head_dim": 64,
10
+ "max_ctx": 512,
11
+ "eos_token_id": 2
12
+ },
13
+ "artifacts": {
14
+ "contexts": {
15
+ "vision": { "bin": "radio_enc_1024.bin" },
16
+ "decoder": { "bin": "parse_decoder_step.bin" }
17
+ },
18
+ "tokenizer": "tokenizer.json",
19
+ "fixture_dir": "."
20
+ },
21
+ "plan": {
22
+ "steps": [
23
+ {
24
+ "host": "nemotron_parse_generate",
25
+ "params": {
26
+ "vision": "radio_enc_1024_f16",
27
+ "decoder": "parse_decoder_step_f16",
28
+ "res": 1024,
29
+ "decoder_start": 2,
30
+ "eos": 2
31
+ }
32
+ }
33
+ ]
34
+ }
35
+ }
v81/parse_cross_proj.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:99c064845844826b7adb7caa3a216bb9fb691e7a69fd8ceb3fe1802d1d198be8
3
+ size 83968000
v81/parse_decoder_step.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ae6924eba1bfe6b4d215999a0ae9383b2a50a5c7c664d4e3fff386ff73720be5
3
+ size 512659456
v81/parse_neck.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:066c5afabfb38130fda03195a8b8ad389a797fb97a75a7c89d430b976649d858
3
+ size 44340240
v81/parse_radio_preproc.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:53d17ca48c9b709453ad59546fa062f0afe79c0a2d5d078e836daec7a01ece21
3
+ size 21017600
v81/radio_enc_1024.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e9287a8f01692ef6c3f9a3b5ee38abe526028ea7b7df927c93c8df2dd8a3b6c7
3
+ size 1329102848
v81/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff