Kazeia-engine/CHANTIER_HMX_DENSE.md

127 lines
10 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Chantier — matmul HMX robuste pour le DENSE sur HTP (crash 0x2e)
Objet : faire tourner le dense (qwen3) sur HTP en **HMX** (prefill ~295) au lieu de HVX (~40), maintenant
que le `.pte` est retiré (l'engine est le seul runtime LLM). Bloqueur = crash du matmul HMX sur le dense.
## Symptôme
Prefill dense (qwen3-4B Q4_0) sur HTP avec HMX (défaut) → `ggml-hex: dspqueue_read failed: 0x0000002e`
dans `flush_pending``llama_decode`, dès que le prompt dépasse ~12-16 tokens. HVX (`GGML_HEXAGON_USE_HMX=0`)
ne crashe pas. La 3.5 (hybride) ne crashe pas en HMX.
## Repro
```
GGML_HEXAGON_OPLOG=1 GGML_HEXAGON_OPBATCH=1 LD_LIBRARY_PATH=./lib ADSP_LIBRARY_PATH=./lib \
./llama-cli -m Qwen3-4B-Q4_0.gguf -ngl 99 -dev HTP0 -t 4 --reasoning off -cnv -st --temp 0 -n 2 \
-p 'un deux trois quatre cinq six sept huit neuf dix onze douze treize quatorze quinze seize'
```
## Hypothèse ÉCARTÉE : overflow fp16
Le matmul fautif a une **activation max-abs = 9.8** (mesuré via OPLOG `s1_amax`), très en-dessous de la plage
fp16 (65504). **Ce n'est PAS un overflow.** Confirmé : clamp ±65504 (conversion f32→f16) ET scaling activations
÷32 puis ÷256 (avec ×S en sortie) n'ont rien changé au crash. Donc ni l'entrée ni l'accumulateur fp16 ne débordent.
## Localisation (via instrumentation, voir plus bas)
Dernier op avant le 0x2e = un **MUL_MAT q4_0**, juste après `FLASH_ATTN_EXT` :
```
KZOP MUL_MAT dst=[2560,38,1,1] src0=q4_0[4096,2560] src1=f32[4096,38] s1_amax=9.8 <- CRASHE
```
C'est l'**o_proj** (projection de sortie d'attention : in 4096 = 32 têtes×128, out 2560 = hidden), donc **k=4096**.
Les autres MUL_MAT du graphe ont **k=2560** et passent (ffn gate/up `[2560,4096]`, qkv `[2560,1024]`).
**suspect : le chemin HMX pour k=4096** (n_dot_tiles = 4096/32 = 128). `OPFILTER=MUL_MAT` (matmuls→CPU)
supprime le crash, confirmant que c'est bien un MUL_MAT HMX.
## Instrumentation — ce qui marche / ne marche pas sur ce device (non-rooté)
-**FARF DSP** : bloqué. Le watcher cherche `<process>.farf` dans `/vendor/lib/rfsa/adsp` (lecture seule
sans root). `FARF(RUNTIME_HIGH)` ne sort pas en logcat sans ce fichier. `getenv` ne marche PAS côté skel DSP.
-**GGML_HEXAGON_VERBOSE** : utilise `GGML_LOG_DEBUG`, masqué par le cli.
-**OPLOG CPU-side** (committé) : `enqueue_op` (ggml-hexagon.cpp) logge en `fprintf(stderr)` chaque op
(type, dims, type/dims src, max-abs activation f32), gaté par `GGML_HEXAGON_OPLOG`. Combiné à
`GGML_HEXAGON_OPBATCH=1` (1 op par flush), le **dernier KZOP avant le 0x2e = l'op fautive**.
- Build debug FARF dispo si besoin : `cmake -DHEXAGON_HTP_DEBUG=ON` dans le sous-build htp-vNN → active
`FARF_HIGH=1` (logs par-matmul m/k/n), mais inutile ici faute de capture (root).
## Avancement (27/05, session chantier)
Hypothèses **éliminées** par mesure (OPLOG + isolation) :
- ❌ Overflow fp16 d'entrée : activation du matmul fautif amax=**9.8** (≪ 65504). Clamp ±65504 = sans effet.
- ❌ Overflow d'accumulation : scaling ÷32 puis ÷256 (×S en sortie) = sans effet.
- ❌ NaN/Inf dans les inputs : instrumentation `nan=0 inf=0` sur tous les ops avant le crash (le `amax` initial
ratait les NaN car `v>amax` est faux sur NaN ; corrigé pour les détecter → toujours 0).
- ❌ Shape pure : le matmul isolé (test-backend-ops, q4_0 k=4096 n=38/512/16 m=2560, **données synthétiques**)
**ne crashe PAS** sur HTP (finit, finite). Donc ce n'est pas la shape seule.
**Op fautive confirmée** : le **o_proj** `MUL_MAT q4_0 [4096,2560] × f32[4096,38]` (k=4096) juste après FLASH_ATTN_EXT
(en OPBATCH=1, le flush de ce matmul survient juste après le log de l'ADD suivant → crash). Inputs propres.
**Conclusion** : crash **value/context-spécifique** — un pattern de valeurs réelles (non reproduit par du random
synthétique), OU un état de contexte/VTCM/session hérité des ops précédentes (FA), qui fait fauter le MAC HMX.
## Avancement #2 (dump-and-replay) — signature NON-DÉTERMINISME / mémoire non-initialisée
- **Replay** : l'activation réelle du o_proj injectée dans le matmul isolé (test-backend-ops, poids q4_0
aléatoire) → **HTP0=NaN** en sortie (vs random→fini), MAIS **pas de crash** en isolation. Donc le matmul
ne crashe pas seul ; il PRODUIT/propage un NaN à partir de cette activation, et le crash 0x2e est en aval.
- **Analyse de l'activation capturée** (`kz_act.bin`, f32 [4096,38]) : **94% de zéros + 8987 inf**.
MAIS un run OPLOG précédent du MÊME op disait `inf=0 amax=9.8`. → **non-déterminisme** (le prefill HTP est
non-déterministe, déjà observé : 1er token 271 vs 15 selon les runs) ET/OU **capture peu fiable** si le
src1 du o_proj est une **view non-contiguë** (lire `ggml_nbytes` octets attrape de la mémoire non-init).
- **OPFILTER=FLASH_ATTN_EXT** (FA→CPU) : **ne stoppe PAS** le crash. Donc le inf ne vient pas (que) de FA.
- **Seuil** : n≤32 (~12 mots, 1 tuile N) OK ; n>32 (~16 mots, 2 tuiles) crashe. Mais le synthétique n=38
(2 tuiles) ne crashe pas → ce n'est pas le multi-tuile seul.
**Faisceau d'indices → mémoire non-initialisée lue par le chemin HTP** (la non-déterminisme en est la
signature classique ; le « 94% zéros + qq inf » ressemble à un buffer partiellement écrit). Candidats :
staging activation/poids VTCM non zéro-paddé pour n>32 multi-tuiles, ou un buffer de sortie/accumulateur
réutilisé sans reset entre ops dans la séquence du modèle (absent en isolation synthétique).
## Avancement #3 — uninit VTCM matmul ÉCARTÉ
Zéro-init de TOUS les buffers VTCM du matmul q4_0 (`vtcm_weight/activation/output/scratch0/1/2`) avant le
compute → **ne stoppe PAS le crash**. Donc l'uninit (si c'en est) n'est PAS dans le staging du matmul fautif :
le inf non-déterministe vient d'**en amont** (FA, KV cache, ou un autre op de la séquence), hors d'atteinte
des sondes CPU-side. Récap des hypothèses éliminées : magnitude, NaN/inf-input(1 run), shape-pure (synthétique),
FA→CPU, uninit-VTCM-matmul. Reste : source non-déterministe amont du inf (instrumentation DSP requise).
## Avancement #4 — 0x2e DÉCODÉ : c'est un reset CDSP, pas une corruption de données (RECADRE)
Connaissance retrouvée dans les briques archivées (`to_delete/qwen35-lab/docs/25_mega_kernel_htp.md`,
`to_delete/briques_qwen35/skel/qwen35-forward-ops.c:777`) :
- **`0x0000002e` = `AEE_ECONNRESET`** : le **watchdog du sous-système CDSP reset la connexion FastRPC**.
Côté DSP, l'op a **fauté** (accès mémoire invalide) ou **dépassé son budget temps** ; le host ne voit que
la connexion coupée. **Ce n'est PAS une erreur de valeurs.** (Caveat : briques = leur propre skel mega-kernel,
pas le HTP officiel ; la sémantique du code transfère, la cause précise des briques non.)
- **Recadrage** : mes hypothèses overflow/NaN/uninit cherchaient une corruption *numérique* → mauvaise catégorie.
Le `inf`/94%-zéros capturé host-side = **conséquence** (host lit un buffer que le DSP n'a pas fini d'écrire),
pas la cause → **explique le non-déterminisme** observé.
- **Meilleure hypothèse mécaniste** : le chemin HMX de l'o_proj (k=4096, post-FA) déclenche un **accès mémoire DSP
invalide** (DMA/cache op sur buffer mal mappé/aliasé propre à ce k) → fault CDSP → reset → 0x2e. Cohérent avec
HMX-only, k=4096-only, post-FA, non-det host, et le synthétique isolé qui ne crashe pas (état de buffers différent).
- **Piste log-to-host-buffer = quasi-morte** : les briques l'ont déjà tentée (progress tracking 16-byte header) →
après le crash le cache DSP n'est pas flushé, le host lit `0xffffffff`. Le contournement que j'envisageais échoue.
- Survie au lieu de fix : `GGML_HEXAGON_ABORT_ON_DSP_ERR=0` (flush non-fatal) existe côté backend — survit au
reset mais ne corrige pas la sortie (garbage). Inutile pour produire du correct.
**device rooté (capture FARF DSP-side) = seule voie réaliste**, désormais étayé par l'échec prouvé du log-to-host.
## Limite atteinte (honnête)
Le root cause est un **fault HMX non-déterministe** (mémoire non-init probable) dans la région o_proj/FA à n>32,
qui ne se reproduit pas en isolation synthétique. Le **pinner exactement demande de l'instrumentation côté DSP**
(dump des buffers VTCM/activation DANS le skel, au moment du MAC) — **bloquée sans root** (FARF inaccessible,
getenv inopérant côté skel). C'est du debug niveau backend Qualcomm. Outils CPU-side (OPLOG/DUMP) committés et
réutilisables ; la suite nécessite soit un device rooté (capture FARF), soit un log-vers-buffer-hôte ajouté au skel.
## Prochaines étapes (révisées après l'avancement ci-dessus)
1. **Dump-and-replay** (tranche value-vs-context) : capturer la vraie activation `[4096,38]` + le poids q4_0
du o_proj fautif (CPU-side dans `enqueue_op`, écrire en fichier), puis rejouer EXACTEMENT ces octets dans un
harness qui lance `hmx_matmul_q_f32` sur HTP. Crash en replay → **value-pattern** (pattern de bits précis
qui faute le MAC/dequant) ; pas de crash → **context/état** (VTCM/session de la séquence d'ops, ex. FA avant).
2. **Si value-pattern** : trouver l'octet/valeur déclencheur (bisection sur les lignes de l'activation), inspecter
le dequant q4_0→fp16 (`dequantize_x4x2_*`) et le MAC (`core_dot_chunk_fp16`, `Q6_mxmem_*`) sur ce pattern.
3. **Si context** : instrumenter l'état VTCM/session avant le matmul (reset explicite entre ops ? interaction FA→matmul).
4. **Instrumentation DSP** (si nécessaire, FARF bloqué sans root) : ajouter dans le skel un **log-vers-buffer-hôte**
(écrire diagnostics dans une zone VTCM/DDR que le CPU relit) — contourne l'absence de capture FARF.
5. **Fallback pragmatique** si le fix est trop lourd : gater le HMX pour cette classe de matmul → HVX (comme le
gate SSM_CONV). Mais le dense est déjà 100% HVX (USE_HMX=0) côté JNI, donc le fallback = statu quo.
⚠ L'oracle `test-backend-ops -o MUL_MAT` a des FAIL `CPU=nan` PRÉ-EXISTANTS (réf CPU NaN sur petits q4_0) — non
fiable comme gate ; valider par la cohérence de génération + l'absence de crash, pas par cet oracle.
## État
**Non-bloquant** : le dense tourne déjà sur HTP en **HVX** (~40 prefill, stable, cohérent) via le routage
auto du JNI (`USE_HMX=0` pour le dense). Ce chantier = optimisation (40→295). 3.5 intacte (HMX + option C).
Outils committés : OPLOG (`GGML_HEXAGON_OPLOG`, dormant par défaut).