diff --git a/package-lock.json b/package-lock.json index f4d31f6e7..aeb6dedea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3598,9 +3598,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", diff --git a/src/components/Collections/SearchQuality/Instruction.mdx b/src/components/Collections/SearchQuality/Instruction.mdx new file mode 100644 index 000000000..a284503ac --- /dev/null +++ b/src/components/Collections/SearchQuality/Instruction.mdx @@ -0,0 +1,44 @@ +### Example Output: +` +[18/10/2024, 01:51:38] Point ID 1(1/100) precision@10: 0.8 +(search time exact: 30ms, regular: 5ms) +` + +### Explanation + +This output provides a comparison between: +- **Exact Search** (full kNN) +- **Approximate Search** (using ANN - Approximate Nearest Neighbor) + +**precision@10: 0.8** +- Out of the top 10 results returned by the exact search, 8 were also found in the ANN search. + +**Search Time Comparison** +- Exact search: 30ms +- ANN search: 5ms (faster but with minor accuracy loss) + +--- + +### Tuning the HNSW Algorithm (Advanced Mode) + +- **"hnsw_ef" parameter**: Controls how many neighbors to consider during a search. + - Increasing **hnsw_ef** improves precision but may slow down the search. + +--- + +### Practical Use + +The ANN search (with HNSW) is significantly faster (5ms vs. 30ms) but may have slight accuracy trade-offs. +**Tip**: Adjust **hnsw_ef** in advanced mode to balance speed and accuracy. + +--- + +### Additional Tuning Parameters (set in collection configuration) + +1. **"m" Parameter** + - Defines the number of edges per node in the graph. + - A higher **m** value improves accuracy but increases memory usage. + +2. **"ef_construct" Parameter** + - Sets the number of neighbors considered during index creation. + - Higher values increase precision but lengthen indexing time. \ No newline at end of file diff --git a/src/components/Collections/SearchQuality/SearchQuality.jsx b/src/components/Collections/SearchQuality/SearchQuality.jsx index 2ba44ca69..e512e6820 100644 --- a/src/components/Collections/SearchQuality/SearchQuality.jsx +++ b/src/components/Collections/SearchQuality/SearchQuality.jsx @@ -4,44 +4,41 @@ import { getSnackbarOptions } from '../../Common/utils/snackbarOptions'; import { useClient } from '../../../context/client-context'; import SearchQualityPanel from './SearchQualityPanel'; import { useSnackbar } from 'notistack'; -import { Box, Card, CardHeader } from '@mui/material'; +import { Box, Card, CardContent, CardHeader, Dialog, Grid, IconButton, Tooltip } from '@mui/material'; import { CopyButton } from '../../Common/CopyButton'; import { bigIntJSON } from '../../../common/bigIntJSON'; import EditorCommon from '../../EditorCommon'; import _ from 'lodash'; +import * as Instruction from './Instruction.mdx'; +import { mdxComponents } from '../../InteractiveTutorial/MdxComponents/MdxComponents'; +import { Close, OpenInFull } from '@mui/icons-material'; const SearchQuality = ({ collectionName }) => { const { enqueueSnackbar, closeSnackbar } = useSnackbar(); const { client } = useClient(); const [collection, setCollection] = React.useState(null); const [log, setLog] = React.useState(''); + const [open, setOpen] = React.useState(false); const handleLogUpdate = (newLog) => { const date = new Date().toLocaleString(); newLog = `[${date}] ${newLog}`; - setLog((prevLog) => { - return newLog + '\n' + prevLog; - }); + setLog((prevLog) => newLog + '\n' + prevLog); }; const clearLogs = () => { - setLog(''); + setLog(' '); }; useEffect(() => { client .getCollection(collectionName) - .then((res) => { - setCollection(() => { - return { ...res }; - }); - }) + .then((res) => setCollection({ ...res })) .catch((err) => { enqueueSnackbar(err.message, getSnackbarOptions('error', closeSnackbar)); }); }, []); - // Check that collection.config.params.vectors?.size exists and integer const isNamedVectors = !collection?.config?.params.vectors?.size && _.isObject(collection?.config?.params?.vectors); let vectors = {}; if (collection) { @@ -49,44 +46,142 @@ const SearchQuality = ({ collectionName }) => { } return ( - <> - {collection?.config?.params?.vectors && ( - - )} + + + {collection?.config?.params?.vectors && ( + + )} + + + { + setOpen(true); + }} + > + + + + } + /> + + + + + - - + } - /> - - + } /> + + + + + + setOpen(false)} maxWidth="md"> + + { + setOpen(false); + }} + > + + + } /> - - - + + + + + + ); }; diff --git a/src/components/Collections/SearchQuality/SearchQualityPanel.jsx b/src/components/Collections/SearchQuality/SearchQualityPanel.jsx index c643afbc6..7e88ffcd8 100644 --- a/src/components/Collections/SearchQuality/SearchQualityPanel.jsx +++ b/src/components/Collections/SearchQuality/SearchQualityPanel.jsx @@ -9,11 +9,13 @@ import { TableHead, TableRow, Tooltip, - IconButton, + Button, FormControlLabel, Switch, CardContent, LinearProgress, + Box, + IconButton, } from '@mui/material'; import { CopyButton } from '../../Common/CopyButton'; import { bigIntJSON } from '../../../common/bigIntJSON'; @@ -42,23 +44,35 @@ const VectorTableRow = ({ vectorObj, name, onCheckIndexQuality, precision, isInP - {isInProgress && } - {!isInProgress && ( - <> + {isInProgress === name && } + {isInProgress !== name && ( + - {precision ? `${precision * 100}%` : '—'} + {precision ? `${precision * 100}%` : null} - - - + {precision ? ( + + + + ) : ( + + )} - + )} @@ -70,7 +84,7 @@ VectorTableRow.propTypes = { name: PropTypes.string, onCheckIndexQuality: PropTypes.func, precision: PropTypes.number, - isInProgress: PropTypes.bool, + isInProgress: PropTypes.string, }; const SearchQualityPanel = ({ collectionName, vectors, loggingFoo, clearLogsFoo, ...other }) => { @@ -87,7 +101,7 @@ const SearchQualityPanel = ({ collectionName, vectors, loggingFoo, clearLogsFoo, }); const [advancedMod, setAdvancedMod] = useState(false); - const [inProgress, setInProgress] = useState(false); + const [inProgress, setInProgress] = useState(null); const [code, setCode] = useState(` // Run this code to estimate search quality versus exact search @@ -168,10 +182,14 @@ const SearchQualityPanel = ({ collectionName, vectors, loggingFoo, clearLogsFoo, return <>No vectors; } - const onCheckIndexQuality = async ({ using = '', limit = 10, params = null, filter = null, timeout }) => { - setInProgress(true); - + const onCheckIndexQuality = async ({ using = '', limit = 10, params = null, filter = null, timeout }, controller) => { + setInProgress(using); clearLogsFoo && clearLogsFoo(); + if (vectorsNames && !vectorsNames.includes(using)) { + loggingFoo && loggingFoo('Vector field name not found\n'); + setInProgress(null); + return; + } const precisions = []; try { const scrollResult = await client.scroll(collectionName, { @@ -188,6 +206,10 @@ const SearchQualityPanel = ({ collectionName, vectors, loggingFoo, clearLogsFoo, loggingFoo && loggingFoo('Starting measuring quality on ' + total + ' requests for ' + using || '---'); for (let idx = 0; idx < total; idx++) { + if (controller.signal.aborted) { + loggingFoo && loggingFoo('Previous operation cancelled \n'); + break; + } const pointId = pointIds[idx]; const precision = await checkIndexPrecision( client, @@ -215,7 +237,7 @@ const SearchQualityPanel = ({ collectionName, vectors, loggingFoo, clearLogsFoo, Math.sqrt(precisions.reduce((x, val) => x + (val - avgPrecision) ** 2, 0) / precisions.length) ); - loggingFoo('Mean precision@' + limit + ' for collection: ' + avgPrecision + ' ± ' + stdDev); + loggingFoo('Mean precision@' + limit + ' for collection: ' + avgPrecision + ' ± ' + stdDev + '\n'); setPrecision((prev) => { return { @@ -224,24 +246,32 @@ const SearchQualityPanel = ({ collectionName, vectors, loggingFoo, clearLogsFoo, }; }); - setInProgress(false); + setInProgress(null); } catch (e) { - setInProgress(false); + setInProgress(null); console.error(e); loggingFoo && loggingFoo(JSON.stringify(e)); } }; const handleRunCode = async (qulityCheckParams) => { - onCheckIndexQuality(qulityCheckParams); + if (window.currentController) { + window.currentController.abort(); + } + const controller = new AbortController(); + window.currentController = controller; + + onCheckIndexQuality(qulityCheckParams, controller); }; return ( - Search Quality + + + Search Quality + setAdvancedMod(!advancedMod)} size="small" />} @@ -251,58 +281,64 @@ const SearchQualityPanel = ({ collectionName, vectors, loggingFoo, clearLogsFoo, } /> - + } variant="heading" sx={{ flexGrow: 1, }} - action={ - <> - - - } + action={} /> + {!advancedMod && ( - - - - - - Vector Name - - - - - Size - - - - - Distance - - - - - Precision - - - - + +
+ + + + + Vector Name + + + + + Size + + + + + Distance + + + + + Precision + + + + - - {Object.keys(vectors).map((vectorName) => ( - onCheckIndexQuality({ using: vectorName })} - precision={precision ? precision[vectorName] : null} - key={vectorName} - isInProgress={inProgress} - /> - ))} - -
+ + {Object.keys(vectors).map((vectorName) => ( + { + if (window.currentController) { + window.currentController.abort(); + } + const controller = new AbortController(); + window.currentController = controller; + onCheckIndexQuality({ using: vectorName }, controller); + }} + precision={precision ? precision[vectorName] : null} + key={vectorName} + isInProgress={inProgress} + /> + ))} + + + )} {advancedMod && ( diff --git a/src/components/Collections/SearchQuality/check-index-precision.js b/src/components/Collections/SearchQuality/check-index-precision.js index 2208269d0..86d60cf54 100644 --- a/src/components/Collections/SearchQuality/check-index-precision.js +++ b/src/components/Collections/SearchQuality/check-index-precision.js @@ -61,11 +61,11 @@ export const checkIndexPrecision = async ( limit + ': ' + precision + - ' (search time exact: ' + + '\n (search time exact: ' + exactSearchElapsed + 'ms, regular: ' + searchElapsed + - 'ms)' + 'ms)\n' ); return precision; diff --git a/src/components/EditorCommon/index.jsx b/src/components/EditorCommon/index.jsx index 4df8d1eb9..7445a79b1 100644 --- a/src/components/EditorCommon/index.jsx +++ b/src/components/EditorCommon/index.jsx @@ -22,7 +22,7 @@ window.MonacoEnvironment = { loader.config({ monaco }); -const EditorCommon = ({ beforeMount, customHeight, ...props }) => { +const EditorCommon = ({ beforeMount, customHeight, paddingBottom = 0, ...props }) => { const monacoRef = useRef(null); const editorWrapper = useRef(null); const theme = useTheme(); @@ -55,7 +55,7 @@ const EditorCommon = ({ beforeMount, customHeight, ...props }) => { if (customHeight) { return; } - setEditorHeight(height - editorWrapper.current?.offsetTop); + setEditorHeight(height - editorWrapper.current?.offsetTop - paddingBottom); }, [height, editorWrapper]); return ( @@ -74,6 +74,7 @@ EditorCommon.propTypes = { height: PropTypes.string, beforeMount: PropTypes.func, customHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + paddingBottom: PropTypes.number, ...Editor.propTypes, };