Skip to content

Commit 3314cf5

Browse files
authored
feat(constants)!: switch URLs to v0.9.0 layout + add MODEL_REGISTRY (#1148)
## Description Refreshes every URL constant to the restructured HF layout under `resolve/v0.9.0` and adds the typed `models` accessor. ### URL refresh All URLs follow `<model>_<size>_<backend>_<precision>.pte`, files sit under per-size and per-backend directories on HF. - `modelUrls.ts` — every URL rewritten; multi-backend URLs hoisted here so the registry stays declarative. The `lfm2_5_350m_xnnpack_8w4da.pte` typo is corrected to `_8da4w.pte`. - `ocr/models.ts`, `tts/models.ts`, `tts/voices.ts` — paths updated to the new shape. - `versions.ts` — `VERSION_TAG → resolve/v0.9.0`; `PREVIOUS_VERSION_TAG = resolve/v0.8.0` retained for the `@deprecated` Llama QLoRA aliases. ### `models` accessor New `constants/modelRegistry.ts` exports `models`, a typed accessor grouped one-to-one with hooks: | Group | Hook | | ----------------------- | ----------------------------------- | | `llm` | `useLLM` (includes vision-capable LLMs like `lfm2_5_vl_*`) | | `classification` | `useClassification` | | `privacy_filter` | `usePrivacyFilter` | | `object_detection` | `useObjectDetection` | | `pose_estimation` | `usePoseEstimation` | | `semantic_segmentation` | `useSemanticSegmentation` | | `instance_segmentation` | `useInstanceSegmentation` | | `style_transfer` | `useStyleTransfer` | | `speech_to_text` | `useSpeechToText` | | `text_to_speech` | `useTextToSpeech` | | `text_embedding` | `useTextEmbeddings` | | `image_embedding` | `useImageEmbeddings` | | `image_generation` | `useTextToImage` | | `vad` | `useVAD` | | `ocr` | `useOCR` / `useVerticalOCR` | Each entry is a function — call it (optionally with `{ quant, backend }`) to get the resolved config: ```ts models.llm.llama3_2_3b() // default (quantized, platform-default backend) models.llm.llama3_2_3b({ quant: false }) // base models.llm.lfm2_5_vl_1_6b() // vision-capable LLM (same hook) models.text_embedding.distiluse_base_multilingual_cased_v2({ backend: 'coreml' }) models.ocr({ language: 'en' }) // OCR is parameterized by language ``` - The `backend` parameter is typed to exactly the backends each model ships with — `models.llm.llama3_2_3b({ backend: 'coreml' })` is a compile-time error (xnnpack-only). - Defaults to the quantized variant when `{ quant }` is omitted. - `text_to_speech` exposes `kokoro_small`/`kokoro_medium` plus plain voice configs under `voices.*`. ESLint's `camelcase` rule is relaxed to `properties: 'never'` so the snake_case property keys pass while bindings/functions stay camelCase. ### Migration ```ts // Before // After LLAMA3_2_1B_SPINQUANT → models.llm.llama3_2_1b() LFM2_5_1_2B_INSTRUCT → models.llm.lfm2_5_1_2b_instruct({ quant: false }) LFM2_5_VL_1_6B_QUANTIZED → models.llm.lfm2_5_vl_1_6b() EFFICIENTNET_V2_S → models.classification.efficientnet_v2_s() PRIVACY_FILTER_OPENAI → models.privacy_filter.openai() YOLO26N_POSE → models.pose_estimation.yolo26n() WHISPER_TINY_EN → models.speech_to_text.whisper_tiny_en() { model: KOKORO_MEDIUM, voice: KOKORO_VOICE_AF_HEART } → { model: models.text_to_speech.kokoro_medium(), voice: models.text_to_speech.voices.af_heart } OCR_ENGLISH → models.ocr({ language: 'en' }) MODEL_REGISTRY.LLM.LLAMA3_2_3B → models.llm.llama3_2_3b() ``` Individual constant imports (`LLAMA3_2_1B_SPINQUANT`, `KOKORO_MEDIUM`, etc.) still work — the new accessor is the recommended path. The flat `MODEL_REGISTRY = { ALL_MODELS: {...} }` export from `modelUrls.ts` is removed; the internal `getModelNameForUrl` lookup is preserved. ### Example apps + docs - All example apps migrated to `models.*()`. Heavily-used groups are destructured at the top of the file (`const segmentation = models.semantic_segmentation;`). - Picker entries compared by `modelName` to handle accessor-function values. - `bare-rn` LLM demo switched to LFM-2.5. - Every documentation code snippet that selected a model via a named constant is rewritten to use the typed `models.<group>.<entry>()` accessor across `03-hooks/**`, `04-typescript-api/**`, `01-fundamentals/**`, etc. The webrtc-integration page intentionally keeps the named-constant style — the snippet reads cleaner alongside imports from other libraries. - Model Registry docs page rewritten for the new accessor; the 0.8.x version's anchor is repointed to its own version to survive the rename. ### Deprecations - `LLAMA3_2_3B_QLORA`, `LLAMA3_2_1B_QLORA` — `@deprecated`; the .pte files stay at `v0.8.0` and the constants still resolve those URLs. Use `LLAMA3_2_*_SPINQUANT` going forward. ### Introduces a breaking change? - [x] Yes - [ ] No URL paths under `${VERSION_TAG}` change — code that hardcoded `resolve/v0.8.0` URLs through the constants keeps working only if it read them at runtime. The flat `MODEL_REGISTRY` export is removed in favour of the new `models` accessor. ### Type of change - [x] New feature (change which adds functionality) - [x] Other (chores, tests, code style improvements etc.) ### Tested on - [ ] iOS - [x] Android `yarn typecheck` and `yarn lint` clean across the monorepo. Every example app runs against the v0.9.0 HF state. ### Testing instructions ```bash yarn typecheck yarn lint ``` In application code: ```ts import { models } from 'react-native-executorch'; const llm = useLLM({ model: models.llm.llama3_2_3b() }); const emb = useTextEmbeddings({ model: models.text_embedding.distiluse_base_multilingual_cased_v2({ backend: 'coreml' }), }); ``` ### Related issues #431 #612 ### Checklist - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have updated the documentation accordingly - [x] My changes generate no new warnings
1 parent 1864a14 commit 3314cf5

84 files changed

Lines changed: 1714 additions & 1079 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintrc.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ module.exports = {
6363
customWordListFile: path.resolve(__dirname, '.cspell-wordlist.txt'),
6464
},
6565
],
66-
'camelcase': 'error',
66+
// `properties: 'never'` lets the lowercase snake_case keys in `models`
67+
// (e.g. `models.text_to_speech.kokoro_small`, mirroring the underlying
68+
// `.pte` filenames) pass while still requiring camelCase for variable
69+
// and function declarations.
70+
'camelcase': ['error', { properties: 'never' }],
6771
'jsdoc/require-jsdoc': 'off',
6872
'jsdoc/require-param': ['error', { checkDestructured: false }],
6973
'jsdoc/check-param-names': ['error', { checkDestructured: false }],

apps/bare-rn/App.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@ import {
1313
TouchableWithoutFeedback,
1414
View,
1515
} from 'react-native';
16-
import {
17-
initExecutorch,
18-
useLLM,
19-
LLAMA3_2_1B_SPINQUANT,
20-
} from 'react-native-executorch';
16+
import { models, initExecutorch, useLLM } from 'react-native-executorch';
2117
import { BareResourceFetcher } from 'react-native-executorch-bare-resource-fetcher';
2218
import { setConfig } from '@kesha-antonov/react-native-background-downloader';
2319
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
@@ -144,7 +140,9 @@ function App() {
144140
const textInputRef = useRef<TextInput>(null);
145141
const scrollViewRef = useRef<ScrollView>(null);
146142

147-
const llm = useLLM({ model: LLAMA3_2_1B_SPINQUANT });
143+
const llm = useLLM({
144+
model: models.llm.lfm2_5_1_2b_instruct(),
145+
});
148146
// Alternatively, to use a custom local model, uncomment below:
149147
// const llm = useLLM({ model: {
150148
// modelSource: require('./assets/ai-models/smolLm2/smolLm2_135M/smolLm2_135M_bf16.pte'),

apps/computer-vision/app/classification/index.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
import Spinner from '../../components/Spinner';
22
import { getImage } from '../../utils';
33
import {
4+
models,
45
useClassification,
5-
EFFICIENTNET_V2_S,
6-
EFFICIENTNET_V2_S_QUANTIZED,
76
ClassificationModelSources,
87
} from 'react-native-executorch';
98
import { ModelPicker, ModelOption } from '../../components/ModelPicker';
9+
const classification = models.classification;
1010

1111
const MODELS: ModelOption<ClassificationModelSources>[] = [
12-
{ label: 'EfficientNet V2 S Quantized', value: EFFICIENTNET_V2_S_QUANTIZED },
13-
{ label: 'EfficientNet V2 S', value: EFFICIENTNET_V2_S },
12+
{
13+
label: 'EfficientNet V2 S Quantized',
14+
value: classification.efficientnet_v2_s(),
15+
},
16+
{
17+
label: 'EfficientNet V2 S',
18+
value: classification.efficientnet_v2_s({ quant: false }),
19+
},
1420
];
1521
import { View, StyleSheet, Image, Text, ScrollView } from 'react-native';
1622
import { BottomBar } from '../../components/BottomBar';
@@ -22,7 +28,7 @@ import ErrorBanner from '../../components/ErrorBanner';
2228

2329
export default function ClassificationScreen() {
2430
const [selectedModel, setSelectedModel] =
25-
useState<ClassificationModelSources>(EFFICIENTNET_V2_S_QUANTIZED);
31+
useState<ClassificationModelSources>(classification.efficientnet_v2_s());
2632
const [results, setResults] = useState<{ label: string; score: number }[]>(
2733
[]
2834
);

apps/computer-vision/app/instance_segmentation/index.tsx

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,9 @@ import { BottomBar } from '../../components/BottomBar';
33
import { getImage } from '../../utils';
44
import { ModelPicker, ModelOption } from '../../components/ModelPicker';
55
import {
6+
models,
67
useInstanceSegmentation,
7-
YOLO26N_SEG,
8-
YOLO26S_SEG,
9-
YOLO26M_SEG,
10-
YOLO26L_SEG,
11-
YOLO26X_SEG,
12-
RF_DETR_NANO_SEG,
138
InstanceSegmentationModelSources,
14-
FASTSAM_S,
15-
FASTSAM_X,
169
} from 'react-native-executorch';
1710
import {
1811
View,
@@ -29,21 +22,25 @@ import ImageWithMasks, {
2922
DisplayInstance,
3023
} from '../../components/ImageWithMasks';
3124
import { StatsBar } from '../../components/StatsBar';
25+
const instanceSegmentation = models.instance_segmentation;
3226

3327
const MODELS: ModelOption<InstanceSegmentationModelSources>[] = [
34-
{ label: 'Yolo26N', value: YOLO26N_SEG },
35-
{ label: 'Yolo26S', value: YOLO26S_SEG },
36-
{ label: 'Yolo26M', value: YOLO26M_SEG },
37-
{ label: 'Yolo26L', value: YOLO26L_SEG },
38-
{ label: 'Yolo26X', value: YOLO26X_SEG },
39-
{ label: 'RF-DeTR Nano', value: RF_DETR_NANO_SEG },
40-
{ label: 'FastSAM-S', value: FASTSAM_S },
41-
{ label: 'FastSAM-X', value: FASTSAM_X },
28+
{ label: 'Yolo26N', value: instanceSegmentation.yolo26n() },
29+
{ label: 'Yolo26S', value: instanceSegmentation.yolo26s() },
30+
{ label: 'Yolo26M', value: instanceSegmentation.yolo26m() },
31+
{ label: 'Yolo26L', value: instanceSegmentation.yolo26l() },
32+
{ label: 'Yolo26X', value: instanceSegmentation.yolo26x() },
33+
{
34+
label: 'RF-DeTR Nano',
35+
value: instanceSegmentation.rf_detr_nano(),
36+
},
37+
{ label: 'FastSAM-S', value: instanceSegmentation.fastsam_s() },
38+
{ label: 'FastSAM-X', value: instanceSegmentation.fastsam_x() },
4239
];
4340

4441
export default function InstanceSegmentationScreen() {
4542
const [selectedModel, setSelectedModel] =
46-
useState<InstanceSegmentationModelSources>(YOLO26N_SEG);
43+
useState<InstanceSegmentationModelSources>(instanceSegmentation.yolo26n());
4744
const [inferenceTime, setInferenceTime] = useState<number | null>(null);
4845

4946
const { setGlobalGenerating } = useContext(GeneratingContext);

apps/computer-vision/app/object_detection/index.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,9 @@ import { BottomBar } from '../../components/BottomBar';
33
import { ModelPicker, ModelOption } from '../../components/ModelPicker';
44
import { getImage } from '../../utils';
55
import {
6+
models,
67
Detection,
78
useObjectDetection,
8-
RF_DETR_NANO,
9-
SSDLITE_320_MOBILENET_V3_LARGE,
10-
YOLO26N,
11-
YOLO26S,
12-
YOLO26M,
13-
YOLO26L,
14-
YOLO26X,
159
ObjectDetectionModelSources,
1610
} from 'react-native-executorch';
1711
import { View, StyleSheet, Image, Text } from 'react-native';
@@ -20,15 +14,22 @@ import React, { useContext, useEffect, useState } from 'react';
2014
import { GeneratingContext } from '../../context';
2115
import ScreenWrapper from '../../ScreenWrapper';
2216
import { StatsBar } from '../../components/StatsBar';
17+
const objectDetection = models.object_detection;
2318

2419
const MODELS: ModelOption<ObjectDetectionModelSources>[] = [
25-
{ label: 'RF-DeTR Nano', value: RF_DETR_NANO },
26-
{ label: 'SSDLite MobileNet', value: SSDLITE_320_MOBILENET_V3_LARGE },
27-
{ label: 'YOLO26N', value: YOLO26N },
28-
{ label: 'YOLO26S', value: YOLO26S },
29-
{ label: 'YOLO26M', value: YOLO26M },
30-
{ label: 'YOLO26L', value: YOLO26L },
31-
{ label: 'YOLO26X', value: YOLO26X },
20+
{
21+
label: 'RF-DeTR Nano',
22+
value: objectDetection.rf_detr_nano(),
23+
},
24+
{
25+
label: 'SSDLite MobileNet',
26+
value: objectDetection.ssdlite_320_mobilenet_v3_large(),
27+
},
28+
{ label: 'YOLO26N', value: objectDetection.yolo26n() },
29+
{ label: 'YOLO26S', value: objectDetection.yolo26s() },
30+
{ label: 'YOLO26M', value: objectDetection.yolo26m() },
31+
{ label: 'YOLO26L', value: objectDetection.yolo26l() },
32+
{ label: 'YOLO26X', value: objectDetection.yolo26x() },
3233
];
3334
import ErrorBanner from '../../components/ErrorBanner';
3435

@@ -41,7 +42,7 @@ export default function ObjectDetectionScreen() {
4142
height: number;
4243
}>();
4344
const [selectedModel, setSelectedModel] =
44-
useState<ObjectDetectionModelSources>(RF_DETR_NANO);
45+
useState<ObjectDetectionModelSources>(objectDetection.rf_detr_nano());
4546
const [inferenceTime, setInferenceTime] = useState<number | null>(null);
4647

4748
const model = useObjectDetection({ model: selectedModel });

apps/computer-vision/app/ocr/index.tsx

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,7 @@ import Spinner from '../../components/Spinner';
22
import { BottomBar } from '../../components/BottomBar';
33
import { ModelPicker, ModelOption } from '../../components/ModelPicker';
44
import { getImage } from '../../utils';
5-
import {
6-
useOCR,
7-
OCR_ENGLISH,
8-
OCR_GERMAN,
9-
OCR_FRENCH,
10-
OCR_SPANISH,
11-
OCR_ITALIAN,
12-
OCR_JAPANESE,
13-
OCR_KOREAN,
14-
OCRProps,
15-
} from 'react-native-executorch';
5+
import { models, useOCR, OCRProps } from 'react-native-executorch';
166
import { View, StyleSheet, Image, Text, ScrollView } from 'react-native';
177
import ImageWithBboxes2 from '../../components/ImageWithOCRBboxes';
188
import React, { useContext, useEffect, useState } from 'react';
@@ -22,14 +12,16 @@ import { StatsBar } from '../../components/StatsBar';
2212

2313
type OCRModelSources = OCRProps['model'];
2414

15+
const ocr = models.ocr.craft;
16+
2517
const MODELS: ModelOption<OCRModelSources>[] = [
26-
{ label: 'English', value: OCR_ENGLISH },
27-
{ label: 'German', value: OCR_GERMAN },
28-
{ label: 'French', value: OCR_FRENCH },
29-
{ label: 'Spanish', value: OCR_SPANISH },
30-
{ label: 'Italian', value: OCR_ITALIAN },
31-
{ label: 'Japanese', value: OCR_JAPANESE },
32-
{ label: 'Korean', value: OCR_KOREAN },
18+
{ label: 'English', value: ocr({ language: 'en' }) },
19+
{ label: 'German', value: ocr({ language: 'de' }) },
20+
{ label: 'French', value: ocr({ language: 'fr' }) },
21+
{ label: 'Spanish', value: ocr({ language: 'es' }) },
22+
{ label: 'Italian', value: ocr({ language: 'it' }) },
23+
{ label: 'Japanese', value: ocr({ language: 'ja' }) },
24+
{ label: 'Korean', value: ocr({ language: 'ko' }) },
3325
];
3426
import ErrorBanner from '../../components/ErrorBanner';
3527

@@ -41,8 +33,9 @@ export default function OCRScreen() {
4133
width: number;
4234
height: number;
4335
}>();
44-
const [selectedModel, setSelectedModel] =
45-
useState<OCRModelSources>(OCR_ENGLISH);
36+
const [selectedModel, setSelectedModel] = useState<OCRModelSources>(
37+
ocr({ language: 'en' })
38+
);
4639
const [inferenceTime, setInferenceTime] = useState<number | null>(null);
4740

4841
const model = useOCR({

apps/computer-vision/app/ocr_vertical/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Spinner from '../../components/Spinner';
22
import { BottomBar } from '../../components/BottomBar';
33
import { getImage } from '../../utils';
4-
import { useVerticalOCR, OCR_ENGLISH } from 'react-native-executorch';
4+
import { models, useVerticalOCR } from 'react-native-executorch';
55
import { View, StyleSheet, Image, Text, ScrollView } from 'react-native';
66
import ImageWithBboxes2 from '../../components/ImageWithOCRBboxes';
77
import React, { useContext, useEffect, useState } from 'react';
@@ -22,7 +22,7 @@ export default function VerticalOCRScreen() {
2222
const [error, setError] = useState<string | null>(null);
2323

2424
const model = useVerticalOCR({
25-
model: OCR_ENGLISH,
25+
model: models.ocr.craft({ language: 'en' }),
2626
independentCharacters: true,
2727
});
2828

apps/computer-vision/app/pose_estimation/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import Spinner from '../../components/Spinner';
22
import { BottomBar } from '../../components/BottomBar';
33
import { getImage } from '../../utils';
44
import {
5+
models,
56
usePoseEstimation,
67
PoseDetections,
78
RnExecutorchError,
89
RnExecutorchErrorCode,
9-
YOLO26N_POSE,
1010
} from 'react-native-executorch';
1111
import { View, StyleSheet, Image, Text } from 'react-native';
1212
import React, { useContext, useEffect, useState } from 'react';
@@ -31,7 +31,7 @@ export default function PoseEstimationScreen() {
3131
const [inferenceTime, setInferenceTime] = useState<number | null>(null);
3232
const [layout, setLayout] = useState({ width: 0, height: 0 });
3333

34-
const model = usePoseEstimation({ model: YOLO26N_POSE });
34+
const model = usePoseEstimation({ model: models.pose_estimation.yolo26n() });
3535
const { setGlobalGenerating } = useContext(GeneratingContext);
3636

3737
useEffect(() => {

apps/computer-vision/app/segment_anything/index.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,10 @@ import {
2121
AlphaType,
2222
} from '@shopify/react-native-skia';
2323
import {
24+
models,
2425
useInstanceSegmentation,
2526
useImageEmbeddings,
2627
useTextEmbeddings,
27-
FASTSAM_S,
28-
FASTSAM_X,
29-
CLIP_VIT_BASE_PATCH32_IMAGE_QUANTIZED,
30-
CLIP_VIT_BASE_PATCH32_TEXT,
3128
InstanceSegmentationModelSources,
3229
SegmentedInstance,
3330
FastSAMLabel,
@@ -48,19 +45,22 @@ import ImageWithMasks, {
4845
} from '../../components/ImageWithMasks';
4946
import { getImage } from '../../utils';
5047
import ColorPalette from '../../colors';
48+
const instanceSegmentation = models.instance_segmentation;
5149

5250
type PromptMode = 'point' | 'box' | 'text';
5351

5452
const MODELS: ModelOption<InstanceSegmentationModelSources>[] = [
55-
{ label: 'FastSAM-S', value: FASTSAM_S },
56-
{ label: 'FastSAM-X', value: FASTSAM_X },
53+
{ label: 'FastSAM-S', value: instanceSegmentation.fastsam_s() },
54+
{ label: 'FastSAM-X', value: instanceSegmentation.fastsam_x() },
5755
];
5856

5957
export default function SegmentAnythingScreen() {
6058
const { setGlobalGenerating } = useContext(GeneratingContext);
6159

6260
const [selectedModel, setSelectedModel] =
63-
useState<InstanceSegmentationModelSources>(FASTSAM_S);
61+
useState<InstanceSegmentationModelSources>(
62+
instanceSegmentation.fastsam_s()
63+
);
6464
const [mode, setMode] = useState<PromptMode>('point');
6565
const [inferenceTime, setInferenceTime] = useState<number | null>(null);
6666

@@ -78,9 +78,11 @@ export default function SegmentAnythingScreen() {
7878
useInstanceSegmentation({ model: selectedModel });
7979

8080
const clipImage = useImageEmbeddings({
81-
model: CLIP_VIT_BASE_PATCH32_IMAGE_QUANTIZED,
81+
model: models.image_embedding.clip_vit_base_patch32_image(),
82+
});
83+
const clipText = useTextEmbeddings({
84+
model: models.text_embedding.clip_vit_base_patch32_text(),
8285
});
83-
const clipText = useTextEmbeddings({ model: CLIP_VIT_BASE_PATCH32_TEXT });
8486
const skiaSource = useImage(imageUri || null);
8587

8688
const [textPrompt, setTextPrompt] = useState('');

0 commit comments

Comments
 (0)