Update API documentation with Llama 3.1 quirks

This commit is contained in:
2026-05-03 16:20:56 +02:00
parent 3243823b40
commit 6fac8775e1
+108 -34
View File
@@ -1,7 +1,7 @@
# Ionos LLM API: Quirks und Workarounds
**Stand:** 2026-05-03 — empirisch ermittelt gegen
`https://openai.inference.de-txl.ionos.com/v1/chat/completions`.
**Stand:** 2026-05-03 (zweite Session, in Doctate umgesetzt) — empirisch
ermittelt gegen `https://openai.inference.de-txl.ionos.com/v1/chat/completions`.
Verhalten kann sich aendern; Datum oben mit dem aktuellen Test-Befund
abgleichen, bevor man auf diese Notizen baut.
@@ -15,15 +15,22 @@ abgleichen, bevor man auf diese Notizen baut.
`https://api.ionos.com/docs/inference-openai/v1/`. Das ist eine
Redoc-Seite mit eingebetteter OpenAPI-3.0.3-Spec — die einzige
verlaessliche Quelle fuer unterstuetzte Parameter, Defaults und Limits.
- Llama 3.1 405B FP8 hat einen **vLLM-Tokenizer-Bug**: das
End-of-Turn-Token `<|eot_id|>` wird als Klartext-String
`assistant\n\n` ausgegeben statt als Special-Token. Folge: der
offizielle `stop`-Parameter mit `<|eot_id|>` feuert nie, das Modell
laeuft bis `max_tokens` voll.
- **Empfohlene Loesung fuer Doctate**: `response_format: json_schema`.
Schema-erzwungenes Decoding stoppt natuerlich am schliessenden `}`,
funktioniert modell-agnostisch und braucht keine Provider-spezifischen
Workarounds.
- Llama 3.1 405B FP8 hat ein bekanntes **End-of-Turn-Termination-Problem**:
in der Sandbox kam `<|eot_id|>` als ASCII-`assistant\n\n` zurueck, was
Quelle der ersten Diagnose war. Aber das eigentliche Problem ist breiter
und modell-inhaerent: Llama 3.1 verfaellt mit Greedy-Decoding und/oder
konflikthaltigen Prompts in Endlos-Schleifen (bestaetigt von Ionos selbst,
HuggingFace-Discussion #32, vLLM-Issues #13530, #13828).
- **Empfohlene Loesung fuer Doctate (Drei-Komponenten-Kombi):**
`temperature: 0.6 + top_p: 0.9` (Sampling) + `response_format:
json_schema` (Form-Garantie) + expliziter Format-Hinweis als zweite
`system`-Message (sagt dem Modell WAS in `document` zu schreiben ist).
Alle drei sind notwendig; jede einzelne weggelassen kippt in einen
anderen Failure-Mode (Timeout, Endlos-Schleife, oder silent
`{"document": ""}`).
- **Wichtig**: gpt-oss-120b braucht weder Sampling noch Format-Hinweis —
es ist schema-aware trainiert und laeuft mit dem Default-Body sauber.
Die Drei-Komponenten-Loesung ist Llama-spezifisch, nicht universell.
## So kommt man an die richtige API-Info
@@ -176,6 +183,8 @@ Gateway-Timeout.
### Empirische Datentabelle
#### Session 1 (Diagnose, Sandbox-Inputs)
Alle Tests mit `meta-llama/Meta-Llama-3.1-405B-Instruct-FP8`,
Input `"Sag hallo."` (ausser T22 = realer Doctate-Body, ~1.5k Prompt-Tokens):
@@ -193,8 +202,41 @@ Input `"Sag hallo."` (ausser T22 = realer Doctate-Body, ~1.5k Prompt-Tokens):
| T22 | T21 + realer Doctate-Body (1539 Prompt-Tokens) | **22.77 s, finish=stop, 482 Tokens** | 22.7 |
Konsistenz: **`finish_reason: "length"` heisst Modell wollte
weiter** — kein sauberer Stop. Nur die letzten vier Loesungswege
bekommen sauberes `"stop"`.
weiter** — kein sauberer Stop.
#### Session 2 (Reproduktion gegen Production-Pipeline, 30 s Timeout)
Alle Tests gegen den realen Doctate-Body von Case `aecf5890` (236
char user_content, ~1040-1115 Prompt-Tokens). Skripte unter
`/tmp/doctate-llama-test/run{1..6}.py`. Production-`LLAMA_SYSTEM_PROMPT`
verbatim (2819 chars).
| Test | Sampling | json_schema | Format-Hinweis | Wallclock | finish | Output |
|---|---|---|---|---|---|---|
| T1 | nein (`temp=0.5`) | ja | nein | **30 s Timeout** | | nichts geliefert |
| T2 | nein (`temp=0.5`) | ja | im Prompt inline | 3.26 s | stop | sauber, 61 Tokens |
| T3 | ja (`0.6 + 0.9 + freq=0.1 + stop`) | nein (Ionos-Doku verbatim) | nein | **30 s Timeout** | | nichts geliefert |
| T4 | ja (`0.6 + 0.9`) | ja | nein | 0.98 s | stop | **`{"document":""}` (silent deletion)** |
| T5 | ja (`0.6 + 0.9`) | ja | im Prompt inline | 3× ⌀ 3.05 s | stop | sauber, 61 Tokens, 3× stabil |
| T6 | ja (`0.6 + 0.9`) | ja | als zweite `system`-Message | 3× ⌀ 3.13 s | stop | sauber, 61 Tokens, 3× stabil — **Production-Konfig** |
**Wichtige Befunde aus Session 2:**
1. **json_schema allein reicht nicht (T1)**: Production-Pfad mit
`temperature: 0.5` ohne `top_p` timeoutet, weil Llama Schema-Resampling
als Endlos-Schleife verkraftet.
2. **Sampling allein reicht nicht (T3)**: Selbst die exakte Ionos-Doku-
Empfehlung mit `stop: ["<|eot_id|>", "<|end_of_text|>"]` timeoutet
gegen unseren 2.8k-Char-Prompt. Die Doku-Empfehlung ist fuer kuerzere
Prompts validiert.
3. **Sampling + Schema ohne Hinweis kippt in silent deletion (T4)**:
Modell respektiert die Form, weiss aber inhaltlich nicht was tun.
`{"document": ""}` waere fuer Doctate als leere Notiz im UI gelandet
— gefaehrlicher als Timeout, weil der Worker `remove_failure_marker`
aufruft. **Worker.rs hat seither einen Empty-Guard.**
4. **Drei-Komponenten-Kombi laeuft 3x stabil (T5, T6)**: Sampling +
Schema + Format-Hinweis. T6 (separate `system`-Message) ist
architektonisch sauberer und gewaehlt fuer die Doctate-Implementation.
## Loesungswege im Vergleich
@@ -202,33 +244,60 @@ bekommen sauberes `"stop"`.
|---|---|---|---|---|
| `stop: ["assistant"]` | sehr schnell | nein | nein | Patch fuer Provider-Bug; bricht im englischen Text |
| Eigener End-Marker via System-Prompt | sehr schnell | mittel | ja | Modell kann Marker bei langem Output vergessen |
| `response_format: json_schema` | schnell | ja | **ja** | Server erzwingt Stop ueber Grammar-Decoding |
| Modellwechsel auf `gpt-oss-120b` / `Llama-3.3-70B` | sehr schnell | ja | | Quirks A+B betreffen diese Modelle nicht |
| `response_format: json_schema` allein | | nein | nein | **In Production NICHT ausreichend** (T1, T4) — kippt entweder in Timeout oder silent deletion |
| **Drei-Komponenten-Kombi** (Sampling + Schema + Format-Hinweis) | schnell (3 s) | ja | | **In Doctate umgesetzt fuer Llama** (T5, T6) |
| Modellwechsel auf `gpt-oss-120b` / `Llama-3.3-70B` | sehr schnell | ja | | Schema-aware, brauchen die Drei-Komponenten-Kombi nicht |
Empfehlung: **`response_format: json_schema`** als Default-Pfad fuer
Doctate, weil derselbe Code-Pfad auf gpt-oss-120b und Llama 3.3 70B
unveraendert weiter funktioniert. Wenn das Schema-Decoding bei
zukuenftigen, viel groesseren Outputs Latenzprobleme macht, wechselt
man auf einen schnelleren Modell-Endpoint, nicht zurueck auf
String-Hacks.
**Empfehlung pro Modell-Familie:**
- Schema-aware Modelle (gpt-oss-120b, vermutlich Llama 3.3 70B):
`temperature: 0.5 + response_format: json_schema`. Body bleibt klein
und stabil.
- Llama 3.1 405B FP8: nur als **Drei-Komponenten-Kombi** brauchbar —
`temperature: 0.6 + top_p: 0.9 + response_format: json_schema +
expliziter Format-Hinweis als zweite system-Message`. Wer eine
Komponente weglaesst, faengt sich Timeout (T1, T3) oder silent
deletion (T4) ein.
## Doctate-Implementation (`server/src/analyze/backend.rs`)
Die Drei-Komponenten-Kombi lebt als Per-Backend-Konfiguration:
```rust
const LLAMA_TEMPERATURE: f32 = 0.6;
const LLAMA_TOP_P: f32 = 0.9;
const LLAMA_FORMAT_INSTRUCTION: &str = "AUSGABEFORMAT: ...";
ionos_backend("llama_3_1_405b", "Llama 3.1 405B", "...405B-Instruct-FP8")
.with_system_prompt(LLAMA_SYSTEM_PROMPT)
.with_temperature(LLAMA_TEMPERATURE)
.with_top_p(LLAMA_TOP_P)
.with_format_instruction(LLAMA_FORMAT_INSTRUCTION),
```
`chat_once` injiziert `format_instruction` als zweite `system`-Message
zwischen `system_prompt` und `user`-Content, falls `Some`. Backends
ohne `format_instruction` (gpt-oss) erzeugen den exakten Body wie vor
dem Llama-Fix — byte-stabil, gepinnt durch
`gpt_oss_has_no_format_instruction_and_no_top_p`-Test.
`worker.rs` hat einen **Empty-Guard** nach `vocab.replace`: leere
`document`-Strings (Failure-Mode T4) loesen einen Failure-Marker statt
einer leeren `document.md` aus.
## Entscheidungsbaum
```mermaid
graph TD
A[Neuer Aufruf gegen Ionos] --> B{Brauche ich strukturierten Output?}
B -->|Ja oder unsicher| C[response_format: json_schema setzen]
B -->|Nein, freier Text| D{Modell?}
D -->|gpt-oss-120b oder Llama 3.3 70B| E[Standard-Aufruf reicht]
D -->|Llama 3.1 405B FP8| F[max_tokens MUSS gesetzt sein]
F --> G{Wie soll gestoppt werden?}
G -->|Sauber via Schema| C
G -->|String-Hack ok| H["stop: array enthaelt 'assistant'"]
G -->|Eigener Marker| I[System-Prompt instruiert + stop matcht]
C --> J[Output ist JSON, server-seitig parsen]
E --> K[Output direkt nutzen]
H --> K
I --> K
A[Neuer Backend-Profil-Eintrag in backend.rs] --> B{Modell?}
B -->|gpt-oss-120b<br/>schema-aware| C[Default: temp 0.5,<br/>use_json_schema=true,<br/>kein top_p, keine format_instruction]
B -->|Llama 3.1 405B FP8| D[Drei-Komponenten-Kombi:<br/>temp 0.6 + top_p 0.9 + schema<br/>+ format_instruction]
B -->|Llama 3.3 70B<br/>noch nicht getestet| E[Erst gegen Doctate-Body testen,<br/>vermutlich wie gpt-oss]
B -->|Anderes neues Modell| F{Schema-aware trainiert?}
F -->|Ja| C
F -->|Nein| D
C --> G[Body byte-stabil zu pre-Llama-Fix]
D --> H[Worker-Empty-Guard schuetzt vor silent deletion]
E --> G
```
## Quellen und Verweise
@@ -254,6 +323,11 @@ einem neuen Bug auf diese Workarounds setzt, immer zuerst pruefen, ob:
`assistant\n\n` ankommt — dann ist Quirk A behoben und die
String-Hacks koennen weg.
3. Die OpenAPI-Spec neue/geaenderte Parameter zeigt.
4. Die Drei-Komponenten-Kombi fuer Llama 3.1 weiterhin noetig ist:
schnellster Check ist, das `format_instruction`-Feld testweise auf
`None` zu setzen und einen Doctate-Case neu zu triggern. Wenn der
Output sauber kommt, kann der Format-Hinweis weg. Wenn er
`{"document":""}` ist, gilt diese Doku noch.
Datum oben aktualisieren, wenn man verifiziert hat, dass diese
Dokumentation noch dem realen Backend-Verhalten entspricht.