Bauen

Erweiterungen erstellen

Scellis zieht die Eigentumsgrenze an einer ungewohnten Stelle: Der Maintainer besitzt die Grammatik, den Checker und das Referenz-Orakel — sonst nichts. Jede Instanz gehört dir. Alles, was der Maintainer berechnen kann, erstellst du selbst aus der Oberfläche heraus — ohne Engine-Patch, ohne Freigabe-Warteschlange. Das Credo hinter dem Design ist deutlich: „Wenn ein Nutzer ohne mich keinen Laplace-Operator erstellen kann, gehört das Substrat mir — nicht ihm.“ Diese Seite ist der praktische Rundgang durch diesen Anspruch: was erstellbar ist, wie eine erstellte Op konkret aussieht, wie ein Kernel Vertrauen verdient — und wie dein Code auf allem aufbaut, was schon im Katalog liegt.

Die erstellbare Oberfläche#

Die erstellbare Oberfläche ist das eigene Vokabular der Plattform, Ende zu Ende — keine seitlich angeschraubte Plugin-Nische:

  • Berechnung: Blöcke — Composite-Blöcke, deren Körper ein Graph als Daten ist, und Funktionsblöcke in der Sandbox — dazu Ops mit deklariertem Shape- und Typverhalten sowie GPU-Kernel, gepaart mit CPU-Referenzen.
  • Training: Gradientenregeln, Optimizer, Scheduler, Losses, Metriken, PEFT-Adapter-Arten und die Aggregationsstrategien, auf denen kollaboratives Training läuft.
  • Oberfläche und Daten: Viewer, Connectors, Format-Codecs, Modelle, Datensätze — und die Packs, die all das ausliefern.

Was der Maintainer behält, sind genau drei Grammatiken: eine Shape-/Signatursprache, in der Ops ihre Verträge deklarieren; eine Buffer-Binding-ABI für Storage-Buffer, Uniforms und Workgroup-Shared-Scratch; und eine Backward-(VJP-)Repräsentation. Alles andere ist eine Instanz über diesen Grammatiken — und weil die Engine die Grammatiken generisch interpretiert, kann sie beim Dispatch einen Built-in nicht von einer Nutzer-Entität unterscheiden. Der Unterschied ist ein Herkunfts-Chip im Katalog, nie ein eigener Codepfad.

Anatomie einer erstellten Op#

Du erstellst in einem Formular, das das Studio aus dem Op-Schema rendert — und der Copilot kann denselben Datensatz entwerfen, denn er schreibt über dieselbe API. Darunter hat der Datensatz eine Form:

yaml
- name: user/ada/mish            # namespaced — never shadows a builtin
  version: 1
  summary: "Mish activation: out = a * tanh(softplus(a))"
  taxonomy: webir.op.elementwise
  inputs:  [{ name: a,   dtype: [f32], shape: ["..."] }]
  outputs: [{ name: out, dtype: [f32], shape: ["..."] }]
  tolerance:   { class: tight }  # justified against the op's semantics
  determinism: { debug: deterministic, prod: deterministic_within_tolerance }
  gradients:   { kind: elementwise, vjp_rule: "grad_out * mish'(a)" }
  kernels:     { forward: [{ id: user/ada/mish_f32, when: { dtype: f32 } }] }

Jedes Feld ist ein geprüfter Vertrag, keine Dokumentation. Die Id ist namensraumgebunden (user/<owner>/<name>), sodass eine Nutzer-Op neben einem Built-in steht, nie an seiner Stelle. Die Shape-Strings sind eine interpretierte Signatur — eine Op, deren Ausgabegröße von Datenwerten abhängt, deklariert genau das, mit einer Worst-Case-Schranke, damit Speicher planbar bleibt. Die Toleranzklasse muss zur Semantik der Op passen: Ein Matmul kann nicht loose deklarieren, um der Konformität auszuweichen. Und Determinismus wird pro Stufe deklariert — debug, prod, fast — nie still per Default gesetzt.

Der Kernel und seine Referenz#

Erstellte Kernel sind kein eingeschränkter Dialekt. Die Binding-ABI deckt lesende und schreibende Storage-Buffer, Uniforms und Workgroup-Shared-Scratch ab — ein gekacheltes Matmul, eine Reduktion oder ein Stencil ist also erstellbar, nicht nur eine elementweise Abbildung. Ein minimaler Forward-Kernel zur Op oben:

wgsl
@group(0) @binding(0) var<storage, read>       in0 : array<f32>;
@group(0) @binding(1) var<storage, read_write> out : array<f32>;
@group(0) @binding(2) var<uniform>             p   : Params;

@compute @workgroup_size(256, 1, 1)
fn main(@builtin(global_invocation_id) gid : vec3<u32>) {
  let i = gid.x;
  if (i >= p.outNumel) { return; }
  let x = in0[i];
  out[i] = x * tanh(log(1.0 + exp(x))); // must match the reference within "tight"
}

Bei der Paarungsregel beginnt das Vertrauen. Neben dem WGSL lieferst du eine Referenzimplementierung — in einer geschlossenen skalaren Grammatik oder als Sandbox-JS mit niedrigerer Vertrauensobergrenze. Der Konformitätsläufer generiert Testfälle, führt beide aus und verlangt Übereinstimmung innerhalb der deklarierten Toleranzklasse. Das Bestehen prägt ein hash-gebundenes ConformanceReceipt, das mit dem Kernel reist und im Katalog sichtbar ist — dasselbe Regime, das auf der Korrektheits-Seite jeden Built-in prüft, über derselben IR-Leiter.

Ein ehrliches Detail: Das Receipt ist profilgebunden. Es belegt die Paarung auf genau der GPU-Umgebung, auf der es lief; auf einer anderen Geräteklasse läuft die Prüfung lokal erneut. Receipts reisen nie als importiertes Vertrauen — das würde mehr behaupten, als getestet wurde.

Hinweis

Konformität schaltet vertrauenswürdige Aktivierung und Teilen frei — nie deine eigene Schleife. Deinen eigenen, noch unkonformen Kernel kannst du auf deinem Gerät jederzeit ausführen: schreiben, laufen lassen, prüfen, korrigieren, wieder laufen lassen.

Auch Gradienten und Reducer verdienen Vertrauen#

Ein deklarierter Backward ist ein gewöhnlicher komponierter Op-Graph — dieselbe Ausdruckskraft wie ein Forward — und er wird per Gradient-Check mit finiten Differenzen gegen deinen eigenen Forward geprüft, inklusive einer verpflichtenden Negativkontrolle. Offen gesagt: Der Gradient-Check ist eine starke notwendige Bedingung, kein formaler Beweis. Ein nutzerdeklarierter Reducer geht weiter — seine behauptete Algebra (Assoziativität, Kommutativität) wird vor der Aktivierung property-getestet, denn ein falsch deklarierter Kombinierer würde per Baum-Reduktion still zu falschen Zahlen führen.

Abb. 1 — Zwei Bahnen. Deinen eigenen Kernel auf deinem eigenen Gerät zu laufen ist nie freigabepflichtig — schreiben, laufen lassen, korrigieren, wieder laufen lassen. Das Gate steht auf der anderen Bahn: Erst wenn Kernel und Referenz innerhalb der deklarierten Toleranz übereinstimmen, entsteht ein Receipt — und erst dieses Receipt öffnet Teilen, Training und Verteilung. Das Receipt ist an die GPU gebunden, auf der es bewiesen wurde; ein anderes Gerät prüft lokal nach.

Den Katalog wiederverwenden wie eine Bibliothek#

Ein erstellter Körper startet beim ganzen Katalog, nicht bei null. Das Design-Mandat lautet „wie pandas importieren und einen DataFrame benutzen“ — aus jedem erstellten Körper heraus rufst du andere Entitäten über eine einzige, kontrollierte Oberfläche auf:

ts
// Inside any authored body: the governed call surface.
const y = ctx.op("core/softmax", x, { axis: -1 }); // a builtin, resolved by name
const z = ctx.call("user/kim/zscore@3", y);        // another author's block
const s = ctx.tensor.matmul(z, w);                 // the tensor-algebra surface
// Every reference resolves through YOUR pinned uses closure —
// never against "whatever happens to be newest".

Die Buchführung ist nicht dein Job. Abhängigkeitskanten werden automatisch aus deinen Aufrufstellen abgeleitet und auf exakt die Versionen gepinnt, die beim Erstellen aufgelöst wurden; das Lockfile hält ihre Hashes fest. Danach löst sich jeder Laufzeitaufruf durch diesen Pin auf — die Aufrufe und die deklarierte Closure sind dieselbe Menge: Was du aufrufst, ist exakt, was du gepinnt hast. Eine erstellte Entität ist damit per Konstruktion reproduzierbar und offline sicher ausführbar; ein Abhängigkeitszyklus oder ein unauflösbarer Pin wird laut abgelehnt, nie zu „das Neueste gewinnt“ aufgelöst.

Zwei Eigenschaften halten die Komposition ehrlich. Effekte sind transitiv über die Closure: Eine als pur deklarierte Funktion kann nicht leise einen Helfer mit Netzwerk-Effekt aufrufen, denn die Effekte des Aufgerufenen zählen für die ganze Kette, und die Einwilligung wird über die Vereinigung eingeholt. Und Vertrauen komponiert konservativ: Ein Aufgerufener läuft auf der strengsten Vertrauensstufe seiner Aufrufkette — nicht vertrauenswürdiger Code kann sich nicht über vertrauenswürdige Aufrufer reinwaschen.

Abb. 2 — Du rufst den Katalog wie eine Bibliothek auf, und die Buchführung schreibt sich selbst: Deine Aufrufstellen leiten die Abhängigkeitskanten ab, und jede wird auf exakte Version und Hash gepinnt. Was du aufrufst, ist exakt das, was du gepinnt hast — offline, und auch in einem Jahr.

Ein Check, eine Messlatte — und ehrliche Namen#

Jede erstellte Entität durchläuft dieselbe Prüfung auf ihrem vollständigen Datensatz — beim Erstellen und erneut bei jeder Bearbeitung. Es ist exakt die Prüfung, die auch jeden Built-in prüft: Fehlende oder unvollständige Angaben werden abgelehnt, und die Ablehnung benennt, was fehlt. Built-ins sind unveränderlich — einen zu „bearbeiten“ forkt eine erstklassige Kopie, die dir gehört, mit festgehaltener Herkunft; deine eigenen Entitäten bearbeitest du direkt, über denselben validierten Pfad.

Auch Überschreiben und Namen bleiben ehrlich. Jede Op, die einen erstellbaren Binding-Vertrag deklariert, ist überschreibbar — die Override-Menge ist eine Katalogabfrage, keine fest verdrahtete Liste — und auf deinem Gerät gewinnt der Kernel deines Workspaces vor dem eines installierten Packs, der vor dem eingebauten Kern gewinnt. Aber eine Nutzer-Op kann sich nie als Built-in ausgeben: Erstellte Ids leben unter user/<owner>/<name>, und ein blanker Built-in-Name löst immer zur Core-Op auf. Überschreiben ist eine lokale Dispatch-Präferenz; Identitätsübernahme ist strukturell ausgeschlossen.

Veröffentlichen ist ein eigener Schritt#

Vertrauensstufen — unverified, community, verified, official — werden pro Artefakt verdient und sind orthogonal zur Autorschaft: Nutzer-Entitäten können verified erreichen. Ist eine Entität bereit, dein Gerät zu verlassen, packst du sie, signierst sie im Browser und bestehst die Publikations-Gates: ein aufgelöstes Lockfile, Konformitätsbelege wo anwendbar, vollständige Metadaten und eine explizite Lizenz. Dieser Weg — und was Käufer und Installierende auf der anderen Seite sehen — ist Thema von Packs und Veröffentlichen. Ist dein Werk eine Trainingskomponente, taucht sie jetzt in den Model-View-Pickern auf wie jede mitgelieferte — siehe Training im Browser.