diff --git a/docs/source/recipes.md b/docs/source/recipes.md index 7f65acf3..b0437e31 100644 --- a/docs/source/recipes.md +++ b/docs/source/recipes.md @@ -40,35 +40,16 @@ The properties within each model stub are defined as the following: ### Image Classification -| Model Tag | Validation Baseline Metric | -| ----------------------------------------------------------------------------------------------------------------- | -------------------------- | -| cv/classification/efficientnet-b0/pytorch/sparseml/imagenet/arch-moderate?recipe_type=original | 76.5% top1 accuracy | -| cv/classification/efficientnet-b4/pytorch/sparseml/imagenet/arch-moderate?recipe_type=original | 82.1% top1 accuracy | -| cv/classification/inception_v3/pytorch/sparseml/imagenet/pruned-conservative?recipe_type=original | 77.4% top1 accuracy | -| cv/classification/inception_v3/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 76.6% top1 accuracy | -| cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/base-none?recipe_type=original | 70.9% top1 accuracy | -| cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/pruned-conservative?recipe_type=original | 70.9% top1 accuracy | -| cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 70.1% top1 accuracy | -| cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/pruned_quant-moderate?recipe_type=original | 70.1% top1 accuracy | -| cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/pruned_quant-moderate?recipe_type=original | 70.1% top1 accuracy | -| cv/classification/resnet_v1-101/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 76.6% top1 accuracy | -| cv/classification/resnet_v1-152/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 77.5% top1 accuracy | -| cv/classification/resnet_v1-18/pytorch/sparseml/imagenet/pruned-conservative?recipe_type=original | 69.8% top1 accuracy | -| cv/classification/resnet_v1-34/pytorch/sparseml/imagenet/pruned-conservative?recipe_type=original | 73.3% top1 accuracy | -| cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned-conservative?recipe_type=original | 76.1% top1 accuracy | -| cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 75.3% top1 accuracy | -| cv/classification/resnet_v1-50/pytorch/sparseml/imagenet-augmented/pruned_quant-aggressive?recipe_type=original | 76.1% top1 accuracy | -| cv/classification/resnet_v1-50/pytorch/sparseml/imagenette/pruned-conservative?recipe_type=original | 99.9% top1 accuracy | -| cv/classification/resnet_v1-50/pytorch/torchvision/imagenette/pruned-conservative?recipe_type=original | 99.9% top1 accuracy | -| cv/classification/vgg-11/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 68.3% top1 accuracy | -| cv/classification/vgg-16/pytorch/sparseml/imagenet/pruned-conservative?recipe_type=original | 71.6% top1 accuracy | -| cv/classification/vgg-16/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 70.8% top1 accuracy | -| cv/classification/vgg-19/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 71.7% top1 accuracy | +
+ +
+ +Image classification table not loading? View full table [here](https://sparsezoo.neuralmagic.com/recipes/cv/classification). ### Object Detection -| Model Tag | Validation Baseline Metric | -| ----------------------------------------------------------------------------------------------------------------- | -------------------------- | -| cv/detection/ssd-resnet50_300/pytorch/sparseml/coco/pruned-moderate?recipe_type=original | 41.8 mAP@0.5 | -| cv/detection/ssd-resnet50_300/pytorch/sparseml/voc/pruned-moderate?recipe_type=original | 51.5 mAP@0.5 | -| cv/detection/yolo_v3-spp/pytorch/ultralytics/coco/pruned-aggressive?recipe_type=original | 62.1 mAP@0.5 | +
+ +
+ +Object detection table not loading? View full table [here](https://sparsezoo.neuralmagic.com/recipes/cv/detection). diff --git a/src/ui/api/index.jsx b/src/ui/api/index.jsx index c11a6f57..4af32f55 100644 --- a/src/ui/api/index.jsx +++ b/src/ui/api/index.jsx @@ -16,4 +16,5 @@ limitations under the License. export * from "./auth"; export * from "./models"; +export * from "./recipes"; export * from "./utils"; diff --git a/src/ui/api/models.jsx b/src/ui/api/models.jsx index 07ddcb5f..4508fa94 100644 --- a/src/ui/api/models.jsx +++ b/src/ui/api/models.jsx @@ -33,7 +33,7 @@ import { API_ROOT, validateAPIResponseJSON } from "./utils"; * training_scheme: string, * sparse_name: string, * sparse_category: string, - * sparse_target + * sparse_target: string * }} requestBody.queries the additional queries for the search result * @returns {Promise} */ diff --git a/src/ui/api/recipes.jsx b/src/ui/api/recipes.jsx new file mode 100644 index 00000000..5e853497 --- /dev/null +++ b/src/ui/api/recipes.jsx @@ -0,0 +1,64 @@ +/* +Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { API_ROOT, validateAPIResponseJSON } from "./utils"; + +/** + * API action for searching for models in the model zoo + * @param {object} requestBody the requestBody + * @param {string} requestBody.domain the domain of the model search + * @param {string} requestBody.subdomain the subdomain of the model search + * @param {string} requestBody.token the token for the model search authentication + * @param {string} requestBody.page the page of search results to return + * @param {string} requestBody.page_legth the amount of search results to return + * @param {{ + * architecture: string, + * sub_architecture: string, + * repo: string, + * framework: string, + * dataset: string, + * training_scheme: string, + * sparse_name: string, + * sparse_category: string, + * sparse_target: string, + * recipe_type: string + * }} requestBody.queries the additional queries for the search result + * @returns {Promise} + */ +export function requestSearchRecipes({ + domain, + subdomain, + token, + page = 1, + page_length = 20, + queries = {}, +}) { + let url = `${API_ROOT}/recipes/search/${domain}/${subdomain}?page=${page}&page_length=${page_length}`; + if (queries) { + for (const [key, value] of Object.entries(queries)) { + url = `${url}&${key}=${value}`; + } + } + return validateAPIResponseJSON( + fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + "nm-token-header": token, + }, + }) + ); +} diff --git a/src/ui/components/model-table/model-table.jsx b/src/ui/components/model-table/model-table.jsx index d025bb74..65dcc141 100644 --- a/src/ui/components/model-table/model-table.jsx +++ b/src/ui/components/model-table/model-table.jsx @@ -40,7 +40,6 @@ function ModelTable({ domain, subdomain, includePagination, includeHeader, queri const modelsState = useSelector(selectModelsState); const results = useSelector(selectModelTable); - useEffect(() => { const status = lodash.get(modelsState.status, `${domain}.${subdomain}`, "idle"); if (authState.token !== null && status === "idle") { @@ -81,6 +80,7 @@ function ModelTable({ domain, subdomain, includePagination, includeHeader, queri copy={lodash.get(results, `${domain}.${subdomain}.copy`, false)} width={lodash.get(results, `${domain}.${subdomain}.width`)} includePagination={includePagination} + loadingMessage="Loading models" /> ); diff --git a/src/ui/components/recipe-table/index.jsx b/src/ui/components/recipe-table/index.jsx new file mode 100644 index 00000000..ad34e7f5 --- /dev/null +++ b/src/ui/components/recipe-table/index.jsx @@ -0,0 +1,17 @@ +/* +Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export { default } from "./recipe-table"; diff --git a/src/ui/components/recipe-table/recipe-table-styles.jsx b/src/ui/components/recipe-table/recipe-table-styles.jsx new file mode 100644 index 00000000..0bf053b6 --- /dev/null +++ b/src/ui/components/recipe-table/recipe-table-styles.jsx @@ -0,0 +1,29 @@ +/* +Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { makeStyles } from "@material-ui/core/styles"; + +export default function makeRecipeTableStyles() { + return makeStyles( + (theme) => ({ + root: { + padding: theme.spacing(0.5), + }, + toolbar: {}, + }), + { name: "RecipeTable" } + ); +} diff --git a/src/ui/components/recipe-table/recipe-table.jsx b/src/ui/components/recipe-table/recipe-table.jsx new file mode 100644 index 00000000..70755829 --- /dev/null +++ b/src/ui/components/recipe-table/recipe-table.jsx @@ -0,0 +1,104 @@ +/* +Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { useEffect } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import PropTypes from "prop-types"; + +import lodash from "lodash"; + +import Typography from "@material-ui/core/Typography"; + +import { + selectAuthState, + selectRecipesState, + searchRecipesThunk, + selectRecipesTable, +} from "../../store"; +import makeStyles from "./recipe-table-styles"; +import ZooTable from "../zoo-table"; + +function RecipeTable({ domain, subdomain, includePagination, includeHeader, queries }) { + const useStyles = makeStyles(); + const classes = useStyles(); + const dispatch = useDispatch(); + + const authState = useSelector(selectAuthState); + const recipesState = useSelector(selectRecipesState); + console.log(recipesState); + const results = useSelector(selectRecipesTable); + + useEffect(() => { + const status = lodash.get(recipesState.status, `${domain}.${subdomain}`, "idle"); + if (authState.token !== null && status === "idle") { + dispatch( + searchRecipesThunk({ + domain, + subdomain, + token: authState.token, + queries, + }) + ); + } + }, [authState.token, recipesState.status, dispatch, domain, subdomain, queries]); + + const rows = lodash + .get(results, `${domain}.${subdomain}.data`, []) + .map((data) => data.row); + const status = lodash.get(results, `${domain}.${subdomain}.status`, "idle"); + const loaded = status !== "idle" && status !== "loading"; + return ( +
+ {loaded && includeHeader && ( + + {lodash.get( + results, + `${domain}.${subdomain}.displayName`, + `${domain} ${subdomain}` + )} + + )} + +
+ ); +} + +RecipeTable.propTypes = { + domain: PropTypes.string.isRequired, + subdomain: PropTypes.string.isRequired, + queries: PropTypes.object, + includePagination: PropTypes.bool, + includeHeader: PropTypes.bool, +}; + +RecipeTable.defaultProps = { + includePagination: false, + includeHeader: false, + queries: {}, +}; + +export default RecipeTable; diff --git a/src/ui/components/zoo-table/zoo-table.jsx b/src/ui/components/zoo-table/zoo-table.jsx index ebf13286..685e53d2 100644 --- a/src/ui/components/zoo-table/zoo-table.jsx +++ b/src/ui/components/zoo-table/zoo-table.jsx @@ -38,6 +38,7 @@ function ZooTable({ width, copy, includePagination, + loadingMessage, }) { const useStyles = makeStyles(); const classes = useStyles(); @@ -68,7 +69,7 @@ function ZooTable({ loaderSize={150} loaderChildren={ - {!error && "Loading models"} + {!error && loadingMessage} } > @@ -135,6 +136,7 @@ ZooTable.propTypes = { ]), copy: PropTypes.oneOfType([PropTypes.bool, PropTypes.arrayOf(PropTypes.bool)]), includePagination: PropTypes.bool, + loadingMessage: PropTypes.string, }; ZooTable.defaultProps = { @@ -146,6 +148,7 @@ ZooTable.defaultProps = { paginationOptions: [10, 25, 100], includePagination: false, copy: false, + loadingMessage: "Loading...", }; export default ZooTable; diff --git a/src/ui/routes/paths.jsx b/src/ui/routes/paths.jsx index 1fae26ff..24d2e89d 100644 --- a/src/ui/routes/paths.jsx +++ b/src/ui/routes/paths.jsx @@ -16,3 +16,5 @@ limitations under the License. export const MODEL_TABLE_ROOT_PATH = "/models"; export const MODEL_TABLE_PATH = "/models/:domain/:subdomain"; +export const RECIPE_TABLE_ROOT_PATH = "/recipes"; +export const RECIPE_TABLE_PATH = "/recipes/:domain/:subdomain"; diff --git a/src/ui/routes/recipes-root/index.jsx b/src/ui/routes/recipes-root/index.jsx new file mode 100644 index 00000000..cd66f8e0 --- /dev/null +++ b/src/ui/routes/recipes-root/index.jsx @@ -0,0 +1,17 @@ +/* +Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export { default } from "./recipes-root"; diff --git a/src/ui/routes/recipes-root/recipes-root-styles.jsx b/src/ui/routes/recipes-root/recipes-root-styles.jsx new file mode 100644 index 00000000..288a41d4 --- /dev/null +++ b/src/ui/routes/recipes-root/recipes-root-styles.jsx @@ -0,0 +1,26 @@ +/* +Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { makeStyles } from "@material-ui/core/styles"; + +export default function makeRecipeTableRootStyles() { + return makeStyles( + (theme) => ({ + root: {}, + }), + { name: "RecipeTableRoot" } + ); +} diff --git a/src/ui/routes/recipes-root/recipes-root.jsx b/src/ui/routes/recipes-root/recipes-root.jsx new file mode 100644 index 00000000..abcab81d --- /dev/null +++ b/src/ui/routes/recipes-root/recipes-root.jsx @@ -0,0 +1,53 @@ +/* +Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; + +import makeStyles from "./recipes-root-styles"; +import RecipesTable from "../../components/recipe-table"; + +const DOMAINS_INFO = [ + { + domain: "cv", + subdomain: "classification", + }, + { + domain: "cv", + subdomain: "detection", + }, +]; + +function RecipeTableRoot() { + const useStyles = makeStyles(); + + const classes = useStyles(); + + return ( +
+ {DOMAINS_INFO.map(({ domain, subdomain }) => ( + + ))} +
+ ); +} + +export default RecipeTableRoot; diff --git a/src/ui/routes/recipes/index.jsx b/src/ui/routes/recipes/index.jsx new file mode 100644 index 00000000..dfa48c0d --- /dev/null +++ b/src/ui/routes/recipes/index.jsx @@ -0,0 +1,17 @@ +/* +Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export { default } from "./recipes"; diff --git a/src/ui/routes/recipes/recipes-styles.jsx b/src/ui/routes/recipes/recipes-styles.jsx new file mode 100644 index 00000000..8fb1e2fd --- /dev/null +++ b/src/ui/routes/recipes/recipes-styles.jsx @@ -0,0 +1,26 @@ +/* +Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { makeStyles } from "@material-ui/core/styles"; + +export default function makeRecipeTableStyles() { + return makeStyles( + (theme) => ({ + root: {}, + }), + { name: "Recipes" } + ); +} diff --git a/src/ui/routes/recipes/recipes.jsx b/src/ui/routes/recipes/recipes.jsx new file mode 100644 index 00000000..8060bf73 --- /dev/null +++ b/src/ui/routes/recipes/recipes.jsx @@ -0,0 +1,36 @@ +/* +Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import RecipeTable from "../../components/recipe-table"; +import { useQuery } from "../../hooks"; + +import makeStyles from "./recipes-styles"; + +function Recipes(props) { + const { domain, subdomain } = props.match.params; + const queries = useQuery(); + const useStyles = makeStyles(); + const classes = useStyles(); + + return ( +
+ +
+ ); +} + +export default Recipes; diff --git a/src/ui/routes/routes.jsx b/src/ui/routes/routes.jsx index b26f346b..b62efbb4 100644 --- a/src/ui/routes/routes.jsx +++ b/src/ui/routes/routes.jsx @@ -16,8 +16,15 @@ limitations under the License. import ModelsRoot from "./models-root"; import Models from "./models"; +import RecipesRoot from "./recipes-root"; +import Recipes from "./recipes"; -import { MODEL_TABLE_PATH, MODEL_TABLE_ROOT_PATH } from "./paths"; +import { + MODEL_TABLE_PATH, + MODEL_TABLE_ROOT_PATH, + RECIPE_TABLE_PATH, + RECIPE_TABLE_ROOT_PATH, +} from "./paths"; export function makeContentRoutes() { return [ @@ -31,5 +38,15 @@ export function makeContentRoutes() { exact: true, component: Models, }, + { + path: RECIPE_TABLE_PATH, + exact: true, + component: Recipes, + }, + { + path: RECIPE_TABLE_ROOT_PATH, + exact: true, + component: RecipesRoot, + }, ]; } diff --git a/src/ui/store/index.jsx b/src/ui/store/index.jsx index 5433cfdd..c2549ff6 100644 --- a/src/ui/store/index.jsx +++ b/src/ui/store/index.jsx @@ -18,13 +18,16 @@ import { configureStore } from "@reduxjs/toolkit"; import authReducer from "./auth-slice"; import modelsReducer from "./models-slice"; +import recipesReducer from "./recipes-slice"; export default configureStore({ reducer: { auth: authReducer, models: modelsReducer, + recipes: recipesReducer, }, }); export * from "./auth-slice"; export * from "./models-slice"; +export * from "./recipes-slice"; diff --git a/src/ui/store/models-slice.jsx b/src/ui/store/models-slice.jsx index c6250638..b7de8aa7 100644 --- a/src/ui/store/models-slice.jsx +++ b/src/ui/store/models-slice.jsx @@ -32,20 +32,22 @@ export const searchModelsThunk = createAsyncThunk( SEARCH_MODELS_PREFIX, async ({ domain, subdomain, token, queries }, thunkApi) => { let page = 1; - const models = []; + let models = []; let body; do { body = await requestSearchModels({ domain, subdomain, token, page, queries }); - models.push(...body.models); - page += 1; - thunkApi.dispatch({ - type: PARTIAL_SEARCH_MODELS_TYPE, - payload: { - domain, - subdomain, - models, - }, - }); + if (body.models.length > 0) { + models = [...models, ...body.models]; + page += 1; + thunkApi.dispatch({ + type: PARTIAL_SEARCH_MODELS_TYPE, + payload: { + domain, + subdomain, + models, + }, + }); + } } while (body.models.length > 0); return models; @@ -76,9 +78,7 @@ const modelsSlice = createSlice({ lodash.setWith(state.status, `${domain}.${subdomain}`, "succeeded", {}); state.error = null; - const models = action.payload.filter( - (model) => !model.tags.map((tag) => tag.name).includes("demo") - ); + const models = action.payload; lodash.setWith(state.models, `${domain}.${subdomain}`, models, {}); }, [searchModelsThunk.rejected]: (state, action) => { @@ -91,9 +91,6 @@ const modelsSlice = createSlice({ lodash.setWith(state.status, `${domain}.${subdomain}`, "partial", {}); state.error = null; - models = models.filter( - (model) => !model.tags.map((tag) => tag.name).includes("demo") - ); lodash.setWith(state.models, `${domain}.${subdomain}`, models, {}); }, }, diff --git a/src/ui/store/recipes-slice.jsx b/src/ui/store/recipes-slice.jsx new file mode 100644 index 00000000..be42facb --- /dev/null +++ b/src/ui/store/recipes-slice.jsx @@ -0,0 +1,198 @@ +/* +Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { createSlice, createAsyncThunk, createSelector } from "@reduxjs/toolkit"; + +import lodash from "lodash"; + +import { getRecipeStub, getFormattedData } from "../utils"; +import { requestSearchRecipes } from "../api"; + +const SEARCH_RECIPES_PREFIX = "recipes/searchRecipes"; +const PARTIAL_SEARCH_RECIPES_TYPE = `${SEARCH_RECIPES_PREFIX}/partial`; +/** + * Async thunk for making a request to search for recipes + * + * @type {AsyncThunk, {domain: string, subdomain: string, token: string, queries: {}}, {}>} + */ +export const searchRecipesThunk = createAsyncThunk( + SEARCH_RECIPES_PREFIX, + async ({ domain, subdomain, token, queries }, thunkApi) => { + let page = 1; + let recipes = []; + let body; + do { + body = await requestSearchRecipes({ domain, subdomain, token, page, queries }); + if (body.recipes.length > 0) { + recipes = [...recipes, ...body.recipes]; + page += 1; + thunkApi.dispatch({ + type: PARTIAL_SEARCH_RECIPES_TYPE, + payload: { + domain, + subdomain, + recipes, + }, + }); + } + } while (body.recipes.length > 0); + return recipes; + } +); + +/** + * Slice for handling the recipes state in the redux store. + * + * @type {Slice<{recipes: Object, error: null, status: Object}, {}, string>} + */ +const recipesSlice = createSlice({ + name: "recipes", + initialState: { + recipes: {}, + error: null, + status: {}, + }, + reducers: {}, + extraReducers: { + [searchRecipesThunk.pending]: (state, action) => { + const { domain, subdomain } = action.meta.arg; + lodash.setWith(state.status, `${domain}.${subdomain}`, "loading", {}); + state.error = null; + }, + [searchRecipesThunk.fulfilled]: (state, action) => { + const { domain, subdomain } = action.meta.arg; + lodash.setWith(state.status, `${domain}.${subdomain}`, "succeeded", {}); + state.error = null; + + const recipes = action.payload; + lodash.setWith(state.recipes, `${domain}.${subdomain}`, recipes, {}); + }, + [searchRecipesThunk.rejected]: (state, action) => { + const { domain, subdomain } = action.meta.arg; + lodash.setWith(state.status, `${domain}.${subdomain}`, "failed", {}); + state.error = action.error.message; + }, + [PARTIAL_SEARCH_RECIPES_TYPE]: (state, action) => { + let { domain, subdomain, recipes } = action.payload; + lodash.setWith(state.status, `${domain}.${subdomain}`, "partial", {}); + state.error = null; + + lodash.setWith(state.recipes, `${domain}.${subdomain}`, recipes, {}); + }, + }, +}); + +/*** + * Available actions for recipes redux store + */ +export const { defaultSearchStatus } = recipesSlice.actions; + +/** + * Simple selector to get the current recipes state + * + * @param state - the redux store state + * @returns {Reducer | Reducer<{recipes: Object, error: null, status: Object}>} + */ +export const selectRecipesState = (state) => state.recipes; + +const DISPLAY_NAMES = { + cv: { + classification: "Image Classification", + detection: "Object Detection", + }, +}; + +/** + * Formats vision recipes to a table data format + * + * @param {string} domain domain of the recipes + * @param {string} subdomain subdomain of the recipes + * @param {{ + * files: { checkpoint: boolean, file_type: string, file_size: number}[], + * results: { recorded_value: number, recorded_units: string, result_category: string } + * }[]} recipes the recipes of specified domain/subdomain + * @param {string} status status of loading recipes of specified domain/subdomain + */ +const visionRecipesToTableData = (domain, subdomain, recipes, status) => { + const displayName = lodash.get( + DISPLAY_NAMES, + `${domain}.${subdomain}`, + `${domain} ${subdomain}` + ); + + const data = recipes.map((recipe) => { + const file_size = `${(recipe.file_size / 1024).toFixed(2)} KB`; + return { + recipe, + row: [ + recipe.model.display_name, + getRecipeStub(recipe), + file_size, + recipe.downloads, + getFormattedData(recipe, "training"), + getFormattedData(recipe, "inference"), + ], + }; + }); + + return { + domain, + subdomain, + displayName, + headers: [ + "Model Name", + "Recipe Stub", + "Content Size", + "Downloads", + "Training Metric", + "Inference Metric", + ], + recipes, + data, + status, + aligns: "left", + copy: [false, true, false, false, false], + }; +}; + +/** + * Selector for recipe table for the current loaded data + */ +export const selectRecipesTable = createSelector( + [selectRecipesState], + (recipesState) => { + const table = {}; + for (let domain in recipesState.status) { + table[domain] = {}; + for (let subdomain in recipesState.status[domain]) { + let data; + if (domain === "cv") { + data = visionRecipesToTableData( + domain, + subdomain, + lodash.get(recipesState.recipes, `${domain}.${subdomain}`, []), + lodash.get(recipesState.status, `${domain}.${subdomain}`, "idle") + ); + } + + table[domain][subdomain] = data; + } + } + return table; + } +); + +export default recipesSlice.reducer; diff --git a/src/ui/utils/model-utils.jsx b/src/ui/utils/model-utils.jsx index 20490212..20cb1091 100644 --- a/src/ui/utils/model-utils.jsx +++ b/src/ui/utils/model-utils.jsx @@ -37,6 +37,16 @@ export const getModelStub = (model) => { return `zoo:${model.domain}/${model.sub_domain}/${archId}/${model.framework}/${model.repo}/${trainingId}/${sparseId}`; }; +/** + * Gets a SparseZoo model stub from a model object + * @param {Object} recipe the model information + * @returns {string} SparseZoo model stub + */ +export const getRecipeStub = (recipe) => { + const modelStub = getModelStub(recipe.model); + return `${modelStub}/${recipe.recipe_type}`; +}; + /** * Formats model's result objects of a specific type * @param {object} model the model information