unicorn who dev commited on
Commit
dee7f43
·
verified ·
1 Parent(s): dee2a0f

Complete FireViewer Android learning SDK, source and verification records

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +29 -0
  2. android/DetectorTargets.kt +18 -0
  3. android/FireDetectorRuntimeCheck.java +44 -0
  4. android/OnDeviceLearning.kt +121 -0
  5. android/README.md +60 -0
  6. android/TrainingRuntimeCheck.java +51 -0
  7. android/prepare_runtime_check.py +26 -0
  8. conversion/README.md +33 -0
  9. conversion/build_trainable_detector.py +142 -0
  10. conversion/build_trainable_fire_multitask.py +91 -0
  11. conversion/check_dfine_final.py +15 -0
  12. conversion/check_dinov3_source_parity.py +19 -0
  13. conversion/check_fire_runtime216.py +30 -0
  14. conversion/convert_dfine_final.py +25 -0
  15. conversion/crop_dfine_outputs.py +20 -0
  16. conversion/dfine-arena-protection.json +36 -0
  17. conversion/dynamic_hf_worker.py +101 -0
  18. conversion/dynamic_lowerings.py +161 -0
  19. conversion/finish_fire_backbone.py +61 -0
  20. conversion/merge_training_graph.py +118 -0
  21. conversion/package/model_manifest.json +29 -0
  22. conversion/package/scripts/convert_batch2.py +335 -0
  23. conversion/patch_onnx2tf_gather_signature.py +19 -0
  24. conversion/prefetch_manifest.json +40 -0
  25. conversion/prepare_learning_backbone.py +17 -0
  26. conversion/protect_dfine_arena.py +36 -0
  27. conversion/publish_learning.py +82 -0
  28. conversion/static_topk_lowering.py +35 -0
  29. models/fireviewer_dinov3_pointing_pilot_v1_learning/README.md +1 -1
  30. models/fireviewer_dinov3_pointing_pilot_v1_learning/artifact_manifest.json +1 -1
  31. models/fireviewer_rfdetr_medium_v110_learning/README.md +1 -1
  32. models/fireviewer_rfdetr_medium_v110_learning/artifact_manifest.json +1 -1
  33. models/fireviewer_rtdetr_v2_r50_learning/README.md +1 -1
  34. models/fireviewer_rtdetr_v2_r50_learning/artifact_manifest.json +1 -1
  35. reports/android/fire-build-final.log +3 -0
  36. reports/android/fireviewer_yolo11m_strict_v1-final.log +25 -0
  37. reports/android/fireviewer_yolo11m_strict_v1.log +25 -0
  38. reports/android/runtime-libraries.json +12 -0
  39. reports/environments/litert-environment.txt +108 -0
  40. reports/environments/mm-environment.txt +68 -0
  41. reports/environments/onnx-environment.txt +43 -0
  42. reports/environments/runtime-environment.json +14 -0
  43. reports/environments/tf-current-freeze.txt +52 -0
  44. reports/environments/tf-probe-freeze.txt +49 -0
  45. reports/environments/tf216-freeze.txt +37 -0
  46. reports/environments/tf219-freeze.txt +49 -0
  47. reports/fireviewer-completion.json +169 -0
  48. reports/fireviewer_dfine_m_strict_v1/publication_receipt.json +8 -0
  49. reports/fireviewer_dfine_m_strict_v1/runtime216_validation.json +11 -0
  50. reports/fireviewer_dfine_m_strict_v1/source.json +25 -0
README.md ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: tensorflow-lite
3
+ license: other
4
+ tags:
5
+ - android
6
+ - on-device-training
7
+ - fire-detection
8
+ ---
9
+
10
+ # Modèles FireViewer pour Android
11
+
12
+ Modèles avec dimensions d’image variables et apprentissage local des têtes d’adaptation. Les réseaux visuels restent figés. Les checkpoints contiennent les paramètres, les moments SGD et le compteur d’étapes pour reprendre et continuer l’apprentissage.
13
+
14
+ | Modèle | Livraison | Apprentissage et reprise sur hôte 2.16.1 | Exécution Android |
15
+ |---|---|---|---|
16
+ | FireViewer D-FINE-M strict v1 | [modèle et contrat](models/fireviewer_dfine_m_strict_v1_learning) | validés | pas de test de ce modèle sur appareil |
17
+ | FireViewer RT-DETRv2-R50 | [modèle et contrat](models/fireviewer_rtdetr_v2_r50_learning) | validés | pas de test de ce modèle sur appareil |
18
+ | FireViewer YOLO11-M strict v1 | [modèle et contrat](models/fireviewer_yolo11m_strict_v1_learning) | validés | validée sur émulateur x86_64 |
19
+ | FireViewer RF-DETR Medium 1.10 strict v1 | [modèle et contrat](models/fireviewer_rfdetr_medium_v110_learning) | validés | pas de test de ce modèle sur appareil |
20
+ | FireViewer DINOv3 pointing pilot v1 | [modèle et contrat](models/fireviewer_dinov3_pointing_pilot_v1_learning) | validés | pas de test de ce modèle sur appareil |
21
+ | FireViewer SegFormer-B2 baseline v1 | source optionnelle introuvable | — | — |
22
+
23
+ Les fichiers de configuration précisent la normalisation, les labels et les sorties. YOLO accepte H/W variables par multiples de 32. Les autres modèles redimensionnent dans le graphe vers leur grille interne ; le dataset peut contenir des images de dimensions différentes.
24
+
25
+ - [SDK Kotlin et guide d’apprentissage continu](android/README.md)
26
+ - [Rapport de livraison et empreintes vérifiées](reports/fireviewer-completion.json)
27
+ - [Sources de conversion](conversion)
28
+
29
+ Les essais utilisent des annotations synthétiques pour vérifier les mises à jour et leur persistance. Ils ne mesurent pas la précision de détection ni l’oubli catastrophique. Aucun téléphone ARM physique n’a été testé. Les notices et conditions des modèles sources sont conservées dans chaque dossier.
android/DetectorTargets.kt ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package org.fireviewer.litert.training
2
+
3
+ /** Coordinates refer to the normalized image supplied to the model. */
4
+ data class ReviewedBox(val x1: Float, val y1: Float, val x2: Float, val y2: Float, val label: Int)
5
+
6
+ fun detectorTargets(boxes: List<ReviewedBox>, classCount: Int): FloatArray {
7
+ require(classCount > 0 && boxes.size <= 100)
8
+ val values = FloatArray(600)
9
+ boxes.forEachIndexed { index, box ->
10
+ require(listOf(box.x1, box.y1, box.x2, box.y2).all { it.isFinite() && it in 0f..1f })
11
+ require(box.x1 < box.x2 && box.y1 < box.y2 && box.label in 0 until classCount)
12
+ val row = index * 6
13
+ values[row] = box.x1; values[row + 1] = box.y1
14
+ values[row + 2] = box.x2; values[row + 3] = box.y2
15
+ values[row + 4] = box.label.toFloat(); values[row + 5] = 1f
16
+ }
17
+ return values
18
+ }
android/FireDetectorRuntimeCheck.java ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import java.io.File;
2
+ import java.util.Arrays;
3
+ import java.util.Map;
4
+ import org.fireviewer.litert.training.OnDeviceLearning;
5
+
6
+ /** Real Android CPU exercise: two image sizes, train, durable save and optimizer resume. */
7
+ public final class FireDetectorRuntimeCheck {
8
+ public static void main(String[] args) {
9
+ File model=new File(args[0]), root=new File(args[1]);
10
+ int threads=args.length>2?Integer.parseInt(args[2]):2;
11
+ String schema="0000000000000000000000000000000000000000000000000000000000000000";
12
+ Map<String,Object> square=TrainingRuntimeCheck.inputs("x",TrainingRuntimeCheck.image(128,128));
13
+ Map<String,Object> rectangle=TrainingRuntimeCheck.inputs("x",TrainingRuntimeCheck.image(128,192));
14
+ float[] target=new float[600];
15
+ target[0]=.3f;target[1]=.3f;target[2]=.7f;target[3]=.7f;target[4]=0;target[5]=1;
16
+ File checkpoint;float[] trained,continued;float firstLoss,secondLoss;
17
+ System.out.println("FIRE_CHECK opening "+model.getName());
18
+ try(OnDeviceLearning session=new OnDeviceLearning(model,schema,threads)) {
19
+ System.out.println("FIRE_CHECK opened");
20
+ float[] original=session.infer(square).get("detections").getValues();
21
+ System.out.println("FIRE_CHECK infer "+original.length);
22
+ firstLoss=session.train(square,target,.001f);
23
+ trained=session.infer(square).get("detections").getValues();
24
+ TrainingRuntimeCheck.check(!Arrays.equals(original,trained),"Training did not change outputs");
25
+ checkpoint=session.save(root,"synthetic-android-runtime-fixture",1L);
26
+ System.out.println("FIRE_CHECK saved loss="+firstLoss);
27
+ secondLoss=session.train(rectangle,target,.001f);
28
+ continued=session.infer(rectangle).get("detections").getValues();
29
+ System.out.println("FIRE_CHECK continued loss="+secondLoss);
30
+ }
31
+ try(OnDeviceLearning resumed=new OnDeviceLearning(model,schema,threads)) {
32
+ resumed.restore(checkpoint);
33
+ TrainingRuntimeCheck.check(Arrays.equals(trained,resumed.infer(square).get("detections").getValues()),"Restored predictions differ");
34
+ resumed.train(rectangle,target,.001f);
35
+ TrainingRuntimeCheck.check(Arrays.equals(continued,resumed.infer(rectangle).get("detections").getValues()),"Resumed optimizer differs");
36
+ }
37
+ try(OnDeviceLearning invalid=new OnDeviceLearning(model,"1000000000000000000000000000000000000000000000000000000000000000",threads)) {
38
+ boolean rejected=false;
39
+ try { invalid.restore(checkpoint); } catch(IllegalArgumentException expected) { rejected=true; }
40
+ TrainingRuntimeCheck.check(rejected,"Incompatible class schema accepted");
41
+ }
42
+ System.out.println("ANDROID_FIRE_TRAINING_PASS {\"runtime\":\""+org.tensorflow.lite.TensorFlowLite.runtimeVersion()+"\",\"mixed_shapes\":[[128,128],[128,192]],\"train\":true,\"restore_exact\":true,\"optimizer_resume_exact\":true,\"incompatible_schema_rejected\":true,\"first_loss\":"+firstLoss+",\"second_loss\":"+secondLoss+"}");
43
+ }
44
+ }
android/OnDeviceLearning.kt ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package org.fireviewer.litert.training
2
+
3
+ import android.util.AtomicFile
4
+ import org.json.JSONObject
5
+ import org.tensorflow.lite.Interpreter
6
+ import java.io.File
7
+ import java.nio.ByteBuffer
8
+ import java.nio.ByteOrder
9
+ import java.security.MessageDigest
10
+ import java.util.UUID
11
+
12
+ /** CPU training and durable optimizer resume; call from a background dispatcher.
13
+ * One session is serialized. Only reviewed annotations belong in targets.
14
+ * The model's contract states which parameters are trainable.
15
+ */
16
+ class OnDeviceLearning(
17
+ private val model: File,
18
+ private val labelSchemaSha256: String,
19
+ threads: Int = 2
20
+ ) : AutoCloseable {
21
+ private val modelSha256 = sha256(model)
22
+ private val engine = Interpreter(model, Interpreter.Options().setNumThreads(threads).setUseXNNPACK(false))
23
+ init {
24
+ require(threads in 1..8)
25
+ require(labelSchemaSha256.matches(Regex("[0-9a-f]{64}")))
26
+ require(engine.signatureKeys.toSet().containsAll(setOf("train", "infer", "save", "restore")))
27
+ }
28
+
29
+ data class Output(val shape: List<Int>, val values: FloatArray)
30
+
31
+ /** Nested primitive arrays carry their current shape to runSignature.
32
+ * Image models accept x as Array<Array<Array<FloatArray>>> in NCHW order.
33
+ * Pixels must already use the normalization from android_model_config.json.
34
+ */
35
+ @Synchronized fun infer(inputs: Map<String, Any>): Map<String, Output> {
36
+ engine.runSignature(inputs, mutableMapOf(), "infer")
37
+ return engine.getSignatureOutputs("infer").associateWith { name ->
38
+ val tensor = engine.getOutputTensorFromSignature(name, "infer")
39
+ require(tensor.dataType().name == "FLOAT32")
40
+ require(tensor.numBytes() <= 128 * 1024 * 1024)
41
+ val values = FloatArray(tensor.numElements())
42
+ tensor.asReadOnlyBuffer().order(ByteOrder.nativeOrder()).asFloatBuffer().get(values)
43
+ require(values.all { it.isFinite() })
44
+ Output(tensor.shape().toList(), values)
45
+ }
46
+ }
47
+
48
+ @Synchronized fun train(inputs: Map<String, Any>, target: FloatArray, learningRate: Float): Float {
49
+ require(learningRate.isFinite() && learningRate in .000001f..1f)
50
+ val tensor = engine.getInputTensorFromSignature("y", "train")
51
+ require(tensor.dataType().name == "FLOAT32" && target.size == tensor.numElements())
52
+ require(target.all { it.isFinite() })
53
+ val call = inputs.toMutableMap()
54
+ call["y"] = floats(target)
55
+ call["learning_rate"] = floats(floatArrayOf(learningRate))
56
+ engine.runSignature(call, mutableMapOf(), "train")
57
+ val loss = engine.getOutputTensorFromSignature("loss", "train")
58
+ .asReadOnlyBuffer().order(ByteOrder.nativeOrder()).getFloat(0)
59
+ require(loss.isFinite()) { "Non-finite loss; discard this candidate and restore its last checkpoint" }
60
+ return loss
61
+ }
62
+
63
+ /** The generation becomes current only after all checkpoint files are hashed.
64
+ * Previous generations stay available for rollback; this method never deletes them.
65
+ */
66
+ @Synchronized fun save(root: File, datasetRevision: String, reviewedSamplesSeen: Long): File {
67
+ require(reviewedSamplesSeen >= 0)
68
+ root.mkdirs()
69
+ val generation = File(root, UUID.randomUUID().toString()).apply { check(mkdir()) }
70
+ val prefix = File(generation, "weights")
71
+ engine.runSignature(mapOf("checkpoint_path" to prefix.absolutePath), mutableMapOf(), "save")
72
+ val files = generation.walkTopDown().filter { it.isFile }.toList()
73
+ require(files.isNotEmpty() && files.all { it.length() > 0 })
74
+ val hashes = JSONObject()
75
+ files.forEach { file -> hashes.put(file.relativeTo(generation).invariantSeparatorsPath, sha256(file)) }
76
+ val receipt = JSONObject().put("schema", 1).put("modelSha256", modelSha256)
77
+ .put("labelSchemaSha256", labelSchemaSha256).put("datasetRevision", datasetRevision)
78
+ .put("reviewedSamplesSeen", reviewedSamplesSeen).put("checkpointPrefix", "weights")
79
+ .put("files", hashes)
80
+ writeAtomic(File(generation, "receipt.json"), receipt.toString(2))
81
+ writeAtomic(File(root, "CURRENT"), generation.name)
82
+ return generation
83
+ }
84
+
85
+ @Synchronized fun restore(generation: File) {
86
+ val receipt = JSONObject(AtomicFile(File(generation, "receipt.json")).openRead().bufferedReader().use { it.readText() })
87
+ require(receipt.getInt("schema") == 1)
88
+ require(receipt.getString("modelSha256") == modelSha256) { "Checkpoint belongs to another model revision" }
89
+ require(receipt.getString("labelSchemaSha256") == labelSchemaSha256) { "Checkpoint label schema differs" }
90
+ val hashes = receipt.getJSONObject("files")
91
+ require(hashes.length() > 0)
92
+ hashes.keys().forEach { name ->
93
+ val file = File(generation, name)
94
+ require(file.canonicalPath.startsWith(generation.canonicalPath + File.separator))
95
+ require(file.isFile && sha256(file) == hashes.getString(name)) { "Checkpoint missing or changed" }
96
+ }
97
+ val prefix = File(generation, receipt.getString("checkpointPrefix"))
98
+ require(prefix.canonicalPath.startsWith(generation.canonicalPath + File.separator))
99
+ engine.runSignature(mapOf("checkpoint_path" to prefix.absolutePath), mutableMapOf(), "restore")
100
+ }
101
+
102
+ @Synchronized override fun close() = engine.close()
103
+
104
+ companion object {
105
+ private fun floats(values: FloatArray) = ByteBuffer.allocateDirect(values.size * 4)
106
+ .order(ByteOrder.nativeOrder()).apply { asFloatBuffer().put(values) }
107
+ private fun sha256(file: File): String {
108
+ val digest = MessageDigest.getInstance("SHA-256")
109
+ file.inputStream().use { stream ->
110
+ val buffer = ByteArray(1024 * 1024)
111
+ while (true) { val count = stream.read(buffer); if (count < 0) break; digest.update(buffer, 0, count) }
112
+ }
113
+ return digest.digest().joinToString("") { "%02x".format(it) }
114
+ }
115
+ private fun writeAtomic(file: File, text: String) {
116
+ val atomic = AtomicFile(file); val stream = atomic.startWrite()
117
+ try { stream.write(text.toByteArray(Charsets.UTF_8)); atomic.finishWrite(stream) }
118
+ catch (error: Exception) { atomic.failWrite(stream); throw error }
119
+ }
120
+ }
121
+ }
android/README.md ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Apprentissage FireViewer sur Android
2
+
3
+ Les fichiers `models/*_learning/model.tflite` contiennent les signatures exécutables `infer`, `train`, `save` et `restore`. Les têtes d'adaptation sont entraînables localement ; les réseaux visuels restent figés. Le modèle livré démarre avec ses poids source et une adaptation identité, sans les mises à jour synthétiques utilisées pour les tests.
4
+
5
+ Utiliser les deux dépendances de même version :
6
+
7
+ ```kotlin
8
+ implementation("org.tensorflow:tensorflow-lite:2.16.1")
9
+ implementation("org.tensorflow:tensorflow-lite-select-tf-ops:2.16.1")
10
+ ```
11
+
12
+ `OnDeviceLearning.kt` fournit les appels, la sauvegarde atomique, les empreintes SHA-256 et la vérification des checkpoints. Exécuter ces opérations dans un travail de fond avec CPU, XNNPACK désactivé. Le mélange LiteRT 2.2 / Select TF Ops 2.16 n'est pas la configuration qualifiée. Les résultats Android exacts figurent dans `reports/fireviewer-completion.json` et les journaux joints ; les essais hôte sont séparés des essais Android. Un émulateur ne mesure pas la vitesse sur téléphone ARM.
13
+
14
+ ## Images et cibles de détection
15
+
16
+ Chaque image est un tableau float `[1][3][H][W]` RGB. Appliquer la normalisation de `android_model_config.json`. Les images d'un même dataset peuvent avoir des tailles différentes et sont traitées individuellement. YOLO accepte des côtés multiples de 32. Les autres architectures redimensionnent dans le graphe vers leur grille interne ; les coordonnées de sortie sont rapportées à la taille de l'entrée `x`.
17
+
18
+ Pour les détecteurs, `y` contient 100 lignes `[x1,y1,x2,y2,classe,présente]`, aplaties en 600 floats. Les coordonnées sont normalisées dans l'image présentée au modèle, après prétraitement. `classe` indexe les labels du contrat. `présente=1` indique une annotation validée ; les lignes inutilisées sont nulles. Une image explicitement négative utilise toutes les lignes nulles. Une image sans annotation connue doit être exclue de l'apprentissage. `DetectorTargets.kt` construit et vérifie ce tableau.
19
+
20
+ ```kotlin
21
+ val imageInputs: Map<String, Any> = mapOf("x" to nchwImage)
22
+ OnDeviceLearning(modelFile, labelSchemaSha256, threads = 1).use { session ->
23
+ if (previousGeneration != null) session.restore(previousGeneration)
24
+ val loss = session.train(imageInputs, reviewedTargets, learningRate = 0.001f)
25
+ val predictions = session.infer(imageInputs)
26
+ val generation = session.save(checkpointRoot, datasetRevision, reviewedSamplesSeen)
27
+ }
28
+ ```
29
+
30
+ Les adaptations modifient les scores des classes et les boîtes des propositions disponibles. Elles ne créent pas de propositions absentes du détecteur figé. Changer le nombre de classes nécessite de reconstruire la tête avec les scripts fournis.
31
+
32
+ ## DINOv3 : quatre tâches
33
+
34
+ `infer` renvoie `segmentation_logits`, `point_logits`, `presence_logits` et `abstention_logits`. Appliquer une sigmoïde pour obtenir les probabilités. Les cartes restent sur la grille 448 × 448 ; remettre les cartes et points à l'échelle de l'image d'origine.
35
+
36
+ Pour `train`, `y` est `[1,3,448,448]` : masque de segmentation, carte de pointage, puis masque des pixels valides. Redimensionner les masques binaires au plus proche voisin et préparer le pointage sur cette même grille. Fournir aussi dans `inputs` :
37
+
38
+ - `presence` : float `[1,2]`, ordre `flame_visible`, `smoke_visible`.
39
+ - `abstention` : float `[1]`.
40
+ - `supervision` : float `[4]`, ordre segmentation, pointage, abstention, présence ; 1 si la tâche est annotée, 0 sinon.
41
+
42
+ Passer ces entrées auxiliaires à `train` uniquement. Une absence d'annotation n'est pas un label négatif. Les adaptateurs de sortie des quatre tâches sont entraînables ; DINOv3 et le décodeur DPT restent figés.
43
+
44
+ ## Reprendre et continuer
45
+
46
+ Chaque génération sauvegarde les paramètres entraînables, les moments SGD et le compteur d'étapes. Son `receipt.json` contient l'empreinte du modèle, celle du schéma de labels, la révision du dataset, le nombre d'exemples vus et les empreintes des fichiers. `CURRENT` désigne la dernière génération complète. `restore` refuse un autre modèle, un autre schéma de labels ou un fichier modifié. Les anciennes générations permettent un retour arrière.
47
+
48
+ Conserver les annotations humaines, l'ordre ou la graine d'échantillonnage et un ensemble de rejeu mêlant anciens et nouveaux exemples. Reprendre cet ordre à partir du compteur sauvegardé. Les checkpoints ne remplacent pas ces données. Évaluer un candidat sur des images réservées avant de l'activer ; les tests d'exécution ne mesurent ni la précision ni l'oubli catastrophique. En cas de perte non finie, restaurer la dernière génération valide.
49
+
50
+ Les scripts et ce SDK sont livrés avec les modèles. Cette livraison ne modifie pas le code de l'application Android en cours de travail dans un autre dossier.
51
+
52
+ ## Reproduire le contrôle Android
53
+
54
+ Avec JDK 21, Kotlin 2.2.10 et Android SDK (plateforme 36, build-tools 36.0.0), définir `JAVA_HOME`, `KOTLIN_HOME` et `ANDROID_SDK_ROOT`, puis lancer `python3 prepare_runtime_check.py` dans ce dossier. Le script télécharge les AAR 2.16.1, compile le SDK et produit `training-check.jar` et `lib/` pour l'émulateur x86_64. Copier ces fichiers et le modèle dans `/data/local/tmp/vds-fire-training` avec `adb push`.
55
+
56
+ ```sh
57
+ adb shell 'CLASSPATH=/data/local/tmp/vds-fire-training/training-check.jar LD_LIBRARY_PATH=/data/local/tmp/vds-fire-training/lib:/system/lib64:/vendor/lib64 app_process /data/local/tmp/vds-fire-training FireDetectorRuntimeCheck /data/local/tmp/vds-fire-training/model.tflite /data/local/tmp/vds-fire-training/checkpoints 1 128'
58
+ ```
59
+
60
+ La dernière ligne `ANDROID_FIRE_TRAINING_PASS` signifie que toutes les assertions ont réussi. Ce programme utilise des cibles synthétiques et des checkpoints distincts ; il ne réécrit pas le fichier modèle. Le paramètre final fixe la hauteur de test, et la seconde image est rectangulaire. Le test final YOLO a été exécuté avec 64 et 96 pixels, en complément de la première version testée avec 128 et 192 pixels.
android/TrainingRuntimeCheck.java ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import java.io.File;
2
+ import java.util.Arrays;
3
+ import java.util.HashMap;
4
+ import java.util.Map;
5
+ import java.nio.ByteBuffer;
6
+ import java.nio.ByteOrder;
7
+ import org.tensorflow.lite.Interpreter;
8
+
9
+ /** Runs under Android app_process with the matched TensorFlow Lite 2.16.1 AARs. */
10
+ public final class TrainingRuntimeCheck {
11
+ static float[][][][] image(int h,int w) {
12
+ float[][][][] x=new float[1][3][h][w];
13
+ for(int c=0;c<3;c++)for(int y=0;y<h;y++)for(int k=0;k<w;k++)x[0][c][y][k]=(float)Math.sin(c+y*.07+k*.11);
14
+ return x;
15
+ }
16
+ static Map<String,Object> inputs(Object... values) {
17
+ Map<String,Object> out=new HashMap<>();for(int n=0;n<values.length;n+=2)out.put((String)values[n],values[n+1]);return out;
18
+ }
19
+ static float[] infer(Interpreter i,float[][][][] x,int count) {
20
+ float[][] scores=new float[1][count];i.runSignature(inputs("x",x),inputs("scores",scores),"infer");return scores[0];
21
+ }
22
+ static float train(Interpreter i,float[][][][] x,float[][] y) {
23
+ ByteBuffer rate=ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder());rate.putFloat(0,.0001f);
24
+ ByteBuffer loss=ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder());
25
+ i.runSignature(inputs("x",x,"y",y,"learning_rate",rate),inputs("loss",loss),"train");return loss.getFloat(0);
26
+ }
27
+ static void check(boolean okay,String message){if(!okay)throw new AssertionError(message);}
28
+ public static void main(String[] args) {
29
+ File model=new File(args[0]);String checkpoint=args[1];int classes=Integer.parseInt(args[2]);
30
+ Interpreter.Options options=new Interpreter.Options().setNumThreads(2).setUseXNNPACK(false);
31
+ float[][][][] x=image(224,224);float[][] target=new float[1][classes];target[0][7]=1;
32
+ float[] trained,continued;float first=0,last=0;
33
+ System.out.println("ANDROID_CHECK opening interpreter");
34
+ try(Interpreter i=new Interpreter(model,options)) {
35
+ System.out.println("ANDROID_CHECK interpreter open");
36
+ float[] original=infer(i,x,classes);
37
+ System.out.println("ANDROID_CHECK inference complete");
38
+ for(int n=0;n<6;n++){last=train(i,n%2==0?x:image(192,320),target);if(n==0)first=last;check(Float.isFinite(last),"loss nonfinite");System.out.println("ANDROID_CHECK step="+n+" loss="+last);}
39
+ trained=infer(i,x,classes);check(!Arrays.equals(original,trained),"weights did not alter output");
40
+ i.runSignature(inputs("checkpoint_path",checkpoint),new HashMap<>(),"save");
41
+ check(new File(checkpoint).length()>0,"empty checkpoint");
42
+ train(i,x,target);continued=infer(i,x,classes);
43
+ }
44
+ try(Interpreter restored=new Interpreter(model,options)) {
45
+ restored.runSignature(inputs("checkpoint_path",checkpoint),new HashMap<>(),"restore");
46
+ check(Arrays.equals(trained,infer(restored,x,classes)),"restore differs");
47
+ train(restored,x,target);check(Arrays.equals(continued,infer(restored,x,classes)),"optimizer resume differs");
48
+ }
49
+ System.out.println("ANDROID_TRAINING_PASS {\"model\":\""+model.getName()+"\",\"mixed_shapes\":[[224,224],[192,320]],\"first_loss\":"+first+",\"last_loss\":"+last+",\"restart_exact\":true,\"optimizer_resume_exact\":true,\"runtime\":\""+org.tensorflow.lite.TensorFlowLite.runtimeVersion()+"\",\"device\":\"Android emulator x86_64\"}");
50
+ }
51
+ }
android/prepare_runtime_check.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pathlib,zipfile,subprocess,os,urllib.request
2
+ root=pathlib.Path(__file__).resolve().parent
3
+ cache=pathlib.Path(os.environ.get('GRADLE_USER_HOME','/workspace/cache/gradle'))/'caches/modules-2/files-2.1'
4
+ java=pathlib.Path(os.environ.get('JAVA_HOME','/workspace/toolchains/jdk-21'))
5
+ android=pathlib.Path(os.environ.get('ANDROID_SDK_ROOT','/workspace/toolchains/android-sdk'))
6
+ kotlin=pathlib.Path(os.environ.get('KOTLIN_HOME','/workspace/toolchains/kotlin-2.2.10'))
7
+ abi=os.environ.get('VDS_ANDROID_ABI','x86_64')
8
+ env={**os.environ,'JAVA_HOME':str(java),'PATH':str(java/'bin')+':'+os.environ['PATH']}
9
+ jars=[]
10
+ dependencies=[('org.tensorflow','tensorflow-lite','2.16.1'),('org.tensorflow','tensorflow-lite-api','2.16.1'),('org.tensorflow','tensorflow-lite-select-tf-ops','2.16.1')]
11
+ for group,name,version in dependencies:
12
+ matches=list((cache/group/name/version).glob('*/*.aar'))
13
+ aar=matches[0] if matches else root/(name+'-'+version+'.aar')
14
+ if not aar.exists():urllib.request.urlretrieve(f'https://repo.maven.apache.org/maven2/{group.replace(".","/")}/{name}/{version}/{name}-{version}.aar',aar)
15
+ with zipfile.ZipFile(aar) as z:
16
+ jar=root/(name+'.jar');jar.write_bytes(z.read('classes.jar'));jars.append(str(jar))
17
+ for item in z.namelist():
18
+ if item.startswith('jni/'+abi+'/') and item.endswith('.so'):
19
+ lib=root/'lib'/pathlib.Path(item).name;lib.parent.mkdir(exist_ok=True);lib.write_bytes(z.read(item))
20
+ classes=root/'classes';classes.mkdir(exist_ok=True)
21
+ sdk=root/'on-device-learning.jar'
22
+ subprocess.run([str(kotlin/'bin/kotlinc'),*[str(p) for p in root.glob('*.kt')],'-cp',':'.join([str(android/'platforms/android-36/android.jar'),*jars]),'-d',str(sdk)],check=True,env=env)
23
+ jars.extend([str(sdk),str(kotlin/'lib/kotlin-stdlib.jar')])
24
+ subprocess.run([str(java/'bin/javac'),'-source','11','-target','11','-cp',':'.join(jars),'-d',str(classes),*[str(p) for p in root.glob('*.java')]],check=True,env=env)
25
+ subprocess.run([str(android/'build-tools/36.0.0/d8'),'--min-api','28','--output',str(root/'training-check.jar'),*[str(p) for p in classes.glob('*.class')],*jars],check=True,env=env)
26
+ print(root/'training-check.jar')
conversion/README.md ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Reconstruction des modèles FireViewer
2
+
3
+ Les artefacts publiés sont directement exécutables sur Android ; ces outils servent à reconstruire les conversions sur Linux. L'environnement de production des fichiers est enregistré dans `reports/environments/`. Les révisions HF immuables, empreintes, labels, grilles et normalisations sont dans chaque `runtime_contract.json` et `source.json`.
4
+
5
+ Le dossier de travail des scripts est `/workspace/vds-litert-batch2`. Copier le contenu de `conversion/` à cet emplacement et recréer les environnements Python avec les versions enregistrées. Les poids sources se placent sous `models/upstream/<key>/` à partir des dépôts HF et révisions des contrats. Utiliser l'authentification HF habituelle ; aucun secret n'est livré.
6
+
7
+ Routes utilisées :
8
+
9
+ | Modèle | Réseau figé | Apprentissage |
10
+ |---|---|---|
11
+ | YOLO11M | ONNX source, conversion H/W dynamiques dans `dynamic_hf_worker.py` | `build_trainable_detector.py` |
12
+ | RF-DETR Medium | adaptateur officiel dans `package/scripts/convert_batch2.py`, via `prepare_learning_backbone.py` | `build_trainable_detector.py` |
13
+ | RT-DETRv2 R50 | `finish_fire_backbone.py`, grille source 768 × 768, TopK natif et positions pré-calculées | `build_trainable_detector.py` |
14
+ | DINOv3 | adaptateur source dans `package/scripts/convert_batch2.py`, contrôle `check_dinov3_source_parity.py` | `build_trainable_fire_multitask.py` |
15
+ | D-FINE-M | `convert_dfine_final.py`, détails de conservation mémoire dans le rapport de parité | `build_trainable_detector.py` |
16
+
17
+ `merge_training_graph.py` partage les poids figés entre les signatures et ajoute le redimensionnement interne pour les grilles fixes. Les scripts de construction écrivent un modèle initialisé avec la tête identité puis exécutent séparément les mises à jour de test. Les poids entraînés sur ces exemples synthétiques ne sont pas enregistrés dans le fichier modèle de livraison.
18
+
19
+ Pour les détecteurs, copier le `source.json` de `reports/<key>/` dans `learning/<key>/source.json`, puis exécuter avec TensorFlow 2.19 :
20
+
21
+ ```sh
22
+ /opt/vds-tf/bin/python build_trainable_detector.py <key>
23
+ ```
24
+
25
+ Pour DINOv3 :
26
+
27
+ ```sh
28
+ /opt/vds-tf/bin/python build_trainable_fire_multitask.py
29
+ ```
30
+
31
+ `check_fire_runtime216.py <key> ...` exécute ensuite les mises à jour, checkpoints et reprises avec TensorFlow Lite 2.16.1 sur Linux, la version des dépendances Android. Les tests Android utilisent le SDK et le programme de `android/` ; ils sont identifiés séparément dans les rapports. Les tests de parité comparent les conversions aux modèles source avant toute adaptation. Ne pas publier une conversion qui échoue à ces contrôles.
32
+
33
+ Les scripts de publication de la session utilisent le fichier d'authentification local `/workspace/.secrets/hf_token`. Ce chemin est une configuration de l'exécution, pas un secret inclus dans les artefacts. Le dépôt cible conserve sa visibilité existante.
conversion/build_trainable_detector.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Frozen detector plus trainable candidate classification/box adaptation on Android.
2
+
3
+ The adaptation cannot generate proposals absent from the frozen detector. It is
4
+ not full detector fine-tuning. Targets are reviewed xyxy/class/mask annotations.
5
+ """
6
+ import os
7
+ os.environ.update(TF_CPP_MIN_LOG_LEVEL='2',TF_NUM_INTRAOP_THREADS='6',TF_NUM_INTEROP_THREADS='2')
8
+ import sys,json,hashlib
9
+ from pathlib import Path
10
+ import tensorflow as tf,numpy as np
11
+ from merge_training_graph import merge,dynamic_resize_backbone,freeze_auxiliary_inputs
12
+ root=Path('/workspace/vds-litert-batch2');key=sys.argv[1];out=root/'learning'/key;out.mkdir(parents=True,exist_ok=True)
13
+ source=json.loads((out/'source.json').read_text());C=source['class_count'];kind=source['kind']
14
+ class_indices=source.get('class_indices',list(range(C)))
15
+ specs=([tf.TensorSpec([1,C+4,None],tf.float32,name='features')] if kind=='yolo' else [tf.TensorSpec([1,None,4],tf.float32,name='features_boxes'),tf.TensorSpec([1,None,C],tf.float32,name='features_logits')])
16
+ if kind=='rtmdet':specs=[tf.TensorSpec([1,None,None,C+4],tf.float32,name=f'features_{stride}') for stride in (8,16,32)]
17
+ specs+=[tf.TensorSpec([1,3,None,None],tf.float32,name='image')]
18
+
19
+ class Adaptation(tf.Module):
20
+ def __init__(self):
21
+ super().__init__()
22
+ self.cw=tf.Variable(tf.eye(C),name='class_weight');self.cb=tf.Variable(tf.zeros([C]),name='class_bias')
23
+ self.bw=tf.Variable(tf.eye(4),name='box_weight');self.bb=tf.Variable(tf.zeros([4]),name='box_bias')
24
+ self.parameters=[self.cw,self.cb,self.bw,self.bb]
25
+ self.momentum=[tf.Variable(tf.zeros_like(v),name='momentum_'+v.name.split(':')[0]) for v in self.parameters]
26
+ self.step=tf.Variable(0,dtype=tf.int64,name='step');self.state=self.parameters+self.momentum+[self.step]
27
+ self.names=['class_weight','class_bias','box_weight','box_bias','momentum_class_weight','momentum_class_bias','momentum_box_weight','momentum_box_bias','step']
28
+ self.infer=tf.function(self._infer,input_signature=specs)
29
+ self.train=tf.function(self._train,input_signature=specs+[tf.TensorSpec([1,100,6],tf.float32,name='y'),tf.TensorSpec([],tf.float32,name='learning_rate')])
30
+ def predict(self,args):
31
+ image=args[-1];shape=tf.cast(tf.shape(image),tf.float32);scale=tf.stack([shape[3],shape[2],shape[3],shape[2]])
32
+ if kind=='yolo':
33
+ values=tf.transpose(args[0],[0,2,1]);boxes=values[:,:,:4]/scale
34
+ p=tf.clip_by_value(values[:,:,4:],1e-6,1-1e-6);logits=tf.math.log(p)-tf.math.log1p(-p)
35
+ elif kind=='rtmdet':
36
+ all_boxes=[];all_logits=[]
37
+ for feature,stride in zip(args[:3],(8,16,32)):
38
+ fs=tf.shape(feature);grid_x,grid_y=tf.meshgrid(tf.range(fs[2]),tf.range(fs[1]));grid=tf.cast(tf.stack([grid_x,grid_y],-1),tf.float32)*stride
39
+ d=feature[:,:,:,C:]*stride;lo=grid[None]-d[:,:,:,:2];hi=grid[None]+d[:,:,:,2:]
40
+ boxes=tf.concat([(lo+hi)/2,hi-lo],-1)/scale
41
+ all_boxes.append(tf.reshape(boxes,[1,-1,4]));all_logits.append(tf.reshape(feature[:,:,:,:C],[1,-1,C]))
42
+ boxes=tf.concat(all_boxes,1);logits=tf.concat(all_logits,1)
43
+ else:boxes,logits=args[:2]
44
+ return boxes@self.bw+self.bb,logits@self.cw+self.cb,scale
45
+ def _infer(self,*args):
46
+ boxes,logits,scale=self.predict(args)
47
+ if kind=='yolo':return {'detections':tf.transpose(tf.concat([boxes*scale,tf.sigmoid(logits)],-1),[0,2,1])}
48
+ probabilities=tf.nn.softmax(logits,axis=-1) if source.get('class_activation')=='softmax' else tf.sigmoid(logits)
49
+ probabilities=tf.gather(probabilities,class_indices,axis=-1)
50
+ return {'boxes':boxes,'logits':logits,'detections':tf.transpose(tf.concat([boxes*scale,probabilities],-1),[0,2,1])}
51
+ def _train(self,*args):
52
+ y,lr=args[-2:];targets=y[0];valid=targets[:,5];gt=targets[:,:4];labels=tf.gather(tf.constant(class_indices,tf.int32),tf.cast(targets[:,4],tf.int32))
53
+ with tf.GradientTape() as tape:
54
+ boxes,logits,_=self.predict(args[:-2]);b=boxes[0];scores=logits[0]
55
+ xyxy=tf.concat([b[:,:2]-b[:,2:]/2,b[:,:2]+b[:,2:]/2],-1)
56
+ lo=tf.maximum(xyxy[:,None,:2],gt[None,:,:2]);hi=tf.minimum(xyxy[:,None,2:],gt[None,:,2:])
57
+ inter=tf.reduce_prod(tf.maximum(hi-lo,0),-1)
58
+ area=tf.reduce_prod(tf.maximum(xyxy[:,2:]-xyxy[:,:2],0),-1)
59
+ ga=tf.reduce_prod(tf.maximum(gt[:,2:]-gt[:,:2],0),-1)
60
+ iou=inter/tf.maximum(area[:,None]+ga[None,:]-inter,1e-7)*valid[None,:]
61
+ # Assignment is discrete; gradients update the prediction, never the matching.
62
+ best_candidate=tf.argmax(iou,axis=0,output_type=tf.int32)
63
+ forced_pairs=tf.transpose(tf.one_hot(best_candidate,tf.shape(b)[0])*valid[:,None])
64
+ forced=tf.reduce_max(forced_pairs,axis=1)
65
+ # Assign a forced candidate to the annotation that selected it,
66
+ # including zero-IoU cases. Collisions keep the larger-IoU target.
67
+ best_target=tf.argmax(iou+forced_pairs*(1+iou),axis=1,output_type=tf.int32)
68
+ positive=tf.stop_gradient(tf.maximum(tf.cast(tf.reduce_max(iou,axis=1)>.5,tf.float32),forced))
69
+ assigned=tf.gather(gt,best_target);assigned_class=tf.gather(labels,best_target)
70
+ onehot=tf.one_hot(assigned_class,C)*positive[:,None]
71
+ if source.get('class_activation')=='softmax':
72
+ onehot+=tf.one_hot(source['background_index'],C)[None,:]*(1-positive[:,None])
73
+ ce=tf.nn.softmax_cross_entropy_with_logits(labels=onehot,logits=scores)
74
+ else:ce=tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=onehot,logits=scores),axis=-1)
75
+ cls=tf.reduce_sum(ce*positive)/tf.maximum(tf.reduce_sum(positive),1)+.05*tf.reduce_sum(ce*(1-positive))/tf.maximum(tf.reduce_sum(1-positive),1)
76
+ target_box=tf.concat([(assigned[:,:2]+assigned[:,2:])/2,assigned[:,2:]-assigned[:,:2]],-1)
77
+ geometry=tf.reduce_sum(tf.reduce_mean(tf.square(b-target_box),axis=-1)*positive)/tf.maximum(tf.reduce_sum(positive),1)
78
+ loss=cls+5*geometry
79
+ gradients=tape.gradient(loss,self.parameters)
80
+ gradients,_=tf.clip_by_global_norm(gradients,10.)
81
+ for v,m,g in zip(self.parameters,self.momentum,gradients):m.assign(.9*m+g);v.assign_sub(lr*m)
82
+ self.step.assign_add(1);return {'loss':loss,'step':self.step.read_value()}
83
+ @tf.function(input_signature=[tf.TensorSpec([],tf.string,name='checkpoint_path')])
84
+ def save(self,checkpoint_path):
85
+ tf.raw_ops.Save(filename=checkpoint_path,tensor_names=self.names,data=[v.read_value() for v in self.state]);return {'step':self.step.read_value()}
86
+ @tf.function(input_signature=[tf.TensorSpec([],tf.string,name='checkpoint_path')])
87
+ def restore(self,checkpoint_path):
88
+ for n,v in zip(self.names,self.state):v.assign(tf.raw_ops.Restore(file_pattern=checkpoint_path,tensor_name=n,dt=v.dtype))
89
+ return {'step':self.step.read_value()}
90
+ @tf.function(input_signature=[])
91
+ def training_state(self):return {'head_kernel':self.cw.read_value(),'step':self.step.read_value()}
92
+
93
+ module=Adaptation();names=['infer','train','save','restore','training_state'];saved=out/'head_saved_model'
94
+ tf.saved_model.save(module,str(saved),signatures={n:getattr(module,n).get_concrete_function() for n in names})
95
+ converter=tf.lite.TFLiteConverter.from_saved_model(str(saved));converter.target_spec.supported_ops=[tf.lite.OpsSet.TFLITE_BUILTINS,tf.lite.OpsSet.SELECT_TF_OPS];converter.experimental_enable_resource_variables=True
96
+ head=out/'head.tflite';head.write_bytes(converter.convert());artifact=out/'model.tflite';backbone=Path(source['backbone'])
97
+ if source.get('freeze_auxiliary_inputs'):
98
+ frozen=out/'frozen_inputs.tflite';freeze_auxiliary_inputs(backbone,frozen,source['freeze_auxiliary_inputs']);backbone=frozen
99
+ preprocessing={'mode':'native_dynamic','stride':32}
100
+ if not source.get('native_dynamic',True):
101
+ wrapped=out/'dynamic_backbone.tflite';preprocessing=dynamic_resize_backbone(backbone,wrapped);backbone=wrapped
102
+ merge(backbone,head,artifact,source['bindings'],image_size=source.get('app_size',320))
103
+ def load():
104
+ i=tf.lite.Interpreter(model_path=str(artifact),num_threads=6,experimental_op_resolver_type=tf.lite.experimental.OpResolverType.BUILTIN_WITHOUT_DEFAULT_DELEGATES)
105
+ return i,{n:i.get_signature_runner(n) for n in names}
106
+ i,s=load();h,w=source.get('test_size',[320,320]);x=np.random.default_rng(34).uniform(0,1,(1,3,h,w)).astype(np.float32)
107
+ before=s['infer'](x=x)
108
+ # Initial adaptation must preserve detector predictions.
109
+ base=tf.lite.Interpreter(model_path=str(backbone),num_threads=6,experimental_op_resolver_type=tf.lite.experimental.OpResolverType.BUILTIN_WITHOUT_DEFAULT_DELEGATES)
110
+ d=base.get_input_details()[0];base.resize_tensor_input(d['index'],x.shape,strict=True);base.allocate_tensors();base.set_tensor(d['index'],x);base.invoke()
111
+ raw=[base.get_tensor(d['index']) for d in base.get_output_details()]
112
+ if kind=='yolo':
113
+ np.testing.assert_allclose(before['detections'],raw[source['bindings']['features']],atol=1e-3,rtol=1e-3)
114
+ pred=before['detections'][0].T;index=np.argmax(pred[:,4:].max(-1));box=pred[index,:4]/np.array([w,h,w,h]);label=int(np.argmax(pred[index,4:]))
115
+ elif kind=='rtmdet':
116
+ bb=[];ss=[]
117
+ for stride in (8,16,32):
118
+ feature=raw[source['bindings'][f'features_{stride}']];yy,xx=np.mgrid[:feature.shape[1],:feature.shape[2]];grid=np.stack([xx,yy],-1)*stride
119
+ d=feature[:,:,:,C:]*stride;lo=grid[None]-d[:,:,:,:2];hi=grid[None]+d[:,:,:,2:]
120
+ bb.append((np.concatenate([(lo+hi)/2,hi-lo],-1)/np.array([w,h,w,h])).reshape(1,-1,4));ss.append(feature[:,:,:,:C].reshape(1,-1,C))
121
+ np.testing.assert_allclose(before['boxes'],np.concatenate(bb,1),atol=1e-4,rtol=1e-4);np.testing.assert_allclose(before['logits'],np.concatenate(ss,1),atol=1e-5,rtol=1e-5)
122
+ index=np.argmax(before['logits'][0].max(-1));box=before['boxes'][0,index];label=int(np.argmax(before['logits'][0,index]))
123
+ else:
124
+ np.testing.assert_allclose(before['boxes'],raw[source['bindings']['features_boxes']],atol=1e-5,rtol=1e-5)
125
+ np.testing.assert_allclose(before['logits'],raw[source['bindings']['features_logits']],atol=1e-5,rtol=1e-5)
126
+ candidates=before['logits'][0][:,class_indices];index=np.argmax(candidates.max(-1));box=before['boxes'][0,index];label=int(np.argmax(candidates[index]))
127
+ y=np.zeros((1,100,6),np.float32);y[0,0,:4]=np.clip(np.r_[box[:2]-box[2:]/2,box[:2]+box[2:]/2],0,1);y[0,0,4:]=[label,1]
128
+ losses=[]
129
+ for _ in range(8):losses.append(float(s['train'](x=x,y=y,learning_rate=np.float32(.005))['loss']))
130
+ assert np.isfinite(losses).all() and losses[-1]<losses[0],losses
131
+ after=s['infer'](x=x);assert any(not np.array_equal(before[n],after[n]) for n in before)
132
+ checkpoint=np.array(str(out/'validation_checkpoint'),dtype=np.bytes_);s['save'](checkpoint_path=checkpoint)
133
+ j,t=load();t['restore'](checkpoint_path=checkpoint)
134
+ for n,v in after.items():np.testing.assert_array_equal(v,t['infer'](x=x)[n])
135
+ shapes=[(h,w),(h,w+64),(h+64,w)]
136
+ for hh,ww in shapes:
137
+ xx=np.random.default_rng(77).uniform(0,1,(1,3,hh,ww)).astype(np.float32)
138
+ for runner in (s,t):runner['train'](x=xx,y=y,learning_rate=np.float32(.001))
139
+ a=s['infer'](x=xx);b=t['infer'](x=xx)
140
+ for n in a:np.testing.assert_array_equal(a[n],b[n]);assert np.isfinite(a[n]).all()
141
+ report={'key':key,'validation_passed':True,'training_scope':'candidate_classification_and_box_adaptation_only','backbone_frozen':True,'limitation':'Does not create proposals absent from frozen detector','initial_detector_parity':True,'parameters_changed':True,'initial_loss':losses[0],'final_loss':losses[-1],'mixed_image_shapes':shapes,'restart_logits_exact':True,'optimizer_resume_exact':True,'variable_image_preprocessing':preprocessing,'android_device_tested':False,'accuracy_evaluated':False,'sha256':hashlib.sha256(artifact.read_bytes()).hexdigest(),'bytes':artifact.stat().st_size,'signatures':i.get_signature_list()}
142
+ (out/'training_validation.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report),flush=True)
conversion/build_trainable_fire_multitask.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """DINOv3 pilot: mutable output adapters for all four original tasks.
2
+
3
+ Frozen DINOv3/DPT; preserves original logits at initialization. Partial labels
4
+ are explicit: missing supervision is never silently converted to a negative.
5
+ """
6
+ import os
7
+ os.environ.update(TF_CPP_MIN_LOG_LEVEL='2',TF_NUM_INTRAOP_THREADS='6',TF_NUM_INTEROP_THREADS='2')
8
+ import json,hashlib
9
+ from pathlib import Path
10
+ import tensorflow as tf,numpy as np
11
+ from merge_training_graph import merge,dynamic_resize_backbone
12
+ root=Path('/workspace/vds-litert-batch2');key='fireviewer_dinov3_pointing_pilot_v1';out=root/'learning'/key;out.mkdir(exist_ok=True)
13
+ S=448
14
+ names=['abstention_logits','point_logits','presence_logits','segmentation_logits']
15
+ shapes=[[1],[1,1,S,S],[1,2],[1,1,S,S]]
16
+ features=[tf.TensorSpec(shape,tf.float32,name='features_'+name) for name,shape in zip(names,shapes)]
17
+ class Head(tf.Module):
18
+ def __init__(self):
19
+ super().__init__()
20
+ self.scale=tf.Variable(tf.ones([4]),name='scale');self.bias=tf.Variable(tf.zeros([4]),name='bias')
21
+ self.pw=tf.Variable(tf.eye(2),name='presence_weight');self.pb=tf.Variable(tf.zeros([2]),name='presence_bias')
22
+ self.parameters=[self.scale,self.bias,self.pw,self.pb]
23
+ self.momentum=[tf.Variable(tf.zeros_like(v),name='momentum_'+v.name.split(':')[0]) for v in self.parameters]
24
+ self.step=tf.Variable(0,dtype=tf.int64,name='step');self.state=self.parameters+self.momentum+[self.step]
25
+ self.state_names=[v.name.split(':')[0] for v in self.state]
26
+ self.infer=tf.function(self._infer,input_signature=features)
27
+ self.train=tf.function(self._train,input_signature=features+[
28
+ tf.TensorSpec([1,3,S,S],tf.float32,name='y'),
29
+ tf.TensorSpec([1,2],tf.float32,name='presence'),
30
+ tf.TensorSpec([1],tf.float32,name='abstention'),
31
+ tf.TensorSpec([4],tf.float32,name='supervision'),
32
+ tf.TensorSpec([],tf.float32,name='learning_rate')])
33
+ def _infer(self,*f):
34
+ return {n:(v@self.pw+self.pb if n=='presence_logits' else v*self.scale[i]+self.bias[i]) for i,(n,v) in enumerate(zip(names,f))}
35
+ def _train(self,*args):
36
+ y,presence,abstention,supervision,rate=args[-5:]
37
+ with tf.GradientTape() as tape:
38
+ outputs=self._infer(*args[:-5]);valid=y[:,2:3]
39
+ spatial=[]
40
+ for name,target in [('segmentation_logits',y[:,0:1]),('point_logits',y[:,1:2])]:
41
+ loss=tf.nn.sigmoid_cross_entropy_with_logits(labels=target,logits=outputs[name])
42
+ spatial.append(tf.reduce_sum(loss*valid)/tf.maximum(tf.reduce_sum(valid),1.))
43
+ point,seg=spatial[1],spatial[0]
44
+ abst=tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=abstention,logits=outputs['abstention_logits']))
45
+ pres=tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=presence,logits=outputs['presence_logits']))
46
+ loss=tf.reduce_sum(tf.stack([seg,point,abst,pres])*supervision*tf.constant([1.,.5,.25,.25]))
47
+ gradients=tape.gradient(loss,self.parameters);gradients,_=tf.clip_by_global_norm(gradients,10.)
48
+ for v,m,g in zip(self.parameters,self.momentum,gradients):m.assign(.9*m+g);v.assign_sub(rate*m)
49
+ self.step.assign_add(1);return {'loss':loss,'step':self.step.read_value()}
50
+ @tf.function(input_signature=[tf.TensorSpec([],tf.string,name='checkpoint_path')])
51
+ def save(self,checkpoint_path):
52
+ tf.raw_ops.Save(filename=checkpoint_path,tensor_names=self.state_names,data=[v.read_value() for v in self.state]);return {'step':self.step.read_value()}
53
+ @tf.function(input_signature=[tf.TensorSpec([],tf.string,name='checkpoint_path')])
54
+ def restore(self,checkpoint_path):
55
+ for n,v in zip(self.state_names,self.state):v.assign(tf.raw_ops.Restore(file_pattern=checkpoint_path,tensor_name=n,dt=v.dtype))
56
+ return {'step':self.step.read_value()}
57
+ module=Head();signatures=['infer','train','save','restore'];saved=out/'head_saved_model'
58
+ tf.saved_model.save(module,str(saved),signatures={n:getattr(module,n).get_concrete_function() for n in signatures})
59
+ converter=tf.lite.TFLiteConverter.from_saved_model(str(saved));converter.target_spec.supported_ops=[tf.lite.OpsSet.TFLITE_BUILTINS,tf.lite.OpsSet.SELECT_TF_OPS];converter.experimental_enable_resource_variables=True
60
+ head=out/'head.tflite';head.write_bytes(converter.convert());base=root/'learning-backbones'/key/'model.tflite'
61
+ dynamic=out/'dynamic_backbone.tflite';preprocessing=dynamic_resize_backbone(base,dynamic)
62
+ artifact=out/'model.tflite';merge(dynamic,head,artifact,{'features_'+n:i for i,n in enumerate(names)},image_size=S)
63
+ def load():
64
+ i=tf.lite.Interpreter(model_path=str(artifact),num_threads=6,experimental_op_resolver_type=tf.lite.experimental.OpResolverType.BUILTIN_WITHOUT_DEFAULT_DELEGATES)
65
+ return i,{n:i.get_signature_runner(n) for n in signatures}
66
+ i,s=load();x=np.random.default_rng(94).normal(size=(1,3,S,S)).astype(np.float32);before=s['infer'](x=x)
67
+ reference=tf.lite.Interpreter(model_path=str(base),num_threads=6,experimental_op_resolver_type=tf.lite.experimental.OpResolverType.BUILTIN_WITHOUT_DEFAULT_DELEGATES);reference.allocate_tensors();reference.set_tensor(reference.get_input_details()[0]['index'],x);reference.invoke()
68
+ for name,d in zip(names,reference.get_output_details()):np.testing.assert_allclose(before[name],reference.get_tensor(d['index']),atol=2e-3,rtol=2e-3)
69
+ y=np.zeros((1,3,S,S),np.float32);y[:,2]=1;y[:,0,150:300,140:280]=1;y[:,1,210:230,200:220]=1
70
+ targets={'y':y,'presence':np.array([[1,0]],np.float32),'abstention':np.array([0],np.float32),'supervision':np.ones(4,np.float32)}
71
+ losses=[float(s['train'](x=x,**targets,learning_rate=np.float32(.01))['loss']) for _ in range(6)]
72
+ assert np.isfinite(losses).all() and losses[-1]<losses[0],losses
73
+ after=s['infer'](x=x);assert all(not np.array_equal(before[n],after[n]) for n in names)
74
+ checkpoint=np.array(str(out/'validation_checkpoint'),dtype=np.bytes_);s['save'](checkpoint_path=checkpoint)
75
+ j,t=load();t['restore'](checkpoint_path=checkpoint)
76
+ for n,v in after.items():np.testing.assert_array_equal(v,t['infer'](x=x)[n])
77
+ shapes=[(193,317),(321,257)]
78
+ for h,w in shapes:
79
+ xx=np.random.default_rng(4).normal(size=(1,3,h,w)).astype(np.float32)
80
+ for runner in (s,t):runner['train'](x=xx,**targets,learning_rate=np.float32(.001))
81
+ a=s['infer'](x=xx);b=t['infer'](x=xx)
82
+ for n in names:np.testing.assert_array_equal(a[n],b[n])
83
+ # Missing labels must not update an otherwise fresh adapter.
84
+ k,u=load();empty={**targets,'supervision':np.zeros(4,np.float32)};initial=u['infer'](x=x)
85
+ assert float(u['train'](x=x,**empty,learning_rate=np.float32(.01))['loss'])==0
86
+ for n,v in initial.items():np.testing.assert_array_equal(v,u['infer'](x=x)[n])
87
+ report={'key':key,'validation_passed':True,'training_scope':'four_task_output_adaptation_only','backbone_frozen':True,'limitation':'Frozen DINOv3 and DPT; only task-output calibration/mixing adapts. Pilot, private research use.','initial_detector_parity':True,'parameters_changed':True,'all_four_tasks_changed':True,'missing_supervision_no_update':True,'initial_loss':losses[0],'final_loss':losses[-1],'mixed_image_shapes':shapes,'restart_logits_exact':True,'optimizer_resume_exact':True,'variable_image_preprocessing':preprocessing,'android_device_tested':False,'accuracy_evaluated':False,'sha256':hashlib.sha256(artifact.read_bytes()).hexdigest(),'bytes':artifact.stat().st_size,'signatures':i.get_signature_list()}
88
+ (out/'training_validation.json').write_text(json.dumps(report,indent=2)+'\n')
89
+ training={'trainSignature':'train','inferSignature':'infer','saveSignature':'save','restoreSignature':'restore','imageInput':'x','targetInput':'y','lossOutput':'loss','checkpointInput':'checkpoint_path','learningRateInput':'learning_rate','inferOutputs':names,'targetEncoding':'segmentation_point_valid_mask_nchw','targetShape':[1,3,S,S],'auxiliaryTargets':{'presence':{'shape':[1,2],'labels':['flame_visible','smoke_visible']},'abstention':{'shape':[1]},'supervision':{'shape':[4],'order':['segmentation','point','abstention','presence'],'meaning':'1=reviewed label exists, 0=missing; not a negative label'}},'scope':report['training_scope']}
90
+ config={'task':'multitask','adapter':'fireviewer_dinov3_multitask','inputWidth':S,'inputHeight':S,'inputLayout':'NCHW','inputType':'FLOAT32','mean':0.,'std':1.,'channelMean':[123.675,116.28,103.53],'channelStd':[58.395,57.12,57.375],'resizeMode':'stretch','isRgb':True,'labels':['flame_visible','smoke_visible'],'outputLayout':'NCHW','scoreActivation':'sigmoid','dynamicMinSize':128,'dynamicMaxSize':1280,'dynamicStride':1,'training':training}
91
+ (out/'android_model_config.json').write_text(json.dumps(config,indent=2)+'\n');(out/'source.json').write_text(json.dumps({'key':key,'kind':'multitask','labels':config['labels'],'backbone':str(base),'private_pilot_only':True},indent=2)+'\n');print(json.dumps(report),flush=True)
conversion/check_dfine_final.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json,hashlib
2
+ from pathlib import Path
3
+ import onnxruntime as ort,numpy as np
4
+ from ai_edge_litert.interpreter import Interpreter,OpResolverType
5
+ r=Path('/workspace/vds-litert-batch2');d=r/'learning-backbones/fireviewer_dfine_m_strict_v1';p=d/'model-protected.tflite'
6
+ options=ort.SessionOptions();options.intra_op_num_threads=6
7
+ ref=ort.InferenceSession(str(d/'raw-onnx/raw-original.onnx'),sess_options=options,providers=['CPUExecutionProvider'])
8
+ i=Interpreter(model_path=str(p),num_threads=6,experimental_op_resolver_type=OpResolverType.BUILTIN_WITHOUT_DEFAULT_DELEGATES);i.allocate_tensors();errors=[]
9
+ for seed in (842,843,844):
10
+ x=np.random.default_rng(seed).uniform(0,1,(1,3,704,704)).astype(np.float32)
11
+ reference=ref.run(None,{'images':x});i.set_tensor(i.get_input_details()[0]['index'],x);i.invoke()
12
+ for expected,detail in zip(reference,i.get_output_details()):
13
+ actual=i.get_tensor(detail['index']);np.testing.assert_allclose(actual,expected,atol=2e-3,rtol=2e-3);errors.append(float(np.max(np.abs(actual-expected))))
14
+ report={'key':'fireviewer_dfine_m_strict_v1','phase':'validated_frozen_component','upstream_onnx_parity_passed':True,'backbone':str(p),'source_sha256':hashlib.sha256((r/'models/upstream/fireviewer_dfine_m_strict_v1/model.onnx').read_bytes()).hexdigest(),'sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'input_shape':[1,3,704,704],'outputs':[{'index':n,'shape':list(map(int,o['shape']))} for n,o in enumerate(i.get_output_details())],'max_abs_errors':errors,'seeds':[842,843,844],'atol':2e-3,'rtol':2e-3,'preserve_all_tensors':False,'arena_protection':json.loads((d/'arena-protection.json').read_text())}
15
+ (d/'parity.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report),flush=True)
conversion/check_dinov3_source_parity.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os,sys,json
2
+ from pathlib import Path
3
+ os.environ.update(VDS_BATCH2_ROOT='/workspace/vds-litert-batch2',HF_TOKEN_FILE='/workspace/.secrets/hf_token',TORCH_NUM_THREADS='6')
4
+ root=Path('/workspace/vds-litert-batch2');sys.path.insert(0,str(root/'package/scripts'))
5
+ import convert_batch2 as worker
6
+ import numpy as np,torch
7
+ from ai_edge_litert.interpreter import Interpreter,OpResolverType
8
+ torch.set_num_threads(6);key='fireviewer_dinov3_pointing_pilot_v1';out=root/'learning-backbones'/key
9
+ def check(module,args,path):
10
+ x=np.random.default_rng(94).normal(size=(1,3,448,448)).astype(np.float32)
11
+ with torch.no_grad():reference=[v.numpy() for v in module(torch.from_numpy(x))]
12
+ i=Interpreter(model_path=str(path),num_threads=6,experimental_op_resolver_type=OpResolverType.BUILTIN_WITHOUT_DEFAULT_DELEGATES);i.allocate_tensors();i.set_tensor(i.get_input_details()[0]['index'],x);i.invoke()
13
+ errors=[]
14
+ for r,d in zip(reference,i.get_output_details()):
15
+ a=i.get_tensor(d['index']);np.testing.assert_allclose(a,r,atol=2e-3,rtol=2e-3);errors.append(float(np.max(np.abs(a-r))))
16
+ report={'source_pytorch_parity_passed':True,'outputs':['abstention_logits','point_logits','presence_logits','segmentation_logits'],'max_abs_errors':errors,'atol':2e-3,'rtol':2e-3}
17
+ (out/'parity.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report));return report
18
+ worker.direct_export=check
19
+ spec=next(s for s in worker.all_specs() if s['key']==key);worker.convert_dinov3_fv(spec,worker.prefetched(key),out)
conversion/check_fire_runtime216.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Run each delivery with the same TensorFlow Lite release as Android (Linux ABI)."""
2
+ import os
3
+ os.environ.update(CUDA_VISIBLE_DEVICES='',TF_CPP_MIN_LOG_LEVEL='2',TF_NUM_INTRAOP_THREADS='4',TF_NUM_INTEROP_THREADS='2')
4
+ import sys,json,hashlib,tempfile
5
+ from pathlib import Path
6
+ import tensorflow as tf,numpy as np
7
+ root=Path('/workspace/vds-litert-batch2')
8
+ for key in sys.argv[1:]:
9
+ out=root/'learning'/key;artifact=out/'model.tflite'
10
+ def load():
11
+ i=tf.lite.Interpreter(model_path=str(artifact),num_threads=4,experimental_op_resolver_type=tf.lite.experimental.OpResolverType.BUILTIN_WITHOUT_DEFAULT_DELEGATES)
12
+ return i,{n:i.get_signature_runner(n) for n in ['infer','train','save','restore']}
13
+ i,s=load();x=np.random.default_rng(420).uniform(0,1,(1,3,128,192)).astype(np.float32)
14
+ if 'dinov3' in key:
15
+ y=np.zeros((1,3,448,448),np.float32);y[:,2]=1;y[:,0,100:250,150:300]=1;y[:,1,160:170,200:210]=1
16
+ targets={'y':y,'presence':np.array([[1,0]],np.float32),'abstention':np.array([0],np.float32),'supervision':np.ones(4,np.float32)}
17
+ else:
18
+ y=np.zeros((1,100,6),np.float32);y[0,0]=[.2,.2,.4,.5,0,1];y[0,1]=[.6,.5,.8,.8,1,1];targets={'y':y}
19
+ before=s['infer'](x=x);loss=float(s['train'](x=x,**targets,learning_rate=np.float32(.001))['loss']);assert np.isfinite(loss)
20
+ after=s['infer'](x=x);assert any(not np.array_equal(before[n],after[n]) for n in before)
21
+ with tempfile.TemporaryDirectory() as d:
22
+ path=np.array(str(Path(d)/'weights'),dtype=np.bytes_);s['save'](checkpoint_path=path)
23
+ j,t=load();t['restore'](checkpoint_path=path)
24
+ for n,v in after.items():np.testing.assert_array_equal(v,t['infer'](x=x)[n])
25
+ for runner in [s,t]:runner['train'](x=x,**targets,learning_rate=np.float32(.001))
26
+ a=s['infer'](x=x);b=t['infer'](x=x)
27
+ for n in a:np.testing.assert_array_equal(a[n],b[n])
28
+ report={'key':key,'runtime':tf.__version__,'platform':'Linux x86_64; same LiteRT release as Android, different ABI','sha256':hashlib.sha256(artifact.read_bytes()).hexdigest(),'train':True,'loss':loss,'restart_exact':True,'optimizer_resume_exact':True,'two_object_fixture':'dinov3' not in key}
29
+ (out/'runtime216_validation.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report),flush=True)
30
+ del i,s,j,t
conversion/convert_dfine_final.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Rebuild the verified D-FINE component from the published upstream ONNX.
2
+
3
+ Run with .venv-onnx Python; the recorded onnx2tf version and protection manifest
4
+ are required. No custom runtime operators or modified weights are introduced.
5
+ """
6
+ import subprocess,shutil
7
+ from pathlib import Path
8
+ import onnx,onnxsim
9
+ from onnxruntime.tools.symbolic_shape_infer import SymbolicShapeInference
10
+ r=Path('/workspace/vds-litert-batch2');d=r/'learning-backbones/fireviewer_dfine_m_strict_v1';work=d/'raw-onnx';work.mkdir(parents=True,exist_ok=True)
11
+ model=onnx.load(r/'models/upstream/fireviewer_dfine_m_strict_v1/model.onnx')
12
+ names=['/model/decoder/Gather_7_output_0','/model/decoder/Gather_8_output_0']
13
+ for n,c in zip(names,[2,4]):model.graph.value_info.append(onnx.helper.make_tensor_value_info(n,onnx.TensorProto.FLOAT,['batch',300,c]))
14
+ model=onnx.utils.Extractor(model).extract_model(['images'],names)
15
+ model.graph.input[0].type.tensor_type.shape.dim[0].dim_value=1
16
+ for value in model.graph.output:value.type.tensor_type.shape.dim[0].dim_value=1
17
+ model=SymbolicShapeInference.infer_shapes(model,auto_merge=True,guess_output_rank=True,verbose=0);onnx.save(model,work/'raw-original.onnx')
18
+ simplified,valid=onnxsim.simplify(model,overwrite_input_shapes={'images':[1,3,704,704]});assert valid
19
+ onnx.save(simplified,work/'simplified.onnx')
20
+ subprocess.run([str(r/'.venv-onnx/bin/python'),str(r/'patch_onnx2tf_gather_signature.py')],check=True)
21
+ subprocess.run([str(r/'.venv-onnx/bin/onnx2tf'),'-i',str(work/'simplified.onnx'),'-o',str(d/'simplified-converted'),'-b','1','-k','images','-nuo'],check=True)
22
+ if not (d/'arena-protection.json').exists():shutil.copy2(r/'dfine-arena-protection.json',d/'arena-protection.json')
23
+ for script in ['crop_dfine_outputs.py','protect_dfine_arena.py']:
24
+ subprocess.run(['/opt/vds-tf/bin/python',str(r/script)],check=True)
25
+ subprocess.run([str(r/'.venv-onnx/bin/python'),str(r/'check_dfine_final.py')],check=True)
conversion/crop_dfine_outputs.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Expose raw D-FINE tensors before the redundant stack/index output wrapper."""
2
+ from pathlib import Path
3
+ import json,flatbuffers,numpy as np
4
+ from tensorflow.lite.python import schema_py_generated as schema
5
+ r=Path('/workspace/vds-litert-batch2/learning-backbones/fireviewer_dfine_m_strict_v1');source=r/'simplified-converted/simplified_float32.tflite'
6
+ m=schema.ModelT.InitFromObj(schema.Model.GetRootAsModel(source.read_bytes(),0));assert len(m.subgraphs)==1;g=m.subgraphs[0]
7
+ names=['wa/model/decoder/decoder/lqe_layers.3/Add_output_0','wa/model/decoder/decoder/Concat_8_output_0']
8
+ indices=[next(j for j,t in enumerate(g.tensors) if t.name.decode()==n) for n in names]
9
+ required=set(indices);ops=[]
10
+ for op in reversed(g.operators):
11
+ if any(int(j) in required for j in op.outputs):
12
+ required.update(int(j) for j in op.inputs if j>=0);ops.append(op)
13
+ g.operators=list(reversed(ops));g.outputs=np.array(indices,np.int32)
14
+ for s in m.signatureDefs or []:
15
+ s.outputs=[]
16
+ for name,index in zip(['logits','boxes'],indices):
17
+ t=schema.TensorMapT();t.name=name.encode();t.tensorIndex=index;s.outputs.append(t)
18
+ m.metadata=None;m.metadataBuffer=None
19
+ b=flatbuffers.Builder(1024);b.Finish(m.Pack(b),file_identifier=b'TFL3');p=r/'simplified-converted/model-cropped.tflite';p.write_bytes(b.Output())
20
+ print(json.dumps({'output':str(p),'tensors':indices,'ops':len(ops)}))
conversion/dfine-arena-protection.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tensors": [
3
+ {
4
+ "index": 1354,
5
+ "name": "wa/model/decoder/decoder/integral/Softmax_output_0",
6
+ "shape": [
7
+ 1200,
8
+ 33
9
+ ]
10
+ },
11
+ {
12
+ "index": 1857,
13
+ "name": "wa/model/decoder/decoder/integral_1/Softmax_output_0",
14
+ "shape": [
15
+ 1200,
16
+ 33
17
+ ]
18
+ },
19
+ {
20
+ "index": 2338,
21
+ "name": "wa/model/decoder/decoder/integral_2/Softmax_output_0",
22
+ "shape": [
23
+ 1200,
24
+ 33
25
+ ]
26
+ },
27
+ {
28
+ "index": 2816,
29
+ "name": "wa/model/decoder/decoder/integral_3/Softmax_output_0",
30
+ "shape": [
31
+ 1200,
32
+ 33
33
+ ]
34
+ }
35
+ ]
36
+ }
conversion/dynamic_hf_worker.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os,sys,re,json,shutil
2
+ from pathlib import Path
3
+ os.environ.update(VDS_BATCH2_ROOT='/workspace/vds-litert-batch2',HF_TOKEN_FILE='/workspace/.secrets/hf_token',HF_FIREVIEWER_DEST_REPO='fireviewer/litert-models',TORCH_NUM_THREADS='8',HF_HOME='/workspace/cache/huggingface')
4
+ import torch,numpy as np,litert_torch
5
+ from ai_edge_litert.interpreter import Interpreter,OpResolverType
6
+ from huggingface_hub import HfApi
7
+ import dynamic_lowerings
8
+ root=Path('/workspace/vds-litert-batch2');sys.path.insert(0,str(root/'package/scripts'))
9
+ import convert_batch2 as worker
10
+ worker.WORK=root/'dynamic-work'
11
+ key=sys.argv[1]
12
+ spec=next((s for s in worker.all_specs() if s['key']==key),None)
13
+ if spec is None:
14
+ import previous_native
15
+ old=next(s for s in json.loads((root/'previous-models.json').read_text()) if s['key']==key)
16
+ spec={**old,'group':'general_models','route':key,'title':key,'task':'vision embeddings' if key=='dinov2' else 'pose estimation'}
17
+ worker.CONV[key]=lambda s,e,o:getattr(previous_native,key)(s,e,o,worker)
18
+ original_prefetched=worker.prefetched
19
+ worker.prefetched=lambda k:json.loads((root/'previous-prefetch.json').read_text())[k] if k==key else original_prefetched(k)
20
+ validation={}
21
+ adapter_sha256=worker.sha256(root/'dynamic_lowerings.py')
22
+
23
+ def dynamic_export(module,args,out):
24
+ global validation
25
+ module=module.cpu().eval();args=tuple(a.cpu() for a in args)
26
+ for sub in module.modules():
27
+ if hasattr(sub,'set_attn_implementation'):
28
+ try:sub.set_attn_implementation('eager')
29
+ except (ValueError,AttributeError):pass
30
+ h,w=args[0].shape[-2:]
31
+ stride=14 if spec['route'] in {'depth','dinov2'} else 16 if spec['route']=='dinov3_fireviewer' else 32
32
+ if h%stride or w%stride:stride=1
33
+ height=stride*torch.export.Dim('height_blocks',min=4,max=128) if stride>1 else torch.export.Dim('height',min=128,max=2048)
34
+ width=stride*torch.export.Dim('width_blocks',min=4,max=128) if stride>1 else torch.export.Dim('width',min=128,max=2048)
35
+ dims=[{} for a in args];dims[0]={2:height,3:width}
36
+ masks=[]
37
+ for i,a in enumerate(args[1:],1):
38
+ if a.ndim==3 and tuple(a.shape[-2:])==(h,w):dims[i]={1:height,2:width};masks.append(i)
39
+ shapes=[(h,w),(h-2*stride,w+stride),(h+stride,w-2*stride)]
40
+ def inputs(hh,ww):
41
+ aa=list(args);aa[0]=torch.from_numpy(np.random.default_rng(42).normal(size=(1,3,hh,ww)).astype(np.float32))
42
+ for i in masks:aa[i]=torch.ones((1,hh,ww),dtype=args[i].dtype)
43
+ return tuple(aa)
44
+ with torch.inference_mode():
45
+ for hh,ww in shapes:module(*inputs(hh,ww))
46
+ if os.environ.get('REUSE_ARTIFACT'):
47
+ shutil.copy2(os.environ['REUSE_ARTIFACT'],out)
48
+ else:
49
+ converted=litert_torch.convert(module,args,dynamic_shapes=tuple(dims))
50
+ out.parent.mkdir(parents=True,exist_ok=True);converted.export(str(out));del converted
51
+ interp=Interpreter(model_path=str(out),num_threads=8,experimental_op_resolver_type=OpResolverType.BUILTIN_WITHOUT_DEFAULT_DELEGATES)
52
+ details=interp.get_input_details();mapping={}
53
+ for j,d in enumerate(details):
54
+ match=re.search(r'args_(\d+)',d['name']);index=int(match.group(1)) if match else j
55
+ mapping[index]=d
56
+ if mapping[0]['shape_signature'].tolist()[2:]!=[-1,-1]:raise RuntimeError('Spatial signature was fixed by converter')
57
+ checks=[]
58
+ for hh,ww in shapes:
59
+ aa=inputs(hh,ww)
60
+ for i,a in enumerate(aa):interp.resize_tensor_input(mapping[i]['index'],tuple(a.shape),strict=True)
61
+ interp.allocate_tensors()
62
+ for i,a in enumerate(aa):interp.set_tensor(mapping[i]['index'],a.numpy())
63
+ interp.invoke()
64
+ with torch.inference_mode():ref=module(*aa)
65
+ if isinstance(ref,torch.Tensor):ref=(ref,)
66
+ refs=[v.detach().numpy() for v in ref]
67
+ errors=[]
68
+ for j,d in enumerate(interp.get_output_details()):
69
+ a=interp.get_tensor(d['index']);candidates=[r for r in refs if r.shape==a.shape]
70
+ if len(candidates)==1:r=candidates[0]
71
+ else:
72
+ match=re.search(r'output_(\d+)',d['name'])
73
+ if not match:raise RuntimeError(f'Ambiguous output mapping for {d["name"]} {a.shape}')
74
+ r=refs[int(match.group(1))]
75
+ if r.shape!=a.shape:raise RuntimeError('Output signature mismatch');np.testing.assert_allclose(a,r,rtol=1e-3,atol=1e-3)
76
+ if not np.isfinite(a).all():raise RuntimeError('Nonfinite output')
77
+ errors.append(float(np.max(np.abs(a-r))))
78
+ checks.append({'image_shape':[1,3,hh,ww],'max_abs_errors':errors})
79
+ print('DYNAMIC_PARITY_OK',key,hh,ww,errors,flush=True)
80
+ validation={'dynamic_validated':True,'checks':checks,'atol':1e-3,'rtol':1e-3,'alignment':stride,'source_resolution_resampled':False,'runtime_requirement':'LiteRT builtin CPU; disable default XNNPACK delegates for these dynamic graphs','android_tested':False,'accuracy_evaluated':False,'conversion_adapter_sha256':adapter_sha256}
81
+ return {**worker.inspect_tflite(out),**validation}
82
+
83
+ base_publish=worker.publish
84
+ def publish(api,s,e,out,arts,report):
85
+ if not validation.get('dynamic_validated'):raise RuntimeError('Dynamic validation gate did not pass')
86
+ report.update(validation)
87
+ return base_publish(api,s,e,out,arts,report)
88
+
89
+ worker.direct_export=dynamic_export;worker.publish=publish
90
+ if spec['route']=='rfdetr_medium':
91
+ from rfdetr.export._litert.exporter import LiteRTExporter
92
+ def patched_convert(self,litert,wrapped_model,input_tensors,output_file):
93
+ dynamic_export(wrapped_model,(input_tensors,),Path(output_file))
94
+ LiteRTExporter._convert_and_save=patched_convert
95
+ if key=='fireviewer_rfdetr_medium_v110' and os.environ.get('RF_NATIVE')=='1':
96
+ import rfdetr_native
97
+ worker.CONV['rfdetr_medium']=lambda s,e,o:rfdetr_native.convert(s,e,o,worker)
98
+ if key=='fireviewer_dfine_m_strict_v1':
99
+ import dfine_native
100
+ worker.CONV['onnx_existing']=lambda s,e,o:dfine_native.convert(s,e,o,worker)
101
+ worker.process(HfApi(token=worker.token()),spec)
conversion/dynamic_lowerings.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Native LiteRT lowerings for shape-dependent reductions and shape arithmetic."""
2
+ import operator
3
+ import torch
4
+ from litert_torch import fx_infra
5
+ from litert_torch.backend.experimental import torch_tfl
6
+ from litert_torch.backend.experimental.torch_tfl._lowerings import _ir_operation
7
+ from litert_torch.backend.lowerings import registry,utils
8
+ from litert_converter.mlir import ir
9
+
10
+ fx_infra.decomp.update_pre_lower_decomp(torch_tfl.decomps)
11
+
12
+ def dynamic_layer_norm(x,normalized_shape,weight,bias,eps):
13
+ axes=list(range(x.ndim-len(normalized_shape),x.ndim))
14
+ mean=x.mean(axes,keepdim=True)
15
+ variance=((x-mean)*(x-mean)).mean(axes,keepdim=True)
16
+ invstd=torch.rsqrt(variance+eps)
17
+ output=(x-mean)*invstd
18
+ if weight is not None:output=output*weight
19
+ if bias is not None:output=output+bias
20
+ return output,mean,invstd
21
+
22
+ fx_infra.decomp.update_pre_lower_decomp({torch.ops.aten.native_layer_norm.default:dynamic_layer_norm})
23
+
24
+ @registry.lower(operator.floordiv)
25
+ def lower_floor_div(lctx,lhs,rhs):
26
+ if isinstance(lhs,int) and isinstance(rhs,int):return lhs//rhs
27
+ if isinstance(lhs,int):lhs=utils.splat(lhs,rhs.type.element_type,rhs.type.shape)
28
+ if isinstance(rhs,int):rhs=utils.splat(rhs,lhs.type.element_type,lhs.type.shape)
29
+ if lhs.type!=rhs.type:raise TypeError('Shape floor division requires matching scalar integer types')
30
+ return _ir_operation('tfl.floor_div',results=[lhs.type],operands=[lhs,rhs])
31
+
32
+ @registry.lower(torch.ops.aten.relu.default)
33
+ def lower_relu(lctx,x):
34
+ return _ir_operation('tfl.relu',results=utils.node_meta_to_ir_types(lctx.node),operands=[x])
35
+
36
+ @registry.lower(torch.ops.aten.cumsum.default)
37
+ def lower_cumsum(lctx,x,dim,dtype=None):
38
+ result=utils.node_meta_to_ir_types(lctx.node)
39
+ if x.type.element_type!=result[0].element_type:
40
+ typ=ir.RankedTensorType.get(x.type.shape,result[0].element_type)
41
+ x=_ir_operation('tfl.cast',results=[typ],operands=[x])
42
+ axis=utils.splat(dim,ir.IntegerType.get_signless(32),[])
43
+ return _ir_operation('tfl.cumsum',results=result,operands=[x,axis],attributes={'exclusive':ir.BoolAttr.get(False),'reverse':ir.BoolAttr.get(False)})
44
+
45
+ @registry.lower(torch.ops.aten._is_all_true.default)
46
+ def lower_all_true(lctx,x):
47
+ if len(x.type.shape)==0:return x
48
+ import numpy as np
49
+ axes=utils.numpy_array_constant(np.arange(len(x.type.shape),dtype=np.int32))
50
+ return _ir_operation('tfl.reduce_all',results=utils.node_meta_to_ir_types(lctx.node),operands=[x,axes],attributes={'keep_dims':ir.BoolAttr.get(False)})
51
+
52
+ # Preserve symbolic dimensions in the operator schema. The upstream int[]
53
+ # schema can specialize reshape dimensions during decomposition.
54
+ @torch.library.custom_op('vds::reshape',mutates_args=(),schema='(Tensor x, SymInt[] shape) -> Tensor')
55
+ def dynamic_reshape(x,shape):
56
+ return x.reshape(shape).clone()
57
+ @dynamic_reshape.register_fake
58
+ def dynamic_reshape_fake(x,shape):
59
+ return x.reshape(shape).clone()
60
+ @registry.lower(torch.ops.vds.reshape.default)
61
+ def lower_reshape(lctx,x,shape):
62
+ return _ir_operation('tfl.reshape',results=utils.node_meta_to_ir_types(lctx.node),operands=[x,utils.convert_shape_to_ir_value(shape)])
63
+
64
+ def dynamic_view(x,shape):return torch.ops.vds.reshape(x,shape)
65
+ fx_infra.decomp.update_pre_lower_decomp({torch.ops.aten.view.default:dynamic_view,torch.ops.aten._unsafe_view.default:dynamic_view})
66
+
67
+ def dynamic_arange(start,end,step=1,*,dtype=None,layout=None,device=None,pin_memory=None):
68
+ result=torch.ops.tfl.range(start,end,step)
69
+ return result.to(dtype=dtype) if dtype is not None else result
70
+ fx_infra.decomp.update_pre_lower_decomp({torch.ops.aten.arange.start_step:dynamic_arange})
71
+
72
+ @torch.library.custom_op('vds::broadcast_to',mutates_args=(),schema='(Tensor x, SymInt[] shape) -> Tensor')
73
+ def dynamic_broadcast(x,shape):return x.expand(shape).clone()
74
+ @dynamic_broadcast.register_fake
75
+ def dynamic_broadcast_fake(x,shape):return x.expand(shape).clone()
76
+ @registry.lower(torch.ops.vds.broadcast_to.default)
77
+ def lower_broadcast(lctx,x,shape):
78
+ return _ir_operation('tfl.broadcast_to',results=utils.node_meta_to_ir_types(lctx.node),operands=[x,utils.convert_shape_to_ir_value(shape)])
79
+ fx_infra.decomp.update_pre_lower_decomp({torch.ops.aten.expand.default:lambda x,shape,implicit=False:torch.ops.vds.broadcast_to(x,shape)})
80
+
81
+ @torch.library.custom_op('vds::linear',mutates_args=())
82
+ def dynamic_linear(x:torch.Tensor,weight:torch.Tensor,bias:torch.Tensor|None)->torch.Tensor:
83
+ return torch.nn.functional.linear(x,weight,bias)
84
+ @dynamic_linear.register_fake
85
+ def dynamic_linear_fake(x,weight,bias):
86
+ return x.new_empty(x.shape[:-1] if weight.ndim==1 else (*x.shape[:-1],weight.shape[0]))
87
+ @registry.lower(torch.ops.vds.linear.default)
88
+ def lower_linear(lctx,x,weight,bias):
89
+ if len(weight.type.shape)==1:
90
+ from litert_converter.mlir.dialects import stablehlo
91
+ from litert_torch.backend.experimental.torch_tfl._lowerings import _tfl_squeeze_lowering
92
+ weight=stablehlo.reshape(ir.RankedTensorType.get([1,weight.type.shape[0]],weight.type.element_type),weight)
93
+ if bias is None:bias=utils.splat(0.0,weight.type.element_type,[1])
94
+ typ=utils.node_meta_to_ir_types(lctx.node)[0]
95
+ expanded=ir.RankedTensorType.get([*typ.shape,1],typ.element_type)
96
+ y=_ir_operation('tfl.fully_connected',results=[expanded],operands=[x,weight,bias],attributes={'fused_activation_function':ir.StringAttr.get('NONE'),'weights_format':ir.StringAttr.get('DEFAULT'),'keep_num_dims':ir.BoolAttr.get(True),'asymmetric_quantize_inputs':ir.BoolAttr.get(False)})
97
+ return _tfl_squeeze_lowering(lctx,y,[-1])
98
+ if bias is None:bias=utils.splat(0.0,weight.type.element_type,[weight.type.shape[0]])
99
+ return _ir_operation('tfl.fully_connected',results=utils.node_meta_to_ir_types(lctx.node),operands=[x,weight,bias],attributes={'fused_activation_function':ir.StringAttr.get('NONE'),'weights_format':ir.StringAttr.get('DEFAULT'),'keep_num_dims':ir.BoolAttr.get(True),'asymmetric_quantize_inputs':ir.BoolAttr.get(False)})
100
+ fx_infra.decomp.update_pre_lower_decomp({torch.ops.aten.linear.default:lambda x,weight,bias=None:torch.ops.vds.linear(x,weight,bias)})
101
+
102
+ fx_infra.decomp.update_pre_convert_decomp({
103
+ torch.ops.aten.linear.default:lambda x,weight,bias=None:torch.ops.vds.linear(x,weight,bias),
104
+ torch.ops.aten.view.default:dynamic_view,
105
+ torch.ops.aten._unsafe_view.default:dynamic_view,
106
+ torch.ops.aten.native_layer_norm.default:dynamic_layer_norm,
107
+ torch.ops.aten.arange.start_step:dynamic_arange,
108
+ torch.ops.aten.expand.default:lambda x,shape,implicit=False:torch.ops.vds.broadcast_to(x,shape),
109
+ })
110
+
111
+ @torch.library.custom_op('vds::resize2d',mutates_args=(),schema='(Tensor x, SymInt[] size, bool align_corners, bool nearest) -> Tensor')
112
+ def dynamic_resize2d(x,size,align_corners,nearest):
113
+ if nearest:return torch.nn.functional.interpolate(x,size=size,mode='nearest')
114
+ return torch.nn.functional.interpolate(x,size=size,mode='bilinear',align_corners=align_corners)
115
+ @dynamic_resize2d.register_fake
116
+ def dynamic_resize2d_fake(x,size,align_corners,nearest):return x.new_empty((x.shape[0],x.shape[1],*size))
117
+ @registry.lower(torch.ops.vds.resize2d.default)
118
+ def lower_resize2d(lctx,x,size,align_corners,nearest):
119
+ from litert_converter.mlir.dialects import stablehlo
120
+ nhwc=stablehlo.transpose(x,[0,2,3,1])
121
+ result=utils.node_meta_to_ir_types(lctx.node)[0]
122
+ typ=ir.RankedTensorType.get([result.shape[i] for i in [0,2,3,1]],result.element_type)
123
+ resized=_ir_operation('tfl.resize_nearest_neighbor' if nearest else 'tfl.resize_bilinear',results=[typ],operands=[nhwc,utils.convert_shape_to_ir_value(size)],attributes={'align_corners':ir.BoolAttr.get(align_corners),'half_pixel_centers':ir.BoolAttr.get(not align_corners and not nearest)})
124
+ return stablehlo.transpose(resized,[0,3,1,2])
125
+ def bilinear_vec(x,output_size,align_corners,scale_factors=None):
126
+ size=output_size if output_size is not None else [int(x.shape[-2+i]*scale_factors[i]) for i in range(2)]
127
+ return torch.ops.vds.resize2d(x,size,align_corners,False)
128
+ def bilinear_default(x,output_size,align_corners,scales_h=None,scales_w=None):
129
+ return torch.ops.vds.resize2d(x,output_size,align_corners,False)
130
+ fx_infra.decomp.update_pre_convert_decomp({torch.ops.aten.upsample_bilinear2d.vec:bilinear_vec,torch.ops.aten.upsample_bilinear2d.default:bilinear_default})
131
+
132
+ from litert_torch.backend.experimental.torch_tfl._lowerings import _tfl_mul_lowering
133
+ @registry.lower(torch.ops.tfl.mul.default)
134
+ def lower_bool_mul(lctx,lhs,rhs,fused_activation_function='NONE'):
135
+ if isinstance(lhs,ir.Value) and isinstance(rhs,ir.Value) and str(lhs.type.element_type)=='i1' and str(rhs.type.element_type)=='i1':
136
+ return _ir_operation('tfl.logical_and',results=utils.node_meta_to_ir_types(lctx.node),operands=[lhs,rhs])
137
+ return _tfl_mul_lowering(lctx,lhs,rhs,fused_activation_function)
138
+
139
+ @torch.library.custom_op('vds::strided_slice',mutates_args=(),schema='(Tensor x, SymInt[] begin, SymInt[] end, int[] strides) -> Tensor')
140
+ def dynamic_strided_slice(x,begin,end,strides):
141
+ return x[tuple(slice(b,e,s) for b,e,s in zip(begin,end,strides))].clone()
142
+ @dynamic_strided_slice.register_fake
143
+ def dynamic_strided_slice_fake(x,begin,end,strides):
144
+ return x[tuple(slice(b,e,s) for b,e,s in zip(begin,end,strides))].clone()
145
+ @registry.lower(torch.ops.vds.strided_slice.default)
146
+ def lower_strided_slice(lctx,x,begin,end,strides):
147
+ from litert_torch.backend.experimental.torch_tfl._lowerings import _tfl_strided_slice_lowering
148
+ return _tfl_strided_slice_lowering(lctx,x,begin,end,strides)
149
+ def dynamic_slice(x,dim=0,start=None,end=None,step=1):
150
+ n=x.shape[dim];b=0 if start is None else start;e=n if end is None else end
151
+ if b<0:b+=n
152
+ if e<0:e+=n
153
+ b=max(0,min(b,n));e=max(b,min(e,n))
154
+ begin=[0]*x.ndim;ends=list(x.shape);strides=[1]*x.ndim
155
+ begin[dim]=b;ends[dim]=e;strides[dim]=step
156
+ return torch.ops.vds.strided_slice(x,begin,ends,strides)
157
+ def dynamic_embedding(weight,indices,padding_idx=-1,scale_grad_by_freq=False,sparse=False):
158
+ flat=torch.ops.vds.reshape(indices,[-1]).to(torch.int32)
159
+ result=torch.ops.tfl.embedding_lookup(flat,weight)
160
+ return torch.ops.vds.reshape(result,[*indices.shape,weight.shape[-1]])
161
+ fx_infra.decomp.update_pre_convert_decomp({torch.ops.aten.slice.Tensor:dynamic_slice,torch.ops.aten.embedding.default:dynamic_embedding})
conversion/finish_fire_backbone.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Fixed internal image grid, checked against upstream, for Android adaptation."""
2
+ import os,sys,json,traceback
3
+ os.environ.update(TORCH_NUM_THREADS='6')
4
+ from pathlib import Path
5
+ import torch,numpy as np,litert_torch
6
+ import static_topk_lowering
7
+ from ai_edge_litert.interpreter import Interpreter,OpResolverType
8
+ root=Path('/workspace/vds-litert-batch2');key=sys.argv[1];out=root/'learning-backbones'/key/'final';out.mkdir(parents=True,exist_ok=True)
9
+ torch.set_num_threads(6);source=root/'models/upstream'/key
10
+ try:
11
+ if 'rtdetr_v2' in key:
12
+ from transformers import AutoModelForObjectDetection
13
+ import transformers.models.rt_detr_v2.modeling_rt_detr_v2 as implementation
14
+ net=AutoModelForObjectDetection.from_pretrained(source,local_files_only=True,attn_implementation='eager').eval();size=768
15
+ class Wrapped(torch.nn.Module):
16
+ def __init__(self):super().__init__();self.m=net
17
+ def forward(self,x):
18
+ o=self.m(pixel_values=x);return o.logits,o.pred_boxes
19
+ else:
20
+ sys.path.insert(0,str(root/'vendor/D-FINE'));from src.core import YAMLConfig
21
+ cfg=YAMLConfig(str(root/'vendor/D-FINE/configs/dfine/dfine_hgnetv2_m_coco.yml'))
22
+ cfg.yaml_cfg.update(num_classes=2,remap_mscoco_category=False,eval_spatial_size=[704,704]);cfg.yaml_cfg['HGNetv2']['pretrained']=False
23
+ net=cfg.model;checkpoint=torch.load(source/'model.pth',map_location='cpu',weights_only=False)
24
+ state=checkpoint['ema']['module'] if 'ema' in checkpoint else checkpoint.get('model',checkpoint);net.load_state_dict(state,strict=True);net=net.eval().deploy();size=704
25
+ class Wrapped(torch.nn.Module):
26
+ def __init__(self):super().__init__();self.m=net
27
+ def forward(self,x):
28
+ o=self.m(x);return o['pred_logits'],o['pred_boxes']
29
+ wrapped=Wrapped().eval();x=np.random.default_rng(842).uniform(0,1,(1,3,size,size)).astype(np.float32)
30
+ with torch.no_grad():reference=[t.numpy() for t in wrapped(torch.from_numpy(x))]
31
+ if 'rtdetr_v2' in key:
32
+ # The upstream runtime assertion checks only the spatial grid versus sequence
33
+ # length. It was executed above; the serialized grid here is immutable.
34
+ implementation.torch_compilable_check=lambda condition,message:None
35
+ # Position embeddings depend only on the fixed feature grid. Materialize
36
+ # the original float64 calculation before export, preserving its values.
37
+ class FixedPosition(torch.nn.Module):
38
+ def __init__(self, original):
39
+ super().__init__(); self.original=original; self.cache={}
40
+ def forward(self,width,height,device,dtype):
41
+ shape=(int(width),int(height))
42
+ return getattr(self, 'grid_'+str(shape[0])+'_'+str(shape[1])).to(device=device,dtype=dtype)
43
+ for parent in list(net.modules()):
44
+ for name,child in list(parent.named_children()):
45
+ if isinstance(child,implementation.RTDetrV2SinePositionEmbedding):
46
+ fixed=FixedPosition(child)
47
+ for stride in (8,16,32):
48
+ grid=size//stride
49
+ fixed.register_buffer('grid_'+str(grid)+'_'+str(grid),child(grid,grid,device=torch.device('cpu'),dtype=torch.float32))
50
+ setattr(parent,name,fixed)
51
+ artifact=out/'model.tflite'
52
+ with torch.no_grad():edge=litert_torch.convert(wrapped,(torch.zeros(1,3,size,size),))
53
+ edge.export(str(artifact));del edge
54
+ i=Interpreter(model_path=str(artifact),num_threads=6,experimental_op_resolver_type=OpResolverType.BUILTIN_WITHOUT_DEFAULT_DELEGATES);i.allocate_tensors();i.set_tensor(i.get_input_details()[0]['index'],x);i.invoke();errors=[]
55
+ for d in i.get_output_details():
56
+ a=i.get_tensor(d['index']);r=next(r for r in reference if r.shape==a.shape);np.testing.assert_allclose(a,r,atol=2e-3,rtol=2e-3);errors.append(float(np.max(np.abs(a-r))))
57
+ report={'key':key,'phase':'validated_frozen_component','backbone':str(artifact),'source_pytorch_parity_passed':True,'input_shape':list(x.shape),'outputs':[{'shape':list(map(int,d['shape'])),'index':n} for n,d in enumerate(i.get_output_details())],'max_abs_errors':errors,'atol':2e-3,'rtol':2e-3,'fixed_shape_assertion_checked_before_export':'rtdetr_v2' in key}
58
+ (out/'parity.json').write_text(json.dumps(report,indent=2)+'\n')
59
+ except Exception as e:
60
+ report={'key':key,'phase':'failed','error':str(e),'traceback':traceback.format_exc()};print(report['traceback'],flush=True)
61
+ (out/'status.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report),flush=True)
conversion/merge_training_graph.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Join a frozen, single-subgraph visual encoder to mutable training signatures.
2
+
3
+ Weights remain shared buffers; inference operators are copied into train/infer.
4
+ No Python callback, custom runtime, or remote computation is used on Android.
5
+ """
6
+ import copy
7
+ import flatbuffers
8
+ import numpy as np
9
+ from tensorflow.lite.python import schema_py_generated as schema
10
+
11
+
12
+ def freeze_auxiliary_inputs(backbone_path,destination,values):
13
+ model=schema.ModelT.InitFromObj(schema.Model.GetRootAsModel(backbone_path.read_bytes(),0))
14
+ graph=model.subgraphs[0];removed=[]
15
+ for position,fill in values.items():
16
+ index=int(graph.inputs[int(position)]);tensor=graph.tensors[index]
17
+ dtype={schema.TensorType.INT32:np.int32,schema.TensorType.INT64:np.int64,schema.TensorType.FLOAT32:np.float32}[tensor.type]
18
+ data=np.full(tensor.shape,fill,dtype=dtype)
19
+ buffer=schema.BufferT();buffer.data=np.frombuffer(data.tobytes(),np.uint8)
20
+ tensor.buffer=len(model.buffers);model.buffers.append(buffer);removed.append(index)
21
+ graph.inputs=np.array([i for i in graph.inputs if i not in removed],np.int32)
22
+ for signature in model.signatureDefs or []:signature.inputs=[t for t in signature.inputs if t.tensorIndex not in removed]
23
+ model.metadata=None;model.metadataBuffer=None
24
+ builder=flatbuffers.Builder(1024);builder.Finish(model.Pack(builder),file_identifier=b'TFL3');destination.write_bytes(builder.Output())
25
+
26
+
27
+ def dynamic_resize_backbone(backbone_path,destination):
28
+ """Explicit stretch preprocessing for architectures whose token grid is fixed."""
29
+ import tensorflow as tf
30
+ backbone=schema.ModelT.InitFromObj(schema.Model.GetRootAsModel(backbone_path.read_bytes(),0))
31
+ assert len(backbone.subgraphs)==1 and len(backbone.subgraphs[0].inputs)==1
32
+ graph=backbone.subgraphs[0];old_input=int(graph.inputs[0]);shape=list(map(int,graph.tensors[old_input].shape))
33
+ assert shape[0:2]==[1,3]
34
+ h,w=map(int,shape[2:])
35
+ class Resize(tf.Module):
36
+ @tf.function(input_signature=[tf.TensorSpec([1,3,None,None],tf.float32,name='x')])
37
+ def run(self,x):return tf.transpose(tf.image.resize(tf.transpose(x,[0,2,3,1]),[h,w]),[0,3,1,2])
38
+ resize=Resize();converter=tf.lite.TFLiteConverter.from_concrete_functions([resize.run.get_concrete_function()],resize)
39
+ prefix=schema.ModelT.InitFromObj(schema.Model.GetRootAsModel(converter.convert(),0));p=prefix.subgraphs[0]
40
+ offset=len(graph.tensors);bo=len(backbone.buffers);oo=len(backbone.operatorCodes)
41
+ for t in p.tensors:t.buffer+=bo
42
+ graph.tensors.extend(p.tensors);backbone.buffers.extend(prefix.buffers);backbone.operatorCodes.extend(prefix.operatorCodes)
43
+ resized=int(p.outputs[0])+offset;new_input=int(p.inputs[0])+offset
44
+ for op in graph.operators:
45
+ if op.inputs is not None:op.inputs=np.array([resized if i==old_input else i for i in op.inputs],np.int32)
46
+ for op in p.operators:
47
+ op.opcodeIndex+=oo
48
+ for field in ('inputs','outputs','intermediates'):
49
+ values=getattr(op,field)
50
+ if values is not None:setattr(op,field,np.array([int(i)+offset if i>=0 else -1 for i in values],np.int32))
51
+ graph.operators=p.operators+graph.operators;graph.inputs=np.array([new_input],np.int32)
52
+ graph.tensors[new_input].shape=np.array(shape,np.int32)
53
+ for s in backbone.signatureDefs or []:
54
+ for t in s.inputs:
55
+ if t.tensorIndex==old_input:t.tensorIndex=new_input
56
+ backbone.metadata=None;backbone.metadataBuffer=None
57
+ builder=flatbuffers.Builder(1024);builder.Finish(backbone.Pack(builder),file_identifier=b'TFL3');destination.write_bytes(builder.Output())
58
+ return {'mode':'bilinear_stretch_inside_graph','native_backbone_shape':shape}
59
+
60
+
61
+ def merge(backbone_path, head_path, destination, output_index, image_size=224):
62
+ backbone = schema.ModelT.InitFromObj(schema.Model.GetRootAsModel(backbone_path.read_bytes(), 0))
63
+ head = schema.ModelT.InitFromObj(schema.Model.GetRootAsModel(head_path.read_bytes(), 0))
64
+ assert len(backbone.subgraphs) == 1, 'Nested backbone control flow needs explicit remapping'
65
+ source = backbone.subgraphs[0]
66
+ assert len(source.inputs) == 1
67
+ assert not any(t.isVariable for t in source.tensors), 'Backbone must be frozen'
68
+ for op in source.operators:
69
+ assert op.mutatingVariableInputs is None or not any(op.mutatingVariableInputs)
70
+ assert backbone.operatorCodes[op.opcodeIndex].builtinCode not in [schema.BuiltinOperator.IF, schema.BuiltinOperator.WHILE, schema.BuiltinOperator.CALL_ONCE]
71
+ buffer_offset = len(head.buffers)
72
+ head.buffers.extend(copy.deepcopy(backbone.buffers))
73
+ opcode_offset = len(head.operatorCodes)
74
+ head.operatorCodes.extend(copy.deepcopy(backbone.operatorCodes))
75
+ for signature in head.signatureDefs:
76
+ name = signature.signatureKey.decode()
77
+ if name not in ('infer', 'train'):
78
+ continue
79
+ graph = head.subgraphs[signature.subgraphIndex]
80
+ bindings = output_index if isinstance(output_index,dict) else {'features':output_index}
81
+ feature_maps=[t for t in signature.inputs if t.name.decode() in bindings]
82
+ assert len(feature_maps)==len(bindings)
83
+ offset = len(graph.tensors)
84
+ tensors = copy.deepcopy(source.tensors)
85
+ for tensor in tensors:
86
+ tensor.buffer += buffer_offset
87
+ graph.tensors.extend(tensors)
88
+ image_input = int(source.inputs[0]) + offset
89
+ sig=list(graph.tensors[image_input].shapeSignature)
90
+ if len(sig)==4 and sig[1:]==[3,-1,-1]:
91
+ hw=image_size if isinstance(image_size,(list,tuple)) else [image_size,image_size]
92
+ graph.tensors[image_input].shape=np.array([1,3,*hw],dtype=np.int32)
93
+ replacements={t.tensorIndex:int(source.outputs[bindings[t.name.decode()]])+offset for t in feature_maps}
94
+ image_maps=[t for t in signature.inputs if t.name==b'image']
95
+ replacements.update({t.tensorIndex:image_input for t in image_maps})
96
+ for op in graph.operators:
97
+ if op.inputs is not None:
98
+ op.inputs = np.array([replacements.get(int(i),int(i)) for i in op.inputs], dtype=np.int32)
99
+ ops = copy.deepcopy(source.operators)
100
+ for op in ops:
101
+ op.opcodeIndex += opcode_offset
102
+ for field in ('inputs', 'outputs', 'intermediates'):
103
+ values = getattr(op, field)
104
+ if values is not None:
105
+ setattr(op, field, np.array([int(i)+offset if i >= 0 else -1 for i in values], dtype=np.int32))
106
+ graph.operators = ops + graph.operators
107
+ removed={t.tensorIndex for t in feature_maps+image_maps}
108
+ graph.inputs = np.array([image_input]+[int(i) for i in graph.inputs if i not in removed], dtype=np.int32)
109
+ feature_map=feature_maps[0]
110
+ signature.inputs=[t for t in signature.inputs if t.tensorIndex not in removed]+[feature_map]
111
+ feature_map.tensorIndex = image_input
112
+ feature_map.name = b'x'
113
+ # Converter metadata from either graph does not describe the combined model.
114
+ head.metadata = None
115
+ head.metadataBuffer = None
116
+ builder = flatbuffers.Builder(1024)
117
+ builder.Finish(head.Pack(builder), file_identifier=b'TFL3')
118
+ destination.write_bytes(builder.Output())
conversion/package/model_manifest.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "destination_repo": "Charlbi/Lite_rt_prepared_for_android_dataset_builder",
3
+ "general_models": [
4
+ {"key":"grounding_dino_base","title":"Grounding DINO Base","repo":"IDEA-Research/grounding-dino-base","license":"apache-2.0","route":"grounding_dino","task":"open-vocabulary object detection"},
5
+ {"key":"owlv2_base_patch16","title":"OWLv2 Base Patch16 Ensemble","repo":"google/owlv2-base-patch16-ensemble","license":"apache-2.0","route":"owlv2","task":"zero-shot object detection"},
6
+ {"key":"depth_anything_v2_small","title":"Depth Anything V2 Small","repo":"depth-anything/Depth-Anything-V2-Small-hf","license":"apache-2.0","route":"depth","task":"monocular depth estimation"},
7
+ {"key":"rtmdet_tiny","title":"RTMDet Tiny","repo":null,"license":"apache-2.0","route":"rtmdet","task":"object detection","checkpoint_url":"https://download.openmmlab.com/mmyolo/v0/rtmdet/rtmdet_tiny_syncbn_fast_8xb32-300e_coco/rtmdet_tiny_syncbn_fast_8xb32-300e_coco_20230102_140117-dbb1dc83.pth","config":"configs/rtmdet/rtmdet_tiny_syncbn_fast_8xb32-300e_coco.py"},
8
+ {"key":"efficientformer_l1","title":"EfficientFormer L1","repo":"timm/efficientformer_l1.snap_dist_in1k","license":"apache-2.0","route":"timm","task":"classification + visual embeddings"},
9
+ {"key":"repvit_m1","title":"RepViT M1","repo":"timm/repvit_m1.dist_in1k","license":"apache-2.0","route":"timm","task":"classification + visual embeddings"},
10
+ {"key":"edgenext_xx_small","title":"EdgeNeXt XX-Small","repo":"timm/edgenext_xx_small.in1k","license":"mit","route":"timm","task":"classification + visual embeddings"},
11
+ {"key":"edgenext_x_small","title":"EdgeNeXt X-Small","repo":"timm/edgenext_x_small.in1k","license":"mit","route":"timm","task":"classification + visual embeddings"},
12
+ {"key":"edgenext_small_usi","title":"EdgeNeXt Small USI","repo":"timm/edgenext_small.usi_in1k","license":"mit","route":"timm","task":"classification + visual embeddings"},
13
+ {"key":"hgnetv2_b0","title":"HGNetV2 B0","repo":"timm/hgnetv2_b0.ssld_stage2_ft_in1k","license":"apache-2.0","route":"timm","task":"classification + visual embeddings"},
14
+ {"key":"caformer_s18","title":"CAFormer S18","repo":"timm/caformer_s18.sail_in1k","license":"apache-2.0","route":"timm","task":"classification + visual embeddings"},
15
+ {"key":"convformer_s18","title":"ConvFormer S18","repo":"timm/convformer_s18.sail_in1k","license":"apache-2.0","route":"timm","task":"classification + visual embeddings"},
16
+ {"key":"picodet_layout_1x","title":"PicoDet Layout 1x","repo":"PaddlePaddle/PicoDet_layout_1x","license":"apache-2.0","route":"picodet","task":"document layout detection"},
17
+ {"key":"picodet_l_layout_3cls","title":"PicoDet-L Layout 3cls","repo":"PaddlePaddle/PicoDet-L_layout_3cls","license":"apache-2.0","route":"picodet","task":"document layout detection"},
18
+ {"key":"table_transformer_detection","title":"Table Transformer Detection","repo":"microsoft/table-transformer-detection","license":"mit","route":"object_detection","task":"table detection"},
19
+ {"key":"table_transformer_structure","title":"Table Transformer Structure Recognition","repo":"microsoft/table-transformer-structure-recognition","license":"mit","route":"object_detection","task":"table structure recognition"}
20
+ ],
21
+ "fireviewer_models": [
22
+ {"key":"fireviewer_dfine_m_strict_v1","title":"FireViewer D-FINE-M strict v1","repo":"fireviewer/fire-smoke-dfine-m-strict-v1","license":"source-specific","route":"object_detection","task":"fire/smoke detection","publish_default":true},
23
+ {"key":"fireviewer_rtdetr_v2_r50","title":"FireViewer RT-DETRv2-R50","repo":"fireviewer/rtdetr-v2-r50-fire-smoke","license":"other/source-specific","route":"object_detection","task":"fire/smoke detection","publish_default":true},
24
+ {"key":"fireviewer_yolo11m_strict_v1","title":"FireViewer YOLO11-M strict v1","repo":"fireviewer/fire-smoke-yolo11m-strict-v1","license":"agpl-3.0","route":"onnx_existing","task":"fire/smoke detection","publish_default":true},
25
+ {"key":"fireviewer_rfdetr_medium_v110","title":"FireViewer RF-DETR Medium 1.10 strict v1","repo":"fireviewer/fire-smoke-rfdetr-medium-v110-strict-v1","license":"composite-research-release","route":"rfdetr_medium","task":"fire/smoke detection","publish_default":true},
26
+ {"key":"fireviewer_dinov3_pointing_pilot_v1","title":"FireViewer DINOv3 pointing pilot v1","repo":"fireviewer/dinov3-pointing-pilot-v1","license":"dinov3-license-and-source-specific-terms","route":"dinov3_fireviewer","task":"segmentation + pointing + abstention + presence","publish_default":false,"base_repo":"facebook/dinov3-vitb16-pretrain-lvd1689m","base_revision":"5931719e67bbdb9737e363e781fb0c67687896bc"},
27
+ {"key":"fireviewer_segformer_b2_baseline_v1","title":"FireViewer SegFormer-B2 baseline v1","repo":"fireviewer/segformer-b2-fire-smoke-baseline-v1","license":"source-specific","route":"segmentation","task":"fire/smoke semantic segmentation","publish_default":true,"legacy_optional":true}
28
+ ]
29
+ }
conversion/package/scripts/convert_batch2.py ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+ import argparse, fcntl, gc, hashlib, importlib.util, json, os, shutil, subprocess, sys, tempfile, time, traceback
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import numpy as np
8
+ import torch
9
+ import torch.nn as nn
10
+ from huggingface_hub import HfApi, snapshot_download
11
+
12
+ ROOT = Path(os.environ.get('VDS_BATCH2_ROOT','/workspace/vds-litert-batch2'))
13
+ PKG = Path(__file__).resolve().parents[1]
14
+ MANIFEST_PATH = PKG / 'model_manifest.json'
15
+ TOKEN_FILE = Path(os.environ.get('HF_TOKEN_FILE','/workspace/HF.txt'))
16
+ DEST_REPO = os.environ.get('HF_DEST_REPO','Charlbi/Lite_rt_prepared_for_android_dataset_builder')
17
+ FIREVIEWER_DEST_REPO = os.environ.get('HF_FIREVIEWER_DEST_REPO','fireviewer/litert-models')
18
+ PREFETCH = ROOT/'models'/'upstream'
19
+ WORK = ROOT/'work'
20
+ ALLOWED = {'apache-2.0','mit','bsd-2-clause','bsd-3-clause'}
21
+ THREADS = max(1,int(os.environ.get('TORCH_NUM_THREADS','8')))
22
+ torch.set_num_threads(THREADS)
23
+
24
+
25
+ def read_json(p: Path): return json.loads(p.read_text(encoding='utf-8'))
26
+ def write_json(p: Path, x: Any): p.parent.mkdir(parents=True,exist_ok=True); p.write_text(json.dumps(x,indent=2,sort_keys=True,default=str)+'\n',encoding='utf-8')
27
+ def sha256(p: Path):
28
+ h=hashlib.sha256()
29
+ with p.open('rb') as f:
30
+ for c in iter(lambda:f.read(8*1024*1024),b''): h.update(c)
31
+ return h.hexdigest()
32
+ def token():
33
+ if not TOKEN_FILE.is_file(): raise RuntimeError(f'missing {TOKEN_FILE}')
34
+ v=TOKEN_FILE.read_text(encoding='utf-8-sig').strip().splitlines()[0].strip()
35
+ if len(v)<10: raise RuntimeError('HF token file is empty')
36
+ return v
37
+
38
+ def run(cmd, cwd=None, env=None):
39
+ print('+',' '.join(map(str,cmd)),flush=True)
40
+ subprocess.run(list(map(str,cmd)),cwd=cwd,env=env,check=True)
41
+
42
+ def inspect_tflite(p: Path):
43
+ from ai_edge_litert.interpreter import Interpreter
44
+ with p.open('rb') as f:
45
+ if f.read(8)[4:8] != b'TFL3': raise RuntimeError(f'bad flatbuffer: {p}')
46
+ i=Interpreter(model_path=str(p),num_threads=THREADS)
47
+ if not any(-1 in d.get('shape_signature',[]) for d in i.get_input_details()): i.allocate_tensors()
48
+ def d(t): return {'name':str(t.get('name','')),'shape':[int(x) for x in t['shape']],'shape_signature':[int(x) for x in t.get('shape_signature',t['shape'])],'dtype':str(np.dtype(t['dtype']))}
49
+ return {'inputs':[d(x) for x in i.get_input_details()],'outputs':[d(x) for x in i.get_output_details()]}
50
+
51
+ def direct_export(module: nn.Module, args: tuple[torch.Tensor,...], out: Path):
52
+ import litert_torch
53
+ out.parent.mkdir(parents=True,exist_ok=True)
54
+ module=module.cpu().eval(); args=tuple(x.detach().cpu() for x in args)
55
+ with torch.inference_mode(): edge=litert_torch.convert(module,args)
56
+ tmp=out.with_suffix('.partial.tflite'); edge.export(str(tmp)); del edge
57
+ check=inspect_tflite(tmp); tmp.replace(out); return check
58
+
59
+ def onnx_to_tflite(onnx: Path, out: Path, spatial_size: int=640):
60
+ temp=out.parent/(out.stem+'_onnx2tf')
61
+ shutil.rmtree(temp,ignore_errors=True); temp.mkdir(parents=True,exist_ok=True)
62
+ run([ROOT/'.venv-onnx/bin/python',PKG/'scripts/convert_onnx.py',str(onnx),str(temp),str(spatial_size)])
63
+ cands=sorted(temp.rglob('*float32.tflite')) or sorted(temp.rglob('*.tflite'), key=lambda p:p.stat().st_size, reverse=True)
64
+ if not cands: raise RuntimeError('onnx2tf produced no tflite')
65
+ shutil.copy2(cands[0],out); return inspect_tflite(out)
66
+
67
+ def all_specs():
68
+ m=read_json(MANIFEST_PATH)
69
+ out=[]
70
+ for group in ('general_models','fireviewer_models'):
71
+ for s in m[group]:
72
+ if s['key']=='fireviewer_dfine_m_strict_v1': s={**s,'route':'onnx_existing'}
73
+ out.append({**s,'group':group})
74
+ return out
75
+
76
+ def resolve_hf(api,s):
77
+ info=api.model_info(s['repo'])
78
+ lic=None
79
+ card=getattr(info,'card_data',None)
80
+ if card is not None:
81
+ try: lic=card.get('license')
82
+ except Exception: lic=getattr(card,'license',None)
83
+ tags=getattr(info,'tags',[]) or []
84
+ if not lic:
85
+ for x in tags:
86
+ if str(x).startswith('license:'): lic=str(x).split(':',1)[1]
87
+ if s['group']=='general_models' and str(lic or s['license']).lower() not in ALLOWED:
88
+ raise RuntimeError(f"non permissive license for public catalog: {lic}")
89
+ return {'repo_id':s['repo'],'sha':info.sha,'license':str(lic or s['license'])}
90
+
91
+ def prefetch(api):
92
+ ROOT.mkdir(parents=True,exist_ok=True); PREFETCH.mkdir(parents=True,exist_ok=True)
93
+ state={'created':time.time(),'destination':DEST_REPO,'models':{}}
94
+ for s in all_specs():
95
+ k=s['key']; print(f'[prefetch] {k}',flush=True)
96
+ try:
97
+ if s['route']=='rtmdet':
98
+ ext=PREFETCH/k; ext.mkdir(parents=True,exist_ok=True)
99
+ ck=ext/'model.pth'
100
+ if not ck.exists(): run(['curl','-L','--fail','--retry','4','-o',str(ck),s['checkpoint_url']])
101
+ state['models'][k]={'local_dir':str(ext),'checkpoint':str(ck),'sha256':sha256(ck),'license':s['license'],'external_url':s['checkpoint_url']}
102
+ continue
103
+ up=resolve_hf(api,s); loc=PREFETCH/k
104
+ snapshot_download(repo_id=s['repo'],revision=up['sha'],local_dir=str(loc),token=token(),ignore_patterns=['*.ckpt','last_ema.pth','checkpoint_best_ema.pth','checkpoints/*'])
105
+ state['models'][k]={**up,'local_dir':str(loc)}
106
+ if s['route']=='dinov3_fireviewer':
107
+ base=PREFETCH/(k+'_base')
108
+ snapshot_download(repo_id=s['base_repo'],revision=s['base_revision'],local_dir=str(base),token=token())
109
+ state['models'][k]['base_local_dir']=str(base)
110
+ except Exception as e:
111
+ state['models'][k]={'error':f'{type(e).__name__}: {e}'}
112
+ print(' !!',state['models'][k]['error'],flush=True)
113
+ write_json(ROOT/'prefetch_manifest.json',state); return state
114
+
115
+ def prefetched(k):
116
+ p=ROOT/'prefetch_manifest.json'
117
+ if not p.exists(): raise RuntimeError('run setup/prefetch first')
118
+ e=read_json(p)['models'].get(k,{})
119
+ if e.get('error'): raise RuntimeError(e['error'])
120
+ return e
121
+
122
+ class TimmWrap(nn.Module):
123
+ def __init__(self,m): super().__init__(); self.m=m
124
+ def forward(self,x):
125
+ f=self.m.forward_features(x); emb=self.m.forward_head(f,pre_logits=True); logits=self.m.forward_head(f)
126
+ return logits,emb
127
+ class ImageODWrap(nn.Module):
128
+ def __init__(self,m): super().__init__(); self.m=m
129
+ def forward(self,pixel_values,pixel_mask):
130
+ o=self.m(pixel_values=pixel_values,pixel_mask=pixel_mask,return_dict=True)
131
+ return o.logits,o.pred_boxes
132
+ class GroundWrap(nn.Module):
133
+ def __init__(self,m): super().__init__(); self.m=m
134
+ def forward(self,pixel_values,pixel_mask,input_ids,attention_mask,token_type_ids):
135
+ o=self.m(pixel_values=pixel_values,pixel_mask=pixel_mask,input_ids=input_ids.long(),attention_mask=attention_mask.long(),token_type_ids=token_type_ids.long(),return_dict=True)
136
+ return o.logits,o.pred_boxes
137
+ class OwlWrap(nn.Module):
138
+ def __init__(self,m): super().__init__(); self.m=m
139
+ def forward(self,pixel_values,input_ids,attention_mask):
140
+ o=self.m(pixel_values=pixel_values,input_ids=input_ids.long(),attention_mask=attention_mask.long(),return_dict=True)
141
+ return o.logits,o.pred_boxes
142
+ class DepthWrap(nn.Module):
143
+ def __init__(self,m): super().__init__(); self.m=m
144
+ def forward(self,pixel_values): return self.m(pixel_values=pixel_values,return_dict=True).predicted_depth
145
+ class SegWrap(nn.Module):
146
+ def __init__(self,m): super().__init__(); self.m=m
147
+ def forward(self,pixel_values): return self.m(pixel_values=pixel_values,return_dict=True).logits
148
+ class DictOutWrap(nn.Module):
149
+ def __init__(self,m): super().__init__(); self.m=m
150
+ def forward(self,x):
151
+ o=self.m(x)
152
+ if isinstance(o,dict): return tuple(o[k] for k in sorted(o))
153
+ if isinstance(o,(tuple,list)): return tuple(o)
154
+ return o
155
+
156
+ def processor_support_files(local: Path):
157
+ names={'preprocessor_config.json','processor_config.json','tokenizer.json','tokenizer_config.json','special_tokens_map.json','vocab.json','merges.txt','vocab.txt','sentencepiece.bpe.model','spiece.model','config.json'}
158
+ return [p for p in local.iterdir() if p.is_file() and p.name in names]
159
+
160
+ def convert_timm(s,e,out):
161
+ import timm
162
+ local=Path(e['local_dir']); cfg=read_json(local/'config.json'); arch=cfg.get('architecture') or cfg.get('model_name')
163
+ weights=next(iter(local.glob('*.safetensors')),None) or next(iter(local.glob('pytorch_model.bin')),None)
164
+ if not arch or not weights: raise RuntimeError('missing timm architecture/weights')
165
+ m=timm.create_model(arch,pretrained=False,checkpoint_path=str(weights)).eval()
166
+ inp=cfg.get('pretrained_cfg',{}).get('input_size',[3,224,224]); x=torch.zeros(1,int(inp[0]),int(inp[1]),int(inp[2]))
167
+ p=out/'model.tflite'; chk=direct_export(TimmWrap(m),(x,),p)
168
+ return [p],{'route':'timm','structural':chk,'outputs':['classification_logits','embedding']}
169
+
170
+ def convert_object_detection(s,e,out):
171
+ from transformers import AutoImageProcessor,AutoModelForObjectDetection
172
+ local=e['local_dir']; proc=AutoImageProcessor.from_pretrained(local,local_files_only=True,trust_remote_code=True)
173
+ m=AutoModelForObjectDetection.from_pretrained(local,local_files_only=True,trust_remote_code=True).eval()
174
+ # Use a static 640 square unless processor fixes another size.
175
+ img=np.zeros((640,640,3),dtype=np.uint8); d=proc(images=img,return_tensors='pt')
176
+ pv=d['pixel_values'].float(); pm=d.get('pixel_mask',torch.ones((pv.shape[0],pv.shape[-2],pv.shape[-1]),dtype=torch.int64)).to(torch.int32)
177
+ p=out/'model.tflite'; chk=direct_export(ImageODWrap(m),(pv,pm),p)
178
+ return [p],{'route':'transformers_object_detection','structural':chk,'outputs':['logits','pred_boxes']}
179
+
180
+ def convert_grounding(s,e,out):
181
+ from transformers import AutoProcessor,AutoModelForZeroShotObjectDetection
182
+ local=e['local_dir']; proc=AutoProcessor.from_pretrained(local,local_files_only=True); m=AutoModelForZeroShotObjectDetection.from_pretrained(local,local_files_only=True).eval()
183
+ d=proc(images=np.zeros((800,800,3),dtype=np.uint8),text='person. car. animal.',padding='max_length',max_length=32,truncation=True,return_tensors='pt')
184
+ pv=d['pixel_values'].float(); pm=d.get('pixel_mask',torch.ones((1,pv.shape[-2],pv.shape[-1]),dtype=torch.int32)).to(torch.int32)
185
+ ids=d['input_ids'].to(torch.int32); att=d['attention_mask'].to(torch.int32); tt=d.get('token_type_ids',torch.zeros_like(ids)).to(torch.int32)
186
+ p=out/'model.tflite'; chk=direct_export(GroundWrap(m),(pv,pm,ids,att,tt),p)
187
+ return [p],{'route':'grounding_dino','structural':chk,'outputs':['logits','pred_boxes'],'text_max_length':32}
188
+
189
+ def convert_owl(s,e,out):
190
+ from transformers import AutoProcessor,AutoModelForZeroShotObjectDetection
191
+ local=e['local_dir']; proc=AutoProcessor.from_pretrained(local,local_files_only=True); m=AutoModelForZeroShotObjectDetection.from_pretrained(local,local_files_only=True).eval()
192
+ d=proc(images=np.zeros((960,960,3),dtype=np.uint8),text=[['person','car','animal']],padding='max_length',max_length=16,truncation=True,return_tensors='pt')
193
+ p=out/'model.tflite'; chk=direct_export(OwlWrap(m),(d['pixel_values'].float(),d['input_ids'].to(torch.int32),d['attention_mask'].to(torch.int32)),p)
194
+ return [p],{'route':'owlv2','structural':chk,'outputs':['logits','pred_boxes'],'text_max_length':16}
195
+
196
+ def convert_depth(s,e,out):
197
+ from transformers import AutoImageProcessor,AutoModelForDepthEstimation
198
+ local=e['local_dir']; proc=AutoImageProcessor.from_pretrained(local,local_files_only=True); m=AutoModelForDepthEstimation.from_pretrained(local,local_files_only=True).eval()
199
+ d=proc(images=np.zeros((518,518,3),dtype=np.uint8),return_tensors='pt'); p=out/'model.tflite'; chk=direct_export(DepthWrap(m),(d['pixel_values'].float(),),p)
200
+ return [p],{'route':'depth','structural':chk,'outputs':['predicted_depth']}
201
+
202
+ def convert_seg(s,e,out):
203
+ from transformers import AutoImageProcessor,AutoModelForSemanticSegmentation
204
+ local=e['local_dir']; proc=AutoImageProcessor.from_pretrained(local,local_files_only=True); m=AutoModelForSemanticSegmentation.from_pretrained(local,local_files_only=True).eval()
205
+ d=proc(images=np.zeros((512,512,3),dtype=np.uint8),return_tensors='pt'); p=out/'model.tflite'; chk=direct_export(SegWrap(m),(d['pixel_values'].float(),),p)
206
+ return [p],{'route':'segmentation','structural':chk,'outputs':['segmentation_logits']}
207
+
208
+ def convert_onnx_existing(s,e,out):
209
+ local=Path(e['local_dir']); onnx=next(iter(local.rglob('*.onnx')),None)
210
+ if onnx is None: raise RuntimeError('no ONNX artifact upstream')
211
+ size=960 if s['key']=='fireviewer_yolo11m_strict_v1' else 704
212
+ p=out/'model.tflite'; chk=onnx_to_tflite(onnx,p,spatial_size=size); return [p],{'route':'onnx2tf','structural':chk,'source_onnx':onnx.name,'source_sha256':sha256(onnx),'image_size':size}
213
+
214
+ def convert_picodet(s,e,out):
215
+ local=Path(e['local_dir']); model=local/'inference.json'; params=local/'inference.pdiparams'
216
+ if not model.exists() or not params.exists(): raise RuntimeError('Paddle inference.json/pdiparams missing')
217
+ onnx=out/'model.onnx'
218
+ run([ROOT/'.venv-onnx/bin/paddle2onnx','--model_dir',str(local),'--model_filename','inference.json','--params_filename','inference.pdiparams','--save_file',str(onnx),'--opset_version','11','--enable_onnx_checker','True'])
219
+ p=out/'model.tflite'; chk=onnx_to_tflite(onnx,p); return [p],{'route':'paddle2onnx+onnx2tf','structural':chk}
220
+
221
+ def convert_rtmdet(s,e,out):
222
+ repo=ROOT/'vendor'/'mmyolo'; ck=Path(e['checkpoint']); cfg=repo/s['config']; od=out/'onnx'; od.mkdir(parents=True,exist_ok=True)
223
+ run([ROOT/'.venv-mm/bin/python',str(repo/'projects/easydeploy/tools/export.py'),str(cfg),str(ck),'--work-dir',str(od),'--img-size','640','640','--batch','1','--device','cpu','--opset','11','--model-only'],cwd=repo)
224
+ ons=sorted(od.rglob('*.onnx'),key=lambda p:p.stat().st_size,reverse=True)
225
+ if not ons: raise RuntimeError('MMYOLO EasyDeploy produced no ONNX')
226
+ p=out/'model.tflite'; chk=onnx_to_tflite(ons[0],p); return [p],{'route':'mmyolo-easydeploy+onnx2tf','structural':chk,'postprocess':'external RTMDet decode/NMS'}
227
+
228
+ def convert_rfdetr_medium(s,e,out):
229
+ from rfdetr import RFDETRMedium
230
+ local=Path(e['local_dir']); cands=list(local.rglob('checkpoint_best_total.pth')) or list(local.rglob('*.pth'))
231
+ if not cands: raise RuntimeError('RF-DETR checkpoint missing')
232
+ training=read_json(local/'training_config.json')
233
+ m=RFDETRMedium(pretrain_weights=str(cands[0]),num_classes=int(training['num_classes']),resolution=int(training['model_config']['resolution']),device='cpu')
234
+ res=m.export(format='litert',output_dir=str(out),quantization=None)
235
+ p=Path(res) if isinstance(res,(str,Path)) else None
236
+ if p is None or not p.is_file():
237
+ cs=list(out.rglob('*.tflite')); p=cs[0] if len(cs)==1 else None
238
+ if p is None: raise RuntimeError('RF-DETR produced no unique LiteRT file')
239
+ final=out/'model.tflite';
240
+ if p.resolve()!=final.resolve(): shutil.copy2(p,final)
241
+ return [final],{'route':'rfdetr-direct-litert','structural':inspect_tflite(final)}
242
+
243
+ def convert_dinov3_fv(s,e,out):
244
+ local=Path(e['local_dir']); adapter=local/'dinov3_adapter.py'; weights=local/'model.safetensors'
245
+ if not adapter.exists() or not weights.exists(): raise RuntimeError('FireViewer DINOv3 adapter/safetensors missing')
246
+ spec=importlib.util.spec_from_file_location('fv_dinov3_adapter',adapter); mod=importlib.util.module_from_spec(spec); assert spec and spec.loader
247
+ sys.modules[spec.name]=mod; spec.loader.exec_module(mod)
248
+ cls=getattr(mod,'DinoV3MultiTaskModel'); base=e.get('base_local_dir') or s['base_repo']
249
+ # Transformers 5.17 nests the existing ViT encoder under model; keep strict load.
250
+ import safetensors.torch as st
251
+ original_load=st.load_file
252
+ def load_compatible(*args,**kwargs):
253
+ state=original_load(*args,**kwargs)
254
+ return {(k.replace('backbone.layer.','backbone.model.layer.',1) if k.startswith('backbone.layer.') else k):v for k,v in state.items()}
255
+ st.load_file=load_compatible
256
+ try:obj=cls.from_safetensors(str(weights),model_id=str(base),revision=None,image_size=448,token=False)
257
+ finally:st.load_file=original_load
258
+ net=getattr(obj,'network',obj).eval(); p=out/'model.tflite'; chk=direct_export(DictOutWrap(net),(torch.zeros(1,3,448,448),),p)
259
+ return [p],{'route':'fireviewer-dinov3-adapter','structural':chk,'warning':'pilot; no public-release approval inherited'}
260
+
261
+ CONV={'timm':convert_timm,'object_detection':convert_object_detection,'grounding_dino':convert_grounding,'owlv2':convert_owl,'depth':convert_depth,'segmentation':convert_seg,'onnx_existing':convert_onnx_existing,'picodet':convert_picodet,'rtmdet':convert_rtmdet,'rfdetr_medium':convert_rfdetr_medium,'dinov3_fireviewer':convert_dinov3_fv}
262
+
263
+ def support_files(s,e):
264
+ if not e.get('local_dir'): return []
265
+ local=Path(e['local_dir'])
266
+ notices=[p for p in local.iterdir() if p.is_file() and (p.name.lower().startswith(('license','copying','notice')) or p.name in {'CHECKPOINT_LICENSE.md','CHECKPOINT_RIGHTS.md','CITATION.md','COMMERCIAL_USE.md','MODEL_RIGHTS_POLICY.md','RIGHTS_AND_ATTRIBUTION.md','THIRD_PARTY_NOTICES.md','labels.json'})]
267
+ return list(dict.fromkeys(processor_support_files(local)+notices))
268
+
269
+ def publish(api,s,e,out,arts,report):
270
+ group='models'
271
+ dest=DEST_REPO if s['group']=='general_models' else FIREVIEWER_DEST_REPO
272
+ if s['group']=='fireviewer_models' and not s.get('publish_default',False) and os.environ.get('PUBLISH_RESTRICTED_DINOV3','0')!='1':
273
+ return 'converted_local_only_rights_gate'
274
+ stage=out/'publish'; shutil.rmtree(stage,ignore_errors=True); stage.mkdir(parents=True)
275
+ for a in arts: shutil.copy2(a,stage/a.name)
276
+ for sf in support_files(s,e): shutil.copy2(sf,stage/sf.name)
277
+ source_readme=Path(e['local_dir'])/'README.md'
278
+ if source_readme.is_file(): shutil.copy2(source_readme,stage/'UPSTREAM_README.md')
279
+ contract={'schema':1,'key':s['key'],'title':s['title'],'task':s['task'],'route':report.get('route',s['route']),'upstream':s.get('repo') or s.get('checkpoint_url'),'upstream_revision':e.get('sha'),'license':e.get('license',s['license']),'tflite':[a.name for a in arts],'structural_only':not report.get('dynamic_validated',False),'benchmark':False,'report':report}
280
+ write_json(stage/'runtime_contract.json',contract)
281
+ write_json(stage/'artifact_manifest.json',{p.name:{'sha256':sha256(p),'bytes':p.stat().st_size} for p in stage.iterdir() if p.is_file()})
282
+ (stage/'README.md').write_text(f"---\nlicense: {str(e.get('license',s['license'])).lower()}\ntags:\n- litert\n- tflite\n- android\n- dataset-preparation\n---\n\n# {s['title']} — LiteRT conversion\n\nSource: `{s.get('repo') or s.get('checkpoint_url')}`.\n\nTask: {s['task']}.\n\nThis conversion is prepared for human-reviewed dataset preprocessing. No accuracy or speed benchmark is claimed by this batch. The serialized LiteRT files were only structurally loaded and tensor allocation was checked.\n\nConversion route: `{s['route']}`.\n",encoding='utf-8')
283
+ if report.get('dynamic_validated'):
284
+ (stage/'README.md').write_text(f"# {s['title']} — dynamic LiteRT conversion\n\nSource: `{s.get('repo')}`, revision `{e.get('sha')}`.\n\nThe image height and width are dynamic. No image resampling is embedded in this graph. The supported bounds and any alignment constraints are recorded in `runtime_contract.json`. Batch size is one. Inputs must be normalized as required by the upstream configuration.\n\nValidation includes real LiteRT inference at several square and rectangular dimensions and numerical comparison against the source model. See `validation.json` for exact shapes, tolerances and measured errors. These are conversion checks, not accuracy or performance benchmarks. Android execution has not been tested. The required CPU interpreter/delegate configuration, when applicable, is recorded in `runtime_contract.json`.\n\nPreserved upstream license and attribution files apply.\n",encoding='utf-8')
285
+ write_json(stage/'validation.json',report)
286
+ write_json(stage/'artifact_manifest.json',{p.name:{'sha256':sha256(p),'bytes':p.stat().st_size} for p in stage.iterdir() if p.is_file() and p.name!='artifact_manifest.json'})
287
+ with (ROOT/'.publish.lock').open('a') as lock:
288
+ fcntl.flock(lock,fcntl.LOCK_EX)
289
+ parent=api.repo_info(repo_id=dest,repo_type='model',revision='main').sha
290
+ commit=api.upload_folder(repo_id=dest,repo_type='model',folder_path=str(stage),path_in_repo=f'{group}/{s["key"]}',revision='main',parent_commit=parent,commit_message=f'Add LiteRT conversion: {s["title"]}')
291
+ paths=[f'{group}/{s["key"]}/{a.name}' for a in arts]
292
+ remote=api.get_paths_info(repo_id=dest,paths=paths,revision=commit.oid,repo_type='model')
293
+ indexed={p.path:p for p in remote}
294
+ for a,path in zip(arts,paths):
295
+ entry=indexed[path]
296
+ if entry.size!=a.stat().st_size or (entry.lfs and entry.lfs.sha256!=sha256(a)):
297
+ raise RuntimeError(f'Remote artifact mismatch: {path}')
298
+ write_json(out/'publication_receipt.json',{'repo':dest,'commit':commit.oid,'verified_paths':paths})
299
+ return f'{dest}/tree/{commit.oid}/{group}/{s["key"]}'
300
+
301
+ def process(api,s):
302
+ k=s['key']; base=WORK/k; base.mkdir(parents=True,exist_ok=True); attempt=Path(tempfile.mkdtemp(prefix='attempt-',dir=base)); st={'key':k,'title':s['title'],'phase':'starting'}
303
+ try:
304
+ e=prefetched(k); st['upstream']=e; st['phase']='converting'; write_json(base/'status.json',st)
305
+ arts,rep=CONV[s['route']](s,e,attempt); rep['artifacts']=[{'name':p.name,'bytes':p.stat().st_size,'sha256':sha256(p),'tensors':inspect_tflite(p)} for p in arts]
306
+ st['report']=rep; st['phase']='publishing'; write_json(base/'status.json',st)
307
+ st['destination']=publish(api,s,e,attempt,arts,rep); st['phase']='pushed' if '/tree/' in st['destination'] else 'converted_local_only'
308
+ except Exception as ex:
309
+ st.update(phase='failed',error=f'{type(ex).__name__}: {ex}',traceback=traceback.format_exc())
310
+ print(st['traceback'],flush=True)
311
+ finally:
312
+ write_json(base/'status.json',st); gc.collect();
313
+ if torch.cuda.is_available():
314
+ try: torch.cuda.empty_cache()
315
+ except Exception: pass
316
+ return st
317
+
318
+ def main():
319
+ ap=argparse.ArgumentParser(); ap.add_argument('--prefetch-only',action='store_true'); ap.add_argument('--general-only',action='store_true'); ap.add_argument('--fireviewer-only',action='store_true'); ap.add_argument('models',nargs='*'); a=ap.parse_args()
320
+ api=HfApi(token=token())
321
+ if a.prefetch_only:
322
+ api.repo_info(repo_id=DEST_REPO,repo_type='model'); prefetch(api); return
323
+ specs=all_specs()
324
+ if a.general_only: specs=[s for s in specs if s['group']=='general_models']
325
+ if a.fireviewer_only: specs=[s for s in specs if s['group']=='fireviewer_models']
326
+ if a.models: specs=[s for s in specs if s['key'] in set(a.models)]
327
+ results={}
328
+ for i,s in enumerate(specs,1):
329
+ print(f'\n[{i}/{len(specs)}] {s["key"]}',flush=True); results[s['key']]=process(api,s); print(' ->',results[s['key']]['phase'],flush=True)
330
+ write_json(WORK/'summary.json',results)
331
+ print('\nPUSHED:',', '.join(k for k,v in results.items() if v['phase']=='pushed') or 'none')
332
+ print('FAILED:',', '.join(k for k,v in results.items() if v['phase']=='failed') or 'none')
333
+ print('LOCAL ONLY:',', '.join(k for k,v in results.items() if v['phase']=='converted_local_only') or 'none')
334
+
335
+ if __name__=='__main__': main()
conversion/patch_onnx2tf_gather_signature.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Repair empty static shape signatures in onnx2tf 2.6.9 GatherElements.
2
+
3
+ An absent optional shape_signature means the static tensor.shape, not rank zero.
4
+ The final D-FINE graph still has to match the original ONNX numerically.
5
+ """
6
+ from pathlib import Path
7
+ import importlib.util
8
+ p=Path(importlib.util.find_spec('onnx2tf').origin).parent/'tflite_builder/op_builders/index.py'
9
+ text=p.read_text();start=text.index('def build_gather_elements_op(');end=text.find('\ndef ',start+5)
10
+ part=text[start:end if end!=-1 else len(text)]
11
+ for variable in ('data','indices','output'):
12
+ old=f'if {variable}_tensor is not None and {variable}_tensor.shape_signature is not None'
13
+ new=f'if {variable}_tensor is not None and {variable}_tensor.shape_signature is not None and len({variable}_tensor.shape_signature) == len({variable}_shape)'
14
+ if new not in part:
15
+ assert old in part;part=part.replace(old,new)
16
+ updated=text[:start]+part+(text[end:] if end!=-1 else '')
17
+ if updated!=text:
18
+ p.with_suffix('.py.vds-original').write_text(text);p.write_text(updated)
19
+ print('GatherElements static signature fallback installed')
conversion/prefetch_manifest.json ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "created": 1789851716.286558,
3
+ "destination": "Charlbi/Lite_rt_prepared_for_android_dataset_builder",
4
+ "models": {
5
+ "fireviewer_dfine_m_strict_v1": {
6
+ "repo_id": "fireviewer/fire-smoke-dfine-m-strict-v1",
7
+ "sha": "a4d15bae27f8deaeb9f89e7e118b4643cddfecad",
8
+ "license": "other",
9
+ "local_dir": "/workspace/vds-litert-batch2/models/upstream/fireviewer_dfine_m_strict_v1"
10
+ },
11
+ "fireviewer_rtdetr_v2_r50": {
12
+ "repo_id": "fireviewer/rtdetr-v2-r50-fire-smoke",
13
+ "sha": "b16bc79df05a69bfd90a7130d77fdbbd0d556465",
14
+ "license": "other",
15
+ "local_dir": "/workspace/vds-litert-batch2/models/upstream/fireviewer_rtdetr_v2_r50"
16
+ },
17
+ "fireviewer_yolo11m_strict_v1": {
18
+ "repo_id": "fireviewer/fire-smoke-yolo11m-strict-v1",
19
+ "sha": "065979cd2300c27eeb355ab1e1524d8223d0b276",
20
+ "license": "agpl-3.0",
21
+ "local_dir": "/workspace/vds-litert-batch2/models/upstream/fireviewer_yolo11m_strict_v1"
22
+ },
23
+ "fireviewer_rfdetr_medium_v110": {
24
+ "repo_id": "fireviewer/fire-smoke-rfdetr-medium-v110-strict-v1",
25
+ "sha": "901382d9c8eef2af8b4c13640413c03dbfdbeec2",
26
+ "license": "other",
27
+ "local_dir": "/workspace/vds-litert-batch2/models/upstream/fireviewer_rfdetr_medium_v110"
28
+ },
29
+ "fireviewer_dinov3_pointing_pilot_v1": {
30
+ "repo_id": "fireviewer/dinov3-pointing-pilot-v1",
31
+ "sha": "bd4cc7e15e296fa95b1cd9478f6952a21a4201ec",
32
+ "license": "other",
33
+ "local_dir": "/workspace/vds-litert-batch2/models/upstream/fireviewer_dinov3_pointing_pilot_v1",
34
+ "base_local_dir": "/workspace/vds-litert-batch2/models/upstream/fireviewer_dinov3_pointing_pilot_v1_base"
35
+ },
36
+ "fireviewer_segformer_b2_baseline_v1": {
37
+ "error": "RepositoryNotFoundError: 404 Client Error. (Request ID: Root=1-6aaef886-7a05968f44ee08895dcd3a44;e2f3be59-92ac-4cb9-9b39-b321c8349160)\n\nRepository Not Found for url: https://huggingface.co/api/models/fireviewer/segformer-b2-fire-smoke-baseline-v1.\nPlease make sure you specified the correct `repo_id` and `repo_type`.\nIf you are trying to access a private or gated repo, make sure you are authenticated and your token has the required permissions.\nFor more details, see https://huggingface.co/docs/huggingface_hub/authentication"
38
+ }
39
+ }
40
+ }
conversion/prepare_learning_backbone.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Prepare frozen inference components; never publish incomplete conversions."""
2
+ import os,sys,json,traceback
3
+ from pathlib import Path
4
+ os.environ.update(VDS_BATCH2_ROOT='/workspace/vds-litert-batch2',HF_TOKEN_FILE='/workspace/.secrets/hf_token',TORCH_NUM_THREADS='6')
5
+ root=Path('/workspace/vds-litert-batch2');sys.path.insert(0,str(root/'package/scripts'))
6
+ import convert_batch2 as worker
7
+ key=sys.argv[1];out=root/'learning-backbones'/key;out.mkdir(parents=True,exist_ok=True)
8
+ status={'key':key,'phase':'converting_frozen_component','published':False}
9
+ (out/'status.json').write_text(json.dumps(status))
10
+ try:
11
+ spec=next(s for s in worker.all_specs() if s['key']==key);entry=worker.prefetched(key)
12
+ artifacts,report=worker.CONV[spec['route']](spec,entry,out)
13
+ status.update(phase='converted_frozen_component',artifacts=[str(p) for p in artifacts],report=report)
14
+ except Exception as e:
15
+ status.update(phase='failed',error=str(e),traceback=traceback.format_exc());print(status['traceback'],flush=True)
16
+ (out/'status.json').write_text(json.dumps(status,indent=2)+'\n')
17
+ print(json.dumps(status),flush=True)
conversion/protect_dfine_arena.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Preserve selected decoder activations until a final, tiny read operation.
2
+
3
+ No prediction arithmetic or weight changes. These reads prevent premature
4
+ in-place reuse in the LiteRT arena; the guard values are not model predictions.
5
+ """
6
+ import os
7
+ os.environ.update(CUDA_VISIBLE_DEVICES='',TF_CPP_MIN_LOG_LEVEL='3')
8
+ import json,flatbuffers,numpy as np
9
+ from pathlib import Path
10
+ from tensorflow.lite.python import schema_py_generated as schema
11
+ r=Path('/workspace/vds-litert-batch2/learning-backbones/fireviewer_dfine_m_strict_v1')
12
+ p=r/'simplified-converted/model-cropped.tflite';m=schema.ModelT.InitFromObj(schema.Model.GetRootAsModel(p.read_bytes(),0));g=m.subgraphs[0]
13
+ protection=json.loads((r/'arena-protection.json').read_text());lookup={t.name.decode():i for i,t in enumerate(g.tensors)}
14
+ def tensor(name,shape,dtype,data=None):
15
+ t=schema.TensorT();t.name=name.encode();t.shape=np.array(shape,np.int32);t.type=dtype;t.hasRank=True;t.buffer=0
16
+ if data is not None:
17
+ b=schema.BufferT();b.data=np.frombuffer(data.tobytes(),np.uint8);t.buffer=len(m.buffers);m.buffers.append(b)
18
+ g.tensors.append(t);return len(g.tensors)-1
19
+ def opcode(code):
20
+ o=schema.OperatorCodeT();o.builtinCode=code;o.deprecatedBuiltinCode=min(code,127);o.version=1;m.operatorCodes.append(o);return len(m.operatorCodes)-1
21
+ reshape=opcode(schema.BuiltinOperator.RESHAPE);gather=opcode(schema.BuiltinOperator.GATHER)
22
+ flat_shape=tensor('arena_guard_shape',[1],schema.TensorType.INT32,np.array([-1],np.int32))
23
+ zero=tensor('arena_guard_zero',[],schema.TensorType.INT32,np.array(0,np.int32))
24
+ for n,item in enumerate(protection['tensors']):
25
+ selected=lookup[item['name']]
26
+ producer=next(o for o in g.operators if selected in o.outputs)
27
+ assert m.operatorCodes[producer.opcodeIndex].builtinCode==schema.BuiltinOperator.SOFTMAX
28
+ # The softmax input aliases corner logits later read by the LQE branch.
29
+ # Retaining this input prevents softmax from overwriting that shared storage.
30
+ source=int(producer.inputs[0]);original=g.tensors[source];count=int(np.prod(original.shape));assert count>0
31
+ flat=tensor('arena_guard_flat_'+str(n),[count],original.type)
32
+ op=schema.OperatorT();op.opcodeIndex=reshape;op.inputs=np.array([source,flat_shape],np.int32);op.outputs=np.array([flat],np.int32);op.builtinOptionsType=schema.BuiltinOptions.ReshapeOptions;op.builtinOptions=schema.ReshapeOptionsT();op.builtinOptions.newShape=np.array([-1],np.int32);g.operators.append(op)
33
+ scalar=tensor('arena_guard_read_'+str(n),[],original.type)
34
+ op=schema.OperatorT();op.opcodeIndex=gather;op.inputs=np.array([flat,zero],np.int32);op.outputs=np.array([scalar],np.int32);op.builtinOptionsType=schema.BuiltinOptions.GatherOptions;op.builtinOptions=schema.GatherOptionsT();op.builtinOptions.axis=0;op.builtinOptions.batchDims=0;g.operators.append(op)
35
+ b=flatbuffers.Builder(1024);b.Finish(m.Pack(b),file_identifier=b'TFL3');dest=r/'model-protected.tflite';dest.write_bytes(b.Output())
36
+ print(json.dumps({'artifact':str(dest),'protected_tensors':len(protection['tensors']),'protected_elements':sum(int(np.prod(t['shape'])) for t in protection['tensors'])}),flush=True)
conversion/publish_learning.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Publish executed trainable variants beside immutable inference families."""
2
+ import json,sys,hashlib,shutil,fcntl
3
+ from pathlib import Path
4
+ from huggingface_hub import HfApi
5
+ root=Path('/workspace/vds-litert-batch2')
6
+ api=HfApi(token=(Path('/workspace/.secrets/hf_token').read_text().strip()))
7
+ manifest=json.loads((root/'package/model_manifest.json').read_text());specs={s['key']:s for g in ('general_models','fireviewer_models') for s in manifest[g]}
8
+ upstreams=json.loads((root/'prefetch_manifest.json').read_text())['models']
9
+ upstreams.update(json.loads((root/'previous-prefetch.json').read_text()))
10
+ for s in json.loads((root/'previous-models.json').read_text()):specs[s['key']]={**s,'title':s['key']}
11
+ def sha(p):return hashlib.sha256(p.read_bytes()).hexdigest()
12
+ for key in sys.argv[1:]:
13
+ directory=root/'learning'/key;report=json.loads((directory/'training_validation.json').read_text());source=json.loads((directory/'source.json').read_text())
14
+ assert report['validation_passed'] and report['optimizer_resume_exact'] and report['restart_logits_exact']
15
+ assert sha(directory/'model.tflite')==report['sha256']
16
+ spec=specs[key];upstream=upstreams[key]
17
+ destination='fireviewer/litert-models' if key.startswith('fireviewer_') else 'Charlbi/Lite_rt_prepared_for_android_dataset_builder'
18
+ if key.startswith('fireviewer_') and not spec.get('publish_default',False):
19
+ # User explicitly requested FireViewer delivery; the pilot permits a
20
+ # private research copy, never a public release under this workflow.
21
+ assert source.get('private_pilot_only') and api.repo_info(destination).private
22
+ stage=directory/'publish';stage.mkdir(exist_ok=True)
23
+ shutil.copy2(directory/'model.tflite',stage/'model.tflite')
24
+ labels=source.get('labels') or json.loads((root/'learning/imagenet-labels.json').read_text())
25
+ labels=[label if labels.count(label)==1 else f'{label} [class {i}]' for i,label in enumerate(labels)]
26
+ prep=source.get('pretrained_cfg',{})
27
+ config={'task':'classification' if prep else 'object_detection','adapter':'classification' if prep else 'yolo','inputWidth':224,'inputHeight':224,'inputLayout':'NCHW','inputType':'FLOAT32','mean':0.,'std':1.,'resizeMode':'stretch','labels':labels,'threads':2,'outputIndex':0,'threshold':.1,'scoreActivation':'softmax' if prep else 'none','dynamicMinSize':128,'dynamicMaxSize':1280,'dynamicStride':32 if key=='convformer_s18' or not prep else 1}
28
+ if prep:
29
+ config.update(channelMean=[v*255 for v in prep['mean']],channelStd=[v*255 for v in prep['std']])
30
+ training={'targetEncoding':'one_hot','targetShape':[1,len(labels)],'inferOutputs':['scores']}
31
+ else:
32
+ config.update(inputWidth=source.get('app_size',320),inputHeight=source.get('app_size',320),mean=0.,std=255.,resizeMode='letterbox' if source['kind']=='yolo' else 'stretch',outputLayout='BCN',coordinates='pixels',outputIndexBoxes=0,outputIndexScores=1)
33
+ if source.get('channelMean'):config.update(channelMean=source['channelMean'],channelStd=source['channelStd'])
34
+ training={'targetEncoding':'boxes_xyxy_class_mask','targetShape':[1,100,6],'inferOutputs':['detections']}
35
+ training.update(trainSignature='train',inferSignature='infer',saveSignature='save',restoreSignature='restore',imageInput='x',targetInput='y',lossOutput='loss',checkpointInput='checkpoint_path',learningRateInput='learning_rate',scope=report['training_scope'])
36
+ config['training']=training
37
+ config['isRgb']=source.get('isRgb',True)
38
+ config['dynamicStride']=report.get('variable_image_preprocessing',{}).get('stride',32 if key=='convformer_s18' else 1)
39
+ config['dynamicMinSize']=source.get('dynamic_min_size',128)
40
+ if (directory/'android_model_config.json').exists():config=json.loads((directory/'android_model_config.json').read_text())
41
+ # The default graph's output order is not necessarily the named detection
42
+ # signature order. Resolve actual tensor indices for the Android adapter.
43
+ import tensorflow as tf
44
+ engine=tf.lite.Interpreter(model_path=str(directory/'model.tflite'),experimental_op_resolver_type=tf.lite.experimental.OpResolverType.BUILTIN_WITHOUT_DEFAULT_DELEGATES)
45
+ named=engine.get_signature_runner('infer').get_output_details()
46
+ main=engine.get_output_details()
47
+ output_indices={name:next(j for j,d in enumerate(main) if d['index']==details['index']) for name,details in named.items()}
48
+ config['namedOutputIndices']=output_indices
49
+ if config['adapter']=='yolo':
50
+ config['outputIndex']=output_indices['detections']
51
+ config.pop('outputIndexBoxes',None);config.pop('outputIndexScores',None)
52
+ del engine
53
+ (stage/'android_model_config.json').write_text(json.dumps(config,indent=2)+'\n')
54
+ report['runtime_requirement']='LiteRT builtin CPU; disable default XNNPACK delegates; Select TF Ops required for checkpoint Save/Restore'
55
+ report['android_runtime_dependencies']=['org.tensorflow:tensorflow-lite:2.16.1','org.tensorflow:tensorflow-lite-select-tf-ops:2.16.1']
56
+ report['frozen_backbone_sha256']=sha(Path(source['backbone']))
57
+ report['head_initialization']=source.get('head_initialization','source_model_or_identity_adapter')
58
+ builder='build_trainable_fire_multitask.py' if source.get('kind')=='multitask' else 'build_trainable_classifier.py' if prep else 'build_trainable_heatmap.py' if source.get('kind')=='heatmap' else 'build_trainable_detector.py'
59
+ report['conversion_source_sha256']={n:sha(root/n) for n in ['merge_training_graph.py',builder]}
60
+ if source.get('parity_report'):
61
+ parity=Path(source['parity_report']);shutil.copy2(parity,stage/'source_parity.json')
62
+ report['source_parity_report']='source_parity.json'
63
+ if (directory/'runtime216_validation.json').exists():
64
+ compatible=json.loads((directory/'runtime216_validation.json').read_text());assert compatible['sha256']==report['sha256']
65
+ shutil.copy2(directory/'runtime216_validation.json',stage/'runtime216_validation.json')
66
+ (stage/'training_validation.json').write_text(json.dumps(report,indent=2)+'\n')
67
+ contract={'schema':2,'key':key+'_learning','source_model':key,'task':config['task'],'upstream':spec.get('repo') or spec.get('checkpoint_url'),'upstream_revision':upstream.get('sha') or upstream.get('sha256'),'backbone_frozen':True,'training_scope':report['training_scope'],'report':report,'optimizer':'momentum SGD, momentum=0.9','checkpoint_contains':['trainable parameters','momentum buffers','global step'],'requires_select_tf_ops':True,'android_configuration':'android_model_config.json'}
68
+ (stage/'runtime_contract.json').write_text(json.dumps(contract,indent=2)+'\n')
69
+ local=Path(upstream['local_dir'])
70
+ for p in local.iterdir():
71
+ if p.is_file() and (p.name.lower().startswith(('license','notice','copying')) or p.name in ('CHECKPOINT_LICENSE.md','CHECKPOINT_RIGHTS.md','CITATION.md','COMMERCIAL_USE.md','MODEL_RIGHTS_POLICY.md','RIGHTS_AND_ATTRIBUTION.md','THIRD_PARTY_NOTICES.md')):shutil.copy2(p,stage/p.name)
72
+ if (local/'README.md').is_file():shutil.copy2(local/'README.md',stage/'UPSTREAM_README.md')
73
+ if key=='rtmdet_tiny':shutil.copy2(root/'vendor/mmyolo/LICENSE',stage/'LICENSE')
74
+ (stage/'README.md').write_text(f"# {spec['title']} — Android learning variant\n\nSource: `{spec.get('repo')}`, revision `{upstream.get('sha')}`.\n\nThis is a real mutable LiteRT model with `infer`, `train`, `save`, and `restore` signatures. Training executes on Android using the image and reviewed targets. Trainable scope: **{report['training_scope']}**. The visual backbone remains frozen. {report.get('limitation','')}\n\nThis learning variant is stored separately under `models/{key}_learning` and has its own explicit Android configuration.\n\nVariable image dimensions: `{report.get('variable_image_preprocessing', {'mode':'native_dynamic'})}`. The Android configuration chooses a bounded input canvas; the image may have any original size. Images are processed individually, so a dataset need not have homogeneous dimensions.\n\nCheckpoints include parameters, momentum and step. Validation executed actual updates, changed parameters, closed/reopened the interpreter, restored identical predictions and reproduced the next optimizer update. See `training_validation.json`. Tests use synthetic supervision to verify execution and persistence; no accuracy or forgetting benchmark is claimed. Android device execution: `{report.get('android_device_tested',False)}`.\n\nUse LiteRT CPU with XNNPACK disabled and Select TF Ops for checkpoint operations. The default label space is retained; changing class count requires rebuilding the head with the supplied conversion sources. Use only human-reviewed labels for training. Retain replay examples and validate a candidate before activating it.\n\nUpstream licenses and attributions continue to apply.\n")
75
+ (stage/'artifact_manifest.json').write_text(json.dumps({p.name:{'sha256':sha(p),'bytes':p.stat().st_size} for p in stage.iterdir() if p.is_file() and p.name!='artifact_manifest.json'},indent=2)+'\n')
76
+ with (root/'.publish.lock').open('a') as lock:
77
+ fcntl.flock(lock,fcntl.LOCK_EX);parent=api.repo_info(destination,repo_type='model').sha
78
+ commit=api.upload_folder(repo_id=destination,repo_type='model',folder_path=str(stage),path_in_repo=f'models/{key}_learning',parent_commit=parent,commit_message=f'Add on-device learning and optimizer resume: {key}')
79
+ path=f'models/{key}_learning/model.tflite';remote=api.get_paths_info(destination,[path],revision=commit.oid)[0]
80
+ assert remote.size==report['bytes'] and remote.lfs.sha256==report['sha256']
81
+ receipt={'repo':destination,'commit':commit.oid,'path':path,'sha256':report['sha256'],'bytes':report['bytes'],'remote_verified':True}
82
+ (directory/'publication_receipt.json').write_text(json.dumps(receipt,indent=2)+'\n');print(json.dumps(receipt),flush=True)
conversion/static_topk_lowering.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Keep detector proposal TopK as a native TFLite operation, not StableHLO sort."""
2
+ import torch
3
+ from litert_torch import fx_infra
4
+ from litert_torch.backend.lowerings import registry, utils
5
+ from litert_torch.backend.experimental.torch_tfl._lowerings import _ir_operation
6
+ from litert_converter.mlir import ir
7
+
8
+ @torch.library.custom_op('vds_static::topk', mutates_args=())
9
+ def native_topk(x: torch.Tensor, k: int) -> tuple[torch.Tensor, torch.Tensor]:
10
+ return torch.topk(x, k, dim=-1, largest=True, sorted=True)
11
+
12
+ @native_topk.register_fake
13
+ def fake_topk(x, k):
14
+ shape = (*x.shape[:-1], k)
15
+ return x.new_empty(shape), x.new_empty(shape, dtype=torch.int64)
16
+
17
+ @registry.lower(torch.ops.vds_static.topk.default)
18
+ def lower_topk(ctx, x, k):
19
+ values, indices = utils.node_meta_to_ir_types(ctx.node)
20
+ i32 = ir.IntegerType.get_signless(32)
21
+ index_type = ir.RankedTensorType.get(indices.shape, i32)
22
+ result = _ir_operation('tfl.topk_v2', results=[values, index_type],
23
+ operands=[x, utils.splat(k, i32, [])])
24
+ return result[0], _ir_operation('tfl.cast', results=[indices], operands=[result[1]])
25
+
26
+ def topk(x, k, dim=-1, largest=True, sorted=True):
27
+ dim = dim % x.ndim
28
+ moved = x.movedim(dim, -1)
29
+ values, indices = torch.ops.vds_static.topk(moved if largest else -moved, k)
30
+ if not largest:
31
+ values = -values
32
+ return values.movedim(-1, dim), indices.movedim(-1, dim)
33
+
34
+ fx_infra.decomp.update_pre_convert_decomp({torch.ops.aten.topk.default: topk})
35
+ fx_infra.decomp.update_pre_lower_decomp({torch.ops.aten.topk.default: topk})
models/fireviewer_dinov3_pointing_pilot_v1_learning/README.md CHANGED
@@ -4,7 +4,7 @@ Source: `fireviewer/dinov3-pointing-pilot-v1`, revision `bd4cc7e15e296fa95b1cd94
4
 
5
  This is a real mutable LiteRT model with `infer`, `train`, `save`, and `restore` signatures. Training executes on Android using the image and reviewed targets. Trainable scope: **four_task_output_adaptation_only**. The visual backbone remains frozen. Frozen DINOv3 and DPT; only task-output calibration/mixing adapts. Pilot, private research use.
6
 
7
- The original inference family is retained under `models/fireviewer_dinov3_pointing_pilot_v1`. This variant has its own explicit Android configuration.
8
 
9
  Variable image dimensions: `{'mode': 'bilinear_stretch_inside_graph', 'native_backbone_shape': [1, 3, 448, 448]}`. The Android configuration chooses a bounded input canvas; the image may have any original size. Images are processed individually, so a dataset need not have homogeneous dimensions.
10
 
 
4
 
5
  This is a real mutable LiteRT model with `infer`, `train`, `save`, and `restore` signatures. Training executes on Android using the image and reviewed targets. Trainable scope: **four_task_output_adaptation_only**. The visual backbone remains frozen. Frozen DINOv3 and DPT; only task-output calibration/mixing adapts. Pilot, private research use.
6
 
7
+ This learning variant is stored separately under `models/fireviewer_dinov3_pointing_pilot_v1_learning` and has its own explicit Android configuration.
8
 
9
  Variable image dimensions: `{'mode': 'bilinear_stretch_inside_graph', 'native_backbone_shape': [1, 3, 448, 448]}`. The Android configuration chooses a bounded input canvas; the image may have any original size. Images are processed individually, so a dataset need not have homogeneous dimensions.
10
 
models/fireviewer_dinov3_pointing_pilot_v1_learning/artifact_manifest.json CHANGED
@@ -4,7 +4,7 @@
4
  "bytes": 368
5
  },
6
  "README.md": {
7
- "sha256": "f0972394a0439cc51556179a5d109a9b5612cdbe2eed3fd2a6be46330d49e1e4",
8
  "bytes": 1742
9
  },
10
  "UPSTREAM_README.md": {
 
4
  "bytes": 368
5
  },
6
  "README.md": {
7
+ "sha256": "bba9d42bf0a747f2e46c1390c218d87e6826a7fdf400ea5c26b818cc26eed3c4",
8
  "bytes": 1742
9
  },
10
  "UPSTREAM_README.md": {
models/fireviewer_rfdetr_medium_v110_learning/README.md CHANGED
@@ -4,7 +4,7 @@ Source: `fireviewer/fire-smoke-rfdetr-medium-v110-strict-v1`, revision `901382d9
4
 
5
  This is a real mutable LiteRT model with `infer`, `train`, `save`, and `restore` signatures. Training executes on Android using the image and reviewed targets. Trainable scope: **candidate_classification_and_box_adaptation_only**. The visual backbone remains frozen. Does not create proposals absent from frozen detector
6
 
7
- The original inference family is retained under `models/fireviewer_rfdetr_medium_v110`. This variant has its own explicit Android configuration.
8
 
9
  Variable image dimensions: `{'mode': 'bilinear_stretch_inside_graph', 'native_backbone_shape': [1, 3, 576, 576]}`. The Android configuration chooses a bounded input canvas; the image may have any original size. Images are processed individually, so a dataset need not have homogeneous dimensions.
10
 
 
4
 
5
  This is a real mutable LiteRT model with `infer`, `train`, `save`, and `restore` signatures. Training executes on Android using the image and reviewed targets. Trainable scope: **candidate_classification_and_box_adaptation_only**. The visual backbone remains frozen. Does not create proposals absent from frozen detector
6
 
7
+ This learning variant is stored separately under `models/fireviewer_rfdetr_medium_v110_learning` and has its own explicit Android configuration.
8
 
9
  Variable image dimensions: `{'mode': 'bilinear_stretch_inside_graph', 'native_backbone_shape': [1, 3, 576, 576]}`. The Android configuration chooses a bounded input canvas; the image may have any original size. Images are processed individually, so a dataset need not have homogeneous dimensions.
10
 
models/fireviewer_rfdetr_medium_v110_learning/artifact_manifest.json CHANGED
@@ -8,7 +8,7 @@
8
  "bytes": 253
9
  },
10
  "README.md": {
11
- "sha256": "d9bff0f5082a744063b9a3fee74e970fa76a2b4ec26ca8ccd958216480df1104",
12
  "bytes": 1730
13
  },
14
  "UPSTREAM_README.md": {
 
8
  "bytes": 253
9
  },
10
  "README.md": {
11
+ "sha256": "114bdc9b3998b19f6a6314092f3ac6fb54e0e9f4b03705347ab65017c20bd6ca",
12
  "bytes": 1730
13
  },
14
  "UPSTREAM_README.md": {
models/fireviewer_rtdetr_v2_r50_learning/README.md CHANGED
@@ -4,7 +4,7 @@ Source: `fireviewer/rtdetr-v2-r50-fire-smoke`, revision `b16bc79df05a69bfd90a713
4
 
5
  This is a real mutable LiteRT model with `infer`, `train`, `save`, and `restore` signatures. Training executes on Android using the image and reviewed targets. Trainable scope: **candidate_classification_and_box_adaptation_only**. The visual backbone remains frozen. Does not create proposals absent from frozen detector
6
 
7
- The original inference family is retained under `models/fireviewer_rtdetr_v2_r50`. This variant has its own explicit Android configuration.
8
 
9
  Variable image dimensions: `{'mode': 'bilinear_stretch_inside_graph', 'native_backbone_shape': [1, 3, 768, 768]}`. The Android configuration chooses a bounded input canvas; the image may have any original size. Images are processed individually, so a dataset need not have homogeneous dimensions.
10
 
 
4
 
5
  This is a real mutable LiteRT model with `infer`, `train`, `save`, and `restore` signatures. Training executes on Android using the image and reviewed targets. Trainable scope: **candidate_classification_and_box_adaptation_only**. The visual backbone remains frozen. Does not create proposals absent from frozen detector
6
 
7
+ This learning variant is stored separately under `models/fireviewer_rtdetr_v2_r50_learning` and has its own explicit Android configuration.
8
 
9
  Variable image dimensions: `{'mode': 'bilinear_stretch_inside_graph', 'native_backbone_shape': [1, 3, 768, 768]}`. The Android configuration chooses a bounded input canvas; the image may have any original size. Images are processed individually, so a dataset need not have homogeneous dimensions.
10
 
models/fireviewer_rtdetr_v2_r50_learning/artifact_manifest.json CHANGED
@@ -4,7 +4,7 @@
4
  "bytes": 355
5
  },
6
  "README.md": {
7
- "sha256": "0ce1d205407bdd4ec4345c3a998e3b8bb5f93924e56af1284784ca41bb10acd4",
8
  "bytes": 1694
9
  },
10
  "UPSTREAM_README.md": {
 
4
  "bytes": 355
5
  },
6
  "README.md": {
7
+ "sha256": "5d72d8cb0d250e44207149f8f4b3cce47affb5c64f40fc57d4bfc13c0a4aab90",
8
  "bytes": 1694
9
  },
10
  "UPSTREAM_README.md": {
reports/android/fire-build-final.log ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ warning: [options] system modules path not set in conjunction with -source 11
2
+ 1 warning
3
+ /workspace/qa/vds-model-training/training-check.jar
reports/android/fireviewer_yolo11m_strict_v1-final.log ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CANNOT LINK EXECUTABLE "/system/bin/dex2oat": "/system/lib64/libart-compiler.so" is 64-bit instead of 32-bit
2
+ FIRE_CHECK opening yolo-final.tflite
3
+ INFO: Initialized TensorFlow Lite runtime.
4
+ INFO: Created TensorFlow Lite delegate for select TF ops.
5
+ native : core/platform/cpu_feature_guard.cc:210 This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
6
+ To enable the following instructions: SSE3 SSE4.1 SSE4.2, in other operations, rebuild TensorFlow with the appropriate compiler flags.
7
+ INFO: TfLiteFlexDelegate delegate: 0 nodes delegated out of 1270 nodes with 0 partitions.
8
+
9
+ VERBOSE: Replacing 9 out of 29 node(s) with delegate (TfLiteFlexDelegate) node, yielding 3 partitions for the whole graph.
10
+ native : core/framework/node_def_util.cc:676 NodeDef mentions attribute use_inter_op_parallelism which is not in the op definition: Op<name=Restore; signature=file_pattern:string, tensor_name:string -> tensor:dt; attr=dt:type; attr=preferred_shard:int,default=-1; is_stateful=true> This may be expected if your graph generating binary is newer than this binary. Unknown attributes will be ignored. NodeDef: {{node Restore}}
11
+ VERBOSE: Replacing 1 out of 20 node(s) with delegate (TfLiteFlexDelegate) node, yielding 2 partitions for the whole graph.
12
+ VERBOSE: Replacing 9 out of 1537 node(s) with delegate (TfLiteFlexDelegate) node, yielding 5 partitions for the whole graph.
13
+ FIRE_CHECK opened
14
+ FIRE_CHECK infer 504
15
+ FIRE_CHECK saved loss=7.3072906
16
+ FIRE_CHECK continued loss=7.0748734
17
+ VERBOSE: Replacing 9 out of 29 node(s) with delegate (TfLiteFlexDelegate) node, yielding 3 partitions for the whole graph.
18
+ native : core/framework/node_def_util.cc:676 NodeDef mentions attribute use_inter_op_parallelism which is not in the op definition: Op<name=Restore; signature=file_pattern:string, tensor_name:string -> tensor:dt; attr=dt:type; attr=preferred_shard:int,default=-1; is_stateful=true> This may be expected if your graph generating binary is newer than this binary. Unknown attributes will be ignored. NodeDef: {{node Restore}}
19
+ VERBOSE: Replacing 1 out of 20 node(s) with delegate (TfLiteFlexDelegate) node, yielding 2 partitions for the whole graph.
20
+ VERBOSE: Replacing 9 out of 1537 node(s) with delegate (TfLiteFlexDelegate) node, yielding 5 partitions for the whole graph.
21
+ VERBOSE: Replacing 9 out of 29 node(s) with delegate (TfLiteFlexDelegate) node, yielding 3 partitions for the whole graph.
22
+ native : core/framework/node_def_util.cc:676 NodeDef mentions attribute use_inter_op_parallelism which is not in the op definition: Op<name=Restore; signature=file_pattern:string, tensor_name:string -> tensor:dt; attr=dt:type; attr=preferred_shard:int,default=-1; is_stateful=true> This may be expected if your graph generating binary is newer than this binary. Unknown attributes will be ignored. NodeDef: {{node Restore}}
23
+ VERBOSE: Replacing 1 out of 20 node(s) with delegate (TfLiteFlexDelegate) node, yielding 2 partitions for the whole graph.
24
+ VERBOSE: Replacing 9 out of 1537 node(s) with delegate (TfLiteFlexDelegate) node, yielding 5 partitions for the whole graph.
25
+ ANDROID_FIRE_TRAINING_PASS {"runtime":"2.16.1","threads":1,"mixed_shapes":[[64,64],[64,96]],"train":true,"restore_exact":true,"optimizer_resume_exact":true,"incompatible_schema_rejected":true,"first_loss":7.3072906,"second_loss":7.0748734}
reports/android/fireviewer_yolo11m_strict_v1.log ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CANNOT LINK EXECUTABLE "/system/bin/dex2oat": "/system/lib64/libart-compiler.so" is 64-bit instead of 32-bit
2
+ FIRE_CHECK opening yolo.tflite
3
+ INFO: Initialized TensorFlow Lite runtime.
4
+ INFO: Created TensorFlow Lite delegate for select TF ops.
5
+ native : core/platform/cpu_feature_guard.cc:210 This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
6
+ To enable the following instructions: SSE3 SSE4.1 SSE4.2, in other operations, rebuild TensorFlow with the appropriate compiler flags.
7
+ INFO: TfLiteFlexDelegate delegate: 0 nodes delegated out of 1270 nodes with 0 partitions.
8
+
9
+ VERBOSE: Replacing 9 out of 29 node(s) with delegate (TfLiteFlexDelegate) node, yielding 3 partitions for the whole graph.
10
+ native : core/framework/node_def_util.cc:676 NodeDef mentions attribute use_inter_op_parallelism which is not in the op definition: Op<name=Restore; signature=file_pattern:string, tensor_name:string -> tensor:dt; attr=dt:type; attr=preferred_shard:int,default=-1; is_stateful=true> This may be expected if your graph generating binary is newer than this binary. Unknown attributes will be ignored. NodeDef: {{node Restore}}
11
+ VERBOSE: Replacing 1 out of 20 node(s) with delegate (TfLiteFlexDelegate) node, yielding 2 partitions for the whole graph.
12
+ VERBOSE: Replacing 9 out of 1532 node(s) with delegate (TfLiteFlexDelegate) node, yielding 5 partitions for the whole graph.
13
+ FIRE_CHECK opened
14
+ FIRE_CHECK infer 2016
15
+ FIRE_CHECK saved loss=7.079305
16
+ FIRE_CHECK continued loss=7.035917
17
+ VERBOSE: Replacing 9 out of 29 node(s) with delegate (TfLiteFlexDelegate) node, yielding 3 partitions for the whole graph.
18
+ native : core/framework/node_def_util.cc:676 NodeDef mentions attribute use_inter_op_parallelism which is not in the op definition: Op<name=Restore; signature=file_pattern:string, tensor_name:string -> tensor:dt; attr=dt:type; attr=preferred_shard:int,default=-1; is_stateful=true> This may be expected if your graph generating binary is newer than this binary. Unknown attributes will be ignored. NodeDef: {{node Restore}}
19
+ VERBOSE: Replacing 1 out of 20 node(s) with delegate (TfLiteFlexDelegate) node, yielding 2 partitions for the whole graph.
20
+ VERBOSE: Replacing 9 out of 1532 node(s) with delegate (TfLiteFlexDelegate) node, yielding 5 partitions for the whole graph.
21
+ VERBOSE: Replacing 9 out of 29 node(s) with delegate (TfLiteFlexDelegate) node, yielding 3 partitions for the whole graph.
22
+ native : core/framework/node_def_util.cc:676 NodeDef mentions attribute use_inter_op_parallelism which is not in the op definition: Op<name=Restore; signature=file_pattern:string, tensor_name:string -> tensor:dt; attr=dt:type; attr=preferred_shard:int,default=-1; is_stateful=true> This may be expected if your graph generating binary is newer than this binary. Unknown attributes will be ignored. NodeDef: {{node Restore}}
23
+ VERBOSE: Replacing 1 out of 20 node(s) with delegate (TfLiteFlexDelegate) node, yielding 2 partitions for the whole graph.
24
+ VERBOSE: Replacing 9 out of 1532 node(s) with delegate (TfLiteFlexDelegate) node, yielding 5 partitions for the whole graph.
25
+ ANDROID_FIRE_TRAINING_PASS {"runtime":"2.16.1","mixed_shapes":[[128,128],[128,192]],"train":true,"restore_exact":true,"optimizer_resume_exact":true,"incompatible_schema_rejected":true,"first_loss":7.079305,"second_loss":7.035917}
reports/android/runtime-libraries.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "url": "https://repo.maven.apache.org/maven2/org/tensorflow/tensorflow-lite/2.16.1/tensorflow-lite-2.16.1.aar",
4
+ "file": "libtensorflowlite_jni.so",
5
+ "sha256": "21f2570e650fc120ad28a379d927392c1883f9738895a3fd5efa0a32ae784507"
6
+ },
7
+ {
8
+ "url": "https://repo.maven.apache.org/maven2/org/tensorflow/tensorflow-lite-select-tf-ops/2.16.1/tensorflow-lite-select-tf-ops-2.16.1.aar",
9
+ "file": "libtensorflowlite_flex_jni.so",
10
+ "sha256": "481de882bef6cc4389c974c5e4b718f77f230bdd697d56dd9937e55430727494"
11
+ }
12
+ ]
reports/environments/litert-environment.txt ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ absl-py==2.5.0
2
+ accelerate==1.15.0
3
+ ai-edge-litert==2.2.0
4
+ ai-edge-quantizer==0.9.0
5
+ annotated-doc==0.0.5
6
+ annotated-types==0.8.0
7
+ anyio==4.14.2
8
+ av==18.1.0
9
+ backports-strenum==1.3.1
10
+ certifi==2026.7.22
11
+ charset-normalizer==3.5.1
12
+ click==8.5.0
13
+ contourpy==1.4.0
14
+ cuda-bindings==13.4.2
15
+ cuda-pathfinder==1.8.2
16
+ cuda-toolkit==13.0.3.0
17
+ cycler==0.12.1
18
+ defusedxml==0.7.1
19
+ filelock==4.0.1
20
+ fire==0.7.1
21
+ flatbuffers==25.12.19
22
+ fonttools==4.65.0
23
+ fsspec==2026.9.0
24
+ h11==0.16.0
25
+ hf-xet==1.6.0
26
+ httpcore==1.0.9
27
+ httpx==0.28.1
28
+ huggingface-hub==1.32.0
29
+ idna==3.20
30
+ immutabledict==4.2.1
31
+ jax==0.11.2
32
+ jaxlib==0.11.2
33
+ jaxtyping==0.3.11
34
+ jinja2==3.1.6
35
+ kagglehub==1.0.2
36
+ kagglesdk==0.1.37
37
+ kiwisolver==1.5.1
38
+ lark==1.3.1
39
+ litert-converter==0.4.0
40
+ litert-lm-builder==0.17.1
41
+ litert-torch==0.9.4
42
+ markdown-it-py==4.2.0
43
+ markupsafe==3.0.3
44
+ matplotlib==3.11.2
45
+ mdurl==0.1.2
46
+ ml-dtypes==0.6.0
47
+ mpmath==1.3.0
48
+ multipledispatch==1.0.0
49
+ networkx==3.6.1
50
+ numpy==2.5.3
51
+ nvidia-cublas==13.1.1.3
52
+ nvidia-cuda-cupti==13.0.85
53
+ nvidia-cuda-nvrtc==13.0.88
54
+ nvidia-cuda-runtime==13.0.96
55
+ nvidia-cudnn-cu13==9.20.0.48
56
+ nvidia-cufft==12.0.0.61
57
+ nvidia-cufile==1.15.1.6
58
+ nvidia-curand==10.4.0.35
59
+ nvidia-cusolver==12.0.4.66
60
+ nvidia-cusparse==12.6.3.3
61
+ nvidia-cusparselt-cu13==0.8.1
62
+ nvidia-nccl-cu13==2.29.7
63
+ nvidia-nvjitlink==13.4.92
64
+ nvidia-nvshmem-cu13==3.4.5
65
+ nvidia-nvtx==13.0.85
66
+ opt-einsum==3.4.0
67
+ ordered-set==4.1.0
68
+ packaging==26.3
69
+ pillow==12.3.0
70
+ pip==26.2.1
71
+ protobuf==7.36.2
72
+ psutil==7.2.2
73
+ pydantic==2.11.10
74
+ pydantic-core==2.33.2
75
+ pydeprecate==0.9.0
76
+ pygments==2.21.0
77
+ pyparsing==3.3.2
78
+ python-dateutil==2.9.0.post0
79
+ pyyaml==6.0.3
80
+ regex==2026.9.10
81
+ requests==2.34.2
82
+ rfdetr @ git+https://github.com/roboflow/rf-detr.git@5b39cbc0a0ef75ab9c9571ccb28b780c7da51432
83
+ rich==15.0.0
84
+ safetensors==0.8.0
85
+ scipy==1.18.1
86
+ sentencepiece==0.2.2
87
+ setuptools==84.0.0
88
+ shellingham==1.5.4
89
+ six==1.17.0
90
+ supervision==0.30.4
91
+ sympy==1.14.0
92
+ tabulate==0.10.0
93
+ termcolor==3.3.0
94
+ timm==1.0.29
95
+ tokenizers==0.23.2
96
+ tomli==2.4.1
97
+ torch==2.13.0
98
+ torchao==0.18.0
99
+ torchvision==0.28.0
100
+ tqdm==4.70.1
101
+ transformers==5.17.0
102
+ triton==3.7.1
103
+ typer==0.27.2
104
+ typing-extensions==4.12.2
105
+ typing-inspection==0.4.2
106
+ urllib3==2.8.0
107
+ wadler-lindig==0.1.7
108
+ xdsl==0.28.0
reports/environments/mm-environment.txt ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ addict==2.4.0
2
+ certifi==2026.7.22
3
+ charset-normalizer==3.5.1
4
+ cmake==4.4.3
5
+ contourpy==1.3.2
6
+ cycler==0.12.1
7
+ filelock==4.0.1
8
+ fonttools==4.65.0
9
+ fsspec==2026.9.0
10
+ idna==3.20
11
+ jinja2==3.1.6
12
+ kiwisolver==1.5.1
13
+ lit==23.1.1
14
+ markdown-it-py==4.2.0
15
+ markupsafe==3.0.3
16
+ matplotlib==3.10.9
17
+ mdurl==0.1.2
18
+ mmcv @ https://download.openmmlab.com/mmcv/dist/cu118/torch2.0.0/mmcv-2.0.1-cp310-cp310-manylinux1_x86_64.whl
19
+ mmdet==3.3.0
20
+ mmengine==0.10.7
21
+ -e file:///workspace/vds-litert-batch2/vendor/mmyolo
22
+ mpmath==1.3.0
23
+ networkx==3.4.2
24
+ numpy==1.26.4
25
+ nvidia-cublas-cu12==12.1.3.1
26
+ nvidia-cuda-cupti-cu12==12.1.105
27
+ nvidia-cuda-nvrtc-cu12==12.1.105
28
+ nvidia-cuda-runtime-cu12==12.1.105
29
+ nvidia-cudnn-cu12==8.9.2.26
30
+ nvidia-cufft-cu12==11.0.2.54
31
+ nvidia-curand-cu12==10.3.2.106
32
+ nvidia-cusolver-cu12==11.4.5.107
33
+ nvidia-cusparse-cu12==12.1.0.106
34
+ nvidia-nccl-cu12==2.18.1
35
+ nvidia-nvjitlink-cu12==12.9.86
36
+ nvidia-nvtx-cu12==12.1.105
37
+ onnx==1.16.2
38
+ onnxsim==0.7.3
39
+ opencv-python==4.11.0.86
40
+ packaging==26.3
41
+ pillow==12.3.0
42
+ platformdirs==4.11.11
43
+ prettytable==3.18.0
44
+ protobuf==7.36.2
45
+ pycocotools==2.0.11
46
+ pygments==2.21.0
47
+ pyparsing==3.3.2
48
+ python-dateutil==2.9.0.post0
49
+ pyyaml==6.0.3
50
+ requests==2.34.2
51
+ rich==15.0.0
52
+ scipy==1.15.3
53
+ setuptools==69.5.1
54
+ shapely==2.1.2
55
+ six==1.17.0
56
+ sympy==1.14.0
57
+ termcolor==3.3.0
58
+ terminaltables==3.1.10
59
+ tomli==2.4.1
60
+ torch @ https://download.pytorch.org/whl/cu118/torch-2.0.1%2Bcu118-cp310-cp310-linux_x86_64.whl
61
+ torchvision @ https://download.pytorch.org/whl/cu118/torchvision-0.15.2%2Bcu118-cp310-cp310-linux_x86_64.whl
62
+ tqdm==4.70.1
63
+ triton==2.0.0
64
+ typing-extensions==4.16.0
65
+ urllib3==2.8.0
66
+ wcwidth==0.8.4
67
+ wheel==0.48.0
68
+ yapf==0.43.0
reports/environments/onnx-environment.txt ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ai-edge-litert==2.1.2
2
+ anyio==4.15.1
3
+ backports-strenum==1.3.1
4
+ certifi==2026.7.22
5
+ flatbuffers==25.12.19
6
+ h11==0.16.0
7
+ h5py==3.14.0
8
+ httpcore==1.0.9
9
+ httpx==0.28.1
10
+ idna==3.20
11
+ iniconfig==2.3.0
12
+ markdown-it-py==4.2.0
13
+ mdurl==0.1.2
14
+ ml-dtypes==0.5.4
15
+ mpmath==1.3.0
16
+ networkx==3.6.1
17
+ numpy==2.2.6
18
+ onnx==1.20.1
19
+ onnx-ir==1.0.0
20
+ onnx2tf==2.6.9
21
+ onnxoptimizer==0.4.2
22
+ onnxruntime==1.26.0
23
+ onnxscript==0.6.2
24
+ onnxsim==0.6.5
25
+ opencv-python==4.13.0.92
26
+ opt-einsum==3.3.0
27
+ packaging==26.3
28
+ paddle2onnx==1.3.1
29
+ paddlepaddle==3.3.1
30
+ pillow==12.3.0
31
+ pluggy==1.6.0
32
+ protobuf==7.35.1
33
+ psutil==5.9.5
34
+ pygments==2.21.0
35
+ pytest==9.0.2
36
+ rich==15.0.0
37
+ safetensors==0.8.0
38
+ setuptools==81.0.0
39
+ sne4onnx==2.0.1
40
+ sng4onnx==2.0.1
41
+ sympy==1.14.0
42
+ tqdm==4.67.1
43
+ typing-extensions==4.16.0
reports/environments/runtime-environment.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "python": "3.12.3",
3
+ "torch": "2.13.0+cu130",
4
+ "cuda_available": true,
5
+ "torch_cuda": "13.0",
6
+ "device": "NVIDIA RTX 4000 Ada Generation",
7
+ "cuda_sum": 499500.0,
8
+ "litert-torch": "0.9.4",
9
+ "ai-edge-litert": "2.2.0",
10
+ "transformers": "5.17.0",
11
+ "timm": "1.0.29",
12
+ "huggingface_hub": "1.32.0",
13
+ "rfdetr": "1.11.0.dev0"
14
+ }
reports/environments/tf-current-freeze.txt ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ absl-py==2.5.0
2
+ ai-edge-litert==2.1.2
3
+ astunparse==1.6.3
4
+ backports-strenum==1.3.1
5
+ certifi==2026.7.22
6
+ charset-normalizer==3.5.1
7
+ flatbuffers==25.12.19
8
+ gast==0.7.0
9
+ google-pasta==0.2.0
10
+ grpcio==1.84.0
11
+ h5py==3.14.0
12
+ idna==3.20
13
+ iniconfig==2.3.0
14
+ keras==3.15.0
15
+ libclang==18.1.1
16
+ markdown-it-py==4.2.0
17
+ mdurl==0.1.2
18
+ ml-dtypes==0.5.4
19
+ mpmath==1.3.0
20
+ namex==0.1.0
21
+ numpy==2.2.6
22
+ onnx==1.20.1
23
+ onnx-ir==1.0.0
24
+ onnx2tf==2.6.9
25
+ onnxoptimizer==0.4.2
26
+ onnxruntime==1.26.0
27
+ onnxscript==0.6.2
28
+ onnxsim==0.6.5
29
+ opencv-python==4.13.0.92
30
+ opt-einsum==3.4.0
31
+ optree==0.20.0
32
+ packaging==26.3
33
+ pluggy==1.6.0
34
+ protobuf==7.35.1
35
+ psutil==5.9.5
36
+ pygments==2.21.0
37
+ pytest==9.0.2
38
+ requests==2.34.2
39
+ rich==15.0.0
40
+ setuptools==81.0.0
41
+ six==1.17.0
42
+ sne4onnx==2.0.1
43
+ sng4onnx==2.0.1
44
+ sympy==1.14.0
45
+ tensorflow==2.21.0
46
+ termcolor==3.3.0
47
+ tf-keras==2.21.0
48
+ tqdm==4.67.1
49
+ typing-extensions==4.16.0
50
+ urllib3==2.8.0
51
+ wheel==0.48.0
52
+ wrapt==2.4.1
reports/environments/tf-probe-freeze.txt ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ absl-py==2.5.0
2
+ anyio==4.15.1
3
+ astunparse==1.6.3
4
+ certifi==2026.7.22
5
+ charset-normalizer==3.5.1
6
+ click==8.5.0
7
+ filelock==4.0.1
8
+ flatbuffers==25.12.19
9
+ fsspec==2026.9.0
10
+ gast==0.7.0
11
+ google-pasta==0.2.0
12
+ grpcio==1.84.0
13
+ h11==0.16.0
14
+ h5py==3.16.0
15
+ hf-xet==1.6.0
16
+ httpcore==1.0.9
17
+ httpx==0.28.1
18
+ huggingface-hub==1.32.0
19
+ idna==3.20
20
+ keras==3.15.1
21
+ libclang==18.1.1
22
+ markdown==3.10.3
23
+ markdown-it-py==4.2.0
24
+ markupsafe==3.0.3
25
+ mdurl==0.1.2
26
+ ml-dtypes==0.6.0
27
+ namex==0.1.0
28
+ numpy==2.1.3
29
+ opt-einsum==3.4.0
30
+ optree==0.20.0
31
+ packaging==26.3
32
+ protobuf==5.29.6
33
+ pygments==2.21.0
34
+ pyyaml==6.0.3
35
+ requests==2.34.2
36
+ rich==15.0.0
37
+ setuptools==84.0.0
38
+ six==1.17.0
39
+ tensorboard==2.19.0
40
+ tensorboard-data-server==0.7.2
41
+ tensorflow==2.19.0
42
+ termcolor==3.3.0
43
+ tf-keras==2.19.0
44
+ tqdm==4.70.1
45
+ typing-extensions==4.16.0
46
+ urllib3==2.8.0
47
+ werkzeug==3.1.8
48
+ wheel==0.48.0
49
+ wrapt==2.4.1
reports/environments/tf216-freeze.txt ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ absl-py==2.5.0
2
+ astunparse==1.6.3
3
+ certifi==2026.7.22
4
+ charset-normalizer==3.5.1
5
+ flatbuffers==25.12.19
6
+ gast==0.7.0
7
+ google-pasta==0.2.0
8
+ grpcio==1.84.0
9
+ h5py==3.16.0
10
+ idna==3.20
11
+ keras==3.15.1
12
+ libclang==18.1.1
13
+ markdown==3.10.3
14
+ markdown-it-py==4.2.0
15
+ markupsafe==3.0.3
16
+ mdurl==0.1.2
17
+ ml-dtypes==0.3.2
18
+ namex==0.1.0
19
+ numpy==1.26.4
20
+ opt-einsum==3.4.0
21
+ optree==0.20.0
22
+ packaging==26.3
23
+ protobuf==4.25.9
24
+ pygments==2.21.0
25
+ requests==2.34.2
26
+ rich==15.0.0
27
+ setuptools==84.0.0
28
+ six==1.17.0
29
+ tensorboard==2.16.2
30
+ tensorboard-data-server==0.7.2
31
+ tensorflow==2.16.1
32
+ termcolor==3.3.0
33
+ typing-extensions==4.16.0
34
+ urllib3==2.8.0
35
+ werkzeug==3.1.8
36
+ wheel==0.48.0
37
+ wrapt==2.4.1
reports/environments/tf219-freeze.txt ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ absl-py==2.5.0
2
+ anyio==4.15.1
3
+ astunparse==1.6.3
4
+ certifi==2026.7.22
5
+ charset-normalizer==3.5.1
6
+ click==8.5.0
7
+ filelock==4.0.1
8
+ flatbuffers==25.12.19
9
+ fsspec==2026.9.0
10
+ gast==0.7.0
11
+ google-pasta==0.2.0
12
+ grpcio==1.84.0
13
+ h11==0.16.0
14
+ h5py==3.16.0
15
+ hf-xet==1.6.0
16
+ httpcore==1.0.9
17
+ httpx==0.28.1
18
+ huggingface-hub==1.32.0
19
+ idna==3.20
20
+ keras==3.15.1
21
+ libclang==18.1.1
22
+ markdown==3.10.3
23
+ markdown-it-py==4.2.0
24
+ markupsafe==3.0.3
25
+ mdurl==0.1.2
26
+ ml-dtypes==0.6.0
27
+ namex==0.1.0
28
+ numpy==2.1.3
29
+ opt-einsum==3.4.0
30
+ optree==0.20.0
31
+ packaging==26.3
32
+ protobuf==5.29.6
33
+ pygments==2.21.0
34
+ pyyaml==6.0.3
35
+ requests==2.34.2
36
+ rich==15.0.0
37
+ setuptools==84.0.0
38
+ six==1.17.0
39
+ tensorboard==2.19.0
40
+ tensorboard-data-server==0.7.2
41
+ tensorflow==2.19.0
42
+ termcolor==3.3.0
43
+ tf-keras==2.19.0
44
+ tqdm==4.70.1
45
+ typing-extensions==4.16.0
46
+ urllib3==2.8.0
47
+ werkzeug==3.1.8
48
+ wheel==0.48.0
49
+ wrapt==2.4.1
reports/fireviewer-completion.json ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "generated_at": "2026-09-20T00:35:11.670200+00:00",
3
+ "repo": "fireviewer/litert-models",
4
+ "models": [
5
+ {
6
+ "key": "fireviewer_dfine_m_strict_v1",
7
+ "title": "FireViewer D-FINE-M strict v1",
8
+ "source": "fireviewer/fire-smoke-dfine-m-strict-v1",
9
+ "status": "published_verified",
10
+ "publication": {
11
+ "repo": "fireviewer/litert-models",
12
+ "commit": "dee2a0f6204cafff559244d3555a79c49023c57e",
13
+ "path": "models/fireviewer_dfine_m_strict_v1_learning/model.tflite",
14
+ "sha256": "b66acda95a5eaf5154031cf80c1ed906b8cb7838617424afa9b837c5cd44064a",
15
+ "bytes": 79120652,
16
+ "remote_verified": true
17
+ },
18
+ "training_scope": "candidate_classification_and_box_adaptation_only",
19
+ "backbone_frozen": true,
20
+ "variable_image_preprocessing": {
21
+ "mode": "bilinear_stretch_inside_graph",
22
+ "native_backbone_shape": [
23
+ 1,
24
+ 3,
25
+ 704,
26
+ 704
27
+ ]
28
+ },
29
+ "host_training_passed": true,
30
+ "host_runtime216_passed": true,
31
+ "android_emulator_passed": false,
32
+ "physical_arm_phone_tested": false,
33
+ "accuracy_evaluated": false
34
+ },
35
+ {
36
+ "key": "fireviewer_rtdetr_v2_r50",
37
+ "title": "FireViewer RT-DETRv2-R50",
38
+ "source": "fireviewer/rtdetr-v2-r50-fire-smoke",
39
+ "status": "published_verified",
40
+ "publication": {
41
+ "repo": "fireviewer/litert-models",
42
+ "commit": "df60095dbaed66e8c0906b69bb0561c7ad71b3ac",
43
+ "path": "models/fireviewer_rtdetr_v2_r50_learning/model.tflite",
44
+ "sha256": "cd2c38cf21c139cf1f9fac45b4ccd6570b8cc3d8d4e5af21561e81c4e13ca565",
45
+ "bytes": 177870964,
46
+ "remote_verified": true
47
+ },
48
+ "training_scope": "candidate_classification_and_box_adaptation_only",
49
+ "backbone_frozen": true,
50
+ "variable_image_preprocessing": {
51
+ "mode": "bilinear_stretch_inside_graph",
52
+ "native_backbone_shape": [
53
+ 1,
54
+ 3,
55
+ 768,
56
+ 768
57
+ ]
58
+ },
59
+ "host_training_passed": true,
60
+ "host_runtime216_passed": true,
61
+ "android_emulator_passed": false,
62
+ "physical_arm_phone_tested": false,
63
+ "accuracy_evaluated": false
64
+ },
65
+ {
66
+ "key": "fireviewer_yolo11m_strict_v1",
67
+ "title": "FireViewer YOLO11-M strict v1",
68
+ "source": "fireviewer/fire-smoke-yolo11m-strict-v1",
69
+ "status": "published_verified",
70
+ "publication": {
71
+ "repo": "fireviewer/litert-models",
72
+ "commit": "ae756eaffe4c953e1ad6a6252830f661c9e11e1e",
73
+ "path": "models/fireviewer_yolo11m_strict_v1_learning/model.tflite",
74
+ "sha256": "f05130795e14427402d1f6f0c28b777d43bd0909be9888b776a3c958e74e222b",
75
+ "bytes": 80800592,
76
+ "remote_verified": true
77
+ },
78
+ "training_scope": "candidate_classification_and_box_adaptation_only",
79
+ "backbone_frozen": true,
80
+ "variable_image_preprocessing": {
81
+ "mode": "native_dynamic",
82
+ "stride": 32
83
+ },
84
+ "host_training_passed": true,
85
+ "host_runtime216_passed": true,
86
+ "android_emulator_passed": true,
87
+ "physical_arm_phone_tested": false,
88
+ "accuracy_evaluated": false
89
+ },
90
+ {
91
+ "key": "fireviewer_rfdetr_medium_v110",
92
+ "title": "FireViewer RF-DETR Medium 1.10 strict v1",
93
+ "source": "fireviewer/fire-smoke-rfdetr-medium-v110-strict-v1",
94
+ "status": "published_verified",
95
+ "publication": {
96
+ "repo": "fireviewer/litert-models",
97
+ "commit": "22e5e0f48a2996e85accf851e4fb7ce59b3cf469",
98
+ "path": "models/fireviewer_rfdetr_medium_v110_learning/model.tflite",
99
+ "sha256": "dba3ade58743b422d492448605e1cb7b709de66323dd0fbdbd930f12af7bad20",
100
+ "bytes": 124411016,
101
+ "remote_verified": true
102
+ },
103
+ "training_scope": "candidate_classification_and_box_adaptation_only",
104
+ "backbone_frozen": true,
105
+ "variable_image_preprocessing": {
106
+ "mode": "bilinear_stretch_inside_graph",
107
+ "native_backbone_shape": [
108
+ 1,
109
+ 3,
110
+ 576,
111
+ 576
112
+ ]
113
+ },
114
+ "host_training_passed": true,
115
+ "host_runtime216_passed": true,
116
+ "android_emulator_passed": false,
117
+ "physical_arm_phone_tested": false,
118
+ "accuracy_evaluated": false
119
+ },
120
+ {
121
+ "key": "fireviewer_dinov3_pointing_pilot_v1",
122
+ "title": "FireViewer DINOv3 pointing pilot v1",
123
+ "source": "fireviewer/dinov3-pointing-pilot-v1",
124
+ "status": "published_verified",
125
+ "publication": {
126
+ "repo": "fireviewer/litert-models",
127
+ "commit": "eb97d601b6287b64e7f2a192f0072c2282b40547",
128
+ "path": "models/fireviewer_dinov3_pointing_pilot_v1_learning/model.tflite",
129
+ "sha256": "6843e9dd525b76678c53e310939f0f63ccae5df084e012e7bc6442eb7a5b7061",
130
+ "bytes": 378204504,
131
+ "remote_verified": true
132
+ },
133
+ "training_scope": "four_task_output_adaptation_only",
134
+ "backbone_frozen": true,
135
+ "variable_image_preprocessing": {
136
+ "mode": "bilinear_stretch_inside_graph",
137
+ "native_backbone_shape": [
138
+ 1,
139
+ 3,
140
+ 448,
141
+ 448
142
+ ]
143
+ },
144
+ "host_training_passed": true,
145
+ "host_runtime216_passed": true,
146
+ "android_emulator_passed": false,
147
+ "physical_arm_phone_tested": false,
148
+ "accuracy_evaluated": false
149
+ },
150
+ {
151
+ "key": "fireviewer_segformer_b2_baseline_v1",
152
+ "title": "FireViewer SegFormer-B2 baseline v1",
153
+ "source": "fireviewer/segformer-b2-fire-smoke-baseline-v1",
154
+ "status": "source_unavailable",
155
+ "reason": "Repository absent from authenticated FireViewer organization listing"
156
+ }
157
+ ],
158
+ "published_count": 5,
159
+ "training": "On-device adaptation heads; all visual backbones frozen",
160
+ "android_runtime": [
161
+ "org.tensorflow:tensorflow-lite:2.16.1",
162
+ "org.tensorflow:tensorflow-lite-select-tf-ops:2.16.1"
163
+ ],
164
+ "app_source_changed": false,
165
+ "other_catalog_work": "paused at user request",
166
+ "conversion_pod": "7iz8rjvy5uld83",
167
+ "conversion_gpu": "RTX 4000 Ada",
168
+ "pod_stop": "performed after final archive and publication verification; local stop receipt is separate"
169
+ }
reports/fireviewer_dfine_m_strict_v1/publication_receipt.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "repo": "fireviewer/litert-models",
3
+ "commit": "dee2a0f6204cafff559244d3555a79c49023c57e",
4
+ "path": "models/fireviewer_dfine_m_strict_v1_learning/model.tflite",
5
+ "sha256": "b66acda95a5eaf5154031cf80c1ed906b8cb7838617424afa9b837c5cd44064a",
6
+ "bytes": 79120652,
7
+ "remote_verified": true
8
+ }
reports/fireviewer_dfine_m_strict_v1/runtime216_validation.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "key": "fireviewer_dfine_m_strict_v1",
3
+ "runtime": "2.16.1",
4
+ "platform": "Linux x86_64; same LiteRT release as Android, different ABI",
5
+ "sha256": "b66acda95a5eaf5154031cf80c1ed906b8cb7838617424afa9b837c5cd44064a",
6
+ "train": true,
7
+ "loss": 2.902190685272217,
8
+ "restart_exact": true,
9
+ "optimizer_resume_exact": true,
10
+ "two_object_fixture": true
11
+ }
reports/fireviewer_dfine_m_strict_v1/source.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "key": "fireviewer_dfine_m_strict_v1",
3
+ "kind": "detr",
4
+ "class_count": 2,
5
+ "labels": [
6
+ "smoke_visible",
7
+ "flame_visible"
8
+ ],
9
+ "class_indices": [
10
+ 0,
11
+ 1
12
+ ],
13
+ "backbone": "/workspace/vds-litert-batch2/learning-backbones/fireviewer_dfine_m_strict_v1/model-protected.tflite",
14
+ "parity_report": "/workspace/vds-litert-batch2/learning-backbones/fireviewer_dfine_m_strict_v1/parity.json",
15
+ "native_dynamic": false,
16
+ "bindings": {
17
+ "features_logits": 0,
18
+ "features_boxes": 1
19
+ },
20
+ "app_size": 704,
21
+ "test_size": [
22
+ 193,
23
+ 317
24
+ ]
25
+ }