sourojitdotcom commited on
Commit
a449848
·
verified ·
1 Parent(s): 83bb5a1

Private review: measured five-language release, Hindi sentence mode and custom GGUFs

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +5 -34
  2. LICENSE +158 -0
  3. LICENSE-APACHE-2.0 +209 -0
  4. NOTICE +75 -0
  5. README.md +305 -0
  6. SHA256SUMS +101 -0
  7. aksharalp.mjs +822 -0
  8. assets/accuracy.png +0 -0
  9. assets/accuracy.svg +1 -0
  10. assets/banner.png +3 -0
  11. assets/banner.svg +1 -0
  12. assets/sizes.png +0 -0
  13. assets/sizes.svg +1 -0
  14. config.json +27 -0
  15. evaluation/bn_frozen.json +74 -0
  16. evaluation/examples.json +274 -0
  17. evaluation/gguf_independent_validation.json +54 -0
  18. evaluation/gguf_parity.json +262 -0
  19. evaluation/gguf_sizes.json +67 -0
  20. evaluation/hindi_existing_results.json +43 -0
  21. evaluation/indicxlit_storage.json +27 -0
  22. evaluation/kn_frozen.json +74 -0
  23. evaluation/latency.json +43 -0
  24. evaluation/multiindic_test.json +318 -0
  25. evaluation/release_summary.json +51 -0
  26. evaluation/sizes.json +41 -0
  27. evaluation/ta_frozen.json +74 -0
  28. evaluation/te_frozen.json +74 -0
  29. gguf/aksharalp-bn-word-f16.gguf +3 -0
  30. gguf/aksharalp-bn-word-q4.gguf +3 -0
  31. gguf/aksharalp-hi-sentence-f16.gguf +3 -0
  32. gguf/aksharalp-hi-sentence-q4.gguf +3 -0
  33. gguf/aksharalp-hi-sentence-q8.gguf +3 -0
  34. gguf/aksharalp-hi-word-f16.gguf +3 -0
  35. gguf/aksharalp-hi-word-q4.gguf +3 -0
  36. gguf/aksharalp-kn-word-f16.gguf +3 -0
  37. gguf/aksharalp-kn-word-q4.gguf +3 -0
  38. gguf/aksharalp-ta-word-f16.gguf +3 -0
  39. gguf/aksharalp-ta-word-q4.gguf +3 -0
  40. gguf/aksharalp-te-word-f16.gguf +3 -0
  41. gguf/aksharalp-te-word-q4.gguf +3 -0
  42. gguf_read.py +383 -0
  43. models/bn/dict.tsv.gz +3 -0
  44. models/bn/inventory.json +8 -0
  45. models/bn/params.json +58 -0
  46. models/bn/training_report.json +31 -0
  47. models/bn/xlit.q4 +3 -0
  48. models/bn/xlit.q4.gz +3 -0
  49. models/hi/dict/lex.bin +3 -0
  50. models/hi/dict/lex.bin.gz +3 -0
.gitattributes CHANGED
@@ -1,35 +1,6 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
  *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz 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
 
1
+ *.q4 filter=lfs diff=lfs merge=lfs -text
2
+ *.q8 filter=lfs diff=lfs merge=lfs -text
 
 
 
 
3
  *.gz filter=lfs diff=lfs merge=lfs -text
4
+ *.gguf filter=lfs diff=lfs merge=lfs -text
5
+ *.bin filter=lfs diff=lfs merge=lfs -text
6
+ assets/banner.png filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
LICENSE ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Creative Commons Attribution-NonCommercial 4.0 International
2
+
3
+ Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
4
+
5
+ Using Creative Commons Public Licenses
6
+
7
+ Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
8
+
9
+ Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors.
10
+
11
+ Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public.
12
+
13
+ Creative Commons Attribution-NonCommercial 4.0 International Public License
14
+
15
+ By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
16
+
17
+ Section 1 – Definitions.
18
+
19
+ a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
20
+
21
+ b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
22
+
23
+ c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
24
+
25
+ d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
26
+
27
+ e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
28
+
29
+ f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
30
+
31
+ g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
32
+
33
+ h. Licensor means the individual(s) or entity(ies) granting rights under this Public License.
34
+
35
+ i. NonCommercial means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange.
36
+
37
+ j. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
38
+
39
+ k. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
40
+
41
+ l. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
42
+
43
+ Section 2 – Scope.
44
+
45
+ a. License grant.
46
+
47
+ 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
48
+
49
+ A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and
50
+
51
+ B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only.
52
+
53
+ 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
54
+
55
+ 3. Term. The term of this Public License is specified in Section 6(a).
56
+
57
+ 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
58
+
59
+ 5. Downstream recipients.
60
+
61
+ A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
62
+
63
+ B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
64
+
65
+ 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
66
+
67
+ b. Other rights.
68
+
69
+ 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
70
+
71
+ 2. Patent and trademark rights are not licensed under this Public License.
72
+
73
+ 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes.
74
+
75
+ Section 3 – License Conditions.
76
+
77
+ Your exercise of the Licensed Rights is expressly made subject to the following conditions.
78
+
79
+ a. Attribution.
80
+
81
+ 1. If You Share the Licensed Material (including in modified form), You must:
82
+
83
+ A. retain the following if it is supplied by the Licensor with the Licensed Material:
84
+
85
+ i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
86
+
87
+ ii. a copyright notice;
88
+
89
+ iii. a notice that refers to this Public License;
90
+
91
+ iv. a notice that refers to the disclaimer of warranties;
92
+
93
+ v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
94
+
95
+ B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
96
+
97
+ C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
98
+
99
+ 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
100
+
101
+ 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
102
+
103
+ 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License.
104
+
105
+ Section 4 – Sui Generis Database Rights.
106
+
107
+ Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
108
+
109
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only;
110
+
111
+ b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and
112
+
113
+ c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
114
+ For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
115
+
116
+ Section 5 – Disclaimer of Warranties and Limitation of Liability.
117
+
118
+ a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.
119
+
120
+ b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.
121
+
122
+ c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
123
+
124
+ Section 6 – Term and Termination.
125
+
126
+ a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
127
+
128
+ b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
129
+
130
+ 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
131
+
132
+ 2. upon express reinstatement by the Licensor.
133
+
134
+ For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
135
+
136
+ c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
137
+
138
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
139
+
140
+ Section 7 – Other Terms and Conditions.
141
+
142
+ a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
143
+
144
+ b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
145
+
146
+ Section 8 – Interpretation.
147
+
148
+ a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
149
+
150
+ b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
151
+
152
+ c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
153
+
154
+ d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
155
+
156
+ Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
157
+
158
+ Creative Commons may be contacted at creativecommons.org.
LICENSE-APACHE-2.0 ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ AksharaLP by Sourojit Dhua — https://huggingface.co/sourojitdotcom/AksharaLP-1M-v2
2
+ Copyright 2026 Sourojit Dhua
3
+
4
+ Licensed under the Apache License, Version 2.0 (the full text follows). Commercial use is permitted.
5
+ Redistributions and derivative works must include the attribution notices in the accompanying
6
+ NOTICE file, as required by Section 4(d) of the License.
7
+
8
+
9
+ Apache License
10
+ Version 2.0, January 2004
11
+ http://www.apache.org/licenses/
12
+
13
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
14
+
15
+ 1. Definitions.
16
+
17
+ "License" shall mean the terms and conditions for use, reproduction,
18
+ and distribution as defined by Sections 1 through 9 of this document.
19
+
20
+ "Licensor" shall mean the copyright owner or entity authorized by
21
+ the copyright owner that is granting the License.
22
+
23
+ "Legal Entity" shall mean the union of the acting entity and all
24
+ other entities that control, are controlled by, or are under common
25
+ control with that entity. For the purposes of this definition,
26
+ "control" means (i) the power, direct or indirect, to cause the
27
+ direction or management of such entity, whether by contract or
28
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
29
+ outstanding shares, or (iii) beneficial ownership of such entity.
30
+
31
+ "You" (or "Your") shall mean an individual or Legal Entity
32
+ exercising permissions granted by this License.
33
+
34
+ "Source" form shall mean the preferred form for making modifications,
35
+ including but not limited to software source code, documentation
36
+ source, and configuration files.
37
+
38
+ "Object" form shall mean any form resulting from mechanical
39
+ transformation or translation of a Source form, including but
40
+ not limited to compiled object code, generated documentation,
41
+ and conversions to other media types.
42
+
43
+ "Work" shall mean the work of authorship, whether in Source or
44
+ Object form, made available under the License, as indicated by a
45
+ copyright notice that is included in or attached to the work
46
+ (an example is provided in the Appendix below).
47
+
48
+ "Derivative Works" shall mean any work, whether in Source or Object
49
+ form, that is based on (or derived from) the Work and for which the
50
+ editorial revisions, annotations, elaborations, or other modifications
51
+ represent, as a whole, an original work of authorship. For the purposes
52
+ of this License, Derivative Works shall not include works that remain
53
+ separable from, or merely link (or bind by name) to the interfaces of,
54
+ the Work and Derivative Works thereof.
55
+
56
+ "Contribution" shall mean any work of authorship, including
57
+ the original version of the Work and any modifications or additions
58
+ to that Work or Derivative Works thereof, that is intentionally
59
+ submitted to Licensor for inclusion in the Work by the copyright owner
60
+ or by an individual or Legal Entity authorized to submit on behalf of
61
+ the copyright owner. For the purposes of this definition, "submitted"
62
+ means any form of electronic, verbal, or written communication sent
63
+ to the Licensor or its representatives, including but not limited to
64
+ communication on electronic mailing lists, source code control systems,
65
+ and issue tracking systems that are managed by, or on behalf of, the
66
+ Licensor for the purpose of discussing and improving the Work, but
67
+ excluding communication that is conspicuously marked or otherwise
68
+ designated in writing by the copyright owner as "Not a Contribution."
69
+
70
+ "Contributor" shall mean Licensor and any individual or Legal Entity
71
+ on behalf of whom a Contribution has been received by Licensor and
72
+ subsequently incorporated within the Work.
73
+
74
+ 2. Grant of Copyright License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ copyright license to reproduce, prepare Derivative Works of,
78
+ publicly display, publicly perform, sublicense, and distribute the
79
+ Work and such Derivative Works in Source or Object form.
80
+
81
+ 3. Grant of Patent License. Subject to the terms and conditions of
82
+ this License, each Contributor hereby grants to You a perpetual,
83
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
84
+ (except as stated in this section) patent license to make, have made,
85
+ use, offer to sell, sell, import, and otherwise transfer the Work,
86
+ where such license applies only to those patent claims licensable
87
+ by such Contributor that are necessarily infringed by their
88
+ Contribution(s) alone or by combination of their Contribution(s)
89
+ with the Work to which such Contribution(s) was submitted. If You
90
+ institute patent litigation against any entity (including a
91
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
92
+ or a Contribution incorporated within the Work constitutes direct
93
+ or contributory patent infringement, then any patent licenses
94
+ granted to You under this License for that Work shall terminate
95
+ as of the date such litigation is filed.
96
+
97
+ 4. Redistribution. You may reproduce and distribute copies of the
98
+ Work or Derivative Works thereof in any medium, with or without
99
+ modifications, and in Source or Object form, provided that You
100
+ meet the following conditions:
101
+
102
+ (a) You must give any other recipients of the Work or
103
+ Derivative Works a copy of this License; and
104
+
105
+ (b) You must cause any modified files to carry prominent notices
106
+ stating that You changed the files; and
107
+
108
+ (c) You must retain, in the Source form of any Derivative Works
109
+ that You distribute, all copyright, patent, trademark, and
110
+ attribution notices from the Source form of the Work,
111
+ excluding those notices that do not pertain to any part of
112
+ the Derivative Works; and
113
+
114
+ (d) If the Work includes a "NOTICE" text file as part of its
115
+ distribution, then any Derivative Works that You distribute must
116
+ include a readable copy of the attribution notices contained
117
+ within such NOTICE file, excluding those notices that do not
118
+ pertain to any part of the Derivative Works, in at least one
119
+ of the following places: within a NOTICE text file distributed
120
+ as part of the Derivative Works; within the Source form or
121
+ documentation, if provided along with the Derivative Works; or,
122
+ within a display generated by the Derivative Works, if and
123
+ wherever such third-party notices normally appear. The contents
124
+ of the NOTICE file are for informational purposes only and
125
+ do not modify the License. You may add Your own attribution
126
+ notices within Derivative Works that You distribute, alongside
127
+ or as an addendum to the NOTICE text from the Work, provided
128
+ that such additional attribution notices cannot be construed
129
+ as modifying the License.
130
+
131
+ You may add Your own copyright statement to Your modifications and
132
+ may provide additional or different license terms and conditions
133
+ for use, reproduction, or distribution of Your modifications, or
134
+ for any such Derivative Works as a whole, provided Your use,
135
+ reproduction, and distribution of the Work otherwise complies with
136
+ the conditions stated in this License.
137
+
138
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
139
+ any Contribution intentionally submitted for inclusion in the Work
140
+ by You to the Licensor shall be under the terms and conditions of
141
+ this License, without any additional terms or conditions.
142
+ Notwithstanding the above, nothing herein shall supersede or modify
143
+ the terms of any separate license agreement you may have executed
144
+ with Licensor regarding such Contributions.
145
+
146
+ 6. Trademarks. This License does not grant permission to use the trade
147
+ names, trademarks, service marks, or product names of the Licensor,
148
+ except as required for reasonable and customary use in describing the
149
+ origin of the Work and reproducing the content of the NOTICE file.
150
+
151
+ 7. Disclaimer of Warranty. Unless required by applicable law or
152
+ agreed to in writing, Licensor provides the Work (and each
153
+ Contributor provides its Contributions) on an "AS IS" BASIS,
154
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
155
+ implied, including, without limitation, any warranties or conditions
156
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
157
+ PARTICULAR PURPOSE. You are solely responsible for determining the
158
+ appropriateness of using or redistributing the Work and assume any
159
+ risks associated with Your exercise of permissions under this License.
160
+
161
+ 8. Limitation of Liability. In no event and under no legal theory,
162
+ whether in tort (including negligence), contract, or otherwise,
163
+ unless required by applicable law (such as deliberate and grossly
164
+ negligent acts) or agreed to in writing, shall any Contributor be
165
+ liable to You for damages, including any direct, indirect, special,
166
+ incidental, or consequential damages of any character arising as a
167
+ result of this License or out of the use or inability to use the
168
+ Work (including but not limited to damages for loss of goodwill,
169
+ work stoppage, computer failure or malfunction, or any and all
170
+ other commercial damages or losses), even if such Contributor
171
+ has been advised of the possibility of such damages.
172
+
173
+ 9. Accepting Warranty or Additional Liability. While redistributing
174
+ the Work or Derivative Works thereof, You may choose to offer,
175
+ and charge a fee for, acceptance of support, warranty, indemnity,
176
+ or other liability obligations and/or rights consistent with this
177
+ License. However, in accepting such obligations, You may act only
178
+ on Your own behalf and on Your sole responsibility, not on behalf
179
+ of any other Contributor, and only if You agree to indemnify,
180
+ defend, and hold each Contributor harmless for any liability
181
+ incurred by, or claims asserted against, such Contributor by reason
182
+ of your accepting any such warranty or additional liability.
183
+
184
+ END OF TERMS AND CONDITIONS
185
+
186
+ APPENDIX: How to apply the Apache License to your work.
187
+
188
+ To apply the Apache License to your work, attach the following
189
+ boilerplate notice, with the fields enclosed by brackets "[]"
190
+ replaced with your own identifying information. (Don't include
191
+ the brackets!) The text should be enclosed in the appropriate
192
+ comment syntax for the file format. We also recommend that a
193
+ file or class name and description of purpose be included on the
194
+ same "printed page" as the copyright notice for easier
195
+ identification within third-party archives.
196
+
197
+ Copyright [yyyy] [name of copyright owner]
198
+
199
+ Licensed under the Apache License, Version 2.0 (the "License");
200
+ you may not use this file except in compliance with the License.
201
+ You may obtain a copy of the License at
202
+
203
+ http://www.apache.org/licenses/LICENSE-2.0
204
+
205
+ Unless required by applicable law or agreed to in writing, software
206
+ distributed under the License is distributed on an "AS IS" BASIS,
207
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
208
+ See the License for the specific language governing permissions and
209
+ limitations under the License.
NOTICE ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ AksharaLP-MultiIndic-8M — Sourojit Dhua, 2026
2
+ https://huggingface.co/sourojitdotcom/AksharaLP-MultiIndic-8M
3
+
4
+ Combined model-card licence: CC-BY-NC-4.0. NON-COMMERCIAL RELEASE.
5
+ The new Bengali/Telugu/Kannada/Tamil weights include Samanantar-derived Aksharantar rows. The upstream Samanantar card specifies CC-BY-NC-4.0; other publisher pages describe CC0 packaging. This release does not certify those weights as commercially clean. An Apache-2.0 clean-data retrain is planned. Preserve source attributions and applicable upstream terms. Selecting CC-BY-NC does not resolve all derivative-rights questions.
6
+
7
+ Original Hindi word/sentence components and runtime code retain Apache-2.0 and their existing notices. They are independently available at:
8
+ https://huggingface.co/sourojitdotcom/AksharaLP-1M-v2
9
+ https://huggingface.co/sourojitdotcom/AksharaLP-Sentence-2.8M
10
+
11
+ Attributions:
12
+ Aksharantar — AI4Bharat, Madhani et al. (2022), CC0 mined/existing packaging and CC-BY manual data.
13
+ Samanantar — AI4Bharat, Ramesh et al. (2022), upstream card CC-BY-NC-4.0.
14
+ Dakshina — Google Research, Roark et al., Processing South Asian Languages Written in the Latin Script, LREC 2020, https://github.com/google-research-datasets/dakshina ; CC-BY-SA-4.0 https://creativecommons.org/licenses/by-sa/4.0/ . Source-attributed training pairs appear inside Aksharantar. Retain the upstream terms.
15
+ IndicCorp v2 — AI4Bharat, Doddapaneni et al. (ACL 2023), CC0. Dictionaries and Hindi LM training.
16
+ IndicXlit — AI4Bharat, MIT. Distillation teacher and comparison model, not redistributed.
17
+ Inter and Noto Sans Indic fonts in the Space — SIL Open Font License 1.1 (included beside each font).
18
+
19
+ For the unchanged Hindi components, retain the following original notice as well:
20
+
21
+ AksharaLP-Sentence-2.8M by Sourojit Dhua
22
+ https://huggingface.co/sourojitdotcom/AksharaLP-Sentence-2.8M
23
+ Copyright 2026 Sourojit Dhua
24
+
25
+ AksharaLP-Sentence-2.8M is licensed under the Apache License, Version 2.0 (see LICENSE).
26
+ Commercial use is permitted.
27
+
28
+ What is in here
29
+ * The v2 word model: a 989K-parameter character Transformer, its reranker, the dictionary pack
30
+ (lexicon + word-bigram language model) and the JavaScript/Python runtime, unchanged from
31
+ AksharaLP-1M-v2 (https://huggingface.co/sourojitdotcom/AksharaLP-1M-v2).
32
+ * The sentence add-on: nlm.q8 (a 2,770,752-parameter Hindi neural language model trained only on
33
+ IndicCorp v2 Hindi, CC0, with our own tokenizer, training code and runtime), plus nlm.mjs, v3.mjs
34
+ and sent.mjs. This add-on and its training data are commercially clean; the full system still
35
+ inherits the v2 word package's terms, which are Apache-2.0 as set out here.
36
+
37
+ ATTRIBUTION
38
+ Under Section 4(d) of the Apache License, any redistribution of this work or of a Derivative Work
39
+ (for example: shipping these files in an app, a web page, a browser extension or an SDK, or
40
+ releasing a fine-tuned, quantized or converted model based on it) must include a readable copy of
41
+ this NOTICE file. Please use the following attribution text:
42
+
43
+ Transliteration by AksharaLP (Sourojit Dhua) — https://huggingface.co/sourojitdotcom/AksharaLP-Sentence-2.8M
44
+
45
+ Recommended in-app credit (e.g. in an About, Settings or Licenses screen):
46
+
47
+ Hindi typing powered by AksharaLP by Sourojit Dhua
48
+
49
+ The model files in this repository were built with the help of the following
50
+ third-party resources. No third-party data is redistributed in this repository
51
+ other than as learned model parameters and derived word lists.
52
+
53
+ ------------------------------------------------------------------------------
54
+ IndicCorp v2 — AI4Bharat
55
+ https://github.com/AI4Bharat/IndicBERT/tree/main#indiccorp-v2
56
+ License: CC0
57
+ Used for: training the sentence language model (nlm.q8); the word-model lexicon and
58
+ word-bigram language model.
59
+
60
+ Aksharantar — AI4Bharat
61
+ https://huggingface.co/datasets/ai4bharat/Aksharantar
62
+ License: CC0 (mined data and existing sources) / CC-BY (manually collected data)
63
+ Used for: romanized <-> Devanagari word pairs for training the word model.
64
+
65
+ IndicXlit — AI4Bharat
66
+ https://github.com/AI4Bharat/IndicXlit
67
+ License: MIT
68
+ Used for: teacher model for word-model distillation.
69
+
70
+ CMUdict — Carnegie Mellon University
71
+ https://github.com/cmusphinx/cmudict
72
+ License: BSD-2-Clause-style
73
+ Used for: English word cues in the word model.
74
+
75
+ Fonts served by the demo Space (Inter, Noto Sans Devanagari) are under the SIL Open Font License 1.1.
README.md ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-nc-4.0
3
+ language:
4
+ - hi
5
+ - bn
6
+ - te
7
+ - kn
8
+ - ta
9
+ tags:
10
+ - transliteration
11
+ - indic
12
+ - multilingual
13
+ - tiny
14
+ - browser
15
+ - javascript
16
+ - offline
17
+ - input-method
18
+ - gguf
19
+ - custom-architecture
20
+ datasets:
21
+ - ai4bharat/Aksharantar
22
+ - ai4bharat/IndicCorpV2
23
+ metrics:
24
+ - accuracy
25
+ ---
26
+
27
+ <p align="center"><img src="assets/banner.svg" alt="AksharaLP MultiIndic — five scripts, one small footprint" width="100%"/></p>
28
+ <p align="center"><a href="https://huggingface.co/spaces/sourojitdotcom/AksharaLP-MultiIndic-demo">Try the private browser demo ↗</a> · <a href="#benchmarks">Measured benchmarks</a> · <a href="#gguf-containers">13 GGUF files</a> · <a href="#licence--data">Licence & data</a></p>
29
+
30
+ # AksharaLP-MultiIndic-8M
31
+
32
+ ### Five word transliterators. One optional Hindi sentence model. Your device.
33
+
34
+ **Hindi · Bengali · Telugu · Kannada · Tamil** — familiar Roman letters in, native script out. Five independent ~989K-parameter character Transformers, a lazily loaded dictionary per language, and the already-shipped **2,770,752-parameter Hindi sentence LM**. No inference server and no GPU required for inference. This is **transliteration, not translation or a general-purpose language model**.
35
+
36
+ > **Private review · non-commercial release.** This bundle's model-card licence is **CC-BY-NC-4.0**, not Apache-2.0. The new Bengali/Telugu/Kannada/Tamil weights include Samanantar-derived Aksharantar rows with non-commercial upstream terms. A commercially clean Apache-2.0 retrain is planned. The Hindi word and sentence components remain independently available under Apache-2.0 in their original repositories. See [Licence & data](#licence--data).
37
+
38
+ <table><tr><td align="center"><h2>4.94M</h2>five word models combined</td><td align="center"><h2>7.72M</h2>with the optional Hindi LM</td><td align="center"><h2>20.55 MB</h2>all word payloads, gzip-9</td><td align="center"><h2>23.48 MB</h2>including Hindi sentence mode</td></tr></table>
39
+
40
+ The **8M name rounds 7,715,520 neural parameters**, not a billion-parameter model. Dictionaries and small candidate-reranker coefficients are separate from that count. Loading one language does not load the other four. Browser inference uses float32 arithmetic after loading low-bit weights: small files do **not** mean equally small RAM use.
41
+
42
+ ## What this release does — and does not prove
43
+
44
+ - Every word Transformer is **988,928 parameters**, except Bengali (**989,056**, with one additional output character). The five sum to **4,944,768**; optional Hindi LM adds **2,770,752**.
45
+ - New word weights are about **0.54 MB gzip-9 per language**; model-plus-dictionary payloads are about **3.05–3.23 MB** each. Hindi keeps its larger, established 3M-word dictionary pack.
46
+ - IndicXlit has **11,487,744 neural parameters** in the comparison port. Its per-language original dictionaries are much larger; the exact byte comparison below uses their released JSON formats, not an optimized repacking.
47
+ - **We trail IndicXlit's full package in every new language on top-1, top-3 and top-8.** This release is a storage/accuracy trade-off, not a state-of-the-art or “best in class” claim.
48
+ - Sentence-level neural rescoring is **Hindi-only**. Bengali, Telugu, Kannada and Tamil convert word by word. The separate Bengali real-chat research front-end is **not** part of this bundle.
49
+
50
+ ## Benchmarks
51
+
52
+ <img src="assets/accuracy.png" alt="Per-language top-1 test comparison, including confidence intervals" width="100%"/>
53
+
54
+ **Aksharantar test, exact match, any accepted reference.** Top-1 %, 95% bootstrap intervals in brackets; paired differences are percentage points, ours minus IndicXlit.
55
+
56
+ | Language | Roman words | Ours + dictionary | IndicXlit full package | Paired gap [95% CI] |
57
+ |---|---:|---:|---:|---:|
58
+ | Hindi | 9,991 | 70.04 [69.16, 70.90] | 69.33 [68.44, 70.23] | +0.71 [-0.04, +1.40] |
59
+ | Bengali | 13,367 | 64.64 [63.83, 65.44] | 70.52 [69.76, 71.34] | -5.88 [-6.52, -5.22] |
60
+ | Telugu | 9,936 | 78.74 [77.93, 79.53] | 82.42 [81.65, 83.14] | -3.67 [-4.27, -3.07] |
61
+ | Kannada | 11,165 | 77.37 [76.61, 78.13] | 82.61 [81.92, 83.29] | -5.24 [-5.83, -4.67] |
62
+ | Tamil | 11,405 | 71.78 [71.01, 72.61] | 75.95 [75.20, 76.73] | -4.16 [-4.75, -3.59] |
63
+
64
+ | Language | Ours top-1 / top-3 / top-8 % | IndicXlit full top-1 / top-3 / top-8 % |
65
+ |---|---:|---:|
66
+ | Hindi | 70.04 / 88.31 / 93.63 | 69.33 / 85.40 / 88.29 |
67
+ | Bengali | 64.64 / 75.66 / 77.97 | 70.52 / 82.25 / 85.44 |
68
+ | Telugu | 78.74 / 86.87 / 88.63 | 82.42 / 90.63 / 92.75 |
69
+ | Kannada | 77.37 / 85.80 / 87.68 | 82.61 / 90.68 / 92.41 |
70
+ | Tamil | 71.78 / 80.05 / 82.61 | 75.95 / 84.21 / 87.26 |
71
+
72
+ **Protocol and caveats**
73
+
74
+ 1. For Bengali/Telugu/Kannada/Tamil, the shipped q4 file, top-500k IndicCorp frequency list and eight-feature linear reranker were frozen before test. The reranker was fitted on **all VALID**, not on test, using the same fixed feature design and L2 regularization selected during validation work. Quantized VALID candidates were regenerated before that fit. There was **one inference pass on the full test per frozen artifact**, with cached predictions scored once; no post-test tuning.
75
+ 2. NFC exact match, ZWJ/ZWNJ removed, any reference accepted. Top-k means at least one reference among the first k candidates, not “k times more accurate.” Intervals use **2,000 bootstrap resamples, seed 0**; paired intervals resample the same words together. Full top-k intervals, hashes and frozen configurations are in [evaluation/](evaluation/).
76
+ 3. IndicXlit scores reuse existing **beam-8 PyTorch-port predictions with the full official frequency dictionary formula**, rescored on exactly the same rows. This is not a fresh run of the official fairseq package. The port masks output to each language's script block. Bengali has one empty Roman input skipped by that runner; its missing prediction is counted incorrect and the row is not dropped.
77
+ 4. Hindi numbers are copied from the measured [AksharaLP-1M-v2 release](https://huggingface.co/sourojitdotcom/AksharaLP-1M-v2), **not re-tested**. Its candidate generation/reranker and 3M-word dictionary differ from the four new models. The old Hindi full-package comparison interval includes zero; it does not establish a significant top-1 win.
78
+ 5. Training reports record **zero valid/test native-word and Roman-form overlap** for all four new training files. The general IndicCorp frequency dictionaries are not test-filtered and can naturally contain test reference words. Distillation teachers may have their own historical overlap. This is not an unseen-vocabulary-only benchmark, and Aksharantar words are not a WhatsApp sentence benchmark.
79
+
80
+ ## Hindi sentence mode — the shipped add-on, unchanged
81
+
82
+ The [AksharaLP-Sentence-2.8M](https://huggingface.co/sourojitdotcom/AksharaLP-Sentence-2.8M) LM chooses jointly among candidates already proposed for each typed Hindi word. It cannot translate, invent a missing candidate, reorder words, or answer questions. The runtime exposes the existing candidate-membership guard.
83
+
84
+ | Dakshina Hindi sentences, 4,976 test items | Existing Hindi v2 word package + bigram context | With the neural sentence LM |
85
+ |---|---:|---:|
86
+ | Word accuracy % | 89.02 | 89.44 |
87
+ | Entire sentences exactly right % | 25.70 | 26.73 |
88
+ | Character error rate % | 4.07 | 3.93 |
89
+ | Word error rate % | 11.20 | 10.78 |
90
+
91
+ These are the **existing one-time published results, not new evaluations**. The 89.02/25.70 baseline already uses the older Hindi bigram context decoder; it is not the context-off demo toggle. Paired reported gain: word accuracy **+0.42 [0.33, 0.51]** points, sentence exact match **+1.02 [0.40, 1.65]** points (the difference of rounded sentence percentages is 1.03). This release retains the default **q8** LM and original settings. No sentence claims are made for the other four languages.
92
+
93
+ ## Footprint & latency
94
+
95
+ <img src="assets/sizes.png" alt="Canonical uncompressed per-language payload comparison on a logarithmic scale" width="100%"/>
96
+
97
+ | Language payload | Canonical raw bytes | Sum gzip-9 bytes | JS CPU p50 / p95 ms per word |
98
+ |---|---:|---:|---:|
99
+ | Hindi | 16,221,771 | 7,905,041 (7.91 MB) | 207.02 / 319.99 |
100
+ | Bengali | 14,658,574 | 3,049,313 (3.05 MB) | 51.61 / 74.00 |
101
+ | Telugu | 16,579,274 | 3,222,365 (3.22 MB) | 51.77 / 87.87 |
102
+ | Kannada | 17,376,245 | 3,224,222 (3.22 MB) | 55.35 / 100.77 |
103
+ | Tamil | 18,695,389 | 3,130,045 (3.13 MB) | 60.98 / 103.39 |
104
+ | Shared word JS runtime + root configuration | 68,781 | 23,186 | — |
105
+ | **All five word packages + shared runtime** | **83,600,034** | **20,554,172 (20.55 MB)** | — |
106
+ | Optional Hindi q8 sentence LM + JS add-on | 3,256,675 | 2,928,839 (2.93 MB) | Not re-timed |
107
+ | **All languages + Hindi sentence add-on** | **86,856,709** | **23,483,011 (23.48 MB)** | — |
108
+
109
+ MB = 1,000,000 bytes. Canonical raw dictionary size means decompressed frequency TSV for new languages, and native lexicon/bigram binaries for Hindi. Browser downloads use gzip companions where provided. gzip-9 values are sums of per-file compression, not actual RAM, not total repository storage, and not a promise about HTTP Content-Encoding. Charts/docs/fonts/Python/GGUF alternatives and duplicated compressed companions are excluded. All exact component sizes are in [sizes.json](evaluation/sizes.json).
110
+
111
+ Latency: **100 evenly spaced VALID words per language**, five warmups, cleared candidate memo per word, single JavaScript thread in **Node v24.22.1 on Intel(R) Xeon(R) Processor, Linux**; initialization excluded. This is **Node CPU latency, not a browser or phone speed claim**. Loading the large Hindi dictionary is substantially more expensive. Browser demo inference happens in a Web Worker and reports live timings. The 15 initial example word candidate lists matched Python exactly; a broader independent GGUF validation sample is below.
112
+
113
+ IndicXlit size comparison: **45,950,976 bytes of FP32 neural parameters**, plus original dictionary JSON raw bytes: Hindi 358,118,856; Bengali 403,487,845; Telugu 761,940,466; Kannada 1,045,504,407; Tamil 1,044,620,827. These compare different precisions, vocabularies and data encodings; IndicXlit was not requantized or repacked. They do not prove that ours is the smallest possible system.
114
+
115
+ ## Real outputs
116
+
117
+ Generated by the bundled JavaScript runtime, **not hand-written target strings**. These are fixed illustrative examples, not an accuracy suite or proof of sentence understanding. Multiword inputs outside Hindi are independent word conversions. Some outputs are wrong: in particular, Tamil `naan` currently produces **ணன்**, not the intended நான். We show it rather than hiding the failure.
118
+
119
+ ### Hindi
120
+
121
+ | Roman input | Actual browser-runtime output |
122
+ |---|---|
123
+ | `namaste` | नमस्ते |
124
+ | `kaise` | कैसे |
125
+ | `dhanyavaad` | धन्यवाद |
126
+ | `mera naam rahul hai` | मेरा नाम राहुल है |
127
+
128
+ ### Bengali
129
+
130
+ | Roman input | Actual browser-runtime output |
131
+ |---|---|
132
+ | `nomoskar` | নমস্কার |
133
+ | `ami` | আমি |
134
+ | `bhalo` | ভালো |
135
+ | `tumi kemon acho` | তুমি কেমন আছো |
136
+
137
+ ### Telugu
138
+
139
+ | Roman input | Actual browser-runtime output |
140
+ |---|---|
141
+ | `namaskaram` | నమస్కారం |
142
+ | `nenu` | నేను |
143
+ | `manchi` | మంచి |
144
+ | `meeru ela unnaru` | మీరు ఎలా ఉన్నారు |
145
+
146
+ ### Kannada
147
+
148
+ | Roman input | Actual browser-runtime output |
149
+ |---|---|
150
+ | `namaskara` | ನಮಸ್ಕಾರ |
151
+ | `nanu` | ನನು |
152
+ | `chennagide` | ಚೆನ್ನಾಗಿದೆ |
153
+ | `neevu hegiddira` | ನೀವು ಹೇಗಿದ್ದೀರಾ |
154
+
155
+ ### Tamil
156
+
157
+ | Roman input | Actual browser-runtime output |
158
+ |---|---|
159
+ | `vanakkam` | வணக்கம் |
160
+ | `naan` | ணன் |
161
+ | `nandri` | நன்றி |
162
+ | `neenga eppadi irukeenga` | நீங்க எப்படி இருகீங்க |
163
+
164
+ ### Hindi sentence examples
165
+
166
+ | Input | Actual output with the optional sentence LM |
167
+ |---|---|
168
+ | `main theek hoon` | मैं ठीक हूं |
169
+ | `ghar main kaun hai` | घर में कौन है |
170
+
171
+ ## JavaScript — browser, worker, or Node
172
+
173
+ Copy `multiindic.mjs`, `aksharalp.mjs`, the `models/` tree and (for Hindi sentence mode) `nlm.mjs`, `v3.mjs`, `sent.mjs`. Serve over HTTPS or localhost; ES modules do not reliably work from `file://`.
174
+
175
+ ```js
176
+ import { MultiIndic } from './multiindic.mjs';
177
+
178
+ // Loads Bengali's weights + dictionary, not every language.
179
+ const engine = await MultiIndic.load('./', { language: 'bn' });
180
+ console.log(engine.word('nomoskar', 8)); // ranked {text, score} objects
181
+ console.log(engine.transliterate('tumi kemon acho').output);
182
+
183
+ await engine.setLanguage('hi');
184
+ await engine.setSentence(true); // lazily loads the q8 Hindi LM
185
+ console.log(engine.transliterate('main theek hoon').output);
186
+ await engine.setSentence(false); // word-by-word, context off
187
+ ```
188
+
189
+ ```js
190
+ // Node >= 18: no dependencies, uses local files.
191
+ import {loadFromDir} from './multiindic.mjs';
192
+ const engine = await loadFromDir('./', {language: 'ta'});
193
+ console.log(engine.word('nandri', 3));
194
+ ```
195
+
196
+ Browser gzip decoding requires `DecompressionStream` support; Node uses its built-in zlib. Switching languages retains already loaded models in memory for reuse. Recreate the engine to release that cache. The demo preserves URLs, email addresses, @mentions, #tags, digits and already-native text. Hindi retains the plain reference convention of changing a final full stop to danda. Roman English words are otherwise treated as transliteration input; there is no multilingual code-switch detector.
197
+
198
+ ## Python reference
199
+
200
+ Requires `numpy` and `torch`. For a private review repository, authenticate with Hugging Face normally; never put a write token in browser code.
201
+
202
+ ```python
203
+ from pathlib import Path
204
+ import sys
205
+ from huggingface_hub import snapshot_download
206
+
207
+ root = Path(snapshot_download('sourojitdotcom/AksharaLP-MultiIndic-8M', token=True))
208
+ sys.path.insert(0, str(root / 'python'))
209
+ from multiindic import MultiIndic
210
+
211
+ engine = MultiIndic(root, language='te', threads=1)
212
+ print(engine.word('namaskaram', k=8))
213
+ engine.set_language('hi').set_sentence(True)
214
+ print(engine.transliterate('main theek hoon'))
215
+ ```
216
+
217
+ The Python reference returns candidate strings from `word()`; JavaScript additionally exposes uncalibrated ranking scores. Those scores are **not probabilities**. Hindi uses the plain reference token conversion in both runtimes, not the older demo's extra brand/acronym heuristics. The model/decoder parity samples are measured separately.
218
+
219
+ ## GGUF containers
220
+
221
+ **These are custom-architecture GGUF v3 tensor containers. They do NOT run in llama.cpp, Ollama, or LM Studio.** Use the bundled [gguf_read.py](gguf_read.py) plus our decoder. A GGUF is not a standalone dictionary or language-model runtime.
222
+
223
+ The writer/reader follows the [existing v2 export](https://huggingface.co/sourojitdotcom/AksharaLP-1M-v2). Word **q4 GGUF carries the byte-exact shipped mixed q4/q8/f16 blob in an I8 tensor**; it is not standard GGML Q4_0. F16 materializes those already-quantized weights as half floats; it does not restore an original full-precision checkpoint. Exact reranker configuration is retained as JSON metadata, and the external dictionary/runtime remain required.
224
+
225
+ For Hindi sentence mode, **q8 GGUF preserves the test-measured default byte-exactly**. An existing **q4 LM** and F16 materialization are included as alternatives with validation parity only. **Do not attach the default q8 test accuracies to q4 or F16 alternatives.** The q4 LM is not the demo default.
226
+
227
+ | GGUF file | Raw bytes | gzip-9 bytes |
228
+ |---|---:|---:|
229
+ | [aksharalp-hi-word-q4.gguf](gguf/aksharalp-hi-word-q4.gguf) | 632,416 | 562,579 |
230
+ | [aksharalp-hi-word-f16.gguf](gguf/aksharalp-hi-word-f16.gguf) | 2,026,048 | 1,348,288 |
231
+ | [aksharalp-bn-word-q4.gguf](gguf/aksharalp-bn-word-q4.gguf) | 591,136 | 541,220 |
232
+ | [aksharalp-bn-word-f16.gguf](gguf/aksharalp-bn-word-f16.gguf) | 1,984,864 | 1,321,177 |
233
+ | [aksharalp-te-word-q4.gguf](gguf/aksharalp-te-word-q4.gguf) | 591,008 | 542,744 |
234
+ | [aksharalp-te-word-f16.gguf](gguf/aksharalp-te-word-f16.gguf) | 1,984,608 | 1,329,379 |
235
+ | [aksharalp-kn-word-q4.gguf](gguf/aksharalp-kn-word-q4.gguf) | 591,008 | 540,642 |
236
+ | [aksharalp-kn-word-f16.gguf](gguf/aksharalp-kn-word-f16.gguf) | 1,984,608 | 1,319,622 |
237
+ | [aksharalp-ta-word-q4.gguf](gguf/aksharalp-ta-word-q4.gguf) | 591,008 | 542,094 |
238
+ | [aksharalp-ta-word-f16.gguf](gguf/aksharalp-ta-word-f16.gguf) | 1,984,608 | 1,328,348 |
239
+ | [aksharalp-hi-sentence-q8.gguf](gguf/aksharalp-hi-sentence-q8.gguf) | 3,488,192 | 2,981,426 |
240
+ | [aksharalp-hi-sentence-q4.gguf](gguf/aksharalp-hi-sentence-q4.gguf) | 2,110,912 | 1,596,808 |
241
+ | [aksharalp-hi-sentence-f16.gguf](gguf/aksharalp-hi-sentence-f16.gguf) | 5,790,336 | 5,159,347 |
242
+
243
+ ### Validation-only parity — not a second test evaluation
244
+
245
+ First **40 ASCII-alphabetic Roman words per language from full VALID** (200 total). Same dictionary and reranker; no fitting or tuning during parity. “Top-8 order” compares the complete ordered candidate list.
246
+
247
+ | Language | q4 top-1 identical | q4 top-8 order identical | F16 top-1 identical | F16 top-8 order identical |
248
+ |---|---:|---:|---:|---:|
249
+ | Hindi | 40/40 | 40/40 | 40/40 | 40/40 |
250
+ | Bengali | 40/40 | 40/40 | 40/40 | 40/40 |
251
+ | Telugu | 40/40 | 40/40 | 40/40 | 40/40 |
252
+ | Kannada | 40/40 | 40/40 | 40/40 | 40/40 |
253
+ | Tamil | 40/40 | 40/40 | 40/40 | 40/40 |
254
+
255
+ Hindi LM: first **12 Dakshina VALID sentences with at most 12 Roman words**, fixed word-model lattices. q8 reproduces **12/12** source decoder outputs; F16 **12/12**; q4 **11/12**. Maximum absolute sequence-score deltas vs q8 are 0.000000, 0.008702, and 6.343046, respectively. This is conversion agreement, **not accuracy**. Across the same validation sample, JavaScript and Python matched **200/200 top-1 and complete top-8 ordered lists**, and **12/12 Hindi sentence outputs**, after using the same plain-reference token policy. Both our structural reader and independent `gguf-py` read all 13 files; complete data is in [gguf_parity.json](evaluation/gguf_parity.json).
256
+
257
+ ```python
258
+ # After the Python setup above:
259
+ engine.set_language('bn')
260
+ engine.load_word_gguf(root / 'gguf/aksharalp-bn-word-q4.gguf')
261
+ print(engine.word('nomoskar'))
262
+ engine.set_language('hi')
263
+ engine.load_sentence_gguf(root / 'gguf/aksharalp-hi-sentence-q8.gguf')
264
+ print(engine.transliterate('main theek hoon'))
265
+ ```
266
+
267
+ ## Licence & data
268
+
269
+ **Model-card licence: CC-BY-NC-4.0. This release is non-commercial.** The Bengali, Telugu, Kannada and Tamil weights contain `aks-Samanantar` Aksharantar training rows. The [current upstream Samanantar card](https://huggingface.co/datasets/ai4bharat/samanantar) states **CC-BY-NC-4.0**. Aksharantar and the publisher's Samanantar packaging page describe CC0 packaging, so provenance terms conflict; this release does **not** certify those weights as commercially clean. A commercially clean **Apache-2.0 retrain is planned**, and would be separately evaluated before replacement.
270
+
271
+ | Resource | Terms / role |
272
+ |---|---|
273
+ | [Aksharantar, AI4Bharat](https://huggingface.co/datasets/ai4bharat/Aksharantar) | CC0 mined/existing packaging; CC-BY manual data. New training includes Samanantar-derived rows: Bengali **114,558**, Telugu **42,413**, Kannada **36,482**, Tamil **34,447**; per-language training reports are bundled. |
274
+ | [Samanantar, AI4Bharat](https://huggingface.co/datasets/ai4bharat/samanantar) | CC-BY-NC-4.0 upstream card; reason for this release's non-commercial restriction. |
275
+ | [Dakshina, Google Research](https://github.com/google-research-datasets/dakshina) | CC-BY-SA-4.0 upstream. Source-attributed pairs appear within Aksharantar; original terms remain, and derivative-rights questions are not resolved by selecting a more restrictive model-card licence. Cite Roark et al., LREC 2020. |
276
+ | [IndicCorp v2, AI4Bharat](https://huggingface.co/datasets/ai4bharat/IndicCorpV2) | CC0; frequency dictionaries and Hindi LM training. |
277
+ | [IndicXlit, AI4Bharat](https://github.com/AI4Bharat/IndicXlit) | MIT teacher for distillation, and comparison baseline; original checkpoint not redistributed. |
278
+ | Runtime code / original Hindi components | Apache-2.0, retained with their existing notices. Inter and Noto Indic demo fonts: SIL OFL 1.1. |
279
+
280
+ The **Hindi components are also separately available under Apache-2.0**: [AksharaLP-1M-v2](https://huggingface.co/sourojitdotcom/AksharaLP-1M-v2) and [AksharaLP-Sentence-2.8M](https://huggingface.co/sourojitdotcom/AksharaLP-Sentence-2.8M). This combined release does not revoke or replace those existing grants. Code, dictionaries and individual components retain their own upstream terms; see [NOTICE](NOTICE), [LICENSE](LICENSE) and [LICENSE-APACHE-2.0](LICENSE-APACHE-2.0). This documentation is engineering provenance, not legal advice.
281
+
282
+ ## FAQ
283
+
284
+ **Is this a multilingual LLM?** No. It cannot answer questions, reason generally, summarize, or translate between languages. It changes Romanized spellings into native script.
285
+
286
+ **Why 8M when a word model has 989K parameters?** Five independent word models total 4,944,768; optional Hindi LM adds 2,770,752; total 7,715,520, rounded to 8M. That count excludes small reranker coefficients and dictionary data. Only selected components load.
287
+
288
+ **Is it the best model for these languages?** Not established. It trails IndicXlit's full package on all four new test languages. We claim measured footprint and measured results, not smallest-ever or best-in-class status.
289
+
290
+ **Does every language understand a sentence?** No. Only Hindi has neural sentence rescoring. Others concatenate independent word predictions, preserving text layout. Hindi rescoring cannot recover a spelling outside its candidate list.
291
+
292
+ **Can I use it commercially?** Not this combined release. Its model-card licence is CC-BY-NC-4.0. Use independently Apache-licensed Hindi releases where appropriate, or wait for a cleared retrain. Retain all source attributions and review applicable component terms.
293
+
294
+ **Does text leave the device?** The demo fetches local model assets from the Space when loading a language. Typed text is processed in the browser and is not sent to an inference API or telemetry service. Once the selected assets are loaded, typing needs no network. This is not a promise to avoid the initial asset download.
295
+
296
+ **Can I run the GGUFs with llama.cpp?** No. GGUF is a container here, not a supported llama.cpp architecture. Use our reader, dictionaries and decoding code.
297
+
298
+ ## Limitations & responsible use
299
+
300
+ Names, ambiguous short words, unseen vocabulary, English code-switching, dialects and chat abbreviations can fail. The output must be user-reviewable; do not silently replace important legal, medical or financial text. Candidate suggestions are uncalibrated. Input encoding truncates a word after 31 Roman characters. Type-as-you-go inference is debounced; older phones and long sentences may be slow. All measured compute here is CPU, not an on-device mobile benchmark. Tiny low-bit downloads still expand to float32 arrays and large dictionary structures in RAM.
301
+
302
+ This private release records the current artifacts. No “best in class,” general-LLM capability, sub-5-MB complete bundle, commercial-clean, or multilingual-sentence claim is made.
303
+
304
+ ---
305
+ **AksharaLP · Sourojit Dhua · 2026-11-10 (IST)** · Private review copy. Do not make public without the owner's approval.
SHA256SUMS ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ d39ccdda1d2005a1b51113aec99f0c29a55d70dc0e35399656866ce0d9438e94 .gitattributes
2
+ cc97a2c4ff7723a1f25179dc7c18f676fa84191bbb36ea7d0061acaf3c102559 LICENSE
3
+ e61722325c93e58388069e84e208e43ca5b6294050676ee27194921cf453a071 LICENSE-APACHE-2.0
4
+ 8dd4007ec7ce19774061f88c9173ef176951e329083c079728c901ce15b15f13 NOTICE
5
+ edc975f244807c134d306d1376fdfb8b29073d5db540e30db0bc90371b3c1e45 README.md
6
+ f6484f5caaebaf2acb68540cc2cb29402030aee22a7e45b25d37424872f769a8 aksharalp.mjs
7
+ e29d4e04be4a7235d4f2b94b6578cd580486960a7068b048e1bf6c1b1799679a assets/accuracy.png
8
+ c5378130af32052f6020b7cdc5a491aa5f1d11de763c37ee5491ba1eaef0bf49 assets/accuracy.svg
9
+ 27da3ac213f72636663b84026093cf2a1c80f825d8d67f110466ae9df0d3d6fc assets/banner.png
10
+ a9f8a61342401feb1e4bb6c1f82d0831d3fcbf35e3a6788eb7027d193b8de129 assets/banner.svg
11
+ 70d7aee1fe5e1e61dc7154c0aa4e33c818ff30ebbbe5d78fb5f52ba09fad1e04 assets/sizes.png
12
+ e6a92bb1337fb4997c5d657e12d0b34208d0240e006aa3eb58bce6d3a9b53bec assets/sizes.svg
13
+ f5689263059364546fe23824634183d8781defb4691f36d951fff63558f16afc config.json
14
+ 13350ec5c2ffa334de1dd6fb11e318fb1eeb5b80791506768546c3e43607539d evaluation/bn_frozen.json
15
+ 79b140c2feac29957461d9ee879db331ea30885dee334675694169315f0d7535 evaluation/examples.json
16
+ 8d7b75ab880b89c749765cba386fcf0c3a290cdb6c1d2c9491be4a341a5453bc evaluation/gguf_independent_validation.json
17
+ 4f898f14ad3912b8ecfa2025476f17ddad7aaa307bcac381620ffa17f2652a27 evaluation/gguf_parity.json
18
+ bf7eba31593b49e8223d56726022d6e68627ad437661003771144995b5cba07f evaluation/gguf_sizes.json
19
+ 392f25d66278a10a0e4f0093539d8f117727737bfd1b3b2d0e4dc31cfb5c792d evaluation/hindi_existing_results.json
20
+ 8e29063e276abfa4320d1fc299b3a5560ec2bbada77bb7147a8fba97563b3acd evaluation/indicxlit_storage.json
21
+ 157f7694918ba21b4e5268edbcf54f3fa5f1416506234ef5b78f1b9a502755da evaluation/kn_frozen.json
22
+ 8a9b660df627f538d67decc52b16b5349dc416d0cb0b25fb0b793f7752f54763 evaluation/latency.json
23
+ 50f1594f15182c2ade9a10d4b971418932e98d7413a40590d2fc96f30ff6b80b evaluation/multiindic_test.json
24
+ 25b2cc588a557f77ee852427fe06c77b108cad74fb6082d299b72cc1e1d58fe0 evaluation/release_summary.json
25
+ a8912129bb1143d06e7f8761b515516b6e503d2f36951e684ee2f602b7911285 evaluation/sizes.json
26
+ 4798b6de7e820251a93924186934287d70407705df242573910c74b60d85e431 evaluation/ta_frozen.json
27
+ eb327b20a375b534bad337dbf90b4e054b58a788fc17a34b142668ccbdae0cca evaluation/te_frozen.json
28
+ 7a0b78e133bb3ed773f6a8bae5e3f1af4b75f5158de4e814efadeaa0002ed580 gguf/aksharalp-bn-word-f16.gguf
29
+ 8311c77889381ce559d483b965027c2d409787ac090b146ae1af08060e4a4552 gguf/aksharalp-bn-word-q4.gguf
30
+ 446ff8f003c3680e9098df94727f8eb67a6a05a7c418888fb0bfeeb2742e098e gguf/aksharalp-hi-sentence-f16.gguf
31
+ 8ecc83bc618b9c67173328a34f0f40e5c83ce77a993a6279acdeae6d724690db gguf/aksharalp-hi-sentence-q4.gguf
32
+ 5ff23bc8d131b560ee29c79ee16d15294600149200948433d76f29f4b8c3e496 gguf/aksharalp-hi-sentence-q8.gguf
33
+ 3b6670429a226a39d357160691c634160667728bae8cfc7a711067424a2d224e gguf/aksharalp-hi-word-f16.gguf
34
+ 737d14197514a2625eb9505eb0a22ebd62cd2a4b764c65761f4084599bb6eb36 gguf/aksharalp-hi-word-q4.gguf
35
+ c9d273c8339864bd37c2b051cab60d2741c1d71e56e8c27d00a981ff28ef3864 gguf/aksharalp-kn-word-f16.gguf
36
+ 0e000f7fc0e52bbcb1f05753a46e6de50d1a1bdd6bb0ef46303361b9c39138ed gguf/aksharalp-kn-word-q4.gguf
37
+ 3baf8764ecc77250666eb3f8eb83f2d7a33763e13227e2d6db8272868dd5a864 gguf/aksharalp-ta-word-f16.gguf
38
+ f270e77e6355d53dc140f7cc7282551f5126302bcfbdbb17d802861510896c04 gguf/aksharalp-ta-word-q4.gguf
39
+ 3c556cec413db8f1485a1ee9ea237406f967937019e0d9d6076c3ae41b7a6231 gguf/aksharalp-te-word-f16.gguf
40
+ 56b9f956b69e4fcf9e8d3f23c9391a091208651e90b06e2bde6d7f46d7cb6a60 gguf/aksharalp-te-word-q4.gguf
41
+ 9348b0d8b522e9e3e45843a95c47087a3331f811a26c52813fa177a506f5cdcf gguf_read.py
42
+ edb2ad0bd7de4e87bd4d49ac406a9ef01011451bf23ac82d4c063bb9cce1dc2b models/bn/dict.tsv.gz
43
+ 7007c674102f7d242b71832c877ea1d58b6b83393ac1ea2d2814f93dec499cda models/bn/inventory.json
44
+ e0bd564ad61f6dcef8eca6bf3d64f8b231146c9885aa4c5b282a2b2420606c4f models/bn/params.json
45
+ 7d85a15252ea42676289a216bb775c2e48fea96634b32d2f2f0dce214951ada3 models/bn/training_report.json
46
+ 9f099f0b9524f446debf1d3f0ae1dc7ed1e88625c33f6dbb7b45e0348bf76376 models/bn/xlit.q4
47
+ 1c87e62a4249a63c333942c1c8736eb2c89f9ee3148ef8fc422784ba6ce21e43 models/bn/xlit.q4.gz
48
+ 73bd5436b77db4ee433c564521f19a2b78cb6a45c429f274d43bef909708347f models/hi/dict/lex.bin
49
+ 3b4dbff703f395e97b4503646fde7782bab0f3d79b9731ef703df6b961e38f55 models/hi/dict/lex.bin.gz
50
+ 254e34abe7c1e6ed82f74d5cbf175aba1aedbee316363fe2a67e06291ba589ee models/hi/dict/lm.bin
51
+ ab3f6b4135862b5bbf42815cc1219274eed711b5a7dfc66f09fc7b0fe32ceed6 models/hi/dict/lm.bin.gz
52
+ 6d60eaf2b4e89a847a750a226680021460e454c6d1a06070a56e8f8664500565 models/hi/dict/params.json
53
+ bc7dd23ba83ec6bc5841b03fa7b8e3cb934d0322dda652863537ce03958ea79d models/hi/dict/params.json.gz
54
+ 620ab5ebf213fc4b8469b88338368b34616390154f72f64f3dcd49a550f01472 models/hi/manifest.json
55
+ acb2bd2c0c12813d18c9696058260d57ea504db76db6d2d0810054a9e65a875e models/hi/manifest.json.gz
56
+ 1dc58564cd649278c819641a65141e9e96a42f3fc7fba555b9072628674c2935 models/hi/params.json
57
+ 8e42f8aec0204bca50891244478d94ac3ba1801f8cc774c475b84c5581377c73 models/hi/params.json.gz
58
+ 2c90f300c0114354fcf0c5a12300b9ba98ce71b4580b54f0b870a9395e869227 models/hi/sentence/nlm.q4
59
+ b8f3850506d93f3b0531beb37362cf1ad7c02a9e1d8c81ebc128efee9e44c17e models/hi/sentence/nlm.q8
60
+ 7face18513d342c59b005f3ea57a0e86310dc863778a828e7743cea0d5c68526 models/hi/sentence/nlm.q8.gz
61
+ 652eafe0c1e2b46fec85663825621284f0a5911f2d7cc6e13b1182ce47cd9803 models/hi/xlit.q4
62
+ 191eb28f75f29c45ec29f044031a6ae5aedd6a80edc3f3a9d1ae78a53c04fb9d models/hi/xlit.q4.gz
63
+ 97035264d30eddb5810467dc5bbf088614221ea6ba2c3fff349c6bbd196bb644 models/kn/dict.tsv.gz
64
+ cc2085a1f34c929678658159bc22eaa7641ce4aecada94fa3336dc82e1e553a3 models/kn/inventory.json
65
+ 6076d9126ffce66630b634e47ac5b38d03cda3bce7ab4d07162aeb0cbc925356 models/kn/params.json
66
+ bfae38dd1fb1c799e9005750b3e7ed93860a2be8051489a3cd0ba9ead17ae30f models/kn/training_report.json
67
+ 3788fd545c297febb7dbbdbdd780e831042395b4ec99260b95c963607afc8664 models/kn/xlit.q4
68
+ bedc5a698dc5c83a964f03586bc0ded67e88fa0b644cdd82fafc21f1e50925d9 models/kn/xlit.q4.gz
69
+ 4d14f514020e7737144c22991e9324a29734b5ab203bef3b68ec6d9f51e7cddb models/ta/dict.tsv.gz
70
+ e1ea48ffbd41b725a435c611d0ebd1da454904bc90e2368eb2fbaecee9f1870b models/ta/inventory.json
71
+ f4520a8320f02573e6b8b6530a372c980903fbf757999540698957e8eec58385 models/ta/params.json
72
+ 0c93e563d879dfef97ca0d36ae6c6ce4ca21dd417741be90478b7191214b0165 models/ta/training_report.json
73
+ 5dd185a033db79835fede66ab41a25f5897f24ad5d7c8ecc9864ca6ec167fdc0 models/ta/xlit.q4
74
+ c15be889468520e6d5ee8dfbc4c77cad781a79f454d4681026fc082ae217afc2 models/ta/xlit.q4.gz
75
+ 20612a52ccf39c816d3068e78e9e9a3aa9aed9156f10de45ebbd543363943640 models/te/dict.tsv.gz
76
+ ff20b9635ecef52f692a53807be1c6a5acf3922212d2bc14f7dd9283002bc690 models/te/inventory.json
77
+ 653f897efe1af78bb52149f2a18dc804b49ecac9c93add867871e64f56e07470 models/te/params.json
78
+ da8bc7af7e481e995e3ada79f21585bdbcf7df8fc751fdf151d8a774f8f0f343 models/te/training_report.json
79
+ aea094e46a3c61eb9487d1aaba3824d3d9dc60f3f1cf6b6f99cadf04fd4318f9 models/te/xlit.q4
80
+ 3513df30b5d3bfa07085baa19f0c9c1076e85268a5df88fcc4b492630b1b4eb2 models/te/xlit.q4.gz
81
+ 037d1f4e40f61457e4e33a9b8a034d2509316b723f27907849d55f0fb9cf23cf multiindic.mjs
82
+ a0bc14ac9629a0bb6b69fe94609eb922a9b71bbd327b17553581b91648d970c2 nlm.mjs
83
+ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 python/bhc/__init__.py
84
+ af77db2406b19466f538ebc35be31b6ffcf23de2d8447e48247a8ba58c1c2697 python/bhc/agree.py
85
+ aebfdd405693b5096744cbab03a30662e440308583302e9b5b54575ce0498231 python/bhc/casual.py
86
+ b07229b0d484048f6f75fd8304dc62e2a05d2dbc450289bfb57f6efd669c9ffc python/bhc/lexlm.py
87
+ 5b7c49b45309ac3f324906bd2546c35cfc4d5a199a9311a9d845233337a41b39 python/bhc/lextrie.py
88
+ ba9c1c4b326328cbbc65085b379835fac522d116e1ea163625bfcadf5c0de723 python/bhc/nlm.py
89
+ 39e674d163ebcb7e7adcf02dd267fd6c9b188be1346486f2695736240c4f42ff python/bhc/skel.py
90
+ 09617bd0f9b90a4179628dd40d786a30ba6a3a2e043d3993ecf36cf223524483 python/bhc/text.py
91
+ 67de6da4dc780c3128ca5c2da4461d1e0616d3f1c65619cc62ef59699a28c2d2 python/bhc/translit2.py
92
+ e93f9cc572853158f7414a46ac24815aea40c76e0939a2b79a6034542aeb2615 python/bhc/translit3.py
93
+ 8eb7235292a0b42a4cdfc07c548809fe312e2eec2c622317ff40e0c203ff5766 python/bhc/xlit_model.py
94
+ 1a577ff4b9075b6ee73debed2955840d30a5796a846ba74af58561866681307f python/bhc/xlit_q.py
95
+ 9348b0d8b522e9e3e45843a95c47087a3331f811a26c52813fa177a506f5cdcf python/gguf_read.py
96
+ e22e0dfcd2cee31d13d5eee303e07dea973032bfdfbd09b2c4060d69b0f12367 python/multiindic.py
97
+ 72cabbf383d3b033df0eb37be00bb7079f51b0d08d793f0ac9d7eec4709dffbb python/requirements.txt
98
+ de4599eedf54731c4d61f2bee9afb0d3776aaa5a49516fa4d2bf720bc2fdbb7a sent.mjs
99
+ f62025ba5b1c6b7a0e3559a0b6a6dc6f53773f188f8be118f9d0e6083b4e5211 tools/build_gguf.py
100
+ 9fcbb1848e3a46daaf9d9b317f240a1d04d7442a7242af99df7a23d24fd48940 tools/gguf_format.py
101
+ dcd348d0f8f26b3316123228ae534d37155bfe552520141a38aa55b14707f0d9 v3.mjs
aksharalp.mjs ADDED
@@ -0,0 +1,822 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * BhashaCore JS runtime — romanized Hindi / Hinglish -> Devanagari, fully offline (browser, Web Worker, Node >= 18).
3
+ * Zero dependencies. Pure-JS port of the reference Python runtime (bhc/translit2.py, bhc/xlit_q.py, bhc/lexlm.py,
4
+ * bhc/lextrie.py, bhc/lid.py, bhc/hinglish.py) over the exported asset formats:
5
+ * xlit.q4 | xlit.q8 | xlit.int8 char Transformer (pre-LN enc/dec), 4/8-bit grouped or per-row INT8 weights
6
+ * lex.bin Devanagari lexicon (LX4 alphabetical front-coded + 4-bit bands, or v2 bucketed)
7
+ * lm.bin pruned backoff word-bigram LM (varint CSR + 4-bit codebooks)
8
+ * params.json decoding params + reranker (linear w or MLP w1/b1/w2/b2; mu / sd)
9
+ * lid.int8 char-CNN + BiGRU token / sentence language ID (optional)
10
+ * loan.tsv, natural_hi.tsv Hinglish loanword lexicon, Natural-Hindi glossary (optional)
11
+ * Bundle layout: a small CORE (model + params.json) that works standalone, plus an optional dictionary pack in
12
+ * dict/ (lex.bin + lm.bin + params.json overriding the core params), fetched on first use, cached with the Cache API
13
+ * when available, and attached with engine.loadDict(); if it cannot be fetched the engine stays in core-only mode.
14
+ *
15
+ * AksharaLP by Sourojit Dhua — https://huggingface.co/sourojitdotcom/AksharaLP-1M-v2
16
+ * Copyright 2026 Sourojit Dhua. Licensed under the Apache License, Version 2.0; commercial use permitted.
17
+ * Attribution required (Apache-2.0 §4(d)): keep this notice and the NOTICE file in redistributions and derivatives:
18
+ * "Transliteration by AksharaLP (Sourojit Dhua) — https://huggingface.co/sourojitdotcom/AksharaLP-1M-v2"
19
+ */
20
+
21
+ // ------------------------------------------------------------------ vocab
22
+ const PAD = 0, BOS = 1, EOS = 2, UNK = 3, MAXLEN = 32, V = 132;
23
+ const NEG = -Infinity;
24
+ const encSrc = (w) => {
25
+ const s = w.toLowerCase().slice(0, MAXLEN - 1), ids = [];
26
+ for (const c of s) { const k = c.charCodeAt(0); ids.push(c.length === 1 && k >= 97 && k <= 122 ? k - 97 + 4 : UNK); }
27
+ ids.push(EOS); return ids;
28
+ };
29
+ const t2i = (ch) => { const k = ch.codePointAt(0); return k >= 0x900 && k < 0x980 ? k - 0x900 + 4 : -1; };
30
+ const encTgt = (w) => { const ids = []; for (const c of [...w].slice(0, MAXLEN - 2)) { const i = t2i(c); ids.push(i < 0 ? UNK : i); } ids.push(EOS); return ids; };
31
+ const decTgt = (ids) => { let s = ""; for (const i of ids) if (i > UNK) s += String.fromCharCode(0x900 + i - 4); return s; };
32
+
33
+ // ------------------------------------------------------------------ binary helpers
34
+ function f16(h) {
35
+ const s = (h & 0x8000) ? -1 : 1, e = (h >> 10) & 0x1f, f = h & 0x3ff;
36
+ if (e === 0) return s * f * 5.960464477539063e-8;
37
+ if (e === 31) return f ? NaN : s * Infinity;
38
+ return s * (1 + f / 1024) * Math.pow(2, e - 15);
39
+ }
40
+ function readHeader(buf) {
41
+ const u8 = new Uint8Array(buf), dv = new DataView(u8.buffer, u8.byteOffset, u8.byteLength);
42
+ const n = dv.getUint32(0, true);
43
+ const meta = JSON.parse(new TextDecoder().decode(u8.subarray(4, 4 + n)));
44
+ const data = u8.subarray(4 + n);
45
+ return { meta, data, dv: new DataView(data.buffer, data.byteOffset, data.byteLength) };
46
+ }
47
+ const prod = (a) => a.reduce((x, y) => x * y, 1);
48
+ const f16arr = (dv, off, n) => { const o = new Float32Array(n); for (let i = 0; i < n; i++) o[i] = f16(dv.getUint16(off + 2 * i, true)); return o; };
49
+
50
+ /** Parse xlit.int8 / xlit.q4 / xlit.q8 / lid.int8 into {cfg, extra, T: {name: {shape, data: Float32Array}}}. */
51
+ export function parseWeights(buf) {
52
+ const { meta, data, dv } = readHeader(buf); const T = {};
53
+ for (const t of meta.tensors) {
54
+ const [k, shape, kind, off] = t; const cnt = prod(shape); let w;
55
+ if (kind === "f16") w = f16arr(dv, off, cnt);
56
+ else if (kind === "i8") { // per-row symmetric INT8 (+ fp16 row scale)
57
+ const r = shape[0], c = cnt / r, sc = f16arr(dv, off + cnt, r); w = new Float32Array(cnt);
58
+ for (let i = 0; i < r; i++) for (let j = 0; j < c; j++) { const q = (data[off + i * c + j] << 24) >> 24; w[i * c + j] = q * sc[i]; }
59
+ } else { // q4 / q8: group-wise symmetric (G inputs per fp16 scale)
60
+ const bits = kind.length > 1 ? parseInt(kind.slice(1)) : meta.bits, G = meta.G, ln = t[4];
61
+ const [r, c] = shape, ng = Math.ceil(c / G), sc = f16arr(dv, off + ln, r * ng); w = new Float32Array(cnt);
62
+ for (let i = 0; i < cnt; i++) {
63
+ let q;
64
+ if (bits === 4) { const b = data[off + (i >> 1)]; q = ((i & 1) ? (b & 15) : (b >> 4)) - 8; }
65
+ else q = (data[off + i] << 24) >> 24;
66
+ const row = (i / c) | 0, col = i - row * c; w[i] = q * sc[row * ng + ((col / G) | 0)];
67
+ }
68
+ }
69
+ T[k] = { shape, data: w };
70
+ }
71
+ return { cfg: meta.cfg, extra: meta.extra || {}, T };
72
+ }
73
+
74
+ // ------------------------------------------------------------------ math kernels
75
+ function linear(x, xo, W, b, out, oo, nin, nout) { // out[oo..] = W x + b ; W row-major [nout, nin]
76
+ for (let o = 0; o < nout; o++) {
77
+ let s = b ? b[o] : 0; const r = o * nin;
78
+ for (let i = 0; i < nin; i++) s += W[r + i] * x[xo + i];
79
+ out[oo + o] = s;
80
+ }
81
+ }
82
+ function layerNorm(x, xo, g, b, out, oo, d) {
83
+ let m = 0; for (let i = 0; i < d; i++) m += x[xo + i]; m /= d;
84
+ let v = 0; for (let i = 0; i < d; i++) { const t = x[xo + i] - m; v += t * t; } v /= d;
85
+ const r = 1 / Math.sqrt(v + 1e-5);
86
+ for (let i = 0; i < d; i++) out[oo + i] = (x[xo + i] - m) * r * g[i] + b[i];
87
+ }
88
+ function erf(x) { // |err| < 1.2e-7 (Numerical Recipes erfc)
89
+ const z = Math.abs(x), t = 1 / (1 + 0.5 * z);
90
+ const r = t * Math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * (0.37409196 + t * (0.09678418 + t * (-0.18628806 +
91
+ t * (0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * (-0.82215223 + t * 0.17087277)))))))));
92
+ return x >= 0 ? 1 - r : r - 1;
93
+ }
94
+ const gelu = (x) => 0.5 * x * (1 + erf(x / Math.SQRT2));
95
+ const sigmoid = (x) => 1 / (1 + Math.exp(-x));
96
+
97
+ // ------------------------------------------------------------------ char Transformer
98
+ export class XlitModel {
99
+ constructor(w) {
100
+ const c = w.cfg; this.V = w.T["temb.weight"].shape[0];
101
+ const start = {hi:0x900,bn:0x980,te:0xc00,kn:0xc80,ta:0xb80}[c.vocab || "hi"];
102
+ this.target = Array.from({length:128}, (_,i)=>String.fromCharCode(start+i));
103
+ if (c.vocab === "bn") this.target.push("\u0964");
104
+ this.decTgt = ids => ids.filter(i=>i>3).map(i=>this.target[i-4]).join("");
105
+ this.d = c.d; this.h = c.h; this.dk = c.d / c.h; this.ff = c.ff; this.ne = c.enc; this.nd = c.dec;
106
+ const g = (k) => { const t = w.T[k]; if (!t) throw new Error("missing tensor " + k); return t.data; };
107
+ this.semb = g("semb.weight"); this.temb = g("temb.weight"); this.spos = g("spos.weight"); this.tpos = g("tpos.weight");
108
+ const att = (p) => ({ q: g(p + ".q.weight"), qb: g(p + ".q.bias"), kv: g(p + ".kv.weight"), kvb: g(p + ".kv.bias"), o: g(p + ".o.weight"), ob: g(p + ".o.bias") });
109
+ const ln = (p) => ({ g: g(p + ".weight"), b: g(p + ".bias") });
110
+ const ffn = (p) => ({ w1: g(p + ".ff.0.weight"), b1: g(p + ".ff.0.bias"), w2: g(p + ".ff.2.weight"), b2: g(p + ".ff.2.bias") });
111
+ this.enc = []; for (let i = 0; i < c.enc; i++) { const p = "enc." + i; this.enc.push({ n1: ln(p + ".n1"), att: att(p + ".att"), n2: ln(p + ".n2"), ff: ffn(p) }); }
112
+ this.dec = []; for (let i = 0; i < c.dec; i++) { const p = "dec." + i; this.dec.push({ n1: ln(p + ".n1"), sa: att(p + ".sa"), n2: ln(p + ".n2"), ca: att(p + ".ca"), n3: ln(p + ".n3"), ff: ffn(p) }); }
113
+ this.en = ln("en"); this.dn = ln("dn"); this.scale = Math.sqrt(c.d);
114
+ this.nParams = Object.values(w.T).reduce((a, t) => a + t.data.length, 0);
115
+ const d = this.d; this._y = new Float32Array(d); this._a = new Float32Array(d); this._f = new Float32Array(this.ff); this._q = new Float32Array(d); this._kv = new Float32Array(2 * d);
116
+ this._sc = new Float64Array(MAXLEN);
117
+ }
118
+ _ffn(L, x, xo, n) { // x[xo] += FF(LN(x))
119
+ const d = this.d, y = this._y, f = this._f, a = this._a;
120
+ layerNorm(x, xo, n.g, n.b, y, 0, d); linear(y, 0, L.w1, L.b1, f, 0, d, this.ff);
121
+ for (let i = 0; i < this.ff; i++) f[i] = gelu(f[i]);
122
+ linear(f, 0, L.w2, L.b2, a, 0, this.ff, d); for (let i = 0; i < d; i++) x[xo + i] += a[i];
123
+ }
124
+ /** attention of one query q (len d) over K/V rows (stride 2d in kvbuf: K at +0, V at +d) -> out (len d) */
125
+ _attend(q, kvbuf, kvo, n, out) {
126
+ const { h, dk, d } = this, sc = n <= MAXLEN ? this._sc : new Float64Array(n), inv = 1 / Math.sqrt(dk);
127
+ for (let hh = 0; hh < h; hh++) {
128
+ let mx = -Infinity;
129
+ for (let j = 0; j < n; j++) { let s = 0; const ko = kvo + j * 2 * d + hh * dk; for (let i = 0; i < dk; i++) s += q[hh * dk + i] * kvbuf[ko + i]; s *= inv; sc[j] = s; if (s > mx) mx = s; }
130
+ let z = 0; for (let j = 0; j < n; j++) { sc[j] = Math.exp(sc[j] - mx); z += sc[j]; }
131
+ for (let i = 0; i < dk; i++) { let s = 0; for (let j = 0; j < n; j++) s += sc[j] * kvbuf[kvo + j * 2 * d + d + hh * dk + i]; out[hh * dk + i] = s / z; }
132
+ }
133
+ }
134
+ /** encode one word -> {S, cross: per decoder layer K/V of memory (Float32Array S*2d)} */
135
+ encode(ids) {
136
+ const d = this.d, S = ids.length, x = new Float32Array(S * d), y = new Float32Array(S * d), kv = new Float32Array(S * 2 * d), att = new Float32Array(d), o = this._a;
137
+ for (let t = 0; t < S; t++) for (let i = 0; i < d; i++) x[t * d + i] = this.semb[ids[t] * d + i] * this.scale + this.spos[t * d + i];
138
+ for (const L of this.enc) {
139
+ for (let t = 0; t < S; t++) { layerNorm(x, t * d, L.n1.g, L.n1.b, y, t * d, d); linear(y, t * d, L.att.kv, L.att.kvb, kv, t * 2 * d, d, 2 * d); }
140
+ const nx = new Float32Array(x);
141
+ for (let t = 0; t < S; t++) {
142
+ linear(y, t * d, L.att.q, L.att.qb, this._q, 0, d, d); this._attend(this._q, kv, 0, S, att);
143
+ linear(att, 0, L.att.o, L.att.ob, o, 0, d, d); for (let i = 0; i < d; i++) nx[t * d + i] += o[i];
144
+ }
145
+ for (let t = 0; t < S; t++) this._ffn(L.ff, nx, t * d, L.n2);
146
+ x.set(nx);
147
+ }
148
+ const mem = new Float32Array(S * d); for (let t = 0; t < S; t++) layerNorm(x, t * d, this.en.g, this.en.b, mem, t * d, d);
149
+ const cross = this.dec.map((L) => { const c = new Float32Array(S * 2 * d); for (let t = 0; t < S; t++) linear(mem, t * d, L.ca.kv, L.ca.kvb, c, t * 2 * d, d, 2 * d); return c; });
150
+ return { S, cross };
151
+ }
152
+ newCache(K) { const d = this.d; return this.dec.map(() => new Float32Array(K * MAXLEN * 2 * d)); }
153
+ /** one decoder step for beam rows `rows` (indices into cache) with tokens tok[row] at position t -> logprobs[row] (Float64Array V) */
154
+ step(enc, cache, rows, tok, t, out) {
155
+ const V = this.V, d = this.d, x = new Float32Array(d), y = this._y, att = new Float32Array(d), o = this._a;
156
+ for (const r of rows) {
157
+ for (let i = 0; i < d; i++) x[i] = this.temb[tok[r] * d + i] * this.scale + this.tpos[t * d + i];
158
+ for (let l = 0; l < this.nd; l++) {
159
+ const L = this.dec[l], C = cache[l], base = r * MAXLEN * 2 * d;
160
+ layerNorm(x, 0, L.n1.g, L.n1.b, y, 0, d);
161
+ linear(y, 0, L.sa.kv, L.sa.kvb, C, base + t * 2 * d, d, 2 * d);
162
+ linear(y, 0, L.sa.q, L.sa.qb, this._q, 0, d, d); this._attend(this._q, C, base, t + 1, att);
163
+ linear(att, 0, L.sa.o, L.sa.ob, o, 0, d, d); for (let i = 0; i < d; i++) x[i] += o[i];
164
+ layerNorm(x, 0, L.n2.g, L.n2.b, y, 0, d);
165
+ linear(y, 0, L.ca.q, L.ca.qb, this._q, 0, d, d); this._attend(this._q, enc.cross[l], 0, enc.S, att);
166
+ linear(att, 0, L.ca.o, L.ca.ob, o, 0, d, d); for (let i = 0; i < d; i++) x[i] += o[i];
167
+ this._ffn(L.ff, x, 0, L.n3);
168
+ }
169
+ layerNorm(x, 0, this.dn.g, this.dn.b, y, 0, d);
170
+ const lp = out[r]; let mx = -Infinity;
171
+ for (let v = 0; v < V; v++) { let s = 0; for (let i = 0; i < d; i++) s += y[i] * this.temb[v * d + i]; lp[v] = s; if (s > mx) mx = s; }
172
+ let z = 0; for (let v = 0; v < V; v++) z += Math.exp(lp[v] - mx); const lz = mx + Math.log(z);
173
+ for (let v = 0; v < V; v++) lp[v] -= lz;
174
+ }
175
+ }
176
+ /** beam search for one word (identical procedure to bhc.xlit_model.beam_search_fast for one batch row) */
177
+ beam(enc, S, { beam: K = 8, nbest = 8, trie = null } = {}) {
178
+ const V = this.V, d = this.d, cache = this.newCache(K), out = Array.from({ length: K }, () => new Float64Array(V));
179
+ let score = new Array(K).fill(NEG); score[0] = 0; let tok = new Array(K).fill(BOS); let hist = Array.from({ length: K }, () => []);
180
+ let nodes = trie ? new Array(K).fill(0) : null; const done = []; const L = Math.min(MAXLEN - 1, S + 8);
181
+ const tmp = new Float32Array(MAXLEN * 2 * d);
182
+ for (let t = 0; t < L; t++) {
183
+ const rows = []; for (let k = 0; k < K; k++) if (score[k] > NEG) rows.push(k);
184
+ this.step(enc, cache, rows, tok, t, out);
185
+ const cand = []; // (total, beam, v)
186
+ for (const k of rows) {
187
+ const lp = out[k]; let allow = null;
188
+ if (trie) { const n = nodes[k]; if (n === null) continue; allow = trie.allowed(n); }
189
+ if (allow) { for (const v of allow) if (v !== PAD && v !== BOS && v !== UNK) cand.push([score[k] + lp[v], k, v]); }
190
+ else { cand.push([score[k] + lp[EOS], k, EOS]); for (let v = 4; v < V; v++) cand.push([score[k] + lp[v], k, v]); }
191
+ }
192
+ cand.sort((a, b) => b[0] - a[0] || (a[1] * V + a[2]) - (b[1] * V + b[2]));
193
+ const top = cand.slice(0, 2 * K);
194
+ const ns = new Array(K).fill(NEG), nt = new Array(K).fill(EOS), par = new Array(K).fill(0), nn = trie ? new Array(K).fill(null) : null;
195
+ let j = 0;
196
+ for (const [sc, b, v] of top) {
197
+ if (!(sc > NEG)) continue;
198
+ if (v === EOS) { if (done.length < nbest) done.push([this.decTgt(hist[b]), sc]); continue; }
199
+ if (j < K) { ns[j] = sc; nt[j] = v; par[j] = b; if (trie) nn[j] = trie.child(nodes[b], v); j++; }
200
+ }
201
+ let alive = true;
202
+ if (done.length >= nbest && (j === 0 || ns[0] < Math.min(...done.map((x) => x[1])))) alive = false;
203
+ else if (!j) alive = false;
204
+ if (!alive) break;
205
+ for (const C of cache) { // reorder self-attention caches by parent beam
206
+ const n = (t + 1) * 2 * d, olds = [];
207
+ for (let k = 0; k < j; k++) { const src = par[k] * MAXLEN * 2 * d; olds.push(C.slice(src, src + n)); }
208
+ for (let k = 0; k < j; k++) C.set(olds[k], k * MAXLEN * 2 * d);
209
+ }
210
+ hist = Array.from({ length: K }, (_, k) => (k < j ? hist[par[k]].concat([nt[k]]) : []));
211
+ score = ns; tok = nt; nodes = nn;
212
+ }
213
+ const best = new Map();
214
+ for (const [s, sc] of done) if (!best.has(s) || sc > best.get(s)) best.set(s, sc);
215
+ return [...best.entries()].sort((a, b) => b[1] - a[1]).slice(0, nbest);
216
+ }
217
+ /** teacher-forced ln P(tgt | src) (as bhc.xlit_model.score_pairs) */
218
+ scorePair(enc, tgt) {
219
+ const V = this.V, ids = [...tgt].slice(0,MAXLEN-2).map(c=>{const i=this.target.indexOf(c);return i<0?UNK:i+4;}).concat([EOS]), cache = this.newCache(1), out = [new Float64Array(V)]; let tok = [BOS], s = 0;
220
+ for (let t = 0; t < ids.length; t++) { this.step(enc, cache, [0], tok, t, out); s += out[0][ids[t]]; tok = [ids[t]]; }
221
+ return s;
222
+ }
223
+ }
224
+
225
+ // ------------------------------------------------------------------ lexicon + bigram LM
226
+ function readVarints(b, n, pos) {
227
+ const out = new Uint32Array(n);
228
+ for (let i = 0; i < n; i++) { let v = 0, sh = 0, c; do { c = b[pos++]; v += (c & 0x7f) * 2 ** sh; sh += 7; } while (c >= 0x80); out[i] = v; }
229
+ return [out, pos];
230
+ }
231
+ const unpack4 = (b, n) => { const o = new Uint8Array(n); for (let i = 0; i < n; i++) o[i] = (i & 1) ? b[i >> 1] & 15 : b[i >> 1] >> 4; return o; };
232
+
233
+ export class LexLM {
234
+ constructor(lexBuf, lmBuf) {
235
+ if (!lexBuf) { // core-only: empty lexicon, no LM
236
+ this.words = []; this.sortedWords = []; this.uni = new Float32Array(0); this.N = 0; this.unk = 0; this.alpha = true; this.hasLM = false; return;
237
+ }
238
+ const b = new Uint8Array(lexBuf), dv = new DataView(b.buffer, b.byteOffset, b.byteLength); const words = [];
239
+ const decodeFC = (P, S, nWords) => { let prev = [], j = 0, pi = 0;
240
+ while (words.length < nWords) { const k = P ? P[pi++] : S[j++]; const cur = prev.slice(0, k);
241
+ for (;;) { const c = S[j++], ch = c & 0x7f; if (ch !== 0x7f) cur.push(ch); if (c & 0x80) break; }
242
+ words.push(String.fromCharCode(...cur.map((x) => 0x900 + x))); prev = cur; } };
243
+ if (b[0] === 0x4c && b[1] === 0x58 && b[2] === 0x34 && b[3] === 0) { // "LX4\0"
244
+ const N = dv.getUint32(4, true), nfc = dv.getUint32(8, true), NB = dv.getUint32(16, true); this.unk = dv.getFloat32(12, true);
245
+ let pos = 20; const ucb = new Float32Array(NB); for (let i = 0; i < NB; i++) ucb[i] = dv.getFloat32(pos + 4 * i, true); pos += 4 * NB;
246
+ const band = unpack4(b.subarray(pos, pos + ((N + 1) >> 1)), N); pos += (N + 1) >> 1;
247
+ this.uni = new Float32Array(N); for (let i = 0; i < N; i++) this.uni[i] = ucb[band[i]];
248
+ decodeFC(b.subarray(pos, pos + N), b.subarray(pos + N, pos + N + nfc), N); this.N = N; this.alpha = true;
249
+ } else { // v2 (frequency-bucketed)
250
+ const N = dv.getUint32(0, true), nb = dv.getUint32(4, true), nfc = dv.getUint32(8, true); this.unk = dv.getFloat32(12, true);
251
+ let [bn, pos] = readVarints(b, nb, 16); const blp = []; for (let i = 0; i < nb; i++) blp.push(f16(dv.getUint16(pos + 2 * i, true))); pos += 2 * nb;
252
+ this.uni = new Float32Array(N); let w = 0; for (let i = 0; i < nb; i++) for (let k = 0; k < bn[i]; k++) this.uni[w++] = blp[i];
253
+ decodeFC(null, b.subarray(pos, pos + nfc), N); this.N = N; this.alpha = false;
254
+ }
255
+ this.words = words;
256
+ let sorted = this.alpha; for (let i = 1; sorted && i < words.length; i++) if (!(words[i - 1] < words[i])) sorted = false;
257
+ if (!sorted) { this.map = new Map(); words.forEach((w, i) => this.map.set(w, i)); }
258
+ this.sortedWords = sorted ? words : [...new Set(words)].sort();
259
+ this.hasLM = false;
260
+ if (lmBuf) this._loadLM(new Uint8Array(lmBuf));
261
+ }
262
+ id(w) {
263
+ if (this.map) { const i = this.map.get(w); return i === undefined ? null : i; }
264
+ let lo = 0, hi = this.words.length; while (lo < hi) { const m = (lo + hi) >> 1; if (this.words[m] < w) lo = m + 1; else hi = m; }
265
+ return lo < this.words.length && this.words[lo] === w ? lo : null;
266
+ }
267
+ _loadLM(b) {
268
+ const dv = new DataView(b.buffer, b.byteOffset, b.byteLength), N = this.N;
269
+ const nbg = dv.getUint32(4, true), nh = dv.getUint32(16, true); let pos = 20;
270
+ const cb = [], cbb = []; for (let i = 0; i < 16; i++) cb.push(dv.getFloat32(pos + 4 * i, true)); pos += 64;
271
+ for (let i = 0; i < 16; i++) cbb.push(dv.getFloat32(pos + 4 * i, true)); pos += 64;
272
+ let cnt, delta; [cnt, pos] = readVarints(b, N + 1, pos); [delta, pos] = readVarints(b, nbg, pos);
273
+ const q = unpack4(b.subarray(pos, pos + ((nbg + 1) >> 1)), nbg); pos += (nbg + 1) >> 1;
274
+ const qb = unpack4(b.subarray(pos, pos + ((nh + 1) >> 1)), nh);
275
+ this.off = new Uint32Array(N + 2); this.succ = new Uint32Array(nbg); this.bq = q; this.cb = Float32Array.from(cb);
276
+ this.bow = new Float32Array(N + 1); let i = 0, hi = 0;
277
+ for (let h = 0; h <= N; h++) {
278
+ this.off[h] = i; const n = cnt[h]; if (!n) continue;
279
+ this.bow[h] = cbb[qb[hi++]]; let acc = -1;
280
+ for (let k = 0; k < n; k++) { acc = acc < 0 ? delta[i] : acc + delta[i] + 1; this.succ[i++] = acc; }
281
+ }
282
+ this.off[N + 1] = i; this.hasLM = nbg > 0;
283
+ }
284
+ unigram(wid) { return wid === null ? this.unk : wid === this.N ? Math.log(0.04) : this.uni[wid]; }
285
+ /** ln P(wid | prev); prev null = sentence start; wid null = OOV; wid N = </s> */
286
+ logp(prev, wid) {
287
+ const h = prev === null ? this.N : prev;
288
+ if (wid !== null && this.hasLM) {
289
+ let lo = this.off[h], hi = this.off[h + 1];
290
+ while (lo < hi) { const m = (lo + hi) >> 1; if (this.succ[m] < wid) lo = m + 1; else hi = m; }
291
+ if (lo < this.off[h + 1] && this.succ[lo] === wid) return this.cb[this.bq[lo]];
292
+ }
293
+ return (this.hasLM ? this.bow[h] : 0) + this.unigram(wid);
294
+ }
295
+ }
296
+
297
+ /** lazy lexicon trie over the sorted word list (same node semantics as bhc/lextrie.py) */
298
+ class LazyTrie {
299
+ constructor(sortedWords) { this.w = sortedWords; this.nodes = [[0, sortedWords.length, 0]]; this.ids = new Map([["0,0," + sortedWords.length, 0]]); this.cache = new Map(); this.allow = new Map(); }
300
+ _bisect(x, lo, hi) { const w = this.w; while (lo < hi) { const m = (lo + hi) >> 1; if (w[m] < x) lo = m + 1; else hi = m; } return lo; }
301
+ children(n) {
302
+ let c = this.cache.get(n); if (c) return c;
303
+ const [lo, hi, d] = this.nodes[n], w = this.w; c = new Map(); let pos = lo;
304
+ if (pos < hi && w[pos].length === d) pos++;
305
+ while (pos < hi) {
306
+ const ch = w[pos].charCodeAt(d), pre = w[pos].slice(0, d);
307
+ const end = this._bisect(pre + String.fromCharCode(ch + 1), pos, hi);
308
+ const key = pos + "," + (d + 1) + "," + end; let nid = this.ids.get(key);
309
+ if (nid === undefined) { nid = this.nodes.length; this.nodes.push([pos, end, d + 1]); this.ids.set(key, nid); }
310
+ c.set(ch - 0x900 + 4, nid); pos = end;
311
+ }
312
+ if (this.cache.size > 400000) { this.cache.clear(); this.allow.clear(); }
313
+ this.cache.set(n, c); return c;
314
+ }
315
+ final(n) { const [lo, , d] = this.nodes[n]; return lo < this.w.length && this.w[lo].length === d; }
316
+ allowed(n) { let a = this.allow.get(n); if (!a) { a = [...this.children(n).keys()]; if (this.final(n)) a.push(EOS); this.allow.set(n, a); } return a; }
317
+ child(n, v) { return this.children(n).get(v); }
318
+ }
319
+
320
+ // ------------------------------------------------------------------ language ID (char-CNN + BiGRU)
321
+ const LID_CH = {}; [..."abcdefghijklmnopqrstuvwxyz'"].forEach((c, i) => (LID_CH[c] = i + 3));
322
+ const WMAX = 16, MODES3 = ["english", "romanized_hindi", "hinglish"];
323
+ const RE_LATIN_APOS = /^[A-Za-z]+(?:'[A-Za-z]+)?$/, RE_DEVA = /^[\u0900-\u0963\u0971-\u097F]+$/;
324
+ function whash(w, n) { let h = 2166136261; for (const ch of new TextEncoder().encode(w.toLowerCase())) h = Math.imul(h ^ ch, 16777619) >>> 0; return h % n; }
325
+
326
+ export class LID {
327
+ constructor(w) {
328
+ this.cfg = w.cfg; this.x = w.extra || {}; const g = (k) => w.T[k] && w.T[k].data;
329
+ Object.assign(this, { ce: g("ce.weight"), c1: g("c1.weight"), c1b: g("c1.bias"), c2: g("c2.weight"), c2b: g("c2.bias"), wemb: g("wemb.weight"),
330
+ wih: g("gru.weight_ih_l0"), whh: g("gru.weight_hh_l0"), bih: g("gru.bias_ih_l0"), bhh: g("gru.bias_hh_l0"),
331
+ wihR: g("gru.weight_ih_l0_reverse"), whhR: g("gru.weight_hh_l0_reverse"), bihR: g("gru.bias_ih_l0_reverse"), bhhR: g("gru.bias_hh_l0_reverse"),
332
+ tw: g("tok.weight"), tb: g("tok.bias"), s0: g("sent.0.weight"), s0b: g("sent.0.bias"), s2: g("sent.2.weight"), s2b: g("sent.2.bias") });
333
+ this.nParams = Object.values(w.T).reduce((a, t) => a + t.data.length, 0);
334
+ }
335
+ _wvec(word) {
336
+ const { ce: E, cf } = this.cfg; const ids = new Array(WMAX).fill(0); const w = [...word.toLowerCase()].slice(0, WMAX - 2);
337
+ ids[0] = 1; w.forEach((c, i) => (ids[i + 1] = LID_CH[c] ?? 2)); ids[w.length + 1] = 2;
338
+ const x = new Float32Array(E * WMAX); for (let t = 0; t < WMAX; t++) for (let i = 0; i < E; i++) x[i * WMAX + t] = this.ce[ids[t] * E + i];
339
+ const conv = (inp, cin, W, b) => { const o = new Float32Array(cf * WMAX);
340
+ for (let oc = 0; oc < cf; oc++) for (let t = 0; t < WMAX; t++) { let s = b[oc];
341
+ for (let ic = 0; ic < cin; ic++) for (let k = 0; k < 3; k++) { const tt = t + k - 1; if (tt >= 0 && tt < WMAX) s += W[(oc * cin + ic) * 3 + k] * inp[ic * WMAX + tt]; }
342
+ o[oc * WMAX + t] = s > 0 ? s : 0; } return o; };
343
+ const h1 = conv(x, E, this.c1, this.c1b), h2 = conv(h1, cf, this.c2, this.c2b);
344
+ const wh = this.cfg.wh, wd = this.cfg.wd, v = new Float32Array(cf + (wh ? wd : 0));
345
+ for (let oc = 0; oc < cf; oc++) { let m = -Infinity; for (let t = 0; t < WMAX; t++) { const val = ids[t] === 0 ? -1e4 : h2[oc * WMAX + t] + h1[oc * WMAX + t]; if (val > m) m = val; } v[oc] = m; }
346
+ if (wh) { const r = whash(word, wh); for (let i = 0; i < wd; i++) v[cf + i] = this.wemb[r * wd + i]; }
347
+ return v;
348
+ }
349
+ _gru(xs, Wih, Whh, bih, bhh, rev) {
350
+ const H = this.cfg.hid, nin = xs[0].length, T = xs.length, out = new Array(T); let h = new Float32Array(H);
351
+ const gi = new Float32Array(3 * H), gh = new Float32Array(3 * H);
352
+ for (let s = 0; s < T; s++) {
353
+ const t = rev ? T - 1 - s : s; linear(xs[t], 0, Wih, bih, gi, 0, nin, 3 * H); linear(h, 0, Whh, bhh, gh, 0, H, 3 * H);
354
+ const nh = new Float32Array(H);
355
+ for (let i = 0; i < H; i++) { const r = sigmoid(gi[i] + gh[i]), z = sigmoid(gi[H + i] + gh[H + i]), n = Math.tanh(gi[2 * H + i] + r * gh[2 * H + i]); nh[i] = (1 - z) * n + z * h[i]; }
356
+ h = nh; out[t] = h;
357
+ }
358
+ return out;
359
+ }
360
+ /** Latin words -> {pen: P(EN) per word, ps: [english, romanized_hindi, hinglish]} */
361
+ run(words) {
362
+ if (!words.length) return { pen: [], ps: null };
363
+ const H = this.cfg.hid, xs = words.map((w) => this._wvec(w)), T = xs.length, nin = xs[0].length;
364
+ const f = this._gru(xs, this.wih, this.whh, this.bih, this.bhh, false), r = this._gru(xs, this.wihR, this.whhR, this.bihR, this.bhhR, true);
365
+ const pen = [], hm = new Float32Array(2 * H), hx = new Float32Array(2 * H).fill(-1e4), z = new Float32Array(2 * H + nin), lo = new Float32Array(2);
366
+ for (let t = 0; t < T; t++) {
367
+ for (let i = 0; i < H; i++) { z[i] = f[t][i]; z[H + i] = r[t][i]; } z.set(xs[t], 2 * H);
368
+ linear(z, 0, this.tw, this.tb, lo, 0, 2 * H + nin, 2); const m = Math.max(lo[0], lo[1]), e0 = Math.exp(lo[0] - m), e1 = Math.exp(lo[1] - m); pen.push(e0 / (e0 + e1));
369
+ for (let i = 0; i < 2 * H; i++) { hm[i] += z[i] / T; if (z[i] > hx[i]) hx[i] = z[i]; }
370
+ }
371
+ const sin = new Float32Array(4 * H + 1); sin.set(hm, 0); sin.set(hx, 2 * H); sin[4 * H] = pen.reduce((a, b) => a + b, 0) / T;
372
+ const h1 = new Float32Array(32); linear(sin, 0, this.s0, this.s0b, h1, 0, 4 * H + 1, 32); for (let i = 0; i < 32; i++) h1[i] = Math.max(0, h1[i]);
373
+ const s = new Float32Array(3); linear(h1, 0, this.s2, this.s2b, s, 0, 32, 3); const m = Math.max(...s); const e = [...s].map((v) => Math.exp(v - m)); const Z = e.reduce((a, b) => a + b, 0);
374
+ return { pen, ps: e.map((v) => v / Z) };
375
+ }
376
+ tag(tokens) {
377
+ const lat = tokens.filter((t) => RE_LATIN_APOS.test(t)); const { pen } = this.run(lat); let k = 0; const th = this.x.tok_th ?? 0.5;
378
+ return tokens.map((t) => (RE_DEVA.test(t) ? "DEV" : RE_LATIN_APOS.test(t) ? (pen[k++] >= th ? "EN" : "HI") : "OTHER"));
379
+ }
380
+ mode(tokens) {
381
+ const nd = tokens.filter((t) => RE_DEVA.test(t)).length, nl = tokens.filter((t) => RE_LATIN_APOS.test(t)).length;
382
+ if (nd && !nl) return "devanagari";
383
+ if (nd && nl) return nd / (nd + nl) >= (this.x.dev_frac ?? 0.5) ? "devanagari" : "mixed_script";
384
+ if (!nl) return "english";
385
+ const { ps } = this.run(tokens.filter((t) => RE_LATIN_APOS.test(t))); const bias = this.x.mode_bias || [0, 0, 0];
386
+ let best = 0, bv = -Infinity; ps.forEach((p, i) => { const v = Math.log(p + 1e-9) + bias[i]; if (v > bv) { bv = v; best = i; } });
387
+ return MODES3[best];
388
+ }
389
+ }
390
+
391
+ // ------------------------------------------------------------------ tokenizer + policy tables
392
+ export const TOKEN_RE = /(https?:\/\/\S+|www\.\S+|[\p{L}\p{N}_.+-]+@[\p{L}\p{N}_-]+\.[\p{L}\p{N}_.]+|[@#][\p{L}\p{N}_]+|[A-Za-z]+(?:'[A-Za-z]+)?|[\u0900-\u0963\u0971-\u097F]+|[0-9\u0966-\u096F]+(?:[.,:][0-9\u0966-\u096F]+)*|[\u0964\u0965]|[^\sA-Za-z0-9\u0900-\u097F])/gu;
393
+ const RE_LATIN = /^[A-Za-z]+$/;
394
+ const RE_ACRO = /^[b-df-hj-np-tv-z]{2,7}$/;
395
+ const LETTER = Object.fromEntries([..."abcdefghijklmnopqrstuvwxyz"].map((c, i) => [c, ("\u090f \u092c\u0940 \u0938\u0940 \u0921\u0940 \u0908 \u090f\u092b \u091c\u0940 \u090f\u091a \u0906\u0908 \u091c\u0947 \u0915\u0947 \u090f\u0932 \u090f\u092e \u090f\u0928 " +
396
+ "\u0913 \u092a\u0940 \u0915\u094d\u092f\u0942 \u0906\u0930 \u090f\u0938 \u091f\u0940 \u092f\u0942 \u0935\u0940 \u0921\u092c\u094d\u0932\u094d\u092f\u0942 \u090f\u0915\u094d\u0938 \u0935\u093e\u0908 \u091c\u0947\u0921").split(" ")[i]]));
397
+ // Hinglish policy letter names (bhc/hinglish baseline): f -> एफ़, z -> ज़ेड
398
+ const LETTERS_H = { ...LETTER, f: "\u090f\u092b\u093c", z: "\u091c\u093c\u0947\u0921" };
399
+ const VAR_SWAPS = [["\u0949", "\u094b"], ["\u094b", "\u0949"], ["\u093e", "\u0949"], ["\u0949", "\u093e"], ["\u0903", ""], ["\u092b\u093c", "\u092b"], ["\u092b", "\u092b\u093c"],
400
+ ["\u091c\u093c", "\u091c"], ["\u091c", "\u091c\u093c"], ["\u0901", "\u0902"], ["\u0902", "\u0901"]];
401
+ export const FEATS = ["sc", "scn", "inlex", "uni", "free0", "infree", "incon", "aw", "visarga", "nukta", "chandra", "acro", "variant", "frank",
402
+ "skel", "skmatch", "lev", "sc_rel", "scn_rel", "uni_rel", "lenr", "visr", "hfin", "acro_nv",
403
+ "ck1", "ck2", "ckr", "abbr", "abbr_r", "novow"]; // v2 casual features (append-only; older params use the first mu.length)
404
+ const BRANDS = new Set(`whatsapp google meet paytm amazon flipkart youtube instagram facebook gmail excel uber ola swiggy zomato
405
+ netflix linkedin github python java jira slack zoom iphone android windows microsoft apple samsung twitter telegram
406
+ chrome firefox safari office365 outlook teams spotify hotstar jio airtel phonepe gpay`.split(/\s+/));
407
+ const KEEP_RE = /^(https?:\/\/|www\.|[@#])|@.+\.|_|\d.*[A-Za-z]|[A-Za-z].*\d|^[a-z]+[A-Z]/;
408
+ const isUpper = (t) => /[A-Z]/.test(t) && t === t.toUpperCase();
409
+ const DEFAULTS = { beam: 8, nbest: 8, cbeam: 8, variants: true, acro: true, w: null, mu: null, sd: null, w_lm: 0.8, oov_pen_s: 4.0, w_ch_s: 1.0, use_lm: true,
410
+ skel: false, skel_k: 200, skel_m: 16, skel_wu: 0.03, visr: false, acro_v: false,
411
+ casual: false, cas_top: 300000, cas_n1: 6, cas_n2: 4, abbr: false, squeeze: 0, k1gate: 99, k2gate: 99 };
412
+ const parseTSV = (txt) => { const m = new Map(); for (const l of txt.split("\n")) { const i = l.indexOf("\t"); if (i > 0) m.set(l.slice(0, i).trim(), l.slice(i + 1).trim()); } return m; };
413
+ const now = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
414
+
415
+ // Sizes are checked against the manifest, never against Content-Length (a CDN redirect or an auth error page has its
416
+ // own, unrelated length); a wrong-size body (e.g. an HTML/plain-text error page served with 200) is rejected.
417
+ const checkSize = (b, expect, url) => { if (expect && b.length !== expect) throw new Error(`fetch ${url}: got ${b.length} bytes, manifest says ${expect}`); return b; };
418
+ async function fetchBytes(base, name, fetchImpl, expect = 0) {
419
+ const url = base.endsWith("/") ? base + name : base + "/" + name;
420
+ const r = await fetchImpl(url); if (!r.ok) throw new Error(`fetch ${url}: ${r.status}`);
421
+ if (name.endsWith(".gz") && typeof DecompressionStream !== "undefined") return new Uint8Array(await new Response(r.body.pipeThrough(new DecompressionStream("gzip"))).arrayBuffer());
422
+ return checkSize(new Uint8Array(await r.arrayBuffer()), expect, url);
423
+ }
424
+
425
+ // ------------------------------------------------------------------ consonant-skeleton index (bhc/skel.py; tables generated from it)
426
+ const SK_DEV = {"\u0915": "K", "\u0916": "K", "\u0917": "G", "\u0918": "G", "\u091a": "C", "\u091b": "C", "\u091c": "J", "\u091d": "J", "\u091f": "T", "\u0920": "T", "\u0924": "T", "\u0925": "T", "\u0921": "D", "\u0922": "D", "\u0926": "D", "\u0927": "D", "\u0919": "N", "\u091e": "N", "\u0923": "N", "\u0928": "N", "\u0929": "N", "\u092e": "N", "\u0902": "N", "\u0901": "N", "\u092a": "P", "\u092b": "P", "\u092c": "B", "\u092d": "B", "\u0930": "R", "\u0931": "R", "\u090b": "R", "\u0943": "R", "\u0932": "L", "\u0933": "L", "\u0935": "V", "\u0936": "S", "\u0937": "S", "\u0938": "S", "\u0939": "H", "\u0903": "H"};
427
+ const SK_ROM1 = {"k": "K", "q": "K", "g": "G", "j": "J", "z": "J", "t": "T", "d": "D", "n": "N", "m": "N", "p": "P", "f": "P", "b": "B", "r": "R", "l": "L", "v": "V", "w": "V", "s": "S", "h": "H"};
428
+ const SK_ASP = "bcdgjkpstz";
429
+ const DR_C = {"\u0915": "k", "\u0916": "kh", "\u0917": "g", "\u0918": "gh", "\u0919": "n", "\u091a": "ch", "\u091b": "chh", "\u091c": "j", "\u091d": "jh", "\u091e": "n", "\u091f": "t", "\u0920": "th", "\u0921": "d", "\u0922": "dh", "\u0923": "n", "\u0924": "t", "\u0925": "th", "\u0926": "d", "\u0927": "dh", "\u0928": "n", "\u092a": "p", "\u092b": "ph", "\u092c": "b", "\u092d": "bh", "\u092e": "m", "\u092f": "y", "\u0930": "r", "\u0932": "l", "\u0935": "v", "\u0936": "sh", "\u0937": "sh", "\u0938": "s", "\u0939": "h", "\u0933": "l"};
430
+ const DR_V = {"\u0905": "a", "\u0906": "aa", "\u0907": "i", "\u0908": "ee", "\u0909": "u", "\u090a": "oo", "\u090b": "ri", "\u090f": "e", "\u0910": "ai", "\u0913": "o", "\u0914": "au", "\u0911": "o"};
431
+ const DR_M = {"\u093e": "a", "\u093f": "i", "\u0940": "ee", "\u0941": "u", "\u0942": "oo", "\u0943": "ri", "\u0947": "e", "\u0948": "ai", "\u094b": "o", "\u094c": "au", "\u0949": "o", "\u0945": "e"};
432
+ const NUKTA_ROM = { k: "q", kh: "kh", g: "g", j: "z", ph: "f", d: "r", dh: "rh" };
433
+ export function devSkel(w) { let o = ""; for (const ch of w) { const c = SK_DEV[ch]; if (c && o[o.length - 1] !== c) o += c; } return o; }
434
+ export function romSkels(w, maxk = 4) {
435
+ w = w.toLowerCase(); let outs = [""]; let i = 0; const n = w.length;
436
+ while (i < n) {
437
+ const ch = w[i]; let step = 1, opts;
438
+ if ((ch === "t" || ch === "s") && w.startsWith("ion", i + 1)) { opts = ch === "t" ? ["SN", "TN"] : ["SN"]; step = 4; }
439
+ else if (ch === "c") {
440
+ const nx = i + 1 < n ? w[i + 1] : "";
441
+ if (nx === "h") { opts = ["C", "K"]; step = 2; while (i + step < n && (w[i + step] === "c" || w[i + step] === "h")) step++; }
442
+ else if (nx === "e" || nx === "i" || nx === "y") opts = ["S", "K"];
443
+ else if (nx === "k") { opts = ["K"]; step = 2; }
444
+ else opts = ["K", "S"];
445
+ } else if (ch === "x") opts = ["KS"];
446
+ else if (SK_ASP.includes(ch) && i + 1 < n && w[i + 1] === "h") { opts = [SK_ROM1[ch] || ""]; step = 2; while (i + step < n && w[i + step] === "h") step++; }
447
+ else opts = [SK_ROM1[ch] || ""];
448
+ const nw = [];
449
+ for (const o of outs) for (const q of opts) { let s = o; for (const c of q) if (!s || s[s.length - 1] !== c) s += c; nw.push(s); }
450
+ outs = [...new Set(nw)].slice(0, maxk); i += step;
451
+ }
452
+ return outs;
453
+ }
454
+ export function devRom(w) {
455
+ const out = [], a = [...w], L = a.length;
456
+ for (let j = 0; j < L; j++) {
457
+ const ch = a[j];
458
+ if (DR_C[ch] !== undefined) {
459
+ out.push(DR_C[ch]); let nx = j + 1 < L ? a[j + 1] : "";
460
+ if (nx === "\u093c") nx = j + 2 < L ? a[j + 2] : "";
461
+ if (nx && DR_M[nx] === undefined && nx !== "\u094d" && j + 1 < L - 1) out.push("a");
462
+ } else if (DR_V[ch] !== undefined) out.push(DR_V[ch]);
463
+ else if (DR_M[ch] !== undefined) out.push(DR_M[ch]);
464
+ else if (ch === "\u0902" || ch === "\u0901") out.push("n");
465
+ else if (ch === "\u0903") out.push("h");
466
+ else if (ch === "\u093c" && out.length) { const l = out[out.length - 1]; out[out.length - 1] = NUKTA_ROM[l] ?? l; }
467
+ }
468
+ return out.join("");
469
+ }
470
+ export function lev(a, b) {
471
+ if (a.length < b.length) [a, b] = [b, a];
472
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
473
+ for (let i = 1; i <= a.length; i++) {
474
+ const cur = [i];
475
+ for (let j = 1; j <= b.length; j++) cur.push(Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] !== b[j - 1] ? 1 : 0)));
476
+ prev = cur;
477
+ }
478
+ return prev[b.length];
479
+ }
480
+ class SkelIndex {
481
+ constructor(lm) {
482
+ const b = new Map(), N = lm.words.length;
483
+ for (let i = 0; i < N; i++) { const k = devSkel(lm.words[i]); if (k) { let v = b.get(k); if (!v) b.set(k, (v = [])); v.push(i); } }
484
+ for (const v of b.values()) v.sort((x, y) => (lm.uni[y] - lm.uni[x]) || (x - y));
485
+ this.b = b;
486
+ }
487
+ lookup(roman, topk = 24) {
488
+ const seen = [], S = new Set();
489
+ for (const k of romSkels(roman)) { const v = this.b.get(k); if (!v) continue; for (const i of v.slice(0, topk)) if (!S.has(i)) { S.add(i); seen.push(i); } }
490
+ return seen;
491
+ }
492
+ }
493
+ // ------------------------------------------------------------------ casual / chat-romanization keys (bhc/casual.py; keep 1:1)
494
+ const CAS_DC = {"\u0915": "k", "\u0916": "k", "\u0917": "g", "\u0918": "g", "\u091a": "c", "\u091b": "c", "\u091c": "j", "\u091d": "j", "\u091f": "t", "\u0920": "t", "\u0924": "t", "\u0925": "t", "\u0921": "d", "\u0922": "d", "\u0926": "d", "\u0927": "d", "\u0923": "n", "\u0928": "n", "\u0929": "n", "\u092a": "p", "\u092b": "p", "\u092c": "b", "\u092d": "b", "\u092e": "m", "\u092f": "y", "\u0930": "r", "\u0931": "r", "\u0932": "l", "\u0933": "l", "\u0935": "v", "\u0936": "s", "\u0937": "s", "\u0938": "s", "\u0939": "h", "\u0958": "k", "\u0959": "k", "\u095a": "g", "\u095b": "j", "\u095c": "R", "\u095d": "R", "\u095e": "p", "\u095f": "y"};
495
+ const CAS_DV = {"\u0905": "a", "\u0906": "a", "\u093e": "a", "\u0907": "i", "\u0908": "i", "\u093f": "i", "\u0940": "i", "\u0909": "u", "\u090a": "u", "\u0941": "u", "\u0942": "u", "\u090f": "e", "\u0910": "e", "\u0947": "e", "\u0948": "e", "\u0945": "e", "\u090d": "e", "\u0913": "o", "\u0914": "o", "\u0911": "o", "\u094b": "o", "\u094c": "o", "\u0949": "o", "\u090b": "r", "\u0943": "r"};
496
+ export const ABBR = {"h": ["\u0939\u0948", "\u0939\u0948\u0902"], "hy": ["\u0939\u0948"], "hn": ["\u0939\u093e\u0901", "\u0939\u0948\u0902"], "hm": ["\u0939\u092e", "\u0939\u092e\u094d\u092e"], "k": ["\u0915\u0947", "\u0915\u0940", "\u0913\u0915\u0947"], "ki": ["\u0915\u0940", "\u0915\u093f"], "n": ["\u0928\u093e", "\u0928"], "ni": ["\u0928\u0939\u0940\u0902", "\u0928\u0940"], "nai": ["\u0928\u0939\u0940\u0902", "\u0928\u0908"], "nhi": ["\u0928\u0939\u0940\u0902"], "nhe": ["\u0928\u0939\u0940\u0902"], "nahi": ["\u0928\u0939\u0940\u0902"], "kr": ["\u0915\u0930"], "krna": ["\u0915\u0930\u0928\u093e"], "kro": ["\u0915\u0930\u094b"], "tm": ["\u0924\u0941\u092e"], "tu": ["\u0924\u0942"], "mai": ["\u092e\u0948\u0902", "\u092e\u0947\u0902"], "me": ["\u092e\u0947\u0902", "\u092e\u0948\u0902"], "m": ["\u092e\u0948\u0902", "\u092e\u0947\u0902"], "thk": ["\u0920\u0940\u0915"], "tk": ["\u0920\u0940\u0915", "\u0924\u0915"], "yr": ["\u092f\u093e\u0930"], "yrr": ["\u092f\u093e\u0930"], "plz": ["\u092a\u094d\u0932\u0940\u091c\u093c"], "pls": ["\u092a\u094d\u0932\u0940\u091c\u093c"], "plzz": ["\u092a\u094d\u0932\u0940\u091c\u093c"], "tq": ["\u0925\u0948\u0902\u0915 \u092f\u0942"], "ty": ["\u0925\u0948\u0902\u0915 \u092f\u0942"], "thx": ["\u0925\u0948\u0902\u0915\u094d\u0938"], "thnx": ["\u0925\u0948\u0902\u0915\u094d\u0938"], "thnks": ["\u0925\u0948\u0902\u0915\u094d\u0938"], "gn": ["\u0917\u0941\u0921 \u0928\u093e\u0907\u091f"], "gm": ["\u0917\u0941\u0921 \u092e\u0949\u0930\u094d\u0928\u093f\u0902\u0917"], "ok": ["\u0913\u0915\u0947"], "okk": ["\u0913\u0915\u0947"], "kk": ["\u0913\u0915\u0947"], "sry": ["\u0938\u0949\u0930\u0940"], "bro": ["\u092c\u094d\u0930\u094b"], "msg": ["\u092e\u0948\u0938\u0947\u091c"], "bday": ["\u092c\u0930\u094d\u0925\u0921\u0947"], "clg": ["\u0915\u0949\u0932\u0947\u091c"], "ofc": ["\u0911\u092b\u093c\u093f\u0938"], "pic": ["\u092a\u093f\u0915"], "bhai": ["\u092d\u093e\u0908"], "bht": ["\u092c\u0939\u0941\u0924"], "bhot": ["\u092c\u0939\u0941\u0924"], "bohot": ["\u092c\u0939\u0941\u0924"], "pta": ["\u092a\u0924\u093e"], "kb": ["\u0915\u092c"], "jb": ["\u091c\u092c"], "sb": ["\u0938\u092c"], "mtlb": ["\u092e\u0924\u0932\u092c"], "kch": ["\u0915\u0941\u091b"], "kuch": ["\u0915\u0941\u091b"]};
497
+ const CAS_VOW = new Set(["a", "e", "i", "o", "u"]), CAS_ASP = new Set([..."kgjtdpbc"]);
498
+ function casFin(s) { let o = ""; for (let i = 0; i < s.length; i++) { const ch = s[i]; if (ch === "a" && i > 0) continue; if (o && o[o.length - 1] === ch) continue; o += ch; } return o; }
499
+ export function k2Of(k1) {
500
+ if (!k1) return "";
501
+ let s = k1[0]; for (const c of k1.slice(1)) if (!CAS_VOW.has(c)) s += c;
502
+ if (s && CAS_VOW.has(s[0])) s = s.slice(1) || s;
503
+ let o = ""; for (const ch of s) if (!(o && o[o.length - 1] === ch)) o += ch; return o;
504
+ }
505
+ export function devKeys(w) {
506
+ const a = [...w], raw = [];
507
+ for (let i = 0; i < a.length; i++) {
508
+ const ch = a[i];
509
+ if (ch === "\u093c") { if (raw.length && raw[raw.length - 1] === "d" && i > 0 && (a[i - 1] === "\u0921" || a[i - 1] === "\u0922")) raw[raw.length - 1] = "R"; continue; }
510
+ if (CAS_DC[ch] !== undefined) raw.push(CAS_DC[ch]); else if (CAS_DV[ch] !== undefined) raw.push(CAS_DV[ch]);
511
+ }
512
+ const s = raw.join(""); if (!s) return [];
513
+ const ks = [...new Set([casFin(s.split("R").join("d")), casFin(s.split("R").join("r"))])].filter((k) => k);
514
+ return ks.sort();
515
+ }
516
+ export function romKeys(t, cap = 12) {
517
+ t = t.toLowerCase().replace(/[^a-z]/g, ""); if (!t) return [[], []];
518
+ t = t.replace(/(.)\1{2,}/g, "$1");
519
+ t = t.split("chh").join("c").split("cch").join("c").split("ch").join("c").split("sh").join("s");
520
+ t = t.split("x").join("ks").split("q").join("k").split("z").join("j").split("f").join("p").split("w").join("v");
521
+ const alts = [], L = t.length, V = (c) => CAS_VOW.has(c); let i = 0;
522
+ while (i < L) {
523
+ const c = t[i], nx = i + 1 < L ? t[i + 1] : "", prev = i > 0 ? t[i - 1] : "";
524
+ if (c === "h" && CAS_ASP.has(prev) && i > 0) { alts.push(["", "h"]); i += 1; continue; }
525
+ if (nx && (c === "a" || c === "e") && (nx === "i" || nx === "y") && (i + 2 >= L || !V(t[i + 2]))) {
526
+ if (c === "a" && nx === "i") { alts.push(["e", "ai"]); i += 2; continue; }
527
+ if (c === "a" && nx === "y" && i + 2 >= L) { alts.push(["e"]); i += 2; continue; }
528
+ if (c === "e" && nx === "y" && i + 2 >= L) { alts.push(["e"]); i += 2; continue; }
529
+ }
530
+ if (nx && (c === "a" || c === "o") && "uvw".includes(nx) && (i + 2 >= L || !V(t[i + 2])) && c + nx !== "av") { alts.push(["o", c + "u"]); i += 2; continue; }
531
+ if (c === "o" && nx === "u") { alts.push(["o", "u"]); i += 2; continue; }
532
+ if (c === "e" && nx === "e") { alts.push(["i"]); i += 2; continue; }
533
+ if (c === "o" && nx === "o") { alts.push(["u", "o"]); i += 2; continue; }
534
+ if (c === "e" && nx === "i" && (i + 2 >= L || !V(t[i + 2]))) { alts.push(["e"]); i += 2; continue; }
535
+ if (c === "e" && nx === "h" && i + 2 < L && !V(t[i + 2]) && i > 0) { alts.push(["e", ""]); i += 1; continue; }
536
+ if (nx && c === "i" && "aeou".includes(nx) && i > 0) { alts.push(["y", "i"]); i += 1; continue; }
537
+ if (c === "y" && i === L - 1 && prev && !V(prev)) { alts.push(["e", "i"]); i += 1; continue; }
538
+ if (c === "n" && V(prev) && i > 0 && (i === L - 1 || (nx && !V(nx) && nx !== "y"))) { alts.push(["n", ""]); i += 1; continue; }
539
+ if ((c === "i" || c === "u") && prev === "r" && i >= 2 && !V(t[i - 2])) { alts.push([c, ""]); i += 1; continue; }
540
+ alts.push([c]); i += 1;
541
+ }
542
+ let outs = [""];
543
+ for (const a of alts) { const nxt = []; for (const o of outs) for (const x of a) nxt.push(o + x); outs = [...new Set(nxt)].slice(0, 64); }
544
+ const k1 = [], seen = new Set();
545
+ for (const o of outs) { const k = casFin(o); if (k && !seen.has(k)) { seen.add(k); k1.push(k); } }
546
+ const k1c = k1.slice(0, cap); const k2 = [...new Set(k1c.map(k2Of).filter((k) => k))];
547
+ return [k1c, k2];
548
+ }
549
+ export function squeezeIn(t, mode = 0) {
550
+ if (!mode) return t;
551
+ if (mode === 1) return t.replace(/([a-z])\1{2,}/g, "$1");
552
+ return t.replace(/([a-z])\1{2,}/g, (m, c) => (/[aeo]/.test(c) ? c + c : c));
553
+ }
554
+ class CasualIndex {
555
+ constructor(lm, topN = 300000, perKey = 8) {
556
+ const N = lm.words.length, uni = lm.uni, order = Array.from({ length: N }, (_, i) => i);
557
+ order.sort((x, y) => (uni[y] - uni[x]) || (x - y));
558
+ this.k1 = new Map(); this.k2 = new Map();
559
+ const put = (m, k, i) => { let b = m.get(k); if (!b) m.set(k, (b = [])); if (b.length < perKey && !b.includes(i)) b.push(i); };
560
+ for (let r = 0; r < Math.min(N, topN); r++) { const i = order[r]; for (const k of devKeys(lm.words[i])) { put(this.k1, k, i); const k2 = k2Of(k); if (k2) put(this.k2, k2, i); } }
561
+ }
562
+ lookup(roman, n1 = 6, n2 = 4) {
563
+ const [k1, k2] = romKeys(roman), r1 = [], r2 = [];
564
+ for (const k of k1) for (const i of this.k1.get(k) || []) if (!r1.includes(i)) r1.push(i);
565
+ for (const k of k2) for (const i of this.k2.get(k) || []) if (!r2.includes(i)) r2.push(i);
566
+ return [r1.slice(0, n1 * 2), r2.slice(0, n2 * 2)];
567
+ }
568
+ }
569
+ const VIS_RE = /[aeiou]h$/;
570
+
571
+ // ------------------------------------------------------------------ the engine
572
+ export class BhashaCore {
573
+ /**
574
+ * Load a bundle (directory with manifest.json) from a URL (browser / worker) or via a custom fetch (Node: file loader).
575
+ * dict: true (default) = also load the dictionary pack and wait for it; false = core only (call loadDict() later).
576
+ * cache: Cache API cache name for the dictionary pack (browser / worker; default "aksharalp-dict"), or false.
577
+ */
578
+ static async load(base, { fetch: fetchImpl = globalThis.fetch, onProgress, dict = true, cache = "aksharalp-dict" } = {}) {
579
+ const t0 = now(); const txt = (b) => new TextDecoder().decode(b);
580
+ let man; try { man = JSON.parse(txt(await fetchBytes(base, "manifest.json", fetchImpl))); } catch { man = { files: { model: "xlit.q4", lex: "lex.bin", lm: "lm.bin", params: "params.json", lid: "lid.int8", loan: "loan.tsv", natural: "natural_hi.tsv" } }; }
581
+ const F = man.files, SZ = man.sizes || {}, get = async (n, opt) => { if (!n) return null; try { const b = await fetchBytes(base, n, fetchImpl, SZ[n] && SZ[n].bytes); onProgress && onProgress(n); return b; } catch (e) { if (opt) return null; throw e; } };
582
+ const [mb, lexb, lmb, pb, lidb, loanb, natb] = await Promise.all([get(F.model), get(F.lex, true), get(F.lm, true), get(F.params, true), get(F.lid, true), get(F.loan, true), get(F.natural, true)]);
583
+ const e = new BhashaCore({ model: mb, lex: lexb, lm: lmb, params: pb ? JSON.parse(txt(pb)) : {}, lid: lidb, loan: loanb ? txt(loanb) : null, natural: natb ? txt(natb) : null, manifest: man });
584
+ e.base = base; e.fetchImpl = fetchImpl;
585
+ if (dict && man.packs && man.packs.dict) await e.loadDict({ onProgress, cache });
586
+ e.loadMs = now() - t0; return e;
587
+ }
588
+ /** Fetch (or read from cache) the dictionary pack and attach it. Returns true on success; on any failure the engine
589
+ * keeps working in core-only mode and the error is kept in this.dictError. */
590
+ async loadDict({ onProgress, cache = "aksharalp-dict" } = {}) {
591
+ const pk = this.manifest && this.manifest.packs && this.manifest.packs.dict; if (!pk) return false;
592
+ const dir = pk.dir.endsWith("/") ? pk.dir : pk.dir + "/", fetchImpl = this.fetchImpl || globalThis.fetch;
593
+ const useCache = cache && typeof caches !== "undefined";
594
+ const get = async (n) => {
595
+ if (!n) return null;
596
+ const ver = (pk.sizes && pk.sizes[n] && pk.sizes[n].sha256) || "", expect = (pk.sizes && pk.sizes[n] && pk.sizes[n].bytes) || 0;
597
+ if (useCache) {
598
+ try {
599
+ const url = new URL(dir + n, new URL(this.base.endsWith("/") ? this.base : this.base + "/", globalThis.location && globalThis.location.href)).href;
600
+ const c = await caches.open(cache), key = url + "?v=" + ver.slice(0, 16), hit = await c.match(key);
601
+ if (hit) { const hb = new Uint8Array(await hit.arrayBuffer()); if (!expect || hb.length === expect) { onProgress && onProgress(n, "cache"); return hb; } await c.delete(key); }
602
+ const r = await fetchImpl(url); if (!r.ok) throw new Error(`fetch ${url}: ${r.status}`);
603
+ const b = checkSize(new Uint8Array(await r.arrayBuffer()), expect, url); try { await c.put(key, new Response(b)); } catch (_) {} // stored as a fresh, non-redirected response
604
+ onProgress && onProgress(n, "network"); return b;
605
+ } catch (e) { if (String(e).includes("fetch ")) throw e; }
606
+ }
607
+ const b = await fetchBytes(this.base, dir + n, fetchImpl, expect); onProgress && onProgress(n, "network"); return b;
608
+ };
609
+ try {
610
+ const [lexb, lmb, pb] = await Promise.all([get(pk.files.lex), get(pk.files.lm), get(pk.files.params)]);
611
+ this.attachDict({ lex: lexb, lm: lmb, params: pb ? JSON.parse(new TextDecoder().decode(pb)) : {} });
612
+ this.dictInfo = { name: pk.name || "dict", words: this.lm.N, bytes: pk.bytes, gz: pk.gz }; return true;
613
+ } catch (e) { this.dictError = String(e && e.message || e); return false; }
614
+ }
615
+ /** attach an already-fetched dictionary pack: {lex, lm, params} (params override the core params) */
616
+ attachDict({ lex, lm = null, params = {} }) {
617
+ this.p = { ...DEFAULTS, ...this.coreParams, ...params };
618
+ this._setLex(lex, lm); this.cache = new Map(); this.hasDict = this.lm.N > 0;
619
+ }
620
+ _setLex(lex, lm) {
621
+ this.lm = new LexLM(lex, lm && this.p.use_lm ? lm : null);
622
+ if (!this.lm.N) { this.p.cbeam = 0; this.p.skel = false; }
623
+ this.cix = this.p.casual && this.lm.N ? new CasualIndex(this.lm, this.p.cas_top) : null;
624
+ // the trie never contains the empty string (as in Python's LazyTrie), otherwise "" would take a constrained-beam slot
625
+ const sw = this.lm.sortedWords, tw = sw.length && sw[0] === "" ? sw.slice(1) : sw;
626
+ this.trie = this.p.cbeam ? new LazyTrie(tw) : null;
627
+ this.skix = this.p.skel ? new SkelIndex(this.lm) : null;
628
+ }
629
+ constructor({ model, lex = null, lm = null, params = {}, lid = null, loan = null, natural = null, manifest = null }) {
630
+ this.coreParams = params; this.p = { ...DEFAULTS, ...params }; this.manifest = manifest;
631
+ this.model = new XlitModel(parseWeights(model));
632
+ this._setLex(lex, lm); this.hasDict = this.lm.N > 0; this.dictInfo = null; this.dictError = null;
633
+ this.lid = lid ? new LID(parseWeights(lid)) : null;
634
+ this.loan = loan ? parseTSV(loan) : new Map();
635
+ this.natural = natural ? parseTSV(natural) : new Map();
636
+ this.cache = new Map();
637
+ }
638
+ info() {
639
+ const s = this.manifest && this.manifest.sizes ? this.manifest.sizes : {};
640
+ const used = Object.values(this.manifest ? this.manifest.files : {}).filter((f) => f && s[f]);
641
+ return { params: this.model.nParams + (this.lid ? this.lid.nParams : 0), xlitParams: this.model.nParams, lidParams: this.lid ? this.lid.nParams : 0,
642
+ lexWords: this.lm.N, bytes: used.reduce((a, f) => a + s[f].bytes, 0), gz: used.reduce((a, f) => a + s[f].gz, 0), hasLID: !!this.lid, hasLM: this.lm.hasLM,
643
+ hasDict: this.hasDict, dict: this.dictInfo, dictError: this.dictError, hasLoan: this.loan.size > 0, hasNatural: this.natural.size > 0 };
644
+ }
645
+
646
+ /** candidate rows with reranker features for one (lower-cased) Latin word: [[devanagari, fv, wid], ...] */
647
+ candFeats(word) {
648
+ const p = this.p, w = squeezeIn(word.toLowerCase(), p.squeeze); const hit = this.cache.get(w); if (hit) return hit;
649
+ const m = this.model, ids = encSrc(w), enc = m.encode(ids), S = ids.length;
650
+ const free = m.beam(enc, S, { beam: p.beam, nbest: p.nbest });
651
+ const con = p.cbeam && this.trie ? m.beam(enc, S, { beam: p.beam, nbest: p.nbest, trie: this.trie }) : [];
652
+ const d = new Map();
653
+ free.forEach(([s, sc], i) => { if (s) d.set(s, { sc, infree: 1, frank: i, free0: +(i === 0), beamEntry: 1 }); });
654
+ for (const [s, sc] of con) if (s) { if (!d.has(s)) d.set(s, { sc, infree: 0, frank: p.nbest, free0: 0, beamEntry: 1 }); d.get(s).incon = 1; }
655
+ if (p.variants) {
656
+ const top = [...d.keys()].sort((a, b) => d.get(b).sc - d.get(a).sc).slice(0, 4);
657
+ for (const s of top) for (const [a, b] of VAR_SWAPS) if (s.includes(a)) {
658
+ const v = s.split(a).join(b); if (!d.has(v) && this.lm.id(v) !== null) d.set(v, { sc: m.scorePair(enc, v), infree: 0, frank: p.nbest, free0: 0, variant: 1 });
659
+ }
660
+ }
661
+ if (p.acro && (RE_ACRO.test(w) || (p.acro_v && w.length >= 2 && w.length <= 4))) {
662
+ const v = [...w].map((c) => LETTER[c]).join("");
663
+ if (!d.has(v)) d.set(v, { sc: m.scorePair(enc, v), infree: 0, frank: p.nbest, free0: 0, acro: 1 }); else d.get(v).acro = 1;
664
+ }
665
+ if (p.visr && VIS_RE.test(w)) { // Aksharantar convention: literal word-final h -> visarga
666
+ const top = [...d.keys()].filter((x) => d.get(x).beamEntry).sort((x, y) => d.get(y).sc - d.get(x).sc).slice(0, 2);
667
+ for (const s of top) {
668
+ const v = s.endsWith("\u0939") ? s.slice(0, -1) + "\u0903" : (!s.endsWith("\u0903") ? s + "\u0903" : null);
669
+ if (v && !d.has(v)) d.set(v, { sc: m.scorePair(enc, v), infree: 0, frank: p.nbest, free0: 0, visr: 1 });
670
+ }
671
+ }
672
+ if (this.skix) {
673
+ const L = w.length + 1, words = this.lm.words, uni = this.lm.uni;
674
+ const ids = this.skix.lookup(w, p.skel_k).map((i) => [i, lev(w, devRom(words[i])) / L - p.skel_wu * uni[i]])
675
+ .sort((x, y) => x[1] - y[1]).slice(0, p.skel_m);
676
+ for (const [i] of ids) { const v = words[i]; if (!d.has(v)) d.set(v, { sc: m.scorePair(enc, v), infree: 0, frank: p.nbest, free0: 0, skel: 1 }); else d.get(v).skel = 1; }
677
+ }
678
+ if (this.cix) {
679
+ const [r1, r2] = this.cix.lookup(w, p.cas_n1, p.cas_n2), words = this.lm.words, nv = (w.match(/[aeiou]/g) || []).length;
680
+ const ab = new Set(p.abbr && Object.prototype.hasOwnProperty.call(ABBR, w) ? ABBR[w] : []), s1 = new Set(r1.slice(0, p.cas_n1).map((i) => words[i]));
681
+ for (const [key, ids, n] of [["ck1", r1, p.cas_n1], ["ck2", r2, p.cas_n2]]) ids.slice(0, n).forEach((i, rk) => {
682
+ const v = words[i];
683
+ if (!d.has(v)) {
684
+ if (!ab.has(v) && ((s1.has(v) && nv > p.k1gate) || (!s1.has(v) && nv > p.k2gate))) return; // vowel gate
685
+ d.set(v, { sc: m.scorePair(enc, v), infree: 0, frank: p.nbest, free0: 0 });
686
+ }
687
+ const e = d.get(v); e[key] = 1; e.ckr = Math.min(e.ckr ?? 1, rk / n);
688
+ });
689
+ }
690
+ if (p.abbr && Object.prototype.hasOwnProperty.call(ABBR, w)) ABBR[w].forEach((v, rk) => {
691
+ if (!d.has(v)) d.set(v, { sc: m.scorePair(enc, v), infree: 0, frank: p.nbest, free0: 0 });
692
+ const e = d.get(v); e.abbr = 1; e.abbr_r = rk;
693
+ });
694
+ const rows = [], sks = new Set(romSkels(w)), Lw = w.length + 1, vis = VIS_RE.test(w), novow = !/[aeiou]/.test(w);
695
+ for (const [s, e] of d) {
696
+ const wid = this.lm.id(s), L = [...s].length;
697
+ rows.push([s, [e.sc, e.sc / (L + 1), +(wid !== null), wid !== null ? this.lm.unigram(wid) : 0, e.free0 || 0, e.infree || 0, e.incon || 0,
698
+ +(s.includes("\u0949") || s.includes("\u0911")), +s.includes("\u0903"), +s.includes("\u093c"), +s.includes("\u0901"), e.acro || 0, e.variant || 0, (e.frank ?? p.nbest) / p.nbest,
699
+ e.skel || 0, +sks.has(devSkel(s)), lev(w, devRom(s)) / Lw, 0, 0, 0, L / Lw, e.visr || 0, +(vis && s.endsWith("\u0903")), +(!!e.acro && novow),
700
+ e.ck1 || 0, e.ck2 || 0, e.ckr ?? 1, e.abbr || 0, e.abbr_r ?? 3, +!/[aeiouy]/.test(w)], wid]);
701
+ }
702
+ if (rows.length) {
703
+ let msc = -Infinity, mscn = -Infinity, mu = null;
704
+ for (const [, fv] of rows) { msc = Math.max(msc, fv[0]); mscn = Math.max(mscn, fv[1]); if (fv[2]) mu = mu === null ? fv[3] : Math.max(mu, fv[3]); }
705
+ for (const [, fv] of rows) { fv[17] = fv[0] - msc; fv[18] = fv[1] - mscn; fv[19] = fv[2] ? fv[3] - (mu ?? 0) : 0; }
706
+ }
707
+ if (this.cache.size > 20000) this.cache.clear();
708
+ this.cache.set(w, rows); return rows;
709
+ }
710
+ score(fv) {
711
+ const p = this.p; if (!p.w && !p.w1) return fv[0] + 0.2 * (fv[2] ? fv[3] : -25.0);
712
+ const n = p.mu.length, z = new Array(n); for (let i = 0; i < n; i++) z[i] = (fv[i] - p.mu[i]) / p.sd[i];
713
+ if (p.w1) { // 1-hidden-layer tanh MLP reranker (fit on VALID only)
714
+ let s = p.b2 || 0;
715
+ for (let h = 0; h < p.w1.length; h++) { const r = p.w1[h]; let a = p.b1[h]; for (let i = 0; i < n; i++) a += r[i] * z[i]; s += p.w2[h] * Math.tanh(a); }
716
+ return s;
717
+ }
718
+ let s = 0; for (let i = 0; i < Math.min(n, p.w.length); i++) s += p.w[i] * z[i]; return s;
719
+ }
720
+ /** word mode: top-k Devanagari candidates for a romanized word */
721
+ word(w, k = 8) {
722
+ if (!RE_LATIN.test(w)) return [{ text: w, score: 0 }];
723
+ const rows = this.candFeats(w).map(([s, fv]) => ({ text: s, score: this.score(fv) }));
724
+ return rows.sort((a, b) => b.score - a.score).slice(0, k);
725
+ }
726
+ /** sentence Viterbi over candidate lattices with the bigram LM (bhc/translit2.py decode_slots) */
727
+ decodeSlots(slots, context = true) {
728
+ const p = this.p, lm = this.lm, lat = [];
729
+ for (const [kind, t] of slots) {
730
+ if (kind === "x") {
731
+ let sc = this.candFeats(t).map(([s, fv, wid]) => [s, this.score(fv), wid]);
732
+ if (sc.length) {
733
+ const mx = Math.max(...sc.map((x) => x[1])), lse = mx + Math.log(sc.reduce((a, x) => a + Math.exp(x[1] - mx), 0));
734
+ sc = sc.map(([s, v, wid]) => [s, p.w_ch_s * (v - lse), wid]).sort((a, b) => b[1] - a[1]).slice(0, 12);
735
+ }
736
+ lat.push(sc.length ? sc : [[t, 0, null]]);
737
+ } else {
738
+ const wid = RE_DEVA.test(t) ? lm.id(t) ?? -1 : -2; lat.push([[t, 0, wid]]);
739
+ }
740
+ }
741
+ const post = lat.map((o) => o.map(([s, v]) => [s, v])); // per-slot candidate list (for UI chips)
742
+ if (!context || !p.use_lm || !lm.hasLM) return { out: lat.map((o) => o.reduce((a, b) => (b[1] > a[1] ? b : a))[0]), lat: post };
743
+ let beams = [[0, null, []]];
744
+ for (const opts of lat) {
745
+ if (opts[0][2] === -2 && opts.length === 1) { beams = beams.map(([s, h, ch]) => [s, h, ch.concat([opts[0][0]])]); continue; }
746
+ const best = new Map();
747
+ for (const [s, h, ch] of beams) for (const [cand, csc, wid] of opts) {
748
+ const w = wid === null || wid === -1 ? null : wid;
749
+ const tot = s + csc + p.w_lm * (lm.logp(h, w) - (w === null ? p.oov_pen_s : 0));
750
+ if (!best.has(cand) || tot > best.get(cand)[0]) best.set(cand, [tot, w, ch.concat([cand])]);
751
+ }
752
+ beams = [...best.values()].sort((a, b) => b[0] - a[0]).slice(0, 16);
753
+ }
754
+ let bb = null, bv = -Infinity;
755
+ for (const b of beams) { const v = b[0] + p.w_lm * lm.logp(b[1], lm.N); if (v > bv) { bv = v; bb = b; } }
756
+ return { out: bb[2], lat: post };
757
+ }
758
+ /** plain Track-B sentence conversion (bhc/translit2.py convert) */
759
+ convertPlain(text) {
760
+ const spans = [...text.matchAll(TOKEN_RE)].map((m) => [m.index, m.index + m[0].length, m[0]]);
761
+ const { out } = this.decodeSlots(spans.map(([, , t]) => (RE_LATIN.test(t) ? ["x", t] : ["fix", t])));
762
+ if (out.length && out[out.length - 1] === ".") out[out.length - 1] = "\u0964";
763
+ let s = "", pos = 0; spans.forEach(([a, b], i) => { s += text.slice(pos, a) + out[i]; pos = b; }); return s + text.slice(pos);
764
+ }
765
+
766
+ /**
767
+ * Full pipeline: tokenise -> language ID -> policy (keep / acronym / loanword / glossary) -> contextual decoding.
768
+ * @param {string} text
769
+ * @param {{mode?: 'conversational'|'natural'|'strict', k?: number, context?: boolean, overrides?: Object<number,string>}} opt
770
+ * context: false = word-by-word (no sentence LM)
771
+ * overrides: {tokenStartOffset: chosenDevanagari} pins a user-picked suggestion (re-decodes the rest in context)
772
+ * @returns {{output: string, tokens: Array<{text,start,end,output,lang,source,candidates:Array<{text,score}>}>, sentence: string|null, ms: number}}
773
+ */
774
+ transliterate(text, { mode = "conversational", k = 5, overrides = {}, context = true } = {}) {
775
+ const t0 = now(); const policy = mode === "strict" ? "strict" : "conversational";
776
+ const spans = [...text.matchAll(TOKEN_RE)].map((m) => [m.index, m.index + m[0].length, m[0]]);
777
+ const toks = spans.map((s) => s[2]);
778
+ const tags = this.lid ? this.lid.tag(toks) : toks.map((t) => (RE_DEVA.test(t) ? "DEV" : RE_LATIN_APOS.test(t) ? (this.loan.has(t.toLowerCase()) ? "EN" : "HI") : "OTHER"));
779
+ const slots = [], src = []; let prevBrand = false;
780
+ for (let i = 0; i < toks.length; i++) {
781
+ const t = toks[i], tag = tags[i], nxt = toks[i + 1] || "", low = t.toLowerCase(), ov = overrides[spans[i][0]];
782
+ if (ov !== undefined) { slots.push(["fix", ov]); src.push("user"); prevBrand = false; continue; }
783
+ if (!RE_LATIN_APOS.test(t) || KEEP_RE.test(t) || nxt === "(") {
784
+ if (t === "." && (i === toks.length - 1 || /^[A-Z]/.test(toks[i + 1] || ""))) slots.push(["fix", "\u0964"]); else slots.push(["fix", t]);
785
+ src.push(RE_DEVA.test(t) ? "devanagari" : KEEP_RE.test(t) ? "keep" : "punct"); prevBrand = false; continue;
786
+ }
787
+ const isBrand = BRANDS.has(low) || (prevBrand && /^[A-Z]/.test(t)); prevBrand = isBrand;
788
+ if (policy === "strict" && isBrand) { slots.push(["fix", t]); src.push("brand"); continue; }
789
+ if (mode === "natural" && tag === "EN" && this.natural.has(low)) { slots.push(["fix", this.natural.get(low)]); src.push("glossary"); continue; }
790
+ if (isUpper(t) && t.length >= 2 && t.length <= 5 && !this.loan.has(low)) { slots.push(["fix", [...low].map((c) => LETTERS_H[c]).join("")]); src.push("acronym"); continue; }
791
+ if ((tag === "EN" || isBrand) && this.loan.has(low)) { slots.push(["fix", this.loan.get(low)]); src.push("loanword"); continue; }
792
+ slots.push(["x", t]); src.push("model");
793
+ }
794
+ const { out, lat } = this.decodeSlots(slots, context);
795
+ let output = "", pos = 0; spans.forEach(([a, b], i) => { output += text.slice(pos, a) + out[i]; pos = b; }); output = (output + text.slice(pos)).trim();
796
+ const tokens = spans.map(([a, b, t], i) => {
797
+ let cands = [];
798
+ if (slots[i][0] === "x") {
799
+ const mx = lat[i][0] ? lat[i][0][1] : 0;
800
+ cands = lat[i].slice(0, k).map(([s, v]) => ({ text: s, score: Math.exp(v - mx) }));
801
+ } else if (RE_LATIN_APOS.test(t) && !KEEP_RE.test(t)) {
802
+ cands = [{ text: out[i], score: 1 }];
803
+ for (const c of this.word(t, k)) if (c.text !== out[i]) cands.push({ text: c.text, score: null });
804
+ if (out[i] !== t) cands.push({ text: t, score: null });
805
+ cands = cands.slice(0, k);
806
+ }
807
+ const isW = RE_LATIN_APOS.test(t) || RE_DEVA.test(t);
808
+ return { text: t, start: a, end: b, output: out[i], lang: isW ? tags[i] : "OTHER", source: src[i], candidates: cands };
809
+ });
810
+ let sentence = null; if (this.lid && toks.length) try { sentence = this.lid.mode(toks); } catch { sentence = null; }
811
+ return { output, tokens, sentence, ms: now() - t0 };
812
+ }
813
+ }
814
+
815
+ /** Node helper: load a bundle from a local directory. */
816
+ export async function loadFromDir(dir, opts = {}) {
817
+ const fs = await import("node:fs/promises"), path = await import("node:path");
818
+ const f = async (u) => { try { const b = await fs.readFile(path.resolve(u)); return new Response(b); } catch { return new Response(null, { status: 404 }); } };
819
+ return BhashaCore.load(dir, { fetch: f, cache: false, ...opts });
820
+ }
821
+ export { BhashaCore as AksharaLP };
822
+ export default BhashaCore;
assets/accuracy.png ADDED
assets/accuracy.svg ADDED
assets/banner.png ADDED

Git LFS Details

  • SHA256: 27da3ac213f72636663b84026093cf2a1c80f825d8d67f110466ae9df0d3d6fc
  • Pointer size: 131 Bytes
  • Size of remote file: 210 kB
assets/banner.svg ADDED
assets/sizes.png ADDED
assets/sizes.svg ADDED
config.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "AksharaLP-MultiIndic-8M",
3
+ "version": "1.0.0",
4
+ "format": "aksharalp-multiindic/1",
5
+ "languages": [
6
+ "hi",
7
+ "bn",
8
+ "te",
9
+ "kn",
10
+ "ta"
11
+ ],
12
+ "parameters": {
13
+ "hi": 988928,
14
+ "bn": 989056,
15
+ "te": 988928,
16
+ "kn": 988928,
17
+ "ta": 988928,
18
+ "word_total": 4944768,
19
+ "hindi_sentence_lm": 2770752,
20
+ "total": 7715520
21
+ },
22
+ "sentence_languages": [
23
+ "hi"
24
+ ],
25
+ "license": "cc-by-nc-4.0",
26
+ "requires_bundled_decoder": true
27
+ }
evaluation/bn_frozen.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": {
3
+ "xlit.q4": "9f099f0b9524f446debf1d3f0ae1dc7ed1e88625c33f6dbb7b45e0348bf76376",
4
+ "dict.tsv.gz": "edb2ad0bd7de4e87bd4d49ac406a9ef01011451bf23ac82d4c063bb9cce1dc2b",
5
+ "params.json": "e0bd564ad61f6dcef8eca6bf3d64f8b231146c9885aa4c5b282a2b2420606c4f"
6
+ },
7
+ "fit": "VALID only; frozen before test",
8
+ "config": {
9
+ "format": "aksharalp-multiindic/1",
10
+ "language": "bn",
11
+ "beam": 8,
12
+ "nbest": 8,
13
+ "model": "xlit.q4",
14
+ "dictionary": "dict.tsv.gz",
15
+ "dictionary_words": 500000,
16
+ "dictionary_total": 29467775,
17
+ "unk": -17.198807885289952,
18
+ "reranker": {
19
+ "type": "linear",
20
+ "features": [
21
+ "sc",
22
+ "scn",
23
+ "inlex",
24
+ "uni",
25
+ "free0",
26
+ "rank",
27
+ "sc_rel",
28
+ "uni_rel"
29
+ ],
30
+ "w": [
31
+ 0.7719883322715759,
32
+ 0.7239015102386475,
33
+ 2.2432072162628174,
34
+ 0.3994755744934082,
35
+ -0.1507813185453415,
36
+ -0.07777873426675797,
37
+ 0.7293219566345215,
38
+ 0.42816251516342163
39
+ ],
40
+ "mu": [
41
+ -5.078212233229818,
42
+ -0.8147631729600909,
43
+ 0.31161147274689055,
44
+ -4.2673892678692775,
45
+ 0.13265511803554703,
46
+ 0.4336724409822265,
47
+ -3.7508561308261763,
48
+ -0.6986245748643944
49
+ ],
50
+ "sd": [
51
+ 2.216960295304686,
52
+ 0.6165648295196909,
53
+ 0.46315298671618116,
54
+ 6.490492456336254,
55
+ 0.33920261805997143,
56
+ 0.28614764292216904,
57
+ 2.341120778815271,
58
+ 1.748762448987244
59
+ ],
60
+ "fit_split": "all Aksharantar VALID",
61
+ "fit_n": 11188,
62
+ "l2": 0.001,
63
+ "seed": 0
64
+ }
65
+ },
66
+ "inventory": {
67
+ "parameters": 989056,
68
+ "model_raw": 589040,
69
+ "model_gz9": 540107,
70
+ "dict_raw": 14068340,
71
+ "dict_gz9": 2508610,
72
+ "dict_words": 500000
73
+ }
74
+ }
evaluation/examples.json ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "hi": [
3
+ {
4
+ "input": "namaste",
5
+ "output": "नमस्ते",
6
+ "top8": [
7
+ "नमस्ते",
8
+ "नमसते",
9
+ "नाश्ते",
10
+ "नामस्ते",
11
+ "नमास्ते",
12
+ "नमस्तें",
13
+ "नास्ते",
14
+ "नम्स्ते"
15
+ ],
16
+ "ms": 405.55157300000064
17
+ },
18
+ {
19
+ "input": "kaise",
20
+ "output": "कैसे",
21
+ "top8": [
22
+ "कैसे",
23
+ "केसे",
24
+ "कइसे",
25
+ "कैस",
26
+ "कईसे",
27
+ "किसे",
28
+ "काइसे",
29
+ "कैशे"
30
+ ],
31
+ "ms": 146.41421700000137
32
+ },
33
+ {
34
+ "input": "dhanyavaad",
35
+ "output": "धन्यवाद",
36
+ "top8": [
37
+ "धन्यवाद",
38
+ "धन्यावाद",
39
+ "धन्यवाद्",
40
+ "धान्यवाद",
41
+ "धनयवाद",
42
+ "धन्यववाद",
43
+ "धन्यवादी",
44
+ "धन्येवाद"
45
+ ],
46
+ "ms": 234.02473099999952
47
+ },
48
+ {
49
+ "input": "mera naam rahul hai",
50
+ "output": "मेरा नाम राहुल है",
51
+ "top8": null,
52
+ "ms": 623.3726830000014
53
+ }
54
+ ],
55
+ "hi_sentence": [
56
+ {
57
+ "input": "main theek hoon",
58
+ "output": "मैं ठीक हूं"
59
+ },
60
+ {
61
+ "input": "ghar main kaun hai",
62
+ "output": "घर में कौन है"
63
+ }
64
+ ],
65
+ "bn": [
66
+ {
67
+ "input": "nomoskar",
68
+ "output": "নমস্কার",
69
+ "top8": [
70
+ "নমস্কার",
71
+ "নমষ্কার",
72
+ "নোমস্কার",
73
+ "নোমসকার",
74
+ "নমসকার",
75
+ "নমস্কর",
76
+ "নোমস্কর"
77
+ ],
78
+ "ms": 62.31514000000061
79
+ },
80
+ {
81
+ "input": "ami",
82
+ "output": "আমি",
83
+ "top8": [
84
+ "আমি",
85
+ "অমি",
86
+ "এমি",
87
+ "আমী",
88
+ "অামি",
89
+ "এএমই",
90
+ "এএম"
91
+ ],
92
+ "ms": 42.25174700000025
93
+ },
94
+ {
95
+ "input": "bhalo",
96
+ "output": "ভালো",
97
+ "top8": [
98
+ "ভালো",
99
+ "ভাল",
100
+ "ভালও",
101
+ "ভলো",
102
+ "বলো",
103
+ "ভল",
104
+ "ভা",
105
+ "ভলও"
106
+ ],
107
+ "ms": 36.55845999999838
108
+ },
109
+ {
110
+ "input": "tumi kemon acho",
111
+ "output": "তুমি কেমন আছো",
112
+ "top8": null,
113
+ "ms": 95.42537099999936
114
+ }
115
+ ],
116
+ "te": [
117
+ {
118
+ "input": "namaskaram",
119
+ "output": "నమస్కారం",
120
+ "top8": [
121
+ "నమస్కారం",
122
+ "నమస్కరం",
123
+ "నామస్కారం",
124
+ "నమస్కారమ్",
125
+ "నామస్కరం",
126
+ "నమాస్కారం",
127
+ "నమస్కారంా",
128
+ "నమస్కారంో"
129
+ ],
130
+ "ms": 57.50473300000158
131
+ },
132
+ {
133
+ "input": "nenu",
134
+ "output": "నేను",
135
+ "top8": [
136
+ "నేను",
137
+ "నెను",
138
+ "నేనూ",
139
+ "నను",
140
+ "నే",
141
+ "ఏను",
142
+ "నేన",
143
+ "నె"
144
+ ],
145
+ "ms": 27.787594999999783
146
+ },
147
+ {
148
+ "input": "manchi",
149
+ "output": "మంచి",
150
+ "top8": [
151
+ "మంచి",
152
+ "మాంచి",
153
+ "మంచీ",
154
+ "మన్చి",
155
+ "మం",
156
+ "మంఛి",
157
+ "మంచ"
158
+ ],
159
+ "ms": 34.4755350000014
160
+ },
161
+ {
162
+ "input": "meeru ela unnaru",
163
+ "output": "మీరు ఎలా ఉన్నారు",
164
+ "top8": null,
165
+ "ms": 103.91343400000005
166
+ }
167
+ ],
168
+ "kn": [
169
+ {
170
+ "input": "namaskara",
171
+ "output": "ನಮಸ್ಕಾರ",
172
+ "top8": [
173
+ "ನಮಸ್ಕಾರ",
174
+ "ನಮಸ್ಕರ",
175
+ "ನಮಸ್ಕರಾ",
176
+ "ನಮಾಸ್ಕರ",
177
+ "ನಮಸಕರ",
178
+ "ನಾಮಸ್ಕರ",
179
+ "ನಮಸಕಾರ",
180
+ "ನಮಸ್ಕಕರ"
181
+ ],
182
+ "ms": 46.95310299999983
183
+ },
184
+ {
185
+ "input": "nanu",
186
+ "output": "ನನು",
187
+ "top8": [
188
+ "ನನು",
189
+ "ನಂ",
190
+ "ನರು",
191
+ "ನಣು",
192
+ "ನಮು",
193
+ "ನಾನ",
194
+ "ಣನು",
195
+ "ನ"
196
+ ],
197
+ "ms": 27.71208200000001
198
+ },
199
+ {
200
+ "input": "chennagide",
201
+ "output": "ಚೆನ್ನಾಗಿದೆ",
202
+ "top8": [
203
+ "ಚೆನ್ನಾಗಿದೆ",
204
+ "ಚನ್ನಾಗಿದೆ",
205
+ "ಚೆನ್ನಗಿದೆ",
206
+ "ಚೆಣ್ಣಾಗಿದೆ",
207
+ "ಚೆನ್ನಾಗಿಡೆ",
208
+ "ಚೇನ್ನಾಗಿದೆ",
209
+ "ಚೆನ್ನಾಗಿದೇ",
210
+ "ಚೆನ್ನಾಗಿದಾ"
211
+ ],
212
+ "ms": 61.05912199999875
213
+ },
214
+ {
215
+ "input": "neevu hegiddira",
216
+ "output": "ನೀವು ಹೇಗಿದ್ದೀರಾ",
217
+ "top8": null,
218
+ "ms": 84.23852799999986
219
+ }
220
+ ],
221
+ "ta": [
222
+ {
223
+ "input": "vanakkam",
224
+ "output": "வணக்கம்",
225
+ "top8": [
226
+ "வணக்கம்",
227
+ "வனக்கம்",
228
+ "வண",
229
+ "வணாக்கம்",
230
+ "வாணக்கம்",
231
+ "வானக்கம்",
232
+ "வநக்கம்",
233
+ "வணக்கம"
234
+ ],
235
+ "ms": 61.89169000000038
236
+ },
237
+ {
238
+ "input": "naan",
239
+ "output": "ணன்",
240
+ "top8": [
241
+ "ணன்",
242
+ "னா",
243
+ "னன்",
244
+ "ணண்",
245
+ "ணண",
246
+ "ணன",
247
+ "ண",
248
+ "ன"
249
+ ],
250
+ "ms": 24.088208000001032
251
+ },
252
+ {
253
+ "input": "nandri",
254
+ "output": "நன்றி",
255
+ "top8": [
256
+ "நன்றி",
257
+ "நந்திரி",
258
+ "நந்த்ரி",
259
+ "நான்றி",
260
+ "னன்றி",
261
+ "நண்ட்ரி",
262
+ "நனத்ரி",
263
+ "ந"
264
+ ],
265
+ "ms": 41.495796000001064
266
+ },
267
+ {
268
+ "input": "neenga eppadi irukeenga",
269
+ "output": "நீங்க எப்படி இருகீங்க",
270
+ "top8": null,
271
+ "ms": 139.49065300000075
272
+ }
273
+ ]
274
+ }
evaluation/gguf_independent_validation.json ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "aksharalp-hi-word-q4.gguf": {
3
+ "tensors": 1,
4
+ "metadata_keys": 18
5
+ },
6
+ "aksharalp-hi-sentence-q4.gguf": {
7
+ "tensors": 1,
8
+ "metadata_keys": 18
9
+ },
10
+ "aksharalp-ta-word-f16.gguf": {
11
+ "tensors": 94,
12
+ "metadata_keys": 16
13
+ },
14
+ "aksharalp-kn-word-q4.gguf": {
15
+ "tensors": 1,
16
+ "metadata_keys": 17
17
+ },
18
+ "aksharalp-bn-word-f16.gguf": {
19
+ "tensors": 94,
20
+ "metadata_keys": 16
21
+ },
22
+ "aksharalp-te-word-f16.gguf": {
23
+ "tensors": 94,
24
+ "metadata_keys": 16
25
+ },
26
+ "aksharalp-kn-word-f16.gguf": {
27
+ "tensors": 94,
28
+ "metadata_keys": 16
29
+ },
30
+ "aksharalp-ta-word-q4.gguf": {
31
+ "tensors": 1,
32
+ "metadata_keys": 17
33
+ },
34
+ "aksharalp-te-word-q4.gguf": {
35
+ "tensors": 1,
36
+ "metadata_keys": 17
37
+ },
38
+ "aksharalp-hi-sentence-f16.gguf": {
39
+ "tensors": 12,
40
+ "metadata_keys": 16
41
+ },
42
+ "aksharalp-bn-word-q4.gguf": {
43
+ "tensors": 1,
44
+ "metadata_keys": 17
45
+ },
46
+ "aksharalp-hi-word-f16.gguf": {
47
+ "tensors": 94,
48
+ "metadata_keys": 17
49
+ },
50
+ "aksharalp-hi-sentence-q8.gguf": {
51
+ "tensors": 1,
52
+ "metadata_keys": 18
53
+ }
54
+ }
evaluation/gguf_parity.json ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "sample": "First 40 alphabetic ASCII roman words per full Aksharantar VALID; first 12 Dakshina VALID sentences with <=12 input words. No test used.",
3
+ "word": {
4
+ "hi": {
5
+ "max_abs_f16_weight_delta": 0.00091552734375,
6
+ "q4": {
7
+ "n": 40,
8
+ "same_top1": 40,
9
+ "same_top8_order": 40,
10
+ "same_top8_set": 40
11
+ },
12
+ "f16": {
13
+ "n": 40,
14
+ "same_top1": 40,
15
+ "same_top8_order": 40,
16
+ "same_top8_set": 40
17
+ }
18
+ },
19
+ "bn": {
20
+ "max_abs_f16_weight_delta": 0.0009765625,
21
+ "q4": {
22
+ "n": 40,
23
+ "same_top1": 40,
24
+ "same_top8_order": 40,
25
+ "same_top8_set": 40
26
+ },
27
+ "f16": {
28
+ "n": 40,
29
+ "same_top1": 40,
30
+ "same_top8_order": 40,
31
+ "same_top8_set": 40
32
+ }
33
+ },
34
+ "te": {
35
+ "max_abs_f16_weight_delta": 0.0009765625,
36
+ "q4": {
37
+ "n": 40,
38
+ "same_top1": 40,
39
+ "same_top8_order": 40,
40
+ "same_top8_set": 40
41
+ },
42
+ "f16": {
43
+ "n": 40,
44
+ "same_top1": 40,
45
+ "same_top8_order": 40,
46
+ "same_top8_set": 40
47
+ }
48
+ },
49
+ "kn": {
50
+ "max_abs_f16_weight_delta": 0.0009765625,
51
+ "q4": {
52
+ "n": 40,
53
+ "same_top1": 40,
54
+ "same_top8_order": 40,
55
+ "same_top8_set": 40
56
+ },
57
+ "f16": {
58
+ "n": 40,
59
+ "same_top1": 40,
60
+ "same_top8_order": 40,
61
+ "same_top8_set": 40
62
+ }
63
+ },
64
+ "ta": {
65
+ "max_abs_f16_weight_delta": 0.0009765625,
66
+ "q4": {
67
+ "n": 40,
68
+ "same_top1": 40,
69
+ "same_top8_order": 40,
70
+ "same_top8_set": 40
71
+ },
72
+ "f16": {
73
+ "n": 40,
74
+ "same_top1": 40,
75
+ "same_top8_order": 40,
76
+ "same_top8_set": 40
77
+ }
78
+ }
79
+ },
80
+ "sentence": {
81
+ "n": 12,
82
+ "source_search_outputs": [
83
+ [
84
+ "विलुप्ति",
85
+ "की",
86
+ "कगार",
87
+ "पर",
88
+ "गुणकारी",
89
+ "तिखुर"
90
+ ],
91
+ [
92
+ "शाहनामा",
93
+ "नौरोज",
94
+ "के",
95
+ "त्योहार",
96
+ "को",
97
+ "महान",
98
+ "जमशेद",
99
+ "के",
100
+ "शासनकाल",
101
+ "से",
102
+ "जोड़ता",
103
+ "है",
104
+ "."
105
+ ],
106
+ [
107
+ "मेहरोत्रा",
108
+ ",",
109
+ "दर",
110
+ "न",
111
+ "."
112
+ ],
113
+ [
114
+ "दुर्गा",
115
+ "भाभी"
116
+ ],
117
+ [
118
+ "स्वामी",
119
+ "बृहस्पति"
120
+ ],
121
+ [
122
+ "बॉलीवुड",
123
+ ",",
124
+ "हिंदी",
125
+ "चलचित्र",
126
+ "उद्योग",
127
+ "भी",
128
+ "मुंबई",
129
+ "में",
130
+ "ही",
131
+ "स्थित",
132
+ "है",
133
+ "."
134
+ ],
135
+ [
136
+ "क्योंकि",
137
+ "ऐसे",
138
+ "शब्द",
139
+ "भाषा",
140
+ "के",
141
+ "मूल",
142
+ "शब्द",
143
+ "होते",
144
+ "हैं",
145
+ "."
146
+ ],
147
+ [
148
+ "अंडे",
149
+ "के",
150
+ "छिलके",
151
+ "अन्य",
152
+ "एकांकी",
153
+ "तथा",
154
+ "बीज",
155
+ "नाटक",
156
+ "-",
157
+ "मोहन",
158
+ "राकेश"
159
+ ],
160
+ [
161
+ "तीसरा",
162
+ "सप्तक",
163
+ "(",
164
+ "कविता",
165
+ "संग्रह",
166
+ ")",
167
+ ","
168
+ ],
169
+ [
170
+ "राह",
171
+ "चलते",
172
+ "एक",
173
+ "वैद्य",
174
+ "को",
175
+ "शंकर",
176
+ "मिल",
177
+ "जाता",
178
+ "है",
179
+ "."
180
+ ],
181
+ [
182
+ "अति",
183
+ "विशिष्ट",
184
+ "सेवा",
185
+ "मेडल"
186
+ ],
187
+ [
188
+ "सारे",
189
+ "ललहते",
190
+ "खेत",
191
+ ",",
192
+ "पेड़",
193
+ "-",
194
+ "पौधे",
195
+ ",",
196
+ "महल",
197
+ "एयर",
198
+ "झोपड़ियां",
199
+ "-",
200
+ "सब",
201
+ "की",
202
+ "सब",
203
+ "उजाड़",
204
+ "जाएंगी",
205
+ "."
206
+ ]
207
+ ],
208
+ "q8": {
209
+ "same_decoder_outputs": 12,
210
+ "max_abs_sequence_score_delta": 0.0,
211
+ "max_abs_weight_delta": 0.0
212
+ },
213
+ "q4": {
214
+ "same_decoder_outputs": 11,
215
+ "max_abs_sequence_score_delta": 6.343046009540558,
216
+ "max_abs_weight_delta": 0.2186431884765625
217
+ },
218
+ "f16": {
219
+ "same_decoder_outputs": 12,
220
+ "max_abs_sequence_score_delta": 0.008701682090759277,
221
+ "max_abs_weight_delta": 0.0009307861328125
222
+ }
223
+ },
224
+ "js_vs_python": {
225
+ "hi": {
226
+ "n": 40,
227
+ "same_top1": 40,
228
+ "same_top8_order": 40,
229
+ "same_top8_set": 40
230
+ },
231
+ "bn": {
232
+ "n": 40,
233
+ "same_top1": 40,
234
+ "same_top8_order": 40,
235
+ "same_top8_set": 40
236
+ },
237
+ "te": {
238
+ "n": 40,
239
+ "same_top1": 40,
240
+ "same_top8_order": 40,
241
+ "same_top8_set": 40
242
+ },
243
+ "kn": {
244
+ "n": 40,
245
+ "same_top1": 40,
246
+ "same_top8_order": 40,
247
+ "same_top8_set": 40
248
+ },
249
+ "ta": {
250
+ "n": 40,
251
+ "same_top1": 40,
252
+ "same_top8_order": 40,
253
+ "same_top8_set": 40
254
+ }
255
+ },
256
+ "js_sentence_vs_python": {
257
+ "n": 12,
258
+ "same_output": 12,
259
+ "mismatches": [],
260
+ "policy": "Both runtimes use plain reference token conversion; older UI brand/acronym heuristics are not used."
261
+ }
262
+ }
evaluation/gguf_sizes.json ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "aksharalp-hi-word-q4.gguf": {
3
+ "raw": 632416,
4
+ "gzip9": 562579,
5
+ "sha256": "737d14197514a2625eb9505eb0a22ebd62cd2a4b764c65761f4084599bb6eb36"
6
+ },
7
+ "aksharalp-hi-word-f16.gguf": {
8
+ "raw": 2026048,
9
+ "gzip9": 1348288,
10
+ "sha256": "3b6670429a226a39d357160691c634160667728bae8cfc7a711067424a2d224e"
11
+ },
12
+ "aksharalp-bn-word-q4.gguf": {
13
+ "raw": 591136,
14
+ "gzip9": 541220,
15
+ "sha256": "8311c77889381ce559d483b965027c2d409787ac090b146ae1af08060e4a4552"
16
+ },
17
+ "aksharalp-bn-word-f16.gguf": {
18
+ "raw": 1984864,
19
+ "gzip9": 1321177,
20
+ "sha256": "7a0b78e133bb3ed773f6a8bae5e3f1af4b75f5158de4e814efadeaa0002ed580"
21
+ },
22
+ "aksharalp-te-word-q4.gguf": {
23
+ "raw": 591008,
24
+ "gzip9": 542744,
25
+ "sha256": "56b9f956b69e4fcf9e8d3f23c9391a091208651e90b06e2bde6d7f46d7cb6a60"
26
+ },
27
+ "aksharalp-te-word-f16.gguf": {
28
+ "raw": 1984608,
29
+ "gzip9": 1329379,
30
+ "sha256": "3c556cec413db8f1485a1ee9ea237406f967937019e0d9d6076c3ae41b7a6231"
31
+ },
32
+ "aksharalp-kn-word-q4.gguf": {
33
+ "raw": 591008,
34
+ "gzip9": 540642,
35
+ "sha256": "0e000f7fc0e52bbcb1f05753a46e6de50d1a1bdd6bb0ef46303361b9c39138ed"
36
+ },
37
+ "aksharalp-kn-word-f16.gguf": {
38
+ "raw": 1984608,
39
+ "gzip9": 1319622,
40
+ "sha256": "c9d273c8339864bd37c2b051cab60d2741c1d71e56e8c27d00a981ff28ef3864"
41
+ },
42
+ "aksharalp-ta-word-q4.gguf": {
43
+ "raw": 591008,
44
+ "gzip9": 542094,
45
+ "sha256": "f270e77e6355d53dc140f7cc7282551f5126302bcfbdbb17d802861510896c04"
46
+ },
47
+ "aksharalp-ta-word-f16.gguf": {
48
+ "raw": 1984608,
49
+ "gzip9": 1328348,
50
+ "sha256": "3baf8764ecc77250666eb3f8eb83f2d7a33763e13227e2d6db8272868dd5a864"
51
+ },
52
+ "aksharalp-hi-sentence-q8.gguf": {
53
+ "raw": 3488192,
54
+ "gzip9": 2981426,
55
+ "sha256": "5ff23bc8d131b560ee29c79ee16d15294600149200948433d76f29f4b8c3e496"
56
+ },
57
+ "aksharalp-hi-sentence-q4.gguf": {
58
+ "raw": 2110912,
59
+ "gzip9": 1596808,
60
+ "sha256": "8ecc83bc618b9c67173328a34f0f40e5c83ce77a993a6279acdeae6d724690db"
61
+ },
62
+ "aksharalp-hi-sentence-f16.gguf": {
63
+ "raw": 5790336,
64
+ "gzip9": 5159347,
65
+ "sha256": "446ff8f003c3680e9098df94727f8eb67a6a05a7c418888fb0bfeeb2742e098e"
66
+ }
67
+ }
evaluation/hindi_existing_results.json ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "word": {
3
+ "ours": {
4
+ "n": 9991,
5
+ "top1": 70.04,
6
+ "top1_ci95": [
7
+ 69.16,
8
+ 70.9
9
+ ],
10
+ "top3": 88.31,
11
+ "top8": 93.63
12
+ },
13
+ "indicxlit_full": {
14
+ "top1": 69.33,
15
+ "top1_ci95": [
16
+ 68.44,
17
+ 70.23
18
+ ],
19
+ "top3": 85.4,
20
+ "top8": 88.29
21
+ },
22
+ "paired_ours_minus_indicxlit": {
23
+ "top1": [
24
+ 0.71,
25
+ -0.04,
26
+ 1.4
27
+ ]
28
+ }
29
+ },
30
+ "sentence": {
31
+ "n": 4976,
32
+ "baseline_word_accuracy": 89.02,
33
+ "word_accuracy": 89.44,
34
+ "baseline_sentence_em": 25.7,
35
+ "sentence_em": 26.73,
36
+ "baseline_cer": 4.07,
37
+ "cer": 3.93,
38
+ "baseline_wer": 11.2,
39
+ "wer": 10.78,
40
+ "source": "https://huggingface.co/sourojitdotcom/AksharaLP-Sentence-2.8M",
41
+ "not_retested": true
42
+ }
43
+ }
evaluation/indicxlit_storage.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "parameters": 11487744,
3
+ "fp32_parameter_bytes": 45950976,
4
+ "dictionaries": {
5
+ "hi": {
6
+ "raw": 358118856,
7
+ "zip_compressed": 42255573
8
+ },
9
+ "bn": {
10
+ "raw": 403487845,
11
+ "zip_compressed": 45529651
12
+ },
13
+ "te": {
14
+ "raw": 761940466,
15
+ "zip_compressed": 84164136
16
+ },
17
+ "kn": {
18
+ "raw": 1045504407,
19
+ "zip_compressed": 114183601
20
+ },
21
+ "ta": {
22
+ "raw": 1044620827,
23
+ "zip_compressed": 104546002
24
+ }
25
+ },
26
+ "note": "Published dictionary JSON archive member bytes, without repacking; ZIP is not gzip-9. FP32 parameter bytes excludes serialization overhead. IndicXlit supports more languages than this bundle."
27
+ }
evaluation/kn_frozen.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": {
3
+ "xlit.q4": "3788fd545c297febb7dbbdbdd780e831042395b4ec99260b95c963607afc8664",
4
+ "dict.tsv.gz": "97035264d30eddb5810467dc5bbf088614221ea6ba2c3fff349c6bbd196bb644",
5
+ "params.json": "6076d9126ffce66630b634e47ac5b38d03cda3bce7ab4d07162aeb0cbc925356"
6
+ },
7
+ "fit": "VALID only; frozen before test",
8
+ "config": {
9
+ "format": "aksharalp-multiindic/1",
10
+ "language": "kn",
11
+ "beam": 8,
12
+ "nbest": 8,
13
+ "model": "xlit.q4",
14
+ "dictionary": "dict.tsv.gz",
15
+ "dictionary_words": 500000,
16
+ "dictionary_total": 23038668,
17
+ "unk": -16.952684623026045,
18
+ "reranker": {
19
+ "type": "linear",
20
+ "features": [
21
+ "sc",
22
+ "scn",
23
+ "inlex",
24
+ "uni",
25
+ "free0",
26
+ "rank",
27
+ "sc_rel",
28
+ "uni_rel"
29
+ ],
30
+ "w": [
31
+ 0.7762413620948792,
32
+ 0.47590601444244385,
33
+ 1.8370015621185303,
34
+ 0.37460821866989136,
35
+ -0.14645907282829285,
36
+ -0.39526867866516113,
37
+ 0.7561540603637695,
38
+ 0.40543532371520996
39
+ ],
40
+ "mu": [
41
+ -5.910224747519752,
42
+ -0.7570656360672512,
43
+ 0.21765999600123598,
44
+ -2.8589205075172495,
45
+ 0.12688805278368503,
46
+ 0.43655597360815745,
47
+ -4.8503386403114055,
48
+ -0.3713378327997356
49
+ ],
50
+ "sd": [
51
+ 2.385781122067686,
52
+ 0.5672953341562836,
53
+ 0.4126559674266211,
54
+ 5.516374375872727,
55
+ 0.3328485249185093,
56
+ 0.2863515606523184,
57
+ 2.4442090407344486,
58
+ 1.2445700746967023
59
+ ],
60
+ "fit_split": "all Aksharantar VALID",
61
+ "fit_n": 6981,
62
+ "l2": 0.001,
63
+ "seed": 0
64
+ }
65
+ },
66
+ "inventory": {
67
+ "parameters": 988928,
68
+ "model_raw": 588904,
69
+ "model_gz9": 539543,
70
+ "dict_raw": 16786145,
71
+ "dict_gz9": 2684083,
72
+ "dict_words": 500000
73
+ }
74
+ }
evaluation/latency.json ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "device": "Intel(R) Xeon(R) Processor",
3
+ "node": "v24.22.1",
4
+ "platform": "linux",
5
+ "method": "100 evenly spaced Aksharantar VALID words per language; 5 warmups; cache cleared per word; beam 8 with dictionary; Node single JS thread; load time excluded",
6
+ "languages": {
7
+ "hi": {
8
+ "n": 100,
9
+ "p50_ms": 207.02,
10
+ "p95_ms": 319.99,
11
+ "mean_ms": 217.33,
12
+ "load_ms": 7697.54
13
+ },
14
+ "bn": {
15
+ "n": 100,
16
+ "p50_ms": 51.61,
17
+ "p95_ms": 74,
18
+ "mean_ms": 52.41,
19
+ "load_ms": 0.02
20
+ },
21
+ "te": {
22
+ "n": 100,
23
+ "p50_ms": 51.77,
24
+ "p95_ms": 87.87,
25
+ "mean_ms": 54.86,
26
+ "load_ms": 398.74
27
+ },
28
+ "kn": {
29
+ "n": 100,
30
+ "p50_ms": 55.35,
31
+ "p95_ms": 100.77,
32
+ "mean_ms": 59.86,
33
+ "load_ms": 380.85
34
+ },
35
+ "ta": {
36
+ "n": 100,
37
+ "p50_ms": 60.98,
38
+ "p95_ms": 103.39,
39
+ "mean_ms": 63.09,
40
+ "load_ms": 451.39
41
+ }
42
+ }
43
+ }
evaluation/multiindic_test.json ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "protocol": "Frozen q4 beam-8 + eight-feature linear reranker fitted on all VALID; one final test per artifact; any-reference NFC and zero-width stripped; 2000 bootstrap replicates, seed 0 (scorer defaults)",
3
+ "languages": {
4
+ "bn": {
5
+ "ours": {
6
+ "n": 13367,
7
+ "top1": 64.64,
8
+ "top1_ci95": [
9
+ 63.83,
10
+ 65.44
11
+ ],
12
+ "top3": 75.66,
13
+ "top3_ci95": [
14
+ 74.9,
15
+ 76.38
16
+ ],
17
+ "top8": 77.97,
18
+ "top8_ci95": [
19
+ 77.23,
20
+ 78.66
21
+ ]
22
+ },
23
+ "indicxlit_full": {
24
+ "n": 13367,
25
+ "top1": 70.52,
26
+ "top1_ci95": [
27
+ 69.76,
28
+ 71.34
29
+ ],
30
+ "top3": 82.25,
31
+ "top3_ci95": [
32
+ 81.61,
33
+ 82.89
34
+ ],
35
+ "top8": 85.44,
36
+ "top8_ci95": [
37
+ 84.86,
38
+ 86.06
39
+ ]
40
+ },
41
+ "paired_ours_minus_indicxlit": {
42
+ "top1": [
43
+ -5.88,
44
+ -6.52,
45
+ -5.22
46
+ ],
47
+ "top3": [
48
+ -6.59,
49
+ -7.18,
50
+ -5.97
51
+ ],
52
+ "top8": [
53
+ -7.47,
54
+ -8.05,
55
+ -6.89
56
+ ]
57
+ },
58
+ "frozen_sha256": {
59
+ "xlit.q4": "9f099f0b9524f446debf1d3f0ae1dc7ed1e88625c33f6dbb7b45e0348bf76376",
60
+ "dict.tsv.gz": "edb2ad0bd7de4e87bd4d49ac406a9ef01011451bf23ac82d4c063bb9cce1dc2b",
61
+ "params.json": "e0bd564ad61f6dcef8eca6bf3d64f8b231146c9885aa4c5b282a2b2420606c4f"
62
+ },
63
+ "test_rows_sha256": "de2dc6a45db144a32074c586b2b92f6af07e9b3b4fef495150d62c59c31c9dfd",
64
+ "baseline_predictions_sha256": "e3e72158035ae04c46691f75eb85df3b59959e9d801cd9099af26148cf7a5c9e",
65
+ "baseline_missing_empty_inputs": 1,
66
+ "baseline_note": "Existing IndicXlit PyTorch port beam-8 + full dictionary predictions, rescored on exactly the same rows; no new baseline inference.",
67
+ "size": {
68
+ "parameters": 989056,
69
+ "model_raw": 589040,
70
+ "model_gz9": 540107,
71
+ "dict_raw": 14068340,
72
+ "dict_gz9": 2508610,
73
+ "dict_words": 500000
74
+ }
75
+ },
76
+ "te": {
77
+ "ours": {
78
+ "n": 9936,
79
+ "top1": 78.74,
80
+ "top1_ci95": [
81
+ 77.93,
82
+ 79.53
83
+ ],
84
+ "top3": 86.87,
85
+ "top3_ci95": [
86
+ 86.2,
87
+ 87.52
88
+ ],
89
+ "top8": 88.63,
90
+ "top8_ci95": [
91
+ 88.0,
92
+ 89.25
93
+ ]
94
+ },
95
+ "indicxlit_full": {
96
+ "n": 9936,
97
+ "top1": 82.42,
98
+ "top1_ci95": [
99
+ 81.65,
100
+ 83.14
101
+ ],
102
+ "top3": 90.63,
103
+ "top3_ci95": [
104
+ 90.04,
105
+ 91.18
106
+ ],
107
+ "top8": 92.75,
108
+ "top8_ci95": [
109
+ 92.21,
110
+ 93.26
111
+ ]
112
+ },
113
+ "paired_ours_minus_indicxlit": {
114
+ "top1": [
115
+ -3.67,
116
+ -4.27,
117
+ -3.07
118
+ ],
119
+ "top3": [
120
+ -3.76,
121
+ -4.27,
122
+ -3.21
123
+ ],
124
+ "top8": [
125
+ -4.13,
126
+ -4.59,
127
+ -3.62
128
+ ]
129
+ },
130
+ "frozen_sha256": {
131
+ "xlit.q4": "aea094e46a3c61eb9487d1aaba3824d3d9dc60f3f1cf6b6f99cadf04fd4318f9",
132
+ "dict.tsv.gz": "20612a52ccf39c816d3068e78e9e9a3aa9aed9156f10de45ebbd543363943640",
133
+ "params.json": "653f897efe1af78bb52149f2a18dc804b49ecac9c93add867871e64f56e07470"
134
+ },
135
+ "test_rows_sha256": "698de748186eb92a43baed29e1e0aa9a15acbd5b20a79b3a4fd7db4b336ceda1",
136
+ "baseline_predictions_sha256": "6c05dfc219ebc012fd96bac5f5b9586fd5ac35d0856a36af28c7362b04a31c82",
137
+ "baseline_note": "Existing IndicXlit PyTorch port beam-8 + full dictionary predictions, rescored on exactly the same rows; no new baseline inference.",
138
+ "size": {
139
+ "parameters": 988928,
140
+ "model_raw": 588904,
141
+ "model_gz9": 541242,
142
+ "dict_raw": 15989177,
143
+ "dict_gz9": 2680527,
144
+ "dict_words": 500000
145
+ }
146
+ },
147
+ "kn": {
148
+ "ours": {
149
+ "n": 11165,
150
+ "top1": 77.37,
151
+ "top1_ci95": [
152
+ 76.61,
153
+ 78.13
154
+ ],
155
+ "top3": 85.8,
156
+ "top3_ci95": [
157
+ 85.16,
158
+ 86.46
159
+ ],
160
+ "top8": 87.68,
161
+ "top8_ci95": [
162
+ 87.08,
163
+ 88.32
164
+ ]
165
+ },
166
+ "indicxlit_full": {
167
+ "n": 11165,
168
+ "top1": 82.61,
169
+ "top1_ci95": [
170
+ 81.92,
171
+ 83.29
172
+ ],
173
+ "top3": 90.68,
174
+ "top3_ci95": [
175
+ 90.16,
176
+ 91.2
177
+ ],
178
+ "top8": 92.41,
179
+ "top8_ci95": [
180
+ 91.94,
181
+ 92.9
182
+ ]
183
+ },
184
+ "paired_ours_minus_indicxlit": {
185
+ "top1": [
186
+ -5.24,
187
+ -5.83,
188
+ -4.67
189
+ ],
190
+ "top3": [
191
+ -4.87,
192
+ -5.39,
193
+ -4.39
194
+ ],
195
+ "top8": [
196
+ -4.73,
197
+ -5.19,
198
+ -4.26
199
+ ]
200
+ },
201
+ "frozen_sha256": {
202
+ "xlit.q4": "3788fd545c297febb7dbbdbdd780e831042395b4ec99260b95c963607afc8664",
203
+ "dict.tsv.gz": "97035264d30eddb5810467dc5bbf088614221ea6ba2c3fff349c6bbd196bb644",
204
+ "params.json": "6076d9126ffce66630b634e47ac5b38d03cda3bce7ab4d07162aeb0cbc925356"
205
+ },
206
+ "test_rows_sha256": "5bfe000c741316d700f4caae046ed05c4cdcaf485ef31707f6254bcc4c9abbd9",
207
+ "baseline_predictions_sha256": "928c3375ffdbca7811a36284ac4afb70b8b5192d28aa35f88a6291f941373407",
208
+ "baseline_note": "Existing IndicXlit PyTorch port beam-8 + full dictionary predictions, rescored on exactly the same rows; no new baseline inference.",
209
+ "size": {
210
+ "parameters": 988928,
211
+ "model_raw": 588904,
212
+ "model_gz9": 539543,
213
+ "dict_raw": 16786145,
214
+ "dict_gz9": 2684083,
215
+ "dict_words": 500000
216
+ }
217
+ },
218
+ "ta": {
219
+ "ours": {
220
+ "n": 11405,
221
+ "top1": 71.78,
222
+ "top1_ci95": [
223
+ 71.01,
224
+ 72.61
225
+ ],
226
+ "top3": 80.05,
227
+ "top3_ci95": [
228
+ 79.37,
229
+ 80.78
230
+ ],
231
+ "top8": 82.61,
232
+ "top8_ci95": [
233
+ 81.95,
234
+ 83.31
235
+ ]
236
+ },
237
+ "indicxlit_full": {
238
+ "n": 11405,
239
+ "top1": 75.95,
240
+ "top1_ci95": [
241
+ 75.2,
242
+ 76.73
243
+ ],
244
+ "top3": 84.21,
245
+ "top3_ci95": [
246
+ 83.56,
247
+ 84.89
248
+ ],
249
+ "top8": 87.26,
250
+ "top8_ci95": [
251
+ 86.66,
252
+ 87.87
253
+ ]
254
+ },
255
+ "paired_ours_minus_indicxlit": {
256
+ "top1": [
257
+ -4.16,
258
+ -4.75,
259
+ -3.59
260
+ ],
261
+ "top3": [
262
+ -4.16,
263
+ -4.66,
264
+ -3.63
265
+ ],
266
+ "top8": [
267
+ -4.65,
268
+ -5.12,
269
+ -4.16
270
+ ]
271
+ },
272
+ "frozen_sha256": {
273
+ "xlit.q4": "5dd185a033db79835fede66ab41a25f5897f24ad5d7c8ecc9864ca6ec167fdc0",
274
+ "dict.tsv.gz": "4d14f514020e7737144c22991e9324a29734b5ab203bef3b68ec6d9f51e7cddb",
275
+ "params.json": "f4520a8320f02573e6b8b6530a372c980903fbf757999540698957e8eec58385"
276
+ },
277
+ "test_rows_sha256": "464c11fec7cfa71680a01c57899aeb691d88f1a871b1c7976184b0a21e2d75a6",
278
+ "baseline_predictions_sha256": "0c2272e9423eb22e3fa04037a9973a777d50070cb9f3bcd03d4037865fd71bc3",
279
+ "baseline_note": "Existing IndicXlit PyTorch port beam-8 + full dictionary predictions, rescored on exactly the same rows; no new baseline inference.",
280
+ "size": {
281
+ "parameters": 988928,
282
+ "model_raw": 588904,
283
+ "model_gz9": 540818,
284
+ "dict_raw": 18105290,
285
+ "dict_gz9": 2588634,
286
+ "dict_words": 500000
287
+ }
288
+ },
289
+ "hi": {
290
+ "ours": {
291
+ "n": 9991,
292
+ "top1": 70.04,
293
+ "top1_ci95": [
294
+ 69.16,
295
+ 70.9
296
+ ],
297
+ "top3": 88.31,
298
+ "top8": 93.63
299
+ },
300
+ "indicxlit_full": {
301
+ "top1": 69.33,
302
+ "top1_ci95": [
303
+ 68.44,
304
+ 70.23
305
+ ],
306
+ "top3": 85.4,
307
+ "top8": 88.29
308
+ },
309
+ "paired_ours_minus_indicxlit": {
310
+ "top1": [
311
+ 0.71,
312
+ -0.04,
313
+ 1.4
314
+ ]
315
+ }
316
+ }
317
+ }
318
+ }
evaluation/release_summary.json ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "AksharaLP-MultiIndic-8M",
3
+ "parameters": 7715520,
4
+ "word_parameters": 4944768,
5
+ "sizes": {
6
+ "languages": {
7
+ "hi": {
8
+ "raw": 16221771,
9
+ "gzip9": 7905041
10
+ },
11
+ "bn": {
12
+ "raw": 14658574,
13
+ "gzip9": 3049313
14
+ },
15
+ "te": {
16
+ "raw": 16579274,
17
+ "gzip9": 3222365
18
+ },
19
+ "kn": {
20
+ "raw": 17376245,
21
+ "gzip9": 3224222
22
+ },
23
+ "ta": {
24
+ "raw": 18695389,
25
+ "gzip9": 3130045
26
+ }
27
+ },
28
+ "shared_js_and_config": {
29
+ "raw": 68781,
30
+ "gzip9": 23186
31
+ },
32
+ "word_only": {
33
+ "raw": 83600034,
34
+ "gzip9": 20554172
35
+ },
36
+ "sentence_addon": {
37
+ "raw": 3256675,
38
+ "gzip9": 2928839
39
+ },
40
+ "with_sentence": {
41
+ "raw": 86856709,
42
+ "gzip9": 23483011
43
+ },
44
+ "note": "Canonical payload bytes and sum of per-file gzip -9 equivalents; MB=1,000,000 bytes. Excludes documentation, Python reference, fonts, charts, GGUF alternatives and duplicate compressed companions. Dictionaries are frequency data, not neural parameters. Runtime computes float32 after dequantization; RAM is not file size."
45
+ },
46
+ "gguf_files": 13,
47
+ "gguf_total": {
48
+ "raw": 24350752,
49
+ "gzip9": 19113674
50
+ }
51
+ }
evaluation/sizes.json ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "languages": {
3
+ "hi": {
4
+ "raw": 16221771,
5
+ "gzip9": 7905041
6
+ },
7
+ "bn": {
8
+ "raw": 14658574,
9
+ "gzip9": 3049313
10
+ },
11
+ "te": {
12
+ "raw": 16579274,
13
+ "gzip9": 3222365
14
+ },
15
+ "kn": {
16
+ "raw": 17376245,
17
+ "gzip9": 3224222
18
+ },
19
+ "ta": {
20
+ "raw": 18695389,
21
+ "gzip9": 3130045
22
+ }
23
+ },
24
+ "shared_js_and_config": {
25
+ "raw": 68781,
26
+ "gzip9": 23186
27
+ },
28
+ "word_only": {
29
+ "raw": 83600034,
30
+ "gzip9": 20554172
31
+ },
32
+ "sentence_addon": {
33
+ "raw": 3256675,
34
+ "gzip9": 2928839
35
+ },
36
+ "with_sentence": {
37
+ "raw": 86856709,
38
+ "gzip9": 23483011
39
+ },
40
+ "note": "Canonical payload bytes and sum of per-file gzip -9 equivalents; MB=1,000,000 bytes. Excludes documentation, Python reference, fonts, charts, GGUF alternatives and duplicate compressed companions. Dictionaries are frequency data, not neural parameters. Runtime computes float32 after dequantization; RAM is not file size."
41
+ }
evaluation/ta_frozen.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": {
3
+ "xlit.q4": "5dd185a033db79835fede66ab41a25f5897f24ad5d7c8ecc9864ca6ec167fdc0",
4
+ "dict.tsv.gz": "4d14f514020e7737144c22991e9324a29734b5ab203bef3b68ec6d9f51e7cddb",
5
+ "params.json": "f4520a8320f02573e6b8b6530a372c980903fbf757999540698957e8eec58385"
6
+ },
7
+ "fit": "VALID only; frozen before test",
8
+ "config": {
9
+ "format": "aksharalp-multiindic/1",
10
+ "language": "ta",
11
+ "beam": 8,
12
+ "nbest": 8,
13
+ "model": "xlit.q4",
14
+ "dictionary": "dict.tsv.gz",
15
+ "dictionary_words": 500000,
16
+ "dictionary_total": 20232595,
17
+ "unk": -16.82280552518196,
18
+ "reranker": {
19
+ "type": "linear",
20
+ "features": [
21
+ "sc",
22
+ "scn",
23
+ "inlex",
24
+ "uni",
25
+ "free0",
26
+ "rank",
27
+ "sc_rel",
28
+ "uni_rel"
29
+ ],
30
+ "w": [
31
+ 0.9623245596885681,
32
+ 0.5414344072341919,
33
+ 1.828786015510559,
34
+ 0.003193225245922804,
35
+ -0.12186560034751892,
36
+ -0.003971157129853964,
37
+ 0.9330659508705139,
38
+ 0.5227358937263489
39
+ ],
40
+ "mu": [
41
+ -6.01308456526665,
42
+ -0.7381095081752549,
43
+ 0.22912272701362366,
44
+ -2.9371551977749273,
45
+ 0.1254346462976686,
46
+ 0.43728267685116573,
47
+ -4.8824671233512555,
48
+ -0.38838364800108277
49
+ ],
50
+ "sd": [
51
+ 2.449121527790252,
52
+ 0.5435378258591739,
53
+ 0.42026937018709404,
54
+ 5.483689004938199,
55
+ 0.3312121045929875,
56
+ 0.28639834850311835,
57
+ 2.5225466225846818,
58
+ 1.2477123699412511
59
+ ],
60
+ "fit_split": "all Aksharantar VALID",
61
+ "fit_n": 8802,
62
+ "l2": 0.001,
63
+ "seed": 0
64
+ }
65
+ },
66
+ "inventory": {
67
+ "parameters": 988928,
68
+ "model_raw": 588904,
69
+ "model_gz9": 540818,
70
+ "dict_raw": 18105290,
71
+ "dict_gz9": 2588634,
72
+ "dict_words": 500000
73
+ }
74
+ }
evaluation/te_frozen.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": {
3
+ "xlit.q4": "aea094e46a3c61eb9487d1aaba3824d3d9dc60f3f1cf6b6f99cadf04fd4318f9",
4
+ "dict.tsv.gz": "20612a52ccf39c816d3068e78e9e9a3aa9aed9156f10de45ebbd543363943640",
5
+ "params.json": "653f897efe1af78bb52149f2a18dc804b49ecac9c93add867871e64f56e07470"
6
+ },
7
+ "fit": "VALID only; frozen before test",
8
+ "config": {
9
+ "format": "aksharalp-multiindic/1",
10
+ "language": "te",
11
+ "beam": 8,
12
+ "nbest": 8,
13
+ "model": "xlit.q4",
14
+ "dictionary": "dict.tsv.gz",
15
+ "dictionary_words": 500000,
16
+ "dictionary_total": 24117810,
17
+ "unk": -16.998461171144285,
18
+ "reranker": {
19
+ "type": "linear",
20
+ "features": [
21
+ "sc",
22
+ "scn",
23
+ "inlex",
24
+ "uni",
25
+ "free0",
26
+ "rank",
27
+ "sc_rel",
28
+ "uni_rel"
29
+ ],
30
+ "w": [
31
+ 0.8893349170684814,
32
+ 0.42266765236854553,
33
+ 1.647674560546875,
34
+ 0.01816709153354168,
35
+ -0.09264160692691803,
36
+ -0.17859779298305511,
37
+ 0.8579440116882324,
38
+ 0.4983164668083191
39
+ ],
40
+ "mu": [
41
+ -5.577303872867701,
42
+ -0.7360878454967777,
43
+ 0.22874146800712822,
44
+ -3.057939543170047,
45
+ 0.12881207760330857,
46
+ 0.43559396119834565,
47
+ -4.512450547449157,
48
+ -0.4069890521202824
49
+ ],
50
+ "sd": [
51
+ 2.345458571867553,
52
+ 0.5172883363068768,
53
+ 0.4200233908567531,
54
+ 5.716765533673338,
55
+ 0.33499283014943054,
56
+ 0.28628677366288935,
57
+ 2.4278868542564216,
58
+ 1.326799761380314
59
+ ],
60
+ "fit_split": "all Aksharantar VALID",
61
+ "fit_n": 7662,
62
+ "l2": 0.001,
63
+ "seed": 0
64
+ }
65
+ },
66
+ "inventory": {
67
+ "parameters": 988928,
68
+ "model_raw": 588904,
69
+ "model_gz9": 541242,
70
+ "dict_raw": 15989177,
71
+ "dict_gz9": 2680527,
72
+ "dict_words": 500000
73
+ }
74
+ }
gguf/aksharalp-bn-word-f16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7a0b78e133bb3ed773f6a8bae5e3f1af4b75f5158de4e814efadeaa0002ed580
3
+ size 1984864
gguf/aksharalp-bn-word-q4.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8311c77889381ce559d483b965027c2d409787ac090b146ae1af08060e4a4552
3
+ size 591136
gguf/aksharalp-hi-sentence-f16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:446ff8f003c3680e9098df94727f8eb67a6a05a7c418888fb0bfeeb2742e098e
3
+ size 5790336
gguf/aksharalp-hi-sentence-q4.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8ecc83bc618b9c67173328a34f0f40e5c83ce77a993a6279acdeae6d724690db
3
+ size 2110912
gguf/aksharalp-hi-sentence-q8.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5ff23bc8d131b560ee29c79ee16d15294600149200948433d76f29f4b8c3e496
3
+ size 3488192
gguf/aksharalp-hi-word-f16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3b6670429a226a39d357160691c634160667728bae8cfc7a711067424a2d224e
3
+ size 2026048
gguf/aksharalp-hi-word-q4.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:737d14197514a2625eb9505eb0a22ebd62cd2a4b764c65761f4084599bb6eb36
3
+ size 632416
gguf/aksharalp-kn-word-f16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c9d273c8339864bd37c2b051cab60d2741c1d71e56e8c27d00a981ff28ef3864
3
+ size 1984608
gguf/aksharalp-kn-word-q4.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0e000f7fc0e52bbcb1f05753a46e6de50d1a1bdd6bb0ef46303361b9c39138ed
3
+ size 591008
gguf/aksharalp-ta-word-f16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3baf8764ecc77250666eb3f8eb83f2d7a33763e13227e2d6db8272868dd5a864
3
+ size 1984608
gguf/aksharalp-ta-word-q4.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f270e77e6355d53dc140f7cc7282551f5126302bcfbdbb17d802861510896c04
3
+ size 591008
gguf/aksharalp-te-word-f16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3c556cec413db8f1485a1ee9ea237406f967937019e0d9d6076c3ae41b7a6231
3
+ size 1984608
gguf/aksharalp-te-word-q4.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:56b9f956b69e4fcf9e8d3f23c9391a091208651e90b06e2bde6d7f46d7cb6a60
3
+ size 591008
gguf_read.py ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Minimal, dependency-light reader for the GGUF files shipped with AksharaLP-MultiIndic-8M.
2
+
3
+ GGUF here is used as what it is: a portable, single-file tensor container -- a header, typed
4
+ key/value metadata, a tensor table, and aligned little-endian tensor data. It is NOT a
5
+ llama.cpp-loadable model: AksharaLP is a custom character-level encoder-decoder transformer with a
6
+ bespoke decoding stack (lexicon-trie candidate beam, hand-built candidate features, gates, MLP
7
+ reranker), and llama.cpp has no such architecture. This reader is the decoder-side tool: it returns
8
+ the weights our reference implementation (python/bhc/xlit_*.py) expects.
9
+
10
+ Only numpy is required. No torch, no llama.cpp, no gguf/gguf-py.
11
+
12
+ python gguf_read.py aksharalp-1m-v2-f16.gguf # summary
13
+ python gguf_read.py --json aksharalp-1m-v2-q4.gguf # metadata + tensor table
14
+
15
+ import gguf_read
16
+ w = gguf_read.load("aksharalp-1m-v2-f16.gguf") # -> Weights(cfg, state_dict, metadata)
17
+ w.state_dict["semb.weight"].shape # (30, 128) float32
18
+
19
+ Two container layouts are understood, both GGUF v3:
20
+
21
+ * aksharalp-1m-v2-q4.gguf one I8 tensor, ``aksharalp.q4.blob``: the byte-exact xlit.q4 file
22
+ (4-byte header length, JSON tensor table, then the payload). Decoded
23
+ here exactly as bhc/xlit_q.py:load_q does.
24
+ * aksharalp-1m-v2-f16.gguf every weight under its state_dict name at F16.
25
+ """
26
+ import json
27
+ import struct
28
+ import sys
29
+
30
+ import numpy as np
31
+
32
+ MAGIC = b"GGUF"
33
+ SUPPORTED_VERSIONS = (3,)
34
+
35
+ # GGUF metadata value types
36
+ GGUF_UINT8, GGUF_INT8, GGUF_UINT16, GGUF_INT16 = 0, 1, 2, 3
37
+ GGUF_UINT32, GGUF_INT32, GGUF_FLOAT32, GGUF_BOOL = 4, 5, 6, 7
38
+ GGUF_STRING, GGUF_ARRAY, GGUF_UINT64, GGUF_INT64, GGUF_FLOAT64 = 8, 9, 10, 11, 12
39
+
40
+ # GGML tensor types we can read: F32, F16, I8 (the three this model uses)
41
+ _GGML_F32, _GGML_F16, _GGML_I8 = 0, 1, 24
42
+ _TENSOR_DTYPES = {_GGML_F32: np.dtype("<f4"), _GGML_F16: np.dtype("<f2"), _GGML_I8: np.dtype("i1")}
43
+ _TENSOR_NAMES = {_GGML_F32: "F32", _GGML_F16: "F16", _GGML_I8: "I8"}
44
+ _KV_FMT = {GGUF_UINT8: "<B", GGUF_INT8: "<b", GGUF_UINT16: "<H", GGUF_INT16: "<h",
45
+ GGUF_UINT32: "<I", GGUF_INT32: "<i", GGUF_FLOAT32: "<f", GGUF_BOOL: "?",
46
+ GGUF_UINT64: "<Q", GGUF_INT64: "<q", GGUF_FLOAT64: "<d"}
47
+
48
+
49
+ class GGUFError(ValueError):
50
+ """Raised for anything that is not a well-formed GGUF v3 file we can read."""
51
+
52
+
53
+ class _Cursor:
54
+ def __init__(self, buf, pos=0):
55
+ self.buf, self.pos = buf, pos
56
+
57
+ def take(self, n):
58
+ if n < 0 or self.pos + n > len(self.buf):
59
+ raise GGUFError("truncated file: wanted %d bytes at %d, file is %d" % (n, self.pos, len(self.buf)))
60
+ b = self.buf[self.pos:self.pos + n]
61
+ self.pos += n
62
+ return b
63
+
64
+ def unpack(self, fmt):
65
+ n = struct.calcsize(fmt)
66
+ return struct.unpack(fmt, self.take(n))[0]
67
+
68
+ def string(self):
69
+ """GGUF strings: u64 length + UTF-8 bytes (not NUL-terminated)."""
70
+ n = self.unpack("Q")
71
+ return self.take(n).decode("utf-8")
72
+
73
+
74
+ class Tensor:
75
+ """One tensor: name, logical shape (GGML order reversed back to row-major), dtype, raw bytes."""
76
+
77
+ __slots__ = ("name", "shape", "type_id", "offset", "data")
78
+
79
+ def __init__(self, name, shape, type_id, offset, data):
80
+ self.name, self.shape, self.type_id, self.offset, self.data = name, shape, type_id, offset, data
81
+
82
+ @property
83
+ def dtype(self):
84
+ return _TENSOR_DTYPES[self.type_id]
85
+
86
+ @property
87
+ def type_name(self):
88
+ return _TENSOR_NAMES[self.type_id]
89
+
90
+ def array(self):
91
+ return np.frombuffer(self.data, dtype=self.dtype).reshape(self.shape)
92
+
93
+ def __repr__(self):
94
+ return "<Tensor %s %s %s>" % (self.name, self.type_name, tuple(self.shape))
95
+
96
+
97
+ class GGUF:
98
+ """Parsed GGUF file: header fields, typed metadata, tensor table, tensor data."""
99
+
100
+ def __init__(self, path):
101
+ self.path = str(path)
102
+ with open(self.path, "rb") as f:
103
+ self.buf = f.read()
104
+ self._parse()
105
+
106
+ # ---------------------------------------------------------------- parsing
107
+ def _parse(self):
108
+ c = _Cursor(self.buf)
109
+ self.magic = c.take(4)
110
+ if self.magic != MAGIC:
111
+ raise GGUFError("%s: not a GGUF file (magic %r)" % (self.path, self.magic))
112
+ self.version = c.unpack("I")
113
+ if self.version not in SUPPORTED_VERSIONS:
114
+ raise GGUFError("%s: GGUF version %d not supported (this reader knows %s)"
115
+ % (self.path, self.version, SUPPORTED_VERSIONS))
116
+ self.tensor_count = c.unpack("Q")
117
+ self.kv_count = c.unpack("Q")
118
+
119
+ self.metadata = {}
120
+ for _ in range(self.kv_count):
121
+ key = c.string()
122
+ self.metadata[key] = self._value(c)
123
+
124
+ self.alignment = int(self.metadata.get("general.alignment", 32))
125
+ if self.alignment <= 0 or self.alignment & (self.alignment - 1):
126
+ raise GGUFError("%s: general.alignment %d is not a power of two" % (self.path, self.alignment))
127
+
128
+ infos = []
129
+ for _ in range(self.tensor_count):
130
+ name = c.string()
131
+ n_dims = c.unpack("I")
132
+ if n_dims > 8:
133
+ raise GGUFError("%s: tensor %s declares %d dims" % (self.path, name, n_dims))
134
+ # GGML stores dims fastest-varying first; reverse to get the row-major shape.
135
+ dims = [c.unpack("Q") for _ in range(n_dims)]
136
+ type_id = c.unpack("I")
137
+ offset = c.unpack("Q")
138
+ infos.append((name, list(reversed(dims)), type_id, offset))
139
+
140
+ pad = (-c.pos) % self.alignment
141
+ self.data_start = c.pos + pad
142
+ if self.data_start > len(self.buf):
143
+ raise GGUFError("%s: tensor info block overruns the file" % self.path)
144
+
145
+ self.tensors = {}
146
+ self.order = []
147
+ for name, shape, type_id, offset in infos:
148
+ if type_id not in _TENSOR_DTYPES:
149
+ raise GGUFError("%s: tensor %s has GGML type %d, which this reader does not decode "
150
+ "(only F32/F16/I8 are used by these files)" % (self.path, name, type_id))
151
+ if name in self.tensors:
152
+ raise GGUFError("%s: duplicate tensor name %r" % (self.path, name))
153
+ numel = int(np.prod(shape, dtype=np.int64)) if shape else 1
154
+ nbytes = numel * _TENSOR_DTYPES[type_id].itemsize
155
+ start = self.data_start + offset
156
+ end = start + nbytes
157
+ if start < self.data_start or end > len(self.buf):
158
+ raise GGUFError("%s: tensor %s spans [%d, %d) outside the data section [%d, %d)"
159
+ % (self.path, name, start, end, self.data_start, len(self.buf)))
160
+ if offset % self.alignment:
161
+ raise GGUFError("%s: tensor %s offset %d is not aligned to %d"
162
+ % (self.path, name, offset, self.alignment))
163
+ self.tensors[name] = Tensor(name, shape, type_id, offset, self.buf[start:end])
164
+ self.order.append(name)
165
+
166
+ def _value(self, c, vtype=None):
167
+ if vtype is None:
168
+ vtype = c.unpack("I")
169
+ if vtype == GGUF_STRING:
170
+ return c.string()
171
+ if vtype == GGUF_ARRAY:
172
+ sub = c.unpack("I")
173
+ n = c.unpack("Q")
174
+ return [self._value(c, sub) for _ in range(n)]
175
+ if vtype in _KV_FMT:
176
+ return c.unpack(_KV_FMT[vtype])
177
+ raise GGUFError("%s: unknown metadata value type %d" % (self.path, vtype))
178
+
179
+ # ---------------------------------------------------------------- checks
180
+ def validate(self):
181
+ """Structural checks; returns a list of human-readable results, raises GGUFError on failure."""
182
+ notes = []
183
+ if len(self.tensors) != self.tensor_count:
184
+ raise GGUFError("tensor count mismatch: header says %d, parsed %d" % (self.tensor_count, len(self.tensors)))
185
+ notes.append("magic GGUF, version %d" % self.version)
186
+ notes.append("%d tensors, %d metadata keys" % (self.tensor_count, len(self.metadata)))
187
+ notes.append("alignment %d, data section at byte %d" % (self.alignment, self.data_start))
188
+ declared = self.metadata.get("general.architecture")
189
+ if declared is None:
190
+ raise GGUFError("general.architecture is missing")
191
+ for name in self.order:
192
+ t = self.tensors[name]
193
+ if t.offset + len(t.data) > len(self.buf) - self.data_start:
194
+ raise GGUFError("tensor %s overruns the data section" % name)
195
+ notes.append("all tensor offsets aligned and inside the data section")
196
+ arch = self.metadata.get("general.architecture")
197
+ notes.append("general.architecture = %r (custom; not a llama.cpp architecture)" % arch)
198
+ return notes
199
+
200
+ # ---------------------------------------------------------------- weights
201
+ def state_dict(self):
202
+ """Return {state_dict name: float32 ndarray} for the reference XlitTransformer."""
203
+ cfg, sd = self._weights()
204
+ return cfg, sd
205
+
206
+ def _weights(self):
207
+ if "aksharalp.nlm.blob" in self.tensors:
208
+ return _weights_from_nlm_blob(bytes(self.tensors["aksharalp.nlm.blob"].data))
209
+ if _BLOB_TENSOR in self.tensors:
210
+ return _weights_from_q4_blob(bytes(self.tensors[_BLOB_TENSOR].data))
211
+ cfg = _cfg_from_metadata(self.metadata)
212
+ sd = {}
213
+ for name in self.order:
214
+ if name.startswith(RERANKER_PREFIX):
215
+ continue # reranker matrices are carried separately; see reranker()
216
+ a = self.tensors[name].array()
217
+ sd[name] = np.ascontiguousarray(a, dtype=np.float32)
218
+ return cfg, sd
219
+
220
+ def reranker(self):
221
+ """{('core'|'pack', 'w1'|'b1'|'w2'|'b2'): float32 ndarray} from the F16 variant, else {}."""
222
+ out = {}
223
+ for name in self.order:
224
+ if not name.startswith(RERANKER_PREFIX):
225
+ continue
226
+ _, part, param = name.split(".")
227
+ out[(part, param)] = np.ascontiguousarray(self.tensors[name].array(), dtype=np.float32)
228
+ return out
229
+
230
+
231
+ _BLOB_TENSOR = "aksharalp.q4.blob"
232
+ RERANKER_PREFIX = "reranker."
233
+
234
+
235
+ def _cfg_from_metadata(md):
236
+ if "aksharalp.config_json" in md: return json.loads(md["aksharalp.config_json"])
237
+ def g(key, default=None):
238
+ v = md.get("aksharalp." + key, default)
239
+ if v is None:
240
+ raise GGUFError("metadata is missing aksharalp.%s" % key)
241
+ return v
242
+ cfg = {"d": int(g("d_model")), "h": int(g("n_heads")), "enc": int(g("encoder_layers")),
243
+ "dec": int(g("decoder_layers")), "ff": int(g("ff_dim")), "p": float(g("dropout", 0.1))}
244
+ return cfg
245
+
246
+
247
+ def _weights_from_q4_blob(blob):
248
+ """Decode the carried xlit.q4 bytes exactly as python/bhc/xlit_q.py:load_q does."""
249
+ n = int.from_bytes(blob[:4], "little")
250
+ meta = json.loads(blob[4:4 + n])
251
+ data = blob[4 + n:]
252
+ bits_default, G = int(meta["bits"]), int(meta["G"])
253
+ cfg = dict(meta["cfg"])
254
+ sd = {}
255
+ for k, shape, kind, off, ln in meta["tensors"]:
256
+ cnt = int(np.prod(shape))
257
+ if kind.startswith("q"):
258
+ bits = int(kind[1:]) if len(kind) > 1 else bits_default
259
+ if bits == 4:
260
+ raw = np.frombuffer(data, np.uint8, ln, off)
261
+ q = np.stack([raw >> 4, raw & 15], 1).reshape(-1)[:cnt].astype(np.int16) - 8
262
+ elif bits == 8:
263
+ q = np.frombuffer(data, np.int8, ln, off).astype(np.int16)
264
+ else:
265
+ raise GGUFError("unsupported q width %d for %s" % (bits, k))
266
+ r, c = shape
267
+ ng = (c + G - 1) // G
268
+ sc = np.frombuffer(data, np.float16, r * ng, off + ln).astype(np.float32).reshape(r, ng)
269
+ w = q.reshape(r, c).astype(np.float32) * np.repeat(sc, G, 1)[:, :c]
270
+ sd[k] = w
271
+ elif kind == "f16":
272
+ sd[k] = np.frombuffer(data, np.float16, cnt, off).astype(np.float32).reshape(shape).copy()
273
+ else:
274
+ raise GGUFError("unsupported tensor kind %r for %s" % (kind, k))
275
+ return cfg, sd
276
+
277
+
278
+ class Weights:
279
+ """Weights + config, in the shape the reference implementation consumes.
280
+
281
+ ``state_dict`` maps state_dict names (as in python/bhc/xlit_q.py:load_q) to float32 ndarrays;
282
+ ``cfg`` is the XlitTransformer config. To build the torch model the reference uses:
283
+
284
+ import torch
285
+ from bhc.xlit_model import XlitTransformer
286
+ m = XlitTransformer(**w.cfg)
287
+ m.load_state_dict({k: torch.from_numpy(v) for k, v in w.state_dict.items()})
288
+ m.eval()
289
+ """
290
+
291
+ def __init__(self, cfg, state_dict, metadata=None, source=None, reranker=None):
292
+ self.cfg, self.state_dict, self.metadata, self.source = cfg, state_dict, metadata or {}, source
293
+ self.reranker = reranker or {}
294
+
295
+ @property
296
+ def n_params(self):
297
+ return int(sum(v.size for v in self.state_dict.values()))
298
+
299
+ def __repr__(self):
300
+ return "<Weights %s: %d tensors, %d params>" % (self.source, len(self.state_dict), self.n_params)
301
+
302
+
303
+ def load(path):
304
+ """Parse either GGUF variant and return Weights (cfg + state_dict of float32 arrays)."""
305
+ g = GGUF(path)
306
+ cfg, sd = g.state_dict()
307
+ return Weights(cfg, sd, g.metadata, g.path, g.reranker())
308
+
309
+
310
+ def tensors(path):
311
+ """Yield (name, shape, type_name, float32 array) for every tensor in the file."""
312
+ g = GGUF(path)
313
+ for name in g.order:
314
+ t = g.tensors[name]
315
+ yield name, tuple(t.shape), t.type_name, t.array()
316
+
317
+
318
+ def q4_blob(path):
319
+ """Bytes of the carried xlit.q4 file from the compact GGUF (byte-identical to the original)."""
320
+ g = GGUF(path)
321
+ if _BLOB_TENSOR not in g.tensors:
322
+ raise GGUFError("%s carries no %s tensor (that is the F16 variant)" % (path, _BLOB_TENSOR))
323
+ return bytes(g.tensors[_BLOB_TENSOR].data)
324
+
325
+
326
+ def main(argv):
327
+ args = [a for a in argv[1:] if not a.startswith("-")]
328
+ flags = {a for a in argv[1:] if a.startswith("-")}
329
+ if not args:
330
+ print(__doc__)
331
+ return 2
332
+ g = GGUF(args[0])
333
+ if "--json" in flags:
334
+ print(json.dumps({"version": g.version, "tensors": g.tensor_count,
335
+ "metadata": g.metadata,
336
+ "tensor_table": [{"name": n, "shape": g.tensors[n].shape,
337
+ "type": g.tensors[n].type_name,
338
+ "offset": g.tensors[n].offset} for n in g.order]},
339
+ indent=1, ensure_ascii=False))
340
+ return 0
341
+ for line in g.validate():
342
+ print(line)
343
+ print("general.name = %r" % g.metadata.get("general.name"))
344
+ print("-- tensors --")
345
+ for n in g.order:
346
+ t = g.tensors[n]
347
+ print(" %-28s %-4s %-18s %d B" % (n, t.type_name, tuple(t.shape), len(t.data)))
348
+ w = load(args[0])
349
+ print("-- weights --")
350
+ print("cfg = %s" % (w.cfg,))
351
+ print("%d tensors, %d parameters" % (len(w.state_dict), w.n_params))
352
+ return 0
353
+
354
+
355
+
356
+
357
+
358
+ def _weights_from_nlm_blob(blob):
359
+ n=int.from_bytes(blob[:4],"little");md=json.loads(blob[4:4+n]);data=blob[4+n:];W={};G=md["G"]
360
+ for k,shape,kind,off,ln in md["tensors"]:
361
+ count=int(np.prod(shape))
362
+ if kind in ("f16","f32"):
363
+ W[k]=np.frombuffer(data,np.float16 if kind=="f16" else np.float32,count,off).astype(np.float32).reshape(shape).copy()
364
+ else:
365
+ r,c=shape;ng=(c+G-1)//G
366
+ if kind=="q8":q=np.frombuffer(data,np.int8,r*c,off).astype(np.float32).reshape(r,c);end=off+r*c
367
+ else:
368
+ nb=(r*c+1)//2;u=np.frombuffer(data,np.uint8,nb,off);q=(np.stack([u>>4,u&15],1).reshape(-1)[:r*c].astype(np.int16)-8).astype(np.float32).reshape(r,c);end=off+nb
369
+ end+=(-end)%4;sc=np.frombuffer(data,np.float16,r*ng,end).astype(np.float32).reshape(r,ng);W[k]=q*np.repeat(sc,G,1)[:,:c]
370
+ return md["cfg"],W
371
+
372
+ def load_word_model(path):
373
+ import torch
374
+ from bhc.xlit_model import XlitTransformer, set_vocab
375
+ w=load(path);set_vocab(w.cfg.get("vocab","hi"));m=XlitTransformer(**w.cfg);m.load_state_dict({k:torch.from_numpy(v.copy()) for k,v in w.state_dict.items()});return m.eval()
376
+
377
+ def load_sentence_lm(path):
378
+ from bhc.nlm import NLM
379
+ w=load(path);vocab=json.loads(w.metadata["aksharalp.tokenizer_json"])
380
+ return NLM(w.cfg,w.state_dict,vocab,w.cfg.get("norm","self"))
381
+
382
+ if __name__ == "__main__":
383
+ sys.exit(main(sys.argv))
models/bn/dict.tsv.gz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:edb2ad0bd7de4e87bd4d49ac406a9ef01011451bf23ac82d4c063bb9cce1dc2b
3
+ size 2508610
models/bn/inventory.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "parameters": 989056,
3
+ "model_raw": 589040,
4
+ "model_gz9": 540107,
5
+ "dict_raw": 14068340,
6
+ "dict_gz9": 2508610,
7
+ "dict_words": 500000
8
+ }
models/bn/params.json ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "format": "aksharalp-multiindic/1",
3
+ "language": "bn",
4
+ "beam": 8,
5
+ "nbest": 8,
6
+ "model": "xlit.q4",
7
+ "dictionary": "dict.tsv.gz",
8
+ "dictionary_words": 500000,
9
+ "dictionary_total": 29467775,
10
+ "unk": -17.198807885289952,
11
+ "reranker": {
12
+ "type": "linear",
13
+ "features": [
14
+ "sc",
15
+ "scn",
16
+ "inlex",
17
+ "uni",
18
+ "free0",
19
+ "rank",
20
+ "sc_rel",
21
+ "uni_rel"
22
+ ],
23
+ "w": [
24
+ 0.7719883322715759,
25
+ 0.7239015102386475,
26
+ 2.2432072162628174,
27
+ 0.3994755744934082,
28
+ -0.1507813185453415,
29
+ -0.07777873426675797,
30
+ 0.7293219566345215,
31
+ 0.42816251516342163
32
+ ],
33
+ "mu": [
34
+ -5.078212233229818,
35
+ -0.8147631729600909,
36
+ 0.31161147274689055,
37
+ -4.2673892678692775,
38
+ 0.13265511803554703,
39
+ 0.4336724409822265,
40
+ -3.7508561308261763,
41
+ -0.6986245748643944
42
+ ],
43
+ "sd": [
44
+ 2.216960295304686,
45
+ 0.6165648295196909,
46
+ 0.46315298671618116,
47
+ 6.490492456336254,
48
+ 0.33920261805997143,
49
+ 0.28614764292216904,
50
+ 2.341120778815271,
51
+ 1.748762448987244
52
+ ],
53
+ "fit_split": "all Aksharantar VALID",
54
+ "fit_n": 11188,
55
+ "l2": 0.001,
56
+ "seed": 0
57
+ }
58
+ }
models/bn/training_report.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "pairs": 1835520,
3
+ "counts": {
4
+ "aks-Dakshina": 76330,
5
+ "aks-AK-Freq": 7365,
6
+ "aks-Existing": 5478,
7
+ "aks-Wikidata": 18249,
8
+ "aks-Samanantar": 114558,
9
+ "aks-IndicCorp": 1009448,
10
+ "ie": 538301,
11
+ "heldout_drop_ie": 8171,
12
+ "dup_ie": 149395,
13
+ "ie_words": 189824,
14
+ "dup_ei": 185794,
15
+ "ei": 65791,
16
+ "heldout_drop_ei": 665,
17
+ "ei_words": 137774
18
+ },
19
+ "unique_natives": 1242898,
20
+ "unique_romans": 1513122,
21
+ "overlap_valid_test_natives": 0,
22
+ "overlap_valid_test_romans": 0,
23
+ "heldout_natives_total": 11576,
24
+ "heldout_romans_total": 24322,
25
+ "margins": {
26
+ "ie": 0.6,
27
+ "ei": 0.35,
28
+ "maxv_ie": 4,
29
+ "maxv_ei": 2
30
+ }
31
+ }
models/bn/xlit.q4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9f099f0b9524f446debf1d3f0ae1dc7ed1e88625c33f6dbb7b45e0348bf76376
3
+ size 589040
models/bn/xlit.q4.gz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1c87e62a4249a63c333942c1c8736eb2c89f9ee3148ef8fc422784ba6ce21e43
3
+ size 540107
models/hi/dict/lex.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:73bd5436b77db4ee433c564521f19a2b78cb6a45c429f274d43bef909708347f
3
+ size 11675371
models/hi/dict/lex.bin.gz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3b4dbff703f395e97b4503646fde7782bab0f3d79b9731ef703df6b961e38f55
3
+ size 6472998