thaki-AI commited on
Commit
23b9f86
·
verified ·
1 Parent(s): 13b3508

Add model card, weights and notices

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ adapter/tokenizer.json filter=lfs diff=lfs merge=lfs -text
37
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
LICENSE.Apache-2.0-Gemma ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent 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
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
LICENSE.OpenMDW-1.1 ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ OpenMDW License Agreement, version 1.1 (OpenMDW-1.1)
2
+
3
+ By exercising rights granted to you under this agreement, you accept and agree
4
+ to its terms.
5
+
6
+ As used in this agreement, "Model Materials" means the materials provided to
7
+ you under this agreement, consisting of: (1) one or more machine learning
8
+ models (including architecture and parameters); and (2) all related artifacts
9
+ (including associated data, documentation and software) that are provided to
10
+ you hereunder.
11
+
12
+ Subject to your compliance with this agreement, permission is hereby granted,
13
+ free of charge, to deal in the Model Materials without restriction, including
14
+ under all copyright, patent, database, and trade secret rights included or
15
+ embodied therein.
16
+
17
+ If you distribute any portion of the Model Materials, you shall retain in your
18
+ distribution (1) a copy of this agreement, and (2) all copyright notices and
19
+ other notices of origin included in the Model Materials that are applicable to
20
+ your distribution.
21
+
22
+ If you file, maintain, or voluntarily participate in a lawsuit against any
23
+ person or entity asserting that the Model Materials directly or indirectly
24
+ infringe any patent or copyright, then all rights and grants made to you
25
+ hereunder are terminated, unless that lawsuit was in response to a
26
+ corresponding lawsuit first brought against you.
27
+
28
+ This agreement does not impose any restrictions or obligations with respect to
29
+ any use, modification, or sharing of any outputs generated by using the Model
30
+ Materials.
31
+
32
+ THE MODEL MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
33
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34
+ FITNESS FOR A PARTICULAR PURPOSE, TITLE, NONINFRINGEMENT, ACCURACY, OR THE
35
+ ABSENCE OF LATENT OR OTHER DEFECTS OR ERRORS, WHETHER OR NOT DISCOVERABLE, ALL
36
+ TO THE GREATEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW.
37
+
38
+ YOU ARE SOLELY RESPONSIBLE FOR (1) CLEARING RIGHTS OF OTHER PERSONS THAT MAY
39
+ APPLY TO THE MODEL MATERIALS OR ANY USE THEREOF, INCLUDING WITHOUT LIMITATION
40
+ ANY PERSON'S COPYRIGHTS OR OTHER RIGHTS INCLUDED OR EMBODIED IN THE MODEL
41
+ MATERIALS; (2) OBTAINING ANY NECESSARY CONSENTS, PERMISSIONS OR OTHER RIGHTS
42
+ REQUIRED FOR ANY USE OF THE MODEL MATERIALS; OR (3) PERFORMING ANY DUE
43
+ DILIGENCE OR UNDERTAKING ANY OTHER INVESTIGATIONS INTO THE MODEL MATERIALS OR
44
+ ANYTHING INCORPORATED OR EMBODIED THEREIN.
45
+
46
+ IN NO EVENT SHALL THE PROVIDERS OF THE MODEL MATERIALS BE LIABLE FOR ANY CLAIM,
47
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
48
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MODEL MATERIALS, THE
49
+ USE THEREOF OR OTHER DEALINGS THEREIN.
MODEL_CARD.ko.md ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FleetWorker-E4B (한국어 카드)
2
+
3
+ 영문 카드: [README.md](./README.md)
4
+
5
+ 버전 1.0.0. [nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16](https://huggingface.co/nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16)을 파인튜닝하여 `ThakiCloud/FleetWorker-E4B`로 공개한 모델입니다.
6
+
7
+ FleetWorker-E4B는 작업 보드 항목을 분류하는 소형 온디바이스 분류기입니다. 항목의 제목을 입력받아 그 항목이 속한 작업 흐름(프로젝트)의 이름을 내놓고, 같은 JSON 객체에 우선순위 제안도 함께 담습니다. 유용한 출력은 프로젝트 레이블입니다. 공개된 모델은 보드의 내부 프로젝트명 대신 설명형의 일반적인 작업 흐름 이름으로 학습되었으므로 가중치에 내부 이름이 들어 있지 않으며, 그 대가로 내부 이름 모델에 비해 프로젝트 정확도가 약 3점 낮습니다. 우선순위 필드는 신뢰할 수 없습니다. 우선순위가 한 번도 설정되지 않은 항목에서는 10건 중 약 8건에 P0으로 답합니다. 프로젝트 필드만 사용하고, 우선순위를 긴급도로 표시하거나 자동으로 적용하지 마십시오.
8
+
9
+ ## 기능
10
+
11
+ 시스템 프롬프트에는 허용되는 프로젝트 레이블과 허용되는 우선순위가 나열되고, 사용자 메시지는 항목 제목(한국어 또는 영어)입니다. 모델은 정확히 하나의 JSON 객체 `{"project":..., "priority":...}`로 답합니다. 프로젝트 레이블은 25개의 일반적인 작업 흐름 이름(예: `synthetic-data-pipeline`, `legal-decision-models`, `point-of-sale-app`)이며 `inference_example.py`에 나열되어 있습니다. 모델은 이 분류 체계 안의 레이블만 출력할 수 있으므로, 다른 분류 체계에 그대로 끼워 쓰는 용도가 아니라 자신의 보드를 파인튜닝할 때 참고하는 용도로 사용하십시오.
12
+
13
+ ## 의도된 사용
14
+
15
+ - 새 작업 보드 항목에 프로젝트 레이블을 제안하고, 사람이 확인하는 용도.
16
+ - 검토 전에 받은 항목을 작업 흐름별로 미리 분류하는 용도.
17
+ - 다른 보드 분류 체계에 맞춘 자체 파인튜닝의 출발점.
18
+
19
+ ## 사용 방법
20
+
21
+ ### 프롬프트 형식
22
+
23
+ 모델은 기본 체크포인트의 채팅 템플릿을 thinking을 끈 상태(`enable_thinking=False`)로 사용합니다. 렌더링된 프롬프트는 정확히 다음과 같습니다.
24
+
25
+ ```text
26
+ <bos><|turn>system
27
+ You triage a task-board item. Output exactly one compact JSON object with keys "project" and "priority" and nothing else. "project" is one of: <the project labels the model was trained on, comma separated>, or "other"; "unknown" if the text gives no way to tell. "priority" is one of: P0, P1, P2, P3.<turn|>
28
+ <|turn>user
29
+ title: Add retry with backoff to the nightly export job<turn|>
30
+ <|turn>model
31
+ ```
32
+
33
+ 출력 형식은 컴팩트한 JSON 객체 하나입니다: `{"project":<label from the prompt list, "other" or "unknown">,"priority":"P0"|"P1"|"P2"|"P3"}`.
34
+
35
+ 꺾쇠괄호로 표시된 프로젝트 목록은 직접 파인튜닝한 레이블 집합으로 바꾸십시오. 제안된 우선순위는 사람에게 보여 주고, 자동으로 다시 기록하지 마십시오.
36
+
37
+ ### Transformers + PEFT adapter
38
+
39
+ ```python
40
+ import torch
41
+ from peft import PeftModel
42
+ from transformers import AutoModelForCausalLM, AutoTokenizer
43
+
44
+ BASE = "nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16"
45
+ tok = AutoTokenizer.from_pretrained(BASE)
46
+ model = AutoModelForCausalLM.from_pretrained(BASE, dtype=torch.bfloat16, device_map="auto")
47
+ model = PeftModel.from_pretrained(model, "ThakiCloud/FleetWorker-E4B", subfolder="adapter").eval()
48
+
49
+ SYSTEM = "You triage a task-board item. Output exactly one compact JSON object with keys \"project\" and \"priority\" and nothing else. \"project\" is one of: <the project labels the model was trained on, comma separated>, or \"other\"; \"unknown\" if the text gives no way to tell. \"priority\" is one of: P0, P1, P2, P3."
50
+ USER = "title: Add retry with backoff to the nightly export job"
51
+ messages = [
52
+ {"role": "system", "content": SYSTEM},
53
+ {"role": "user", "content": USER},
54
+ ]
55
+ inputs = tok.apply_chat_template(messages, add_generation_prompt=True, enable_thinking=False,
56
+ return_tensors="pt", return_dict=True).to(model.device)
57
+ out = model.generate(**inputs, max_new_tokens=64, do_sample=False)
58
+ print(tok.decode(out[0, inputs["input_ids"].shape[-1]:], skip_special_tokens=True))
59
+ ```
60
+
61
+ ### Merged BF16
62
+
63
+ LoRA delta가 기본 가중치(BF16)에 병합되어 있습니다.
64
+
65
+ ```python
66
+ import torch
67
+ from transformers import AutoModelForCausalLM, AutoTokenizer
68
+
69
+ tok = AutoTokenizer.from_pretrained("ThakiCloud/FleetWorker-E4B")
70
+ model = AutoModelForCausalLM.from_pretrained("ThakiCloud/FleetWorker-E4B", dtype=torch.bfloat16, device_map="auto").eval()
71
+
72
+ SYSTEM = "You triage a task-board item. Output exactly one compact JSON object with keys \"project\" and \"priority\" and nothing else. \"project\" is one of: <the project labels the model was trained on, comma separated>, or \"other\"; \"unknown\" if the text gives no way to tell. \"priority\" is one of: P0, P1, P2, P3."
73
+ USER = "title: Add retry with backoff to the nightly export job"
74
+ messages = [
75
+ {"role": "system", "content": SYSTEM},
76
+ {"role": "user", "content": USER},
77
+ ]
78
+ inputs = tok.apply_chat_template(messages, add_generation_prompt=True, enable_thinking=False,
79
+ return_tensors="pt", return_dict=True).to(model.device)
80
+ out = model.generate(**inputs, max_new_tokens=64, do_sample=False)
81
+ print(tok.decode(out[0, inputs["input_ids"].shape[-1]:], skip_special_tokens=True))
82
+ ```
83
+
84
+ ### llama.cpp (GGUF)
85
+
86
+ | File | Quantization | Size |
87
+ |---|---|---|
88
+ | `FleetWorker-E4B-Q4_K_M.gguf` | Q4_K_M, imatrix from training rows (recommended) | 5.34 GB (5335295200 bytes) |
89
+ | `FleetWorker-E4B-Q4_K_M-NV.gguf` | Q4_K_M with NVIDIA's recipe (Q8_0 token embeddings, BF16 per-layer projection), imatrix | 6.18 GB (6180447456 bytes) |
90
+ | `FleetWorker-E4B-Q8_0.gguf` | Q8_0 | 8.03 GB (8031245280 bytes) |
91
+
92
+ ```bash
93
+ llama-server -m FleetWorker-E4B-Q4_K_M.gguf -c 8192 -ngl 99 --port 8080
94
+ ```
95
+
96
+ 채팅 완성 요청에는 GGUF에 내장된 템플릿이 적용되며, thinking은 요청마다 끕니다.
97
+
98
+ ```bash
99
+ curl http://localhost:8080/v1/chat/completions -H 'content-type: application/json' -d '{"messages": [{"role": "system", "content": "You triage a task-board item. Output exactly one compact JSON object with keys \"project\" and \"priority\" and nothing else. \"project\" is one of: <the project labels the model was trained on, comma separated>, or \"other\"; \"unknown\" if the text gives no way to tell. \"priority\" is one of: P0, P1, P2, P3."}, {"role": "user", "content": "title: Add retry with backoff to the nightly export job"}], "temperature": 0, "chat_template_kwargs": {"enable_thinking": false}}'
100
+ ```
101
+
102
+ 보고된 GGUF 수치는 Hugging Face 채팅 템플릿으로 렌더링한 토큰 id를 보내 측정했으므로, 프롬프트가 BF16 실행과 바이트 단위로 동일합니다. 원시 문자열로 `/completion`을 호출한다면 서버가 `<bos>`를 한 번 더 붙이지 않는지 확인하십시오.
103
+
104
+ ## 평가
105
+
106
+ 보류된(held-out) 테스트 분할에서 greedy decoding으로 측정했으며, 모든 비교 대상에 같은 프롬프트를 사용했습니다.
107
+
108
+ 테스트 분할: 정답 항목 텍스트로 채점한 두 개의 보류 집합입니다. 하나는 학습 시점 이후에 수집한 206개 항목의 독립 집합(보류된 검증 기간과 그 이후 항목)이고, 다른 하나는 594개 항목의 더 큰 테스트 집합입니다. 프로젝트 정확도는 전체 항목 기준이며, 우선순위는 명시적으로 우선순위가 지정된 항목만으로 채점합니다. FleetWorker-E4B는 이 파일들의 alias 버전(sha256 접두사 ef19eeee 독립 집합, 220269b7 더 큰 집합)으로 채점했고, base와 v1 행은 내부 이름 버전으로 채점했습니다.
109
+ 테스트 파일 지문(sha256 접두사, 원장 기준): 5711ab1b436d5f49, d27e10421aa2dbb3.
110
+
111
+ | Arm (model) | n | Project acc., independent set | Project acc., larger test set |
112
+ |---|---|---|---|
113
+ | Bolt base, zero-shot (internal label set) (nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16) | 206 items (independent) / 594 items (larger) | 0.2961 | 0.3855 |
114
+ | Earlier version (v1 adapter) (internal label set) (nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16) | 206 items (independent) / 594 items (larger) | 0.4951 | 0.5875 |
115
+ | FleetWorker-E4B (nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16) | 206 items (independent) / 594 items (larger) | 0.5291 | 0.5892 |
116
+
117
+ 실행 조건:
118
+ - Bolt base, zero-shot (internal label set): greedy decode, enable_thinking=False, transformers 5.19.0; 1x B200
119
+ - Earlier version (v1 adapter) (internal label set): greedy decode, enable_thinking=False, transformers 5.19.0; 1x B200
120
+ - FleetWorker-E4B: greedy decode, enable_thinking=False, transformers 5.19.0; 1x H100
121
+
122
+ 이 결과와 함께 기록된 주의 사항:
123
+ - Bolt base, zero-shot (internal label set): zero-shot 측정입니다.
124
+ - Earlier version (v1 adapter) (internal label set): 우선순위는 명시적으로 설정된 레이블에 대해서만 채점했습니다. v1은 모든 항목에 P2를 예측하므로(P2 recall 1.0, P0/P3 0), 전체 우선순위 점수는 실력이 아니라 기본 레이블의 비중과 같습니다.
125
+ - FleetWorker-E4B: 실명 v4 집합과 같은 항목이며, 프로젝트 레이블만 설명형의 일반적인 alias로 바꿨습니다. 보드 기본값 그대로인 항목에서는 151건 중 120건에 P0을 예측합니다.
126
+
127
+ ### 명시적으로 우선순위가 지정된 항목의 우선순위 정확도
128
+
129
+ | Variant | n | Independent set (55 items) | Larger test set (243 items) |
130
+ |---|---|---|---|
131
+ | Bolt base, zero-shot (internal label set) | 55 items | 0.3273 | 0.2716 |
132
+ | Earlier version (v1 adapter) (internal label set) | 55 items | 0.3636 | 0.2922 |
133
+ | FleetWorker-E4B | 55 items | 0.6 | 0.5432 |
134
+ | Always predict the most common class | 55 items | 0.3636 | 0.5267 |
135
+
136
+ 측정 기준: small_set은 독립 집합에서 ��시적으로 우선순위가 지정된 55개 항목이고, large_set은 더 큰 테스트 집합에서 명시적으로 우선순위가 지정된 243개 항목입니다. 보드 기본값 그대로인 항목은 제외했습니다.
137
+ 주의 사항: 정확도는 명시적으로 우선순위가 지정된 항목에 대해서만 계산했습니다. 'always predict the most common class' 행이 우선순위 제안이 넘어야 하는 기준선이며, 더 큰 집합에서 FleetWorker-E4B는 이를 1.7점 앞섭니다. 우선순위가 한 번도 설정되지 않은 항목에서는 151건 중 120건에 P0으로 답하므로, 그 항목들에서 우선순위 필드는 쓸 수 없습니다.
138
+
139
+ ### 클래스별 우선순위 (명시적으로 레이블된 항목만)
140
+
141
+ | Variant | n | P0 recall | P1 recall | P2 recall | P3 recall |
142
+ |---|---|---|---|---|---|
143
+ | FleetWorker-E4B recall | 55 explicit items | 0.5 | 0.4667 | 0.9412 | 0.0 |
144
+ | Earlier version (v1 adapter, internal label set) recall | 55 explicit items | 0.0 | 0.2 | 1.0 | 0.0 |
145
+
146
+ 측정 기준: 206개 항목 독립 테스트 집합 중 명시적으로 우선순위가 지정된 55개 항목이며, 우선순위 클래스별 recall입니다.
147
+ 주의 사항: 우선순위는 제안일 뿐이며 우선순위가 지정되지 않은 항목에서는 사용할 수 없습니다. 보드 기본값 그대로인 151개 항목에서 모델은 120건에 P0, 27건에 P1, 4건에 P2로 답합니다. decode 시점의 prior 보정을 이전 버전에서 시도했으나 사전 등록한 점검을 통과하지 못했으므로 배포하지 않습니다.
148
+
149
+ ### 독립 집합(206개 항목)에서의 병합 가중치와 GGUF
150
+
151
+ | Variant | n | Project | Priority (explicit items) |
152
+ |---|---|---|---|
153
+ | Merged BF16 | 206 items | 0.5291 | 0.6 |
154
+ | GGUF Q8_0 | 206 items | 0.5291 | 0.6 |
155
+ | GGUF Q4_K_M (imatrix) | 206 items | 0.5291 | 0.6 |
156
+ | GGUF Q4_K_M-NV | 206 items | 0.5291 | 0.6 |
157
+
158
+ 측정 기준: 하드웨어 H100 1장, 독립 집합입니다. GGUF 실행은 Hugging Face 채팅 템플릿의 토큰 id를 llama-server(llama.cpp 344be0a)에 입력했고 greedy decoding입니다.
159
+ 주의 사항: 모든 변형에서 프로젝트 정확도와 명시적 우선순위 정확도가 동일합니다. 유출 점검(내부 식별자를 요구하는 프롬프트 130개, 프로젝트명 패턴 강제): 병합 BF16, Q4_K_M, Q4_K_M-NV에서 0건이었고, 테스트 출력 1,077개에서 내부 이름은 0건이었습니다.
160
+
161
+ ## 학습
162
+
163
+ 방법: 기본 모델의 LoRA 파인튜닝이며, loss는 assistant 턴에만 적용했습니다. 아래 값은 학습 기록에서 그대로 옮긴 것입니다.
164
+
165
+ | Setting | Value |
166
+ |---|---|
167
+ | LoRA rank r | 16 (from training summary) |
168
+ | LoRA alpha | 32 (from training summary) |
169
+ | Learning rate | 0.0001 (from training summary) |
170
+ | Epochs | 2.0 (from training summary) |
171
+ | Target modules | attn+mlp (from training summary) |
172
+ | Gradient accumulation | 8 (from training summary) |
173
+ | Max sequence length | 4096 (from training summary) |
174
+ | Optimizer steps | 1406 (from training summary) |
175
+ | Peak GPU memory (GiB) | 17.53 (from training summary) |
176
+ | Wall time (s) | 5815.2 (from training summary) |
177
+
178
+ ## 데이터
179
+
180
+ 작업 보드 항목(제목, 그리고 보드 이벤트 로그의 정답 프로젝트 및 우선순위 레이블), 보드 자체 규칙으로 만든 행, 그리고 더 큰 언어 모델이 작성한 소규모의 paraphrase 및 어려운 사례 집합으로 구성됩니다. 마지막 집합은 코드 검증기가 레이블이 보존되었음을 확인한 경우에만 남겼습니다. 내부 프로젝트명은 학습 전에 설명형의 일반적인 alias로 대체했습니다(토큰 전체 단위 치환, 잔존 이름 0건, 코드로 검사). 보드 기본값에 불과한 우선순위 레이블은 loss에서 제외했습니다. 학습, 검증, 테스트 분할은 기간과 항목 계열 기준으로 서로 겹치지 않습니다. 이 버전은 기본 가중치에서 2 epoch 학습했고, 검증 loss를 기준으로 1406 step 중 step 450의 체크포인트를 선택했습니다.
181
+
182
+ 학습 데이터는 내부 자료이며 공개하지 않습니다. 이 저장소에는 데이터셋 파일이 들어 있지 않습니다.
183
+
184
+ 분할 크기(항목 수): train 5621, val 145, test 665, test_indep 206.
185
+
186
+ ## 한계
187
+
188
+ - 우선순위는 우선순위가 지정되지 않은 항목에서 사용할 수 없습니다. 보드 기본값 그대로인 항목에서는 독립 집합 151건 중 120건에 P0으로 답합니다. 명시적으로 우선순위가 지정된 항목에서는 다수 클래스 기준선을 넘지만(55개 항목에서 0.600 대 0.364, 243개 항목에서 0.543 대 0.527) P3은 한 번도 예측하지 않습니다. 프로젝트 필드만 사용하고, 우선순위를 자동으로 표시하거나 적용하지 마십시오.
189
+ - base와 v1 행은 프롬프트와 레이블에 내부 프로젝트명을 넣어 측정했습니다. FleetWorker-E4B는 같은 항목을 일반 alias로 채점했으므로, 비교는 같은 항목 기준이지만 레이블 문자열까지 동일한 ��은 아닙니다. 내부 이름으로 학습한 모델은 독립 집합에서 0.563을 기록했으며, 공개하지 않습니다.
190
+ - 프로젝트 정확도는 약 0.53에서 0.59입니다. 10건 중 대략 4에서 5건은 수정이 필요할 것으로 예상하십시오. 166개 alias 중 91개는 학습 항목이 없는 프로젝트명에서 나왔으므로, 실제로 출력되는 레이블은 25개뿐입니다.
191
+ - 학습 데이터와 보드는 내부 자료이며 공개하지 않습니다. 레이블은 한 팀의 작업 흐름이므로, 이 모델은 자신의 보드를 파인튜닝하기 위한 출발점으로 사용하십시오.
192
+ - decode 시점의 prior 보정을 이전 버전에서 시도했으나 사전 등록한 점검을 통과하지 못했으므로 배포하지 않습니다.
193
+ - 위의 프롬프트 형식 밖의 동작(다른 출력 스키마, thinking 모드, 긴 항목 본문)은 평가하지 않았습니다.
194
+
195
+ ## 라이선스 및 출처 표기
196
+
197
+ - 모델 자료: OpenMDW License Agreement 1.1 버전(`LICENSE.OpenMDW-1.1`, https://openmdw.ai/license/1-1/)의 적용을 받습니다. 이 저장소는 [nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16](https://huggingface.co/nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16)의 파생물이며 해당 계약서 사본을 포함합니다.
198
+ - 출처: NVIDIA 기본 모델은 Google Gemma 4를 기반으로 post-training되었으며, Gemma 4의 사용은 Apache License, Version 2.0(`LICENSE.Apache-2.0-Gemma`)의 적용을 받습니다.
199
+ - NVIDIA의 governing-terms 고지는 `NOTICE.md`에 원문 그대로 보존되어 있으며, 출처 체인과 ThakiCloud의 수정 사항에 대한 설명이 함께 실려 있습니다.
200
+ - 파인튜닝된 가중치에는 ThakiCloud의 LoRA 수정이 더해졌으나, 위의 조건은 달라지지 않습니다.
201
+ - 이 한국어 카드는 참고용 번역입니다. 해석에 차이가 있을 경우 영문 `NOTICE.md`와 `LICENSE` 파일이 우선합니다.
NOTICE.md ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # NOTICE for FleetWorker-E4B
2
+
3
+ ## Origin chain
4
+
5
+ 1. Google Gemma 4 (`google/gemma-4-E4B-it`), Apache License, Version 2.0 (`LICENSE.Apache-2.0-Gemma`).
6
+ 2. NVIDIA Nemotron 3.5 Bolt E4B (`nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16`), post-trained by NVIDIA on Gemma 4, OpenMDW License Agreement, version 1.1 (`LICENSE.OpenMDW-1.1`).
7
+ 3. `ThakiCloud/FleetWorker-E4B`, a LoRA fine-tune of (2) by ThakiCloud.
8
+
9
+ ## Modifications by ThakiCloud
10
+
11
+ ThakiCloud trained a LoRA adapter on top of the NVIDIA model for the task described in README.md (version 1.0.0). The adapter is merged into the base weights in the merged checkpoint and the GGUF files. The base model's tokenizer and chat template are used unchanged.
12
+
13
+ ## Notices retained from the NVIDIA model card
14
+
15
+ The following text is reproduced verbatim from the base model's README (section "License/Terms of Use"):
16
+
17
+ > **Governing Terms:** Use of this model is governed by the [OpenMDW License Agreement, version 1.1](https://github.com/OpenMDW/OpenMDW/blob/main/1.1/LICENSE.OpenMDW-1.1). This model has been post-trained on the Google Gemma 4 model, and use of the Google Gemma 4 model is governed by [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
18
+
19
+ The NVIDIA model card also ships `bias.md`, `explainability.md`, `privacy.md` and `safety.md` for the base model; they describe the base model and remain applicable.
20
+
21
+ ## Agreements
22
+
23
+ Copies of both agreements are included in this repository: `LICENSE.OpenMDW-1.1` and `LICENSE.Apache-2.0-Gemma`.
README.md ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: transformers
3
+ license: other
4
+ license_name: openmdw-1.1
5
+ license_link: https://openmdw.ai/license/1-1/
6
+ base_model: nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16
7
+ base_model_relation: finetune
8
+ pipeline_tag: text-generation
9
+ language:
10
+ - en
11
+ - ko
12
+ tags:
13
+ - lora
14
+ - gemma4
15
+ - nemotron
16
+ - on-device
17
+ - task-triage
18
+ - classification
19
+ - agents
20
+ - thakicloud
21
+ ---
22
+
23
+ # FleetWorker-E4B
24
+
25
+ Version 1.0.0. Fine-tune of [nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16](https://huggingface.co/nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16) published as `ThakiCloud/FleetWorker-E4B`.
26
+
27
+ Korean card: [MODEL_CARD.ko.md](./MODEL_CARD.ko.md)
28
+
29
+ FleetWorker-E4B is a small on-device classifier for task-board items. Given the title of an item it names the work stream (project) the item belongs to, with a priority suggestion in the same JSON object. The project label is the useful output. The released model was trained on descriptive, generic work-stream names in place of the board's internal project names, so no internal names are in the weights; this costs about 3 points of project accuracy against the internal-name model. The priority field is not reliable: on items whose priority was never set it answers P0 for about 8 in 10. Use the project field only, and never show the priority as urgency or apply it automatically.
30
+
31
+ ## What it does
32
+
33
+ The system prompt lists the allowed project labels and the allowed priorities; the user message is the item title (Korean or English). The model replies with exactly one JSON object `{"project":..., "priority":...}`. Project labels are 25 generic work-stream names (for example `synthetic-data-pipeline`, `legal-decision-models`, `point-of-sale-app`), listed in `inference_example.py`; the model can only emit labels from that taxonomy, so use it as a reference for fine-tuning your own board rather than as a drop-in for another taxonomy.
34
+
35
+ ## Intended use
36
+
37
+ - Suggesting a project label for new task-board items, with a person confirming it.
38
+ - Pre-sorting an inbox of items into work streams before review.
39
+ - Starting point for your own fine-tune on a different board taxonomy.
40
+
41
+ ## How to use
42
+
43
+ ### Prompt format
44
+
45
+ The model uses the base checkpoint's chat template with thinking disabled (`enable_thinking=False`). The rendered prompt is exactly:
46
+
47
+ ```text
48
+ <bos><|turn>system
49
+ You triage a task-board item. Output exactly one compact JSON object with keys "project" and "priority" and nothing else. "project" is one of: <the project labels the model was trained on, comma separated>, or "other"; "unknown" if the text gives no way to tell. "priority" is one of: P0, P1, P2, P3.<turn|>
50
+ <|turn>user
51
+ title: Add retry with backoff to the nightly export job<turn|>
52
+ <|turn>model
53
+ ```
54
+
55
+ Output format: one compact JSON object: `{"project":<label from the prompt list, "other" or "unknown">,"priority":"P0"|"P1"|"P2"|"P3"}`.
56
+
57
+ Replace the angle-bracket project list with the label set you fine-tune for. Show the suggested priority to a person; do not write it back automatically.
58
+
59
+ ### Transformers + PEFT adapter
60
+
61
+ ```python
62
+ import torch
63
+ from peft import PeftModel
64
+ from transformers import AutoModelForCausalLM, AutoTokenizer
65
+
66
+ BASE = "nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16"
67
+ tok = AutoTokenizer.from_pretrained(BASE)
68
+ model = AutoModelForCausalLM.from_pretrained(BASE, dtype=torch.bfloat16, device_map="auto")
69
+ model = PeftModel.from_pretrained(model, "ThakiCloud/FleetWorker-E4B", subfolder="adapter").eval()
70
+
71
+ SYSTEM = "You triage a task-board item. Output exactly one compact JSON object with keys \"project\" and \"priority\" and nothing else. \"project\" is one of: <the project labels the model was trained on, comma separated>, or \"other\"; \"unknown\" if the text gives no way to tell. \"priority\" is one of: P0, P1, P2, P3."
72
+ USER = "title: Add retry with backoff to the nightly export job"
73
+ messages = [
74
+ {"role": "system", "content": SYSTEM},
75
+ {"role": "user", "content": USER},
76
+ ]
77
+ inputs = tok.apply_chat_template(messages, add_generation_prompt=True, enable_thinking=False,
78
+ return_tensors="pt", return_dict=True).to(model.device)
79
+ out = model.generate(**inputs, max_new_tokens=64, do_sample=False)
80
+ print(tok.decode(out[0, inputs["input_ids"].shape[-1]:], skip_special_tokens=True))
81
+ ```
82
+
83
+ ### Merged BF16
84
+
85
+ The LoRA delta is merged into the base weights (BF16).
86
+
87
+ ```python
88
+ import torch
89
+ from transformers import AutoModelForCausalLM, AutoTokenizer
90
+
91
+ tok = AutoTokenizer.from_pretrained("ThakiCloud/FleetWorker-E4B")
92
+ model = AutoModelForCausalLM.from_pretrained("ThakiCloud/FleetWorker-E4B", dtype=torch.bfloat16, device_map="auto").eval()
93
+
94
+ SYSTEM = "You triage a task-board item. Output exactly one compact JSON object with keys \"project\" and \"priority\" and nothing else. \"project\" is one of: <the project labels the model was trained on, comma separated>, or \"other\"; \"unknown\" if the text gives no way to tell. \"priority\" is one of: P0, P1, P2, P3."
95
+ USER = "title: Add retry with backoff to the nightly export job"
96
+ messages = [
97
+ {"role": "system", "content": SYSTEM},
98
+ {"role": "user", "content": USER},
99
+ ]
100
+ inputs = tok.apply_chat_template(messages, add_generation_prompt=True, enable_thinking=False,
101
+ return_tensors="pt", return_dict=True).to(model.device)
102
+ out = model.generate(**inputs, max_new_tokens=64, do_sample=False)
103
+ print(tok.decode(out[0, inputs["input_ids"].shape[-1]:], skip_special_tokens=True))
104
+ ```
105
+
106
+ ### llama.cpp (GGUF)
107
+
108
+ | File | Quantization | Size |
109
+ |---|---|---|
110
+ | `FleetWorker-E4B-Q4_K_M.gguf` | Q4_K_M, imatrix from training rows (recommended) | 5.34 GB (5335295200 bytes) |
111
+ | `FleetWorker-E4B-Q4_K_M-NV.gguf` | Q4_K_M with NVIDIA's recipe (Q8_0 token embeddings, BF16 per-layer projection), imatrix | 6.18 GB (6180447456 bytes) |
112
+ | `FleetWorker-E4B-Q8_0.gguf` | Q8_0 | 8.03 GB (8031245280 bytes) |
113
+
114
+ ```bash
115
+ llama-server -m FleetWorker-E4B-Q4_K_M.gguf -c 8192 -ngl 99 --port 8080
116
+ ```
117
+
118
+ Chat completions apply the template embedded in the GGUF; disable thinking per request:
119
+
120
+ ```bash
121
+ curl http://localhost:8080/v1/chat/completions -H 'content-type: application/json' -d '{"messages": [{"role": "system", "content": "You triage a task-board item. Output exactly one compact JSON object with keys \"project\" and \"priority\" and nothing else. \"project\" is one of: <the project labels the model was trained on, comma separated>, or \"other\"; \"unknown\" if the text gives no way to tell. \"priority\" is one of: P0, P1, P2, P3."}, {"role": "user", "content": "title: Add retry with backoff to the nightly export job"}], "temperature": 0, "chat_template_kwargs": {"enable_thinking": false}}'
122
+ ```
123
+
124
+ The reported GGUF numbers were measured by sending token ids rendered with the Hugging Face chat template, so the prompt is byte-identical to the BF16 run. If you call `/completion` with a raw string, check that the server does not add a second `<bos>`.
125
+
126
+ ## Evaluation
127
+
128
+ Greedy decoding on a held-out test split, same prompts for every arm.
129
+
130
+ Test split: two held-out sets scored with the gold item text: an independent set of 206 items taken after the training cut-off (held-out validation period plus later items), and a larger test set of 594 items. Project accuracy is over all items; priority is scored on explicitly prioritised items only. FleetWorker-E4B is scored on the alias versions of these files (sha256 prefixes ef19eeee independent, 220269b7 larger); the base and v1 rows on the internal-name versions.
131
+ Test file fingerprint (sha256 prefix, from the ledger): 5711ab1b436d5f49, d27e10421aa2dbb3.
132
+
133
+ | Arm (model) | n | Project acc., independent set | Project acc., larger test set |
134
+ |---|---|---|---|
135
+ | Bolt base, zero-shot (internal label set) (nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16) | 206 items (independent) / 594 items (larger) | 0.2961 | 0.3855 |
136
+ | Earlier version (v1 adapter) (internal label set) (nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16) | 206 items (independent) / 594 items (larger) | 0.4951 | 0.5875 |
137
+ | FleetWorker-E4B (nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16) | 206 items (independent) / 594 items (larger) | 0.5291 | 0.5892 |
138
+
139
+ Run conditions:
140
+ - Bolt base, zero-shot (internal label set): greedy decode, enable_thinking=False, transformers 5.19.0; 1x B200
141
+ - Earlier version (v1 adapter) (internal label set): greedy decode, enable_thinking=False, transformers 5.19.0; 1x B200
142
+ - FleetWorker-E4B: greedy decode, enable_thinking=False, transformers 5.19.0; 1x H100
143
+
144
+ Caveats recorded with these results:
145
+ - Bolt base, zero-shot (internal label set): zero-shot
146
+ - Earlier version (v1 adapter) (internal label set): priority on explicitly-set labels only: v1 predicts P2 for everything (recall P2 1.0, P0/P3 0), so its overall priority score equals the default-label share, not skill.
147
+ - FleetWorker-E4B: Same items as the real-name v4 sets, with project labels replaced by descriptive generic aliases. On items left at the board default it predicts P0 for 120/151.
148
+
149
+ ### Priority accuracy on explicitly prioritised items
150
+
151
+ | Variant | n | Independent set (55 items) | Larger test set (243 items) |
152
+ |---|---|---|---|
153
+ | Bolt base, zero-shot (internal label set) | 55 items | 0.3273 | 0.2716 |
154
+ | Earlier version (v1 adapter) (internal label set) | 55 items | 0.3636 | 0.2922 |
155
+ | FleetWorker-E4B | 55 items | 0.6 | 0.5432 |
156
+ | Always predict the most common class | 55 items | 0.3636 | 0.5267 |
157
+
158
+ Measured with small_set = the 55 explicitly prioritised items of the independent set; large_set = the 243 explicitly prioritised items of the larger test set. Items left at the board default are excluded.
159
+ Caveats: Accuracy over explicitly prioritised items only. The 'always predict the most common class' row is the bar a priority suggestion has to beat; on the larger set FleetWorker-E4B clears it by 1.7 points. On items whose priority was never set it answers P0 for 120 of 151, so the priority field is not usable there.
160
+
161
+ ### Priority by class (explicitly labelled items only)
162
+
163
+ | Variant | n | P0 recall | P1 recall | P2 recall | P3 recall |
164
+ |---|---|---|---|---|---|
165
+ | FleetWorker-E4B recall | 55 explicit items | 0.5 | 0.4667 | 0.9412 | 0.0 |
166
+ | Earlier version (v1 adapter, internal label set) recall | 55 explicit items | 0.0 | 0.2 | 1.0 | 0.0 |
167
+
168
+ Measured with the 55 explicitly prioritised items of the 206-item independent test set; recall per priority class.
169
+ Caveats: Priority is a suggestion only and is not usable on unprioritised items: on the 151 items left at the board default the model answers P0 for 120, P1 for 27 and P2 for 4. A decode-time prior correction was tried on an earlier version and failed its pre-registered check, so it is not shipped.
170
+
171
+ ### Merged weights and GGUF on the independent set (206 items)
172
+
173
+ | Variant | n | Project | Priority (explicit items) |
174
+ |---|---|---|---|
175
+ | Merged BF16 | 206 items | 0.5291 | 0.6 |
176
+ | GGUF Q8_0 | 206 items | 0.5291 | 0.6 |
177
+ | GGUF Q4_K_M (imatrix) | 206 items | 0.5291 | 0.6 |
178
+ | GGUF Q4_K_M-NV | 206 items | 0.5291 | 0.6 |
179
+
180
+ Measured with hardware H100, 1 GPU; independent set; GGUF runs feed the Hugging Face chat-template token ids to llama-server (llama.cpp 344be0a), greedy.
181
+ Caveats: All variants give identical project and explicit-priority accuracy. Leak probe (130 prompts asking for internal identifiers, project-name pattern enforced): 0 hits on merged BF16, Q4_K_M and Q4_K_M-NV; 0 internal names in 1,077 test outputs.
182
+
183
+ ## Training
184
+
185
+ Method: LoRA fine-tune of the base model, loss on assistant turns only. Values below are copied from the training record.
186
+
187
+ | Setting | Value |
188
+ |---|---|
189
+ | LoRA rank r | 16 (from training summary) |
190
+ | LoRA alpha | 32 (from training summary) |
191
+ | Learning rate | 0.0001 (from training summary) |
192
+ | Epochs | 2.0 (from training summary) |
193
+ | Target modules | attn+mlp (from training summary) |
194
+ | Gradient accumulation | 8 (from training summary) |
195
+ | Max sequence length | 4096 (from training summary) |
196
+ | Optimizer steps | 1406 (from training summary) |
197
+ | Peak GPU memory (GiB) | 17.53 (from training summary) |
198
+ | Wall time (s) | 5815.2 (from training summary) |
199
+
200
+ ## Data
201
+
202
+ Task-board items (title and gold project and priority labels from the board's event log), plus rows built from the board's own rules, plus a smaller set of paraphrases and harder cases written by a larger language model and kept only when code validators confirmed the label was preserved. Internal project names were replaced by descriptive generic aliases (whole-token replacement, 0 residual names, checked by code) before training. Priority labels that were just the board default were masked out of the loss. Train, validation and test splits are disjoint by period and item family. This version was trained from the base weights for two epochs and the checkpoint at step 450 of 1406 was selected on validation loss.
203
+
204
+ The training data is internal and is not released. This repository contains no dataset files.
205
+
206
+ Split sizes (items): train 5621, val 145, test 665, test_indep 206.
207
+
208
+ ## Limitations
209
+
210
+ - Priority is not usable on unprioritised items: on items left at the board default it answers P0 for 120 of 151 in the independent set. On explicitly prioritised items it beats the majority-class baseline (0.600 vs 0.364 on 55 items; 0.543 vs 0.527 on 243 items) and never predicts P3. Use the project field only; never show or apply the priority automatically.
211
+ - The base and v1 rows were measured with the internal project names in the prompt and labels; FleetWorker-E4B is scored on the same items with the generic aliases, so the comparison is on the same items but not on identical label strings. A model trained on the internal names reached 0.563 on the independent set; it is not released.
212
+ - Project accuracy is about 0.53 to 0.59: expect roughly 4 to 5 in 10 items to need a correction. 91 of the 166 aliases come from project names without any training items, so only 25 labels are emitted in practice.
213
+ - Training data and the board are internal and not released. The labels are one team's work streams; use the model as a starting point for fine-tuning your own board.
214
+ - A decode-time prior correction was tried on an earlier version and failed its pre-registered check, so it is not shipped.
215
+ - Behavior outside the prompt format above (other output schemas, thinking mode, long item bodies) was not evaluated.
216
+
217
+ ## License and attribution
218
+
219
+ - Model materials: governed by the OpenMDW License Agreement, version 1.1 (`LICENSE.OpenMDW-1.1`, https://openmdw.ai/license/1-1/). This repository is a derivative of [nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16](https://huggingface.co/nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16) and ships a copy of the agreement.
220
+ - Origin: the NVIDIA base model was post-trained on Google Gemma 4, whose use is governed by the Apache License, Version 2.0 (`LICENSE.Apache-2.0-Gemma`).
221
+ - NVIDIA's governing-terms notice is retained verbatim in `NOTICE.md`, together with the origin chain and a statement of ThakiCloud's modifications.
222
+ - The fine-tuned weights add ThakiCloud's LoRA modifications; they do not change the terms above.
adapter/adapter_config.json ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "alora_invocation_tokens": null,
3
+ "alpha_pattern": {},
4
+ "arrow_config": null,
5
+ "auto_mapping": null,
6
+ "base_model_name_or_path": "nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-BF16",
7
+ "bias": "none",
8
+ "corda_config": null,
9
+ "ensure_weight_tying": false,
10
+ "eva_config": null,
11
+ "exclude_modules": null,
12
+ "fan_in_fan_out": false,
13
+ "inference_mode": true,
14
+ "init_lora_weights": true,
15
+ "layer_replication": null,
16
+ "layers_pattern": null,
17
+ "layers_to_transform": null,
18
+ "loftq_config": {},
19
+ "lora_alpha": 32,
20
+ "lora_bias": false,
21
+ "lora_dropout": 0.05,
22
+ "lora_ga_config": null,
23
+ "megatron_config": null,
24
+ "megatron_core": "megatron.core",
25
+ "modules_to_save": null,
26
+ "monteclora_config": null,
27
+ "peft_type": "LORA",
28
+ "peft_version": "0.20.0",
29
+ "qalora_group_size": 16,
30
+ "r": 16,
31
+ "rank_pattern": {},
32
+ "revision": null,
33
+ "target_modules": [
34
+ "model.language_model.layers.26.self_attn.o_proj",
35
+ "model.language_model.layers.4.self_attn.o_proj",
36
+ "model.language_model.layers.16.mlp.up_proj",
37
+ "model.language_model.layers.4.mlp.up_proj",
38
+ "model.language_model.layers.19.mlp.gate_proj",
39
+ "model.language_model.layers.14.mlp.gate_proj",
40
+ "model.language_model.layers.35.self_attn.o_proj",
41
+ "model.language_model.layers.31.mlp.down_proj",
42
+ "model.language_model.layers.25.mlp.down_proj",
43
+ "model.language_model.layers.40.self_attn.o_proj",
44
+ "model.language_model.layers.31.self_attn.q_proj",
45
+ "model.language_model.layers.28.mlp.up_proj",
46
+ "model.language_model.layers.39.mlp.gate_proj",
47
+ "model.language_model.layers.31.mlp.gate_proj",
48
+ "model.language_model.layers.27.mlp.gate_proj",
49
+ "model.language_model.layers.32.mlp.down_proj",
50
+ "model.language_model.layers.15.self_attn.q_proj",
51
+ "model.language_model.layers.29.mlp.gate_proj",
52
+ "model.language_model.layers.3.self_attn.k_proj",
53
+ "model.language_model.layers.40.self_attn.q_proj",
54
+ "model.language_model.layers.7.mlp.up_proj",
55
+ "model.language_model.layers.13.self_attn.v_proj",
56
+ "model.language_model.layers.37.mlp.gate_proj",
57
+ "model.language_model.layers.2.mlp.up_proj",
58
+ "model.language_model.layers.23.self_attn.k_proj",
59
+ "model.language_model.layers.26.mlp.down_proj",
60
+ "model.language_model.layers.2.self_attn.v_proj",
61
+ "model.language_model.layers.22.self_attn.q_proj",
62
+ "model.language_model.layers.32.mlp.gate_proj",
63
+ "model.language_model.layers.22.mlp.gate_proj",
64
+ "model.language_model.layers.32.self_attn.o_proj",
65
+ "model.language_model.layers.30.mlp.down_proj",
66
+ "model.language_model.layers.16.mlp.gate_proj",
67
+ "model.language_model.layers.12.self_attn.q_proj",
68
+ "model.language_model.layers.12.self_attn.o_proj",
69
+ "model.language_model.layers.3.mlp.down_proj",
70
+ "model.language_model.layers.6.mlp.up_proj",
71
+ "model.language_model.layers.36.mlp.down_proj",
72
+ "model.language_model.layers.4.self_attn.v_proj",
73
+ "model.language_model.layers.6.self_attn.q_proj",
74
+ "model.language_model.layers.22.self_attn.o_proj",
75
+ "model.language_model.layers.21.mlp.gate_proj",
76
+ "model.language_model.layers.3.self_attn.o_proj",
77
+ "model.language_model.layers.33.mlp.up_proj",
78
+ "model.language_model.layers.12.mlp.down_proj",
79
+ "model.language_model.layers.11.self_attn.k_proj",
80
+ "model.language_model.layers.15.mlp.up_proj",
81
+ "model.language_model.layers.15.self_attn.k_proj",
82
+ "model.language_model.layers.23.mlp.up_proj",
83
+ "model.language_model.layers.26.mlp.up_proj",
84
+ "model.language_model.layers.30.mlp.gate_proj",
85
+ "model.language_model.layers.0.mlp.gate_proj",
86
+ "model.language_model.layers.30.self_attn.o_proj",
87
+ "model.language_model.layers.17.self_attn.v_proj",
88
+ "model.language_model.layers.34.self_attn.q_proj",
89
+ "model.language_model.layers.41.self_attn.q_proj",
90
+ "model.language_model.layers.37.self_attn.o_proj",
91
+ "model.language_model.layers.9.mlp.up_proj",
92
+ "model.language_model.layers.29.mlp.up_proj",
93
+ "model.language_model.layers.10.mlp.down_proj",
94
+ "model.language_model.layers.21.self_attn.k_proj",
95
+ "model.language_model.layers.14.self_attn.v_proj",
96
+ "model.language_model.layers.0.self_attn.v_proj",
97
+ "model.language_model.layers.15.self_attn.o_proj",
98
+ "model.language_model.layers.8.self_attn.q_proj",
99
+ "model.language_model.layers.26.mlp.gate_proj",
100
+ "model.language_model.layers.39.self_attn.o_proj",
101
+ "model.language_model.layers.17.self_attn.q_proj",
102
+ "model.language_model.layers.39.mlp.up_proj",
103
+ "model.language_model.layers.8.mlp.down_proj",
104
+ "model.language_model.layers.19.self_attn.o_proj",
105
+ "model.language_model.layers.15.mlp.gate_proj",
106
+ "model.language_model.layers.21.self_attn.q_proj",
107
+ "model.language_model.layers.24.self_attn.o_proj",
108
+ "model.language_model.layers.23.mlp.down_proj",
109
+ "model.language_model.layers.17.mlp.up_proj",
110
+ "model.language_model.layers.37.self_attn.q_proj",
111
+ "model.language_model.layers.1.self_attn.v_proj",
112
+ "model.language_model.layers.11.self_attn.o_proj",
113
+ "model.language_model.layers.17.self_attn.o_proj",
114
+ "model.language_model.layers.8.self_attn.v_proj",
115
+ "model.language_model.layers.1.mlp.up_proj",
116
+ "model.language_model.layers.36.mlp.gate_proj",
117
+ "model.language_model.layers.2.mlp.down_proj",
118
+ "model.language_model.layers.13.self_attn.k_proj",
119
+ "model.language_model.layers.38.mlp.up_proj",
120
+ "model.language_model.layers.31.self_attn.o_proj",
121
+ "model.language_model.layers.2.self_attn.k_proj",
122
+ "model.language_model.layers.13.mlp.gate_proj",
123
+ "model.language_model.layers.37.mlp.up_proj",
124
+ "model.language_model.layers.41.mlp.up_proj",
125
+ "model.language_model.layers.21.self_attn.v_proj",
126
+ "model.language_model.layers.22.mlp.up_proj",
127
+ "model.language_model.layers.0.self_attn.o_proj",
128
+ "model.language_model.layers.14.self_attn.k_proj",
129
+ "model.language_model.layers.15.mlp.down_proj",
130
+ "model.language_model.layers.36.self_attn.q_proj",
131
+ "model.language_model.layers.7.self_attn.q_proj",
132
+ "model.language_model.layers.7.mlp.down_proj",
133
+ "model.language_model.layers.9.mlp.down_proj",
134
+ "model.language_model.layers.37.mlp.down_proj",
135
+ "model.language_model.layers.23.self_attn.q_proj",
136
+ "model.language_model.layers.29.self_attn.q_proj",
137
+ "model.language_model.layers.23.self_attn.v_proj",
138
+ "model.language_model.layers.20.mlp.up_proj",
139
+ "model.language_model.layers.0.mlp.down_proj",
140
+ "model.language_model.layers.6.mlp.down_proj",
141
+ "model.language_model.layers.3.mlp.gate_proj",
142
+ "model.language_model.layers.17.self_attn.k_proj",
143
+ "model.language_model.layers.13.self_attn.q_proj",
144
+ "model.language_model.layers.28.self_attn.o_proj",
145
+ "model.language_model.layers.7.self_attn.v_proj",
146
+ "model.language_model.layers.30.mlp.up_proj",
147
+ "model.language_model.layers.35.self_attn.q_proj",
148
+ "model.language_model.layers.8.mlp.up_proj",
149
+ "model.language_model.layers.35.mlp.down_proj",
150
+ "model.language_model.layers.38.self_attn.q_proj",
151
+ "model.language_model.layers.24.self_attn.q_proj",
152
+ "model.language_model.layers.7.self_attn.k_proj",
153
+ "model.language_model.layers.40.mlp.gate_proj",
154
+ "model.language_model.layers.26.self_attn.q_proj",
155
+ "model.language_model.layers.25.self_attn.o_proj",
156
+ "model.language_model.layers.25.mlp.up_proj",
157
+ "model.language_model.layers.18.self_attn.v_proj",
158
+ "model.language_model.layers.25.mlp.gate_proj",
159
+ "model.language_model.layers.21.mlp.down_proj",
160
+ "model.language_model.layers.5.mlp.gate_proj",
161
+ "model.language_model.layers.11.self_attn.q_proj",
162
+ "model.language_model.layers.14.self_attn.o_proj",
163
+ "model.language_model.layers.2.self_attn.q_proj",
164
+ "model.language_model.layers.7.mlp.gate_proj",
165
+ "model.language_model.layers.5.mlp.up_proj",
166
+ "model.language_model.layers.0.self_attn.q_proj",
167
+ "model.language_model.layers.0.self_attn.k_proj",
168
+ "model.language_model.layers.11.mlp.up_proj",
169
+ "model.language_model.layers.35.mlp.gate_proj",
170
+ "model.language_model.layers.5.self_attn.k_proj",
171
+ "model.language_model.layers.11.mlp.gate_proj",
172
+ "model.language_model.layers.27.self_attn.q_proj",
173
+ "model.language_model.layers.18.self_attn.k_proj",
174
+ "model.language_model.layers.8.self_attn.k_proj",
175
+ "model.language_model.layers.4.self_attn.k_proj",
176
+ "model.language_model.layers.10.mlp.gate_proj",
177
+ "model.language_model.layers.11.self_attn.v_proj",
178
+ "model.language_model.layers.27.mlp.up_proj",
179
+ "model.language_model.layers.2.self_attn.o_proj",
180
+ "model.language_model.layers.7.self_attn.o_proj",
181
+ "model.language_model.layers.9.mlp.gate_proj",
182
+ "model.language_model.layers.19.self_attn.q_proj",
183
+ "model.language_model.layers.21.mlp.up_proj",
184
+ "model.language_model.layers.12.mlp.gate_proj",
185
+ "model.language_model.layers.18.mlp.gate_proj",
186
+ "model.language_model.layers.25.self_attn.q_proj",
187
+ "model.language_model.layers.22.mlp.down_proj",
188
+ "model.language_model.layers.30.self_attn.q_proj",
189
+ "model.language_model.layers.6.mlp.gate_proj",
190
+ "model.language_model.layers.21.self_attn.o_proj",
191
+ "model.language_model.layers.34.mlp.up_proj",
192
+ "model.language_model.layers.22.self_attn.v_proj",
193
+ "model.language_model.layers.16.self_attn.v_proj",
194
+ "model.language_model.layers.3.self_attn.q_proj",
195
+ "model.language_model.layers.1.self_attn.o_proj",
196
+ "model.language_model.layers.24.mlp.gate_proj",
197
+ "model.language_model.layers.36.self_attn.o_proj",
198
+ "model.language_model.layers.4.self_attn.q_proj",
199
+ "model.language_model.layers.16.self_attn.q_proj",
200
+ "model.language_model.layers.22.self_attn.k_proj",
201
+ "model.language_model.layers.8.self_attn.o_proj",
202
+ "model.language_model.layers.19.mlp.up_proj",
203
+ "model.language_model.layers.20.mlp.gate_proj",
204
+ "model.language_model.layers.14.self_attn.q_proj",
205
+ "model.language_model.layers.10.self_attn.q_proj",
206
+ "model.language_model.layers.15.self_attn.v_proj",
207
+ "model.language_model.layers.9.self_attn.k_proj",
208
+ "model.language_model.layers.16.mlp.down_proj",
209
+ "model.language_model.layers.41.self_attn.o_proj",
210
+ "model.language_model.layers.27.self_attn.o_proj",
211
+ "model.language_model.layers.29.mlp.down_proj",
212
+ "model.language_model.layers.28.mlp.gate_proj",
213
+ "model.language_model.layers.33.mlp.down_proj",
214
+ "model.language_model.layers.10.self_attn.v_proj",
215
+ "model.language_model.layers.19.self_attn.v_proj",
216
+ "model.language_model.layers.28.mlp.down_proj",
217
+ "model.language_model.layers.39.mlp.down_proj",
218
+ "model.language_model.layers.6.self_attn.k_proj",
219
+ "model.language_model.layers.38.self_attn.o_proj",
220
+ "model.language_model.layers.1.self_attn.q_proj",
221
+ "model.language_model.layers.24.mlp.down_proj",
222
+ "model.language_model.layers.23.mlp.gate_proj",
223
+ "model.language_model.layers.9.self_attn.v_proj",
224
+ "model.language_model.layers.38.mlp.down_proj",
225
+ "model.language_model.layers.34.mlp.down_proj",
226
+ "model.language_model.layers.4.mlp.down_proj",
227
+ "model.language_model.layers.1.mlp.down_proj",
228
+ "model.language_model.layers.12.mlp.up_proj",
229
+ "model.language_model.layers.13.self_attn.o_proj",
230
+ "model.language_model.layers.20.self_attn.v_proj",
231
+ "model.language_model.layers.33.mlp.gate_proj",
232
+ "model.language_model.layers.5.self_attn.o_proj",
233
+ "model.language_model.layers.12.self_attn.v_proj",
234
+ "model.language_model.layers.16.self_attn.k_proj",
235
+ "model.language_model.layers.20.self_attn.o_proj",
236
+ "model.language_model.layers.6.self_attn.o_proj",
237
+ "model.language_model.layers.18.mlp.up_proj",
238
+ "model.language_model.layers.20.self_attn.k_proj",
239
+ "model.language_model.layers.41.mlp.gate_proj",
240
+ "model.language_model.layers.19.mlp.down_proj",
241
+ "model.language_model.layers.13.mlp.down_proj",
242
+ "model.language_model.layers.14.mlp.down_proj",
243
+ "model.language_model.layers.4.mlp.gate_proj",
244
+ "model.language_model.layers.11.mlp.down_proj",
245
+ "model.language_model.layers.20.mlp.down_proj",
246
+ "model.language_model.layers.16.self_attn.o_proj",
247
+ "model.language_model.layers.10.self_attn.o_proj",
248
+ "model.language_model.layers.13.mlp.up_proj",
249
+ "model.language_model.layers.3.mlp.up_proj",
250
+ "model.language_model.layers.18.self_attn.o_proj",
251
+ "model.language_model.layers.10.self_attn.k_proj",
252
+ "model.language_model.layers.17.mlp.gate_proj",
253
+ "model.language_model.layers.34.mlp.gate_proj",
254
+ "model.language_model.layers.35.mlp.up_proj",
255
+ "model.language_model.layers.8.mlp.gate_proj",
256
+ "model.language_model.layers.9.self_attn.o_proj",
257
+ "model.language_model.layers.9.self_attn.q_proj",
258
+ "model.language_model.layers.20.self_attn.q_proj",
259
+ "model.language_model.layers.32.self_attn.q_proj",
260
+ "model.language_model.layers.23.self_attn.o_proj",
261
+ "model.language_model.layers.33.self_attn.o_proj",
262
+ "model.language_model.layers.5.self_attn.q_proj",
263
+ "model.language_model.layers.39.self_attn.q_proj",
264
+ "model.language_model.layers.5.mlp.down_proj",
265
+ "model.language_model.layers.28.self_attn.q_proj",
266
+ "model.language_model.layers.27.mlp.down_proj",
267
+ "model.language_model.layers.29.self_attn.o_proj",
268
+ "model.language_model.layers.1.mlp.gate_proj",
269
+ "model.language_model.layers.17.mlp.down_proj",
270
+ "model.language_model.layers.14.mlp.up_proj",
271
+ "model.language_model.layers.5.self_attn.v_proj",
272
+ "model.language_model.layers.6.self_attn.v_proj",
273
+ "model.language_model.layers.36.mlp.up_proj",
274
+ "model.language_model.layers.31.mlp.up_proj",
275
+ "model.language_model.layers.34.self_attn.o_proj",
276
+ "model.language_model.layers.2.mlp.gate_proj",
277
+ "model.language_model.layers.24.mlp.up_proj",
278
+ "model.language_model.layers.18.mlp.down_proj",
279
+ "model.language_model.layers.40.mlp.up_proj",
280
+ "model.language_model.layers.18.self_attn.q_proj",
281
+ "model.language_model.layers.10.mlp.up_proj",
282
+ "model.language_model.layers.12.self_attn.k_proj",
283
+ "model.language_model.layers.1.self_attn.k_proj",
284
+ "model.language_model.layers.19.self_attn.k_proj",
285
+ "model.language_model.layers.0.mlp.up_proj",
286
+ "model.language_model.layers.32.mlp.up_proj",
287
+ "model.language_model.layers.40.mlp.down_proj",
288
+ "model.language_model.layers.41.mlp.down_proj",
289
+ "model.language_model.layers.38.mlp.gate_proj",
290
+ "model.language_model.layers.3.self_attn.v_proj",
291
+ "model.language_model.layers.33.self_attn.q_proj"
292
+ ],
293
+ "target_parameters": null,
294
+ "task_type": "CAUSAL_LM",
295
+ "trainable_token_indices": null,
296
+ "use_bdlora": null,
297
+ "use_dora": false,
298
+ "use_qalora": false,
299
+ "use_rslora": false,
300
+ "velora_config": null
301
+ }
adapter/adapter_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:db3ba1be40dfbc7177c5b8b0d93aa9b781b0cb5d10783c7a9e9f583581f627ff
3
+ size 139602808
adapter/chat_template.jinja ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- if conversational_mode | default(false) -%}
2
+ {%- set spoken_instruction = "You are having a spoken conversation with the user. Respond as briefly as possible while still addressing their questions fully." -%}
3
+ {%- if messages and messages[0]['role'] in ['system', 'developer'] -%}
4
+ {%- set spoken = namespace(text='') -%}
5
+ {%- if messages[0]['content'] is string -%}
6
+ {%- set spoken.text = messages[0]['content'] | trim -%}
7
+ {%- else -%}
8
+ {%- for part in messages[0]['content'] -%}
9
+ {%- set spoken.text = spoken.text + (part['text'] | trim) + ' ' -%}
10
+ {%- endfor -%}
11
+ {%- set spoken.text = spoken.text | trim -%}
12
+ {%- endif -%}
13
+ {%- if spoken.text != spoken_instruction -%}
14
+ {%- set spoken.text = (spoken.text + '\n\n' if spoken.text else '') + spoken_instruction -%}
15
+ {%- endif -%}
16
+ {%- set messages = [{'role': messages[0]['role'], 'content': spoken.text}] + messages[1:] -%}
17
+ {%- else -%}
18
+ {%- set messages = [{'role': 'system', 'content': spoken_instruction}] + messages -%}
19
+ {%- endif -%}
20
+ {%- endif -%}
21
+ {#
22
+ Template: Nemotron updated Gemma4 chat template, reasoning-off suffix ablation
23
+ Published: 2026-08-20
24
+ Context: Preserve content ordering before tool-calls while matching the original
25
+ Gemma 4 generation prompt when enable_thinking=false.
26
+ #}
27
+ {%- macro format_parameters(properties, required, filter_keys=false) -%}
28
+ {%- set standard_keys = ['description', 'type', 'properties', 'required', 'nullable'] -%}
29
+ {%- set ns = namespace(found_first=false) -%}
30
+ {%- for key, value in properties | dictsort -%}
31
+ {%- set add_comma = false -%}
32
+ {%- if not filter_keys or key not in standard_keys -%}
33
+ {%- if ns.found_first %},{% endif -%}
34
+ {%- set ns.found_first = true -%}
35
+ {{ key }}:{
36
+ {%- if value['description'] -%}
37
+ description:<|"|>{{ value['description'] }}<|"|>
38
+ {%- set add_comma = true -%}
39
+ {%- endif -%}
40
+ {%- if value['type'] | upper == 'STRING' -%}
41
+ {%- if value['enum'] -%}
42
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
43
+ enum:{{ format_argument(value['enum']) }}
44
+ {%- endif -%}
45
+ {%- elif value['type'] | upper == 'ARRAY' -%}
46
+ {%- if value['items'] is mapping and value['items'] -%}
47
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
48
+ items:{
49
+ {%- set ns_items = namespace(found_first=false) -%}
50
+ {%- for item_key, item_value in value['items'] | dictsort -%}
51
+ {%- if item_value is not none -%}
52
+ {%- if ns_items.found_first %},{% endif -%}
53
+ {%- set ns_items.found_first = true -%}
54
+ {%- if item_key == 'properties' -%}
55
+ properties:{
56
+ {%- if item_value is mapping -%}
57
+ {{- format_parameters(item_value, value['items']['required'] | default([])) -}}
58
+ {%- endif -%}
59
+ }
60
+ {%- elif item_key == 'required' -%}
61
+ required:[
62
+ {%- for req_item in item_value -%}
63
+ <|"|>{{- req_item -}}<|"|>
64
+ {%- if not loop.last %},{% endif -%}
65
+ {%- endfor -%}
66
+ ]
67
+ {%- elif item_key == 'type' -%}
68
+ {%- if item_value is string -%}
69
+ type:{{ format_argument(item_value | upper) }}
70
+ {%- else -%}
71
+ type:{{ format_argument(item_value | map('upper') | list) }}
72
+ {%- endif -%}
73
+ {%- else -%}
74
+ {{ item_key }}:{{ format_argument(item_value) }}
75
+ {%- endif -%}
76
+ {%- endif -%}
77
+ {%- endfor -%}
78
+ }
79
+ {%- endif -%}
80
+ {%- endif -%}
81
+ {%- if value['nullable'] %}
82
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
83
+ nullable:true
84
+ {%- endif -%}
85
+ {%- if value['type'] | upper == 'OBJECT' -%}
86
+ {%- if value['properties'] is defined and value['properties'] is mapping -%}
87
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
88
+ properties:{
89
+ {{- format_parameters(value['properties'], value['required'] | default([])) -}}
90
+ }
91
+ {%- elif value is mapping -%}
92
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
93
+ properties:{
94
+ {{- format_parameters(value, value['required'] | default([]), filter_keys=true) -}}
95
+ }
96
+ {%- endif -%}
97
+ {%- if value['required'] -%}
98
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
99
+ required:[
100
+ {%- for item in value['required'] | default([]) -%}
101
+ <|"|>{{- item -}}<|"|>
102
+ {%- if not loop.last %},{% endif -%}
103
+ {%- endfor -%}
104
+ ]
105
+ {%- endif -%}
106
+ {%- endif -%}
107
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
108
+ type:<|"|>{{ value['type'] | upper }}<|"|>}
109
+ {%- endif -%}
110
+ {%- endfor -%}
111
+ {%- endmacro -%}
112
+ {%- macro format_function_declaration(tool_data) -%}
113
+ declaration:{{- tool_data['function']['name'] -}}{description:<|"|>{{- tool_data['function']['description'] -}}<|"|>
114
+ {%- set params = tool_data['function']['parameters'] -%}
115
+ {%- if params -%}
116
+ ,parameters:{
117
+ {%- if params['properties'] -%}
118
+ properties:{ {{- format_parameters(params['properties'], params['required']) -}} },
119
+ {%- endif -%}
120
+ {%- if params['required'] -%}
121
+ required:[
122
+ {%- for item in params['required'] -%}
123
+ <|"|>{{- item -}}<|"|>
124
+ {{- ',' if not loop.last -}}
125
+ {%- endfor -%}
126
+ ],
127
+ {%- endif -%}
128
+ {%- if params['type'] -%}
129
+ type:<|"|>{{- params['type'] | upper -}}<|"|>}
130
+ {%- endif -%}
131
+ {%- endif -%}
132
+ {%- if 'response' in tool_data['function'] -%}
133
+ {%- set response_declaration = tool_data['function']['response'] -%}
134
+ ,response:{
135
+ {%- if response_declaration['description'] -%}
136
+ description:<|"|>{{- response_declaration['description'] -}}<|"|>,
137
+ {%- endif -%}
138
+ {%- if response_declaration['type'] | upper == 'OBJECT' -%}
139
+ type:<|"|>{{- response_declaration['type'] | upper -}}<|"|>}
140
+ {%- endif -%}
141
+ {%- endif -%}
142
+ }
143
+ {%- endmacro -%}
144
+ {%- macro format_argument(argument, escape_keys=True) -%}
145
+ {%- if argument is none -%}
146
+ {{- 'null' -}}
147
+ {%- elif argument is string -%}
148
+ {{- '<|"|>' + argument + '<|"|>' -}}
149
+ {%- elif argument is boolean -%}
150
+ {{- 'true' if argument else 'false' -}}
151
+ {%- elif argument is mapping -%}
152
+ {{- '{' -}}
153
+ {%- set ns = namespace(found_first=false) -%}
154
+ {%- for key, value in argument | dictsort -%}
155
+ {%- if ns.found_first %},{% endif -%}
156
+ {%- set ns.found_first = true -%}
157
+ {%- if escape_keys -%}
158
+ {{- '<|"|>' + key + '<|"|>' -}}
159
+ {%- else -%}
160
+ {{- key -}}
161
+ {%- endif -%}
162
+ :{{- format_argument(value, escape_keys=escape_keys) -}}
163
+ {%- endfor -%}
164
+ {{- '}' -}}
165
+ {%- elif argument is sequence -%}
166
+ {{- '[' -}}
167
+ {%- for item in argument -%}
168
+ {{- format_argument(item, escape_keys=escape_keys) -}}
169
+ {%- if not loop.last %},{% endif -%}
170
+ {%- endfor -%}
171
+ {{- ']' -}}
172
+ {%- else -%}
173
+ {{- argument -}}
174
+ {%- endif -%}
175
+ {%- endmacro -%}
176
+ {%- macro strip_thinking(text) -%}
177
+ {%- set ns = namespace(result='') -%}
178
+ {%- for part in text.split('<channel|>') -%}
179
+ {%- if '<|channel>' in part -%}
180
+ {%- set ns.result = ns.result + part.split('<|channel>')[0] -%}
181
+ {%- else -%}
182
+ {%- set ns.result = ns.result + part -%}
183
+ {%- endif -%}
184
+ {%- endfor -%}
185
+ {{- ns.result | trim -}}
186
+ {%- endmacro -%}
187
+
188
+ {%- macro format_tool_response_block(tool_name, response) -%}
189
+ {{- '<|tool_response>' -}}
190
+ {%- if response is mapping -%}
191
+ {{- 'response:' + tool_name + '{' -}}
192
+ {%- for key, value in response | dictsort -%}
193
+ {{- key -}}:{{- format_argument(value, escape_keys=False) -}}
194
+ {%- if not loop.last %},{% endif -%}
195
+ {%- endfor -%}
196
+ {{- '}' -}}
197
+ {%- else -%}
198
+ {{- 'response:' + tool_name + '{value:' + format_argument(response, escape_keys=False) + '}' -}}
199
+ {%- endif -%}
200
+ {{- '<tool_response|>' -}}
201
+ {%- endmacro -%}
202
+
203
+ {#- ===== SETUP ===== -#}
204
+ {%- set ns = namespace(prev_message_type=None, prev_non_tool_role=None) -%}
205
+ {%- set loop_messages = messages -%}
206
+ {%- set enable_thinking = enable_thinking | default(false) -%}
207
+ {%- set preserve_thinking = preserve_thinking | default(false) -%}
208
+ {{- bos_token -}}
209
+ {#- Handle System/Tool Definitions Block -#}
210
+ {%- if enable_thinking or tools or (messages and messages[0]['role'] in ['system', 'developer']) -%}
211
+ {{- '<|turn>system\n' -}}
212
+ {#- Inject Thinking token at the very top of the FIRST system turn -#}
213
+ {%- if enable_thinking -%}
214
+ {{- '<|think|>\n' -}}
215
+ {%- set ns.prev_message_type = 'think' -%}
216
+ {%- endif -%}
217
+ {%- if messages and messages[0]['role'] in ['system', 'developer'] -%}
218
+ {%- if messages[0]['content'] is string -%}
219
+ {{- messages[0]['content'] | trim -}}
220
+ {%- elif messages[0]['content'] is sequence -%}
221
+ {%- for item in messages[0]['content'] -%}
222
+ {{- item['text'] | trim + ' '-}}
223
+ {%- endfor -%}
224
+ {%- endif -%}
225
+ {%- set loop_messages = messages[1:] -%}
226
+ {%- endif -%}
227
+ {%- if tools -%}
228
+ {%- for tool in tools %}
229
+ {{- '<|tool>' -}}
230
+ {{- format_function_declaration(tool) | trim -}}
231
+ {{- '<tool|>' -}}
232
+ {%- endfor %}
233
+ {%- set ns.prev_message_type = 'tool' -%}
234
+ {%- endif -%}
235
+ {{- '<turn|>\n' -}}
236
+ {%- endif %}
237
+
238
+ {#- Pre-scan: find last user message index for reasoning guard -#}
239
+ {%- set ns_turn = namespace(last_user_idx=-1) -%}
240
+ {%- for i in range(loop_messages | length) -%}
241
+ {%- if loop_messages[i]['role'] == 'user' -%}
242
+ {%- set ns_turn.last_user_idx = i -%}
243
+ {%- endif -%}
244
+ {%- endfor -%}
245
+
246
+ {#- Loop through messages -#}
247
+ {%- for message in loop_messages -%}
248
+ {%- if message['role'] != 'tool' -%}
249
+ {%- set ns.prev_message_type = None -%}
250
+ {%- set role = 'model' if message['role'] == 'assistant' else message['role'] -%}
251
+ {#- Detect continuation using tracked state — O(1) instead of O(n) backward scan -#}
252
+ {%- set continue_same_model_turn = (role == 'model' and ns.prev_non_tool_role == 'assistant') -%}
253
+ {%- if not continue_same_model_turn -%}
254
+ {{- '<|turn>' + role + '\n' }}
255
+
256
+ {%- endif -%}
257
+
258
+ {#- Render reasoning/reasoning_content as thinking channel -#}
259
+ {%- set thinking_text = message.get('reasoning') or message.get('reasoning_content') -%}
260
+ {%- set thinking_gate = (loop.index0 > ns_turn.last_user_idx) or (preserve_thinking and message.get('tool_calls')) -%}
261
+ {%- if thinking_text and thinking_gate -%}
262
+ {{- '<|channel>thought\n' + thinking_text + '\n<channel|>' -}}
263
+ {%- endif -%}
264
+
265
+ {%- set captured_content -%}
266
+ {%- if message.get('content') is string -%}
267
+ {%- if role == 'model' -%}
268
+ {{- strip_thinking(message['content']) -}}
269
+ {%- else -%}
270
+ {{- message['content'] | trim -}}
271
+ {%- endif -%}
272
+ {%- elif message.get('content') is sequence -%}
273
+ {%- for item in message['content'] -%}
274
+ {%- if item.get('type') == 'text' -%}
275
+ {%- if role == 'model' -%}
276
+ {{- strip_thinking(item['text']) -}}
277
+ {%- else -%}
278
+ {{- item['text'] | trim -}}
279
+ {%- endif -%}
280
+ {%- elif item.get('type') in ['image', 'image_url'] -%}
281
+ {{- '<|image|>' -}}
282
+ {%- elif item.get('type') in ['audio', 'input_audio'] -%}
283
+ {{- '<|audio|>' -}}
284
+ {%- elif item.get('type') == 'video' -%}
285
+ {{- '<|video|>' -}}
286
+ {%- endif -%}
287
+ {%- endfor -%}
288
+ {%- endif -%}
289
+ {%- endset -%}
290
+
291
+ {#- The sole intentional behavior change from HF main: preserve
292
+ OpenAI assistant content before its tool calls. -#}
293
+ {%- if role == 'model' and message.get('tool_calls') -%}
294
+ {{- captured_content -}}
295
+ {%- endif -%}
296
+
297
+ {%- if message.get('tool_calls') -%}
298
+ {%- for tool_call in message.get('tool_calls') -%}
299
+ {%- set function = tool_call['function'] -%}
300
+ {{- '<|tool_call>call:' + function['name'] + '{' -}}
301
+ {%- if function['arguments'] is mapping -%}
302
+ {%- set ns_args = namespace(found_first=false) -%}
303
+ {%- for key, value in function['arguments'] | dictsort -%}
304
+ {%- if ns_args.found_first %},{% endif -%}
305
+ {%- set ns_args.found_first = true -%}
306
+ {{- key -}}:{{- format_argument(value, escape_keys=False) -}}
307
+ {%- endfor -%}
308
+ {%- elif function['arguments'] is none -%}
309
+ {%- else -%}
310
+ {{- raise_exception(
311
+ "chat_template: tool_calls[].function.arguments must be a "
312
+ "JSON object (mapping), not a string. Deserialize arguments "
313
+ "before passing to the template."
314
+ ) -}}
315
+ {%- endif -%}
316
+ {{- '}<tool_call|>' -}}
317
+ {%- endfor -%}
318
+ {%- set ns.prev_message_type = 'tool_call' -%}
319
+ {%- endif -%}
320
+
321
+ {%- set ns_tr_out = namespace(flag=false) -%}
322
+ {%- if message.get('tool_responses') -%}
323
+ {#- Legacy: tool_responses embedded on the assistant message (Google/Gemma native) -#}
324
+ {%- for tool_response in message.get('tool_responses') -%}
325
+ {{- format_tool_response_block(tool_response['name'] | default('unknown', true), tool_response['response']) -}}
326
+ {%- set ns_tr_out.flag = true -%}
327
+ {%- set ns.prev_message_type = 'tool_response' -%}
328
+ {%- endfor -%}
329
+ {%- elif message.get('tool_calls') -%}
330
+ {#- OpenAI Chat Completions: forward-scan consecutive role:tool messages -#}
331
+ {%- set ns_tool_scan = namespace(stopped=false) -%}
332
+ {%- for k in range(loop.index0 + 1, loop_messages | length) -%}
333
+ {%- if ns_tool_scan.stopped -%}
334
+ {%- elif loop_messages[k]['role'] != 'tool' -%}
335
+ {%- set ns_tool_scan.stopped = true -%}
336
+ {%- else -%}
337
+ {%- set follow = loop_messages[k] -%}
338
+ {#- Resolve tool_call_id to function name -#}
339
+ {%- set ns_tname = namespace(name=follow.get('name') or 'unknown') -%}
340
+ {%- for tc in message.get('tool_calls') -%}
341
+ {%- if tc.get('id') == follow.get('tool_call_id') -%}
342
+ {%- set ns_tname.name = tc['function']['name'] -%}
343
+ {%- endif -%}
344
+ {%- endfor -%}
345
+ {#- Handle content as string or content-parts array -#}
346
+ {%- set tool_body = follow.get('content') -%}
347
+ {%- if tool_body is string -%}
348
+ {{- format_tool_response_block(ns_tname.name, tool_body) -}}
349
+ {%- elif tool_body is sequence and tool_body is not string -%}
350
+ {%- set ns_txt = namespace(s='') -%}
351
+ {%- for part in tool_body -%}
352
+ {%- if part.get('type') == 'text' -%}
353
+ {%- set ns_txt.s = ns_txt.s + (part.get('text') | default('')) -%}
354
+ {%- endif -%}
355
+ {%- endfor -%}
356
+ {{- format_tool_response_block(ns_tname.name, ns_txt.s) -}}
357
+ {%- for part in tool_body -%}
358
+ {%- if part.get('type') in ['image', 'image_url'] -%}
359
+ {{- '<|image|>' -}}
360
+ {%- elif part.get('type') in ['audio', 'input_audio'] -%}
361
+ {{- '<|audio|>' -}}
362
+ {%- elif part.get('type') == 'video' -%}
363
+ {{- '<|video|>' -}}
364
+ {%- endif -%}
365
+ {%- endfor -%}
366
+ {%- else -%}
367
+ {{- format_tool_response_block(ns_tname.name, tool_body) -}}
368
+ {%- endif -%}
369
+ {%- set ns_tr_out.flag = true -%}
370
+ {%- set ns.prev_message_type = 'tool_response' -%}
371
+ {%- endif -%}
372
+ {%- endfor -%}
373
+ {%- endif -%}
374
+
375
+ {%- if not (role == 'model' and message.get('tool_calls')) -%}
376
+ {{- captured_content -}}
377
+ {%- endif -%}
378
+ {%- set has_content = captured_content | trim | length > 0 -%}
379
+
380
+ {#- Forward-scan: find next non-tool message role for continuation detection -#}
381
+ {%- set next_nt = namespace(role=None, found=false) -%}
382
+ {%- for j in range(loop.index0 + 1, loop_messages | length) -%}
383
+ {%- if not next_nt.found -%}
384
+ {%- if loop_messages[j]['role'] != 'tool' -%}
385
+ {%- set next_nt.role = loop_messages[j]['role'] -%}
386
+ {%- set next_nt.found = true -%}
387
+ {%- endif -%}
388
+ {%- endif -%}
389
+ {%- endfor -%}
390
+
391
+ {%- set continues_into_next = (
392
+ role == 'model'
393
+ and next_nt.role == 'assistant'
394
+ and (not message.get('tool_calls') or ns_tr_out.flag)
395
+ ) -%}
396
+
397
+ {%- if ns.prev_message_type == 'tool_call' and not ns_tr_out.flag -%}
398
+ {{- '<|tool_response>' -}}
399
+ {%- elif continues_into_next -%}
400
+ {%- elif not (ns_tr_out.flag and not has_content and not next_nt.found) -%}
401
+ {{- '<turn|>\n' -}}
402
+ {%- endif -%}
403
+
404
+ {#- Track previous non-tool role for next iteration (avoids O(n) backward scan) -#}
405
+ {%- set ns.prev_non_tool_role = message['role'] -%}
406
+ {%- endif -%}
407
+ {%- endfor -%}
408
+
409
+ {%- if add_generation_prompt -%}
410
+ {%- if ns.prev_message_type != 'tool_response' and ns.prev_message_type != 'tool_call' -%}
411
+ {{- '<|turn>model\n' -}}
412
+ {%- elif ns.prev_message_type == 'tool_response' and enable_thinking -%}
413
+ {{- '<|channel>thought\n' -}}
414
+ {%- endif -%}
415
+ {%- endif -%}
adapter/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cc8d3a0ce36466ccc1278bf987df5f71db1719b9ca6b4118264f45cb627bfe0f
3
+ size 32169626
adapter/tokenizer_config.json ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "audio_token": "<|audio|>",
3
+ "backend": "tokenizers",
4
+ "boa_token": "<|audio>",
5
+ "boi_token": "<|image>",
6
+ "bos_token": "<bos>",
7
+ "eoa_token": "<audio|>",
8
+ "eoc_token": "<channel|>",
9
+ "eoi_token": "<image|>",
10
+ "eos_token": "<eos>",
11
+ "eot_token": "<turn|>",
12
+ "escape_token": "<|\"|>",
13
+ "etc_token": "<tool_call|>",
14
+ "etd_token": "<tool|>",
15
+ "etr_token": "<tool_response|>",
16
+ "extra_special_tokens": [
17
+ "<|video|>"
18
+ ],
19
+ "image_token": "<|image|>",
20
+ "is_local": true,
21
+ "local_files_only": true,
22
+ "mask_token": "<mask>",
23
+ "model_max_length": 1000000000000000019884624838656,
24
+ "model_specific_special_tokens": {
25
+ "audio_token": "<|audio|>",
26
+ "boa_token": "<|audio>",
27
+ "boi_token": "<|image>",
28
+ "eoa_token": "<audio|>",
29
+ "eoc_token": "<channel|>",
30
+ "eoi_token": "<image|>",
31
+ "eot_token": "<turn|>",
32
+ "escape_token": "<|\"|>",
33
+ "etc_token": "<tool_call|>",
34
+ "etd_token": "<tool|>",
35
+ "etr_token": "<tool_response|>",
36
+ "image_token": "<|image|>",
37
+ "soc_token": "<|channel>",
38
+ "sot_token": "<|turn>",
39
+ "stc_token": "<|tool_call>",
40
+ "std_token": "<|tool>",
41
+ "str_token": "<|tool_response>",
42
+ "think_token": "<|think|>"
43
+ },
44
+ "pad_token": "<pad>",
45
+ "padding_side": "left",
46
+ "processor_class": "Gemma4Processor",
47
+ "response_schema": {
48
+ "properties": {
49
+ "content": {
50
+ "type": "string"
51
+ },
52
+ "role": {
53
+ "const": "assistant"
54
+ },
55
+ "thinking": {
56
+ "type": "string"
57
+ },
58
+ "tool_calls": {
59
+ "items": {
60
+ "properties": {
61
+ "function": {
62
+ "properties": {
63
+ "arguments": {
64
+ "additionalProperties": {},
65
+ "type": "object",
66
+ "x-parser": "gemma4-tool-call"
67
+ },
68
+ "name": {
69
+ "type": "string"
70
+ }
71
+ },
72
+ "type": "object",
73
+ "x-regex": "call\\:(?P<name>\\w+)(?P<arguments>\\{.*\\})"
74
+ },
75
+ "type": {
76
+ "const": "function"
77
+ }
78
+ },
79
+ "type": "object"
80
+ },
81
+ "type": "array",
82
+ "x-regex-iterator": "<\\|tool_call>(.*?)<tool_call\\|>"
83
+ }
84
+ },
85
+ "type": "object",
86
+ "x-regex": "(\\<\\|channel\\>thought\\n(?P<thinking>.*?)\\<channel\\|\\>)?(?P<tool_calls>\\<\\|tool_call\\>.*\\<tool_call\\|\\>)?(?P<content>(?:(?!\\<turn\\|\\>)(?!\\<\\|tool_response\\>).)+)?(?:\\<turn\\|\\>|\\<\\|tool_response\\>)?"
87
+ },
88
+ "response_template": {
89
+ "defaults": {
90
+ "role": "assistant"
91
+ },
92
+ "fields": {
93
+ "content": {
94
+ "close": [
95
+ "<turn|>",
96
+ "<|tool_response>",
97
+ "<eos>"
98
+ ],
99
+ "content": "text"
100
+ },
101
+ "thinking": {
102
+ "close": "<channel|>",
103
+ "content": "text",
104
+ "open": "<|channel>thought\n"
105
+ },
106
+ "tool_calls": {
107
+ "close": "<tool_call|>",
108
+ "content": "json",
109
+ "content_args": {
110
+ "string_delims": [
111
+ [
112
+ "<|\"|>",
113
+ "<|\"|>"
114
+ ]
115
+ ],
116
+ "unquoted_keys": true
117
+ },
118
+ "open_pattern": "<\\|tool_call>call:(?P<name>\\w+)",
119
+ "repeats": true,
120
+ "transform": {
121
+ "function": {
122
+ "arguments": "{content}",
123
+ "name": "{name}"
124
+ },
125
+ "type": "function"
126
+ }
127
+ }
128
+ },
129
+ "start_anchor": [
130
+ "<|turn>model\n",
131
+ "<tool_response|>"
132
+ ]
133
+ },
134
+ "soc_token": "<|channel>",
135
+ "sot_token": "<|turn>",
136
+ "stc_token": "<|tool_call>",
137
+ "std_token": "<|tool>",
138
+ "str_token": "<|tool_response>",
139
+ "think_token": "<|think|>",
140
+ "tokenizer_class": "GemmaTokenizer",
141
+ "unk_token": "<unk>"
142
+ }
bias.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ | Field | Response |
2
+ | :---- | :---- |
3
+ | Participation considerations from adversely impacted groups [protected classes](https://www.senate.ca.gov/content/protected-classes) in model design and testing: | None |
4
+ | Bias Metric (If Measured): | [BBQ Accuracy Scores in Ambiguous Contexts](https://github.com/nyu-mll/BBQ/) |
5
+ | Which characteristic (feature) show(s) the greatest difference in performance?: | The model shows high variance in the characteristics when it is used with a high temperature. |
6
+ | Measures taken to mitigate against unwanted bias: | Supervised Fine-Tuning (SFT) and Reinforcement Learning from Human Feedback (RLHF) employed to calibrate the model’s reasoning capabilities to maintain logical consistency and appropriate complexity when interacting with or interpreting data from diverse age demographics. |
7
+ | If using internal data, description of methods implemented in data acquisition or processing, if any, to address the prevalence of identifiable biases in the training, testing, and validation data: | The training datasets contain a large amount of synthetic data generated by LLMs. We manually curated prompts. |
8
+ | Tools used to assess statistical imbalances and highlight patterns that may introduce bias into AI models: | [BBQ](https://github.com/nyu-mll/BBQ/) |
9
+ | Tools used to assess statistical imbalances and highlight patterns that may introduce bias into AI models: | These datasets, such as web-scraped finance reasoning data derived from SEC EDGAR filings, science and math problem datasets, OpenResearcher/source-document datasets, Common Crawl, CC-News, Wikimedia, and long-context document datasets, do not collectively or exhaustively represent all demographic groups (and proportionally therein). For instance, these datasets do not contain explicit mentions of demographic classes such as age, gender, or ethnicity in approximately 97% to 99.9% of finance reasoning samples and in over 85% of samples across the broader assessed datasets. In the subset where such terms are present, these datasets contain notable representational skews. For example, ethnicity mentions are often dominated by Middle Eastern contexts (found in finance documents) or "White," "Two or more," and "Black or African American" as the most frequent ethnic identifiers, while references categorized as male-only significantly outnumber those categorized as female-only. Furthermore, gender is explicitly mentioned in approximately 12% of samples across the broader dataset assessment, yet in only 0.9% of finance-specific samples. Dataset-level results vary by source type, with long-context/source-document datasets containing higher explicit demographic mention rates compared to certain web-scraped sources. To mitigate these imbalances, we recommend considering evaluation techniques such as bias audits, fine-tuning with demographically balanced datasets, and mitigation strategies such as counterfactual data augmentation to align with the desired model behavior. This evaluation used a 3,000-sample subset per dataset, identified as the optimal threshold for maximizing embedder accuracy. |
10
+ | Unwanted Bias Testing: | Constrained to English-language inputs. Multi-lingual parity is not currently claimed or guaranteed. |
chat_template.jinja ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- if conversational_mode | default(false) -%}
2
+ {%- set spoken_instruction = "You are having a spoken conversation with the user. Respond as briefly as possible while still addressing their questions fully." -%}
3
+ {%- if messages and messages[0]['role'] in ['system', 'developer'] -%}
4
+ {%- set spoken = namespace(text='') -%}
5
+ {%- if messages[0]['content'] is string -%}
6
+ {%- set spoken.text = messages[0]['content'] | trim -%}
7
+ {%- else -%}
8
+ {%- for part in messages[0]['content'] -%}
9
+ {%- set spoken.text = spoken.text + (part['text'] | trim) + ' ' -%}
10
+ {%- endfor -%}
11
+ {%- set spoken.text = spoken.text | trim -%}
12
+ {%- endif -%}
13
+ {%- if spoken.text != spoken_instruction -%}
14
+ {%- set spoken.text = (spoken.text + '\n\n' if spoken.text else '') + spoken_instruction -%}
15
+ {%- endif -%}
16
+ {%- set messages = [{'role': messages[0]['role'], 'content': spoken.text}] + messages[1:] -%}
17
+ {%- else -%}
18
+ {%- set messages = [{'role': 'system', 'content': spoken_instruction}] + messages -%}
19
+ {%- endif -%}
20
+ {%- endif -%}
21
+ {#
22
+ Template: Nemotron updated Gemma4 chat template, reasoning-off suffix ablation
23
+ Published: 2026-08-20
24
+ Context: Preserve content ordering before tool-calls while matching the original
25
+ Gemma 4 generation prompt when enable_thinking=false.
26
+ #}
27
+ {%- macro format_parameters(properties, required, filter_keys=false) -%}
28
+ {%- set standard_keys = ['description', 'type', 'properties', 'required', 'nullable'] -%}
29
+ {%- set ns = namespace(found_first=false) -%}
30
+ {%- for key, value in properties | dictsort -%}
31
+ {%- set add_comma = false -%}
32
+ {%- if not filter_keys or key not in standard_keys -%}
33
+ {%- if ns.found_first %},{% endif -%}
34
+ {%- set ns.found_first = true -%}
35
+ {{ key }}:{
36
+ {%- if value['description'] -%}
37
+ description:<|"|>{{ value['description'] }}<|"|>
38
+ {%- set add_comma = true -%}
39
+ {%- endif -%}
40
+ {%- if value['type'] | upper == 'STRING' -%}
41
+ {%- if value['enum'] -%}
42
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
43
+ enum:{{ format_argument(value['enum']) }}
44
+ {%- endif -%}
45
+ {%- elif value['type'] | upper == 'ARRAY' -%}
46
+ {%- if value['items'] is mapping and value['items'] -%}
47
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
48
+ items:{
49
+ {%- set ns_items = namespace(found_first=false) -%}
50
+ {%- for item_key, item_value in value['items'] | dictsort -%}
51
+ {%- if item_value is not none -%}
52
+ {%- if ns_items.found_first %},{% endif -%}
53
+ {%- set ns_items.found_first = true -%}
54
+ {%- if item_key == 'properties' -%}
55
+ properties:{
56
+ {%- if item_value is mapping -%}
57
+ {{- format_parameters(item_value, value['items']['required'] | default([])) -}}
58
+ {%- endif -%}
59
+ }
60
+ {%- elif item_key == 'required' -%}
61
+ required:[
62
+ {%- for req_item in item_value -%}
63
+ <|"|>{{- req_item -}}<|"|>
64
+ {%- if not loop.last %},{% endif -%}
65
+ {%- endfor -%}
66
+ ]
67
+ {%- elif item_key == 'type' -%}
68
+ {%- if item_value is string -%}
69
+ type:{{ format_argument(item_value | upper) }}
70
+ {%- else -%}
71
+ type:{{ format_argument(item_value | map('upper') | list) }}
72
+ {%- endif -%}
73
+ {%- else -%}
74
+ {{ item_key }}:{{ format_argument(item_value) }}
75
+ {%- endif -%}
76
+ {%- endif -%}
77
+ {%- endfor -%}
78
+ }
79
+ {%- endif -%}
80
+ {%- endif -%}
81
+ {%- if value['nullable'] %}
82
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
83
+ nullable:true
84
+ {%- endif -%}
85
+ {%- if value['type'] | upper == 'OBJECT' -%}
86
+ {%- if value['properties'] is defined and value['properties'] is mapping -%}
87
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
88
+ properties:{
89
+ {{- format_parameters(value['properties'], value['required'] | default([])) -}}
90
+ }
91
+ {%- elif value is mapping -%}
92
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
93
+ properties:{
94
+ {{- format_parameters(value, value['required'] | default([]), filter_keys=true) -}}
95
+ }
96
+ {%- endif -%}
97
+ {%- if value['required'] -%}
98
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
99
+ required:[
100
+ {%- for item in value['required'] | default([]) -%}
101
+ <|"|>{{- item -}}<|"|>
102
+ {%- if not loop.last %},{% endif -%}
103
+ {%- endfor -%}
104
+ ]
105
+ {%- endif -%}
106
+ {%- endif -%}
107
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
108
+ type:<|"|>{{ value['type'] | upper }}<|"|>}
109
+ {%- endif -%}
110
+ {%- endfor -%}
111
+ {%- endmacro -%}
112
+ {%- macro format_function_declaration(tool_data) -%}
113
+ declaration:{{- tool_data['function']['name'] -}}{description:<|"|>{{- tool_data['function']['description'] -}}<|"|>
114
+ {%- set params = tool_data['function']['parameters'] -%}
115
+ {%- if params -%}
116
+ ,parameters:{
117
+ {%- if params['properties'] -%}
118
+ properties:{ {{- format_parameters(params['properties'], params['required']) -}} },
119
+ {%- endif -%}
120
+ {%- if params['required'] -%}
121
+ required:[
122
+ {%- for item in params['required'] -%}
123
+ <|"|>{{- item -}}<|"|>
124
+ {{- ',' if not loop.last -}}
125
+ {%- endfor -%}
126
+ ],
127
+ {%- endif -%}
128
+ {%- if params['type'] -%}
129
+ type:<|"|>{{- params['type'] | upper -}}<|"|>}
130
+ {%- endif -%}
131
+ {%- endif -%}
132
+ {%- if 'response' in tool_data['function'] -%}
133
+ {%- set response_declaration = tool_data['function']['response'] -%}
134
+ ,response:{
135
+ {%- if response_declaration['description'] -%}
136
+ description:<|"|>{{- response_declaration['description'] -}}<|"|>,
137
+ {%- endif -%}
138
+ {%- if response_declaration['type'] | upper == 'OBJECT' -%}
139
+ type:<|"|>{{- response_declaration['type'] | upper -}}<|"|>}
140
+ {%- endif -%}
141
+ {%- endif -%}
142
+ }
143
+ {%- endmacro -%}
144
+ {%- macro format_argument(argument, escape_keys=True) -%}
145
+ {%- if argument is none -%}
146
+ {{- 'null' -}}
147
+ {%- elif argument is string -%}
148
+ {{- '<|"|>' + argument + '<|"|>' -}}
149
+ {%- elif argument is boolean -%}
150
+ {{- 'true' if argument else 'false' -}}
151
+ {%- elif argument is mapping -%}
152
+ {{- '{' -}}
153
+ {%- set ns = namespace(found_first=false) -%}
154
+ {%- for key, value in argument | dictsort -%}
155
+ {%- if ns.found_first %},{% endif -%}
156
+ {%- set ns.found_first = true -%}
157
+ {%- if escape_keys -%}
158
+ {{- '<|"|>' + key + '<|"|>' -}}
159
+ {%- else -%}
160
+ {{- key -}}
161
+ {%- endif -%}
162
+ :{{- format_argument(value, escape_keys=escape_keys) -}}
163
+ {%- endfor -%}
164
+ {{- '}' -}}
165
+ {%- elif argument is sequence -%}
166
+ {{- '[' -}}
167
+ {%- for item in argument -%}
168
+ {{- format_argument(item, escape_keys=escape_keys) -}}
169
+ {%- if not loop.last %},{% endif -%}
170
+ {%- endfor -%}
171
+ {{- ']' -}}
172
+ {%- else -%}
173
+ {{- argument -}}
174
+ {%- endif -%}
175
+ {%- endmacro -%}
176
+ {%- macro strip_thinking(text) -%}
177
+ {%- set ns = namespace(result='') -%}
178
+ {%- for part in text.split('<channel|>') -%}
179
+ {%- if '<|channel>' in part -%}
180
+ {%- set ns.result = ns.result + part.split('<|channel>')[0] -%}
181
+ {%- else -%}
182
+ {%- set ns.result = ns.result + part -%}
183
+ {%- endif -%}
184
+ {%- endfor -%}
185
+ {{- ns.result | trim -}}
186
+ {%- endmacro -%}
187
+
188
+ {%- macro format_tool_response_block(tool_name, response) -%}
189
+ {{- '<|tool_response>' -}}
190
+ {%- if response is mapping -%}
191
+ {{- 'response:' + tool_name + '{' -}}
192
+ {%- for key, value in response | dictsort -%}
193
+ {{- key -}}:{{- format_argument(value, escape_keys=False) -}}
194
+ {%- if not loop.last %},{% endif -%}
195
+ {%- endfor -%}
196
+ {{- '}' -}}
197
+ {%- else -%}
198
+ {{- 'response:' + tool_name + '{value:' + format_argument(response, escape_keys=False) + '}' -}}
199
+ {%- endif -%}
200
+ {{- '<tool_response|>' -}}
201
+ {%- endmacro -%}
202
+
203
+ {#- ===== SETUP ===== -#}
204
+ {%- set ns = namespace(prev_message_type=None, prev_non_tool_role=None) -%}
205
+ {%- set loop_messages = messages -%}
206
+ {%- set enable_thinking = enable_thinking | default(false) -%}
207
+ {%- set preserve_thinking = preserve_thinking | default(false) -%}
208
+ {{- bos_token -}}
209
+ {#- Handle System/Tool Definitions Block -#}
210
+ {%- if enable_thinking or tools or (messages and messages[0]['role'] in ['system', 'developer']) -%}
211
+ {{- '<|turn>system\n' -}}
212
+ {#- Inject Thinking token at the very top of the FIRST system turn -#}
213
+ {%- if enable_thinking -%}
214
+ {{- '<|think|>\n' -}}
215
+ {%- set ns.prev_message_type = 'think' -%}
216
+ {%- endif -%}
217
+ {%- if messages and messages[0]['role'] in ['system', 'developer'] -%}
218
+ {%- if messages[0]['content'] is string -%}
219
+ {{- messages[0]['content'] | trim -}}
220
+ {%- elif messages[0]['content'] is sequence -%}
221
+ {%- for item in messages[0]['content'] -%}
222
+ {{- item['text'] | trim + ' '-}}
223
+ {%- endfor -%}
224
+ {%- endif -%}
225
+ {%- set loop_messages = messages[1:] -%}
226
+ {%- endif -%}
227
+ {%- if tools -%}
228
+ {%- for tool in tools %}
229
+ {{- '<|tool>' -}}
230
+ {{- format_function_declaration(tool) | trim -}}
231
+ {{- '<tool|>' -}}
232
+ {%- endfor %}
233
+ {%- set ns.prev_message_type = 'tool' -%}
234
+ {%- endif -%}
235
+ {{- '<turn|>\n' -}}
236
+ {%- endif %}
237
+
238
+ {#- Pre-scan: find last user message index for reasoning guard -#}
239
+ {%- set ns_turn = namespace(last_user_idx=-1) -%}
240
+ {%- for i in range(loop_messages | length) -%}
241
+ {%- if loop_messages[i]['role'] == 'user' -%}
242
+ {%- set ns_turn.last_user_idx = i -%}
243
+ {%- endif -%}
244
+ {%- endfor -%}
245
+
246
+ {#- Loop through messages -#}
247
+ {%- for message in loop_messages -%}
248
+ {%- if message['role'] != 'tool' -%}
249
+ {%- set ns.prev_message_type = None -%}
250
+ {%- set role = 'model' if message['role'] == 'assistant' else message['role'] -%}
251
+ {#- Detect continuation using tracked state — O(1) instead of O(n) backward scan -#}
252
+ {%- set continue_same_model_turn = (role == 'model' and ns.prev_non_tool_role == 'assistant') -%}
253
+ {%- if not continue_same_model_turn -%}
254
+ {{- '<|turn>' + role + '\n' }}
255
+
256
+ {%- endif -%}
257
+
258
+ {#- Render reasoning/reasoning_content as thinking channel -#}
259
+ {%- set thinking_text = message.get('reasoning') or message.get('reasoning_content') -%}
260
+ {%- set thinking_gate = (loop.index0 > ns_turn.last_user_idx) or (preserve_thinking and message.get('tool_calls')) -%}
261
+ {%- if thinking_text and thinking_gate -%}
262
+ {{- '<|channel>thought\n' + thinking_text + '\n<channel|>' -}}
263
+ {%- endif -%}
264
+
265
+ {%- set captured_content -%}
266
+ {%- if message.get('content') is string -%}
267
+ {%- if role == 'model' -%}
268
+ {{- strip_thinking(message['content']) -}}
269
+ {%- else -%}
270
+ {{- message['content'] | trim -}}
271
+ {%- endif -%}
272
+ {%- elif message.get('content') is sequence -%}
273
+ {%- for item in message['content'] -%}
274
+ {%- if item.get('type') == 'text' -%}
275
+ {%- if role == 'model' -%}
276
+ {{- strip_thinking(item['text']) -}}
277
+ {%- else -%}
278
+ {{- item['text'] | trim -}}
279
+ {%- endif -%}
280
+ {%- elif item.get('type') in ['image', 'image_url'] -%}
281
+ {{- '<|image|>' -}}
282
+ {%- elif item.get('type') in ['audio', 'input_audio'] -%}
283
+ {{- '<|audio|>' -}}
284
+ {%- elif item.get('type') == 'video' -%}
285
+ {{- '<|video|>' -}}
286
+ {%- endif -%}
287
+ {%- endfor -%}
288
+ {%- endif -%}
289
+ {%- endset -%}
290
+
291
+ {#- The sole intentional behavior change from HF main: preserve
292
+ OpenAI assistant content before its tool calls. -#}
293
+ {%- if role == 'model' and message.get('tool_calls') -%}
294
+ {{- captured_content -}}
295
+ {%- endif -%}
296
+
297
+ {%- if message.get('tool_calls') -%}
298
+ {%- for tool_call in message.get('tool_calls') -%}
299
+ {%- set function = tool_call['function'] -%}
300
+ {{- '<|tool_call>call:' + function['name'] + '{' -}}
301
+ {%- if function['arguments'] is mapping -%}
302
+ {%- set ns_args = namespace(found_first=false) -%}
303
+ {%- for key, value in function['arguments'] | dictsort -%}
304
+ {%- if ns_args.found_first %},{% endif -%}
305
+ {%- set ns_args.found_first = true -%}
306
+ {{- key -}}:{{- format_argument(value, escape_keys=False) -}}
307
+ {%- endfor -%}
308
+ {%- elif function['arguments'] is none -%}
309
+ {%- else -%}
310
+ {{- raise_exception(
311
+ "chat_template: tool_calls[].function.arguments must be a "
312
+ "JSON object (mapping), not a string. Deserialize arguments "
313
+ "before passing to the template."
314
+ ) -}}
315
+ {%- endif -%}
316
+ {{- '}<tool_call|>' -}}
317
+ {%- endfor -%}
318
+ {%- set ns.prev_message_type = 'tool_call' -%}
319
+ {%- endif -%}
320
+
321
+ {%- set ns_tr_out = namespace(flag=false) -%}
322
+ {%- if message.get('tool_responses') -%}
323
+ {#- Legacy: tool_responses embedded on the assistant message (Google/Gemma native) -#}
324
+ {%- for tool_response in message.get('tool_responses') -%}
325
+ {{- format_tool_response_block(tool_response['name'] | default('unknown', true), tool_response['response']) -}}
326
+ {%- set ns_tr_out.flag = true -%}
327
+ {%- set ns.prev_message_type = 'tool_response' -%}
328
+ {%- endfor -%}
329
+ {%- elif message.get('tool_calls') -%}
330
+ {#- OpenAI Chat Completions: forward-scan consecutive role:tool messages -#}
331
+ {%- set ns_tool_scan = namespace(stopped=false) -%}
332
+ {%- for k in range(loop.index0 + 1, loop_messages | length) -%}
333
+ {%- if ns_tool_scan.stopped -%}
334
+ {%- elif loop_messages[k]['role'] != 'tool' -%}
335
+ {%- set ns_tool_scan.stopped = true -%}
336
+ {%- else -%}
337
+ {%- set follow = loop_messages[k] -%}
338
+ {#- Resolve tool_call_id to function name -#}
339
+ {%- set ns_tname = namespace(name=follow.get('name') or 'unknown') -%}
340
+ {%- for tc in message.get('tool_calls') -%}
341
+ {%- if tc.get('id') == follow.get('tool_call_id') -%}
342
+ {%- set ns_tname.name = tc['function']['name'] -%}
343
+ {%- endif -%}
344
+ {%- endfor -%}
345
+ {#- Handle content as string or content-parts array -#}
346
+ {%- set tool_body = follow.get('content') -%}
347
+ {%- if tool_body is string -%}
348
+ {{- format_tool_response_block(ns_tname.name, tool_body) -}}
349
+ {%- elif tool_body is sequence and tool_body is not string -%}
350
+ {%- set ns_txt = namespace(s='') -%}
351
+ {%- for part in tool_body -%}
352
+ {%- if part.get('type') == 'text' -%}
353
+ {%- set ns_txt.s = ns_txt.s + (part.get('text') | default('')) -%}
354
+ {%- endif -%}
355
+ {%- endfor -%}
356
+ {{- format_tool_response_block(ns_tname.name, ns_txt.s) -}}
357
+ {%- for part in tool_body -%}
358
+ {%- if part.get('type') in ['image', 'image_url'] -%}
359
+ {{- '<|image|>' -}}
360
+ {%- elif part.get('type') in ['audio', 'input_audio'] -%}
361
+ {{- '<|audio|>' -}}
362
+ {%- elif part.get('type') == 'video' -%}
363
+ {{- '<|video|>' -}}
364
+ {%- endif -%}
365
+ {%- endfor -%}
366
+ {%- else -%}
367
+ {{- format_tool_response_block(ns_tname.name, tool_body) -}}
368
+ {%- endif -%}
369
+ {%- set ns_tr_out.flag = true -%}
370
+ {%- set ns.prev_message_type = 'tool_response' -%}
371
+ {%- endif -%}
372
+ {%- endfor -%}
373
+ {%- endif -%}
374
+
375
+ {%- if not (role == 'model' and message.get('tool_calls')) -%}
376
+ {{- captured_content -}}
377
+ {%- endif -%}
378
+ {%- set has_content = captured_content | trim | length > 0 -%}
379
+
380
+ {#- Forward-scan: find next non-tool message role for continuation detection -#}
381
+ {%- set next_nt = namespace(role=None, found=false) -%}
382
+ {%- for j in range(loop.index0 + 1, loop_messages | length) -%}
383
+ {%- if not next_nt.found -%}
384
+ {%- if loop_messages[j]['role'] != 'tool' -%}
385
+ {%- set next_nt.role = loop_messages[j]['role'] -%}
386
+ {%- set next_nt.found = true -%}
387
+ {%- endif -%}
388
+ {%- endif -%}
389
+ {%- endfor -%}
390
+
391
+ {%- set continues_into_next = (
392
+ role == 'model'
393
+ and next_nt.role == 'assistant'
394
+ and (not message.get('tool_calls') or ns_tr_out.flag)
395
+ ) -%}
396
+
397
+ {%- if ns.prev_message_type == 'tool_call' and not ns_tr_out.flag -%}
398
+ {{- '<|tool_response>' -}}
399
+ {%- elif continues_into_next -%}
400
+ {%- elif not (ns_tr_out.flag and not has_content and not next_nt.found) -%}
401
+ {{- '<turn|>\n' -}}
402
+ {%- endif -%}
403
+
404
+ {#- Track previous non-tool role for next iteration (avoids O(n) backward scan) -#}
405
+ {%- set ns.prev_non_tool_role = message['role'] -%}
406
+ {%- endif -%}
407
+ {%- endfor -%}
408
+
409
+ {%- if add_generation_prompt -%}
410
+ {%- if ns.prev_message_type != 'tool_response' and ns.prev_message_type != 'tool_call' -%}
411
+ {{- '<|turn>model\n' -}}
412
+ {%- elif ns.prev_message_type == 'tool_response' and enable_thinking -%}
413
+ {{- '<|channel>thought\n' -}}
414
+ {%- endif -%}
415
+ {%- endif -%}
config.json ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Gemma4ForConditionalGeneration"
4
+ ],
5
+ "audio_config": {
6
+ "_name_or_path": "",
7
+ "architectures": null,
8
+ "attention_chunk_size": 12,
9
+ "attention_context_left": 13,
10
+ "attention_context_right": 0,
11
+ "attention_invalid_logits_value": -1000000000.0,
12
+ "attention_logit_cap": 50.0,
13
+ "chunk_size_feed_forward": 0,
14
+ "conv_kernel_size": 5,
15
+ "dtype": "bfloat16",
16
+ "gradient_clipping": 10000000000.0,
17
+ "hidden_act": "silu",
18
+ "hidden_size": 1024,
19
+ "id2label": {
20
+ "0": "LABEL_0",
21
+ "1": "LABEL_1"
22
+ },
23
+ "initializer_range": 0.02,
24
+ "is_encoder_decoder": false,
25
+ "label2id": {
26
+ "LABEL_0": 0,
27
+ "LABEL_1": 1
28
+ },
29
+ "model_type": "gemma4_audio",
30
+ "num_attention_heads": 8,
31
+ "num_hidden_layers": 12,
32
+ "output_attentions": false,
33
+ "output_hidden_states": false,
34
+ "output_proj_dims": 1536,
35
+ "problem_type": null,
36
+ "residual_weight": 0.5,
37
+ "return_dict": true,
38
+ "rms_norm_eps": 1e-06,
39
+ "subsampling_conv_channels": [
40
+ 128,
41
+ 32
42
+ ],
43
+ "use_clipped_linears": true
44
+ },
45
+ "audio_token_id": 258881,
46
+ "boa_token_id": 256000,
47
+ "boi_token_id": 255999,
48
+ "dtype": "bfloat16",
49
+ "eoa_token_id": 258883,
50
+ "eoa_token_index": 258883,
51
+ "eoi_token_id": 258882,
52
+ "eos_token_id": [
53
+ 1,
54
+ 106
55
+ ],
56
+ "image_token_id": 258880,
57
+ "initializer_range": 0.02,
58
+ "model_type": "gemma4",
59
+ "text_config": {
60
+ "attention_bias": false,
61
+ "attention_dropout": 0.0,
62
+ "attention_k_eq_v": false,
63
+ "bos_token_id": 2,
64
+ "dtype": "bfloat16",
65
+ "enable_moe_block": false,
66
+ "eos_token_id": 1,
67
+ "expert_intermediate_size": null,
68
+ "final_logit_softcapping": 30.0,
69
+ "global_head_dim": 512,
70
+ "head_dim": 256,
71
+ "hidden_activation": "gelu_pytorch_tanh",
72
+ "hidden_size": 2560,
73
+ "hidden_size_per_layer_input": 256,
74
+ "initializer_range": 0.02,
75
+ "intermediate_size": 10240,
76
+ "layer_types": [
77
+ "sliding_attention",
78
+ "sliding_attention",
79
+ "sliding_attention",
80
+ "sliding_attention",
81
+ "sliding_attention",
82
+ "full_attention",
83
+ "sliding_attention",
84
+ "sliding_attention",
85
+ "sliding_attention",
86
+ "sliding_attention",
87
+ "sliding_attention",
88
+ "full_attention",
89
+ "sliding_attention",
90
+ "sliding_attention",
91
+ "sliding_attention",
92
+ "sliding_attention",
93
+ "sliding_attention",
94
+ "full_attention",
95
+ "sliding_attention",
96
+ "sliding_attention",
97
+ "sliding_attention",
98
+ "sliding_attention",
99
+ "sliding_attention",
100
+ "full_attention",
101
+ "sliding_attention",
102
+ "sliding_attention",
103
+ "sliding_attention",
104
+ "sliding_attention",
105
+ "sliding_attention",
106
+ "full_attention",
107
+ "sliding_attention",
108
+ "sliding_attention",
109
+ "sliding_attention",
110
+ "sliding_attention",
111
+ "sliding_attention",
112
+ "full_attention",
113
+ "sliding_attention",
114
+ "sliding_attention",
115
+ "sliding_attention",
116
+ "sliding_attention",
117
+ "sliding_attention",
118
+ "full_attention"
119
+ ],
120
+ "max_position_embeddings": 65536,
121
+ "model_type": "gemma4_text",
122
+ "num_attention_heads": 8,
123
+ "num_experts": null,
124
+ "num_global_key_value_heads": null,
125
+ "num_hidden_layers": 42,
126
+ "num_key_value_heads": 2,
127
+ "num_kv_shared_layers": 18,
128
+ "pad_token_id": 0,
129
+ "rms_norm_eps": 1e-06,
130
+ "rope_parameters": {
131
+ "full_attention": {
132
+ "partial_rotary_factor": 0.25,
133
+ "rope_theta": 1000000.0,
134
+ "rope_type": "proportional"
135
+ },
136
+ "sliding_attention": {
137
+ "rope_theta": 10000.0,
138
+ "rope_type": "default"
139
+ }
140
+ },
141
+ "sliding_window": 512,
142
+ "tie_word_embeddings": true,
143
+ "top_k_experts": null,
144
+ "use_bidirectional_attention": null,
145
+ "use_cache": true,
146
+ "use_double_wide_mlp": false,
147
+ "vocab_size": 262144,
148
+ "vocab_size_per_layer_input": 262144
149
+ },
150
+ "tie_word_embeddings": true,
151
+ "transformers_version": "5.5.0.dev0",
152
+ "video_token_id": 258884,
153
+ "vision_config": {
154
+ "_name_or_path": "",
155
+ "architectures": null,
156
+ "attention_bias": false,
157
+ "attention_dropout": 0.0,
158
+ "chunk_size_feed_forward": 0,
159
+ "default_output_length": 280,
160
+ "dtype": "bfloat16",
161
+ "global_head_dim": 64,
162
+ "head_dim": 64,
163
+ "hidden_activation": "gelu_pytorch_tanh",
164
+ "hidden_size": 768,
165
+ "id2label": {
166
+ "0": "LABEL_0",
167
+ "1": "LABEL_1"
168
+ },
169
+ "initializer_range": 0.02,
170
+ "intermediate_size": 3072,
171
+ "is_encoder_decoder": false,
172
+ "label2id": {
173
+ "LABEL_0": 0,
174
+ "LABEL_1": 1
175
+ },
176
+ "max_position_embeddings": 131072,
177
+ "model_type": "gemma4_vision",
178
+ "num_attention_heads": 12,
179
+ "num_hidden_layers": 16,
180
+ "num_key_value_heads": 12,
181
+ "output_attentions": false,
182
+ "output_hidden_states": false,
183
+ "patch_size": 16,
184
+ "pooling_kernel_size": 3,
185
+ "position_embedding_size": 10240,
186
+ "problem_type": null,
187
+ "return_dict": true,
188
+ "rms_norm_eps": 1e-06,
189
+ "rope_parameters": {
190
+ "rope_theta": 100.0,
191
+ "rope_type": "default"
192
+ },
193
+ "standardize": false,
194
+ "use_clipped_linears": true
195
+ },
196
+ "vision_soft_tokens_per_image": 280
197
+ }
explainability.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ | Field | Response |
2
+ | :---- | :---- |
3
+ | Intended Task/Domain: | Text generation, reasoning, and chat |
4
+ | Model Type: | Text-to-text Gemma-4 |
5
+ | Intended Users: | Generative AI creators working with conversational AI models and image content. |
6
+ | Output: | Text |
7
+ | Tools used to evaluate datasets to identify synthetic data and ensure data authenticity. | We used a Gemma-3 4B-based filtering model fine-tuned on [Nemotron Content Safety Dataset v2](https://huggingface.co/datasets/nvidia/Aegis-AI-Content-Safety-Dataset-2.0) to ensure the quality of synthetic data. |
8
+ | Describe how the model works: | Generates text by predicting the next word or token based on the context provided in the input sequence using multiple self-attention layers. |
9
+ | List the groups (or group characteristics) for which this was tested to produce comparable outcomes: | Age, Disability Status, Gender Identity, Nationality, Physical Appearance, Ethnicity, Socioeconomic Status, Sexual Orientation, Religion |
10
+ | Technical Limitations & Mitigation: | This model performs particularly well in instruction following regimes, as such may be strongly influenced by untrusted inputs and should be paired with appropriate guardrails and data filtering to better align use-case behaviors when exposed to such data. |
11
+ | Verified to have met prescribed NVIDIA quality standards: | Yes |
12
+ | Performance Metrics: | Accuracy, Throughput, and User-side throughput |
13
+ | Potential Known Risks: | The model was optimized explicitly for instruction following and as such is more susceptible to prompt injection and jailbreaking in various forms as a result of its instruction tuning. This means that the model should be paired with additional rails or system filtering to limit exposure to instructions from malicious sources -- either directly or indirectly by retrieval (e.g. via visiting a website) -- as they may yield outputs that can lead to harmful, system-level outcomes up to and including remote code execution in agentic systems when effective security controls including guardrails are not in place. The model may generate answers that may be inaccurate, omit key information, include irrelevant or redundant text, or produce socially unacceptable or undesirable text, even if the prompt itself does not include anything explicitly offensive. |
14
+ | Licensing: | Use of this model is governed by the [OpenMDW License Agreement, version 1.1](https://github.com/OpenMDW/OpenMDW/blob/main/1.1/LICENSE.OpenMDW-1.1).  This model has been post-trained on the Google Gemma 4 model, and use of the Google Gemma 4 model is governed by [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
generation_config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 2,
3
+ "do_sample": true,
4
+ "eos_token_id": [
5
+ 1,
6
+ 106,
7
+ 50
8
+ ],
9
+ "pad_token_id": 0,
10
+ "temperature": 1.0,
11
+ "top_k": 64,
12
+ "top_p": 0.95,
13
+ "transformers_version": "5.5.0.dev0"
14
+ }
handler.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Custom Hugging Face Inference Endpoint handler for Gemma 4 checkpoints.
2
+
3
+ The checkpoint is a Gemma 4 image-text-to-text, tool-calling model
4
+ (``Gemma4ForConditionalGeneration``). The handler deliberately loads the
5
+ processor from ``path`` and calls its ``apply_chat_template`` method so the
6
+ ``chat_template.jinja`` packaged with the individual checkpoint is used.
7
+
8
+ Request payload (``data``) keys:
9
+
10
+ inputs (required) A string prompt or a list of chat messages.
11
+ tools (optional) OpenAI-style tool/function schemas.
12
+ parameters (optional) Keyword arguments forwarded to ``model.generate``.
13
+ enable_thinking
14
+ (optional) Gemma 4 chat-template control; defaults to false,
15
+ matching the checkpoint template's own default.
16
+ chat_template_kwargs
17
+ (optional) Additional checkpoint-template keyword arguments.
18
+
19
+ Message content can be a string or a list of text/image/video parts. Image
20
+ parts accept ``image`` or ``image_url`` as a URL, data URI, or base64 string.
21
+
22
+ Response: ``[{"generated_text": "..."}]``.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import base64
28
+ import io
29
+ from typing import Any, Dict, List, Optional, Union
30
+
31
+ import torch
32
+ from PIL import Image
33
+ from transformers import AutoModelForImageTextToText, AutoProcessor
34
+
35
+
36
+ DEFAULT_GENERATION_KWARGS: Dict[str, Any] = {
37
+ "max_new_tokens": 1024,
38
+ "do_sample": False,
39
+ "temperature": 0.7,
40
+ "top_p": 0.9,
41
+ }
42
+
43
+
44
+ class EndpointHandler:
45
+ def __init__(self, path: str = "") -> None:
46
+ # ``path`` is the model repository root supplied by the endpoint.
47
+ # Loading from this path also loads that checkpoint's chat_template.jinja.
48
+ self.processor = AutoProcessor.from_pretrained(path, trust_remote_code=True)
49
+
50
+ dtype = torch.bfloat16 if torch.cuda.is_available() else torch.float32
51
+ self.model = AutoModelForImageTextToText.from_pretrained(
52
+ path,
53
+ dtype=dtype,
54
+ device_map="auto" if torch.cuda.is_available() else None,
55
+ trust_remote_code=True,
56
+ )
57
+ self.model.eval()
58
+
59
+ self.tokenizer = getattr(self.processor, "tokenizer", self.processor)
60
+
61
+ @staticmethod
62
+ def _load_image(ref: str) -> Image.Image:
63
+ """Load a PIL image from a URL, data URI, or raw base64 string."""
64
+ if ref.startswith(("http://", "https://")):
65
+ import requests
66
+
67
+ response = requests.get(ref, timeout=30)
68
+ response.raise_for_status()
69
+ return Image.open(io.BytesIO(response.content)).convert("RGB")
70
+
71
+ if ref.startswith("data:"):
72
+ try:
73
+ ref = ref.split(",", 1)[1]
74
+ except IndexError as exc:
75
+ raise ValueError("Malformed image data URI") from exc
76
+
77
+ return Image.open(io.BytesIO(base64.b64decode(ref))).convert("RGB")
78
+
79
+ def _collect_images(self, messages: List[Dict[str, Any]]) -> List[Image.Image]:
80
+ images: List[Image.Image] = []
81
+ for message in messages:
82
+ content = message.get("content")
83
+ if not isinstance(content, list):
84
+ continue
85
+ for item in content:
86
+ if not isinstance(item, dict):
87
+ continue
88
+ if item.get("type") == "image" or "image" in item or "image_url" in item:
89
+ ref = item.get("image") or item.get("image_url")
90
+ if isinstance(ref, dict):
91
+ ref = ref.get("url")
92
+ if isinstance(ref, str):
93
+ images.append(self._load_image(ref))
94
+ return images
95
+
96
+ @staticmethod
97
+ def _build_messages(
98
+ inputs: Union[str, List[Dict[str, Any]]],
99
+ ) -> List[Dict[str, Any]]:
100
+ if isinstance(inputs, str):
101
+ return [{"role": "user", "content": inputs}]
102
+ if not isinstance(inputs, list):
103
+ raise TypeError("'inputs' must be a string or a list of chat messages")
104
+ return inputs
105
+
106
+ def _decode_completion(self, generated_ids: torch.Tensor) -> str:
107
+ # Gemma 4's channel and tool-call delimiters are special tokens. Do not
108
+ # use skip_special_tokens=True: doing so corrupts structured tool output.
109
+ text = self.processor.batch_decode(
110
+ generated_ids,
111
+ skip_special_tokens=False,
112
+ clean_up_tokenization_spaces=False,
113
+ )[0]
114
+
115
+ # Remove generation terminators without stripping structural special tokens.
116
+ for token in (self.tokenizer.eos_token, self.tokenizer.pad_token):
117
+ if token:
118
+ while text.endswith(token):
119
+ text = text[: -len(token)]
120
+ return text
121
+
122
+ @torch.inference_mode()
123
+ def __call__(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
124
+ if not isinstance(data, dict):
125
+ raise TypeError("Request payload must be a dictionary")
126
+
127
+ request = dict(data)
128
+ if "inputs" not in request:
129
+ raise ValueError("Request payload must include 'inputs'")
130
+
131
+ inputs = request.pop("inputs")
132
+ tools: Optional[List[Dict[str, Any]]] = request.pop("tools", None)
133
+ parameters: Dict[str, Any] = request.pop("parameters", {}) or {}
134
+ enable_thinking = bool(request.pop("enable_thinking", False))
135
+ chat_template_kwargs: Dict[str, Any] = request.pop(
136
+ "chat_template_kwargs", {}
137
+ ) or {}
138
+
139
+ if request:
140
+ unknown = ", ".join(sorted(request))
141
+ raise ValueError(f"Unknown request key(s): {unknown}")
142
+ if not isinstance(parameters, dict):
143
+ raise TypeError("'parameters' must be a dictionary")
144
+ if not isinstance(chat_template_kwargs, dict):
145
+ raise TypeError("'chat_template_kwargs' must be a dictionary")
146
+
147
+ messages = self._build_messages(inputs)
148
+ images = self._collect_images(messages)
149
+
150
+ # Top-level enable_thinking is authoritative and is passed directly to
151
+ # the packaged Gemma 4 Jinja template.
152
+ chat_template_kwargs = {
153
+ **chat_template_kwargs,
154
+ "enable_thinking": enable_thinking,
155
+ }
156
+ prompt = self.processor.apply_chat_template(
157
+ messages,
158
+ tools=tools,
159
+ tokenize=False,
160
+ add_generation_prompt=True,
161
+ **chat_template_kwargs,
162
+ )
163
+
164
+ processor_kwargs: Dict[str, Any] = {
165
+ "text": [prompt],
166
+ "return_tensors": "pt",
167
+ }
168
+ if images:
169
+ processor_kwargs["images"] = images
170
+
171
+ model_inputs = self.processor(**processor_kwargs)
172
+ model_inputs = {key: value.to(self.model.device) for key, value in model_inputs.items()}
173
+
174
+ generation_kwargs = {**DEFAULT_GENERATION_KWARGS, **parameters}
175
+ if not generation_kwargs.get("do_sample", False):
176
+ for key in ("temperature", "top_p", "top_k"):
177
+ generation_kwargs.pop(key, None)
178
+
179
+ generated_ids = self.model.generate(**model_inputs, **generation_kwargs)
180
+ input_length = model_inputs["input_ids"].shape[1]
181
+ new_tokens = generated_ids[:, input_length:]
182
+
183
+ return [{"generated_text": self._decode_completion(new_tokens)}]
inference_example.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Run FleetWorker-E4B on one task-board item. Use the project field; ignore priority (see the model card).
2
+
3
+ pip install "transformers>=5" torch
4
+ python inference_example.py "title: Add retry with backoff to the nightly export job"
5
+ """
6
+ import json
7
+ import sys
8
+
9
+ import torch
10
+ from transformers import AutoModelForCausalLM, AutoTokenizer
11
+
12
+ REPO = "ThakiCloud/FleetWorker-E4B"
13
+ LABELS = ["ai-experiment-measurement", "gitops-deployment-health", "synthetic-data-pipeline", "company-operations-agents", "ondevice-small-model", "todo-board-tooling", "enterprise-ops-benchmark", "shop-assistant-console", "commerce-growth-agents", "platform-strategy-planning", "decision-routing-research", "legal-decision-models", "secops-automation-console", "multimodal-api-gateway", "character-ai-app", "telecom-phy-research", "engineering-blog-publishing", "generative-video-training", "semiconductor-design-llm", "agent-trajectory-distillation", "korean-humanization-corpus", "edge-inference-box", "unattended-agent-runs", "personal-agent-system", "music-generation-rooms"]
14
+ SYSTEM = "You triage a task-board item. Output exactly one compact JSON object with keys \"project\" and \"priority\" and nothing else. \"project\" is one of: ai-experiment-measurement, gitops-deployment-health, synthetic-data-pipeline, company-operations-agents, ondevice-small-model, todo-board-tooling, enterprise-ops-benchmark, shop-assistant-console, commerce-growth-agents, platform-strategy-planning, decision-routing-research, legal-decision-models, secops-automation-console, multimodal-api-gateway, character-ai-app, telecom-phy-research, engineering-blog-publishing, generative-video-training, semiconductor-design-llm, agent-trajectory-distillation, korean-humanization-corpus, edge-inference-box, unattended-agent-runs, personal-agent-system, music-generation-rooms, or \"other\"; \"unknown\" if the text gives no way to tell. \"priority\" is one of: P0, P1, P2, P3."
15
+
16
+ tok = AutoTokenizer.from_pretrained(REPO)
17
+ model = AutoModelForCausalLM.from_pretrained(REPO, dtype=torch.bfloat16, device_map="auto").eval()
18
+ stop = [i for i in {tok.eos_token_id, tok.convert_tokens_to_ids("<turn|>")} if isinstance(i, int)]
19
+ item = sys.argv[1] if len(sys.argv) > 1 else "title: Add retry with backoff to the nightly export job"
20
+ messages = [{"role": "system", "content": SYSTEM}, {"role": "user", "content": item}]
21
+ prompt = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True, enable_thinking=False)
22
+ ids = tok(prompt, add_special_tokens=False, return_tensors="pt").to(model.device)
23
+ with torch.no_grad():
24
+ out = model.generate(**ids, max_new_tokens=64, do_sample=False, eos_token_id=stop)
25
+ text = tok.decode(out[0, ids["input_ids"].shape[1]:], skip_special_tokens=False).split("<turn|>")[0].strip()
26
+ pred = json.loads(text)
27
+ print({"project": pred.get("project"), "valid_label": pred.get("project") in LABELS + ["other", "unknown"]})
model-00001-of-00001.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:05a7213f8b0bf2409de51b5f0bba67aeae8d0d83383aaaebf64395303eb4d675
3
+ size 15882477468
model-frozen-shared-kv.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:32aaa38402af5a51c6d6b27aeb8a26d3e6964e5590330f0e7b2f546608da4023
3
+ size 110118048
model.safetensors.index.json ADDED
The diff for this file is too large to render. See raw diff
 
privacy.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ | Field | Response |
2
+ | :---- | :---- |
3
+ | Generatable or reverse engineerable personal data\*? | No |
4
+ | Personal data used to create this model? | No |
5
+ | Was consent obtained for any personal data\* used? | Not Applicable |
6
+ | (For GPAI Models): A description of any methods implemented in data acquisition or processing, if any, to address the prevalence of personal data in the training data, where relevant and applicable. | We used only prompts that do not contain any personal data for synthetic data generation. |
7
+ | How often is dataset reviewed? | Before Release |
8
+ | Is a mechanism in place to honor data subject right of access or deletion of personal data? | Not Applicable |
9
+ | If personal data was collected for the development of the model, was it collected directly by NVIDIA? | Not Applicable |
10
+ | If personal data was collected for the development of the model by NVIDIA, do you maintain or have access to disclosures made to data subjects? | Not Applicable |
11
+ | If personal data was collected for the development of this AI model, was it minimized to only what was required? | Not Applicable |
12
+ | Was data from user interactions with the AI model (e.g. user input and prompts) used to train the model? | No |
13
+ | Is there provenance for all datasets used in training? | Yes |
14
+ | Does data labeling (annotation, metadata) comply with privacy laws? | Yes |
15
+ | Is data compliant with data subject requests for data correction or removal, if such a request was made? | No, not possible with externally-sourced data. |
16
+ | Applicable Privacy Policy | [https://www.nvidia.com/en-us/about-nvidia/privacy-policy/](https://www.nvidia.com/en-us/about-nvidia/privacy-policy/) |
processor_config.json ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "audio_ms_per_token": 40,
3
+ "audio_seq_length": 750,
4
+ "feature_extractor": {
5
+ "dither": 0.0,
6
+ "feature_extractor_type": "Gemma4AudioFeatureExtractor",
7
+ "feature_size": 128,
8
+ "fft_length": 512,
9
+ "fft_overdrive": false,
10
+ "frame_length": 320,
11
+ "hop_length": 160,
12
+ "input_scale_factor": 1.0,
13
+ "max_frequency": 8000.0,
14
+ "mel_floor": 0.001,
15
+ "min_frequency": 0.0,
16
+ "padding_side": "right",
17
+ "padding_value": 0.0,
18
+ "per_bin_mean": null,
19
+ "per_bin_stddev": null,
20
+ "preemphasis": 0.0,
21
+ "preemphasis_htk_flavor": true,
22
+ "return_attention_mask": true,
23
+ "sampling_rate": 16000
24
+ },
25
+ "image_processor": {
26
+ "do_convert_rgb": true,
27
+ "do_normalize": false,
28
+ "do_rescale": true,
29
+ "do_resize": true,
30
+ "image_mean": [
31
+ 0.0,
32
+ 0.0,
33
+ 0.0
34
+ ],
35
+ "image_processor_type": "Gemma4ImageProcessor",
36
+ "image_seq_length": 280,
37
+ "image_std": [
38
+ 1.0,
39
+ 1.0,
40
+ 1.0
41
+ ],
42
+ "max_soft_tokens": 280,
43
+ "patch_size": 16,
44
+ "pooling_kernel_size": 3,
45
+ "resample": 3,
46
+ "rescale_factor": 0.00392156862745098
47
+ },
48
+ "image_seq_length": 280,
49
+ "processor_class": "Gemma4Processor",
50
+ "video_processor": {
51
+ "do_convert_rgb": true,
52
+ "do_normalize": true,
53
+ "do_rescale": true,
54
+ "do_resize": true,
55
+ "do_sample_frames": true,
56
+ "image_mean": [
57
+ 0.0,
58
+ 0.0,
59
+ 0.0
60
+ ],
61
+ "image_std": [
62
+ 1.0,
63
+ 1.0,
64
+ 1.0
65
+ ],
66
+ "max_soft_tokens": 70,
67
+ "num_frames": 32,
68
+ "patch_size": 16,
69
+ "pooling_kernel_size": 3,
70
+ "resample": 3,
71
+ "rescale_factor": 0.00392156862745098,
72
+ "return_metadata": false,
73
+ "video_processor_type": "Gemma4VideoProcessor"
74
+ }
75
+ }
safety.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ | Field | Response |
2
+ | :---- | :---- |
3
+ | Model Application Field(s): | Chat, Instruction Following, Chatbot Development, Code Generation, Reasoning, Customer Service |
4
+ | Describe the life critical impact (if present). | Not Applicable |
5
+ | Description of methods implemented in data acquisition or processing, if any, to address other types of potentially harmful data in the training, testing, and validation data: | We used a guard model for content safety to exclude potentially harmful data from training. |
6
+ | Description of any methods implemented in data acquisition or processing, if any, to address illegal or harmful content in the training data, including, but not limited to, child sexual abuse material (CSAM) and non-consensual intimate imagery (NCII) | We used a Gemma-3 4B-based guard model trained on [Nemotron Content Safety Dataset v2](https://huggingface.co/datasets/nvidia/Aegis-AI-Content-Safety-Dataset-2.0) for content safety to exclude potentially illegal or harmful content from the training. |
7
+ | Use Case Restrictions: | Use of this model is governed by the [OpenMDW License Agreement, version 1.1](https://github.com/OpenMDW/OpenMDW/blob/main/1.1/LICENSE.OpenMDW-1.1).  This model has been post-trained on the Google Gemma 4 model, and use of the Google Gemma 4 model is governed by [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)|
8
+ | Model and dataset restrictions: | The Principle of least privilege (PoLP) is applied limiting access for dataset generation and model development. Restrictions enforce dataset access during training, and dataset license constraints adhered to. |
9
+ | This AI model was developed based on our policies to ensure responsible data handling and risk mitigation. The datasets used for training have been scanned for harmful content and illegal content, consistent with our policies including scanning for Child Sexual Abuse Material (CSAM). Ongoing review and monitoring mechanisms are in place based on our policies and to maintain data integrity. | True. We use [Nemotron Content Safety Dataset V2](https://huggingface.co/datasets/nvidia/Aegis-AI-Content-Safety-Dataset-2.0) and an internal safety dataset specialized for minority sexuality for content safety evaluation to ensure the safety of this model. |
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cc8d3a0ce36466ccc1278bf987df5f71db1719b9ca6b4118264f45cb627bfe0f
3
+ size 32169626
tokenizer_config.json ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "audio_token": "<|audio|>",
3
+ "backend": "tokenizers",
4
+ "boa_token": "<|audio>",
5
+ "boi_token": "<|image>",
6
+ "bos_token": "<bos>",
7
+ "eoa_token": "<audio|>",
8
+ "eoc_token": "<channel|>",
9
+ "eoi_token": "<image|>",
10
+ "eos_token": "<eos>",
11
+ "eot_token": "<turn|>",
12
+ "escape_token": "<|\"|>",
13
+ "etc_token": "<tool_call|>",
14
+ "etd_token": "<tool|>",
15
+ "etr_token": "<tool_response|>",
16
+ "extra_special_tokens": [
17
+ "<|video|>"
18
+ ],
19
+ "image_token": "<|image|>",
20
+ "mask_token": "<mask>",
21
+ "model_max_length": 1000000000000000019884624838656,
22
+ "pad_token": "<pad>",
23
+ "padding_side": "left",
24
+ "processor_class": "Gemma4Processor",
25
+ "response_schema": {
26
+ "type": "object",
27
+ "properties": {
28
+ "role": {
29
+ "const": "assistant"
30
+ },
31
+ "thinking": {
32
+ "type": "string"
33
+ },
34
+ "content": {
35
+ "type": "string"
36
+ },
37
+ "tool_calls": {
38
+ "x-regex-iterator": "<\\|tool_call>(.*?)<tool_call\\|>",
39
+ "type": "array",
40
+ "items": {
41
+ "type": "object",
42
+ "properties": {
43
+ "type": {
44
+ "const": "function"
45
+ },
46
+ "function": {
47
+ "type": "object",
48
+ "x-regex": "call\\:(?P<name>\\w+)(?P<arguments>\\{.*\\})",
49
+ "properties": {
50
+ "name": {
51
+ "type": "string"
52
+ },
53
+ "arguments": {
54
+ "type": "object",
55
+ "x-parser": "gemma4-tool-call",
56
+ "additionalProperties": {}
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ },
64
+ "x-regex": "(\\<\\|channel\\>thought\\n(?P<thinking>.*?)\\<channel\\|\\>)?(?P<tool_calls>\\<\\|tool_call\\>.*\\<tool_call\\|\\>)?(?P<content>(?:(?!\\<turn\\|\\>)(?!\\<\\|tool_response\\>).)+)?(?:\\<turn\\|\\>|\\<\\|tool_response\\>)?"
65
+ },
66
+ "response_template": {
67
+ "defaults": {
68
+ "role": "assistant"
69
+ },
70
+ "fields": {
71
+ "content": {
72
+ "close": [
73
+ "<turn|>",
74
+ "<|tool_response>",
75
+ "<eos>"
76
+ ],
77
+ "content": "text"
78
+ },
79
+ "thinking": {
80
+ "close": "<channel|>",
81
+ "content": "text",
82
+ "open": "<|channel>thought\n"
83
+ },
84
+ "tool_calls": {
85
+ "close": "<tool_call|>",
86
+ "content": "json",
87
+ "content_args": {
88
+ "string_delims": [
89
+ [
90
+ "<|\"|>",
91
+ "<|\"|>"
92
+ ]
93
+ ],
94
+ "unquoted_keys": true
95
+ },
96
+ "open_pattern": "<\\|tool_call>call:(?P<name>\\w+)",
97
+ "repeats": true,
98
+ "transform": {
99
+ "function": {
100
+ "arguments": "{content}",
101
+ "name": "{name}"
102
+ },
103
+ "type": "function"
104
+ }
105
+ }
106
+ },
107
+ "start_anchor": [
108
+ "<|turn>model\n",
109
+ "<tool_response|>"
110
+ ]
111
+ },
112
+ "soc_token": "<|channel>",
113
+ "sot_token": "<|turn>",
114
+ "stc_token": "<|tool_call>",
115
+ "std_token": "<|tool>",
116
+ "str_token": "<|tool_response>",
117
+ "think_token": "<|think|>",
118
+ "tokenizer_class": "GemmaTokenizer",
119
+ "unk_token": "<unk>",
120
+ "chat_template": "{%- if conversational_mode | default(false) -%}\n {%- set spoken_instruction = \"You are having a spoken conversation with the user. Respond as briefly as possible while still addressing their questions fully.\" -%}\n {%- if messages and messages[0]['role'] in ['system', 'developer'] -%}\n {%- set spoken = namespace(text='') -%}\n {%- if messages[0]['content'] is string -%}\n {%- set spoken.text = messages[0]['content'] | trim -%}\n {%- else -%}\n {%- for part in messages[0]['content'] -%}\n {%- set spoken.text = spoken.text + (part['text'] | trim) + ' ' -%}\n {%- endfor -%}\n {%- set spoken.text = spoken.text | trim -%}\n {%- endif -%}\n {%- if spoken.text != spoken_instruction -%}\n {%- set spoken.text = (spoken.text + '\\n\\n' if spoken.text else '') + spoken_instruction -%}\n {%- endif -%}\n {%- set messages = [{'role': messages[0]['role'], 'content': spoken.text}] + messages[1:] -%}\n {%- else -%}\n {%- set messages = [{'role': 'system', 'content': spoken_instruction}] + messages -%}\n {%- endif -%}\n{%- endif -%}\n{#\n Template: Nemotron updated Gemma4 chat template, reasoning-off suffix ablation\n Published: 2026-08-20\n Context: Preserve content ordering before tool-calls while matching the original\n Gemma 4 generation prompt when enable_thinking=false.\n#}\n{%- macro format_parameters(properties, required, filter_keys=false) -%}\n {%- set standard_keys = ['description', 'type', 'properties', 'required', 'nullable'] -%}\n {%- set ns = namespace(found_first=false) -%}\n {%- for key, value in properties | dictsort -%}\n {%- set add_comma = false -%}\n {%- if not filter_keys or key not in standard_keys -%}\n {%- if ns.found_first %},{% endif -%}\n {%- set ns.found_first = true -%}\n {{ key }}:{\n {%- if value['description'] -%}\n description:<|\"|>{{ value['description'] }}<|\"|>\n {%- set add_comma = true -%}\n {%- endif -%}\n {%- if value['type'] | upper == 'STRING' -%}\n {%- if value['enum'] -%}\n {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n enum:{{ format_argument(value['enum']) }}\n {%- endif -%}\n {%- elif value['type'] | upper == 'ARRAY' -%}\n {%- if value['items'] is mapping and value['items'] -%}\n {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n items:{\n {%- set ns_items = namespace(found_first=false) -%}\n {%- for item_key, item_value in value['items'] | dictsort -%}\n {%- if item_value is not none -%}\n {%- if ns_items.found_first %},{% endif -%}\n {%- set ns_items.found_first = true -%}\n {%- if item_key == 'properties' -%}\n properties:{\n {%- if item_value is mapping -%}\n {{- format_parameters(item_value, value['items']['required'] | default([])) -}}\n {%- endif -%}\n }\n {%- elif item_key == 'required' -%}\n required:[\n {%- for req_item in item_value -%}\n <|\"|>{{- req_item -}}<|\"|>\n {%- if not loop.last %},{% endif -%}\n {%- endfor -%}\n ]\n {%- elif item_key == 'type' -%}\n {%- if item_value is string -%}\n type:{{ format_argument(item_value | upper) }}\n {%- else -%}\n type:{{ format_argument(item_value | map('upper') | list) }}\n {%- endif -%}\n {%- else -%}\n {{ item_key }}:{{ format_argument(item_value) }}\n {%- endif -%}\n {%- endif -%}\n {%- endfor -%}\n }\n {%- endif -%}\n {%- endif -%}\n {%- if value['nullable'] %}\n {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n nullable:true\n {%- endif -%}\n {%- if value['type'] | upper == 'OBJECT' -%}\n {%- if value['properties'] is defined and value['properties'] is mapping -%}\n {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n properties:{\n {{- format_parameters(value['properties'], value['required'] | default([])) -}}\n }\n {%- elif value is mapping -%}\n {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n properties:{\n {{- format_parameters(value, value['required'] | default([]), filter_keys=true) -}}\n }\n {%- endif -%}\n {%- if value['required'] -%}\n {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n required:[\n {%- for item in value['required'] | default([]) -%}\n <|\"|>{{- item -}}<|\"|>\n {%- if not loop.last %},{% endif -%}\n {%- endfor -%}\n ]\n {%- endif -%}\n {%- endif -%}\n {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n type:<|\"|>{{ value['type'] | upper }}<|\"|>}\n {%- endif -%}\n {%- endfor -%}\n{%- endmacro -%}\n{%- macro format_function_declaration(tool_data) -%}\n declaration:{{- tool_data['function']['name'] -}}{description:<|\"|>{{- tool_data['function']['description'] -}}<|\"|>\n {%- set params = tool_data['function']['parameters'] -%}\n {%- if params -%}\n ,parameters:{\n {%- if params['properties'] -%}\n properties:{ {{- format_parameters(params['properties'], params['required']) -}} },\n {%- endif -%}\n {%- if params['required'] -%}\n required:[\n {%- for item in params['required'] -%}\n <|\"|>{{- item -}}<|\"|>\n {{- ',' if not loop.last -}}\n {%- endfor -%}\n ],\n {%- endif -%}\n {%- if params['type'] -%}\n type:<|\"|>{{- params['type'] | upper -}}<|\"|>}\n {%- endif -%}\n {%- endif -%}\n {%- if 'response' in tool_data['function'] -%}\n {%- set response_declaration = tool_data['function']['response'] -%}\n ,response:{\n {%- if response_declaration['description'] -%}\n description:<|\"|>{{- response_declaration['description'] -}}<|\"|>,\n {%- endif -%}\n {%- if response_declaration['type'] | upper == 'OBJECT' -%}\n type:<|\"|>{{- response_declaration['type'] | upper -}}<|\"|>}\n {%- endif -%}\n {%- endif -%}\n }\n{%- endmacro -%}\n{%- macro format_argument(argument, escape_keys=True) -%}\n {%- if argument is none -%}\n {{- 'null' -}}\n {%- elif argument is string -%}\n {{- '<|\"|>' + argument + '<|\"|>' -}}\n {%- elif argument is boolean -%}\n {{- 'true' if argument else 'false' -}}\n {%- elif argument is mapping -%}\n {{- '{' -}}\n {%- set ns = namespace(found_first=false) -%}\n {%- for key, value in argument | dictsort -%}\n {%- if ns.found_first %},{% endif -%}\n {%- set ns.found_first = true -%}\n {%- if escape_keys -%}\n {{- '<|\"|>' + key + '<|\"|>' -}}\n {%- else -%}\n {{- key -}}\n {%- endif -%}\n :{{- format_argument(value, escape_keys=escape_keys) -}}\n {%- endfor -%}\n {{- '}' -}}\n {%- elif argument is sequence -%}\n {{- '[' -}}\n {%- for item in argument -%}\n {{- format_argument(item, escape_keys=escape_keys) -}}\n {%- if not loop.last %},{% endif -%}\n {%- endfor -%}\n {{- ']' -}}\n {%- else -%}\n {{- argument -}}\n {%- endif -%}\n{%- endmacro -%}\n{%- macro strip_thinking(text) -%}\n {%- set ns = namespace(result='') -%}\n {%- for part in text.split('<channel|>') -%}\n {%- if '<|channel>' in part -%}\n {%- set ns.result = ns.result + part.split('<|channel>')[0] -%}\n {%- else -%}\n {%- set ns.result = ns.result + part -%}\n {%- endif -%}\n {%- endfor -%}\n {{- ns.result | trim -}}\n{%- endmacro -%}\n\n{%- macro format_tool_response_block(tool_name, response) -%}\n {{- '<|tool_response>' -}}\n {%- if response is mapping -%}\n {{- 'response:' + tool_name + '{' -}}\n {%- for key, value in response | dictsort -%}\n {{- key -}}:{{- format_argument(value, escape_keys=False) -}}\n {%- if not loop.last %},{% endif -%}\n {%- endfor -%}\n {{- '}' -}}\n {%- else -%}\n {{- 'response:' + tool_name + '{value:' + format_argument(response, escape_keys=False) + '}' -}}\n {%- endif -%}\n {{- '<tool_response|>' -}}\n{%- endmacro -%}\n\n{#- ===== SETUP ===== -#}\n{%- set ns = namespace(prev_message_type=None, prev_non_tool_role=None) -%}\n{%- set loop_messages = messages -%}\n{%- set enable_thinking = enable_thinking | default(false) -%}\n{%- set preserve_thinking = preserve_thinking | default(false) -%}\n{{- bos_token -}}\n{#- Handle System/Tool Definitions Block -#}\n{%- if enable_thinking or tools or (messages and messages[0]['role'] in ['system', 'developer']) -%}\n {{- '<|turn>system\\n' -}}\n {#- Inject Thinking token at the very top of the FIRST system turn -#}\n {%- if enable_thinking -%}\n {{- '<|think|>\\n' -}}\n {%- set ns.prev_message_type = 'think' -%}\n {%- endif -%}\n {%- if messages and messages[0]['role'] in ['system', 'developer'] -%}\n {%- if messages[0]['content'] is string -%}\n {{- messages[0]['content'] | trim -}}\n {%- elif messages[0]['content'] is sequence -%}\n {%- for item in messages[0]['content'] -%}\n {{- item['text'] | trim + ' '-}}\n {%- endfor -%}\n {%- endif -%}\n {%- set loop_messages = messages[1:] -%}\n {%- endif -%}\n {%- if tools -%}\n {%- for tool in tools %}\n {{- '<|tool>' -}}\n {{- format_function_declaration(tool) | trim -}}\n {{- '<tool|>' -}}\n {%- endfor %}\n {%- set ns.prev_message_type = 'tool' -%}\n {%- endif -%}\n {{- '<turn|>\\n' -}}\n{%- endif %}\n\n{#- Pre-scan: find last user message index for reasoning guard -#}\n{%- set ns_turn = namespace(last_user_idx=-1) -%}\n{%- for i in range(loop_messages | length) -%}\n {%- if loop_messages[i]['role'] == 'user' -%}\n {%- set ns_turn.last_user_idx = i -%}\n {%- endif -%}\n{%- endfor -%}\n\n{#- Loop through messages -#}\n{%- for message in loop_messages -%}\n {%- if message['role'] != 'tool' -%}\n {%- set ns.prev_message_type = None -%}\n {%- set role = 'model' if message['role'] == 'assistant' else message['role'] -%}\n {#- Detect continuation using tracked state \u2014 O(1) instead of O(n) backward scan -#}\n {%- set continue_same_model_turn = (role == 'model' and ns.prev_non_tool_role == 'assistant') -%}\n {%- if not continue_same_model_turn -%}\n {{- '<|turn>' + role + '\\n' }}\n\n {%- endif -%}\n\n {#- Render reasoning/reasoning_content as thinking channel -#}\n {%- set thinking_text = message.get('reasoning') or message.get('reasoning_content') -%}\n {%- set thinking_gate = (loop.index0 > ns_turn.last_user_idx) or (preserve_thinking and message.get('tool_calls')) -%}\n {%- if thinking_text and thinking_gate -%}\n {{- '<|channel>thought\\n' + thinking_text + '\\n<channel|>' -}}\n {%- endif -%}\n\n {%- set captured_content -%}\n {%- if message.get('content') is string -%}\n {%- if role == 'model' -%}\n {{- strip_thinking(message['content']) -}}\n {%- else -%}\n {{- message['content'] | trim -}}\n {%- endif -%}\n {%- elif message.get('content') is sequence -%}\n {%- for item in message['content'] -%}\n {%- if item.get('type') == 'text' -%}\n {%- if role == 'model' -%}\n {{- strip_thinking(item['text']) -}}\n {%- else -%}\n {{- item['text'] | trim -}}\n {%- endif -%}\n {%- elif item.get('type') in ['image', 'image_url'] -%}\n {{- '<|image|>' -}}\n {%- elif item.get('type') in ['audio', 'input_audio'] -%}\n {{- '<|audio|>' -}}\n {%- elif item.get('type') == 'video' -%}\n {{- '<|video|>' -}}\n {%- endif -%}\n {%- endfor -%}\n {%- endif -%}\n {%- endset -%}\n\n {#- The sole intentional behavior change from HF main: preserve\n OpenAI assistant content before its tool calls. -#}\n {%- if role == 'model' and message.get('tool_calls') -%}\n {{- captured_content -}}\n {%- endif -%}\n\n {%- if message.get('tool_calls') -%}\n {%- for tool_call in message.get('tool_calls') -%}\n {%- set function = tool_call['function'] -%}\n {{- '<|tool_call>call:' + function['name'] + '{' -}}\n {%- if function['arguments'] is mapping -%}\n {%- set ns_args = namespace(found_first=false) -%}\n {%- for key, value in function['arguments'] | dictsort -%}\n {%- if ns_args.found_first %},{% endif -%}\n {%- set ns_args.found_first = true -%}\n {{- key -}}:{{- format_argument(value, escape_keys=False) -}}\n {%- endfor -%}\n {%- elif function['arguments'] is none -%}\n {%- else -%}\n {{- raise_exception(\n \"chat_template: tool_calls[].function.arguments must be a \"\n \"JSON object (mapping), not a string. Deserialize arguments \"\n \"before passing to the template.\"\n ) -}}\n {%- endif -%}\n {{- '}<tool_call|>' -}}\n {%- endfor -%}\n {%- set ns.prev_message_type = 'tool_call' -%}\n {%- endif -%}\n\n {%- set ns_tr_out = namespace(flag=false) -%}\n {%- if message.get('tool_responses') -%}\n {#- Legacy: tool_responses embedded on the assistant message (Google/Gemma native) -#}\n {%- for tool_response in message.get('tool_responses') -%}\n {{- format_tool_response_block(tool_response['name'] | default('unknown', true), tool_response['response']) -}}\n {%- set ns_tr_out.flag = true -%}\n {%- set ns.prev_message_type = 'tool_response' -%}\n {%- endfor -%}\n {%- elif message.get('tool_calls') -%}\n {#- OpenAI Chat Completions: forward-scan consecutive role:tool messages -#}\n {%- set ns_tool_scan = namespace(stopped=false) -%}\n {%- for k in range(loop.index0 + 1, loop_messages | length) -%}\n {%- if ns_tool_scan.stopped -%}\n {%- elif loop_messages[k]['role'] != 'tool' -%}\n {%- set ns_tool_scan.stopped = true -%}\n {%- else -%}\n {%- set follow = loop_messages[k] -%}\n {#- Resolve tool_call_id to function name -#}\n {%- set ns_tname = namespace(name=follow.get('name') or 'unknown') -%}\n {%- for tc in message.get('tool_calls') -%}\n {%- if tc.get('id') == follow.get('tool_call_id') -%}\n {%- set ns_tname.name = tc['function']['name'] -%}\n {%- endif -%}\n {%- endfor -%}\n {#- Handle content as string or content-parts array -#}\n {%- set tool_body = follow.get('content') -%}\n {%- if tool_body is string -%}\n {{- format_tool_response_block(ns_tname.name, tool_body) -}}\n {%- elif tool_body is sequence and tool_body is not string -%}\n {%- set ns_txt = namespace(s='') -%}\n {%- for part in tool_body -%}\n {%- if part.get('type') == 'text' -%}\n {%- set ns_txt.s = ns_txt.s + (part.get('text') | default('')) -%}\n {%- endif -%}\n {%- endfor -%}\n {{- format_tool_response_block(ns_tname.name, ns_txt.s) -}}\n {%- for part in tool_body -%}\n {%- if part.get('type') in ['image', 'image_url'] -%}\n {{- '<|image|>' -}}\n {%- elif part.get('type') in ['audio', 'input_audio'] -%}\n {{- '<|audio|>' -}}\n {%- elif part.get('type') == 'video' -%}\n {{- '<|video|>' -}}\n {%- endif -%}\n {%- endfor -%}\n {%- else -%}\n {{- format_tool_response_block(ns_tname.name, tool_body) -}}\n {%- endif -%}\n {%- set ns_tr_out.flag = true -%}\n {%- set ns.prev_message_type = 'tool_response' -%}\n {%- endif -%}\n {%- endfor -%}\n {%- endif -%}\n\n {%- if not (role == 'model' and message.get('tool_calls')) -%}\n {{- captured_content -}}\n {%- endif -%}\n {%- set has_content = captured_content | trim | length > 0 -%}\n\n {#- Forward-scan: find next non-tool message role for continuation detection -#}\n {%- set next_nt = namespace(role=None, found=false) -%}\n {%- for j in range(loop.index0 + 1, loop_messages | length) -%}\n {%- if not next_nt.found -%}\n {%- if loop_messages[j]['role'] != 'tool' -%}\n {%- set next_nt.role = loop_messages[j]['role'] -%}\n {%- set next_nt.found = true -%}\n {%- endif -%}\n {%- endif -%}\n {%- endfor -%}\n\n {%- set continues_into_next = (\n role == 'model'\n and next_nt.role == 'assistant'\n and (not message.get('tool_calls') or ns_tr_out.flag)\n ) -%}\n\n {%- if ns.prev_message_type == 'tool_call' and not ns_tr_out.flag -%}\n {{- '<|tool_response>' -}}\n {%- elif continues_into_next -%}\n {%- elif not (ns_tr_out.flag and not has_content and not next_nt.found) -%}\n {{- '<turn|>\\n' -}}\n {%- endif -%}\n\n {#- Track previous non-tool role for next iteration (avoids O(n) backward scan) -#}\n {%- set ns.prev_non_tool_role = message['role'] -%}\n {%- endif -%}\n{%- endfor -%}\n\n{%- if add_generation_prompt -%}\n {%- if ns.prev_message_type != 'tool_response' and ns.prev_message_type != 'tool_call' -%}\n {{- '<|turn>model\\n' -}}\n {%- elif ns.prev_message_type == 'tool_response' and enable_thinking -%}\n {{- '<|channel>thought\\n' -}}\n {%- endif -%}\n{%- endif -%}\n"
121
+ }