← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
loss_kernels.c
Go to the documentation of this file.
1/**
2 * @file loss_kernels.c
3 * @brief Loss function kernels (cross-entropy, etc.)
4 *
5 * CK-ENGINE KERNEL RULES:
6 * =======================
7 * 1. NO malloc/free - memory via bump allocator, pointers passed in
8 * 2. NO OpenMP - parallelization at orchestrator/codegen layer
9 * 3. API must define: inputs, outputs, workspace, and memory layouts
10 * 4. Pure computation - deterministic, no side effects
11 *
12 * After changes: make test && make llamacpp-parity-full
13 *
14 * Cross-entropy: L = -log(softmax(logits)[target])
15 */
16
17#include "ckernel_engine.h"
18
19#include <math.h>
20#include <stdlib.h>
21
22static void zero_row_f32(float *row, int cols)
23{
24 for (int i = 0; i < cols; ++i) {
25 row[i] = 0.0f;
26 }
27}
28
29static int parse_env_bool_on(const char *name)
30{
31 const char *v = getenv(name);
32 if (!v || !v[0]) {
33 return 0;
34 }
35 if (v[0] == '1' || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T') {
36 return 1;
37 }
38 return 0;
39}
40
41/*
42 * Diagnostics escape hatch:
43 * CK_CE_LEGACY_MODE=1 restores historical all-valid CE code path.
44 * Production defaults to PyTorch-aligned index-target reduction semantics.
45 */
46static int ce_legacy_mode_enabled(void)
47{
48 static int cached = -1;
49 if (cached < 0) {
50 cached = parse_env_bool_on("CK_CE_LEGACY_MODE") ? 1 : 0;
51 }
52 return cached;
53}
54
55/*
56 * Index-target cross entropy (mean reduction) with PyTorch-aligned semantics:
57 * - ignore_index fixed to -100,
58 * - denominator = number of valid (non-ignored) targets,
59 * - all-ignored rows => NaN loss for mean reduction.
60 *
61 * Invalid targets (outside [0, vocab_size)) are treated as hard failures for
62 * loss reporting (loss_out=NaN) while keeping gradients deterministic.
63 */
64static void softmax_cross_entropy_loss_index_mean_impl(const float *logits,
65 const int32_t *targets,
66 int tokens,
67 int vocab_size,
68 float *d_logits,
69 float *loss_out,
70 int force_strict_math)
71{
72 if (!logits || !targets || !d_logits || tokens <= 0 || vocab_size <= 0) {
73 if (loss_out) {
74 *loss_out = 0.0f;
75 }
76 return;
77 }
78
79 const int ignore_index = -100;
80 const int strict = (force_strict_math >= 0) ? (force_strict_math ? 1 : 0)
82 double total_loss = 0.0;
83 int valid_tokens = 0;
84 int invalid_target_seen = 0;
85
86 for (int t = 0; t < tokens; ++t) {
87 const float *row = logits + (size_t)t * (size_t)vocab_size;
88 float *drow = d_logits + (size_t)t * (size_t)vocab_size;
89 const int target = targets[t];
90
91 if (target == ignore_index) {
93 continue;
94 }
95 if (target < 0 || target >= vocab_size) {
97 invalid_target_seen = 1;
98 continue;
99 }
100
101 if (strict) {
102 double max_logit = (double)row[0];
103 for (int v = 1; v < vocab_size; ++v) {
104 const double rv = (double)row[v];
105 if (rv > max_logit) {
106 max_logit = rv;
107 }
108 }
109
110 double sum_exp = 0.0;
111 for (int v = 0; v < vocab_size; ++v) {
112 sum_exp += exp((double)row[v] - max_logit);
113 }
114 const double inv_sum = 1.0 / sum_exp;
115 for (int v = 0; v < vocab_size; ++v) {
116 drow[v] = (float)(exp((double)row[v] - max_logit) * inv_sum);
117 }
118
119 total_loss += -(double)row[target] + max_logit + log(sum_exp);
120 } else {
121 float max_logit = row[0];
122 for (int v = 1; v < vocab_size; ++v) {
123 if (row[v] > max_logit) {
124 max_logit = row[v];
125 }
126 }
127
128 double sum_exp = 0.0;
129 for (int v = 0; v < vocab_size; ++v) {
130 const float e = expf(row[v] - max_logit);
131 drow[v] = e;
132 sum_exp += (double)e;
133 }
134 const float inv_sum = 1.0f / (float)sum_exp;
135 for (int v = 0; v < vocab_size; ++v) {
136 drow[v] *= inv_sum;
137 }
138
139 total_loss += -(double)row[target] + (double)max_logit + log(sum_exp);
140 }
141
142 drow[target] -= 1.0f;
143 ++valid_tokens;
144 }
145
146 if (valid_tokens > 0) {
147 const float scale = 1.0f / (float)valid_tokens;
148 for (int t = 0; t < tokens; ++t) {
149 float *drow = d_logits + (size_t)t * (size_t)vocab_size;
150 for (int v = 0; v < vocab_size; ++v) {
151 drow[v] *= scale;
152 }
153 }
154 }
155
156 if (loss_out) {
157 if (invalid_target_seen || valid_tokens == 0) {
158 *loss_out = NAN;
159 } else {
160 *loss_out = (float)(total_loss / (double)valid_tokens);
161 }
162 }
163}
164
165/*
166 * Legacy CE numerics preserved for all-valid index targets.
167 * This is the historical v7 behavior used by long-horizon drift baselines.
168 */
169static void softmax_cross_entropy_loss_legacy_mean_impl(const float *logits,
170 const int32_t *targets,
171 int tokens,
172 int vocab_size,
173 float *d_logits,
174 float *loss_out)
175{
176 const int strict = ck_strict_parity_enabled();
177 const double scale = 1.0 / (double)tokens;
178 double total_loss = 0.0;
179
180 for (int t = 0; t < tokens; ++t) {
181 const float *row = logits + (size_t)t * (size_t)vocab_size;
182 float *drow = d_logits + (size_t)t * (size_t)vocab_size;
183 const int target = targets[t];
184
185 if (strict) {
186 double max_logit = (double)row[0];
187 for (int v = 1; v < vocab_size; ++v) {
188 const double rv = (double)row[v];
189 if (rv > max_logit) {
190 max_logit = rv;
191 }
192 }
193
194 double sum_exp = 0.0;
195 for (int v = 0; v < vocab_size; ++v) {
196 sum_exp += exp((double)row[v] - max_logit);
197 }
198 const double inv_sum = 1.0 / sum_exp;
199 for (int v = 0; v < vocab_size; ++v) {
200 const double p = exp((double)row[v] - max_logit) * inv_sum;
201 drow[v] = (float)(p * scale);
202 }
203
204 if (target >= 0 && target < vocab_size) {
205 const double log_sum_exp = log(sum_exp);
206 const double target_logit = (double)row[target];
207 total_loss += -(target_logit - max_logit - log_sum_exp);
208 drow[target] -= (float)scale;
209 }
210 } else {
211 float max_logit = row[0];
212 for (int v = 1; v < vocab_size; ++v) {
213 if (row[v] > max_logit) {
214 max_logit = row[v];
215 }
216 }
217
218 double sum_exp = 0.0;
219 for (int v = 0; v < vocab_size; ++v) {
220 const float e = expf(row[v] - max_logit);
221 drow[v] = e;
222 sum_exp += (double)e;
223 }
224
225 const float inv_sum = 1.0f / (float)sum_exp;
226 for (int v = 0; v < vocab_size; ++v) {
227 drow[v] *= inv_sum;
228 }
229
230 if (target >= 0 && target < vocab_size) {
231 const double log_sum_exp = log(sum_exp);
232 const double target_logit = (double)row[target];
233 total_loss += -(target_logit - (double)max_logit - log_sum_exp);
234 drow[target] -= 1.0f;
235 }
236
237 for (int v = 0; v < vocab_size; ++v) {
238 drow[v] *= (float)scale;
239 }
240 }
241 }
242
243 if (loss_out) {
244 *loss_out = (float)(total_loss / (double)tokens);
245 }
246}
247
248static int ce_targets_all_valid_no_ignore(const int32_t *targets, int tokens, int vocab_size)
249{
250 for (int t = 0; t < tokens; ++t) {
251 const int target = targets[t];
252 if (target < 0 || target >= vocab_size) {
253 return 0;
254 }
255 }
256 return 1;
257}
258
259void softmax_cross_entropy_loss(const float *logits,
260 const int32_t *targets,
261 int tokens,
262 int vocab_size,
263 float *d_logits,
264 float *loss_out)
265{
266 if (!logits || !targets || !d_logits || tokens <= 0 || vocab_size <= 0) {
267 if (loss_out) {
268 *loss_out = 0.0f;
269 }
270 return;
271 }
272
275 logits, targets, tokens, vocab_size, d_logits, loss_out);
276 return;
277 }
278
280 logits, targets, tokens, vocab_size, d_logits, loss_out, -1);
281}
282
283/*
284 * PyTorch-index-target aligned CE variant:
285 * - stable log-sum-exp per row,
286 * - mean reduction over valid (non-ignore) targets,
287 * - ignore_index fixed to -100 (PyTorch default).
288 *
289 * This keeps the same ABI as softmax_cross_entropy_loss so it can be selected
290 * by name from parity harnesses without graph/codegen changes.
291 */
292void softmax_cross_entropy_loss_ptref(const float *logits,
293 const int32_t *targets,
294 int tokens,
295 int vocab_size,
296 float *d_logits,
297 float *loss_out)
298{
299 /*
300 * Keep a strict reference variant for parity experiments:
301 * - always uses strict math path,
302 * - same reduction / ignore semantics as default kernel.
303 */
305 logits, targets, tokens, vocab_size, d_logits, loss_out, 1);
306}
int ck_strict_parity_enabled(void)
void softmax_cross_entropy_loss_ptref(const float *logits, const int32_t *targets, int tokens, int vocab_size, float *d_logits, float *loss_out)
static int ce_targets_all_valid_no_ignore(const int32_t *targets, int tokens, int vocab_size)
static int ce_legacy_mode_enabled(void)
static void zero_row_f32(float *row, int cols)
static void softmax_cross_entropy_loss_index_mean_impl(const float *logits, const int32_t *targets, int tokens, int vocab_size, float *d_logits, float *loss_out, int force_strict_math)
static void softmax_cross_entropy_loss_legacy_mean_impl(const float *logits, const int32_t *targets, int tokens, int vocab_size, float *d_logits, float *loss_out)
static int parse_env_bool_on(const char *name)
void softmax_cross_entropy_loss(const float *logits, const int32_t *targets, int tokens, int vocab_size, float *d_logits, float *loss_out)
int vocab_size
Definition true_bpe.h:193