https://huggingface.co/nvidia/nemotron-ocr-v1/tree/main

#8
LICENSE DELETED
@@ -1,243 +0,0 @@
1
- All binary model files are licensed under NVIDIA Open Model License Agreement.
2
- All source code files are licensed under the Apache 2.0 License.
3
-
4
- ------------
5
- NVIDIA Open Model License Agreement
6
- Last Modified: October 24, 2025
7
- https://www.nvidia.com/en-us/agreements/enterprise-software/nvidia-open-model-license/
8
-
9
- This NVIDIA Open Model License Agreement (the “Agreement”) is a legal agreement between the Legal Entity You represent, or if no entity is identified, You and NVIDIA Corporation and its Affiliates (“NVIDIA”) and governs Your use of the Models that NVIDIA provides to You under this Agreement. NVIDIA and You are each a “party” and collectively the “parties.”
10
-
11
- NVIDIA models released under this Agreement are intended to be used permissively and enable the further development of AI technologies. Subject to the terms of this Agreement, NVIDIA confirms that:
12
-
13
- - Models are commercially usable.
14
- - You are free to create and distribute Derivative Models.
15
- - NVIDIA does not claim ownership to any outputs generated using the Models or Derivative Models.
16
- By using, reproducing, modifying, distributing, performing or displaying any portion or element of the Model or Derivative Model, or otherwise accepting the terms of this Agreement, you agree to be bound by this Agreement.
17
-
18
- 1. Definitions. The following definitions apply to this Agreement:
19
-
20
- 1.1 "Derivative Model" means all (a) modifications to the Model, (b) works based on the Model, and (c) any other derivative works of the Model. An output is not a Derivative Model.
21
-
22
- 1.2 "Legal Entity" means the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of fifty percent (50%) or more of the outstanding shares, or (c) beneficial ownership of such entity.
23
-
24
- 1.3 “Model” means the machine learning model, software, checkpoints, learnt weights, algorithms, parameters, configuration files and documentation shared under this Agreement.
25
-
26
- 1.4 "NVIDIA Cosmos Model" means a multimodal Model shared under this Agreement
27
-
28
- 1.5 "Special-Purpose Model" means a Model that is only competent in a narrow set of purpose-specific tasks and should not be used for unintended or general-purpose applications
29
-
30
- 1.6 “You” or “Your” means an individual or Legal Entity exercising permissions granted by this Agreement.
31
-
32
- 2. Conditions for Use, License Grant, AI Ethics and IP Ownership.
33
-
34
- 2.1 Conditions for Use. The Model and any Derivative Model are subject to additional terms as described in Section 2 and Section 3 of this Agreement and govern Your use. If You institute copyright or patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Model or a Derivative Model constitutes direct or contributory copyright or patent infringement, then any licenses granted to You under this Agreement for that Model or Derivative Model will terminate as of the date such litigation is filed. If You bypass, disable, reduce the efficacy of, or circumvent any technical limitation, safety guardrail or associated safety guardrail hyperparameter, encryption, security, digital rights management, or authentication mechanism (collectively “Guardrail”) contained in the Model without a substantially similar Guardrail appropriate for your use case, your rights under this Agreement will automatically terminate. NVIDIA may indicate in relevant documentation that a Model is a Special-Purpose Model. NVIDIA may update this Agreement to comply with legal and regulatory requirements at any time and You agree to either comply with any updated license or cease Your copying, use, and distribution of the Model and any Derivative Model.
35
-
36
- 2.2 License Grant. The rights granted herein are explicitly conditioned on Your full compliance with the terms of this Agreement. Subject to the terms and conditions of this Agreement, NVIDIA hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, revocable (as stated in Section 2.1) license to publicly perform, publicly display, reproduce, use, create derivative works of, make, have made, sell, offer for sale, distribute (through multiple tiers of distribution) and import the Model.
37
-
38
- 2.3 AI Ethics. Use of the Models under the Agreement must be consistent with NVIDIA’s Trustworthy AI terms found at https://www.nvidia.com/en-us/agreements/trustworthy-ai/terms/.
39
-
40
- 2.4 NVIDIA owns the Model and any Derivative Models created by NVIDIA. Subject to NVIDIA’s underlying ownership rights in the Model or its Derivative Models, You are and will be the owner of Your Derivative Models. NVIDIA claims no ownership rights in outputs. You are responsible for outputs and their subsequent uses. Except as expressly granted in this Agreement, (a) NVIDIA reserves all rights, interests and remedies in connection with the Model and (b) no other license or right is granted to you by implication, estoppel or otherwise.
41
-
42
- 3. Redistribution. You may reproduce and distribute copies of the Model or Derivative Models thereof in any medium, with or without modifications, provided that You meet the following conditions:
43
-
44
- 3.1 If you distribute the Model, You must give any other recipients of the Model a copy of this Agreement and include the following attribution notice within a “Notice” text file with such copies: “Licensed by NVIDIA Corporation under the NVIDIA Open Model License”;
45
-
46
- 3.2 If you distribute or make available a NVIDIA Cosmos Model, or a product or service (including an AI model) that contains or uses a NVIDIA Cosmos Model, use a NVIDIA Cosmos Model to create a Derivative Model, or use a NVIDIA Cosmos Model or its outputs to create, train, fine tune, or otherwise improve an AI model, you will include “Built on NVIDIA Cosmos” on a related website, user interface, blogpost, about page, or product documentation; and
47
-
48
- 3.3 You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Models as a whole, provided Your use, reproduction, and distribution of the Model otherwise complies with the conditions stated in this Agreement.
49
-
50
- 4. Separate Components. The Models may include or be distributed with components provided with separate legal notices or terms that accompany the components, such as an Open Source Software License or other third-party license. The components are subject to the applicable other licenses, including any proprietary notices, disclaimers, requirements and extended use rights; except that this Agreement will prevail regarding the use of third-party Open Source Software License, unless a third-party Open Source Software License requires its license terms to prevail. “Open Source Software License” means any software, data or documentation subject to any license identified as an open source license by the Open Source Initiative (https://opensource.org), Free Software Foundation (https://www.fsf.org) or other similar open source organization or listed by the Software Package Data Exchange (SPDX) Workgroup under the Linux Foundation (https://www.spdx.org).
51
-
52
- 5. Trademarks. This Agreement does not grant permission to use the trade names, trademarks, service marks, or product names of NVIDIA, except as required for reasonable and customary use in describing the origin of the Model and reproducing the content of the “Notice” text file.
53
-
54
- 6. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, NVIDIA provides the Model on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for reviewing Model documentation, including any Special-Purpose Model limitations, and determining the appropriateness of using or redistributing the Model, Derivative Models and outputs. You assume any risks associated with Your exercise of permissions under this Agreement.
55
-
56
- 7. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, will NVIDIA be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this Agreement or out of the use or inability to use the Model, Derivative Models or outputs (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if NVIDIA has been advised of the possibility of such damages.
57
-
58
- 8. Indemnity. You will indemnify and hold harmless NVIDIA from and against any claim by any third party arising out of or related to your use or distribution of the Model, Derivative Models or outputs.
59
-
60
- 9. Feedback. NVIDIA appreciates your feedback, and You agree that NVIDIA may use it without restriction or compensation to You.
61
-
62
- 10. Governing Law. This Agreement will be governed in all respects by the laws of the United States and the laws of the State of Delaware, without regard to conflict of laws principles or the United Nations Convention on Contracts for the International Sale of Goods. The state and federal courts residing in Santa Clara County, California will have exclusive jurisdiction over any dispute or claim arising out of or related to this Agreement, and the parties irrevocably consent to personal jurisdiction and venue in those courts; except that, either party may apply for injunctive remedies or an equivalent type of urgent legal relief in any jurisdiction.
63
-
64
- 11. Trade and Compliance. You agree to comply with all applicable export, import, trade and economic sanctions laws and regulations, as amended, including without limitation U.S. Export Administration Regulations and Office of Foreign Assets Control regulations. These laws include restrictions on destinations, end-users and end-use.
65
-
66
- Version Release Date: October 24, 2025
67
-
68
- -----------------
69
- Apache License
70
- Version 2.0, January 2004
71
- http://www.apache.org/licenses/
72
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
73
- 1. Definitions.
74
- "License" shall mean the terms and conditions for use, reproduction,
75
- and distribution as defined by Sections 1 through 9 of this document.
76
- "Licensor" shall mean the copyright owner or entity authorized by
77
- the copyright owner that is granting the License.
78
- "Legal Entity" shall mean the union of the acting entity and all
79
- other entities that control, are controlled by, or are under common
80
- control with that entity. For the purposes of this definition,
81
- "control" means (i) the power, direct or indirect, to cause the
82
- direction or management of such entity, whether by contract or
83
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
84
- outstanding shares, or (iii) beneficial ownership of such entity.
85
- "You" (or "Your") shall mean an individual or Legal Entity
86
- exercising permissions granted by this License.
87
- "Source" form shall mean the preferred form for making modifications,
88
- including but not limited to software source code, documentation
89
- source, and configuration files.
90
- "Object" form shall mean any form resulting from mechanical
91
- transformation or translation of a Source form, including but
92
- not limited to compiled object code, generated documentation,
93
- and conversions to other media types.
94
- "Work" shall mean the work of authorship, whether in Source or
95
- Object form, made available under the License, as indicated by a
96
- copyright notice that is included in or attached to the work
97
- (an example is provided in the Appendix below).
98
- "Derivative Works" shall mean any work, whether in Source or Object
99
- form, that is based on (or derived from) the Work and for which the
100
- editorial revisions, annotations, elaborations, or other modifications
101
- represent, as a whole, an original work of authorship. For the purposes
102
- of this License, Derivative Works shall not include works that remain
103
- separable from, or merely link (or bind by name) to the interfaces of,
104
- the Work and Derivative Works thereof.
105
- "Contribution" shall mean any work of authorship, including
106
- the original version of the Work and any modifications or additions
107
- to that Work or Derivative Works thereof, that is intentionally
108
- submitted to Licensor for inclusion in the Work by the copyright owner
109
- or by an individual or Legal Entity authorized to submit on behalf of
110
- the copyright owner. For the purposes of this definition, "submitted"
111
- means any form of electronic, verbal, or written communication sent
112
- to the Licensor or its representatives, including but not limited to
113
- communication on electronic mailing lists, source code control systems,
114
- and issue tracking systems that are managed by, or on behalf of, the
115
- Licensor for the purpose of discussing and improving the Work, but
116
- excluding communication that is conspicuously marked or otherwise
117
- designated in writing by the copyright owner as "Not a Contribution."
118
- "Contributor" shall mean Licensor and any individual or Legal Entity
119
- on behalf of whom a Contribution has been received by Licensor and
120
- subsequently incorporated within the Work.
121
- 2. Grant of Copyright License. Subject to the terms and conditions of
122
- this License, each Contributor hereby grants to You a perpetual,
123
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
124
- copyright license to reproduce, prepare Derivative Works of,
125
- publicly display, publicly perform, sublicense, and distribute the
126
- Work and such Derivative Works in Source or Object form.
127
- 3. Grant of Patent License. Subject to the terms and conditions of
128
- this License, each Contributor hereby grants to You a perpetual,
129
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
130
- (except as stated in this section) patent license to make, have made,
131
- use, offer to sell, sell, import, and otherwise transfer the Work,
132
- where such license applies only to those patent claims licensable
133
- by such Contributor that are necessarily infringed by their
134
- Contribution(s) alone or by combination of their Contribution(s)
135
- with the Work to which such Contribution(s) was submitted. If You
136
- institute patent litigation against any entity (including a
137
- cross-claim or counterclaim in a lawsuit) alleging that the Work
138
- or a Contribution incorporated within the Work constitutes direct
139
- or contributory patent infringement, then any patent licenses
140
- granted to You under this License for that Work shall terminate
141
- as of the date such litigation is filed.
142
- 4. Redistribution. You may reproduce and distribute copies of the
143
- Work or Derivative Works thereof in any medium, with or without
144
- modifications, and in Source or Object form, provided that You
145
- meet the following conditions:
146
- (a) You must give any other recipients of the Work or
147
- Derivative Works a copy of this License; and
148
- (b) You must cause any modified files to carry prominent notices
149
- stating that You changed the files; and
150
- (c) You must retain, in the Source form of any Derivative Works
151
- that You distribute, all copyright, patent, trademark, and
152
- attribution notices from the Source form of the Work,
153
- excluding those notices that do not pertain to any part of
154
- the Derivative Works; and
155
- (d) If the Work includes a "NOTICE" text file as part of its
156
- distribution, then any Derivative Works that You distribute must
157
- include a readable copy of the attribution notices contained
158
- within such NOTICE file, excluding those notices that do not
159
- pertain to any part of the Derivative Works, in at least one
160
- of the following places: within a NOTICE text file distributed
161
- as part of the Derivative Works; within the Source form or
162
- documentation, if provided along with the Derivative Works; or,
163
- within a display generated by the Derivative Works, if and
164
- wherever such third-party notices normally appear. The contents
165
- of the NOTICE file are for informational purposes only and
166
- do not modify the License. You may add Your own attribution
167
- notices within Derivative Works that You distribute, alongside
168
- or as an addendum to the NOTICE text from the Work, provided
169
- that such additional attribution notices cannot be construed
170
- as modifying the License.
171
- You may add Your own copyright statement to Your modifications and
172
- may provide additional or different license terms and conditions
173
- for use, reproduction, or distribution of Your modifications, or
174
- for any such Derivative Works as a whole, provided Your use,
175
- reproduction, and distribution of the Work otherwise complies with
176
- the conditions stated in this License.
177
- 5. Submission of Contributions. Unless You explicitly state otherwise,
178
- any Contribution intentionally submitted for inclusion in the Work
179
- by You to the Licensor shall be under the terms and conditions of
180
- this License, without any additional terms or conditions.
181
- Notwithstanding the above, nothing herein shall supersede or modify
182
- the terms of any separate license agreement you may have executed
183
- with Licensor regarding such Contributions.
184
- 6. Trademarks. This License does not grant permission to use the trade
185
- names, trademarks, service marks, or product names of the Licensor,
186
- except as required for reasonable and customary use in describing the
187
- origin of the Work and reproducing the content of the NOTICE file.
188
- 7. Disclaimer of Warranty. Unless required by applicable law or
189
- agreed to in writing, Licensor provides the Work (and each
190
- Contributor provides its Contributions) on an "AS IS" BASIS,
191
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
192
- implied, including, without limitation, any warranties or conditions
193
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
194
- PARTICULAR PURPOSE. You are solely responsible for determining the
195
- appropriateness of using or redistributing the Work and assume any
196
- risks associated with Your exercise of permissions under this License.
197
- 8. Limitation of Liability. In no event and under no legal theory,
198
- whether in tort (including negligence), contract, or otherwise,
199
- unless required by applicable law (such as deliberate and grossly
200
- negligent acts) or agreed to in writing, shall any Contributor be
201
- liable to You for damages, including any direct, indirect, special,
202
- incidental, or consequential damages of any character arising as a
203
- result of this License or out of the use or inability to use the
204
- Work (including but not limited to damages for loss of goodwill,
205
- work stoppage, computer failure or malfunction, or any and all
206
- other commercial damages or losses), even if such Contributor
207
- has been advised of the possibility of such damages.
208
- 9. Accepting Warranty or Additional Liability. While redistributing
209
- the Work or Derivative Works thereof, You may choose to offer,
210
- and charge a fee for, acceptance of support, warranty, indemnity,
211
- or other liability obligations and/or rights consistent with this
212
- License. However, in accepting such obligations, You may act only
213
- on Your own behalf and on Your sole responsibility, not on behalf
214
- of any other Contributor, and only if You agree to indemnify,
215
- defend, and hold each Contributor harmless for any liability
216
- incurred by, or claims asserted against, such Contributor by reason
217
- of your accepting any such warranty or additional liability.
218
- END OF TERMS AND CONDITIONS
219
- APPENDIX: How to apply the Apache License to your work.
220
- To apply the Apache License to your work, attach the following
221
- boilerplate notice, with the fields enclosed by brackets "[]"
222
- replaced with your own identifying information. (Don't include
223
- the brackets!) The text should be enclosed in the appropriate
224
- comment syntax for the file format. We also recommend that a
225
- file or class name and description of purpose be included on the
226
- same "printed page" as the copyright notice for easier
227
- identification within third-party archives.
228
-
229
- Copyright [yyyy] [name of copyright owner]
230
-
231
- Licensed under the Apache License, Version 2.0 (the "License");
232
- you may not use this file except in compliance with the License.
233
- You may obtain a copy of the License at
234
-
235
- http://www.apache.org/licenses/LICENSE-2.0
236
-
237
- Unless required by applicable law or agreed to in writing, software
238
- distributed under the License is distributed on an "AS IS" BASIS,
239
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
240
- See the License for the specific language governing permissions and
241
- limitations under the License.
242
-
243
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -14,7 +14,6 @@ tags:
14
  - text recognition
15
  - layout analysis
16
  - ingestion
17
- new_version: nvidia/nemotron-ocr-v2
18
  ---
19
 
20
  # Nemotron OCR v1
@@ -346,4 +345,4 @@ Please report security vulnerabilities or NVIDIA AI Concerns [here](https://app.
346
  | Model Application Field(s): | Text recognition and structured OCR for multimodal retrieval. Inputs can include natural scene images, scanned documents, charts, tables, and infographics. |
347
  | Use Case Restrictions: | Abide by [NVIDIA Open Model License Agreement](https://www.nvidia.com/en-us/agreements/enterprise-software/nvidia-open-model-license/) and the use of the post-processing scripts are licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt). |
348
  | Model and dataset restrictions: | The principle of least privilege (PoLP) is applied, limiting access for dataset generation and model development. Restrictions enforce dataset access only during training, and all dataset license constraints are adhered to. |
349
- | Describe the life critical impact (if present): | Not applicable. |
 
14
  - text recognition
15
  - layout analysis
16
  - ingestion
 
17
  ---
18
 
19
  # Nemotron OCR v1
 
345
  | Model Application Field(s): | Text recognition and structured OCR for multimodal retrieval. Inputs can include natural scene images, scanned documents, charts, tables, and infographics. |
346
  | Use Case Restrictions: | Abide by [NVIDIA Open Model License Agreement](https://www.nvidia.com/en-us/agreements/enterprise-software/nvidia-open-model-license/) and the use of the post-processing scripts are licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt). |
347
  | Model and dataset restrictions: | The principle of least privilege (PoLP) is applied, limiting access for dataset generation and model development. Restrictions enforce dataset access only during training, and all dataset license constraints are adhered to. |
348
+ | Describe the life critical impact (if present): | Not applicable. |
config.json CHANGED
@@ -1 +0,0 @@
1
- {}
 
 
example.py CHANGED
@@ -8,7 +8,7 @@ from nemotron_ocr.inference.pipeline import NemotronOCR
8
 
9
 
10
  def main(image_path, merge_level, no_visualize, model_dir):
11
- ocr_pipeline = NemotronOCR(model_dir=model_dir)
12
 
13
  predictions = ocr_pipeline(image_path, merge_level=merge_level, visualize=not no_visualize)
14
 
 
8
 
9
 
10
  def main(image_path, merge_level, no_visualize, model_dir):
11
+ ocr_pipeline = NemotronOCR()
12
 
13
  predictions = ocr_pipeline(image_path, merge_level=merge_level, visualize=not no_visualize)
14
 
nemotron-ocr/cpp/non_maximal_suppression/cuda_non_maximal_suppression.cu CHANGED
@@ -157,8 +157,11 @@ void device_row_collapse(torch::PackedTensorAccessor64<T, 5> allQuads,
157
  torch::PackedTensorAccessor64<T, 3> allConfs,
158
  T confThreshold, T iouThreshold,
159
  torch::PackedTensorAccessor64<int32_t, 1> allOutCounts,
160
- torch::PackedTensorAccessor64<T, 3> allOutEmbedQuads,
161
- torch::PackedTensorAccessor64<int32_t, 2> allOutIds)
 
 
 
162
  {
163
  typedef InPlaceQuad_<T> Quadf;
164
  static_assert(sizeof(Quadf) == sizeof(T) * 8, "Invalid QuadMem size!");
@@ -303,9 +306,11 @@ void device_row_collapse(torch::PackedTensorAccessor64<T, 5> allQuads,
303
  }
304
 
305
  write_embed_quad(outEmbedQuads, outQuad, storeOff + procLabel - 1);
 
306
  if (threadRank == 0) {
307
  allOutIds[b][storeOff + procLabel - 1] = r * 32 + startIdx;
308
  }
 
309
  }
310
 
311
  if (threadRank == 0) {
@@ -316,9 +321,9 @@ void device_row_collapse(torch::PackedTensorAccessor64<T, 5> allQuads,
316
  #undef threadRank
317
  }
318
 
319
- template<typename T>
320
  __global__
321
- void device_a2a_adjacency_sparse(const int32_t *ptrQuadCts,
322
  T iouThreshold,
323
  torch::PackedTensorAccessor64<T, 3> embedQuads,
324
  torch::PackedTensorAccessor64<bool, 2> outIsStart,
@@ -327,11 +332,7 @@ void device_a2a_adjacency_sparse(const int32_t *ptrQuadCts,
327
  {
328
  const uint32_t b = blockIdx.y;
329
 
330
- const int32_t quadCt = ptrQuadCts[b];
331
-
332
- if (quadCt == 0) {
333
- return;
334
- }
335
 
336
  const int32_t jobIdx = blockIdx.x * blockDim.x + threadIdx.x;
337
  const int32_t row = jobIdx / quadCt;
@@ -342,7 +343,7 @@ void device_a2a_adjacency_sparse(const int32_t *ptrQuadCts,
342
  return;
343
  }
344
 
345
- T* exData = embedQuads[b].data();
346
 
347
  const auto qRow = StridedEmbedQuad_<T>{ exData + row * embedQuads.stride(2), embedQuads.stride(1) }.Bounds(),
348
  qCol = StridedEmbedQuad_<T>{ exData + col * embedQuads.stride(2), embedQuads.stride(1) }.Bounds();
@@ -404,9 +405,9 @@ void device_a2a_adjacency_sparse(const int32_t *ptrQuadCts,
404
  }
405
  }
406
 
407
- template<uint32_t NumWarps, typename T, int32_t I_CELL_SIZE>
408
  __global__
409
- void device_a2a_adjacency_build_grid(const int32_t *ptrQuadCts,
410
  torch::PackedTensorAccessor64<T, 3> embedQuads,
411
  torch::PackedTensorAccessor64<int32_t, 4> outGridCells,
412
  torch::PackedTensorAccessor64<int32_t, 3> outQuadCells)
@@ -422,10 +423,10 @@ void device_a2a_adjacency_build_grid(const int32_t *ptrQuadCts,
422
 
423
  const uint32_t b = blockIdx.z;
424
 
425
- const uint32_t quadCt = ptrQuadCts[b];
426
  const uint32_t quadIdx = blockIdx.y;
427
 
428
- if (quadIdx >= quadCt) {
429
  return;
430
  }
431
 
@@ -484,9 +485,9 @@ void device_a2a_adjacency_build_grid(const int32_t *ptrQuadCts,
484
 
485
  typedef uint8_t visit_mask_t;
486
 
487
- template<uint32_t NumWarps, typename T>
488
  __global__
489
- void device_a2a_adjacency_with_grid(const int32_t *ptrQuadCts,
490
  T iouThreshold,
491
  torch::PackedTensorAccessor64<T, 3> allEmbedQuads,
492
  torch::PackedTensorAccessor64<int32_t, 4> allCells,
@@ -502,10 +503,10 @@ void device_a2a_adjacency_with_grid(const int32_t *ptrQuadCts,
502
 
503
  const uint32_t b = blockIdx.z;
504
 
505
- const uint32_t quadCt = ptrQuadCts[b];
506
  const uint32_t quadIdx = blockIdx.y;
507
 
508
- if (quadIdx >= quadCt) {
509
  return;
510
  }
511
 
@@ -534,7 +535,7 @@ void device_a2a_adjacency_with_grid(const int32_t *ptrQuadCts,
534
  auto exAdjCounts = reinterpret_cast<uint32_t*>(outAdjCounts[b].data());
535
  auto exAdjValues = outSparseAdj[b][quadIdx].data();
536
 
537
- T *exData = allEmbedQuads[b].data();
538
 
539
  const auto bdsAnchor = Quad_<T>{ s_quadVerts }.Bounds();
540
 
@@ -598,8 +599,9 @@ void device_a2a_adjacency_with_grid(const int32_t *ptrQuadCts,
598
  }
599
  }
600
 
 
601
  __global__
602
- void device_flatten_graph_iterative(const int32_t *ptrQuadCts,
603
  torch::PackedTensorAccessor64<bool, 2> allIsStart,
604
  volatile uint32_t *allAdjCounts,
605
  volatile uint32_t *allAdjValues
@@ -620,12 +622,14 @@ void device_flatten_graph_iterative(const int32_t *ptrQuadCts,
620
  const uint32_t b = blockIdx.z;
621
  const uint32_t anchorRow = blockIdx.y;
622
 
623
- const uint32_t quadCt = ptrQuadCts[b];
624
 
625
  // Only need to check this if there are multiple examples, since in the case of a single example,
626
  // the grid is precisely sized to that quadCt
627
- if (anchorRow >= quadCt) {
628
- return;
 
 
629
  }
630
 
631
  auto isStart = allIsStart[b].data();
@@ -686,13 +690,12 @@ void device_flatten_graph_iterative(const int32_t *ptrQuadCts,
686
  visitStack[1] = anchorRow;
687
  #ifndef NDEBUG
688
  for (uint32_t i = 2; i < VISIT_STACK_SIZE; ++i) {
689
- visitStack[i] = TERM_VALUE;
690
  }
691
  #endif
692
  int32_t visitPtr = 1;
693
 
694
- // NOTE: This loop is actually terminated by the `if (warpNextCol == TERM_VALUE)` check below
695
- for (uint32_t dfsIter = 0; true; ++dfsIter) {
696
  #ifdef NMS_VERIFY_CORRECTNESS
697
  assert(visitPtr >= 0 && visitPtr < VISIT_STACK_SIZE);
698
  #endif
@@ -704,7 +707,7 @@ void device_flatten_graph_iterative(const int32_t *ptrQuadCts,
704
  if (threadNextCol == warpNextCol) {
705
  #ifndef NDEBUG
706
  // This makes it easier to debug where the pointer is
707
- visitStack[visitPtr] = TERM_VALUE;
708
  #endif
709
  --visitPtr;
710
  }
@@ -728,15 +731,12 @@ void device_flatten_graph_iterative(const int32_t *ptrQuadCts,
728
  const uint32_t procAdjCount = adjCounts[procRow];
729
  auto procAdjValues = adjValues + (procRow * maxExCount);
730
 
 
 
 
 
731
  for (uint32_t i = threadRank; i < procAdjCount; i += WARP_SIZE) {
732
- uint32_t adjCol = procAdjValues[i];
733
-
734
- auto group = cg::coalesced_threads();
735
- // Offsetting by the iteration number will help balance out the maximum depth of any stack in the warp.
736
- // The reason behind this is due to how otherwise, warp-0 will always get a new element, warp-1 iff the adj graph
737
- // has more than one element, warp-2 iff the adj graph has more than two elements, and so on. Basically,
738
- // the warps have decreasing pressure. With the rotation mechanism, it helps to balance out stack usage.
739
- adjCol = group.shfl(adjCol, (group.thread_rank() + dfsIter) % group.size());
740
 
741
  // This will set the queued flag for this column, if it's not already set.
742
  // It also returns the old state. In our case, we only want to add this value to the
@@ -748,6 +748,7 @@ void device_flatten_graph_iterative(const int32_t *ptrQuadCts,
748
 
749
  bool alreadyAdded = oldMask & ADDED_MASK;
750
 
 
751
  const uint32_t gThreadRank = group.thread_rank();
752
  uint32_t notAddedBallot = group.ballot(!alreadyAdded);
753
  if (notAddedBallot) {
@@ -824,7 +825,8 @@ void add_to_set(const torch::TensorAccessor<int32_t, 1>& adjCounts,
824
  }
825
  }
826
 
827
- void cpu_flatten_graph(const int32_t *ptrQuadCts,
 
828
  torch::Tensor isStartTensorGPU,
829
  torch::Tensor adjCountsTensorGPU,
830
  torch::Tensor adjValuesTensorGPU)
@@ -838,7 +840,7 @@ void cpu_flatten_graph(const int32_t *ptrQuadCts,
838
  auto allAdjValues = adjValuesTensor.accessor<int32_t, 3>();
839
 
840
  for (int32_t b = 0; b < allAdjCounts.size(0); ++b) {
841
- const int32_t quadCt = ptrQuadCts[b];
842
 
843
  for (int32_t row = 0; row < quadCt; ++row) {
844
  std::unordered_set<int32_t> fullAdjSet;
@@ -893,9 +895,9 @@ void device_a2a_adj_cleanup(const int32_t *counts,
893
  }
894
  }
895
 
896
- template<uint32_t NumWarps, typename T>
897
  __global__
898
- void device_a2a_collapse(torch::PackedTensorAccessor64<int32_t, 1> quadCounts,
899
  torch::PackedTensorAccessor64<T, 3> allEmbedQuads,
900
  torch::PackedTensorAccessor64<bool, 2> allIsLeadRow,
901
  const int64_t *regionCounts,
@@ -915,14 +917,16 @@ void device_a2a_collapse(torch::PackedTensorAccessor64<int32_t, 1> quadCounts,
915
  const uint32_t b = blockIdx.z;
916
  const uint32_t row = blockIdx.y;
917
 
918
- const int32_t quadCt = quadCounts[b];
919
 
920
- if (row >= quadCt) {
921
- return;
 
 
922
  }
923
 
924
  // Only process the lead rows
925
- const auto isLeadRow = allIsLeadRow[b].data();
926
  if (!isLeadRow[row]) {
927
  return;
928
  }
@@ -941,7 +945,7 @@ void device_a2a_collapse(torch::PackedTensorAccessor64<int32_t, 1> quadCounts,
941
  __syncthreads();
942
  }
943
 
944
- T *exData = allEmbedQuads[b].data();
945
 
946
  const int32_t adjCount = allAdjCounts[b][row];
947
  const int32_t *adjIdxs = allAdjValues[b][row].data();
@@ -982,12 +986,20 @@ void device_a2a_collapse(torch::PackedTensorAccessor64<int32_t, 1> quadCounts,
982
 
983
  // Figure out the output position
984
  uint32_t writePosition = 0;
985
- for (int32_t i = threadRank; i < b; i += BLOCK_WIDTH) {
986
- writePosition += regionCounts[i];
 
 
987
  }
988
 
 
989
  const uint8_t *pCurrIsLeadRow = reinterpret_cast<const uint8_t*>(isLeadRow);
990
- for (int32_t i = threadRank; i < row; i += BLOCK_WIDTH) {
 
 
 
 
 
991
  if (pCurrIsLeadRow[i]) {
992
  ++writePosition;
993
  }
@@ -1063,9 +1075,13 @@ CollapseRowsResult collapse_rows(
1063
  int64_t embedSize = sizeof(EmbedQuad_<scalar_t>) / sizeof(scalar_t);
1064
  auto rowMergeTensor = torch::empty({ quads.size(0), embedSize, quads.size(1) * quads.size(2) }, quads.options());
1065
 
 
1066
  auto idsTensor = torch::full({ quads.size(0), quads.size(1) * quads.size(2) },
1067
  std::numeric_limits<int32_t>::max(),
1068
  counts.options().dtype(torch::kInt32));
 
 
 
1069
 
1070
  dim3 blockSize(32, 3, 1);
1071
  dim3 gridSize(1,
@@ -1077,8 +1093,10 @@ CollapseRowsResult collapse_rows(
1077
  probs.packed_accessor64<scalar_t, 3>(),
1078
  probThreshold, iouThreshold,
1079
  counts.packed_accessor64<int32_t, 1>(),
1080
- rowMergeTensor.packed_accessor64<scalar_t, 3>(),
1081
- idsTensor.packed_accessor64<int32_t, 2>()
 
 
1082
  );
1083
 
1084
  #ifdef NMS_VERIFY_CORRECTNESS
@@ -1101,6 +1119,7 @@ CollapseRowsResult collapse_rows(
1101
 
1102
  counts = counts.slice(/*dim=*/ 0, 0, counts.size(0) - 1);
1103
 
 
1104
  int64_t maxExCount;
1105
  if (counts.size(0) > 1) {
1106
  maxExCount = counts.max().item<int32_t>();
@@ -1112,12 +1131,13 @@ CollapseRowsResult collapse_rows(
1112
 
1113
  rowMergeTensor = rowMergeTensor.slice(2, 0, maxExCount);
1114
  idsTensor = idsTensor.slice(1, 0, maxExCount);
1115
- auto order = torch::argsort(idsTensor, /*dim=*/ 1, s_sortOrder);
1116
 
1117
  auto embOrder = order.unsqueeze(1).expand_as(rowMergeTensor);
1118
 
1119
  rowMergeTensor = torch::gather(rowMergeTensor, /*dim=*/ 2, embOrder);
1120
  idsTensor = torch::gather(idsTensor, /*dim=*/ 1, order);
 
1121
 
1122
  return { counts, rowMergeTensor, totalQuads, idsTensor, imageWidth, imageHeight };
1123
  }
@@ -1157,8 +1177,8 @@ struct AdjacencyResult {
1157
  int64_t MaxExCount;
1158
  };
1159
 
1160
- template<typename T>
1161
- void cpu_a2a_adjacency_sparse(const int32_t *ptrQuadCts,
1162
  const T iouThreshold,
1163
  torch::Tensor embedQuadsTensor,
1164
  torch::Tensor outIsStartTensorGPU,
@@ -1176,7 +1196,7 @@ void cpu_a2a_adjacency_sparse(const int32_t *ptrQuadCts,
1176
  auto adjValues = outSparseAdjTensor.accessor<int32_t, 3>();
1177
 
1178
  for (int32_t b = 0; b < embedQuadsTensor.size(0); ++b) {
1179
- const int32_t quadCt = ptrQuadCts[b];
1180
 
1181
  T *exData = embedQuads[b].data();
1182
 
@@ -1264,6 +1284,13 @@ AdjacencyResult compute_all_to_all_adjacency(
1264
  counts.options().dtype(torch::kInt32));
1265
  #endif
1266
 
 
 
 
 
 
 
 
1267
  #ifdef NMS_VERIFY_CORRECTNESS
1268
  auto cpuAdjValuesTensor = adjValuesTensor.cpu();
1269
  auto cpuAdjCountsTensor = adjCountsTensor.cpu();
@@ -1291,15 +1318,23 @@ AdjacencyResult compute_all_to_all_adjacency(
1291
  //blockSize = dim3{ GRID_NUM_WARPS * 32, 1, 1 };
1292
  //gridSize = dim3{ 1, static_cast<uint32_t>(maxExCount), static_cast<uint32_t>(counts.size(0)) };
1293
 
1294
- //device_a2a_adjacency_build_grid<GRID_NUM_WARPS, scalar_t, CELL_SIZE> KERNEL_ARG2(gridSize, blockSize) (
1295
- // counts.data_ptr<int32_t>(),
 
 
 
 
1296
  // collapseResult.StridedMergeQuads.packed_accessor64<scalar_t, 3>(),
1297
  // gridCellsTensor.packed_accessor64<int32_t, 4>(),
1298
  // quadCellExtentsTensor.packed_accessor64<int32_t, 3>()
1299
  //);
1300
 
1301
- //device_a2a_adjacency_with_grid<GRID_NUM_WARPS, scalar_t> KERNEL_ARG3(gridSize, blockSize, smemSize) (
1302
- // counts.data_ptr<int32_t>(),
 
 
 
 
1303
  // iouThreshold,
1304
  // collapseResult.StridedMergeQuads.packed_accessor64<scalar_t, 3>(),
1305
  // gridCellsTensor.packed_accessor64<int32_t, 4>(),
@@ -1316,9 +1351,11 @@ AdjacencyResult compute_all_to_all_adjacency(
1316
  gridSize = dim3{div_up(totalWork, blockSize.x),
1317
  static_cast<uint32_t>(counts.size(0))};
1318
 
 
 
1319
  // This algorithm is O(n^2) with n being the current number of quads
1320
- device_a2a_adjacency_sparse<scalar_t> KERNEL_ARG2(gridSize, blockSize) (
1321
- counts.data_ptr<int32_t>(),
1322
  iouThreshold,
1323
  collapseResult.StridedMergeQuads.packed_accessor64<scalar_t, 3>(),
1324
  isStartTensor.packed_accessor64<bool, 2>(),
@@ -1328,9 +1365,7 @@ AdjacencyResult compute_all_to_all_adjacency(
1328
 
1329
 
1330
  #ifdef NMS_VERIFY_CORRECTNESS
1331
- auto cpuCounts = counts.cpu();
1332
-
1333
- cpu_a2a_adjacency_sparse<scalar_t>(cpuCounts.data_ptr<int32_t>(), iouThreshold,
1334
  collapseResult.StridedMergeQuads, cpuIsStartTensor, cpuAdjCountsTensor, cpuAdjValuesTensor);
1335
 
1336
  adjValuesTensor = std::get<0>(torch::sort(adjValuesTensor, /*dim=*/ 2));
@@ -1345,12 +1380,16 @@ AdjacencyResult compute_all_to_all_adjacency(
1345
  auto maxDepthTensor = torch::tensor(0, adjCountsTensor.options());
1346
  #endif
1347
 
 
 
 
 
1348
  blockSize = dim3{ 128, 1, 1 };
1349
  gridSize = dim3{ 1, static_cast<uint32_t>(maxExCount), static_cast<uint32_t>(counts.size(0)) };
1350
  smemSize = div_up(maxExCount * sizeof(visit_mask_t), sizeof(uint32_t)) * sizeof(uint32_t);
1351
 
1352
- device_flatten_graph_iterative KERNEL_ARG3(gridSize, blockSize, smemSize) (
1353
- counts.data_ptr<int32_t>(),
1354
  isStartTensor.packed_accessor64<bool, 2>(),
1355
  reinterpret_cast<uint32_t*>(adjCountsTensor.data_ptr<int32_t>()),
1356
  reinterpret_cast<uint32_t*>(adjValuesTensor.data_ptr<int32_t>())
@@ -1360,7 +1399,7 @@ AdjacencyResult compute_all_to_all_adjacency(
1360
  );
1361
 
1362
  #ifdef NMS_VERIFY_CORRECTNESS
1363
- cpu_flatten_graph(cpuCounts.data_ptr<int32_t>(), cpuIsStartTensor, cpuAdjCountsTensor, cpuAdjValuesTensor);
1364
 
1365
  cpuAdjValuesTensor = std::get<0>(torch::sort(cpuAdjValuesTensor, /*dim=*/ 2));
1366
  adjValuesTensor = std::get<0>(torch::sort(adjValuesTensor, /*dim=*/ 2));
@@ -1398,6 +1437,7 @@ AdjacencyResult compute_all_to_all_adjacency(
1398
  cpuIsStartTensor = isStartTensor.cpu();
1399
  cpuAdjCountsTensor = adjCountsTensor.cpu();
1400
  cpuAdjValuesTensor = adjValuesTensor.cpu();
 
1401
  auto cpuCollapseIds = collapseResult.QuadIds.cpu();
1402
 
1403
  static std::vector<std::unordered_set<int32_t>> s_knownGroups;
@@ -1549,11 +1589,22 @@ nms_result_t
1549
  dim3 blockSize(NUM_WARPS * 32, 1, 1);
1550
  dim3 gridSize(1, adjResult.MaxExCount, counts.size(0));
1551
 
 
 
 
 
 
 
 
1552
  torch::Tensor outQuads = torch::empty({ numOutQuads, 4, 2 }, embedQuads.options());
1553
  torch::Tensor outConf = torch::empty({ numOutQuads }, embedQuads.options());
1554
 
1555
- device_a2a_collapse<NUM_WARPS, scalar_t> KERNEL_ARG2(gridSize, blockSize) (
1556
- counts.packed_accessor64<int32_t, 1>(),
 
 
 
 
1557
  embedQuads.packed_accessor64<scalar_t, 3>(),
1558
  isLeadRow.packed_accessor64<bool, 2>(),
1559
  regionCounts.data_ptr<int64_t>(),
 
157
  torch::PackedTensorAccessor64<T, 3> allConfs,
158
  T confThreshold, T iouThreshold,
159
  torch::PackedTensorAccessor64<int32_t, 1> allOutCounts,
160
+ torch::PackedTensorAccessor64<T, 3> allOutEmbedQuads
161
+ #ifdef NMS_VERIFY_CORRECTNESS
162
+ , torch::PackedTensorAccessor64<int32_t, 2> allOutIds
163
+ #endif
164
+ )
165
  {
166
  typedef InPlaceQuad_<T> Quadf;
167
  static_assert(sizeof(Quadf) == sizeof(T) * 8, "Invalid QuadMem size!");
 
306
  }
307
 
308
  write_embed_quad(outEmbedQuads, outQuad, storeOff + procLabel - 1);
309
+ #ifdef NMS_VERIFY_CORRECTNESS
310
  if (threadRank == 0) {
311
  allOutIds[b][storeOff + procLabel - 1] = r * 32 + startIdx;
312
  }
313
+ #endif
314
  }
315
 
316
  if (threadRank == 0) {
 
321
  #undef threadRank
322
  }
323
 
324
+ template<bool IsSingleExample, typename T>
325
  __global__
326
+ void device_a2a_adjacency_sparse(const uint64_t punCounts,
327
  T iouThreshold,
328
  torch::PackedTensorAccessor64<T, 3> embedQuads,
329
  torch::PackedTensorAccessor64<bool, 2> outIsStart,
 
332
  {
333
  const uint32_t b = blockIdx.y;
334
 
335
+ const int32_t quadCt = IsSingleExample ? punCounts : reinterpret_cast<const int32_t*>(punCounts)[b];
 
 
 
 
336
 
337
  const int32_t jobIdx = blockIdx.x * blockDim.x + threadIdx.x;
338
  const int32_t row = jobIdx / quadCt;
 
343
  return;
344
  }
345
 
346
+ T* exData = IsSingleExample ? embedQuads.data() : embedQuads[b].data();
347
 
348
  const auto qRow = StridedEmbedQuad_<T>{ exData + row * embedQuads.stride(2), embedQuads.stride(1) }.Bounds(),
349
  qCol = StridedEmbedQuad_<T>{ exData + col * embedQuads.stride(2), embedQuads.stride(1) }.Bounds();
 
405
  }
406
  }
407
 
408
+ template<uint32_t NumWarps, bool IsSingleExample, typename T, int32_t I_CELL_SIZE>
409
  __global__
410
+ void device_a2a_adjacency_build_grid(const uint64_t punCounts,
411
  torch::PackedTensorAccessor64<T, 3> embedQuads,
412
  torch::PackedTensorAccessor64<int32_t, 4> outGridCells,
413
  torch::PackedTensorAccessor64<int32_t, 3> outQuadCells)
 
423
 
424
  const uint32_t b = blockIdx.z;
425
 
426
+ const uint32_t quadCt = IsSingleExample ? punCounts : reinterpret_cast<const int32_t*>(punCounts)[b];
427
  const uint32_t quadIdx = blockIdx.y;
428
 
429
+ if (!IsSingleExample && quadIdx >= quadCt) {
430
  return;
431
  }
432
 
 
485
 
486
  typedef uint8_t visit_mask_t;
487
 
488
+ template<uint32_t NumWarps, bool IsSingleExample, typename T>
489
  __global__
490
+ void device_a2a_adjacency_with_grid(const uint64_t punCounts,
491
  T iouThreshold,
492
  torch::PackedTensorAccessor64<T, 3> allEmbedQuads,
493
  torch::PackedTensorAccessor64<int32_t, 4> allCells,
 
503
 
504
  const uint32_t b = blockIdx.z;
505
 
506
+ const uint32_t quadCt = IsSingleExample ? punCounts : reinterpret_cast<const int32_t*>(punCounts)[b];
507
  const uint32_t quadIdx = blockIdx.y;
508
 
509
+ if (!IsSingleExample && quadIdx >= quadCt) {
510
  return;
511
  }
512
 
 
535
  auto exAdjCounts = reinterpret_cast<uint32_t*>(outAdjCounts[b].data());
536
  auto exAdjValues = outSparseAdj[b][quadIdx].data();
537
 
538
+ T *exData = IsSingleExample ? allEmbedQuads.data() : allEmbedQuads[b].data();
539
 
540
  const auto bdsAnchor = Quad_<T>{ s_quadVerts }.Bounds();
541
 
 
599
  }
600
  }
601
 
602
+ template<bool IsSingleExample>
603
  __global__
604
+ void device_flatten_graph_iterative(const uint64_t punCounts,
605
  torch::PackedTensorAccessor64<bool, 2> allIsStart,
606
  volatile uint32_t *allAdjCounts,
607
  volatile uint32_t *allAdjValues
 
622
  const uint32_t b = blockIdx.z;
623
  const uint32_t anchorRow = blockIdx.y;
624
 
625
+ const uint32_t quadCt = IsSingleExample ? punCounts : reinterpret_cast<const int32_t*>(punCounts)[b];
626
 
627
  // Only need to check this if there are multiple examples, since in the case of a single example,
628
  // the grid is precisely sized to that quadCt
629
+ if constexpr (!IsSingleExample) {
630
+ if (anchorRow >= quadCt) {
631
+ return;
632
+ }
633
  }
634
 
635
  auto isStart = allIsStart[b].data();
 
690
  visitStack[1] = anchorRow;
691
  #ifndef NDEBUG
692
  for (uint32_t i = 2; i < VISIT_STACK_SIZE; ++i) {
693
+ visitStack[i] = -2;
694
  }
695
  #endif
696
  int32_t visitPtr = 1;
697
 
698
+ while (true) {
 
699
  #ifdef NMS_VERIFY_CORRECTNESS
700
  assert(visitPtr >= 0 && visitPtr < VISIT_STACK_SIZE);
701
  #endif
 
707
  if (threadNextCol == warpNextCol) {
708
  #ifndef NDEBUG
709
  // This makes it easier to debug where the pointer is
710
+ visitStack[visitPtr] = -2;
711
  #endif
712
  --visitPtr;
713
  }
 
731
  const uint32_t procAdjCount = adjCounts[procRow];
732
  auto procAdjValues = adjValues + (procRow * maxExCount);
733
 
734
+ // Offsetting by the iteration number will help balance out the maximum depth of any stack in the warp.
735
+ // The reason behind this is due to how otherwise, warp-0 will always get a new element, warp-1 iff the adj graph
736
+ // has more than one element, warp-2 iff the adj graph has more than two elements, and so on. Basically,
737
+ // the warps have decreasing pressure. With the rotation mechanism, it helps to balance out stack usage.
738
  for (uint32_t i = threadRank; i < procAdjCount; i += WARP_SIZE) {
739
+ const uint32_t adjCol = procAdjValues[i];
 
 
 
 
 
 
 
740
 
741
  // This will set the queued flag for this column, if it's not already set.
742
  // It also returns the old state. In our case, we only want to add this value to the
 
748
 
749
  bool alreadyAdded = oldMask & ADDED_MASK;
750
 
751
+ auto group = cg::coalesced_threads();
752
  const uint32_t gThreadRank = group.thread_rank();
753
  uint32_t notAddedBallot = group.ballot(!alreadyAdded);
754
  if (notAddedBallot) {
 
825
  }
826
  }
827
 
828
+ template<bool IsSingleExample>
829
+ void cpu_flatten_graph(const uint64_t punCounts,
830
  torch::Tensor isStartTensorGPU,
831
  torch::Tensor adjCountsTensorGPU,
832
  torch::Tensor adjValuesTensorGPU)
 
840
  auto allAdjValues = adjValuesTensor.accessor<int32_t, 3>();
841
 
842
  for (int32_t b = 0; b < allAdjCounts.size(0); ++b) {
843
+ const int32_t quadCt = IsSingleExample ? punCounts : reinterpret_cast<const int32_t*>(punCounts)[b];
844
 
845
  for (int32_t row = 0; row < quadCt; ++row) {
846
  std::unordered_set<int32_t> fullAdjSet;
 
895
  }
896
  }
897
 
898
+ template<uint32_t NumWarps, typename T, bool IsSingleExample>
899
  __global__
900
+ void device_a2a_collapse(const uint64_t punCounts,
901
  torch::PackedTensorAccessor64<T, 3> allEmbedQuads,
902
  torch::PackedTensorAccessor64<bool, 2> allIsLeadRow,
903
  const int64_t *regionCounts,
 
917
  const uint32_t b = blockIdx.z;
918
  const uint32_t row = blockIdx.y;
919
 
920
+ const int32_t quadCt = IsSingleExample ? punCounts : reinterpret_cast<const int32_t*>(punCounts)[b];
921
 
922
+ if constexpr (!IsSingleExample) {
923
+ if (row >= quadCt) {
924
+ return;
925
+ }
926
  }
927
 
928
  // Only process the lead rows
929
+ const auto isLeadRow = IsSingleExample ? allIsLeadRow.data() : allIsLeadRow[b].data();
930
  if (!isLeadRow[row]) {
931
  return;
932
  }
 
945
  __syncthreads();
946
  }
947
 
948
+ T *exData = IsSingleExample ? allEmbedQuads.data() : allEmbedQuads[b].data();
949
 
950
  const int32_t adjCount = allAdjCounts[b][row];
951
  const int32_t *adjIdxs = allAdjValues[b][row].data();
 
986
 
987
  // Figure out the output position
988
  uint32_t writePosition = 0;
989
+ if constexpr (!IsSingleExample) {
990
+ for (int32_t i = threadRank; i < b; i += BLOCK_WIDTH) {
991
+ writePosition += regionCounts[i];
992
+ }
993
  }
994
 
995
+ const int32_t numLongs = row >> 3; // Divide by 8
996
  const uint8_t *pCurrIsLeadRow = reinterpret_cast<const uint8_t*>(isLeadRow);
997
+ const uint64_t *lpCurrIsLeadRow = reinterpret_cast<const uint64_t*>(pCurrIsLeadRow);
998
+
999
+ for (int32_t i = threadRank; i < numLongs; i += BLOCK_WIDTH) {
1000
+ writePosition += __popcll(lpCurrIsLeadRow[i]);
1001
+ }
1002
+ for (int32_t i = (numLongs * 8) + threadRank; i < row; i += BLOCK_WIDTH) {
1003
  if (pCurrIsLeadRow[i]) {
1004
  ++writePosition;
1005
  }
 
1075
  int64_t embedSize = sizeof(EmbedQuad_<scalar_t>) / sizeof(scalar_t);
1076
  auto rowMergeTensor = torch::empty({ quads.size(0), embedSize, quads.size(1) * quads.size(2) }, quads.options());
1077
 
1078
+ #ifdef NMS_VERIFY_CORRECTNESS
1079
  auto idsTensor = torch::full({ quads.size(0), quads.size(1) * quads.size(2) },
1080
  std::numeric_limits<int32_t>::max(),
1081
  counts.options().dtype(torch::kInt32));
1082
+ #else
1083
+ torch::Tensor idsTensor;
1084
+ #endif
1085
 
1086
  dim3 blockSize(32, 3, 1);
1087
  dim3 gridSize(1,
 
1093
  probs.packed_accessor64<scalar_t, 3>(),
1094
  probThreshold, iouThreshold,
1095
  counts.packed_accessor64<int32_t, 1>(),
1096
+ rowMergeTensor.packed_accessor64<scalar_t, 3>()
1097
+ #ifdef NMS_VERIFY_CORRECTNESS
1098
+ , idsTensor.packed_accessor64<int32_t, 2>()
1099
+ #endif
1100
  );
1101
 
1102
  #ifdef NMS_VERIFY_CORRECTNESS
 
1119
 
1120
  counts = counts.slice(/*dim=*/ 0, 0, counts.size(0) - 1);
1121
 
1122
+ #ifdef NMS_VERIFY_CORRECTNESS
1123
  int64_t maxExCount;
1124
  if (counts.size(0) > 1) {
1125
  maxExCount = counts.max().item<int32_t>();
 
1131
 
1132
  rowMergeTensor = rowMergeTensor.slice(2, 0, maxExCount);
1133
  idsTensor = idsTensor.slice(1, 0, maxExCount);
1134
+ auto order = torch::argsort(idsTensor, /*dim=*/ 1, s_sortOrder); s_sortOrder = !s_sortOrder;
1135
 
1136
  auto embOrder = order.unsqueeze(1).expand_as(rowMergeTensor);
1137
 
1138
  rowMergeTensor = torch::gather(rowMergeTensor, /*dim=*/ 2, embOrder);
1139
  idsTensor = torch::gather(idsTensor, /*dim=*/ 1, order);
1140
+ #endif
1141
 
1142
  return { counts, rowMergeTensor, totalQuads, idsTensor, imageWidth, imageHeight };
1143
  }
 
1177
  int64_t MaxExCount;
1178
  };
1179
 
1180
+ template<bool IsSingleExample, typename T>
1181
+ void cpu_a2a_adjacency_sparse(const uint64_t punCounts,
1182
  const T iouThreshold,
1183
  torch::Tensor embedQuadsTensor,
1184
  torch::Tensor outIsStartTensorGPU,
 
1196
  auto adjValues = outSparseAdjTensor.accessor<int32_t, 3>();
1197
 
1198
  for (int32_t b = 0; b < embedQuadsTensor.size(0); ++b) {
1199
+ const int32_t quadCt = IsSingleExample ? punCounts : reinterpret_cast<const int32_t*>(punCounts)[b];
1200
 
1201
  T *exData = embedQuads[b].data();
1202
 
 
1284
  counts.options().dtype(torch::kInt32));
1285
  #endif
1286
 
1287
+ // If the batch is only a single example, instead of hitting global memory for the count, we can
1288
+ // just encode the count into the pointer instead
1289
+ uint64_t ptrCounts = reinterpret_cast<uint64_t>(counts.data_ptr<int32_t>());
1290
+ if (counts.size(0) == 1) {
1291
+ ptrCounts = maxExCount;
1292
+ }
1293
+
1294
  #ifdef NMS_VERIFY_CORRECTNESS
1295
  auto cpuAdjValuesTensor = adjValuesTensor.cpu();
1296
  auto cpuAdjCountsTensor = adjCountsTensor.cpu();
 
1318
  //blockSize = dim3{ GRID_NUM_WARPS * 32, 1, 1 };
1319
  //gridSize = dim3{ 1, static_cast<uint32_t>(maxExCount), static_cast<uint32_t>(counts.size(0)) };
1320
 
1321
+ //auto buildGridFn = counts.size(0) == 1 ?
1322
+ // device_a2a_adjacency_build_grid<GRID_NUM_WARPS, true, scalar_t, CELL_SIZE> :
1323
+ // device_a2a_adjacency_build_grid<GRID_NUM_WARPS, false, scalar_t, CELL_SIZE>;
1324
+
1325
+ //buildGridFn KERNEL_ARG2(gridSize, blockSize) (
1326
+ // ptrCounts,
1327
  // collapseResult.StridedMergeQuads.packed_accessor64<scalar_t, 3>(),
1328
  // gridCellsTensor.packed_accessor64<int32_t, 4>(),
1329
  // quadCellExtentsTensor.packed_accessor64<int32_t, 3>()
1330
  //);
1331
 
1332
+ //auto adjGridFn = counts.size(0) == 1 ?
1333
+ // device_a2a_adjacency_with_grid<GRID_NUM_WARPS, true, scalar_t> :
1334
+ // device_a2a_adjacency_with_grid<GRID_NUM_WARPS, false, scalar_t>;
1335
+
1336
+ //adjGridFn KERNEL_ARG3(gridSize, blockSize, smemSize) (
1337
+ // ptrCounts,
1338
  // iouThreshold,
1339
  // collapseResult.StridedMergeQuads.packed_accessor64<scalar_t, 3>(),
1340
  // gridCellsTensor.packed_accessor64<int32_t, 4>(),
 
1351
  gridSize = dim3{div_up(totalWork, blockSize.x),
1352
  static_cast<uint32_t>(counts.size(0))};
1353
 
1354
+ auto adjFn = counts.size(0) == 1 ? device_a2a_adjacency_sparse<true, scalar_t> : device_a2a_adjacency_sparse<false, scalar_t>;
1355
+
1356
  // This algorithm is O(n^2) with n being the current number of quads
1357
+ adjFn KERNEL_ARG2(gridSize, blockSize) (
1358
+ ptrCounts,
1359
  iouThreshold,
1360
  collapseResult.StridedMergeQuads.packed_accessor64<scalar_t, 3>(),
1361
  isStartTensor.packed_accessor64<bool, 2>(),
 
1365
 
1366
 
1367
  #ifdef NMS_VERIFY_CORRECTNESS
1368
+ cpu_a2a_adjacency_sparse<true>(ptrCounts, iouThreshold,
 
 
1369
  collapseResult.StridedMergeQuads, cpuIsStartTensor, cpuAdjCountsTensor, cpuAdjValuesTensor);
1370
 
1371
  adjValuesTensor = std::get<0>(torch::sort(adjValuesTensor, /*dim=*/ 2));
 
1380
  auto maxDepthTensor = torch::tensor(0, adjCountsTensor.options());
1381
  #endif
1382
 
1383
+ auto traverseFn = counts.size(0) == 1 ?
1384
+ device_flatten_graph_iterative<true> :
1385
+ device_flatten_graph_iterative<false>;
1386
+
1387
  blockSize = dim3{ 128, 1, 1 };
1388
  gridSize = dim3{ 1, static_cast<uint32_t>(maxExCount), static_cast<uint32_t>(counts.size(0)) };
1389
  smemSize = div_up(maxExCount * sizeof(visit_mask_t), sizeof(uint32_t)) * sizeof(uint32_t);
1390
 
1391
+ traverseFn KERNEL_ARG3(gridSize, blockSize, smemSize) (
1392
+ ptrCounts,
1393
  isStartTensor.packed_accessor64<bool, 2>(),
1394
  reinterpret_cast<uint32_t*>(adjCountsTensor.data_ptr<int32_t>()),
1395
  reinterpret_cast<uint32_t*>(adjValuesTensor.data_ptr<int32_t>())
 
1399
  );
1400
 
1401
  #ifdef NMS_VERIFY_CORRECTNESS
1402
+ cpu_flatten_graph<true>(ptrCounts, cpuIsStartTensor, cpuAdjCountsTensor, cpuAdjValuesTensor);
1403
 
1404
  cpuAdjValuesTensor = std::get<0>(torch::sort(cpuAdjValuesTensor, /*dim=*/ 2));
1405
  adjValuesTensor = std::get<0>(torch::sort(adjValuesTensor, /*dim=*/ 2));
 
1437
  cpuIsStartTensor = isStartTensor.cpu();
1438
  cpuAdjCountsTensor = adjCountsTensor.cpu();
1439
  cpuAdjValuesTensor = adjValuesTensor.cpu();
1440
+ auto cpuCounts = counts.cpu();
1441
  auto cpuCollapseIds = collapseResult.QuadIds.cpu();
1442
 
1443
  static std::vector<std::unordered_set<int32_t>> s_knownGroups;
 
1589
  dim3 blockSize(NUM_WARPS * 32, 1, 1);
1590
  dim3 gridSize(1, adjResult.MaxExCount, counts.size(0));
1591
 
1592
+ // If the batch is only a single example, instead of hitting global memory for the count, we can
1593
+ // just encode the count into the pointer instead
1594
+ uint64_t ptrCounts = reinterpret_cast<uint64_t>(counts.data_ptr<int32_t>());
1595
+ if (counts.size(0) == 1) {
1596
+ ptrCounts = adjResult.MaxExCount;
1597
+ }
1598
+
1599
  torch::Tensor outQuads = torch::empty({ numOutQuads, 4, 2 }, embedQuads.options());
1600
  torch::Tensor outConf = torch::empty({ numOutQuads }, embedQuads.options());
1601
 
1602
+ auto collapseFn = counts.size(0) == 1 ?
1603
+ device_a2a_collapse<NUM_WARPS, scalar_t, true> :
1604
+ device_a2a_collapse<NUM_WARPS, scalar_t, false>;
1605
+
1606
+ collapseFn KERNEL_ARG2(gridSize, blockSize) (
1607
+ ptrCounts,
1608
  embedQuads.packed_accessor64<scalar_t, 3>(),
1609
  isLeadRow.packed_accessor64<bool, 2>(),
1610
  regionCounts.data_ptr<int64_t>(),
nemotron-ocr/pyproject.toml CHANGED
@@ -5,7 +5,6 @@ description = "Nemoton OCR"
5
  authors = [{ name = "NVIDIA Nemotron" }]
6
  requires-python = ">=3.12,<3.13"
7
  dependencies = [
8
- "huggingface_hub>=0.20.0",
9
  "pandas>=2.3.3",
10
  "pillow>=12.0.0",
11
  "scikit-learn>=1.7.2",
 
5
  authors = [{ name = "NVIDIA Nemotron" }]
6
  requires-python = ">=3.12,<3.13"
7
  dependencies = [
 
8
  "pandas>=2.3.3",
9
  "pillow>=12.0.0",
10
  "scikit-learn>=1.7.2",
nemotron-ocr/src/nemotron_ocr/inference/pipeline.py CHANGED
@@ -6,7 +6,6 @@ import io
6
  import json
7
  import os
8
  from pathlib import Path
9
- from typing import Optional
10
 
11
  import numpy as np
12
  import torch
@@ -21,7 +20,6 @@ from nemotron_ocr.inference.post_processing.data.text_region import TextBlock
21
  from nemotron_ocr.inference.post_processing.quad_rectify import QuadRectify
22
  from nemotron_ocr.inference.post_processing.research_ops import parse_relational_results, reorder_boxes
23
  from nemotron_ocr.inference.pre_processing import interpolate_and_pad, pad_to_square
24
- from huggingface_hub import hf_hub_download
25
  from nemotron_ocr_cpp import quad_non_maximal_suppression, region_counts_to_indices, rrect_to_quads
26
  from PIL import Image, ImageDraw, ImageFont
27
  from torch import amp
@@ -39,57 +37,25 @@ MERGE_LEVELS = {"word", "sentence", "paragraph"}
39
  DEFAULT_MERGE_LEVEL = "paragraph"
40
 
41
 
42
- # HuggingFace repository for downloading model weights
43
- HF_REPO_ID = "nvidia/nemotron-ocr-v1"
44
- CHECKPOINT_FILES = ["detector.pth", "recognizer.pth", "relational.pth", "charset.txt"]
45
-
46
-
47
  class NemotronOCR:
48
  """
49
  A high-level pipeline for performing OCR on images.
50
-
51
- Model weights are automatically downloaded from Hugging Face Hub
52
- (nvidia/nemotron-ocr-v1) if not found locally.
53
  """
54
 
55
- def __init__(self, model_dir: Optional[str] = None):
56
- # If model_dir is provided and contains all required files, use it directly
57
- if model_dir is not None:
58
- local_path = Path(model_dir)
59
- if all((local_path / f).is_file() for f in CHECKPOINT_FILES):
60
- self._model_dir = local_path
61
- else:
62
- self._model_dir = self._download_checkpoints()
63
- else:
64
- self._model_dir = self._download_checkpoints()
65
 
66
  self._load_models()
67
  self._load_charset()
68
  self._initialize_processors()
69
 
70
- @staticmethod
71
- def _download_checkpoints() -> Path:
72
- """Download model checkpoints from HuggingFace Hub (cached locally after first download)."""
73
- downloaded_path = None
74
- for filename in CHECKPOINT_FILES:
75
- downloaded_path = hf_hub_download(
76
- repo_id=HF_REPO_ID,
77
- filename=f"checkpoints/{filename}",
78
- )
79
- # All checkpoint files are in the same directory
80
- return Path(downloaded_path).parent
81
-
82
  def _load_models(self):
83
  """Loads all necessary models into memory."""
84
  self.detector = FOTSDetector(coordinate_mode="RBOX", backbone="regnet_y_8gf", verbose=False)
85
- self.detector.load_state_dict(
86
- torch.load(self._model_dir / "detector.pth", weights_only=True), strict=True
87
- )
88
 
89
  self.recognizer = TransformerRecognizer(nic=self.detector.num_features[-1], num_tokens=858, max_width=32)
90
- self.recognizer.load_state_dict(
91
- torch.load(self._model_dir / "recognizer.pth", weights_only=True), strict=True
92
- )
93
 
94
  self.relational = GlobalRelationalModel(
95
  num_input_channels=self.detector.num_features,
@@ -98,9 +64,7 @@ class NemotronOCR:
98
  k=16,
99
  num_layers=4,
100
  )
101
- self.relational.load_state_dict(
102
- torch.load(self._model_dir / "relational.pth", weights_only=True), strict=True
103
- )
104
 
105
  for model in (self.detector, self.recognizer, self.relational):
106
  model = model.cuda()
@@ -217,17 +181,29 @@ class NemotronOCR:
217
  e2e_det_conf = torch.sigmoid(det_conf)
218
  e2e_det_coords = rrect_to_quads(det_rboxes.float(), DETECTOR_DOWNSAMPLE)
219
 
220
- quads, confidence, region_counts = quad_non_maximal_suppression(
221
- e2e_det_coords,
222
- e2e_det_conf,
223
- prob_threshold=NMS_PROB_THRESHOLD,
224
- iou_threshold=NMS_IOU_THRESHOLD,
225
- kernel_height=2,
226
- kernel_width=3,
227
- max_regions=NMS_MAX_REGIONS,
228
- verbose=False,
229
- )[:3]
230
-
 
 
 
 
 
 
 
 
 
 
 
 
231
 
232
  if quads.shape[0] == 0:
233
  rec_rectified_quads = torch.empty(0, 128, 8, 32, dtype=torch.float32, device=padded_image.device)
 
6
  import json
7
  import os
8
  from pathlib import Path
 
9
 
10
  import numpy as np
11
  import torch
 
20
  from nemotron_ocr.inference.post_processing.quad_rectify import QuadRectify
21
  from nemotron_ocr.inference.post_processing.research_ops import parse_relational_results, reorder_boxes
22
  from nemotron_ocr.inference.pre_processing import interpolate_and_pad, pad_to_square
 
23
  from nemotron_ocr_cpp import quad_non_maximal_suppression, region_counts_to_indices, rrect_to_quads
24
  from PIL import Image, ImageDraw, ImageFont
25
  from torch import amp
 
37
  DEFAULT_MERGE_LEVEL = "paragraph"
38
 
39
 
 
 
 
 
 
40
  class NemotronOCR:
41
  """
42
  A high-level pipeline for performing OCR on images.
 
 
 
43
  """
44
 
45
+ def __init__(self, model_dir="./checkpoints"):
46
+ self._model_dir = Path(model_dir)
 
 
 
 
 
 
 
 
47
 
48
  self._load_models()
49
  self._load_charset()
50
  self._initialize_processors()
51
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  def _load_models(self):
53
  """Loads all necessary models into memory."""
54
  self.detector = FOTSDetector(coordinate_mode="RBOX", backbone="regnet_y_8gf", verbose=False)
55
+ self.detector.load_state_dict(torch.load(self._model_dir / "detector.pth"), strict=True)
 
 
56
 
57
  self.recognizer = TransformerRecognizer(nic=self.detector.num_features[-1], num_tokens=858, max_width=32)
58
+ self.recognizer.load_state_dict(torch.load(self._model_dir / "recognizer.pth"), strict=True)
 
 
59
 
60
  self.relational = GlobalRelationalModel(
61
  num_input_channels=self.detector.num_features,
 
64
  k=16,
65
  num_layers=4,
66
  )
67
+ self.relational.load_state_dict(torch.load(self._model_dir / "relational.pth"), strict=True)
 
 
68
 
69
  for model in (self.detector, self.recognizer, self.relational):
70
  model = model.cuda()
 
181
  e2e_det_conf = torch.sigmoid(det_conf)
182
  e2e_det_coords = rrect_to_quads(det_rboxes.float(), DETECTOR_DOWNSAMPLE)
183
 
184
+ # FIXME: quad_non_maximal_suppression fails with batch size > 1
185
+ all_quads = []
186
+ all_confidence = []
187
+ all_region_counts = []
188
+
189
+ for idx in range(e2e_det_coords.shape[0]):
190
+ quads, confidence, region_counts = quad_non_maximal_suppression(
191
+ e2e_det_coords[idx].unsqueeze(0),
192
+ e2e_det_conf[idx].unsqueeze(0),
193
+ prob_threshold=NMS_PROB_THRESHOLD,
194
+ iou_threshold=NMS_IOU_THRESHOLD,
195
+ kernel_height=2,
196
+ kernel_width=3,
197
+ max_regions=NMS_MAX_REGIONS,
198
+ verbose=False,
199
+ )[:3]
200
+ all_quads.append(quads)
201
+ all_confidence.append(confidence)
202
+ all_region_counts.append(region_counts)
203
+
204
+ quads = torch.cat(all_quads, dim=0)
205
+ confidence = torch.cat(all_confidence, dim=0)
206
+ region_counts = torch.cat(all_region_counts, dim=0)
207
 
208
  if quads.shape[0] == 0:
209
  rec_rectified_quads = torch.empty(0, 128, 8, 32, dtype=torch.float32, device=padded_image.device)