Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ All notable changes to `explorables` are documented here.
- Reframed the local collection home as a general, status-first course library: available courses are primary, planned AI courses sit in a quieter roadmap, and visible product branding now uses `Explorables` while technical identifiers remain lowercase.
- `AI from First Principles` is versioned as `0.4.0-foundations.1`, with self-contained canonical
lesson prose, prerequisite bridges, worked examples, implementation connections, failure modes,
and explanation-based recaps across all thirteen lessons.
and explanation-based recaps across all thirteen lessons. Because browser progress is scoped to
the course version, existing `0.3.0-guided.1` progress does not resume in this new edition.
- The product requirements, architecture, authoring guide, implementation plan, status, and model-learning roadmap now include guided delivery.
- The local course server now treats its default port as strict. An occupied port fails with an actionable message instead of silently changing the browser-storage origin.
- First-party and scaffolded host adapters now use browser course state as the progress authority and distinguish pausing from finishing or resetting.
Expand Down
11 changes: 11 additions & 0 deletions docs/course-authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,24 @@ Each simulated step applies `next = current + arrivals - serviced`, then enforce
and upper bounds. The growing queue is therefore evidence of a rate imbalance, not evidence that
the service has stopped entirely.

For the predicted case, the intermediate lengths are `4 + 5 - 3 = 6` after one second and
`6 + 5 - 3 = 8` after two. In code, `current`, `arrivals`, and `serviced` are numeric inputs to one
update, while `capacity` supplies its upper bound. A deliberately broken update that returns `8`
when capacity is `7` violates the bounded-queue invariant; the correct result is `min(8, 7) = 7`.

:::exercise{path="../exercises/bounded-queue" command="pnpm test" title="Bound the queue"}
Implement the capacity check and run the supplied tests.
:::

## Check your understanding

Why can a queue grow even while the service is successfully completing work?

## Recap

- Queue growth is the difference between arrivals and completed work.
- Each update must enforce both the empty-queue lower bound and the configured capacity.
- A passing ordinary example is not evidence that boundary cases are correct.
```

Only `explorable` and `exercise` are supported. Unknown directives fail validation. Relative Markdown links and assets resolve from the lesson file; paths may not escape the course root. Raw HTML is sanitised.
Expand Down
1 change: 1 addition & 0 deletions docs/implementation-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ The v0.1 runtime MVP remains verified. Course-session continuity now provides a
## Latest verification

```text
19 Aug canonical foundations pass (clean worktree: format/lint/typecheck, 33 suites/102 tests, 17 course suites/54 tests, 14 starter/reference pairs, all builds, 16 browser tests, axe, 320px overflow check, and in-app visual QA)
19 Aug general library redesign pass (typecheck, 7 suites/29 targeted tests, collection validation/build, 15 browser tests, 2 site tests, axe, light/dark visual QA at 843px and 320px)
18 Aug Model Atlas unit/integration pass (33 suites/100 tests; strict descriptors, exact traces, comparisons, renderer lifecycle, validator, and real catalogue bundle)
18 Aug pnpm check / build pass (Node 26 shell emitted expected unsupported-engine warning; supported Node lines verified separately)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ function softmax(logits: number[]): number[] {
return exponents.map((value) => value / total);
}

function crossEntropy(logits: number[], target: number): number {
const maximum = Math.max(...logits);
const shiftedTarget = (logits[target] ?? Number.NEGATIVE_INFINITY) - maximum;
const shiftedExponentialSum = logits.reduce(
(sum, logit) => sum + Math.exp(logit - maximum),
0,
);
return Math.log(shiftedExponentialSum) - shiftedTarget;
}

function validate(weights: number[][], tokenIds: number[]): number {
if (weights.length === 0) throw new RangeError("weights must not be empty");
const vocabularySize = weights.length;
Expand All @@ -41,10 +51,10 @@ export function meanLoss(weights: number[][], tokenIds: number[]): number {
validate(weights, tokenIds);
const pairs = nextTokenPairs(tokenIds);
return (
pairs.reduce((sum, pair) => {
const probabilities = softmax(weights[pair.input] ?? []);
return sum - Math.log(probabilities[pair.target] ?? 0);
}, 0) / pairs.length
pairs.reduce(
(sum, pair) => sum + crossEntropy(weights[pair.input] ?? [], pair.target),
0,
) / pairs.length
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,34 @@ describe("shifted next-token training", () => {
).toBeCloseTo(1.3132617);
});

it("keeps cross-entropy finite when the target softmax probability underflows", () => {
expect(
meanLoss(
[
[0, -1000],
[0, 0],
],
[0, 1],
),
).toBeCloseTo(1000);
});

it("averages accumulated gradients across all valid positions", () => {
expect(
trainStep(
[
[0, 0],
[0, 0],
],
[0, 1, 1],
1,
),
).toEqual([
[-0.25, 0.25],
[-0.25, 0.25],
]);
});

it("reduces the correctly shifted objective", () => {
const sequence = [0, 1, 0, 2];
let weights = identityBiased;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";

const { nucleus } = await import(
process.env.EXPLORABLES_SOLUTION === "1"
? "../solution/sampling.ts"
Expand All @@ -11,4 +12,10 @@ describe("nucleus distribution", () => {
expect(
nucleus([2, 1, 0], 1, 0.9).reduce((a: number, b: number) => a + b, 0),
).toBeCloseTo(1));
it("stably scales large logits at a non-unit temperature", () => {
const distribution = nucleus([2000, 1998], 2, 1);
expect(distribution.every(Number.isFinite)).toBe(true);
expect(distribution[0] ?? Number.NaN).toBeCloseTo(0.7310586);
expect(distribution[1] ?? Number.NaN).toBeCloseTo(0.2689414);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ not establish that the objective represents the intended task.

## Shift inputs and targets

Let a tokenised window be `x₀, x₁, ..., x_{T−1}`. At position `t`, the input
Let a tokenised window be `x₀, x₁, ..., x_{T−1}`, where capital `T` is the
sequence length and lowercase `t` is a position index. At position `t`, the input
contains token `xₜ`, and the training target is the token one step to its right,
`xₜ₊₁`. The valid aligned pairs are therefore

Expand Down
3 changes: 2 additions & 1 deletion templates/basic-course/explorables/hello/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ const module: ExplorableModule = {
label.textContent = "Input";
const input = document.createElement("input");
input.type = "range";
input.min = "0";
input.min = "-10";
input.max = "10";
input.step = "0.5";
input.value = "2";
const output = document.createElement("output");
output.setAttribute("aria-live", "polite");
Expand Down
17 changes: 8 additions & 9 deletions templates/basic-course/lessons/01-introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,9 @@ checkpoints:

# Introduction

A **function** accepts an input and returns an output. This lesson uses a doubling function: it
multiplies every finite input by `2`. The input `3`, for example, produces `3 × 2 = 6`.

The important contract is not just one remembered answer. The same rule must work for positive,
negative, and fractional inputs, while the program must decide what to do with values such as
`Infinity` that are not finite.
A **function** accepts an input and returns an output. This lesson investigates a function named
`double`. Its name is a clue, but the saved runs are the evidence you will use to infer its exact
rule. The explorer accepts positive, negative, and fractional finite numbers.

> **Predict:** What output do you expect for an input of 4, and which single rule supports your
> prediction?
Expand All @@ -49,9 +46,11 @@ Choose an input, observe the output, and save at least two runs to infer the rel

## Explain the evidence

Compare the saved runs. If both satisfy `output = input × 2`, one rule accounts for more than a
single example. A few matching examples do not prove the implementation handles every JavaScript
number, so the exercise tests the boundary as well as the ordinary calculation.
Compare the saved runs. They should support the rule `output = input × 2`: an input of `3`, for
example, produces `3 × 2 = 6`. One rule now accounts for more than a single remembered answer. A
few matching examples do not prove the implementation handles every JavaScript number, so the
exercise tests the boundary as well as the ordinary calculation. In particular, a non-finite input
such as `Infinity` must be rejected rather than passed through the multiplication.

For an input named `value`, the implementation responsibility is:

Expand Down