7.8 KiB
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>.farfdans/vendor/lib/rfsa/adsp(lecture seule sans root).FARF(RUNTIME_HIGH)ne sort pas en logcat sans ce fichier.getenvne 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 enfprintf(stderr)chaque op (type, dims, type/dims src, max-abs activation f32), gaté parGGML_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=ONdans le sous-build htp-vNN → activeFARF_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=0sur tous les ops avant le crash (leamaxinitial ratait les NaN carv>amaxest 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 disaitinf=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ë (lireggml_nbytesoctets 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).
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)
- Dump-and-replay (tranche value-vs-context) : capturer la vraie activation
[4096,38]+ le poids q4_0 du o_proj fautif (CPU-side dansenqueue_op, écrire en fichier), puis rejouer EXACTEMENT ces octets dans un harness qui lancehmx_matmul_q_f32sur 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). - 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. - Si context : instrumenter l'état VTCM/session avant le matmul (reset explicite entre ops ? interaction FA→matmul).
- 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.
- 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).