127 lines
10 KiB
Markdown
127 lines
10 KiB
Markdown
# 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).
|