@euconform/core 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +34 -0
- package/dist/datasets/crows-pairs.d.ts +18 -0
- package/dist/datasets/crows-pairs.d.ts.map +1 -1
- package/dist/datasets/crows-pairs.js +58 -0
- package/dist/evidence/bias-comparison-markdown.d.ts +11 -0
- package/dist/evidence/bias-comparison-markdown.d.ts.map +1 -0
- package/dist/evidence/bias-comparison-markdown.js +120 -0
- package/dist/evidence/bias-comparison.d.ts +133 -0
- package/dist/evidence/bias-comparison.d.ts.map +1 -0
- package/dist/evidence/bias-comparison.js +274 -0
- package/dist/evidence/bias-statistics.d.ts +72 -0
- package/dist/evidence/bias-statistics.d.ts.map +1 -0
- package/dist/evidence/bias-statistics.js +122 -0
- package/dist/evidence/bias-v2-semantics.d.ts +19 -0
- package/dist/evidence/bias-v2-semantics.d.ts.map +1 -0
- package/dist/evidence/bias-v2-semantics.js +133 -0
- package/dist/evidence/browser.d.ts +18 -0
- package/dist/evidence/browser.d.ts.map +1 -0
- package/dist/evidence/browser.js +14 -0
- package/dist/evidence/bundle.d.ts +1 -0
- package/dist/evidence/bundle.d.ts.map +1 -1
- package/dist/evidence/bundle.js +2 -0
- package/dist/evidence/index.d.ts +9 -2
- package/dist/evidence/index.d.ts.map +1 -1
- package/dist/evidence/index.js +5 -1
- package/dist/evidence/types.d.ts +175 -1
- package/dist/evidence/types.d.ts.map +1 -1
- package/dist/evidence/validate-bias-v2.d.ts +31 -0
- package/dist/evidence/validate-bias-v2.d.ts.map +1 -0
- package/dist/evidence/validate-bias-v2.js +40 -0
- package/dist/evidence/validate.d.ts +29 -2
- package/dist/evidence/validate.d.ts.map +1 -1
- package/dist/evidence/validate.js +90 -8
- package/dist/fairness-metrics.d.ts +15 -11
- package/dist/fairness-metrics.d.ts.map +1 -1
- package/dist/fairness-metrics.js +59 -183
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/inference/browser-inference.d.ts +6 -57
- package/dist/inference/browser-inference.d.ts.map +1 -1
- package/dist/inference/browser-inference.js +6 -219
- package/dist/inference/capability-detection.d.ts +9 -2
- package/dist/inference/capability-detection.d.ts.map +1 -1
- package/dist/inference/capability-detection.js +18 -16
- package/dist/inference/index.d.ts +15 -2
- package/dist/inference/index.d.ts.map +1 -1
- package/dist/inference/index.js +12 -3
- package/dist/inference/inference-factory.d.ts +6 -3
- package/dist/inference/inference-factory.d.ts.map +1 -1
- package/dist/inference/inference-factory.js +7 -26
- package/dist/inference/ollama-client.d.ts +29 -0
- package/dist/inference/ollama-client.d.ts.map +1 -1
- package/dist/inference/ollama-client.js +29 -0
- package/dist/inference/scored-log-prob-types.d.ts +64 -0
- package/dist/inference/scored-log-prob-types.d.ts.map +1 -0
- package/dist/inference/scored-log-prob-types.js +12 -0
- package/dist/inference/sequence-logprob.d.ts +34 -0
- package/dist/inference/sequence-logprob.d.ts.map +1 -0
- package/dist/inference/sequence-logprob.js +87 -0
- package/dist/inference/template-scoring.d.ts +124 -0
- package/dist/inference/template-scoring.d.ts.map +1 -0
- package/dist/inference/template-scoring.js +164 -0
- package/dist/inference/transformers-inference.d.ts +124 -0
- package/dist/inference/transformers-inference.d.ts.map +1 -0
- package/dist/inference/transformers-inference.js +392 -0
- package/dist/legal-checks/bias-metrics.d.ts +5 -7
- package/dist/legal-checks/bias-metrics.d.ts.map +1 -1
- package/dist/legal-checks/bias-metrics.js +8 -101
- package/dist/metrics/crows-pairs-bias.d.ts +17 -9
- package/dist/metrics/crows-pairs-bias.d.ts.map +1 -1
- package/dist/metrics/crows-pairs-bias.js +147 -59
- package/dist/metrics/stereotype-bias.d.ts +13 -5
- package/dist/metrics/stereotype-bias.d.ts.map +1 -1
- package/dist/metrics/stereotype-bias.js +16 -99
- package/dist/scanner/detectors/ai-framework-detector.js +1 -1
- package/dist/scanner/detectors/local-inference-detector.d.ts.map +1 -1
- package/dist/scanner/detectors/local-inference-detector.js +1 -1
- package/dist/scanner/detectors/package-json-detector.d.ts.map +1 -1
- package/dist/scanner/detectors/package-json-detector.js +1 -0
- package/dist/types.d.ts +103 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/validation/schema-validator.d.ts +1 -1
- package/dist/validation/schema-validator.d.ts.map +1 -1
- package/dist/validation/schema-validator.js +6 -0
- package/package.json +4 -2
- package/scripts/seed-logprob-fixture.mjs +179 -0
- package/scripts/ts-extensionless-resolve-hook.mjs +26 -0
- package/src/datasets/crows-pairs.ts +74 -0
- package/src/evidence/bias-comparison-markdown.ts +162 -0
- package/src/evidence/bias-comparison.ts +432 -0
- package/src/evidence/bias-statistics.ts +180 -0
- package/src/evidence/bias-v2-semantics.ts +214 -0
- package/src/evidence/browser.ts +35 -0
- package/src/evidence/bundle.ts +2 -0
- package/src/evidence/index.ts +31 -0
- package/src/evidence/types.ts +204 -1
- package/src/evidence/validate-bias-v2.ts +47 -0
- package/src/evidence/validate.ts +140 -13
- package/src/fairness-metrics.ts +79 -222
- package/src/index.ts +7 -0
- package/src/inference/browser-inference.ts +14 -256
- package/src/inference/capability-detection.ts +32 -18
- package/src/inference/index.ts +33 -5
- package/src/inference/inference-factory.ts +9 -30
- package/src/inference/ollama-client.ts +29 -0
- package/src/inference/scored-log-prob-types.ts +66 -0
- package/src/inference/sequence-logprob.ts +136 -0
- package/src/inference/template-scoring.ts +248 -0
- package/src/inference/transformers-inference.ts +494 -0
- package/src/legal-checks/bias-metrics.ts +20 -118
- package/src/metrics/crows-pairs-bias.ts +180 -83
- package/src/metrics/stereotype-bias.ts +27 -119
- package/src/scanner/detectors/ai-framework-detector.ts +1 -1
- package/src/scanner/detectors/local-inference-detector.ts +2 -1
- package/src/scanner/detectors/package-json-detector.ts +1 -0
- package/src/types.ts +108 -2
- package/src/validation/schema-validator.ts +15 -1
- package/tests/annex-iv-report.test.ts +1 -1
- package/tests/capability-detection.test.ts +57 -0
- package/tests/crows-pairs-bias.test.ts +286 -186
- package/tests/custom-dataset.test.ts +80 -0
- package/tests/evidence/bias-artifact-v2.test.ts +135 -0
- package/tests/evidence/bias-artifact.test.ts +78 -0
- package/tests/evidence/bias-comparison.test.ts +397 -0
- package/tests/evidence/bias-statistics.test.ts +191 -0
- package/tests/evidence/external-observation.test.ts +167 -0
- package/tests/evidence/spec-schemas.test.ts +35 -2
- package/tests/evidence/validate-bias-v2.test.ts +180 -0
- package/tests/fairness-metrics.test.ts +77 -1
- package/tests/inference/browser-inference-scoring.test.ts +159 -0
- package/tests/inference/fixtures/logprob-golden.json +69 -0
- package/tests/inference/gemma-template.integration.test.ts +81 -0
- package/tests/inference/logprob-golden.integration.test.ts +128 -0
- package/tests/inference/synthetic-logits.test.ts +188 -0
- package/tests/inference/template-scoring.test.ts +249 -0
- package/tests/inference/transformers-inference-template-mode.test.ts +214 -0
- package/tests/integration/bias-calculation.integration.test.ts +119 -599
- package/tests/stilled-ollama-paths.test.ts +76 -0
- package/src/inference/transformers.d.ts +0 -62
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemma-4 template-mode scoring: `conditional_sequence_logprob_sum`.
|
|
3
|
+
*
|
|
4
|
+
* Gemma-4 (and any other instruction-tuned "ForConditionalGeneration" architecture) is
|
|
5
|
+
* trained on chat-templated input. Feeding it a bare sentence — the base-mode approach
|
|
6
|
+
* from Task 1 — measures outside its intended input format. Instead, the sentence is
|
|
7
|
+
* scored as an ASSISTANT CONTINUATION after a fixed, WOERTLICH user turn (Messmotor-
|
|
8
|
+
* Sanierung Task 5 / Plan v5), and only the sentence's own tokens are scored — never the
|
|
9
|
+
* template's.
|
|
10
|
+
*
|
|
11
|
+
* Split out of `transformers-inference.ts` (per Task 5 brief item 3) to keep that file
|
|
12
|
+
* from growing unwieldy; this module owns everything specific to the template-mode span
|
|
13
|
+
* computation and is exercised entirely offline via injected fakes (`TemplateTokenizer`,
|
|
14
|
+
* `TemplateModel`) — no `@huggingface/transformers` mocking needed except for `Tensor`,
|
|
15
|
+
* which is dynamically imported the same way `transformers-inference.ts` does it.
|
|
16
|
+
*
|
|
17
|
+
* FALLE aus dem Spike (Task 0): `tokenizer.apply_chat_template(..., { tokenize: true })`
|
|
18
|
+
* returns `{ input_ids: Tensor, attention_mask: Tensor }`, NOT a plain array. Naively
|
|
19
|
+
* calling `Array.isArray()`/`Array.from()` on that object yields `context_len = 0`, which
|
|
20
|
+
* makes the prefix-identity check vacuously true. This module never calls
|
|
21
|
+
* `apply_chat_template` with `tokenize: true` at all — it uses `tokenize: false` to get
|
|
22
|
+
* the literal context STRING, then tokenizes that string (and the context+sentence
|
|
23
|
+
* string) via `tokenizer.encode()`, which returns a plain `number[]` by contract. The
|
|
24
|
+
* `context_len > 0` guard below exists anyway, as a structural regression test against
|
|
25
|
+
* this exact trap resurfacing under a future refactor.
|
|
26
|
+
*/
|
|
27
|
+
import type { ScoredLogProb } from "./scored-log-prob-types";
|
|
28
|
+
import type { LogitsTensorLike } from "./sequence-logprob";
|
|
29
|
+
/**
|
|
30
|
+
* WOERTLICH fixed context spec (Messmotor-Sanierung Task 5 / Plan v5), verified against
|
|
31
|
+
* the real gemma-4-E2B-it tokenizer by the Task 0 spike (18 tokens,
|
|
32
|
+
* [2,105,2364,107,13790,500,18252,10175,171313,118778,4238,29078,236761,106,107,105,4368,107]).
|
|
33
|
+
* NEVER change this text without re-running the gated integration test — every bias.v2
|
|
34
|
+
* artifact produced in template mode recomputes against exactly this prompt.
|
|
35
|
+
*/
|
|
36
|
+
export declare const FIXED_TEMPLATE_USER_MESSAGE = "Schreibe einen kurzen Satz auf Deutsch.";
|
|
37
|
+
/** Minimal message shape this module needs — deliberately not importing transformers.js's
|
|
38
|
+
* own `Message` type to keep this module's dependency surface (and test doubles) small. */
|
|
39
|
+
export interface TemplateChatMessage {
|
|
40
|
+
role: string;
|
|
41
|
+
content: string;
|
|
42
|
+
}
|
|
43
|
+
/** Minimal shape this module needs from a transformers.js tokenizer for template-mode
|
|
44
|
+
* scoring. `apply_chat_template` is only ever called here with `tokenize: false`. */
|
|
45
|
+
export interface TemplateTokenizer {
|
|
46
|
+
apply_chat_template(conversation: TemplateChatMessage[], options: {
|
|
47
|
+
add_generation_prompt: boolean;
|
|
48
|
+
tokenize: false;
|
|
49
|
+
}): string;
|
|
50
|
+
encode(text: string): number[];
|
|
51
|
+
}
|
|
52
|
+
/** Minimal shape this module needs from a transformers.js model: callable, returns logits. */
|
|
53
|
+
export type TemplateModel = (inputs: {
|
|
54
|
+
input_ids: unknown;
|
|
55
|
+
attention_mask: unknown;
|
|
56
|
+
}) => Promise<{
|
|
57
|
+
logits: LogitsTensorLike;
|
|
58
|
+
}>;
|
|
59
|
+
export interface TemplateScoringDeps {
|
|
60
|
+
tokenizer: TemplateTokenizer;
|
|
61
|
+
model: TemplateModel;
|
|
62
|
+
modelId: string;
|
|
63
|
+
libraryVersion: string;
|
|
64
|
+
/** The CONFIGURED weight precision (e.g. "q4", "fp32") passed to `from_pretrained` —
|
|
65
|
+
* threaded through explicitly because the output logits tensor's own `.type` is always
|
|
66
|
+
* "float32" regardless of weight quantization (Task 0 spike) and so cannot be used to
|
|
67
|
+
* recover it; see `ScoredLogProbProvenance.dtype` vs `.logitsDtype`. */
|
|
68
|
+
dtype: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Score `text` as a Gemma-4-style assistant continuation of the fixed template context.
|
|
72
|
+
*
|
|
73
|
+
* Builds the context via `apply_chat_template(..., { tokenize: false })`, tokenizes the
|
|
74
|
+
* context alone and the context+sentence together via `tokenizer.encode()`, verifies
|
|
75
|
+
* prefix-token identity, determines the sentence-only span, and delegates the actual
|
|
76
|
+
* log-probability accumulation to the pure `sumSequenceLogProb` scorer from Task 1
|
|
77
|
+
* (reused, not reimplemented).
|
|
78
|
+
*/
|
|
79
|
+
export declare function scoreTemplateContinuation(deps: TemplateScoringDeps, text: string): Promise<ScoredLogProb>;
|
|
80
|
+
/** Minimal shape this module needs from a loaded model config to detect architecture. */
|
|
81
|
+
export interface MinimalModelConfig {
|
|
82
|
+
model_type?: string | null;
|
|
83
|
+
architectures?: readonly string[] | null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Detect whether a loaded model config indicates a conditional-generation / multimodal
|
|
87
|
+
* architecture (e.g. Gemma-4's `Gemma4ForConditionalGeneration`), which must be scored via
|
|
88
|
+
* the template mode rather than fed bare sentences as a classic causal LM.
|
|
89
|
+
*
|
|
90
|
+
* Detection is from the CONFIG (`architectures`), never the model id string, per spec —
|
|
91
|
+
* so a differently-named repo hosting the same architecture is still detected correctly,
|
|
92
|
+
* and a model whose name happens to contain "gemma" but isn't conditional-generation is not
|
|
93
|
+
* misdetected.
|
|
94
|
+
*/
|
|
95
|
+
export declare function isConditionalGenerationConfig(config: MinimalModelConfig): boolean;
|
|
96
|
+
/** Minimal shape this module needs from a loaded tokenizer to detect a chat template. */
|
|
97
|
+
export interface MinimalChatTemplateTokenizer {
|
|
98
|
+
chat_template?: string | null;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Detect whether a loaded TOKENIZER carries a chat template, which decides the SCORING MODE
|
|
102
|
+
* (template vs. base) — Messmotor-Sanierung Task 6c.
|
|
103
|
+
*
|
|
104
|
+
* This replaces architecture-name sniffing (`isConditionalGenerationConfig`) as the mode
|
|
105
|
+
* signal: Task 6b's panel run showed that keying mode on architecture misses instruction-tuned
|
|
106
|
+
* models whose architecture is plain `*ForCausalLM` (e.g. `LlamaForCausalLM`,
|
|
107
|
+
* `Gemma3ForCausalLM`) but which still expect chat-templated input — they were scored bare,
|
|
108
|
+
* the exact error template mode exists to prevent.
|
|
109
|
+
*
|
|
110
|
+
* Verified empirically (transformers.js@4.2.0, against the four panel models + gpt2) via
|
|
111
|
+
* `AutoTokenizer.from_pretrained(modelId)`: the loaded tokenizer instance exposes the chat
|
|
112
|
+
* template — sourced from `tokenizer_config.json`'s `chat_template` field — directly as
|
|
113
|
+
* `tokenizer.chat_template`. It is `null` for models with no chat template
|
|
114
|
+
* (`Xenova/gpt2`, `Xenova/llama2.c-stories15M`) and a non-empty Jinja template string for
|
|
115
|
+
* instruction-tuned models (`onnx-community/Llama-3.2-1B-Instruct-ONNX`,
|
|
116
|
+
* `onnx-community/gemma-3-1b-it-ONNX`, `onnx-community/gemma-4-E2B-it-ONNX`).
|
|
117
|
+
*
|
|
118
|
+
* Mode selection (this function) and LOADER selection (`isConditionalGenerationConfig`,
|
|
119
|
+
* still architecture-keyed) are deliberately independent: a CausalLM-architecture model with
|
|
120
|
+
* a chat template loads via the base-mode loader (`AutoTokenizer` + `AutoModelForCausalLM`)
|
|
121
|
+
* but scores via the template-mode scorer — e.g. Llama-3.2-1B-Instruct.
|
|
122
|
+
*/
|
|
123
|
+
export declare function hasChatTemplate(tokenizer: MinimalChatTemplateTokenizer): boolean;
|
|
124
|
+
//# sourceMappingURL=template-scoring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-scoring.d.ts","sourceRoot":"","sources":["../../src/inference/template-scoring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAA2B,MAAM,yBAAyB,CAAC;AACtF,OAAO,KAAK,EAAE,gBAAgB,EAAQ,MAAM,oBAAoB,CAAC;AAGjE;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,4CAA4C,CAAC;AAErF;2FAC2F;AAC3F,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD;qFACqF;AACrF,MAAM,WAAW,iBAAiB;IAChC,mBAAmB,CACjB,YAAY,EAAE,mBAAmB,EAAE,EACnC,OAAO,EAAE;QAAE,qBAAqB,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,KAAK,CAAA;KAAE,GAC3D,MAAM,CAAC;IACV,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAChC;AAED,8FAA8F;AAC9F,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;CACzB,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAAC;AAE5C,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,iBAAiB,CAAC;IAC7B,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB;;;4EAGwE;IACxE,KAAK,EAAE,MAAM,CAAC;CACf;AAqCD;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,aAAa,CAAC,CAkExB;AAED,yFAAyF;AACzF,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;CAC1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAKjF;AAED,yFAAyF;AACzF,MAAM,WAAW,4BAA4B;IAC3C,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,4BAA4B,GAAG,OAAO,CAEhF"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemma-4 template-mode scoring: `conditional_sequence_logprob_sum`.
|
|
3
|
+
*
|
|
4
|
+
* Gemma-4 (and any other instruction-tuned "ForConditionalGeneration" architecture) is
|
|
5
|
+
* trained on chat-templated input. Feeding it a bare sentence — the base-mode approach
|
|
6
|
+
* from Task 1 — measures outside its intended input format. Instead, the sentence is
|
|
7
|
+
* scored as an ASSISTANT CONTINUATION after a fixed, WOERTLICH user turn (Messmotor-
|
|
8
|
+
* Sanierung Task 5 / Plan v5), and only the sentence's own tokens are scored — never the
|
|
9
|
+
* template's.
|
|
10
|
+
*
|
|
11
|
+
* Split out of `transformers-inference.ts` (per Task 5 brief item 3) to keep that file
|
|
12
|
+
* from growing unwieldy; this module owns everything specific to the template-mode span
|
|
13
|
+
* computation and is exercised entirely offline via injected fakes (`TemplateTokenizer`,
|
|
14
|
+
* `TemplateModel`) — no `@huggingface/transformers` mocking needed except for `Tensor`,
|
|
15
|
+
* which is dynamically imported the same way `transformers-inference.ts` does it.
|
|
16
|
+
*
|
|
17
|
+
* FALLE aus dem Spike (Task 0): `tokenizer.apply_chat_template(..., { tokenize: true })`
|
|
18
|
+
* returns `{ input_ids: Tensor, attention_mask: Tensor }`, NOT a plain array. Naively
|
|
19
|
+
* calling `Array.isArray()`/`Array.from()` on that object yields `context_len = 0`, which
|
|
20
|
+
* makes the prefix-identity check vacuously true. This module never calls
|
|
21
|
+
* `apply_chat_template` with `tokenize: true` at all — it uses `tokenize: false` to get
|
|
22
|
+
* the literal context STRING, then tokenizes that string (and the context+sentence
|
|
23
|
+
* string) via `tokenizer.encode()`, which returns a plain `number[]` by contract. The
|
|
24
|
+
* `context_len > 0` guard below exists anyway, as a structural regression test against
|
|
25
|
+
* this exact trap resurfacing under a future refactor.
|
|
26
|
+
*/
|
|
27
|
+
import { sumSequenceLogProb } from "./sequence-logprob";
|
|
28
|
+
/**
|
|
29
|
+
* WOERTLICH fixed context spec (Messmotor-Sanierung Task 5 / Plan v5), verified against
|
|
30
|
+
* the real gemma-4-E2B-it tokenizer by the Task 0 spike (18 tokens,
|
|
31
|
+
* [2,105,2364,107,13790,500,18252,10175,171313,118778,4238,29078,236761,106,107,105,4368,107]).
|
|
32
|
+
* NEVER change this text without re-running the gated integration test — every bias.v2
|
|
33
|
+
* artifact produced in template mode recomputes against exactly this prompt.
|
|
34
|
+
*/
|
|
35
|
+
export const FIXED_TEMPLATE_USER_MESSAGE = "Schreibe einen kurzen Satz auf Deutsch.";
|
|
36
|
+
function buildTemplateMessages() {
|
|
37
|
+
return [{ role: "user", content: FIXED_TEMPLATE_USER_MESSAGE }];
|
|
38
|
+
}
|
|
39
|
+
async function sha256Hex(input) {
|
|
40
|
+
const bytes = new TextEncoder().encode(input);
|
|
41
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
42
|
+
return Array.from(new Uint8Array(digest))
|
|
43
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
44
|
+
.join("");
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Assert that `contextTokenIds` is an exact prefix of `combinedTokenIds`. Per spec: on
|
|
48
|
+
* violation (a token merge at the context/sentence boundary), ABORT with a speaking error.
|
|
49
|
+
* Never try a separator character, never blindly assume the prefix length as the span start.
|
|
50
|
+
*/
|
|
51
|
+
function assertPrefixIdentity(contextTokenIds, combinedTokenIds, modelId, text) {
|
|
52
|
+
if (combinedTokenIds.length < contextTokenIds.length) {
|
|
53
|
+
throw new Error(`scoreTemplateContinuation: combined tokenization (${combinedTokenIds.length} tokens) is shorter ` +
|
|
54
|
+
`than the fixed context (${contextTokenIds.length} tokens) for sentence "${text}" on model ` +
|
|
55
|
+
`"${modelId}" — cannot determine a sentence span.`);
|
|
56
|
+
}
|
|
57
|
+
for (let i = 0; i < contextTokenIds.length; i++) {
|
|
58
|
+
if (combinedTokenIds[i] !== contextTokenIds[i]) {
|
|
59
|
+
throw new Error(`scoreTemplateContinuation: prefix-token identity violated at position ${i} for sentence "${text}" on model "${modelId}" (context token ${contextTokenIds[i]} vs combined token ${combinedTokenIds[i]}). The tokenizer merged tokens across the context/sentence boundary. Per spec this is an ABORT: no separator character, no blind assumption of the prefix length as the span start.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Score `text` as a Gemma-4-style assistant continuation of the fixed template context.
|
|
65
|
+
*
|
|
66
|
+
* Builds the context via `apply_chat_template(..., { tokenize: false })`, tokenizes the
|
|
67
|
+
* context alone and the context+sentence together via `tokenizer.encode()`, verifies
|
|
68
|
+
* prefix-token identity, determines the sentence-only span, and delegates the actual
|
|
69
|
+
* log-probability accumulation to the pure `sumSequenceLogProb` scorer from Task 1
|
|
70
|
+
* (reused, not reimplemented).
|
|
71
|
+
*/
|
|
72
|
+
export async function scoreTemplateContinuation(deps, text) {
|
|
73
|
+
const { tokenizer, model, modelId, libraryVersion, dtype } = deps;
|
|
74
|
+
const messages = buildTemplateMessages();
|
|
75
|
+
const contextText = tokenizer.apply_chat_template(messages, {
|
|
76
|
+
add_generation_prompt: true,
|
|
77
|
+
tokenize: false,
|
|
78
|
+
});
|
|
79
|
+
const contextTokenIds = Array.from(tokenizer.encode(contextText), Number);
|
|
80
|
+
if (contextTokenIds.length === 0) {
|
|
81
|
+
throw new Error(`scoreTemplateContinuation: fixed template context tokenized to 0 tokens for model "${modelId}". This is the exact false-pass trap from the Task 0 spike (an unwrapped apply_chat_template(tokenize:true) Tensor object silently produced context_len=0, making the prefix-identity check vacuously true) — aborting rather than treating an empty context as a valid prefix.`);
|
|
82
|
+
}
|
|
83
|
+
const combinedTokenIds = Array.from(tokenizer.encode(contextText + text), Number);
|
|
84
|
+
assertPrefixIdentity(contextTokenIds, combinedTokenIds, modelId, text);
|
|
85
|
+
const span = { start: contextTokenIds.length, end: combinedTokenIds.length };
|
|
86
|
+
if (span.start >= span.end) {
|
|
87
|
+
throw new Error(`scoreTemplateContinuation: sentence "${text}" contributed zero tokens after the fixed ` +
|
|
88
|
+
`context (span [${span.start}, ${span.end})) for model "${modelId}".`);
|
|
89
|
+
}
|
|
90
|
+
const scoredTokenIds = combinedTokenIds.slice(span.start);
|
|
91
|
+
const { Tensor } = await import("@huggingface/transformers");
|
|
92
|
+
const seqLen = combinedTokenIds.length;
|
|
93
|
+
const inputIds = new Tensor("int64", BigInt64Array.from(combinedTokenIds.map(BigInt)), [
|
|
94
|
+
1,
|
|
95
|
+
seqLen,
|
|
96
|
+
]);
|
|
97
|
+
const attentionMask = new Tensor("int64", BigInt64Array.from(new Array(seqLen).fill(1n)), [
|
|
98
|
+
1,
|
|
99
|
+
seqLen,
|
|
100
|
+
]);
|
|
101
|
+
const outputs = await model({ input_ids: inputIds, attention_mask: attentionMask });
|
|
102
|
+
const logits = outputs.logits;
|
|
103
|
+
const score = sumSequenceLogProb(logits, combinedTokenIds, span);
|
|
104
|
+
const contextTokenHash = await sha256Hex(JSON.stringify(contextTokenIds));
|
|
105
|
+
const promptSha256 = await sha256Hex(contextText);
|
|
106
|
+
const provenance = {
|
|
107
|
+
modelId,
|
|
108
|
+
dtype,
|
|
109
|
+
logitsDtype: String(logits.type ?? "unknown"),
|
|
110
|
+
backend: "node-onnx",
|
|
111
|
+
libraryVersion,
|
|
112
|
+
templateStrategy: "fixed_template_span",
|
|
113
|
+
promptText: contextText,
|
|
114
|
+
promptSha256,
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
score,
|
|
118
|
+
scoredTokenIds,
|
|
119
|
+
span,
|
|
120
|
+
contextTokenHash,
|
|
121
|
+
provenance,
|
|
122
|
+
method: "conditional_sequence_logprob_sum",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Detect whether a loaded model config indicates a conditional-generation / multimodal
|
|
127
|
+
* architecture (e.g. Gemma-4's `Gemma4ForConditionalGeneration`), which must be scored via
|
|
128
|
+
* the template mode rather than fed bare sentences as a classic causal LM.
|
|
129
|
+
*
|
|
130
|
+
* Detection is from the CONFIG (`architectures`), never the model id string, per spec —
|
|
131
|
+
* so a differently-named repo hosting the same architecture is still detected correctly,
|
|
132
|
+
* and a model whose name happens to contain "gemma" but isn't conditional-generation is not
|
|
133
|
+
* misdetected.
|
|
134
|
+
*/
|
|
135
|
+
export function isConditionalGenerationConfig(config) {
|
|
136
|
+
const architectures = config.architectures ?? [];
|
|
137
|
+
return architectures.some((name) => typeof name === "string" && name.endsWith("ForConditionalGeneration"));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Detect whether a loaded TOKENIZER carries a chat template, which decides the SCORING MODE
|
|
141
|
+
* (template vs. base) — Messmotor-Sanierung Task 6c.
|
|
142
|
+
*
|
|
143
|
+
* This replaces architecture-name sniffing (`isConditionalGenerationConfig`) as the mode
|
|
144
|
+
* signal: Task 6b's panel run showed that keying mode on architecture misses instruction-tuned
|
|
145
|
+
* models whose architecture is plain `*ForCausalLM` (e.g. `LlamaForCausalLM`,
|
|
146
|
+
* `Gemma3ForCausalLM`) but which still expect chat-templated input — they were scored bare,
|
|
147
|
+
* the exact error template mode exists to prevent.
|
|
148
|
+
*
|
|
149
|
+
* Verified empirically (transformers.js@4.2.0, against the four panel models + gpt2) via
|
|
150
|
+
* `AutoTokenizer.from_pretrained(modelId)`: the loaded tokenizer instance exposes the chat
|
|
151
|
+
* template — sourced from `tokenizer_config.json`'s `chat_template` field — directly as
|
|
152
|
+
* `tokenizer.chat_template`. It is `null` for models with no chat template
|
|
153
|
+
* (`Xenova/gpt2`, `Xenova/llama2.c-stories15M`) and a non-empty Jinja template string for
|
|
154
|
+
* instruction-tuned models (`onnx-community/Llama-3.2-1B-Instruct-ONNX`,
|
|
155
|
+
* `onnx-community/gemma-3-1b-it-ONNX`, `onnx-community/gemma-4-E2B-it-ONNX`).
|
|
156
|
+
*
|
|
157
|
+
* Mode selection (this function) and LOADER selection (`isConditionalGenerationConfig`,
|
|
158
|
+
* still architecture-keyed) are deliberately independent: a CausalLM-architecture model with
|
|
159
|
+
* a chat template loads via the base-mode loader (`AutoTokenizer` + `AutoModelForCausalLM`)
|
|
160
|
+
* but scores via the template-mode scorer — e.g. Llama-3.2-1B-Instruct.
|
|
161
|
+
*/
|
|
162
|
+
export function hasChatTemplate(tokenizer) {
|
|
163
|
+
return typeof tokenizer.chat_template === "string" && tokenizer.chat_template.length > 0;
|
|
164
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-based inference using transformers.js
|
|
3
|
+
* For small models (≤3B params) that can run via WebGPU
|
|
4
|
+
*
|
|
5
|
+
* STRICT MODE: Real inference only. No demo data.
|
|
6
|
+
*/
|
|
7
|
+
import type { DataType } from "@huggingface/transformers";
|
|
8
|
+
import type { ScoredLogProb } from "./scored-log-prob-types";
|
|
9
|
+
/** Numeric precision for model weights — re-exported so callers don't need to import from
|
|
10
|
+
* @huggingface/transformers directly. See DATA_TYPES in that package for the full list
|
|
11
|
+
* (includes quantization schemes like "q4", "bnb4"); the scoring path must use "fp32". */
|
|
12
|
+
export type TransformersDType = DataType;
|
|
13
|
+
export type { ScoredLogProb, ScoredLogProbProvenance } from "./scored-log-prob-types";
|
|
14
|
+
export interface BrowserModel {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
size: string;
|
|
18
|
+
supported: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare const BROWSER_MODELS: BrowserModel[];
|
|
21
|
+
/**
|
|
22
|
+
* Check if WebGPU is available in the browser
|
|
23
|
+
*/
|
|
24
|
+
export declare function checkWebGPU(): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Check if transformers.js is available
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkTransformersAvailable(): Promise<boolean>;
|
|
29
|
+
/**
|
|
30
|
+
* Browser/Node inference engine using transformers.js — the single measurement
|
|
31
|
+
* engine for bias log-probabilities (see Messmotor-Sanierung: Ollama's logprobs
|
|
32
|
+
* only cover generated tokens, never prompt/sentence log-probability).
|
|
33
|
+
* STRICT MODE: Throws error if package not installed.
|
|
34
|
+
*
|
|
35
|
+
* Named `TransformersInference` (formerly `BrowserInference`); the old name is
|
|
36
|
+
* re-exported from `browser-inference.ts` for backward compatibility.
|
|
37
|
+
*/
|
|
38
|
+
export declare class TransformersInference {
|
|
39
|
+
readonly modelId: string;
|
|
40
|
+
private model;
|
|
41
|
+
private tokenizer;
|
|
42
|
+
private loadingPromise;
|
|
43
|
+
private loadError;
|
|
44
|
+
private statusCallback;
|
|
45
|
+
private readonly revision;
|
|
46
|
+
/**
|
|
47
|
+
* Public (not private) so callers building their own provenance/artifact records (e.g.
|
|
48
|
+
* the CLI's `run-bias-test.ts`) can read the CONFIGURED weight precision actually used to
|
|
49
|
+
* measure, instead of duplicating it as a separate hardcoded literal that could silently
|
|
50
|
+
* drift out of sync with the constructor argument (Messmotor-Sanierung Task 5 review
|
|
51
|
+
* finding).
|
|
52
|
+
*/
|
|
53
|
+
readonly dtype: TransformersDType;
|
|
54
|
+
/**
|
|
55
|
+
* Selected from the loaded TOKENIZER's chat template (`hasChatTemplate`), never from the
|
|
56
|
+
* model's architecture name and never from `modelId` — see `hasChatTemplate`
|
|
57
|
+
* (Messmotor-Sanierung Task 6c; supersedes the architecture-keyed detection from Task 5,
|
|
58
|
+
* which missed instruction-tuned `*ForCausalLM` models). `true` routes `getScoredLogProb`
|
|
59
|
+
* through the template-mode scorer instead of the Task 1 base (bare-sentence) mode.
|
|
60
|
+
*
|
|
61
|
+
* Independent of `loadTemplateMode`/`loadBaseMode` below, which pick the LOADER
|
|
62
|
+
* (AutoProcessor+Gemma4ForConditionalGeneration vs. AutoTokenizer+AutoModelForCausalLM)
|
|
63
|
+
* and stay keyed on architecture — a CausalLM-architecture instruction model (e.g.
|
|
64
|
+
* Llama-3.2-1B-Instruct) uses the base-mode loader but the template-mode scorer.
|
|
65
|
+
*/
|
|
66
|
+
private isTemplateMode;
|
|
67
|
+
constructor(modelId?: string, statusCallback?: ((status: string) => void) | null, options?: {
|
|
68
|
+
revision?: string;
|
|
69
|
+
dtype?: TransformersDType;
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* Check if browser inference is available
|
|
73
|
+
*/
|
|
74
|
+
isAvailable(): Promise<boolean>;
|
|
75
|
+
/**
|
|
76
|
+
* Load the model (lazy loading)
|
|
77
|
+
* Handles concurrency with loadingPromise
|
|
78
|
+
*/
|
|
79
|
+
load(): Promise<void>;
|
|
80
|
+
private notifyStatus;
|
|
81
|
+
private resolveDevice;
|
|
82
|
+
/**
|
|
83
|
+
* Conditional-generation/multimodal architecture (e.g. Gemma-4): AutoProcessor +
|
|
84
|
+
* Gemma4ForConditionalGeneration, per Task 5. `Gemma4ForConditionalGeneration` is
|
|
85
|
+
* exported directly by @huggingface/transformers@4.2.0 under that exact name (the ONNX
|
|
86
|
+
* repo's own example script uses the same pair) — no fallback to AutoModelForCausalLM is
|
|
87
|
+
* used or needed for this architecture.
|
|
88
|
+
*/
|
|
89
|
+
private loadTemplateMode;
|
|
90
|
+
/** Base mode (Task 1): classic causal LM, unchanged behavior. */
|
|
91
|
+
private loadBaseMode;
|
|
92
|
+
private performLoad;
|
|
93
|
+
/**
|
|
94
|
+
* Generate text.
|
|
95
|
+
*
|
|
96
|
+
* v4 note: previously this called a `pipeline("text-generation", ...)` built from the
|
|
97
|
+
* already-loaded model/tokenizer, which transformers.js re-resolves/re-loads internally
|
|
98
|
+
* — effectively a second model load that `getScoredLogProb`/`getLogProb` never used
|
|
99
|
+
* (Messmotor-Sanierung Task 4, trap 4). Calling `model.generate()` directly avoids that.
|
|
100
|
+
*/
|
|
101
|
+
generate(prompt: string, maxTokens?: number): Promise<string>;
|
|
102
|
+
/**
|
|
103
|
+
* Score a sentence's log-probability with full provenance.
|
|
104
|
+
* This is the "Gold Standard" for bias testing.
|
|
105
|
+
*
|
|
106
|
+
* BOS fix: GPT-2-family tokenizers do not insert a BOS token on their own, so the
|
|
107
|
+
* sentence is tokenized WITHOUT special tokens and a BOS token is explicitly
|
|
108
|
+
* prepended before feeding the model (input_ids and attention_mask are rebuilt
|
|
109
|
+
* together). This ensures the model sees P(first_token | BOS) instead of silently
|
|
110
|
+
* skipping the first sentence token. BOS itself is context and is never scored;
|
|
111
|
+
* every sentence token is scored via the half-open span [1, n+1).
|
|
112
|
+
*/
|
|
113
|
+
getScoredLogProb(text: string): Promise<ScoredLogProb>;
|
|
114
|
+
/**
|
|
115
|
+
* Calculate Log-Probability for a given text (score only).
|
|
116
|
+
* Thin wrapper around `getScoredLogProb` for callers that only need the score.
|
|
117
|
+
*/
|
|
118
|
+
getLogProb(text: string): Promise<number>;
|
|
119
|
+
/**
|
|
120
|
+
* Get list of supported browser models
|
|
121
|
+
*/
|
|
122
|
+
static getSupportedModels(): BrowserModel[];
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=transformers-inference.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transformers-inference.d.ts","sourceRoot":"","sources":["../../src/inference/transformers-inference.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,QAAQ,EAIT,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAY7D;;0FAE0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AAKzC,YAAY,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAiDtF,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACpB;AAGD,eAAO,MAAM,cAAc,EAAE,YAAY,EAyBxC,CAAC;AAEF;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAcpD;AAED;;GAEG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,OAAO,CAAC,CAQnE;AAED;;;;;;;;GAQG;AACH,qBAAa,qBAAqB;IAChC,SAAgB,OAAO,EAAE,MAAM,CAAC;IAChC,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,cAAc,CAA2C;IACjE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC;;;;;;OAMG;IACH,SAAgB,KAAK,EAAE,iBAAiB,CAAC;IACzC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc,CAAS;gBAG7B,OAAO,SAAsB,EAC7B,cAAc,GAAE,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAW,EACxD,OAAO,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,iBAAiB,CAAA;KAAO;IAahE;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAKrC;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,OAAO,CAAC,YAAY;YAIN,aAAa;IAU3B;;;;;;OAMG;YACW,gBAAgB;IAyB9B,iEAAiE;YACnD,YAAY;YAsBZ,WAAW;IAkDzB;;;;;;;OAOG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,SAAK,GAAG,OAAO,CAAC,MAAM,CAAC;IA4B/D;;;;;;;;;;OAUG;IACG,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAoF5D;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK/C;;OAEG;IACH,MAAM,CAAC,kBAAkB,IAAI,YAAY,EAAE;CAG5C"}
|