Mike0021's picture
Enable browser HTTP commands with CORS-aware errors and bounded requests
39371ea verified
Raw
History Blame Contribute Delete
2.16 kB
// Transformers.js 4.2.0 exposes top_p but does not apply it in generate().
// Apply nucleus filtering after its temperature/repetition processors and
// before its normal multinomial sampler. No min-p or top-k filter is added.
export function nucleusProcessor(topP = 0.95) {
if (!(topP > 0 && topP <= 1)) throw Error('topP must be in (0, 1].');
let sorted;
return (_inputIds, logits) => {
if (topP === 1) return logits;
const size = logits.dims.at(-1);
sorted ??= new Float32Array(size);
if (sorted.length !== size) sorted = new Float32Array(size);
for (let offset = 0; offset < logits.data.length; offset += size) {
const scores = logits.data.subarray(offset, offset + size);
let max = -Infinity;
for (let i = 0; i < size; i++) max = Math.max(max, scores[i]);
if (!Number.isFinite(max)) throw Error('The model produced invalid sampling scores.');
let total = 0;
for (let i = 0; i < size; i++) total += Math.exp(scores[i] - max);
const tailMass = (1 - topP) * total;
// Most vocabulary entries have negligible mass. Exclude a tail only
// after measuring its entire mass; this is exact, not a top-k shortcut.
let floor = max - 8, count, mass;
do {
count = 0; mass = 0;
for (let i = 0; i < size; i++) {
if (scores[i] >= floor) sorted[count++] = scores[i];
else mass += Math.exp(scores[i] - max);
}
if (mass <= tailMass) break;
floor -= 8;
} while (true);
sorted.subarray(0, count).sort();
let removed = 0;
// Always retain at least one token, including when logits have ties.
while (removed < count - 1) {
const next = mass + Math.exp(sorted[removed] - max);
if (next > tailMass) break;
mass = next; removed++;
}
const cutoff = removed ? sorted[removed - 1] : floor;
let tied = 0;
for (let i = removed - 1; i >= 0 && sorted[i] === cutoff; i--) tied++;
for (let i = 0; i < size; i++) {
if (scores[i] < cutoff || (scores[i] === cutoff && tied-- > 0)) scores[i] = -Infinity;
}
}
return logits;
};
}