diff --git a/.github/workflows/test-lp.yml b/.github/workflows/test-lp.yml index e4ca1204d8..3a6ddb0f98 100644 --- a/.github/workflows/test-lp.yml +++ b/.github/workflows/test-lp.yml @@ -19,12 +19,13 @@ jobs: - name: Run hugo command to test site builds run: | hugo - - name: Get all changed markdown files + - name: Get changed Learning Path and install guide markdown files id: changed-markdown-files uses: tj-actions/changed-files@v46 with: files: | - content/**/**.md + content/learning-paths/**/*.md + content/install-guides/**/*.md - name: Check for capital letters or spaces in content directory run: | echo "Checking for capital letters or spaces in content directory paths (excluding file extensions)..." @@ -63,7 +64,7 @@ jobs: run: | echo "Checking YAML fields for changed learning paths..." python3 tools/verify_index_fields.py ${{ steps.changed-markdown-files.outputs.all_changed_files }} - - name: Run test suite for all changed .md files + - name: Run test suite for changed Learning Paths and install guides id: run-suite if: steps.changed-markdown-files.outputs.any_changed == 'true' # Run the test suite @@ -71,7 +72,7 @@ jobs: set -o pipefail; ./tools/test_lp.sh ${{ steps.changed-markdown-files.outputs.all_changed_files }} 2>&1 | tee test-lp-output.txt - name: Parse test suite errors id: test-suite-state - if: success() + if: success() && steps.changed-markdown-files.outputs.any_changed == 'true' # Catch any missed errors if running multiple tests run: | cat test-lp-output.txt | grep -q 'Tests failed in test suite' && echo "TEST_SUITE_ERRORS=true" >> "$GITHUB_ENV" \ @@ -83,7 +84,7 @@ jobs: exit 1 - name: Parse test maintenance off id: maintenance-state - if: success() + if: success() && steps.changed-markdown-files.outputs.any_changed == 'true' # Check if maintenance is turned off run: | cat test-lp-output.txt | grep -q 'maintenance is turned off' && echo "MAINTENANCE=off" >> "$GITHUB_ENV" \ diff --git a/.gitignore b/.gitignore index aec2525dcf..19fbd974da 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /tools/social_card_generation/.social-card-manifest.json* __pycache__/ resources/ +reports/ node_modules/ package-lock.json .hugo_build.lock diff --git a/.wordlist.txt b/.wordlist.txt index d9e88a933c..5ac3d54eec 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -7655,3 +7655,33 @@ Andreas COFF disassemblers otool +EventRecord +EventRecorderConf +ExifTool +GLIBCXX +GLib +Kostiantyn +Liam +OpenSandbox +Perehuda +StartA +StopA +WASI +Wasm +WebAssembly +arewefastyet +bdec +bffefbea +contentauth +dialogs +efd +exiftool +labelled +libjpeg +libstdc +opensandbox +osb +patool +wasmCloud +wasmcloud +wg diff --git a/assets/contributors.csv b/assets/contributors.csv index 51dc890257..8495f0c8d8 100644 --- a/assets/contributors.csv +++ b/assets/contributors.csv @@ -188,3 +188,4 @@ Sammy Sahnine,,,,, Matthew Sloyan,Arm,matthewsloyanARM,,, Austin Hill,,,,, Andreas Mullen,Arm,,,, +Liam Randall,,,,, diff --git a/assets/search-lab/app.js b/assets/search-lab/app.js new file mode 100644 index 0000000000..b5788237e6 --- /dev/null +++ b/assets/search-lab/app.js @@ -0,0 +1,169 @@ +const $ = id => document.getElementById(id); +const main = document.querySelector('main[data-index]'); +let worker, workerFailure; +let allowBlankSearch = false; +const searchStop = new Set('a an and are at be can do for from how i in is it me my of on or the to using what with'.split(' ')); +let nextId = 0, generation = 0, offset = 0; +const pending = new Map(); +function failWorker() { + workerFailure = Error('Search is unavailable. Reload the page to retry.'); + worker?.terminate(); + ++generation; + for (const request of pending.values()) request.reject(workerFailure); + pending.clear(); + $('more').hidden = true; + $('search-diagnostics').hidden = true; + $('status').textContent = workerFailure.message; +} +try { + worker = new Worker(main.dataset.worker, { type: 'module' }); + worker.onmessage = ({ data }) => { + const request = pending.get(data.id); + if (!request) return; + pending.delete(data.id); + data.error ? request.reject(Error(data.error)) : request.resolve(data.result); + }; + worker.onerror = failWorker; + worker.onmessageerror = failWorker; +} catch { failWorker(); } +function call(action, input) { + if (workerFailure) return Promise.reject(workerFailure); + return new Promise((resolve, reject) => { + const id = ++nextId; + pending.set(id, { resolve, reject }); + try { worker.postMessage({ id, action, input, url: main.dataset.index }); } + catch { failWorker(); } + }); +} +const ready = call('init'); +const tool = { + name: 'search_arm_content', + description: 'Find Arm Learning Paths and install guides by keyword or author name, with word prefixes and limited typo tolerance. Relevance defaults to complete query matches first. Returns source URLs and metadata excerpts; use these sources to read the full instructions. Scores indicate keyword relevance, not confidence.', + inputSchema: { type: 'object', properties: { + query: { type: 'string', maxLength: 300, description: 'Leave empty to browse all content matching the filters, newest first.' }, + type: { type: 'string', enum: ['', 'learning-path', 'install-guide'], default: '' }, + category: { type: 'string', description: 'Learning Path category slug, such as servers-and-cloud-computing. Ignored when type is install-guide.' }, + sort: { type: 'string', enum: ['relevance', 'updated'], default: 'relevance' }, + offset: { type: 'integer', minimum: 0, default: 0, description: 'Number of results to skip for pagination.' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 } + }, required: ['query'], additionalProperties: false }, + annotations: { readOnlyHint: true }, + async execute(input, { signal } = {}) { + try { + signal?.throwIfAborted(); + await ready; + signal?.throwIfAborted(); + const result = await call('search', input); + signal?.throwIfAborted(); + return { content: [{ type: 'text', text: JSON.stringify(result) }], structuredContent: result }; + } catch (error) { + if (signal?.aborted) throw error; + return { isError: true, content: [{ type: 'text', text: error.message }] }; + } + } +}; +// The tester and the registered tool invoke precisely the same callback. +$('run-tool').onclick = async () => { + try { $('tool-output').textContent = JSON.stringify(await tool.execute(JSON.parse($('tool-input').value)), null, 2); } + catch (error) { $('tool-output').textContent = error.message; } +}; +async function register() { + const context = document.modelContext || navigator.modelContext; + if (!context?.registerTool) { + $('webmcp').textContent = 'Native WebMCP is unavailable in this browser. Use the tool tester below.'; + return; + } + try { + await context.registerTool(tool); + $('webmcp').textContent = 'WebMCP registered: search_arm_content'; + } catch (error) { $('webmcp').textContent = `WebMCP registration failed: ${error.message}. The tester still works.`; } +} +register(); +async function render() { + const current = ++generation; + const query = $('query').value.trim(); + $('clear-blank-search').hidden = !allowBlankSearch || $('query').value !== ''; + $('category').disabled = $('type').value === 'install-guide'; + if ($('category').disabled) $('category').value = ''; + const params = new URLSearchParams(); + if (query) params.set('q', query); + if ($('type').value) params.set('type', $('type').value); + if ($('category').value) params.set('category', $('category').value); + params.set('sort', $('sort').value); + history.replaceState(null, '', `${location.pathname}${params.size ? '?' + params : ''}${location.hash}`); + if (!offset) $('results').replaceChildren(); + $('more').hidden = true; + $('search-diagnostics').hidden = true; + if (workerFailure) { $('status').textContent = workerFailure.message; return; } + if (!query && !allowBlankSearch) { + $('status').textContent = 'Enter a search term, or click Search to browse all content matching your filters.'; + return; + } + if (query && !(query.toLowerCase().match(/[a-z0-9]+/g) || []).some(w => !searchStop.has(w))) { + $('status').textContent = `"${query}" contains only common words that search ignores. Try adding a more specific term.`; + return; + } + $('status').textContent = 'Searching…'; + const start = performance.now(); + try { + await ready; + const result = await call('search', { query, type: $('type').value, category: $('category').value, sort: $('sort').value, limit: 10, offset }); + if (current !== generation) return; + $('status').textContent = `${result.total} result${result.total === 1 ? '' : 's'}${query ? ` for “${query}”` : ' · newest first'}`; + $('performance').textContent = `Last search: ${result.searchMs.toFixed(1)} ms engine · ${(performance.now() - start).toFixed(1)} ms response`; + $('search-diagnostics').hidden = false; + if (!result.total) $('status').textContent += query ? '. Try fewer words or remove a filter.' : '. Try removing a filter.'; + if (result.total) $('status').textContent += `. Showing ${offset + result.results.length} of ${result.total}.`; + for (const record of result.results) { + const li = document.createElement('li'); li.className = 'search-result-card'; + const a = document.createElement('a'); a.href = record.url; a.textContent = record.title; + const h = document.createElement('h3'); h.className = 'result-title'; h.append(a); + const meta = document.createElement('p'); meta.className = 'result-meta'; + const badge = document.createElement('span'); badge.className = 'result-type'; + badge.textContent = record.type === 'learning-path' ? 'Learning Path' : 'Install guide'; meta.append(badge); + if (record.category) { const category = document.createElement('span'); category.textContent = categoryLabel(record.category); meta.append(category); } + const p = document.createElement('p'); p.className = 'result-description'; p.textContent = record.description || record.summary; + const footer = document.createElement('div'); footer.className = 'result-footer'; + if (record.authors?.length) { const authors = document.createElement('p'); authors.textContent = 'By ' + record.authors.join(', '); footer.append(authors); } + const updated = document.createElement('p'); + if (record.lastUpdated) { + const time = document.createElement('time'); time.dateTime = record.lastUpdated; + time.textContent = new Date(record.lastUpdated + 'T00:00:00Z').toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' }); + updated.append('Last updated: ', time); + } else { updated.textContent = 'Last updated: unavailable'; } + footer.append(updated); li.append(meta, h, p, footer); $('results').append(li); + } + $('more').hidden = result.total <= offset + result.results.length; + if (offset && result.results.length) { const firstNew = $('results').children[offset]; firstNew?.querySelector('a')?.focus(); } + } catch (error) { if (current === generation) $('status').textContent = error.message; } +} +function categoryLabel(slug) { + return slug.split('-').map((word, index) => index && ['and'].includes(word) ? word : word[0].toUpperCase() + word.slice(1)).join(' '); +} +for (const button of main.querySelectorAll('[data-query]')) button.onclick = () => { + $('query').value = button.dataset.query; $('type').value = ''; $('category').value = ''; offset = 0; render(); +}; +let timer; +$('query').oninput = () => { $('clear-blank-search').hidden = true; allowBlankSearch = false; ++generation; $('more').hidden = true; clearTimeout(timer); $('results').replaceChildren(); $('search-diagnostics').hidden = true; if (!$('query').value) { offset = 0; $('status').textContent = 'Enter a search term, or click Search to browse all content matching your filters.'; } else { $('status').textContent = ''; } }; +$('search-form').onsubmit = event => { event.preventDefault(); allowBlankSearch = true; clearTimeout(timer); offset = 0; render(); }; +$('clear-blank-search').onclick = () => { + clearTimeout(timer); + allowBlankSearch = false; + $('query').value = ''; + offset = 0; + render(); + $('query').focus(); +}; +for (const id of ['type', 'category', 'sort']) $(id).onchange = () => { if (!$('query').value.trim()) allowBlankSearch = true; offset = 0; render(); }; +$('more').onclick = () => { offset += 10; render(); }; +const params = new URLSearchParams(location.search); +$('query').value = (params.get('q') || '').slice(0, 300); +if (['updated', 'relevance'].includes(params.get('sort'))) $('sort').value = params.get('sort'); +if (['learning-path', 'install-guide'].includes(params.get('type'))) $('type').value = params.get('type'); +try { + const info = await ready; + for (const category of info.categories) { const option = document.createElement('option'); option.value = category; option.textContent = categoryLabel(category); $('category').append(option); } + if (info.categories.includes(params.get('category'))) $('category').value = params.get('category'); + $('metrics').textContent = `${info.count} documents · ${(info.bytes / 1024).toFixed(0)} KB index (uncompressed) · ${info.loadMs.toFixed(0)} ms to download and prepare`; + render(); +} catch (error) { $('status').textContent = `Unable to load search: ${error.message} Reload to retry.`; } diff --git a/assets/search-lab/engine.mjs b/assets/search-lab/engine.mjs new file mode 100644 index 0000000000..bfbaed764e --- /dev/null +++ b/assets/search-lab/engine.mjs @@ -0,0 +1,102 @@ +// Dependency-free BM25 baseline. Exact identifiers stay searchable alongside words. +const stop = new Set('a an and are at be can do for from how i in is it me my of on or the to using what with'.split(' ')); +const aliases = new Map([['cpp', 'c++'], ['csharp', 'c#']]); +export function tokenize(text) { + const tokens = String(text).toLowerCase().match(/[\p{L}\p{N}]+(?:[.+#-][\p{L}\p{N}+#]+)*#?/gu) || []; + return tokens.map(token => aliases.get(token) || token); +} +// One insertion, deletion, substitution, or adjacent transposition. +function oneEditAway(a, b) { + if (Math.abs(a.length - b.length) > 1) return false; + let i = 0; + while (i < a.length && a[i] === b[i]) i++; + if (i === a.length) return b.length === a.length + 1; + if (a.length === b.length) { + return a.slice(i + 1) === b.slice(i + 1) + || (a[i] === b[i + 1] && a[i + 1] === b[i] && a.slice(i + 2) === b.slice(i + 2)); + } + return a.length > b.length ? a.slice(i + 1) === b.slice(i) : a.slice(i) === b.slice(i + 1); +} +export function createEngine(records) { + const fields = [['title', 5], ['description', 3], ['authors', 3], ['metadata', 2], ['prerequisites', 2], ['summary', 1], ['faq', 1]]; + const docs = records.map(record => ({ record, fields: fields.map(([key, boost]) => { + const value = record[key] || ''; + const tokens = tokenize(Array.isArray(value) ? value.join(' ') : value); + const counts = new Map(); + for (const token of tokens) counts.set(token, (counts.get(token) || 0) + 1); + return { counts, length: tokens.length, boost }; + }) })); + const averages = fields.map((_, i) => docs.reduce((n, d) => n + d.fields[i].length, 0) / (docs.length || 1) || 1); + const df = new Map(); + for (const doc of docs) { + const terms = new Set(doc.fields.flatMap(f => [...f.counts.keys()])); + for (const term of terms) df.set(term, (df.get(term) || 0) + 1); + } + const vocabulary = [...df.keys()]; + return function search(input = {}) { + if (!input || typeof input !== 'object' || Array.isArray(input)) throw Error('Arguments must be an object.'); + if (Object.keys(input).some(key => !['query', 'type', 'category', 'limit', 'offset', 'sort'].includes(key))) throw Error('Unknown search argument.'); + const { query, type = '', category = '', limit = 10, offset = 0, sort = 'relevance' } = input; + if (typeof query !== 'string' || query.length > 300) throw Error('Query must be a string of at most 300 characters.'); + if (!['', 'learning-path', 'install-guide'].includes(type)) throw Error('Unknown content type.'); + if (typeof category !== 'string' || (category && !records.some(r => r.category === category))) throw Error('Unknown category.'); + if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw Error('Limit must be an integer from 1 to 100.'); + if (!Number.isInteger(offset) || offset < 0) throw Error('Offset must be a non-negative integer.'); + if (!['updated', 'relevance'].includes(sort)) throw Error('Unknown sort order.'); + const terms = [...new Set(tokenize(query).filter(t => !stop.has(t)))]; + const browsing = !query.trim(); + if (!browsing && !terms.length) return { total: 0, results: [] }; + const expansions = terms.map(term => { + const candidates = [{ token: term, weight: 1 }]; + if (/^\p{L}{3,}$/u.test(term)) { + for (const token of vocabulary) { + if (token !== term && token.startsWith(term)) candidates.push({ token, weight: 0.65 }); + } + // Correct a typo only when there is no exact token or prefix completion. + if (!df.has(term) && candidates.length === 1 && /^\p{L}{5,}$/u.test(term)) { + for (const token of vocabulary) { + if (/^\p{L}{5,}$/u.test(token) && oneEditAway(term, token)) candidates.push({ token, weight: 0.4 }); + } + } + } + return candidates.map(candidate => ({ ...candidate, + idf: Math.log(1 + (docs.length - (df.get(candidate.token) || 0) + 0.5) / ((df.get(candidate.token) || 0) + 0.5)) + })); + }); + const ranked = []; + for (const doc of docs) { + if ((type && doc.record.type !== type) || (type !== 'install-guide' && category && doc.record.category !== category)) continue; + if (browsing) { + ranked.push({ record: { ...doc.record, score: 0 }, tier: 0 }); + continue; + } + let score = 0, matched = 0, exact = 0; + for (const candidates of expansions) { + let termScore = 0; + let exactMatch = false; + doc.fields.forEach((field, i) => { + let best = 0; + for (const { token, weight, idf } of candidates) { + const tf = field.counts.get(token) || 0; + if (weight === 1 && tf) exactMatch = true; + best = Math.max(best, weight * field.boost * idf * tf * 2.2 / (tf + 1.2 * (0.25 + 0.75 * field.length / averages[i]))); + } + termScore += best; + }); + if (termScore) matched++; + if (exactMatch) exact++; + score += termScore; + } + // Reward coverage so long natural-language queries can still find partial matches. + if (matched) ranked.push({ record: { ...doc.record, score: score * (matched / terms.length) ** 2 }, + tier: exact === terms.length ? 2 : matched === terms.length ? 1 : 0 }); + } + ranked.sort((a, b) => { + // ISO date strings sort chronologically; missing dates follow dated results. + const dateOrder = (browsing || sort === 'updated') ? (b.record.lastUpdated || '').localeCompare(a.record.lastUpdated || '') : 0; + const coverageOrder = sort === 'relevance' ? b.tier - a.tier : 0; + return dateOrder || coverageOrder || b.record.score - a.record.score || a.record.id.localeCompare(b.record.id); + }); + return { total: ranked.length, results: ranked.slice(offset, offset + limit).map(item => item.record) }; + }; +} diff --git a/assets/search-lab/style.css b/assets/search-lab/style.css new file mode 100644 index 0000000000..f957cc92e2 --- /dev/null +++ b/assets/search-lab/style.css @@ -0,0 +1,100 @@ +/* Keep all experiment styles inside the shared site's content area. */ +.browser-search { + --search-surface: #fff; + --search-field: #fff; + --search-border: #c4c1cc; + --search-muted: #5c586b; + --search-link: #6023de; + --search-chip: #f0e9ff; + max-width: 1080px; + margin: 0 auto; + padding: 40px 16px 56px; + color: inherit; +} +html[theme="dark"] .browser-search { + --search-surface: #151237; + --search-field: var(--lp-background-color, #08052e); + --search-border: #52496d; + --search-muted: #c1bbd0; + --search-link: var(--lp-accent-secondary, #d1bafc); + --search-chip: #29204a; + color-scheme: dark; +} +.browser-search *, .browser-search *::before, .browser-search *::after { box-sizing: border-box; } +.browser-search .search-intro { margin-bottom: 28px; } +.browser-search .search-eyebrow { color: var(--search-link); font-weight: 700; letter-spacing: .12em; margin: 0 0 12px; } +.browser-search h2 { margin: 0 0 12px; font-size: clamp(1.7rem, 4vw, 2.25rem); line-height: 1.25; } +.browser-search p { line-height: 1.6; } +.browser-search .search-intro > p:last-child { margin-bottom: 0; } +.browser-search .search-panel { background: var(--search-surface); border: 1px solid var(--search-border); border-radius: 8px; padding: 28px; } +.browser-search label { display: block; font-weight: 700; margin-bottom: 8px; } +.browser-search .query-row { display: flex; gap: 12px; } +.browser-search .query-field { position: relative; display: flex; flex: 1; min-width: 0; } +.browser-search .query-clear { + position: absolute; right: 8px; top: 50%; transform: translateY(-50%); + width: 32px; height: 36px; padding: 0; border: 0; border-radius: 4px; + background: transparent; color: var(--search-muted); font: inherit; font-size: 22px; + cursor: pointer; opacity: 0; +} +.browser-search .query-field:hover .query-clear, +.browser-search .query-field:focus-within .query-clear { opacity: 1; } +.browser-search .query-field:has(.query-clear:not([hidden])) input { padding-right: 44px; } +.browser-search input, .browser-search select, .browser-search textarea { + font: inherit; color: inherit; border: 1px solid var(--search-border); + border-radius: 4px; background: var(--search-field); padding: 12px; min-height: 48px; +} +.browser-search input { flex: 1; width: 100%; min-width: 0; } +.browser-search input::placeholder { color: var(--search-muted); opacity: 1; } +.browser-search select, .browser-search textarea { width: 100%; } +.browser-search select:disabled { opacity: .55; cursor: not-allowed; } +.browser-search .search-button { + display: inline-flex; align-items: center; justify-content: center; min-height: 48px; + border: 1px solid var(--lp-accent-primary, #6023de); border-radius: 4px; + background: var(--lp-accent-primary, #6023de); color: white; padding: 10px 26px; + font: inherit; font-weight: 700; cursor: pointer; +} +.browser-search .search-button:hover { background: #4c18b8; border-color: #4c18b8; } +.browser-search .search-button-secondary { background: transparent; color: var(--search-link); border-color: var(--search-link); } +.browser-search .search-button-secondary:hover { background: var(--search-chip); border-color: var(--search-link); } +.browser-search :focus-visible { outline: 3px solid var(--search-link); outline-offset: 4px; } +.browser-search .search-examples { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 14px; color: var(--search-muted); } +.browser-search .search-examples button { border: 0; background: transparent; color: var(--search-link); text-decoration: underline; text-underline-offset: 3px; font: inherit; cursor: pointer; padding: 4px 0; min-height: 32px; } +.browser-search .filters { display: grid; grid-template-columns: 1fr 1.4fr 1.2fr; gap: 20px; margin-top: 24px; padding-top: 24px; border-top: 1px solid var(--search-border); } +.browser-search .filters > div { min-width: 0; } +.browser-search .search-results { margin-top: 28px; } +.browser-search #status { font-weight: 700; margin-bottom: 20px; } +.browser-search .search-diagnostics { margin: -8px 0 20px; font-size: .9rem; } +.browser-search .search-diagnostics p { margin: 4px 0; } +.browser-search #results { list-style: none; padding: 0; margin: 0; display: grid; gap: 16px; } +.browser-search .search-result-card { background: var(--search-surface); border: 1px solid var(--search-border); border-radius: 8px; padding: 24px 28px; } +.browser-search .search-result-card:hover { border-color: var(--search-link); } +.browser-search .result-meta { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center; margin: 0 0 12px; color: var(--search-muted); } +.browser-search .result-type { background: var(--search-chip); color: var(--search-link); border-radius: 4px; padding: 2px 10px; font-weight: 700; } +.browser-search .result-title { font-size: 1.35rem; margin: 0 0 10px; line-height: 1.35; } +.browser-search a { color: var(--search-link); text-underline-offset: 3px; } +.browser-search .result-title a { text-decoration: none; } +.browser-search .result-title a:hover { text-decoration: underline; } +.browser-search .result-description { margin: 0 0 16px; } +.browser-search .result-footer { display: flex; flex-wrap: wrap; gap: 8px 24px; color: var(--search-muted); border-top: 1px solid var(--search-border); padding-top: 14px; } +.browser-search .result-footer p { margin: 0; } +.browser-search #more { margin: 24px 0 0; } +.browser-search [hidden] { display: none !important; } +.browser-search .muted { color: var(--search-muted); } +.browser-search .agent-section { margin-top: 48px; padding-top: 36px; border-top: 1px solid var(--search-border); scroll-margin-top: 100px; } +.browser-search #search-form { scroll-margin-top: 100px; } +.browser-search .agent-section .search-intro { margin-bottom: 16px; } +.browser-search .search-tools { margin-top: 20px; } +.browser-search summary { cursor: pointer; font-weight: 700; } +.browser-search .search-tools h3 { font-size: 1.25rem; margin-top: 24px; } +.browser-search .search-tools textarea { font-family: ui-monospace, monospace; margin-bottom: 12px; } +.browser-search pre { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 450px; overflow: auto; background: var(--search-field); color: inherit; border-radius: 4px; } +.browser-search pre:not(:empty) { border: 1px solid var(--search-border); padding: 16px; } +@media (max-width: 760px) { + .browser-search { padding: 28px 16px 40px; } + .browser-search .filters { grid-template-columns: 1fr; gap: 16px; } + .browser-search .search-panel, .browser-search .search-result-card { padding: 20px; } +} +@media (max-width: 440px) { + .browser-search .query-row { flex-direction: column; } + .browser-search .query-row .search-button { width: 100%; } +} diff --git a/assets/search-lab/worker.js b/assets/search-lab/worker.js new file mode 100644 index 0000000000..300349124a --- /dev/null +++ b/assets/search-lab/worker.js @@ -0,0 +1,20 @@ +import { createEngine } from './engine.mjs'; +let search; +self.onmessage = async ({ data }) => { + const { id, action, input, url } = data; + try { + if (action === 'init') { + const start = performance.now(); + const response = await fetch(url); + if (!response.ok) throw Error(`Index request failed (${response.status}).`); + const text = await response.text(); + const index = JSON.parse(text); + search = createEngine(index.records); + self.postMessage({ id, result: { count: index.records.length, bytes: new TextEncoder().encode(text).length, loadMs: performance.now() - start, categories: [...new Set(index.records.map(r => r.category).filter(Boolean))].sort() } }); + } else { + if (!search) throw Error('Search index is not ready.'); + const start = performance.now(); + self.postMessage({ id, result: { ...search(input), searchMs: performance.now() - start } }); + } + } catch (error) { self.postMessage({ id, error: error.message }); } +}; diff --git a/config.production.toml b/config.production.toml index a3295223ff..ccb9d7635a 100644 --- a/config.production.toml +++ b/config.production.toml @@ -1,5 +1,3 @@ [params] # Use Arm's stable navigation and footer bundle in production. global_web_components_url = "https://www.arm.com/arm-global-web-components/devhub/app.js" -# Use the production chatbot component on learn.arm.com. -chatbot_component_url = "https://content.ipexplorer.arm.com/VirtualFAE/learnarm/chat-ai.js" diff --git a/config.toml b/config.toml index 97f5c52cd6..58440bd2b7 100644 --- a/config.toml +++ b/config.toml @@ -77,9 +77,6 @@ assetsDir = "assets" # Optionally override the global web components bundle for a specific deployment. # global_web_components_url = "https://www.arm.com/arm-global-web-components/devhub/app.js" -# Use the pre-production chatbot component for local and internal builds. -chatbot_component_url = "https://content.dev.bespin.arm.com/VirtualFAE/learnarm/chat-ai.js" - # Specify a value here if your content directory is not in your repo's root directory # github_subdir = "" diff --git a/content/install-guides/c2patool.md b/content/install-guides/c2patool.md index 6806971709..763a5bf3cb 100644 --- a/content/install-guides/c2patool.md +++ b/content/install-guides/c2patool.md @@ -5,8 +5,6 @@ minutes_to_complete: 20 official_docs: https://github.com/contentauth/c2patool author: Kostiantyn Perehuda -draft: true - test_maintenance: false ### FIXED, DO NOT MODIFY @@ -17,19 +15,13 @@ multitool_install_part: false layout: installtoolsall --- -## Overview +Content Credentials use the Coalition for Content Provenance and Authenticity (C2PA) standard to record media provenance. [C2PA Tool](https://github.com/contentauth/c2patool), invoked as `c2patool`, reads, creates, and validates Content Credentials in supported media files. -[C2PA Tool](https://github.com/contentauth/c2patool), invoked as `c2patool`, reads, creates, and -validates Content Credentials in supported media files. Content Credentials use the -Coalition for Content Provenance and Authenticity (C2PA) standard to record media provenance. +You can install C2PA Tool on 64-bit Arm Linux, macOS on Apple silicon, or Windows on Arm. The Linux instructions in this guide cover Ubuntu and Debian. -You can install C2PA Tool on 64-bit Arm Linux, macOS on Apple silicon, or Windows on Arm. -The Linux instructions cover Ubuntu and Debian. +On macOS, you'll install C2PA Tool with Homebrew. -On macOS, install C2PA Tool with Homebrew. On Arm Linux and Windows on Arm, the following -steps use Cargo, Rust's package manager, to download the source code, compile it for Arm, -and install the executable. Building from source produces an executable that runs natively -on your Arm system. +On Arm Linux and Windows on Arm, use Cargo, the Rust package manager, to install C2PA Tool. With Cargo, you'll download the source code and compile it for Arm. This produces an executable for your Arm platform. Homebrew and Cargo install the version available from their package source. For release information, see [C2PA Tool releases](https://github.com/contentauth/c2patool/releases). @@ -67,7 +59,7 @@ Build C2PA Tool from source with the ARM64 Rust toolchain to install a native AR on Windows on Arm. Install the Microsoft C++ Build Tools and [Rust](https://www.rust-lang.org/tools/install) with the -MSVC toolchain for ARM64. Install [7-Zip](https://www.7-zip.org/) and add its installation +Microsoft Visual C++ (MSVC) toolchain for ARM64. Install [7-Zip](https://www.7-zip.org/) and add its installation directory (typically `C:\Program Files\7-Zip`) to your `PATH`. Open a new PowerShell window and confirm that 7-Zip is available: @@ -76,7 +68,9 @@ Open a new PowerShell window and confirm that 7-Zip is available: 7z -h ``` -The output displays the 7-Zip version and command usage. Then build and install C2PA Tool from source: +The output displays the 7-Zip version and command usage. + +Then, build and install C2PA Tool from source: ```console cargo install c2patool --locked @@ -106,7 +100,7 @@ The expected output starts with: Tool for displaying and creating C2PA manifests. ``` -## Troubleshooting +## Troubleshoot installation issues If your shell can't find `c2patool`, open a new terminal. For a Cargo installation, confirm that `$HOME/.cargo/bin` on Linux or `%USERPROFILE%\.cargo\bin` on Windows is in your `PATH`. @@ -139,3 +133,7 @@ For a Cargo installation, run: ```console cargo uninstall c2patool ``` + +## Next steps + +You can now use the C2PA Tool to work with Content Credentials on Arm. \ No newline at end of file diff --git a/content/install-guides/exiftool.md b/content/install-guides/exiftool.md index 74c98b90d1..d2d189a019 100644 --- a/content/install-guides/exiftool.md +++ b/content/install-guides/exiftool.md @@ -5,8 +5,6 @@ minutes_to_complete: 10 official_docs: https://exiftool.org/install.html author: Kostiantyn Perehuda -draft: true - test_maintenance: false ### FIXED, DO NOT MODIFY @@ -17,14 +15,12 @@ multitool_install_part: false layout: installtoolsall --- -## Overview - [ExifTool](https://exiftool.org/) reads and writes metadata in image, audio, and video files. -You can install it on macOS on Apple silicon, 64-bit Arm Linux, or Windows 11 on Arm. -On Windows on Arm, you use the Windows x64 executable under emulation. +You can install ExifTool on macOS on Apple silicon, 64-bit Arm Linux, or Windows 11 on Arm. +On Windows on Arm, you'll use the Windows x64 executable under emulation. -Package-manager commands install the version available from your package source. See the -[ExifTool download page](https://exiftool.org/) for current releases. +Package-manager commands install the version available from your package source. For current releases, see the +[ExifTool download page](https://exiftool.org/). ## Install on macOS @@ -62,8 +58,11 @@ Follow the [official Windows installation instructions](https://exiftool.org/ins 2. Extract the archive and open the extracted folder containing `exiftool(-k).exe`. 3. In File Explorer, select **View > Show > File name extensions**, then rename `exiftool(-k).exe` to `exiftool.exe`. 4. Move `exiftool.exe` and its accompanying `exiftool_files` folder into a permanent folder, such as `C:\Users\\Tools\ExifTool`. Keep them together. -5. Search the Start menu for **Edit environment variables for your account** and open it. Under **User variables**, select **Path**, then **Edit > New**. Add the full path to the folder directly containing `exiftool.exe`, such as `C:\Users\\Tools\ExifTool`, replacing `` with your Windows user folder name. Select **OK** to save and close the dialogs. -6. Open a new PowerShell window so it picks up the change. +5. Search the **Start** menu for **Edit environment variables for your account** and open it. +6. Under **User variables**, select **Path**, then select **Edit > New**. +7. Add the full path to the folder directly containing `exiftool.exe`, such as `C:\Users\\Tools\ExifTool`. Replace `` with the name of your Windows user folder. +8. Select **OK** to save and close the dialogs. +9. Open a new PowerShell window so that it picks up the change. ## Verify the installation @@ -79,7 +78,7 @@ The output is similar to: 13.55 ``` -Your version can differ. List the available metadata tags to confirm ExifTool runs: +Your version can differ. List the available metadata tags to confirm that ExifTool runs: ```console exiftool -list @@ -87,11 +86,14 @@ exiftool -list The output begins with `Available tags:` followed by tag names. -## Troubleshooting +## Troubleshoot installation issues + +If your shell can't find `exiftool`, open a new terminal. Confirm that the directory containing +`exiftool` is in your `PATH`. On Windows, also check that the executable is named `exiftool.exe` and that `exiftool_files` is still beside it. + +If Ubuntu can't locate the package, check that the `universe` repository is enabled for your Ubuntu +release. After checking, run `sudo apt update` again. -If your shell can't find `exiftool`, open a new terminal and confirm that the directory containing -`exiftool` is in your `PATH`. On Windows, also check that the executable is named `exiftool.exe` -and that `exiftool_files` is still beside it. +## Next steps -If Ubuntu cannot locate the package, check that the `universe` repository is enabled for your Ubuntu -release, then run `sudo apt update` again. +You can now use ExifTool to read and write media metadata on Arm. diff --git a/content/install-guides/libjpeg-turbo.md b/content/install-guides/libjpeg-turbo.md index c2582eab5a..e8e496463b 100644 --- a/content/install-guides/libjpeg-turbo.md +++ b/content/install-guides/libjpeg-turbo.md @@ -5,8 +5,6 @@ minutes_to_complete: 10 official_docs: https://libjpeg-turbo.org/Documentation/Documentation author: Kostiantyn Perehuda -draft: true - test_maintenance: false ### FIXED, DO NOT MODIFY @@ -17,11 +15,10 @@ multitool_install_part: false layout: installtoolsall --- -## Overview - -[libjpeg-turbo](https://www.libjpeg-turbo.org/) provides JPEG encoding and decoding libraries and -command-line tools. The tools include `cjpeg` for encoding, `djpeg` for decoding, and `jpegtran` -for transforming JPEG images. +[libjpeg-turbo](https://www.libjpeg-turbo.org/) is a JPEG image codec that uses single instruction, +multiple data (SIMD) instructions +to accelerate JPEG encoding and decoding. It provides libraries and command-line tools, +including `cjpeg` for encoding, `djpeg` for decoding, and `jpegtran` for transforming JPEG images. You can install the tools on macOS on Apple silicon, 64-bit Arm Linux, or Windows on Arm. Package-manager commands on macOS and Linux install the version available from your package source. @@ -37,6 +34,8 @@ brew install jpeg-turbo ## Install on Arm Linux +Use the commands for your Linux distribution. + On Ubuntu and Debian, install the `libjpeg-turbo-progs` package: ```console @@ -56,21 +55,25 @@ runtime library doesn't provide these tools. ## Install on Windows on Arm {{% notice Note %}} -The following steps use libjpeg-turbo version 3.1.4.1. You can use another version that provides -a native ARM64 installer. Replace the installer with the file for your chosen version. +The following steps use libjpeg-turbo version `3.1.4.1`. You can use another version that provides +an ARM64 installer. Replace the installer with the file for your chosen version. To find the latest version, see [libjpeg-turbo releases](https://github.com/libjpeg-turbo/libjpeg-turbo/releases). {{% /notice %}} 1. Download `libjpeg-turbo-3.1.4.1-vc-arm64.exe` from the [libjpeg-turbo 3.1.4.1 release](https://github.com/libjpeg-turbo/libjpeg-turbo/releases/tag/3.1.4.1). 2. Run the installer and note the installation directory. -3. Add the installation's `bin` directory, containing `jpegtran.exe`, to your user's `Path` - environment variable. -4. Open a new PowerShell window so it picks up the change. +3. Open the installation directory in **File Explorer**. +4. Open the `bin` folder and confirm that it contains `jpegtran.exe`. +5. Copy the full path to the `bin` folder from the address bar. +6. Search the **Start** menu for **Edit environment variables for your account** and open it. +7. Under **User variables**, select **Path**, then select **Edit > New**. +8. Paste the path to the `bin` folder, then select **OK** to save and close the dialogs. +9. Open a new PowerShell window to use the updated `Path`. ## Verify the installation -Print the installed version: +In your terminal, or PowerShell on Windows, print the installed version: ```console jpegtran -version @@ -85,11 +88,15 @@ libjpeg-turbo version 3.1.4.1 (build 20260327) Your version and build can differ. A successful version check confirms that your terminal can find and run `jpegtran`. -## Troubleshooting +## Troubleshoot installation issues + +If your shell can't find `jpegtran`, open a new terminal. Confirm that the directory containing the +executable is in your `PATH`. On Windows, check for `jpegtran.exe` in the `bin` +directory of the installation. On Linux, check that you installed the command-line tools package for your distribution. + +If Ubuntu can't locate the package, check that the `universe` repository is enabled for your +release. Then, run `sudo apt update` again. -If your shell can't find `jpegtran`, open a new terminal and confirm that the directory containing the -executable is in your `PATH`. On Windows, check for `jpegtran.exe` in the installation's `bin` -directory. On Linux, check that you installed the command-line tools package for your distribution. +## Next steps -If Ubuntu cannot locate the package, check that the `universe` repository is enabled for your -release, then run `sudo apt update` again. +You can now encode, decode, and transform JPEG images on Arm using `libjpeg-turbo`. diff --git a/content/install-guides/opensandbox.md b/content/install-guides/opensandbox.md new file mode 100644 index 0000000000..16c80ded7f --- /dev/null +++ b/content/install-guides/opensandbox.md @@ -0,0 +1,253 @@ +--- +title: OpenSandbox + +description: Install OpenSandbox on Arm Linux and run a Python command in a Docker-backed sandbox. + +minutes_to_complete: 20 +official_docs: https://open-sandbox.ai/getting-started/ + +author: Anupras Mohapatra + +test_maintenance: false + +additional_search_terms: +- opensandbox +- osb +- sandbox +- docker +- containers +- arm64 + +weight: 1 +tool_install: true +multi_install: false +multitool_install_part: false +layout: installtoolsall +--- + +OpenSandbox provides a server and a command-line interface (CLI) for creating and managing isolated environments. Its Docker runtime runs sandboxes as containers that share the host Linux kernel. + +You'll install OpenSandbox on an `aarch64` host with Ubuntu 24.04 LTS or later. Then, you'll start a local server and create a sandbox. Finally, you'll run a Python command inside it to verify the installation. + +## Before you begin + +You need an Arm Linux system running Ubuntu 24.04 LTS with `sudo` access. The system also needs to have internet access to the Ubuntu package repositories, Python package index, and Docker Hub. Allow free disk space for Docker images and container data. You don't need kernel-based virtual machine (KVM) for the Docker runtime. + +Check the host architecture and operating system: + +```bash +uname -m +cat /etc/os-release +``` + +Confirm that the architecture is `aarch64` and that the operating system is Ubuntu 24.04 LTS or later. + +You also need Docker Engine version 20.10 or later installed on your system. For installation steps, see the [Docker Engine install guide](/install-guides/docker/docker-engine/). + +Confirm that Docker works without `sudo`: + +```bash +docker version +docker info --format '{{.Architecture}}' +``` + +The server version should be at least `20.10`, and the architecture should be `aarch64`. If Docker reports a socket permission error, log out and log back in before continuing. + +### Install `uv` + +Install `uv` using the [official installer](https://docs.astral.sh/uv/getting-started/installation/): + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +Make the installed command available in your current shell, then check its version: + +```bash +export PATH="$HOME/.local/bin:$PATH" +uv --version +``` + +Add the same `export PATH` line to `~/.bashrc` if `~/.local/bin` isn't already on your `PATH` in new shells. + +## Install and configure OpenSandbox + +Install the OpenSandbox server and CLI as user-level tools: + +```bash +uv tool install opensandbox-server +uv tool install opensandbox-cli +``` + +Confirm that both commands are available: + +```bash +opensandbox-server --help +osb --version +``` + +Generate a server configuration for the Docker runtime: + +```bash +opensandbox-server init-config ~/.sandbox.toml --example docker +``` + +`host` under `[server]` is set to `127.0.0.1` by default. Confirm that this is the case in `~/.sandbox.toml`: + +```toml +[server] +host = "127.0.0.1" +``` + +Binding the server to localhost keeps its API reachable from the same host without exposing port `8080`. + +The example configuration has no API key. Use this configuration only for testing the installation. If the server asks you to acknowledge the unauthenticated configuration, enter `YES`. + +Start the server in this terminal and leave it running: + +```bash +opensandbox-server +``` + +In a second terminal on the same host, check the server health endpoint: + +```bash +curl http://127.0.0.1:8080/health +``` + +The expected output is: + +```output +{"status":"healthy"} +``` + +Configure the CLI to use the local server and proxy sandbox requests through it: + +```bash +osb config init +osb config set connection.domain localhost:8080 +osb config set connection.protocol http +osb config set connection.use_server_proxy true +``` + +## Verify the installation + +Create a sandbox from the Python image with a 30-minute timeout: + +```bash +osb sandbox create --image python:3.12 --timeout 30m -o json +``` + +The first run downloads the Python image and the OpenSandbox execution helper image. The command returns JSON containing the new sandbox ID. + +The output is similar to: + +```output +{ + "id": "5e4b52ca-9947-4f3a-a1a7-6254239cd431", + "status": "created", + "timeout": "0:30:00", + "image": "python:3.12" +} +``` + +Copy the `id` into a shell variable in the second terminal: + +```bash +sandbox_id="" +``` + +Replace `` with the `id` that was returned by your `create` command. + +Check that the sandbox is healthy: + +```bash +osb sandbox health "$sandbox_id" -o json +``` + +The output is similar to: + +```output +{ + "sandbox_id": "5e4b52ca-9947-4f3a-a1a7-6254239cd431", + "healthy": true +} +``` + +Run the architecture check inside the sandbox: + +```bash +osb command run "$sandbox_id" -o raw -- sh -c 'uname -m' +``` + +The expected output is: + +```output +aarch64 +``` + +The `aarch64` result confirms that the sandbox container runs on Arm. + +Run a Python command inside the sandbox: + +```bash +osb command run "$sandbox_id" -o raw -- sh -c 'python -c "print(6 * 7)"' +``` + +The expected output is: + +```output +42 +``` + +The Python result confirms that you can run a command in the sandbox through the OpenSandbox CLI. + +## Clean up test resources + +Delete the test sandbox: + +```bash +osb sandbox kill "$sandbox_id" -o json +``` + +The output is similar to: + +```output +[ + { + "sandbox_id": "5e4b52ca-9947-4f3a-a1a7-6254239cd431", + "status": "deletion_requested" + } +] +``` + +List sandboxes to confirm deletion: + +```bash +osb sandbox list -o json +``` + +The output is similar to: + +```output +{ + "items": [], + "pagination": { + "page": 1, + "page_size": 20, + "total_items": 0, + "total_pages": 0, + "has_next_page": false + } +} +``` + +The `kill` command can return `deletion_requested` while removal completes. Repeat the `osb sandbox list` command if the sandbox is still present. The server configuration, CLI settings, and downloaded images remain available for future use. + +After deleting the sandbox, return to the terminal with the running server. Shut the server by pressing **Ctrl + C**. + +## Next steps + +You can now use OpenSandbox to create and manage Docker-backed sandboxes on Arm Linux. + +For additional commands, see the [OpenSandbox CLI documentation](https://open-sandbox.ai/cli/). For server authentication and deployment settings, see the [OpenSandbox configuration documentation](https://open-sandbox.ai/getting-started/configuration). diff --git a/content/install-guides/wasmcloud.md b/content/install-guides/wasmcloud.md new file mode 100644 index 0000000000..efc6f85aab --- /dev/null +++ b/content/install-guides/wasmcloud.md @@ -0,0 +1,201 @@ +--- +title: wasmCloud +description: Install and verify the wash CLI for wasmCloud on Arm Linux so you can develop and run WebAssembly components on Arm servers and cloud instances. +minutes_to_complete: 10 +official_docs: https://wasmcloud.com/docs/installation/ +author: Jason Andrews +additional_search_terms: + - wash + - WebAssembly + - WASI + - Graviton + - aarch64 + +### FIXED, DO NOT MODIFY +weight: 1 +tool_install: true +multi_install: false +multitool_install_part: false +layout: installtoolsall +--- + +[wasmCloud](https://wasmcloud.com/) hosts applications packaged as WebAssembly components. Its Wasm Shell, `wash`, provides commands for developing, building, and running components, including a local development server through `wash dev`. + +You can install `wash` on Arm Linux without compiling wasmCloud or installing Rust. To build an application, you'll need the appropriate language toolchain. + +## Before you begin + +You need an Arm Linux system and internet access. + +Confirm your processor architecture: + +```bash +uname -m +``` + +The expected output is: + +```output +aarch64 +``` + +## Install `wash` + +There are two ways to install `wash`. The install script is a single command that always installs the latest version. The GitHub release download lets you pin a specific version and verify the checksum before installing. + +### Install using the install script + +Install `curl`: + +```bash +sudo apt-get update +sudo apt-get install -y curl +``` + +Run the install script to download and install `wash`: + +```bash +curl -fsSL https://wasmcloud.com/sh | bash +``` + +The script detects your platform automatically and installs `wash` to `~/.wash/bin`. + +The output is similar to: + +```output +[INFO] Installing wash - The Wasm Shell +[INFO] Install directory: ~/.wash/bin +[INFO] Detecting platform... +[INFO] Platform detected: aarch64-unknown-linux-gnu +[INFO] Fetching latest release information... +[INFO] Version: v2.12.0 +[INFO] Downloading wash binary... +[SUCCESS] wash v2.12.0 installed successfully to ~/.wash/bin/wash +[SUCCESS] Verified installation +``` + +The script updates your shell profile automatically. + + Open a new terminal, or run the following command to use `wash` in your current session: + +```bash +source ~/.bashrc +``` + +### Install a specific version from GitHub + +Use this method when you need a pinned, verifiable install. + +Install `curl` and `ca-certificates`: + +```bash +sudo apt-get update +sudo apt-get install -y ca-certificates curl +``` + +{{% notice Note %}} +The following commands use `wash` version `2.11.0`. The same commands work with other versions. Replace the file used in these steps with the file for your version of choice. To find the latest version, see [wasmCloud releases](https://github.com/wasmCloud/wasmCloud/releases). +{{% /notice %}} + +Download the prebuilt `aarch64` binary: + +```bash +mkdir -p "$HOME/.cache/wasmcloud-install/2.11.0" +curl -fL --retry 3 \ + https://github.com/wasmCloud/wasmCloud/releases/download/v2.11.0/wash-aarch64-unknown-linux-gnu \ + -o "$HOME/.cache/wasmcloud-install/2.11.0/wash" +``` + +Verify the SHA-256 checksum against the value published for the [v2.11.0 release](https://github.com/wasmCloud/wasmCloud/releases/tag/v2.11.0): + +```bash +printf '%s %s\n' \ + 'c3b2204dd99ae2bc46c1236967f4a328cebe3b57d3d5a1605091618eff66bcad' \ + "$HOME/.cache/wasmcloud-install/2.11.0/wash" \ + | sha256sum --check +``` + +The output is similar to: + +```output +/home/ubuntu/.cache/wasmcloud-install/2.11.0/wash: OK +``` + +If your username isn't `ubuntu`, the path will reflect your actual username. If the checksum fails, download the file again and confirm that the version and asset name match before continuing. + +Install `wash` to your user executable directory: + +```bash +mkdir -p "$HOME/.local/bin" +install -m 0755 "$HOME/.cache/wasmcloud-install/2.11.0/wash" "$HOME/.local/bin/wash" +``` + +Add `$HOME/.local/bin` to your PATH: + +```bash +export PATH="$HOME/.local/bin:$PATH" +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc +``` + +## Verify the installation + +Check the installed version: + +```bash +wash --version +``` + +The output is similar to: + +```output +wash 2.12.0 +``` + +Confirm that the development host command is available: + +```bash +wash dev --help +``` + +The output is similar to: + +```output +Start a development server for a Wasm component + +Usage: wash dev [OPTIONS] + +Options: + -o, --output + Specify output format (text or json) + + [default: text] + + -l, --log-level + Set the opentelemetry log level (trace, debug, info, warn, error) + + [default: INFO] + + --verbose + Enable verbose output + + --non-interactive + Run in non-interactive mode: skip terminal checks for host exec and accept the default + answer for any confirmation prompts. Automatically enabled when stdin is not a TTY + + --user-config + Path to user configuration file + + --meters + What to measure about guests: `off`, `duration`, or `fuel` [default: duration] + + -h, --help + Print help (see a summary with '-h') +``` + +Displaying help doesn't start a listener or load an application. + +## Next steps + +You can now use `wash` to build and run a WebAssembly application on Arm. + +To learn more, see [Run and benchmark a WebAssembly application on AWS Graviton with wasmCloud](/learning-paths/servers-and-cloud-computing/wasmcloud/). diff --git a/content/learning-paths/automotive/innexis_ana_zena/1_understand_zena_css.md b/content/learning-paths/automotive/innexis_ana_zena/1_understand_zena_css.md new file mode 100755 index 0000000000..77e7b18122 --- /dev/null +++ b/content/learning-paths/automotive/innexis_ana_zena/1_understand_zena_css.md @@ -0,0 +1,34 @@ +--- +title: Understand the Zena CSS virtual platform +weight: 2 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Understand the Zena CSS virtual platform + +In [Innexis ANA](https://www.siemens.com/en-gb/products/ic/hav/innexis/architecture-native-acceleration/), a *chamber* provides the environment in which you create and run a virtual platform. The **Arm-Zena-CSS** vSoC describes the virtual hardware. The [**Arm-Zena-CSS EWAOL Software Stack**](https://arm-zena-css.docs.arm.com/en/v2.2/overview.html) supplies the software images used to boot it. + +This Learning Path follows Siemens Innexis ANA **2026.2**. ANA menus, supplied assets, and software behavior may change in later releases. Use the ANA User Guide that matches your chamber version when a screen differs from this tutorial. + +Zena CSS has more than one processing area. You will inspect these consoles after starting the virtual platform: + +| Console in ANA | What to look for | +| --- | --- | +| **RSE** | Firmware messages associated with the Root of Security for the system. | +| **Safety Island** | SCP firmware messages from the Safety Island. | +| **Primary Compute Secure** | Secure-world and boot-stage messages for the primary-compute system. | +| **Primary Compute** | EWAOL Linux boot messages and the login prompt. | + +The RSE starts the boot process on a Cortex-M55. It prepares firmware for the Cortex-R82AE Safety Island and the Primary Compute. SCP firmware on the Safety Island then powers on the Primary Compute. + +Trusted Firmware-A (TF-A) and OP-TEE run in the Primary Compute secure world. U-Boot and systemd-boot lead to Linux in the normal world. The two Primary Compute console views show software running on the same Cortex-A720AE cores, not two separate groups of CPUs. See the [Arm Zena CSS boot process](https://arm-zena-css.docs.arm.com/en/latest/design/boot_process.html) for the reference software flow. + +The firmware consoles help you see how the virtual system reaches Linux. They do not, by themselves, demonstrate that an application is secure or that a product has passed a security assessment. + +For this Learning Path, your first working result is the **EWAOL Linux login prompt on the primary-compute console**. You will then inspect the guest from its shell. You do not need to change firmware or rebuild the software stack. + +## Check your understanding + +Before continuing, locate **Arm-Zena-CSS** and its **EWAOL Software Stack** in your chamber. If either is absent, ask the chamber administrator which Zena assets are available. diff --git a/content/learning-paths/automotive/innexis_ana_zena/2_create_and_start.md b/content/learning-paths/automotive/innexis_ana_zena/2_create_and_start.md new file mode 100755 index 0000000000..7d7aee4f96 --- /dev/null +++ b/content/learning-paths/automotive/innexis_ana_zena/2_create_and_start.md @@ -0,0 +1,68 @@ +--- +title: Create and start a Zena CSS virtual platform +weight: 3 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Create and start a Zena CSS virtual platform + +Use the ANA Web IDE to create a new virtual-platform project. This Learning Path uses the supplied EWAOL images, so you do not need to build software before the first boot. + +## Create the project + +1. Sign in to the ANA chamber provided by your administrator and open the **Web IDE**. +2. Start the **Create a New Virtual Platform Project** workflow described in your Innexis ANA User Guide. +3. Select **Arm-Zena-CSS** as the vSoC. +4. Select **Arm-Zena-CSS EWAOL Software Stack** as the software stack. +5. Give the project a recognizable name and finish the creation workflow. Record any configuration option you change from its default. + +![ANA project form with Arm-Zena-CSS and its EWAOL software stack selected#center](create_zena_project.png "Create a Zena CSS virtual platform project") + +The new project appears in the Web IDE with a Zena CSS virtual platform and its selected EWAOL software. Verify both selections before starting. Project creation alone does not show that Linux can boot. + +![Zena CSS project and virtual SoC configuration in the ANA Web IDE#center](project_conf.png "Zena CSS project configuration") + + +{{% notice Note %}} +If the project workflow offers a different Zena or EWAOL variant, record its full name and check with the chamber administrator before continuing. The prebuilt software must match the chamber host and vSoC configuration. +{{% /notice %}} + +## Configure Host Access before starting + +Set up the folder for the file-sharing exercise while the virtual platform is stopped. ANA disables **Host Filesystem - Access Path** and **Browse** while a VP is running. + +1. In the Web IDE **Explorer**, create a folder in your new project for the shared files, such as **test_folder**. The **New Folder** control is in the **WORKSPACE** section of Explorer. +2. Open **Lab Manager**, select the new virtual platform, and expand **Software > Host Access**. +3. Next to **Host Filesystem - Access Path**, choose **Browse**. The directory selector shows the ANA project workspace, not folders on your local computer. Select the shared folder in your project. +4. Set **Host Filesystem - Guest Mount ID** to **share** and choose **Save**. + +Confirm that **Access Path** shows the selected project folder and **Guest Mount ID** is **share** before choosing **Start**. You will use this folder and mount ID in the file-sharing exercise. + +![ANA Host Access settings with a project folder and guest mount ID share#center](ana_host_access.png "Configure Host Access before starting") + +## Start the virtual platform + +1. Open **Lab Manager** from the Web IDE. +2. Select the virtual platform you just created. +3. Choose **Start**. +4. Open the **Serial Console** view and select the primary-compute console. + +![ANA Lab Manager showing a stopped virtual platform and its Start button#center](ana_lab_manager_start.png "Start the Zena CSS virtual platform") + +Lab Manager shows that the selected virtual platform is starting or running, and the primary-compute console begins displaying boot messages. Boot may take time; wait for a Linux login prompt before treating it as complete. + +![Primary Compute serial console at the EWAOL Linux login prompt#center](primary_compute_console.png "EWAOL Linux login prompt") + + +{{% notice Note %}} +If the virtual platform stops before reaching a login prompt, record its Lab Manager state and the last visible console messages before retrying. Check the selected vSoC, software stack, and chamber compatibility. +{{% /notice %}} + + +## Record the starting configuration + +Keep a short record of the ANA release, chamber host type, project name, vSoC selection, software-stack name, and any memory setting you changed. + +You are ready to continue when you can open the console for the **same project** that you started. diff --git a/content/learning-paths/automotive/innexis_ana_zena/3_observe_boot.md b/content/learning-paths/automotive/innexis_ana_zena/3_observe_boot.md new file mode 100755 index 0000000000..903b601d98 --- /dev/null +++ b/content/learning-paths/automotive/innexis_ana_zena/3_observe_boot.md @@ -0,0 +1,103 @@ +--- +title: Observe Zena CSS boot and inspect Linux +weight: 4 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Observe Zena CSS boot and inspect Linux + +Zena CSS exposes separate consoles for its RSE, Safety Island, and primary-compute software. Check each view before entering commands in the Linux guest. + +## Follow the boot handoff + +Use this sequence to connect the console views to the [Arm Zena CSS boot process](https://arm-zena-css.docs.arm.com/en/latest/design/boot_process.html): + +1. The Runtime Security Engine (RSE) starts first and prepares the Safety Island firmware and the Primary Compute boot image. +2. SCP firmware on the Safety Island starts and powers on the Primary Compute. +3. Trusted Firmware-A (TF-A) starts in the Primary Compute secure world. OP-TEE provides secure-world services, while U-Boot continues the normal-world boot. +4. U-Boot starts systemd-boot, which starts the Linux kernel and EWAOL userspace. + +The Primary Compute Secure and Primary Compute views do not represent separate sets of CPUs. The secure-world software and Linux use the same application processor cores. This sequence describes the handoff; console timestamps from different views are not a single synchronized timeline. + +## Inspect the boot consoles + +In ANA's **Serial Console** view, select each console for your virtual platform: + +1. **RSE:** look for TF-M or RSE firmware messages. +2. **Safety Island:** look for an SCP firmware startup message. +3. **Primary Compute Secure:** look for TF-A or OP-TEE boot messages. +4. **Primary Compute:** wait for EWAOL Linux to finish booting. + +![ANA Serial Console showing Safety Island firmware output and the console selector#center](ana_console_selector.png "Select a Zena CSS boot console") + +The primary-compute console reaches a Linux login prompt. The other consoles show messages from different parts of the system; do not expect the Linux shell on those views. + +Record one identifying line from each console and the exact text of the Linux login prompt. If early firmware messages are no longer visible, record what is available instead of inferring that the stage did not run. A firmware message does not replace a Linux boot check or prove that image authentication succeeded. + +## Log in and inspect the guest + +Use the login details supplied for your chamber and software image. The Siemens reference guide uses the root account for its EWAOL example. + +Run the following commands in the **primary-compute Linux console**: + +```bash +cat /etc/os-release; uname -m; uname -r; grep -E '^(MemTotal|MemAvailable):' /proc/meminfo; df -h / +``` + +`uname -m` reports the Linux guest architecture, commonly `aarch64`. The other commands report this guest's actual OS release, kernel, memory, and root-filesystem capacity. The author's Zena guest returned: + +```output +ID=ewaol +NAME="Edge Workload Abstraction and Orchestration Layer" +VERSION="v2.1.0 (styhead)" +VERSION_ID=v2.1.0 +VERSION_CODENAME="styhead" +PRETTY_NAME="Edge Workload Abstraction and Orchestration Layer v2.1.0 (styhead)" +CPE_NAME="cpe:/o:openembedded:ewaol:v2.1.0" +aarch64 +6.6.54-yocto-standard +MemTotal: 8120232 kB +MemAvailable: 7857784 kB +Filesystem Size Used Avail Use% Mounted on +/dev/root 28G 1.3G 25G 5% / +``` + + +## Check which CPUs Linux can use + +In the same **Primary Compute Linux console**, inspect the CPUs present and online in this guest: + +```bash +cat /sys/devices/system/cpu/present +cat /sys/devices/system/cpu/online +grep -c '^processor' /proc/cpuinfo +lscpu +``` + +The `present` file lists CPUs Linux identifies as present. The `online` file lists CPUs available to the scheduler. The count from `/proc/cpuinfo` shows the processor entries in this Linux guest; it is not a count of every processing element in the Zena CSS vSoC. + +The author's Zena Linux guest returned these relevant lines. Your values can differ if your virtual platform uses another CPU configuration: + +```output +0-15 +0-15 +16 +Architecture: aarch64 +CPU(s): 16 + On-line CPU(s) list: 0-15 + Model name: Cortex-A720AE + Core(s) per cluster: 16 + Cluster(s): 1 +``` + +Linux sees 16 online Cortex-A720AE CPUs in this run. The Cortex-R82AE Safety Island and Cortex-M55 RSE run separate firmware and do not appear as extra Linux CPUs. The secure-world console also does not represent another set of CPUs. Compare the reported count with the CPU configuration selected for your VP. + +The cluster fields describe the topology presented to this Linux guest. They do not, by themselves, establish the vSoC's internal cluster layout or the topology of a future physical device. + +The vSoC's configured DRAM and the RAM usable by its Linux image can differ. Compare the Linux-visible **MemTotal** with the project configuration when you document the environment. + +If the login prompt does not appear, return to the primary-compute console and inspect its last boot messages. If a command is unavailable in your image, record the command and error and use the available files under **/proc** to inspect the same property. + +You are ready to continue when you can log in, identify the guest OS and resources, and explain why the RSE and Safety Island cores are absent from the Linux CPU list. The CPU output shown here comes from the author's Zena guest; record the values from your own VP. diff --git a/content/learning-paths/automotive/innexis_ana_zena/4_connect_to_guest.md b/content/learning-paths/automotive/innexis_ana_zena/4_connect_to_guest.md new file mode 100755 index 0000000000..358fb66bd3 --- /dev/null +++ b/content/learning-paths/automotive/innexis_ana_zena/4_connect_to_guest.md @@ -0,0 +1,86 @@ +--- +title: Connect to the Zena CSS Linux guest +weight: 5 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Connect to the Zena CSS Linux guest + +Find this VP's port mapping, authorize an SSH tunnel, and connect through the external port mapped to guest port `22`. The port and target address belong to the selected VP. Get them from ANA each time rather than copying values from another project. + +## Check the mapping and guest listener + +In **Lab Manager > Networking > Port Mapping** (or **Device Access Ports**), select the Zena VP you started. + +![ANA Port Mapping panel with the Authorize button and generated SSH tunnel command#center](ana_port_mapping.png "Authorize an ANA SSH tunnel") + +Find the TCP entry whose **target port** is `22`. Record its target IP and external port in your private run notes. Read the network prefix from the guest interface. Do not treat every listed external port as an SSH port. + +![ANA Port Mapping table showing the external TCP port mapped to guest port 22#center](ana_port_mapping2.png "Find the SSH port mapping") + +On the **primary-compute Linux console**, inspect the guest network and SSH service: + +```bash +ip -4 -br addr +systemctl is-active sshd.socket +``` + +The tested guest returned: + +```output +lo UNKNOWN 127.0.0.1/8 +eth0 UP 192.168.122.153/24 metric 10 +active +``` + +Compare the target IP in ANA's port mapping with the addresses shown by `ip -br addr`. If they differ, confirm that the mapping belongs to the VP you started. For the Zena run shown here, the mapping targeted `192.168.122.39`, while `eth0` had `192.168.122.153/24` from DHCP. Both addresses are in the same `/24` subnet. The following command added the mapped target IP temporarily as a second address on `eth0`: + +```bash +ip addr add 192.168.122.39/24 dev eth0 +ip -4 addr show dev eth0 | grep 'inet ' +``` + +The output then showed both `192.168.122.153/24` and `192.168.122.39/24`, with the latter marked `secondary`. + +``` +inet 192.168.122.153/24 metric 10 brd 192.168.122.255 scope global dynamic eth0 +inet 192.168.122.39/24 scope global secondary eth0 +``` + +Use the target IP from **your current mapping**, and the prefix for **your guest subnet**; do not copy `192.168.122.39/24` unless it matches your VP. This address is lost when the guest restarts. + +The author's guest reported `sshd.socket` as active. This does not confirm that the ANA tunnel works. If the socket is inactive on your image, start it and confirm it is active: + +```bash +systemctl start sshd.socket +systemctl is-active sshd.socket +``` + + +## Authorize the tunnel and connect + +In ANA, select or register your local SSH public key and choose **Authorize** for this VP. Copy the complete tunnel command that ANA generates and run it on your **local computer**. Keep that terminal open. Do not include the generated endpoint or key material in a published example. + +In a second **local terminal**, enter the external port from the mapping whose target port is `22`: + +```bash +printf 'External port mapped to guest port 22: ' +read -r ANA_SSH_PORT +ssh -4 -p "$ANA_SSH_PORT" root@127.0.0.1 +``` + +Run this command in the resulting **guest SSH shell**: + +```bash +uname -m; hostname +``` + +The verified SSH shell returned `aarch64` for `uname -m`; `hostname` returned `ana-rd-zena`. A response from the serial console alone does not verify SSH; run the check through the authorized local tunnel. + +If the connection fails before SSH authentication, check the terminal running the ANA tunnel. In this run, it initially reported `channel ... open failed: connect failed: No route to host`. The SSH connection succeeded after the mapped guest IP was added, without restarting the tunnel. If the error persists after confirming the guest address and SSH listener, give the chamber administrator the VP name, current mapping, and redacted tunnel error so they can check the relay route. + +## What you have learned + +You can reach the selected virtual platform from your development computer using its current ANA port mapping. Keep the authorized tunnel open if you use it to copy files in a later exercise. \ No newline at end of file diff --git a/content/learning-paths/automotive/innexis_ana_zena/5_share_and_verify.md b/content/learning-paths/automotive/innexis_ana_zena/5_share_and_verify.md new file mode 100755 index 0000000000..b6f9c318bb --- /dev/null +++ b/content/learning-paths/automotive/innexis_ana_zena/5_share_and_verify.md @@ -0,0 +1,79 @@ +--- +title: Share a file with the Zena CSS guest +weight: 6 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Share a file with the Zena CSS guest + +Before starting the VP, you configured **Lab Manager > Software > Host Access** and set **Host Filesystem - Guest Mount ID** to **share** in the previous step. The **Access Path** points to a folder in the ANA project workspace. ANA disables **Browse** while the VP is running, so set Host Access before the first start. + +This exercise uses that 9p share to exchange a small file between the ANA workspace and the EWAOL Linux guest. If you skipped Host Access, stop the VP, complete the configuration in the previous step, and start it again before mounting the share. + +## Prepare a known test file + +On your local computer, create a text file named **lp1-check.txt** containing this single line: + +```text +ANA Zena LP1 file exchange +``` + +In the Web IDE **Explorer**, right-click the folder selected as **Access Path**, choose **Upload Files...**, and select **lp1-check.txt** from your local computer. Confirm that the file appears in that ANA workspace folder. The Host Access folder selector browses the ANA workspace; it does not browse your local computer. + +## Mount the share in Linux + +Run these commands in the **primary-compute Linux console**: + +```bash +mkdir -p /mnt/ana-share +mount -t 9p -o trans=virtio share /mnt/ana-share +mount | grep ' /mnt/ana-share ' +cat /mnt/ana-share/lp1-check.txt +``` + +The `cat` command printed `ANA Zena LP1 file exchange` in the tested Zena guest. + +{{% notice Note %}} +In the tested ANA 2026.2 environment, `ls -l /mnt/ana-share` displayed the filenames but returned `Protocol error` and exit status `2`. The directory listing failed, although the known file remained readable. Verify the file with `cat` and the exact-content check below. +{{% /notice %}} + +```output +ls: reading directory '/mnt/ana-share': Protocol error +``` + +Check the exact contents: + +```bash +grep -Fx 'ANA Zena LP1 file exchange' /mnt/ana-share/lp1-check.txt && echo 'File check passed' +``` + +The tested guest printed **File check passed**. This confirms that the guest can read the selected file. It does not test guest networking or application execution. + +```output +File check passed +``` + +Create a second small file from the **guest** and inspect it in the ANA workspace: + +```bash +printf 'Zena guest wrote this file\n' > /mnt/ana-share/lp1-return.txt +cat /mnt/ana-share/lp1-return.txt +``` + +In the Web IDE **Explorer**, confirm that **lp1-return.txt** appears in the shared folder. Right-click the file and choose **Download**. Open the downloaded file on your local computer and confirm that it contains `Zena guest wrote this file`. The tested Zena share supported this guest-to-workspace write. + +![ANA Web IDE Explorer showing the shared folder and file written by the Zena guest#center](ana_share_folder.png "Verify the guest-written file in ANA") + +If the file does not appear, check that the Web IDE shared the folder containing **lp1-check.txt** and that the mount ID is **share**. If an individual file operation fails transiently, retry that operation once and record the error if it repeats. + +## Stop and check the virtual platform + +Before stopping, save the boot marker, guest environment output, SSH check, file checks, and any permitted screenshots on your local computer. Return to **Lab Manager**, select this virtual platform, and choose **Stop**. Confirm that its state changes from running to stopped; a disconnected SSH session alone is not a stop check. Keep the project so you can use it for a later exercise. + +For a persistence check, start the **same project** again. In the tested run, the shared folder and files remained in the ANA workspace, but Linux needed the 9p mount command again after the VP restarted. Remount the share and read **lp1-check.txt** to confirm it is still available. Stop the virtual platform again when you finish this check. + +## What you have accomplished + +You have completed this Learning Path when you have a recorded Linux boot, guest environment details, a successful SSH connection, a verified file round trip, and a stopped virtual platform. These results establish a boot and guest-access baseline on this virtual platform. They do not measure physical-silicon performance or validate its security properties. \ No newline at end of file diff --git a/content/learning-paths/automotive/innexis_ana_zena/_index.md b/content/learning-paths/automotive/innexis_ana_zena/_index.md new file mode 100755 index 0000000000..e5192fb51c --- /dev/null +++ b/content/learning-paths/automotive/innexis_ana_zena/_index.md @@ -0,0 +1,63 @@ +--- +title: Get Started with Arm Zena CSS on Siemens Innexis ANA +description: With Siemens Innexis ANA 2026.2, start a Zena CSS virtual platform, boot EWAOL Linux, connect to the guest, and exchange a file. + +draft: true +cascade: + draft: true + +minutes_to_complete: 60 + +who_is_this_for: Automotive and embedded Linux developers with access to an Innexis ANA chamber. + +learning_objectives: + - Create and start an Arm Zena CSS virtual platform in Innexis ANA. + - Identify the boot consoles, verify EWAOL Linux boot, and inspect the guest environment. + - Connect to the Linux guest through ANA's authorized SSH tunnel. + - Exchange a test file with the Linux guest and check its contents. +prerequisites: + - Access to an operating Siemens Innexis ANA chamber with the Arm-Zena-CSS vSoC and its EWAOL software stack. + - Permission to use the chamber and its supplied software assets. + - Basic familiarity with the Linux command line. + - A local SSH client and a public key that you can authorize for the selected virtual platform. + +author: Odin Shen + +generate_summary_faq: false +rerun_summary: false +rerun_faqs: false + +### Tags +skilllevels: Introductory +subjects: Containers and Virtualization +armips: + - Cortex-A + - Cortex-R + - Cortex-M +tools_software_languages: + - Arm Zena CSS + - Siemens Innexis ANA + - SSH +operatingsystems: + - Linux + +further_reading: + - resource: + title: Zena CSS + link: https://www.arm.com/products/autonomous-machines/zena-compute-subsystems + type: documentation + - resource: + title: Siemens Innexis Architecture Native Acceleration + link: https://www.siemens.com/en-gb/products/ic/hav/innexis/architecture-native-acceleration/ + type: documentation + - resource: + title: EWAOL development + link: https://gitlab.com/soafee/ewaol/meta-ewaol + type: documentation + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +--- diff --git a/content/learning-paths/automotive/innexis_ana_zena/_next-steps.md b/content/learning-paths/automotive/innexis_ana_zena/_next-steps.md new file mode 100755 index 0000000000..c3db0de5a2 --- /dev/null +++ b/content/learning-paths/automotive/innexis_ana_zena/_next-steps.md @@ -0,0 +1,8 @@ +--- +# ================================================================================ +# FIXED, DO NOT MODIFY THIS FILE +# ================================================================================ +weight: 21 # Set to always be larger than the content in this path to be at the end of the navigation. +title: "Next Steps" # Always the same, html page title. +layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. +--- diff --git a/content/learning-paths/automotive/innexis_ana_zena/ana_console_selector.png b/content/learning-paths/automotive/innexis_ana_zena/ana_console_selector.png new file mode 100755 index 0000000000..5ee0626c6f Binary files /dev/null and b/content/learning-paths/automotive/innexis_ana_zena/ana_console_selector.png differ diff --git a/content/learning-paths/automotive/innexis_ana_zena/ana_host_access.png b/content/learning-paths/automotive/innexis_ana_zena/ana_host_access.png new file mode 100755 index 0000000000..4097763a04 Binary files /dev/null and b/content/learning-paths/automotive/innexis_ana_zena/ana_host_access.png differ diff --git a/content/learning-paths/automotive/innexis_ana_zena/ana_lab_manager_start.png b/content/learning-paths/automotive/innexis_ana_zena/ana_lab_manager_start.png new file mode 100755 index 0000000000..f983bcf330 Binary files /dev/null and b/content/learning-paths/automotive/innexis_ana_zena/ana_lab_manager_start.png differ diff --git a/content/learning-paths/automotive/innexis_ana_zena/ana_port_mapping.png b/content/learning-paths/automotive/innexis_ana_zena/ana_port_mapping.png new file mode 100755 index 0000000000..ddec41d73b Binary files /dev/null and b/content/learning-paths/automotive/innexis_ana_zena/ana_port_mapping.png differ diff --git a/content/learning-paths/automotive/innexis_ana_zena/ana_port_mapping2.png b/content/learning-paths/automotive/innexis_ana_zena/ana_port_mapping2.png new file mode 100755 index 0000000000..0a86b64941 Binary files /dev/null and b/content/learning-paths/automotive/innexis_ana_zena/ana_port_mapping2.png differ diff --git a/content/learning-paths/automotive/innexis_ana_zena/ana_share_folder.png b/content/learning-paths/automotive/innexis_ana_zena/ana_share_folder.png new file mode 100755 index 0000000000..8e9fd9c24f Binary files /dev/null and b/content/learning-paths/automotive/innexis_ana_zena/ana_share_folder.png differ diff --git a/content/learning-paths/automotive/innexis_ana_zena/create_zena_project.png b/content/learning-paths/automotive/innexis_ana_zena/create_zena_project.png new file mode 100755 index 0000000000..f545658f5a Binary files /dev/null and b/content/learning-paths/automotive/innexis_ana_zena/create_zena_project.png differ diff --git a/content/learning-paths/automotive/innexis_ana_zena/primary_compute_console.png b/content/learning-paths/automotive/innexis_ana_zena/primary_compute_console.png new file mode 100755 index 0000000000..d2f0825e5c Binary files /dev/null and b/content/learning-paths/automotive/innexis_ana_zena/primary_compute_console.png differ diff --git a/content/learning-paths/automotive/innexis_ana_zena/project_conf.png b/content/learning-paths/automotive/innexis_ana_zena/project_conf.png new file mode 100755 index 0000000000..eec71c25c6 Binary files /dev/null and b/content/learning-paths/automotive/innexis_ana_zena/project_conf.png differ diff --git a/content/learning-paths/automotive/innexis_zena_llamacpp/1_check_guest.md b/content/learning-paths/automotive/innexis_zena_llamacpp/1_check_guest.md new file mode 100644 index 0000000000..3ff2c1cef2 --- /dev/null +++ b/content/learning-paths/automotive/innexis_zena_llamacpp/1_check_guest.md @@ -0,0 +1,81 @@ +--- +title: Check the Zena CSS Linux guest +description: Check the Zena CSS Linux guest's architecture, tools, and storage before building a CPU workload. +weight: 2 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Check the Zena CSS Linux guest + +Start from the Linux console or SSH shell prepared in the Zena CSS onboarding Learning Path. Check this guest before downloading source code or a model. The required tools and free space depend on the actual EWAOL image and chamber configuration. + +## Record the environment + +Run these commands in the **Primary Compute Linux guest**: + +```bash +cat /etc/os-release; uname -m; uname -r; cat /sys/devices/system/cpu/online; grep -E '^(MemTotal|MemAvailable):' /proc/meminfo; df -h / +``` + +The guest reports an Arm64 architecture, commonly `aarch64`, and shows its own CPU, memory, and root-filesystem values. The tested setup used ANA 2026.02 on a g4 chamber without changing the supplied Zena VP's CPU or memory settings. ANA Lab Manager showed **Simulation CPUs: 4**; the Linux output below shows **16 online CPUs** (`0-15`) and the EWAOL image's approximately 8 GB of usable guest memory. Use the Linux output when describing guest-visible CPUs; do not read the ANA simulation setting as a Linux core count. + +``` +ID=ewaol +NAME="Edge Workload Abstraction and Orchestration Layer" +VERSION="v2.1.0 (styhead)" +VERSION_ID=v2.1.0 +VERSION_CODENAME="styhead" +PRETTY_NAME="Edge Workload Abstraction and Orchestration Layer v2.1.0 (styhead)" +CPE_NAME="cpe:/o:openembedded:ewaol:v2.1.0" +aarch64 +6.6.54-yocto-standard +0-15 +MemTotal: 8120232 kB +MemAvailable: 7856280 kB +Filesystem Size Used Avail Use% Mounted on +/dev/root 28G 1.3G 25G 5% / +``` + +## Check build and transfer tools + +See which tools are available in this guest: + +```bash +for tool in git cmake make gcc g++ wget sha256sum tar; do + command -v "$tool" || printf 'Missing: %s\n' "$tool" +done +``` + +The later pages need a C/C++ compiler, Make, CMake, Git, a model transfer method, and a SHA-256 utility. If a tool is missing, record it before proceeding. Do not install a package from an unrelated Linux distribution into the EWAOL image. + +``` +/usr/bin/git +Missing: cmake +/usr/bin/make +/usr/bin/gcc +/usr/bin/g++ +/usr/bin/wget +/usr/bin/sha256sum +/usr/bin/tar +``` + +## Set the work directory + +The Zena guest shown above has 25 GB available on its root filesystem. Use one directory under `/root` for the source, build, model, and results: + +```bash +LP2_WORK_DIR=/root/zena-llamacpp +mkdir -p "$LP2_WORK_DIR" +``` + +The tested run used this directory for the source, build, model, and result archive. The root filesystem had 25 GB available before these files were created. + +If your guest has less available space, choose a directory on a filesystem with enough capacity before running the next page. Keep this shell open so `LP2_WORK_DIR` remains set. + +ANA's **Auto Shutdown** duration is configurable. Check the selected duration in Lab Manager before starting the build, and plan to save your work before the VP stops. + +## What you have learned + +You have identified the Linux guest and set a work directory for the workload. The next page builds llama.cpp using the tools available in this image. diff --git a/content/learning-paths/automotive/innexis_zena_llamacpp/2_build_llama_cpp.md b/content/learning-paths/automotive/innexis_zena_llamacpp/2_build_llama_cpp.md new file mode 100644 index 0000000000..1ddedd1ae8 --- /dev/null +++ b/content/learning-paths/automotive/innexis_zena_llamacpp/2_build_llama_cpp.md @@ -0,0 +1,111 @@ +--- +title: Build llama.cpp in the Zena CSS guest +description: Build a pinned llama.cpp revision for CPU inference in the Zena CSS Linux guest. +weight: 3 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Build llama.cpp in the Zena CSS guest + +Build the tested llama.cpp revision in the Zena CSS guest. Keep the source and build output in the work directory from the previous page. + +## Make CMake available + +Check whether CMake is already installed. In the tested Zena guest, the command returned: + +```bash +cmake --version +``` + +```output +sh: cmake: command not found +``` + +Use the [Kitware CMake 3.28.6 release](https://github.com/Kitware/CMake/releases/tag/v3.28.6) for Linux AArch64 in the Zena work directory. This does not change the guest's system packages: + +```bash +cd "$LP2_WORK_DIR" && +wget -O cmake-3.28.6-linux-aarch64.tar.gz.part \ + https://github.com/Kitware/CMake/releases/download/v3.28.6/cmake-3.28.6-linux-aarch64.tar.gz && + mv cmake-3.28.6-linux-aarch64.tar.gz.part cmake-3.28.6-linux-aarch64.tar.gz && +sha256sum cmake-3.28.6-linux-aarch64.tar.gz && +tar -xzf cmake-3.28.6-linux-aarch64.tar.gz && +export PATH="$LP2_WORK_DIR/cmake-3.28.6-linux-aarch64/bin:$PATH" && +cmake --version +``` + +The Zena download completed with a 53,907,530-byte archive. The hash and version commands returned: + +```output +7909cc2128ce9442c63ce674a0bfb0e4f4ce04cef667d887e15ad5670d594ba7 cmake-3.28.6-linux-aarch64.tar.gz +cmake version 3.28.6 +``` + +The SHA-256 value identifies the downloaded file used in this run; it has not been checked against a publisher-supplied checksum. If the download or extraction fails on your guest, keep the error output and stop before the llama.cpp build. Keep this shell open so the CMake path remains available. + +## Get the source at a fixed revision + +Clone llama.cpp and check out the full commit used in the tested Zena run: + +```bash +LP2_LLAMA_COMMIT=b86d2f07542b29ab099aed34fd6b6d1b2fd4b81c +git clone https://github.com/ggml-org/llama.cpp.git "$LP2_WORK_DIR/llama.cpp" && +cd "$LP2_WORK_DIR/llama.cpp" && +git checkout --detach "$LP2_LLAMA_COMMIT" && +git rev-parse HEAD +``` + +The final command returned the requested full commit: + +```output +b86d2f07542b29ab099aed34fd6b6d1b2fd4b81c +``` + +If your checkout reports another commit, stop before configuring the build. + +## Configure a CPU build + +In the same **Zena guest shell**, configure the pinned source for a CPU build: + +```bash +cd "$LP2_WORK_DIR/llama.cpp" +cmake -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DGGML_NATIVE=ON \ + -DLLAMA_CURL=OFF \ + -DBUILD_SHARED_LIBS=OFF +``` + +This CMake invocation was used in the tested Zena run. Its output reported GNU 14.2.0 for C and C++, included the CPU backend, and ended with: + +```output +-- Configuring done (3.3s) +-- Generating done (0.2s) +-- Build files have been written to: /root/zena-llamacpp/llama.cpp/build +``` + +CMake also reported that OpenMP was not found. That warning did not prevent configuration; it does not establish how many threads a later inference run will use. The supplied excerpt does not include a numeric exit status. Stop if configuration reports an error. + +## Build and identify llama.cpp + +After configuration succeeds, compile the CPU binary: + +```bash +cmake --build build --config Release -j4 +./build/bin/llama-cli --version +``` + +The tested Zena build output reached `Built target llama-cli`, and the binary returned: + +```output +version: 0.6.0-dev (build 11482, commit b86d2f075) +built with GNU 14.2.0 for Linux aarch64 +``` + +The full build also downloaded UI assets from Hugging Face during compilation, so that build required network access. `-j4` limits build jobs; it does not set the virtual platform's CPU count. The supplied excerpts do not include a complete build log or numeric exit status. If the build fails, save its error output and check the work filesystem before retrying. + +## What you have learned + +You have a source revision and a binary identity to associate with the inference result. Keep `LP2_WORK_DIR` available in this shell for the next page. diff --git a/content/learning-paths/automotive/innexis_zena_llamacpp/3_prepare_model.md b/content/learning-paths/automotive/innexis_zena_llamacpp/3_prepare_model.md new file mode 100644 index 0000000000..e6993c05f2 --- /dev/null +++ b/content/learning-paths/automotive/innexis_zena_llamacpp/3_prepare_model.md @@ -0,0 +1,81 @@ +--- +title: Prepare and verify a text model +description: Download a pinned GGUF text model and verify its size and SHA-256 checksum in the Zena CSS guest. +weight: 4 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Prepare and verify a text model + +Use one small GGUF text model so you can focus on application bring-up. The tested Zena run used the [Qwen2.5 0.5B Instruct Q4_0 GGUF file](https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF/blob/main/qwen2.5-0.5b-instruct-q4_0.gguf). The publisher lists an Apache-2.0 license and SHA-256 value for this file. The downloaded file matched that hash before inference. + +Use the file's [full upload commit](https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF/commit/6dd44a1fb35d11b5d1b28902876ce3cc9e882d0e) instead of the moving `main` branch. The file in the tested Zena run was 428,730,208 bytes. Verify its hash in your guest before loading it. + +## Choose the model location + +Use the work filesystem checked on the first page. Make sure it still has room for a temporary download and the complete model: + +```bash +LP2_MODEL_DIR="$LP2_WORK_DIR/models" +mkdir -p "$LP2_MODEL_DIR" +df -h "$LP2_MODEL_DIR" +LP2_MODEL_FILE="$LP2_MODEL_DIR/qwen2.5-0.5b-instruct-q4_0.gguf" +``` + +In the tested Zena guest, the model directory was created and its filesystem reported: + +```output +Filesystem Size Used Avail Use% Mounted on +/dev/root 28G 2.9G 23G 12% / +``` + +These figures describe the guest before the model download. + +## Obtain the fixed model file + +If this guest can reach Hugging Face, download the file from the fixed repository revision: + +```bash +LP2_MODEL_REV=6dd44a1fb35d11b5d1b28902876ce3cc9e882d0e +wget -q -O "$LP2_MODEL_FILE.part" \ + "https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF/resolve/$LP2_MODEL_REV/qwen2.5-0.5b-instruct-q4_0.gguf?download=true" && + mv "$LP2_MODEL_FILE.part" "$LP2_MODEL_FILE" +printf 'Download exit status: %s\n' "$?" +``` + +The fixed-revision download in the tested Zena guest completed with: + +```output +Download exit status: 0 +``` + +Continue only if the download exit status is `0`. A successful download command does not establish that the file has the expected contents; check it below. + +If the guest cannot download directly, obtain the same approved file through a transfer route configured for this VP. The [Zena CSS onboarding Learning Path](../innexis_ana_zena/) describes ANA Host Access and an authorized SSH tunnel; each new VP needs its own access configuration. Set `LP2_MODEL_FILE` to the transferred file's actual path and verify its hash before continuing. + +## Check the file identity + +Compare the downloaded or transferred file with the SHA-256 published for this Q4_0 file: + +```bash +LP2_MODEL_SHA256=7671c0c304e6ce5a7fc577bcb12aba01e2c155cc2efd29b2213c95b18edaf6ed +test -s "$LP2_MODEL_FILE" && wc -c "$LP2_MODEL_FILE" +printf '%s %s\n' "$LP2_MODEL_SHA256" "$LP2_MODEL_FILE" | sha256sum -c - +printf 'Hash check exit status: %s\n' "$?" +``` + +In the tested Zena guest, the file size and hash check returned: + +```output +428730208 /root/zena-llamacpp/models/qwen2.5-0.5b-instruct-q4_0.gguf +/root/zena-llamacpp/models/qwen2.5-0.5b-instruct-q4_0.gguf: OK +Hash check exit status: 0 +``` + +If your check fails, stop before inference. Investigate the file source, revision, partial download, or expected hash rather than accepting a changed artifact silently. + +## What you have learned + +You have identified a model artifact separately from the runtime binary. Keep `LP2_MODEL_FILE` available in the same guest shell for inference. diff --git a/content/learning-paths/automotive/innexis_zena_llamacpp/4_run_inference.md b/content/learning-paths/automotive/innexis_zena_llamacpp/4_run_inference.md new file mode 100644 index 0000000000..cc1210d8cb --- /dev/null +++ b/content/learning-paths/automotive/innexis_zena_llamacpp/4_run_inference.md @@ -0,0 +1,82 @@ +--- +title: Run CPU inference and inspect the result +description: Run llama.cpp with a verified model and confirm that the Zena CSS guest generates text on its CPU. +weight: 5 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Run CPU inference and inspect the result + +Run a short text prompt with the binary and model you prepared. Use one fixed command so its output can be traced back to the source revision, model file, and Zena guest. + +## Check the command options + +The tested Zena build exposes `--single-turn` and `--simple-io`. Confirm that your build exposes both options: + +```bash +LP2_LLAMA_CLI="$LP2_WORK_DIR/llama.cpp/build/bin/llama-cli" +test -x "$LP2_LLAMA_CLI" +"$LP2_LLAMA_CLI" --help > "$LP2_WORK_DIR/llama-cli-help.txt" 2>&1 +grep -F -- '--single-turn' "$LP2_WORK_DIR/llama-cli-help.txt" +grep -F -- '--simple-io' "$LP2_WORK_DIR/llama-cli-help.txt" +``` + +In the tested Zena guest, both searches returned matching help text: + +```output +-st, --single-turn run conversation for a single turn only, then exit when done +--simple-io use basic IO for better compatibility in subprocesses and limited +``` + +If either option is missing from your build, revise the command below before running it. + +## Run a text-generation smoke test + +Create a results directory and save the prompt beside the application output: + +```bash +LP2_RESULTS_DIR="$LP2_WORK_DIR/results" +mkdir -p "$LP2_RESULTS_DIR" +printf '%s\n' 'Write one short sentence about a virtual platform.' \ + > "$LP2_RESULTS_DIR/first-prompt.txt" +``` + +Run the following command on the **Zena Linux guest**. It requests zero GPU layers with `-ngl 0` and saves both the log and exit code: + +```bash +if "$LP2_LLAMA_CLI" \ + -m "$LP2_MODEL_FILE" \ + -t 4 -tb 4 -ngl 0 -c 512 -n 128 --temp 0 --seed 42 \ + --single-turn --simple-io \ + -p 'Write one short sentence about a virtual platform.' \ + > "$LP2_RESULTS_DIR/first-run.log" 2>&1; then + printf '0\n' > "$LP2_RESULTS_DIR/first-run.exit" +else + printf '%s\n' "$?" > "$LP2_RESULTS_DIR/first-run.exit" +fi +cat "$LP2_RESULTS_DIR/first-run.exit" +tail -n 60 "$LP2_RESULTS_DIR/first-run.log" +``` + +In the tested Zena guest, the saved exit code was `0`. The log identified build `b11482-b86d2f075`, loaded the verified Q4_0 file, and contained a non-empty generated response: + +```output +build : b11482-b86d2f075 +model : /root/zena-llamacpp/models/qwen2.5-0.5b-instruct-q4_0.gguf +ftype : Q4_0 + +> Write one short sentence about a virtual platform. +A virtual platform is a platform that allows users to create and share digital content, such as images, videos, and documents, in a virtual environment. + +Exiting... +``` + +The CPU-only build also reported that no usable GPU was found. This warning did not prevent inference. This request is a functional smoke test: inspect your own log to confirm that text appears between the prompt and `Exiting...`. The recorded answer used the general meaning of "virtual platform", so it is not a definition of an automotive Virtual SoC or a test of answer quality. + +If model loading fails, check the hash and available memory before changing model or runtime settings. Save the failing log and exit code; a changed model or command is a new test condition. + +## What you have learned + +You have a recorded CPU text-inference result on the selected virtual platform. The next page changes only the prompt and collects the evidence from both runs. diff --git a/content/learning-paths/automotive/innexis_zena_llamacpp/5_iterate_and_collect.md b/content/learning-paths/automotive/innexis_zena_llamacpp/5_iterate_and_collect.md new file mode 100644 index 0000000000..0a9a38c2bd --- /dev/null +++ b/content/learning-paths/automotive/innexis_zena_llamacpp/5_iterate_and_collect.md @@ -0,0 +1,164 @@ +--- +title: Change the prompt and collect the results +description: Change one inference prompt, record both results, and transfer the result archive from the Zena CSS guest. +weight: 6 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Change the prompt and collect the results + +Keep the same Zena virtual platform, llama.cpp binary, GGUF file, and runtime flags. Change only the prompt, then compare the two recorded requests. This is a controlled input iteration, not a source-code modification or a performance comparison. + +## Run the second request + +Save the changed prompt and run the same CPU command in the **guest shell**: + +```bash +printf '%s\n' 'Write one short sentence about an automotive dashboard.' \ + > "$LP2_RESULTS_DIR/second-prompt.txt" +if "$LP2_LLAMA_CLI" \ + -m "$LP2_MODEL_FILE" \ + -t 4 -tb 4 -ngl 0 -c 512 -n 128 --temp 0 --seed 42 \ + --single-turn --simple-io \ + -p 'Write one short sentence about an automotive dashboard.' \ + > "$LP2_RESULTS_DIR/second-run.log" 2>&1; then + printf '0\n' > "$LP2_RESULTS_DIR/second-run.exit" +else + printf '%s\n' "$?" > "$LP2_RESULTS_DIR/second-run.exit" +fi +cat "$LP2_RESULTS_DIR/second-run.exit" +sed -n '1,100p' "$LP2_RESULTS_DIR/second-run.log" +``` + +The second tested Zena request used the same command options and model path with only the prompt changed. Its saved exit code was `0`, and the log included: + +```output +build : b11482-b86d2f075 +model : /root/zena-llamacpp/models/qwen2.5-0.5b-instruct-q4_0.gguf +ftype : Q4_0 + +> Write one short sentence about an automotive dashboard. +An automotive dashboard is a crucial component of a vehicle's interface, displaying crucial information such as speed, distance, fuel consumption, and other critical data to the driver. + +Exiting... +``` + +The two requests demonstrate that the guest application accepted different prompts and generated text. Their output does not establish answer quality or provide a controlled performance comparison. Your generated wording may differ. + +Both prompt files, both logs, and both exit-code files were present in the tested results directory. The saved prompts contained: + +```output +Write one short sentence about a virtual platform. +Write one short sentence about an automotive dashboard. +``` + +## Record the workload identities + +Save the environment and artifact identities beside the two outputs: + +```bash +cat /etc/os-release > "$LP2_RESULTS_DIR/os-release.txt" +uname -a > "$LP2_RESULTS_DIR/uname.txt" +git -C "$LP2_WORK_DIR/llama.cpp" rev-parse HEAD \ + > "$LP2_RESULTS_DIR/llama-source-commit.txt" +"$LP2_LLAMA_CLI" --version \ + > "$LP2_RESULTS_DIR/llama-version.txt" 2>&1 +sha256sum "$LP2_MODEL_FILE" \ + > "$LP2_RESULTS_DIR/model.sha256" +ls -lh "$LP2_RESULTS_DIR" +``` + +In the tested Zena guest, these files recorded EWAOL v2.1.0 (`styhead`) on Linux 6.6.54 `aarch64`, llama.cpp commit `b86d2f07542b29ab099aed34fd6b6d1b2fd4b81c`, and `llama-cli` 0.6.0-dev build 11482 with GNU 14.2.0. The model hash remained `7671c0c304e6ce5a7fc577bcb12aba01e2c155cc2efd29b2213c95b18edaf6ed`. + +Record the build and run options used for these results: + +```bash +printf '%s\n' \ + 'CMAKE_BUILD_TYPE=Release' \ + 'GGML_NATIVE=ON' \ + 'LLAMA_CURL=OFF' \ + 'BUILD_SHARED_LIBS=OFF' \ + 'Build jobs: 4' \ + 'Inference: -t 4 -tb 4 -ngl 0 -c 512 -n 128 --temp 0 --seed 42 --single-turn --simple-io' \ + > "$LP2_RESULTS_DIR/build-and-run-options.txt" +``` + +The tested Zena guest saved `build-and-run-options.txt` with these values: + +```output +CMAKE_BUILD_TYPE=Release +GGML_NATIVE=ON +LLAMA_CURL=OFF +BUILD_SHARED_LIBS=OFF +Build jobs: 4 +Inference: -t 4 -tb 4 -ngl 0 -c 512 -n 128 --temp 0 --seed 42 --single-turn --simple-io +``` + +Check that each status is `0` and inspect each log for generated text. Compare `first-prompt.txt` with `second-prompt.txt` to confirm the intended single input change. Retain the full raw logs even if the published page shows only short, redacted excerpts. + +## Export and stop + +Check the transfer route on **this virtual platform**. A file or 9p mount created on another VP will not appear automatically here. If Host Access is configured and mounted for this VP, use its current shared folder. Otherwise, use an SSH tunnel authorized for this VP, or configure Host Access while the VP is stopped and restart it. The [Zena CSS onboarding Learning Path](../innexis_ana_zena/) explains both routes. + +In the tested Zena VM, `/mnt/ana-share` was not mounted, the guest shell was an SSH session, and `tar` was available. Package only the small result files before transferring them; the model file is not part of this archive: + +```bash +LP2_ARCHIVE="$LP2_WORK_DIR/lp2-results.tar" +tar -cf "$LP2_ARCHIVE" -C "$LP2_RESULTS_DIR" \ + first-prompt.txt first-run.exit first-run.log \ + second-prompt.txt second-run.exit second-run.log \ + os-release.txt uname.txt llama-source-commit.txt llama-version.txt \ + model.sha256 build-and-run-options.txt && + tar -tf "$LP2_ARCHIVE" && + sha256sum "$LP2_ARCHIVE" && + wc -c "$LP2_ARCHIVE" +``` + +The tested archive contained the 12 named result files and no model binary. The archive check returned: + +```output +19bc18de2f02ec9fb151e7ca32272c234bce21247ac59eb0faf8667594ea0900 /root/zena-llamacpp/lp2-results.tar +20480 /root/zena-llamacpp/lp2-results.tar +Archive step exit status: 0 +``` + +If this VP restarts before you download the archive, check that the file still exists and has the recorded SHA-256. Obtain the current SSH port mapping for this VP and authorize a new tunnel if the previous authorization has expired. + +After the tested VP restarted, checking the archived file against its previously recorded digest returned `OK` with exit status `0`. This confirms that the archive survived this restart; check your own archive if your VP restarts. + +Keep this VP's authorized ANA tunnel open. On your **local macOS terminal**, find the current external port mapped to guest port `22` in ANA Port Mapping. Set the port in the same terminal you will use for the download: + +```bash +printf 'Current ANA external SSH port: ' +read -r ANA_SSH_PORT +test -n "$ANA_SSH_PORT" +``` + +Continue only if the `test` command succeeds. The variable belongs to your local terminal, not the guest shell. Download the archive to a local file, then inspect it: + +```bash +LP2_LOCAL_ARCHIVE="$HOME/lp2-results.tar" +ssh -T -4 -p "$ANA_SSH_PORT" root@127.0.0.1 \ + 'cat /root/zena-llamacpp/lp2-results.tar' > "$LP2_LOCAL_ARCHIVE.part" && + shasum -a 256 "$LP2_LOCAL_ARCHIVE.part" && + wc -c "$LP2_LOCAL_ARCHIVE.part" && + tar -tf "$LP2_LOCAL_ARCHIVE.part" +``` + +Compare the local SHA-256 with the value recorded in the guest. If they match and all 12 files are listed, keep the verified copy: + +```bash +mv "$LP2_LOCAL_ARCHIVE.part" "$LP2_LOCAL_ARCHIVE" +``` + +The downloaded copy in this test matched the guest SHA-256 `19bc18de2f02ec9fb151e7ca32272c234bce21247ac59eb0faf8667594ea0900`, was 20,480 bytes, and contained the same 12 files. Your archive digest may differ because your logs and file metadata differ. On Linux, use `sha256sum` instead of macOS `shasum -a 256`. + +Keep model binaries out of the result bundle unless their terms permit redistribution. Check the raw logs for private endpoints, usernames, or keys before sharing the archive outside your team. + +After confirming the files are saved, return to **Lab Manager** and check the VP state. If the VP is still running and you have finished, choose **Stop**. **Auto Shutdown** is configurable, so check its duration before starting a long task. In the tested run, it was set to 60 minutes and stopped the VP; Lab Manager showed **Status: stopped** and **Health Check: Valid**. Do not delete a shared project or assume that a guest directory on tmpfs will survive a later start. + +## What you have learned + +You built and ran a CPU text workload and preserved evidence for two controlled requests. The result supports functional bring-up on the tested ANA Zena CSS configuration. It does not predict physical-silicon throughput, prove model-answer quality, or show GPU or NPU execution. diff --git a/content/learning-paths/automotive/innexis_zena_llamacpp/_index.md b/content/learning-paths/automotive/innexis_zena_llamacpp/_index.md new file mode 100644 index 0000000000..f5960fb862 --- /dev/null +++ b/content/learning-paths/automotive/innexis_zena_llamacpp/_index.md @@ -0,0 +1,59 @@ +--- +title: Run Local LLM Inference with llama.cpp on Arm Zena CSS + +draft: true +cascade: + draft: true + +description: Build llama.cpp in a Zena CSS Linux guest, verify a text model, and run two recorded CPU inference requests. + +minutes_to_complete: 60 + +who_is_this_for: AI and application developers who can access a Zena CSS virtual platform in Siemens Innexis ANA. + +learning_objectives: + - Check that the Zena CSS Linux guest has the tools and storage needed for a CPU AI workload. + - Build and identify a fixed llama.cpp revision in the guest. + - Verify the identity of one GGUF text model before using it. + - Run two controlled inference requests and preserve their results. + +prerequisites: + - Access to a running Arm Zena CSS virtual platform and its EWAOL Linux guest in Siemens Innexis ANA. + - A working guest console or authorized SSH connection, plus a way to transfer files to and from the guest. + - Outbound HTTPS access from the guest to GitHub and Hugging Face for the tested download and build workflow. + - Permission to obtain the selected llama.cpp source and text model. + - Basic Linux command-line knowledge. + +author: Odin Shen + +generate_summary_faq: false +rerun_summary: false +rerun_faqs: false + +### Tags +skilllevels: Introductory +subjects: Containers and Virtualization +armips: + - Cortex-A +tools_software_languages: + - Arm Zena CSS + - Siemens Innexis ANA + - llama.cpp +operatingsystems: + - Linux + +further_reading: + - resource: + title: Arm Zena Compute Subsystem + link: https://www.arm.com/products/autonomous-machines/zena-compute-subsystems + type: documentation + - resource: + title: llama.cpp + link: https://github.com/ggml-org/llama.cpp + type: documentation + +### FIXED, DO NOT MODIFY +weight: 1 +layout: learningpathall +learning_path_main_page: "yes" +--- diff --git a/content/learning-paths/automotive/innexis_zena_llamacpp/_next-steps.md b/content/learning-paths/automotive/innexis_zena_llamacpp/_next-steps.md new file mode 100644 index 0000000000..727b395ddd --- /dev/null +++ b/content/learning-paths/automotive/innexis_zena_llamacpp/_next-steps.md @@ -0,0 +1,8 @@ +--- +# ================================================================================ +# FIXED, DO NOT MODIFY THIS FILE +# ================================================================================ +weight: 21 # The weight controls the order of the pages. _index.md always has weight 1. +title: "Next Steps" # Always the same, html page title. +layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. +--- diff --git a/content/learning-paths/automotive/openadkit1_container/_index.md b/content/learning-paths/automotive/openadkit1_container/_index.md index 65e4e6ccc5..7db791cf1b 100644 --- a/content/learning-paths/automotive/openadkit1_container/_index.md +++ b/content/learning-paths/automotive/openadkit1_container/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy Open AD Kit containerized autonomous driving simulation on Arm Neoverse +title: Deploy Open AD Kit driving simulation in a container on Arm description: Learn how to deploy and run containerized autonomous driving simulations using Autoware Open AD Kit on Arm Neoverse with Docker, demonstrating SOAFEE-based Shift-Left development workflows. minutes_to_complete: 60 diff --git a/content/learning-paths/automotive/openadkit2_safetyisolation/_index.md b/content/learning-paths/automotive/openadkit2_safetyisolation/_index.md index 3bfb072554..1fb60afd39 100644 --- a/content/learning-paths/automotive/openadkit2_safetyisolation/_index.md +++ b/content/learning-paths/automotive/openadkit2_safetyisolation/_index.md @@ -1,5 +1,5 @@ --- -title: Prototype safety-critical isolation for autonomous driving systems on Neoverse +title: Prototype autonomous driving safety isolation on Arm Neoverse description: Learn how to implement functional safety isolation for autonomous driving systems on Arm Neoverse using DDS-based communication, containerized deployment, and ISO 26262 compliance principles. minutes_to_complete: 60 diff --git a/content/learning-paths/automotive/zenacssdebug/_index.md b/content/learning-paths/automotive/zenacssdebug/_index.md index 745a3f8ca0..5f855700aa 100644 --- a/content/learning-paths/automotive/zenacssdebug/_index.md +++ b/content/learning-paths/automotive/zenacssdebug/_index.md @@ -1,5 +1,5 @@ --- -title: Debug Arm Zena CSS Reference Software Stack with Arm Development Studio +title: Debug Arm Zena CSS with Arm Development Studio description: Learn how to debug the Arm Zena CSS Reference Software Stack using Arm Development Studio on a Fixed Virtual Platform, covering RSE, Safety Island, and Linux kernel debugging workflows. diff --git a/content/learning-paths/cross-platform/adler32/_index.md b/content/learning-paths/cross-platform/adler32/_index.md index 10dfef756e..a38b061d6d 100644 --- a/content/learning-paths/cross-platform/adler32/_index.md +++ b/content/learning-paths/cross-platform/adler32/_index.md @@ -1,5 +1,5 @@ --- -title: Write Neon intrinsics using GitHub Copilot to improve Adler32 performance +title: Optimize Adler32 with Neon intrinsics using GitHub Copilot description: Learn how to use GitHub Copilot to write Neon intrinsics that accelerate the Adler32 checksum algorithm on Arm platforms, achieving significant performance improvements over standard C implementations. diff --git a/content/learning-paths/cross-platform/ai-agent-cpu-orchestration/_index.md b/content/learning-paths/cross-platform/ai-agent-cpu-orchestration/_index.md index 42409ca5af..4d60c79798 100644 --- a/content/learning-paths/cross-platform/ai-agent-cpu-orchestration/_index.md +++ b/content/learning-paths/cross-platform/ai-agent-cpu-orchestration/_index.md @@ -1,5 +1,5 @@ --- -title: Run a local AI agent with Ollama to visualize CPU orchestration on Arm +title: Visualize Arm CPU orchestration with Ollama and a local AI agent description: Run a local AI concierge agent with Ollama on Arm and visualize how the CPU orchestrates web search, scraping, and text processing while Gemma 3 handles model reasoning. diff --git a/content/learning-paths/cross-platform/aws-greengrass-pacbti-test/_index.md b/content/learning-paths/cross-platform/aws-greengrass-pacbti-test/_index.md index 4287d21b09..825fae9f7b 100644 --- a/content/learning-paths/cross-platform/aws-greengrass-pacbti-test/_index.md +++ b/content/learning-paths/cross-platform/aws-greengrass-pacbti-test/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy an AWS IoT Greengrass custom component to Arm devices and verify PAC/BTI support +title: Verify PAC/BTI support in an AWS IoT Greengrass component on Arm description: Learn how to register Arm devices as AWS IoT Greengrass core devices, build and deploy a custom component, and use MQTT to verify PAC/BTI support across Armv8 and Armv9 platforms. diff --git a/content/learning-paths/cross-platform/build-a-reachy-robot-app-on-pi/_index.md b/content/learning-paths/cross-platform/build-a-reachy-robot-app-on-pi/_index.md index e47a597a20..2e5290aa06 100644 --- a/content/learning-paths/cross-platform/build-a-reachy-robot-app-on-pi/_index.md +++ b/content/learning-paths/cross-platform/build-a-reachy-robot-app-on-pi/_index.md @@ -1,5 +1,5 @@ --- -title: Build an edge AI Reachy Mini app with Raspberry Pi, MediaPipe, and MuJoCo +title: Build a Reachy Mini MuJoCo simulation on Raspberry Pi description: Run MediaPipe gesture inference on a Raspberry Pi 5, connect to a Reachy Mini MuJoCo simulation on a development machine, and use a browser dashboard to decide Reachy's fate with a thumbs-up or thumbs-down. diff --git a/content/learning-paths/cross-platform/compute-flow-getting-started/_index.md b/content/learning-paths/cross-platform/compute-flow-getting-started/_index.md new file mode 100644 index 0000000000..04d3a88573 --- /dev/null +++ b/content/learning-paths/cross-platform/compute-flow-getting-started/_index.md @@ -0,0 +1,124 @@ +--- +title: Build an audio-to-audio pipeline on Arm with Compute Flow +description: Build and run an audio-to-audio pipeline as an application on your Arm-based device, combining Compute Flow bricks for speech recognition, translation, and speech synthesis + +draft: true +cascade: + draft: true + +minutes_to_complete: 15 + +who_is_this_for: This is an introductory topic for developers who want to combine multiple AI models into a performant end-to-end AI application running on an Arm-based CPU. + +learning_objectives: + - Identify how Compute Flow bricks and manifests separate application code from model implementations. + - Deploy an AI audio-to-audio translation pipeline with Compute Flow. + - Select a speech recognition model and verify the pipeline using the same application and recording. + +prerequisites: + - An Arm-based system such as an Apple® silicon Mac, Arm-based Linux machine, or Android® phone. + - Basic Python or C++ knowledge and familiarity with terminal commands + - Internet access and at least 15 GB of available storage + +# START generated_summary_faq +generated_summary_faq: + template_version: summary-faq-v3 + generated_at: '2026-10-30T19:19:38Z' + generator: ai + ai_assisted: true + ai_review_required: true + model: gpt-5 + prompt_template: summary-faq-v3 + source_hash: 4d713ef29d940b9043e743e78543b697ed42036d7557c4840126ee434003ac9d + summary_generated_at: '2026-10-30T19:19:38Z' + summary_source_hash: 4d713ef29d940b9043e743e78543b697ed42036d7557c4840126ee434003ac9d + faq_generated_at: '2026-10-30T19:19:38Z' + faq_source_hash: 4d713ef29d940b9043e743e78543b697ed42036d7557c4840126ee434003ac9d + summary: >- + You'll set up Compute Flow and use it to build an audio-to-audio pipeline on an Arm-based device. First, you'll learn about Compute Flow and bricks. Then, you'll create an application that connects speech-to-text, translation, + and text-to-speech bricks. You'll start with the Parakeet model for speech-to-text. Then, you'll switch to Moonshine by installing its implementation and model, rebuilding, and selecting its + manifest. Finally, you'll compare speech-to-text performance between the two models. + # faqs: + # - question: How do I know my Mac is using the Arm architecture? + # answer: >- + # Run `uname -m` in your terminal and confirm that the output is `arm64`. + # - question: What should I do if Compute Flow reports missing dependencies? + # answer: >- + # Run `compute-flow check`, follow the suggested action for each missing dependency, and rerun + # `compute-flow check` to confirm the result. The check does not install dependencies or test + # whether a compiler can compile and link a program. + # - question: Which option should I use to switch from Parakeet to Moonshine for speech recognition? + # answer: >- + # Install the Moonshine speech-to-text implementation and model, rebuild with + # `compute-flow build native`, then pass `moonshine_manifest_base_en.json` to your application + # or CLI. You can keep your Python script unchanged because both variants implement `stt.v1`. + # - question: What result should I expect when the pipeline runs successfully? + # answer: >- + # You should see the transcription and translation in your terminal. The application saves + # synthesized audio to `translated_french.wav` by default, or to the path you provide with + # `--output`. + # - question: How do I compare the two speech-to-text variants correctly? + # answer: >- + # Run the built speech-to-text CLI for both models on the same 9.963-second mono 16 kHz sample, + # changing only the manifest, and compare their total `run()` times. These sample timings do not + # assess transcription accuracy. +# END generated_summary_faq + +author: Kieran Hejmadi + +# New Learning Paths are opted in for the next manual generated summary/FAQ run. +# The generator resets this to false after a successful write. +generate_summary_faq: false + +# Optional one-shot controls: set either field to true to regenerate just that +# generated section the next time the summary/FAQ tool runs. The tool resets +# them to false after a successful write. +rerun_summary: false +rerun_faqs: false + +### Tags +skilllevels: Introductory +subjects: ML +armips: [] +tools_software_languages: + - Compute Flow + - Python + - uv +operatingsystems: + - macOS + - Linux + - Android + +### Cross-platform metadata only +shared_path: true +shared_between: + - laptops-and-desktops + - mobile-graphics-and-gaming + - servers-and-cloud-computing + +further_reading: + - resource: + title: Compute Flow source repository (public URL placeholder) + link: https://github.com/ARM-software/compute-flow + type: documentation + - resource: + title: Install uv + link: https://docs.astral.sh/uv/getting-started/installation/ + type: documentation + +# Publication checks: confirm the public repository and documentation URLs, +# and verify native dependency bundles are accessible from the public checkout. +# Source reference: Compute Flow 885a3466e33eb5e843cd55d1d0180e0bc292520a. +# Validated on macOS 26.6.2 arm64 with Python 3.12.6 and AppleClang 21.0.0. +# Both pipelines completed with the same script and input recording. WAV checks +# passed at 24 kHz: Parakeet output 9.36 s, Moonshine output 7.76 s. +# afplay exited successfully for both WAV files. +# Local validation used the certifi CA bundle and disabled discovery of an +# incompatible pre-installed llama.cpp package. + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +--- diff --git a/content/learning-paths/cross-platform/compute-flow-getting-started/_next-steps.md b/content/learning-paths/cross-platform/compute-flow-getting-started/_next-steps.md new file mode 100644 index 0000000000..727b395ddd --- /dev/null +++ b/content/learning-paths/cross-platform/compute-flow-getting-started/_next-steps.md @@ -0,0 +1,8 @@ +--- +# ================================================================================ +# FIXED, DO NOT MODIFY THIS FILE +# ================================================================================ +weight: 21 # The weight controls the order of the pages. _index.md always has weight 1. +title: "Next Steps" # Always the same, html page title. +layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. +--- diff --git a/content/learning-paths/cross-platform/compute-flow-getting-started/how-to-1.md b/content/learning-paths/cross-platform/compute-flow-getting-started/how-to-1.md new file mode 100644 index 0000000000..fe8a201adb --- /dev/null +++ b/content/learning-paths/cross-platform/compute-flow-getting-started/how-to-1.md @@ -0,0 +1,68 @@ +--- +title: Understand the Compute Flow SDK +description: Learn about Compute Flow bricks, manifests, and shared interfaces to combine multiple AI models and run them locally on-device. +weight: 2 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Compute Flow bricks + +Compute Flow is an SDK for building AI applications from reusable AI building blocks called bricks. Each brick exposes an AI capability through a shared interface. You connect the interfaces in Python or C++, while the brick handles model-specific inference. + +A brick type defines an input and output contract. A brick variant implements that contract using a particular model family. For example, Parakeet TDT and Moonshine are different speech-to-text variants. Both accept audio and its sample rate, and return text. + +You'll use three different bricks to build an audio translation application in Python or C++ on your Arm-based device. The first recognizes human speech, the second translates speech into text, and the third generates audio. Each brick uses different models to accomplish the goal of translating English into French. + +The application processes a short English recording into French speech through three stages: + +```text +English WAV (Input) + | + v +Stage 1. Speech-to-text: Parakeet TDT + | English text + v +Stage 2. Text translation: Hy-MT2 + | French text + v +Stage 3. Text-to-speech: Qwen3-TTS + | + v +French WAV (Output) +``` + +Your application coordinates the stages. It passes the speech-to-text transcription to the translation brick, then passes the translation to the speech synthesis brick. Compute Flow doesn't automatically assemble this pipeline for you. + +## Separate the application from the model + +You'll use the following contracts: + +| Stage | Brick type | Input | Output | +| --- | --- | --- | --- | +| Audio transcription | `speech-to-text (stt)` | `raw_wav` and `sample_rate` | `text` | +| Text translation | `lm_text` | `text_prompt` | `text` | +| Speech synthesis | `text-to-speech (tts)` | `text_prompt` | `left_channel`, `right_channel`, and `sample_rate` | + +### Understand the model manifest + +A model package contains the weights and supporting assets that a brick needs. A model manifest is a JSON configuration file in that package. It tells Compute Flow which brick implementation to use and how to load and run the models. + +The manifest describes model filenames, inference runtimes, and model-specific tensor formats. It also describes supporting assets such as tokenizers. The manifest provides configuration values and constants that the brick needs. The weights live in separate files that the manifest references. + +For example, the Parakeet package includes `parakeet_tdt_0_6b_v3_manifest.json`. The application reads this file with `cf.load_manifest()`. It selects the implementation with `cf.load_brick()`, and prepares the models with `brick.setup()`. This keeps model-specific file paths and runtime configuration out of your application code. + +## Compute Flow Brick Manager + +To discover and install the required Compute Flow bricks, you'll use Compute Flow Brick Manager alongside the selected AI models. + +The Brick Manager is responsible for installing the brick type and variant based on the selected AI model. It builds the developed application for the selected runtime platforms. + +To complete setup, you need network access to download dependencies and model packages. After setup, the application reads the installed assets and performs inference on your device. It writes a `.wav` file locally and doesn't call a hosted inference service. You don't need a microphone because the input recording is included in a model package. + +## What you've learned and what's next + +You've learned that Compute Flow provides reusable AI building blocks that you can combine to build locally deployable AI applications. You've also learned about Compute Flows Brick Manager. + +Next, you'll build and run the example audio translation application with Compute Flow. diff --git a/content/learning-paths/cross-platform/compute-flow-getting-started/how-to-2.md b/content/learning-paths/cross-platform/compute-flow-getting-started/how-to-2.md new file mode 100644 index 0000000000..31c07359c6 --- /dev/null +++ b/content/learning-paths/cross-platform/compute-flow-getting-started/how-to-2.md @@ -0,0 +1,668 @@ +--- +title: Run a local speech translation pipeline +description: Build Compute Flow on your device and connect three Compute Flow bricks to translate an English recording into French speech. +weight: 3 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Prepare your host device + +You'll install three model packages, build Compute Flow for your device, and create an application. + +First, check the CPU architecture of your device: + +```bash +uname -m +``` + +The expected output is `arm64` or `aarch64`. + +If `uv` isn't installed, use the [uv standalone installer](https://docs.astral.sh/uv/getting-started/installation/) with the following command: + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +Next, [install Xcode Command Line Tools](https://developer.apple.com/documentation/xcode/installing-the-command-line-tools/) if you're compiling natively for MacOS on Apple® silicon. The package includes Git, Clang, and the macOS SDK. If you already have Xcode or the Command Line Tools installed, use those tools. + +Check that the tools are available: + +```bash +uv --version +git --version +command -v cc +command -v c++ +``` + +If you're targeting an Android device with cross compilation, [install the Android Platform Tools](https://developer.android.com/tools/releases/platform-tools) with the `adb` utility. + +Finally, remove any previously installed `llama.cpp` versions. + +For example, if `llama.cpp` was previously installed using `brew` package manager, run the following command: + +```bash +brew uninstall llama.cpp +``` + +## Set up Compute Flow + +Clone the Compute Flow repository: + +```bash +git clone https://github.com/ARM-software/compute-flow.git +cd compute-flow +``` + +Create and activate the Python environment, then check the required system dependencies: + +```bash +uv sync && source .venv/bin/activate +compute-flow check +``` + +The environment check reports the available dependencies and native toolchain: + +```output +╭───────────────────────────────────────── OK Environment Ready ─────────────────────────────────────────╮ +│ All required system dependencies are available. │ +│ ─────────────────────────────────────────── Dependencies ─────────────────────────────────────────── │ +│ │ +│ Dependency Executable Status Path │ +│ ────────────────────────────────────────────────────────────────────────────────────────────────── │ +│ Git git found /Library/Developer/CommandLineTools/usr/bin/git │ +│ │ +│ ───────────────────────────────────────── Native Toolchain ───────────────────────────────────────── │ +│ │ +│ Compiler Toolchain Version Status Path │ +│ ────────────────────────────────────────────────────────────────────────────────────────────────── │ +│ C compiler Clang (cc) 21.0.0 found /Library/Developer/CommandLineTools/usr/bin/cc │ +│ C++ compiler Clang (c++) 21.0.0 found /Library/Developer/CommandLineTools/usr/bin/c++ │ +│ │ +│ ────────────────────────────────────────────── Tools ─────────────────────────────────────────────── │ +│ │ +│ Name Version Provided by Status Path │ +│ ────────────────────────────────────────────────────────────────────────────────────────────────── │ +│ CMake 4.0.2 Brick Manager will be - │ +│ downloaded when │ +│ needed │ +│ Git LFS 3.6.1 Brick Manager will be - │ +│ downloaded when │ +│ needed │ +│ │ +│ ──────────────────────────────────────────── Toolchains ──────────────────────────────────────────── │ +│ │ +│ Name Version Provided by Status Path │ +│ ────────────────────────────────────────────────────────────────────────────────────────────────── │ +│ Android™ NDK r27c Brick Manager will be - │ +│ downloaded when │ +│ needed │ +│ Linux® Arm® 14.2.rel1 Brick Manager will be - │ +│ 64-bit downloaded when │ +│ toolchain needed │ +│ │ +╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + +``` + +Compute Flow requires Python 3.11 or later. `uv sync` installs the project Python dependencies into the virtual environment. With its default settings, [uv also downloads a compatible Python interpreter](https://docs.astral.sh/uv/concepts/python-versions/) if it can't find one locally. + +`compute-flow check` reports missing system dependencies and suggests installation commands. It doesn't install dependencies or test whether the compiler can compile and link a program. If it reports a missing native compiler or Git installation, follow the suggested action and run the check again. + +Brick Manager automatically downloads managed tools, such as CMake and Git Large File Storage (LFS), when a command needs them. The check reports these as `will be downloaded when needed`, so you don't need to install them manually. For example, `compute-flow build native` downloads its managed CMake version if it isn't already available. + +## Install the three model packages + +Update the Brick Manager registry cache and install the models that will be used by your pipeline: + +{{% notice Note %}} +For faster Hugging Face downloads and higher rate limits, set the optional `HF_TOKEN` environment variable before installing the models. You can [create a Hugging Face access token](https://huggingface.co/settings/tokens) in account settings. +{{% /notice %}} + +```bash +compute-flow update +compute-flow model install stt:parakeet-tdt parakeet-tdt-0-6b-v3-int8-dynamic +compute-flow model install lm_text:lm-text-generic hy-mt2-1-8b-2bit-translation +compute-flow model install tts:qwen3-tts qwen3-tts-0-6b-custom-voice +``` + +Each `model install` command installs the brick source, if needed, and its model package. + +The identifiers select the following implementations: + +| Brick | Model package | Pipeline role | +| --- | --- | --- | +| `stt:parakeet-tdt` | Parakeet TDT 0.6B v3, dynamic INT8 | Transcribe the English recording | +| `lm_text:lm-text-generic` | Hy-MT2 1.8B, 2-bit | Translate English text into French | +| `tts:qwen3-tts` | Qwen3-TTS 0.6B Custom Voice | Synthesize French speech | + +Models are downloaded to the `~/.compute-flow` directory by default. The `compute-flow model info` command displays details about a specified model: + +```bash +compute-flow model info stt:parakeet-tdt parakeet-tdt-0-6b-v3-int8-dynamic +compute-flow model info lm_text:lm-text-generic hy-mt2-1-8b-2bit-translation +compute-flow model info tts:qwen3-tts qwen3-tts-0-6b-custom-voice +``` + +## Build for your device + +Build the installed bricks: + +{{< tabpane-normal >}} +{{< tab header="Native Arm64" >}} +```bash +compute-flow build native +``` +{{< /tab >}} +{{< tab header="Cross-compile for Android Arm64" >}} + +```bash +compute-flow build android_arm64 +``` + +Note that as of November 2026, the Android NDK doesn't support cross-compilation from Linux Arm64 hosts. Instead, use a macOS or Linux x86-64 host system. + +{{< /tab >}} +{{< /tabpane-normal >}} + +If successful, the output is similar to: + +```output +╭────────────────────────────── OK Build Complete ──────────────────────────────╮ +│ Build directory /Users/user/compute_flow/compute_flow/build_native │ +│ Application SDK /Users/user/compute_flow/compute_flow/build_native/sdk │ +╰───────────────────────────────────────────────────────────────────────────────╯ +``` + +The build command downloads inference runtimes that are optimized for your deployment platform. It packages them with your installed brick implementations into an application SDK available at `build_native/sdk`. + +## Create the application + +Create a directory for your application: + +```bash +mkdir -p audio_translation_example +``` + +Follow the instructions for your target device. + +{{< tabpane-normal >}} +{{< tab header="Native Arm64" >}} + +Save the following code as `audio_translation_example/audio_translation.py`: + +```python +"""Translate speech between languages with three Compute Flow bricks.""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +import compute_flow_py as cf +import numpy as np +import soundfile as sf + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--stt-manifest", required=True) + parser.add_argument("--translation-manifest", required=True) + parser.add_argument("--tts-manifest", required=True) + parser.add_argument("--input", required=True, dest="input_audio") + parser.add_argument("--source-language", required=True) + parser.add_argument("--destination-language", required=True) + parser.add_argument( + "--output", default="translated_french.wav", dest="output_audio" + ) + args = parser.parse_args(argv) + + # Load the input as a batch containing one mono float32 waveform. + input_audio, input_sample_rate = sf.read( + args.input_audio, dtype="float32", always_2d=True + ) + mono_audio = np.mean(input_audio, axis=1, dtype=np.float32) + raw_wav = np.ascontiguousarray(mono_audio.reshape(1, -1), dtype=np.float32) + sample_rate = np.asarray([input_sample_rate], dtype=np.int64) + + options = cf.BrickOptions() + options.runtime.enable_warmup = True + + # Load the manifests. + stt_manifest = cf.load_manifest(args.stt_manifest) + translation_manifest = cf.load_manifest(args.translation_manifest) + tts_manifest = cf.load_manifest(args.tts_manifest) + + # Configure the requested translation and text-to-speech languages. + translation_manifest.set_config("src_lang", args.source_language) + translation_manifest.set_config("dst_lang", args.destination_language) + tts_manifest.set_config("language", args.destination_language.lower()) + + # Load the bricks. + stt_brick = cf.load_brick(stt_manifest) + translation_brick = cf.load_brick(translation_manifest) + tts_brick = cf.load_brick(tts_manifest) + + # Set up the bricks. + stt_brick.setup(stt_manifest, options) + translation_brick.setup(translation_manifest, options) + tts_brick.setup(tts_manifest, options) + + ################################# + # Run speech-to-text brick + ################################# + stt_brick.reset_states() + + stt_input = {"raw_wav": raw_wav, "sample_rate": sample_rate} + transcription_chunks = [] + while not stt_brick.is_generation_complete(): + stt_output = stt_brick.run(stt_input) + transcription_chunks.append(str(np.asarray(stt_output["text"]).item())) + + ################################# + # Run translation brick + ################################# + translation_brick.reset_states() + + translation_input = {"text_prompt": "".join(transcription_chunks)} + translation_chunks = [] + while not translation_brick.is_generation_complete(): + translation_output = translation_brick.run(translation_input) + translation_text = np.asarray(translation_output["text"]).item() + translation_chunks.append(str(translation_text)) + + tts_input = {"text_prompt": "".join(translation_chunks)} + left_chunks = [] + right_chunks = [] + output_sample_rate = 0 + + ################################# + # Run text-to-speech brick + ################################# + tts_brick.reset_states() + + while not tts_brick.is_generation_complete(): + tts_output = tts_brick.run(tts_input) + left = np.asarray(tts_output["left_channel"], dtype=np.float32).reshape(-1) + right = np.asarray(tts_output["right_channel"], dtype=np.float32).reshape(-1) + sample_rate_tensor = np.asarray(tts_output["sample_rate"]) + output_sample_rate = int(sample_rate_tensor.reshape(-1)[0]) + + if left.size: + left_chunks.append(left.copy()) + right_chunks.append(right.copy()) + + tts_brick.reset_states() + + if not left_chunks or output_sample_rate == 0: + raise RuntimeError("Qwen3-TTS did not generate audio") + + left_audio = np.concatenate(left_chunks) + right_audio = np.concatenate(right_chunks) + output_audio = np.column_stack((left_audio, right_audio)) + + sf.write( + args.output_audio, + output_audio, + output_sample_rate, + subtype="FLOAT", + ) + + print(f"Transcription: {''.join(transcription_chunks)}") + print(f"Translation: {''.join(translation_chunks)}") + print(f"Saved: {Path(args.output_audio).resolve()}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) +``` +{{< /tab >}} +{{< tab header="Cross-compile for Android Arm64" >}} + +Save the following code as `audio_translation_example/audio_translation.cpp`: + +```cpp +//Translate speech between languages with three Compute Flow bricks. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compute_flow/cli_examples/internal/wav.h" +#include "compute_flow/compute_flow.h" + +namespace bricks = compute_flow::bricks; + +namespace { + +void print_usage(const char* name) { + std::cout << "Usage: " << name + << " --stt-manifest --translation-manifest --tts-manifest \n" + " --input --source-language --destination-language \n" + " [--output ] [--help]\n\n" + "Translate speech between languages with three Compute Flow bricks.\n" + "The output defaults to translated_french.wav.\n"; +} + +int32_t run(int32_t argc, char** argv) { + std::string stt_manifest_path; + std::string translation_manifest_path; + std::string tts_manifest_path; + std::string input_path; + std::string source_language; + std::string destination_language; + std::string output_path = "translated_french.wav"; + const option long_options[] = { + {"stt-manifest", required_argument, nullptr, 's'}, + {"translation-manifest", required_argument, nullptr, 't'}, + {"tts-manifest", required_argument, nullptr, 'v'}, + {"input", required_argument, nullptr, 'i'}, + {"source-language", required_argument, nullptr, 'l'}, + {"destination-language", required_argument, nullptr, 'd'}, + {"output", required_argument, nullptr, 'o'}, + {"help", no_argument, nullptr, 'h'}, + {nullptr, 0, nullptr, 0}, + }; + int32_t option_value = 0; + while ((option_value = getopt_long(argc, argv, "h", long_options, nullptr)) != -1) { + switch (option_value) { + case 's': + stt_manifest_path = optarg; + break; + case 't': + translation_manifest_path = optarg; + break; + case 'v': + tts_manifest_path = optarg; + break; + case 'i': + input_path = optarg; + break; + case 'l': + source_language = optarg; + break; + case 'd': + destination_language = optarg; + break; + case 'o': + output_path = optarg; + break; + case 'h': + print_usage(argv[0]); + return EXIT_SUCCESS; + default: + print_usage(argv[0]); + return EXIT_FAILURE; + } + } + + if (stt_manifest_path.empty() || translation_manifest_path.empty() || tts_manifest_path.empty() || + input_path.empty() || source_language.empty() || destination_language.empty() || output_path.empty() || + optind != argc) { + std::cerr << "Error: provide all required arguments and no positional arguments.\n"; + print_usage(argv[0]); + return EXIT_FAILURE; + } + + // Load the input as a batch containing one mono float32 waveform. + std::vector audio; + int64_t input_sample_rate = bricks::internal::read_mono_wav(input_path, audio); + + COMPUTE_FLOW_CHECK(!audio.empty()); + + bricks::BrickOptions options; + options.runtime.enable_warmup = true; + + // Load the manifests and configure the requested languages before setup. + auto stt_manifest = bricks::load_manifest(stt_manifest_path); + auto translation_manifest = bricks::load_manifest(translation_manifest_path); + auto tts_manifest = bricks::load_manifest(tts_manifest_path); + std::string tts_language = destination_language; + + std::transform(tts_language.begin(), tts_language.end(), tts_language.begin(), + [](unsigned char value) { return static_cast(std::tolower(value)); }); + translation_manifest.set_config("src_lang", source_language); + translation_manifest.set_config("dst_lang", destination_language); + tts_manifest.set_config("language", tts_language); + + auto stt_brick = bricks::load_brick(stt_manifest); + auto translation_brick = bricks::load_brick(translation_manifest); + auto tts_brick = bricks::load_brick(tts_manifest); + + stt_brick->setup(stt_manifest, options); + translation_brick->setup(translation_manifest, options); + tts_brick->setup(tts_manifest, options); + + // Run speech-to-text, keeping the input buffers alive for every generation call. + stt_brick->reset_states(); + + compute_flow::TensorDict stt_input; + + stt_input.declare_tensor("raw_wav"); + stt_input.declare_tensor("sample_rate"); + + stt_input["raw_wav"].data = audio.data(); + stt_input["raw_wav"].format.dt = compute_flow::DataType::F32; + stt_input["raw_wav"].format.shape = {1, static_cast(audio.size())}; + stt_input["sample_rate"].data = &input_sample_rate; + stt_input["sample_rate"].format.dt = compute_flow::DataType::I64; + stt_input["sample_rate"].format.shape = {1}; + std::string transcription; + + while (!stt_brick->is_generation_complete()) { + const auto output = stt_brick->run(stt_input); + const auto& text = output["text"]; + const int64_t num_bytes = text.format.get_nbytes(); + + if (num_bytes > 0) { + transcription.append(static_cast(text.data), static_cast(num_bytes)); + } + } + + std::cout << "Transcription: " << transcription << std::endl; + + // Translate the complete transcription. + translation_brick->reset_states(); + + compute_flow::TensorDict translation_input; + + translation_input.declare_tensor("text_prompt"); + + translation_input["text_prompt"].data = transcription.data(); + translation_input["text_prompt"].format.dt = compute_flow::DataType::String; + translation_input["text_prompt"].format.shape = {static_cast(transcription.size())}; + std::string translation; + + while (!translation_brick->is_generation_complete()) { + const auto output = translation_brick->run(translation_input); + const auto& text = output["text"]; + const int64_t num_bytes = text.format.get_nbytes(); + + if (num_bytes > 0) { + translation.append(static_cast(text.data), static_cast(num_bytes)); + } + } + + std::cout << "Translation: " << translation << std::endl; + + // Synthesize the translation, copying chunks before the brick reuses its buffers. + tts_brick->reset_states(); + + compute_flow::TensorDict tts_input; + + tts_input.declare_tensor("text_prompt"); + + tts_input["text_prompt"].data = translation.data(); + tts_input["text_prompt"].format.dt = compute_flow::DataType::String; + tts_input["text_prompt"].format.shape = {static_cast(translation.size())}; + std::vector left_audio; + std::vector right_audio; + int64_t output_sample_rate = 0; + + while (!tts_brick->is_generation_complete()) { + const auto output = tts_brick->run(tts_input); + const int64_t num_samples = output["left_channel"].format.shape[0]; + output_sample_rate = *static_cast(output["sample_rate"].data); + + COMPUTE_FLOW_CHECK(num_samples == output["right_channel"].format.shape[0]); + + if (num_samples > 0) { + const auto* left = static_cast(output["left_channel"].data); + const auto* right = static_cast(output["right_channel"].data); + + left_audio.insert(left_audio.end(), left, left + num_samples); + right_audio.insert(right_audio.end(), right, right + num_samples); + } + } + + tts_brick->reset_states(); + + COMPUTE_FLOW_CHECK(!left_audio.empty() && output_sample_rate > 0); + + bricks::internal::write_stereo_wav(output_path, left_audio.data(), right_audio.data(), + static_cast(left_audio.size()), output_sample_rate); + + std::cout << "Saved: " << std::filesystem::absolute(output_path).string() << std::endl; + return EXIT_SUCCESS; +} + +} // namespace + +int32_t main(int32_t argc, char** argv) { + try { + return run(argc, argv); + } catch (const std::exception& error) { + std::cerr << "Error: " << error.what() << std::endl; + return EXIT_FAILURE; + } +} +``` + +Then, create a `CMakeLists.txt` file in the same directory. + +```cmake +cmake_minimum_required(VERSION 3.24) +project(audio_translation LANGUAGES CXX) + +add_subdirectory("${COMPUTE_FLOW_SDK_DIR}" compute_flow_sdk) + +add_executable(audio_translation audio_translation.cpp ../compute_flow/cli_examples/internal/wav.cpp) +target_include_directories(audio_translation PRIVATE ..) +target_link_libraries(audio_translation PRIVATE compute_flow::sdk) + +target_link_options( + audio_translation + PRIVATE -fopenmp -static-openmp "LINKER:-z,max-page-size=16384" "LINKER:-z,common-page-size=16384" +) +``` + +{{< /tab >}} +{{< /tabpane-normal >}} + +The code provides everything that you need to run the audio-to-audio translation pipeline. The code first loads and configures each of the three bricks required for the application. It then runs each brick, with the output from the previous brick being passed to the next one. Finally, it saves the pipeline output. + +All three bricks share these deployment methods, documented in [Brick Runtime Interface V1](https://github.com/ARM-software/ComputeFlow/blob/main/docs/design/brick_runtime_interface_v1.md). + + +## Translate a pre-recorded sample + +Resolve the package locations through Brick Manager instead of hard-coding cache paths. Then, run your application using the sample supplied with Parakeet. + +{{< tabpane-normal >}} +{{< tab header="Native Arm64" >}} + +Run the application: + +```bash +PARAKEET_PACKAGE="$(compute-flow model path stt:parakeet-tdt parakeet-tdt-0-6b-v3-int8-dynamic)" +HY_MT2_PACKAGE="$(compute-flow model path lm_text:lm-text-generic hy-mt2-1-8b-2bit-translation)" +QWEN3_TTS_PACKAGE="$(compute-flow model path tts:qwen3-tts qwen3-tts-0-6b-custom-voice)" + +python audio_translation_example/audio_translation.py \ + --stt-manifest "$PARAKEET_PACKAGE/parakeet_tdt_0_6b_v3_manifest.json" \ + --translation-manifest "$HY_MT2_PACKAGE/hy_mt2_1_8b_manifest.json" \ + --tts-manifest "$QWEN3_TTS_PACKAGE/qwen3_tts_custom_voice_en_manifest.json" \ + --input "$PARAKEET_PACKAGE/samples/beckett.wav" \ + --source-language English \ + --destination-language French \ + --output translated_french.wav +``` +{{< /tab >}} +{{< tab header="Cross-compile for Android Arm64" >}} +Compile the application: +```bash +cmake -S audio_translation_example -B build_audio_translation_android \ + -DCOMPUTE_FLOW_SDK_DIR="$PWD/build_android_arm64/sdk" \ + -DCMAKE_TOOLCHAIN_FILE="$(sed -n 's/^CMAKE_TOOLCHAIN_FILE:[^=]*=//p' build_android_arm64/CMakeCache.txt)" \ + -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-28 -DCMAKE_BUILD_TYPE=Release +cmake --build build_audio_translation_android --parallel +``` +Connect your Android device using `adb` and push the model files to the device: + +```bash +adb shell mkdir -p /data/local/tmp/compute-flow/audio-translation/stt \ + /data/local/tmp/compute-flow/audio-translation/translation \ + /data/local/tmp/compute-flow/audio-translation/tts +adb push --sync "$(compute-flow model path stt:parakeet-tdt parakeet-tdt-0-6b-v3-int8-dynamic)"/. \ + /data/local/tmp/compute-flow/audio-translation/stt/ +adb push --sync "$(compute-flow model path lm_text:lm-text-generic hy-mt2-1-8b-2bit-translation)"/. \ + /data/local/tmp/compute-flow/audio-translation/translation/ +adb push --sync "$(compute-flow model path tts:qwen3-tts qwen3-tts-0-6b-custom-voice)"/. \ + /data/local/tmp/compute-flow/audio-translation/tts/ +``` + +Next, push the binary to the device: + +```bash +adb push --sync build_audio_translation_android/audio_translation /data/local/tmp/compute-flow/audio-translation/ +``` +Finally, run the application and retrieve the French translation: + +```bash +adb shell 'cd /data/local/tmp/compute-flow/audio-translation && + chmod +x audio_translation && + ./audio_translation \ + --stt-manifest stt/parakeet_tdt_0_6b_v3_manifest.json \ + --translation-manifest translation/hy_mt2_1_8b_manifest.json \ + --tts-manifest tts/qwen3_tts_custom_voice_en_manifest.json \ + --input stt/samples/beckett.wav \ + --source-language English --destination-language French \ + --output translated_french.wav' +adb pull /data/local/tmp/compute-flow/audio-translation/translated_french.wav audio_translation_example/ +``` + +{{< /tab >}} +{{< /tabpane-normal >}} + +The expected output is: + +```output +Transcription: Ever tried, ever failed. No matter. Try again. Fail again. Fail better. +Translation: Avez-vous déjà essayé, avez-vous déjà échoué. Peu importe. Essayez à nouveau. Échouez encore. Échouez mieux. +``` + +Check that the transcription matches the English recording and that the translation preserves its meaning. + +An accurate translation in the polite form is as follows: + +```text +Avez-vous déjà essayé, avez-vous déjà échoué. Peu importe. Essayez à nouveau. Échouez encore. Échouez mieux. +``` + +You'll later learn how to select alternative models with different performance and accuracy tradeoffs. + +## What you've accomplished and what's next + +You've installed three Compute Flow brick types and used three of their model variants to build an audio-to-audio pipeline. Your application now turns an English recording into a local French `.wav` file. + +Next, you'll replace the speech recognition model and run the same application again. diff --git a/content/learning-paths/cross-platform/compute-flow-getting-started/how-to-3.md b/content/learning-paths/cross-platform/compute-flow-getting-started/how-to-3.md new file mode 100644 index 0000000000..b8926cd201 --- /dev/null +++ b/content/learning-paths/cross-platform/compute-flow-getting-started/how-to-3.md @@ -0,0 +1,114 @@ +--- +title: Replace the speech-to-text brick +description: Replace the Parakeet AI model with Moonshine in your speech-to-text brick +weight: 4 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## How the speech-to-text brick update works + +Now that you've built the audio-to-audio pipeline, you can make changes to the speech-to-text component. + +Speech-to-text is a brick type: audio and a sample rate go in, and text comes out. + +You'll replace both the Parakeet implementation and its model package with `stt:moonshine` and `moonshine-base-en`. Both implementations follow `stt.v1`, even though Parakeet uses LiteRT and Moonshine uses ONNX Runtime. + +The Python script remains unchanged. `cf.load_manifest(args.stt_manifest)` reads the manifest supplied as an argument. `cf.load_brick(stt_manifest)` selects its implementation, and `stt.setup(stt_manifest, options)` prepares the models. No model name, weights path, or inference backend is hardcoded into these calls. The same `stt.run(stt_input)` loop returns text for the translation stage. + +## Install Moonshine and rebuild Compute Flow + +Using the same virtual environment as earlier, list Moonshine models, install the English package, and inspect its manifest: + +```bash +compute-flow model list stt:moonshine +compute-flow model install stt:moonshine moonshine-base-en +compute-flow model info stt:moonshine moonshine-base-en +``` + +The package contains `moonshine_manifest_base_en.json`. + +Build Compute Flow again to include the newly installed implementation: + +{{< tabpane code="true" >}} +{{< tab header="Native Arm64" >}} +compute-flow build native +{{< /tab >}} +{{< tab header="Cross-compile for Android Arm64" >}} +compute-flow build android_arm64 +cmake --build build_audio_translation_android --parallel +{{< /tab >}} +{{< /tabpane >}} + +This build adds the Moonshine implementation to the available bricks. Don't edit your Python application or replace the other models. + +Selecting a different manifest works only when its brick implementation has been built and its model assets are installed. + +## Run the same application with Moonshine + +Resolve the package paths again so that the command also works in a fresh terminal with the environment activated. Then, run the commands for your device. + +{{< tabpane-normal >}} +{{< tab header="Native Arm64" >}} + +Rerun the application with Moonshine: + +```bash +MOONSHINE_PACKAGE="$(compute-flow model path stt:moonshine moonshine-base-en)" +HY_MT2_PACKAGE="$(compute-flow model path lm_text:lm-text-generic hy-mt2-1-8b-2bit-translation)" +QWEN3_TTS_PACKAGE="$(compute-flow model path tts:qwen3-tts qwen3-tts-0-6b-custom-voice)" + +python audio_translation_example/audio_translation.py \ + --stt-manifest "$MOONSHINE_PACKAGE/moonshine_manifest_base_en.json" \ + --translation-manifest "$HY_MT2_PACKAGE/hy_mt2_1_8b_manifest.json" \ + --tts-manifest "$QWEN3_TTS_PACKAGE/qwen3_tts_custom_voice_en_manifest.json" \ + --input "$MOONSHINE_PACKAGE/samples/beckett.wav" \ + --source-language English \ + --destination-language French \ + --output translated_french_moonshine.wav +``` +{{< /tab >}} +{{< tab header="Cross-compile for Android Arm64" >}} +Push the Moonshine model: +```bash +adb shell mkdir -p /data/local/tmp/compute-flow/audio-translation/moonshine + +adb push --sync "$(compute-flow model path stt:moonshine moonshine-base-en)"/. \ + /data/local/tmp/compute-flow/audio-translation/moonshine/ +``` + +Rerun the application using Moonshine: + +```bash +adb push --sync build_audio_translation_android/audio_translation \ + /data/local/tmp/compute-flow/audio-translation/ + +adb shell 'cd /data/local/tmp/compute-flow/audio-translation && + chmod +x audio_translation && + ./audio_translation \ + --stt-manifest moonshine/moonshine_manifest_base_en.json \ + --translation-manifest translation/hy_mt2_1_8b_manifest.json \ + --tts-manifest tts/qwen3_tts_custom_voice_en_manifest.json \ + --input stt/samples/beckett.wav \ + --source-language English --destination-language French \ + --output translated_french_moonshine.wav' +``` + +Retrieve the translated audio sample: + +```bash +adb pull /data/local/tmp/compute-flow/audio-translation/translated_french_moonshine.wav \ + audio_translation_example/ +``` + +{{< /tab >}} +{{< /tabpane-normal >}} + +The Moonshine package includes the same `beckett.wav` recording that you used in the first run. + +## What you've accomplished and what's next + +You've replaced Parakeet with Moonshine by installing its brick and model package, rebuilding, and selecting a different manifest. The shared speech-to-text interface kept your application source code unchanged. + +Next, you'll compare performance between Parakeet and Moonshine. diff --git a/content/learning-paths/cross-platform/compute-flow-getting-started/how-to-4.md b/content/learning-paths/cross-platform/compute-flow-getting-started/how-to-4.md new file mode 100644 index 0000000000..853888f2f3 --- /dev/null +++ b/content/learning-paths/cross-platform/compute-flow-getting-started/how-to-4.md @@ -0,0 +1,143 @@ +--- +title: Compare speech-to-text performance +description: Compare Parakeet and Moonshine speech-to-text performance using the same audio sample. +weight: 5 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Compare speech recognition performance + +Use the built speech-to-text CLI to compare the speech recognition stage. Changing the manifest lets you experiment quickly with another model. + +The Parakeet model files total approximately 683 MB, with dynamic INT8 quantization for fully connected weights and FP32 external interfaces. The Moonshine FP32 model files total approximately 247 MB. These are different model families using LiteRT and ONNX Runtime respectively. + +### Compare on native Arm64 + +Resolve the Parakeet package path for its manifest. Run both alternatives on the same mono 16 kHz recording, with one CPU thread and profiling enabled: + +```bash +PARAKEET_PACKAGE="$(compute-flow model path stt:parakeet-tdt parakeet-tdt-0-6b-v3-int8-dynamic)" +INPUT_AUDIO="$MOONSHINE_PACKAGE/samples/beckett.wav" +``` + +The operator breakdowns have been omitted for brevity. + + +{{< tabpane-normal >}} + {{< tab header="Parakeet dynamic INT8" >}} +Run the Parakeet manifest: + +```bash +./build_native/stt_cli \ + -m "$PARAKEET_PACKAGE/parakeet_tdt_0_6b_v3_manifest.json" \ + -a "$INPUT_AUDIO" \ + -t 1 --enable-op-profiling +``` + +The output is similar to: + +```output +[TRANSCRIPTION] +Ever tried, ever failed. No matter. Try again. Fail again. Fail better. +... +All Calls +Scope: run() [total ms: 985.12] +Scope stats | calls: 30 | avg ms: 32.84 | min ms: 0.34 | max ms: 487.10 +``` + {{< /tab >}} + {{< tab header="Moonshine FP32" >}} +Run the Moonshine manifest: + +```bash +./build_native/stt_cli \ + -m "$MOONSHINE_PACKAGE/moonshine_manifest_base_en.json" \ + -a "$INPUT_AUDIO" \ + -t 1 --enable-op-profiling +``` + +The output is similar to: + +```output +[TRANSCRIPTION] +Ever tried ever failed, no matter try again fail again fail better. +---------------- +Scope: run() [total ms: 290.30] +Scope stats | calls: 1 | avg ms: 290.30 | min ms: 290.30 | max ms: 290.30 +``` + {{< /tab >}} +{{< /tabpane-normal >}} + +Compare the total `run()` time. 30 Parakeet streaming calls complete one transcription compared with a single call for Moonshine. + +These results are one example of how to compare the models. Your timings will vary with your hardware and setup. On an Apple M3 Pro, Moonshine took 290.30 ms and Parakeet took 985.12 ms to process a single 9.963-second audio sample. The timings exclude setup and warmup, include profiling overhead, and don't assess transcription accuracy. + +Pass your own representative audio samples in a JSON file with `-j runs.json` to evaluate more inputs. The shared Python API and CLI let you quickly profile models across different runtimes. You can find the most suitable model for your application by changing manifests. + +### Compare on an Android Arm64 phone + +Use the Android build and model packages from earlier. Run both models on the same mono 16 kHz recording, with one CPU thread and profiling enabled. + +From the Compute Flow repository root, transfer the speech-to-text CLI and make it executable: + +```bash +adb push --sync build_android_arm64/stt_cli /data/local/tmp/compute-flow/audio-translation/ +adb shell chmod +x /data/local/tmp/compute-flow/audio-translation/stt_cli +``` + +The following results were measured on a Pixel 8 running Android 16. The operator breakdowns have been omitted for brevity. + +{{< tabpane-normal >}} + {{< tab header="Parakeet dynamic INT8" >}} +Run the Parakeet manifest: + +```bash +adb shell 'cd /data/local/tmp/compute-flow/audio-translation && + ./stt_cli \ + -m stt/parakeet_tdt_0_6b_v3_manifest.json \ + -a stt/samples/beckett.wav \ + -t 1 --enable-op-profiling' +``` + +The output is similar to: + +```output +[TRANSCRIPTION] +Ever tried, ever failed. No matter. Try again. Fail again. Fail better. +... +All Calls +Scope: run() [total ms: 1154.04] +Scope stats | calls: 30 | avg ms: 38.47 | min ms: 0.36 | max ms: 565.70 +``` + {{< /tab >}} + {{< tab header="Moonshine FP32" >}} +Run the Moonshine manifest with the same input recording: + +```bash +adb shell 'cd /data/local/tmp/compute-flow/audio-translation && + ./stt_cli \ + -m moonshine/moonshine_manifest_base_en.json \ + -a stt/samples/beckett.wav \ + -t 1 --enable-op-profiling' +``` + +The output is similar to: + +```output +[TRANSCRIPTION] +Ever tried ever failed, no matter try again fail again fail better. +---------------- +Scope: run() [total ms: 552.39] +Scope stats | calls: 1 | avg ms: 552.39 | min ms: 552.39 | max ms: 552.39 +``` + {{< /tab >}} +{{< /tabpane-normal >}} + +Compare the total `run()` time across all calls. On the Pixel 8, Moonshine took 552.39 ms and Parakeet took 1154.04 ms to process the same 9.963-second sample. These are single-run measurements. Your timings will vary with device load, temperature, and hardware. The timings exclude setup and warmup, include profiling overhead, and don't assess transcription accuracy. + +## What you've accomplished + +You've compared speech recognition performance between Parakeet and Moonshine. + +You can extend the Learning Path to combine other bricks and select compatible models for your own applications. diff --git a/content/learning-paths/cross-platform/explore-model-artifacts-with-model-explorer/_index.md b/content/learning-paths/cross-platform/explore-model-artifacts-with-model-explorer/_index.md index 714946aa37..cf25df3652 100644 --- a/content/learning-paths/cross-platform/explore-model-artifacts-with-model-explorer/_index.md +++ b/content/learning-paths/cross-platform/explore-model-artifacts-with-model-explorer/_index.md @@ -1,5 +1,5 @@ --- -title: Inspect model artifacts and runtime profiles with Google Model Explorer and Arm extensions +title: Inspect Arm model artifacts with Google Model Explorer description: Inspect ExecuTorch PTE, TOSA, VGF, ETRecord, and ETDump model artifacts with Google Model Explorer and Arm extensions. diff --git a/content/learning-paths/cross-platform/github-arm-runners/_index.md b/content/learning-paths/cross-platform/github-arm-runners/_index.md index dc1d3b7901..1c6063b5bc 100644 --- a/content/learning-paths/cross-platform/github-arm-runners/_index.md +++ b/content/learning-paths/cross-platform/github-arm-runners/_index.md @@ -1,5 +1,5 @@ --- -title: Build multi-architecture container images with GitHub Arm-hosted runners +title: Build multi-architecture images with GitHub Arm-hosted runners minutes_to_complete: 30 diff --git a/content/learning-paths/cross-platform/gitlab/2-gitlab-cicd.md b/content/learning-paths/cross-platform/gitlab/2-gitlab-cicd.md index 7580ff9bd7..de5e234947 100644 --- a/content/learning-paths/cross-platform/gitlab/2-gitlab-cicd.md +++ b/content/learning-paths/cross-platform/gitlab/2-gitlab-cicd.md @@ -1,5 +1,5 @@ --- -title: Automate the build and deployment of a multi-arch application with GitLab CI/CD +title: Automate multi-architecture builds and deployments with GitLab CI/CD weight: 3 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/cross-platform/litespark-inference/_index.md b/content/learning-paths/cross-platform/litespark-inference/_index.md index 509262bec6..77cc2b88ae 100644 --- a/content/learning-paths/cross-platform/litespark-inference/_index.md +++ b/content/learning-paths/cross-platform/litespark-inference/_index.md @@ -1,5 +1,5 @@ --- -title: Run and benchmark BitNet-2B inference on Arm CPUs with Litespark-Inference +title: Benchmark BitNet-2B inference on Arm with Litespark-Inference description: Run and benchmark BitNet-2B on Arm CPUs with Litespark-Inference, compare memory use and performance with PyTorch, and measure energy per token on Apple silicon. minutes_to_complete: 25 diff --git a/content/learning-paths/cross-platform/llm-fine-tuning-for-web-applications/how-to-9.md b/content/learning-paths/cross-platform/llm-fine-tuning-for-web-applications/how-to-9.md index cc32ba4986..7fdacc149f 100644 --- a/content/learning-paths/cross-platform/llm-fine-tuning-for-web-applications/how-to-9.md +++ b/content/learning-paths/cross-platform/llm-fine-tuning-for-web-applications/how-to-9.md @@ -1,5 +1,5 @@ --- -title: Prepared the Fine Tune Large Language Model for ExecuTorch and Mobile Deployment +title: Prepare the fine-tuned LLM for ExecuTorch and mobile deployment weight: 11 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/cross-platform/mcp-ai-agent/_index.md b/content/learning-paths/cross-platform/mcp-ai-agent/_index.md index c33bebc810..51600ecc22 100644 --- a/content/learning-paths/cross-platform/mcp-ai-agent/_index.md +++ b/content/learning-paths/cross-platform/mcp-ai-agent/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy an MCP Server on Raspberry Pi 5 for AI agent interaction using OpenAI SDK +title: Deploy an MCP server on Raspberry Pi 5 with the OpenAI SDK description: Learn how to deploy a Model Context Protocol server on Raspberry Pi 5 and use the OpenAI Agent SDK to create AI agents with custom tools for local inference. minutes_to_complete: 30 diff --git a/content/learning-paths/cross-platform/mcp-ai-agent/intro-to-mcp-uv.md b/content/learning-paths/cross-platform/mcp-ai-agent/intro-to-mcp-uv.md index 1c5a1e1285..68e16ece6a 100644 --- a/content/learning-paths/cross-platform/mcp-ai-agent/intro-to-mcp-uv.md +++ b/content/learning-paths/cross-platform/mcp-ai-agent/intro-to-mcp-uv.md @@ -1,5 +1,5 @@ --- -title: Introduction to Model Context Protocol (MCP) and Python uv package for local AI agents +title: Explore MCP and Python uv for local AI agents weight: 2 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/cross-platform/nvidia-jetpack-yocto-build/_index.md b/content/learning-paths/cross-platform/nvidia-jetpack-yocto-build/_index.md index 90a04edf53..fdaf224012 100644 --- a/content/learning-paths/cross-platform/nvidia-jetpack-yocto-build/_index.md +++ b/content/learning-paths/cross-platform/nvidia-jetpack-yocto-build/_index.md @@ -1,5 +1,5 @@ --- -title: Build NVIDIA JetPack Yocto images for Jetson Orin NX, Orin Nano, and Thor platforms +title: Build JetPack Yocto images for NVIDIA Jetson platforms minutes_to_complete: 180 description: Build a custom Yocto-based NVIDIA JetPack image on a Google Axion C4A virtual machine, then flash and run it on an NVIDIA Jetson Orin NX, Orin Nano, or Thor platform. diff --git a/content/learning-paths/cross-platform/nvidia-jetpack-yocto-build/instance.md b/content/learning-paths/cross-platform/nvidia-jetpack-yocto-build/instance.md index 1cb15243d1..77c6c74375 100644 --- a/content/learning-paths/cross-platform/nvidia-jetpack-yocto-build/instance.md +++ b/content/learning-paths/cross-platform/nvidia-jetpack-yocto-build/instance.md @@ -1,5 +1,5 @@ --- -title: Provision a Google Axion C4A virtual machine for Yocto image builds on Arm +title: Provision a Google Axion C4A VM for Arm Yocto builds description: Provision a Google Axion C4A virtual machine with Ubuntu 22.04 and sufficient storage to build NVIDIA Jetson Yocto images on Arm. weight: 3 diff --git a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/_index.md b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/_index.md index ff99fa976b..7d592eea9c 100644 --- a/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/_index.md +++ b/content/learning-paths/cross-platform/pytorch-digit-classification-arch-training/_index.md @@ -1,5 +1,5 @@ --- -title: Create and train a PyTorch model for digit classification using the MNIST dataset +title: Train a PyTorch MNIST digit classifier on Arm minutes_to_complete: 160 diff --git a/content/learning-paths/cross-platform/ros2-device-connect/_index.md b/content/learning-paths/cross-platform/ros2-device-connect/_index.md index 601f7a5060..8bf81146f0 100644 --- a/content/learning-paths/cross-platform/ros2-device-connect/_index.md +++ b/content/learning-paths/cross-platform/ros2-device-connect/_index.md @@ -1,5 +1,5 @@ --- -title: Expose an Arm-based ROS 2 system as a discoverable device on Device Connect +title: Expose a ROS 2 system on Arm through Device Connect description: Learn how to expose a ROS 2 system running in Docker on an Arm-based Linux device as a discoverable Device Connect device, and inspect its ROS 2 graph through remote procedure calls. diff --git a/content/learning-paths/cross-platform/ros2-device-connect/profiles-and-hardware.md b/content/learning-paths/cross-platform/ros2-device-connect/profiles-and-hardware.md index 5ec37e3704..c502227730 100644 --- a/content/learning-paths/cross-platform/ros2-device-connect/profiles-and-hardware.md +++ b/content/learning-paths/cross-platform/ros2-device-connect/profiles-and-hardware.md @@ -1,5 +1,5 @@ --- -title: Explore deployment profiles and try the adapter with a Raspberry Pi 5 camera +title: Test deployment profiles with a Raspberry Pi 5 camera description: Use a ros2-device-connect profile to run the adapter with a Raspberry Pi 5 camera, and review how the PuppyPi robot profile exposes selected capabilities. weight: 5 diff --git a/content/learning-paths/cross-platform/rust_armds/_index.md b/content/learning-paths/cross-platform/rust_armds/_index.md index 75245f58ee..70162fe0fd 100644 --- a/content/learning-paths/cross-platform/rust_armds/_index.md +++ b/content/learning-paths/cross-platform/rust_armds/_index.md @@ -1,5 +1,5 @@ --- -title: Build an embedded application with Rust and debug with Arm Development Studio +title: Build and debug an embedded Rust app with Arm Development Studio minutes_to_complete: 60 description: Learn how to build an embedded Rust application for Arm processors, run it on a Fixed Virtual Platform, and debug it using Arm Development Studio. diff --git a/content/learning-paths/cross-platform/tinkerblox_ultraedge/background.md b/content/learning-paths/cross-platform/tinkerblox_ultraedge/background.md index dcf75ea4a3..24cb2b1629 100644 --- a/content/learning-paths/cross-platform/tinkerblox_ultraedge/background.md +++ b/content/learning-paths/cross-platform/tinkerblox_ultraedge/background.md @@ -1,5 +1,5 @@ --- -title: Understand UltraEdge HPC-I architecture for edge AI and mixed workloads +title: Explore UltraEdge HPC-I architecture for edge AI weight: 2 diff --git a/content/learning-paths/cross-platform/tinkerblox_ultraedge/yocto_installation.md b/content/learning-paths/cross-platform/tinkerblox_ultraedge/yocto_installation.md index fa3c6b5cf6..f9ccdb7b37 100644 --- a/content/learning-paths/cross-platform/tinkerblox_ultraedge/yocto_installation.md +++ b/content/learning-paths/cross-platform/tinkerblox_ultraedge/yocto_installation.md @@ -1,5 +1,5 @@ --- -title: Build and install Yocto images for NXP S32G-VNP-GLDBOX3 with UltraEdge +title: Build Yocto images for NXP S32G-VNP-GLDBOX3 with UltraEdge weight: 4 diff --git a/content/learning-paths/cross-platform/topdown-compare/2-code-examples.md b/content/learning-paths/cross-platform/topdown-compare/2-code-examples.md index a62d3685ac..3be510d54a 100644 --- a/content/learning-paths/cross-platform/topdown-compare/2-code-examples.md +++ b/content/learning-paths/cross-platform/topdown-compare/2-code-examples.md @@ -1,5 +1,5 @@ --- -title: "Measure cross-platform performance with topdown-tool and Perf PMU counters" +title: "Measure performance with topdown-tool and Perf PMU counters" weight: 7 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/cross-platform/topdown-compare/_index.md b/content/learning-paths/cross-platform/topdown-compare/_index.md index 24edcb4acb..9bae7aa75e 100644 --- a/content/learning-paths/cross-platform/topdown-compare/_index.md +++ b/content/learning-paths/cross-platform/topdown-compare/_index.md @@ -1,5 +1,5 @@ --- -title: Compare Arm Neoverse and Intel x86 top-down performance analysis with PMU counters +title: Compare Arm and x86 top-down performance with PMU counters minutes_to_complete: 30 diff --git a/content/learning-paths/embedded-and-microcontrollers/alif-image-classification/_index.md b/content/learning-paths/embedded-and-microcontrollers/alif-image-classification/_index.md index 10b5b21413..252cff18af 100644 --- a/content/learning-paths/embedded-and-microcontrollers/alif-image-classification/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/alif-image-classification/_index.md @@ -1,5 +1,5 @@ --- -title: Run image classification on an Alif Ensemble E8 DevKit using ExecuTorch and Ethos-U85 +title: Run image classification with ExecuTorch on Alif E8 and Ethos-U85 description: Deploy a MobileNetV2 image classification model to an Alif Ensemble E8 DevKit and run inference on the Ethos-U85 NPU. diff --git a/content/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/6-python-api.md b/content/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/6-python-api.md index 09359efff0..1d332a61bd 100644 --- a/content/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/6-python-api.md +++ b/content/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/6-python-api.md @@ -98,4 +98,4 @@ You've used the Python API to run the same kind of analysis you performed from t You can now incorporate these MLIA checks into an automation or integration workflow. -To inspect these results interactively on Visual Studio Code, see [(Optional) Use the MLIA and Model Explorer Visual Studio Code extension](/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/7-vscode-plugin/). +To inspect these results interactively on Visual Studio Code, see [(Optional) Use the MLIA and Model Explorer VS Code extension](/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/7-vscode-plugin/). diff --git a/content/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/7-vscode-plugin.md b/content/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/7-vscode-plugin.md index c15ba2d54e..1bf0a5e698 100644 --- a/content/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/7-vscode-plugin.md +++ b/content/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/7-vscode-plugin.md @@ -1,5 +1,5 @@ --- -title: (Optional) Use the MLIA and Model Explorer Visual Studio Code extension +title: (Optional) Use the MLIA and Model Explorer VS Code extension description: Set up MLIA in VS Code, analyze a MobileNet model for Ethos-U, and inspect compatibility, performance metrics, and advice in Model Explorer. diff --git a/content/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/_index.md b/content/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/_index.md index 0941b4e69f..ae0ebc3b26 100644 --- a/content/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/_index.md @@ -1,5 +1,5 @@ --- -title: Analyze machine learning models for Arm Ethos-U with Arm ML Inference Advisor +title: Analyze Arm Ethos-U models with Arm ML Inference Advisor description: Use Arm ML Inference Advisor (MLIA) from the command line to check model compatibility, estimate performance, and identify target-aware model improvement opportunities for Ethos-U. diff --git a/content/learning-paths/embedded-and-microcontrollers/avh_greengrass/_index.md b/content/learning-paths/embedded-and-microcontrollers/avh_greengrass/_index.md index d182ff01bb..dc06ffc35f 100644 --- a/content/learning-paths/embedded-and-microcontrollers/avh_greengrass/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/avh_greengrass/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy IoT applications with AWS IoT Greengrass and Arm Virtual Hardware +title: Deploy IoT apps with AWS IoT Greengrass and Arm Virtual Hardware description: Learn how to set up AWS IoT Greengrass Core on Arm Virtual Hardware and deploy AWS Greengrass components to a virtual Raspberry Pi 4 device. diff --git a/content/learning-paths/embedded-and-microcontrollers/azure-iot/aggregation.md b/content/learning-paths/embedded-and-microcontrollers/azure-iot/aggregation.md index 6e3c422b20..3eef7cfcbb 100644 --- a/content/learning-paths/embedded-and-microcontrollers/azure-iot/aggregation.md +++ b/content/learning-paths/embedded-and-microcontrollers/azure-iot/aggregation.md @@ -162,23 +162,23 @@ func start ``` Once running, observe the HTTP trigger endpoint, which should appear similar to the following: -![img36 alt-text#center](figures/36.webp) +![Terminal output from func start showing GetAverageTemperature running at a local HTTP endpoint, confirming the Azure Function is ready for a browser request.#center](figures/36.webp) Next, start the simulator to stream sensor data and open the HTTP trigger endpoint URL in your web browser. You will see the calculated average temperature displayed: -![img37 alt-text#center](figures/37.png) +![Browser response from the local average temperature endpoint showing a Success message and an averageTemperature value of 23.45.#center](figures/37.png) ## Deploy to Azure Function App Now that your Azure Function is fully tested and ready, it's time to deploy it to Azure, making it accessible online and available for integration with other services and applications. Visual Studio Code provides an easy and efficient way to deploy Azure Functions directly from your local development environment. Follow these steps to deploy your function 1. In Visual Studio Code, open the Command Palette (Ctrl+Shift+P on Windows/Linux, or Cmd+Shift+P on macOS) and search for "Azure Functions: Deploy to Function App": -![img38 alt-text#center](figures/38.png) +![Visual Studio Code Command Palette with Azure Functions: Deploy to Function App selected to publish the local function.#center](figures/38.png) 2. The deployment wizard will guide you through the following selections: * Subscription: choose the Azure subscription you wish to use, * Select a function app: Select the Function App that you previously created in Azure (in this example, "IoTTemperatureAlertFunc") * Confirm your deployment: -![img39 alt-text#center](figures/39.png) +![Visual Studio Code deployment confirmation warning that publishing to IoTTemperatureAlertFunc will overwrite its previous deployment, with Deploy available to continue.#center](figures/39.png) 3. Wait for the deployment to complete. This process typically takes a few moments. Once deployed, your Azure Function is hosted in Azure and ready for use. 4. Open the Azure Portal, and go to your function app (in this example, "IoTTemperatureAlertFunc"). You will see the deployed functions: -![img40 alt-text#center](figures/40.webp) +![Azure Function App overview showing both cosmosdb_trigger and GetAverageTemperature listed as enabled functions after deployment.#center](figures/40.webp) ## Configure Function App Settings You have just deployed the functions to Azure. Previously, when testing the functions locally, you used the `local.settings.json` file to store the Cosmos DB connection string. However, this local configuration file is not deployed to Azure. Therefore, you need to update the corresponding settings directly within the Azure portal. @@ -191,7 +191,7 @@ Follow these steps to configure the Cosmos DB connection string 3. Click the **+ Add** button. 4. Enter the name you used in your code (such as "armiotcosmosdb_DOCUMENTDB"). 5. Paste the Cosmos DB connection string into the Value field: -![img41 alt-text#center](figures/41.webp) +![Azure Function App Environment variables page with the Add/Edit application setting pane open for the Cosmos DB connection string name and value.#center](figures/41.webp) 6. Click **Apply** to add the setting. 7. Click **Apply** at the bottom to apply changes. Then, confirm to save changes @@ -200,11 +200,11 @@ Once you've configured the connection string, test your deployed Azure Function 1. Return to the Overview page of your Azure Function App. 2. Click on your HTTP-triggered function (GetAverageTemperature). 3. Click Get function URL and copy the displayed URL (under default): -![img42 alt-text#center](figures/42.webp) +![Azure GetAverageTemperature Code and Test page with the Get Function URL pane open and a copy button beside the default function URL.#center](figures/42.webp) 4. Open this URL in your web browser. 5. Start your IoT simulator to begin streaming telemetry data to Cosmos DB. 6. Refresh or access the function URL again, and you should see the calculated average temperature displayed: -![img43 alt-text#center](figures/43.png) +![Browser response from the deployed GetAverageTemperature endpoint showing a Success message and an averageTemperature value of 26.04.#center](figures/43.png) This confirms your Azure Function successfully connects to Cosmos DB, retrieves real-time data, and calculates the average temperature as intended diff --git a/content/learning-paths/embedded-and-microcontrollers/azure-iot/device_registration.md b/content/learning-paths/embedded-and-microcontrollers/azure-iot/device_registration.md index 93f4421789..6b65f34ce7 100644 --- a/content/learning-paths/embedded-and-microcontrollers/azure-iot/device_registration.md +++ b/content/learning-paths/embedded-and-microcontrollers/azure-iot/device_registration.md @@ -170,15 +170,15 @@ To connect the Python application you developed earlier to Azure IoT Hub, follow 1. Register a Device on Azure IoT Hub: * Open the Azure Portal, go to your IoT Hub, and click **Devices** under **Device management**: -![img6 alt-text#center](figures/06.webp) +![Azure IoT Hub Devices page with Devices selected under Device management and the Add Device button available above an empty device list.#center](figures/06.webp) * Click **Add Device**, enter a device ID (for example, "arm64Device01"), and leave the authentication type as **Symmetric key**: -![img7 alt-text#center](figures/07.webp) +![Azure IoT Hub Create a device form with arm64Device01 entered as the device ID, Symmetric key selected for authentication, and Save available.#center](figures/07.webp) * Click **Save**. 2. Next, you’ll need to retrieve the connection string to integrate your Python application with Azure IoT Hub. * From the device list, select your newly-created device, "arm64Device01". * Copy the **Primary connection string** from the device details page. You’ll need this connection string to authenticate your Python application when connecting and streaming telemetry data. -![img8 alt-text#center](figures/08.webp) +![Azure IoT Hub details for arm64Device01 showing the masked Primary connection string and its copy button, which provides the credential for the Python simulator.#center](figures/08.webp) Ensure this connection string is stored securely, as it provides authentication credentials for your device. diff --git a/content/learning-paths/embedded-and-microcontrollers/azure-iot/monitoring.md b/content/learning-paths/embedded-and-microcontrollers/azure-iot/monitoring.md index 2d4b2db26e..e59ba2e000 100644 --- a/content/learning-paths/embedded-and-microcontrollers/azure-iot/monitoring.md +++ b/content/learning-paths/embedded-and-microcontrollers/azure-iot/monitoring.md @@ -60,9 +60,9 @@ For Python functions on Linux-based plans, local development and deployment repr You will start by creating an Azure Function App, in which you will create an Azure Function that regularly queries temperature data from Cosmos DB. In the next step, you will add the capability to send notifications, whenever the temperature reading exceeds a predefined threshold. Proceed as follows: 1. Sign in to the Azure Portal. 2. Click **Create a resource**, type “Function App”, and select it: -![img24 alt-text#center](figures/24.webp) +![Azure Marketplace search results for Function App, with the Microsoft Function App tile and its Create menu visible.#center](figures/24.webp) 3. Click **Create**, then select Consumption as a hosting option: -![img25 alt-text#center](figures/25.webp) +![Azure Create Function App hosting options with the Consumption plan selected and the Select button available to continue.#center](figures/25.webp) 4. Provide the required details: * Subscription: Your Azure subscription. * Resource Group: Select your existing IoT resource group. @@ -73,7 +73,7 @@ You will start by creating an Azure Function App, in which you will create an Az * Operating System: Select Linux as Windows is unavailable for Python. 5. Click Review + Create, and then Create. -![img26 alt-text#center](figures/26.webp) +![Azure Create Function App Basics form with a resource group and function app name entered, Python 3.11 selected as the runtime, and Linux selected as the operating system.#center](figures/26.webp) ## Install Prerequisites Before writing the code make sure you have the following tools installed: @@ -99,11 +99,11 @@ Ensure you also see a v4.x.x output, indicating compatibility with Python v2 mod Follow these steps to create an Azure Function locally using Visual Studio Code: 1. In Visual Studio Code, click View->Command Palette... 2. Type "Create Function": -![img27 alt-text#center](figures/27.webp) +![Visual Studio Code Command Palette showing Azure Functions: Create Function as the selected command.#center](figures/27.webp) 3. Select Azure Functions: Create Function... 4. Select folder for your new function. For example create a new folder `Arm.AzureIoT.AzureFunctions` 5. Visual Studio Code will display the wizard, which enables you to configure your function: -![img28 alt-text#center](figures/28.png) +![Visual Studio Code Create new project wizard at the language selection step, with Python highlighted for the Azure Function project.#center](figures/28.png) 6. Use this wizard to configure the function: * Select a language: Select Python * Select a Python programming model: Model V2 (Recommended) @@ -176,7 +176,7 @@ func start You should then see the following logs, depending on the generated temperature values: -![img29 alt-text#center](figures/29.webp) +![Azure Functions Core Tools terminal output showing the Cosmos DB trigger running for new sensor readings. Normal temperatures are logged as within limits, while readings above 28 degrees Celsius are flagged as high.#center](figures/29.webp) ## Monitoring and Notifications In this section, you will extend your existing Azure Function to send email notifications using `SendGrid` whenever the temperature exceeds the defined threshold @@ -185,23 +185,23 @@ In this section, you will extend your existing Azure Function to send email noti Follow these steps to create a SendGrid account: 1. Sign in to the Azure Portal. 2. Click “Create a resource” and search for SendGrid. -![img30 alt-text#center](figures/30.webp) +![Azure Marketplace search results for SendGrid, with the Twilio SendGrid tile and its Subscribe menu visible.#center](figures/30.webp) 3. Select Twilio SendGrid, choose the Free 100 (2022) plan, and then click Subscribe. 4. Provide the following details: * Subscription: Select your Azure subscription. * Resource group: Choose your existing IoT project resource group. * Name: Enter a descriptive name (e.g., iot-alerts-sendgrid). -![img31 alt-text#center](figures/31.webp) +![Azure Twilio SendGrid subscription form showing the resource group, an example account name, and the Free 100 plan before review and subscription.#center](figures/31.webp) 5. Click Review + subscribe and then Subscribe. 6. On the next screen, click Configure account now: -![img32 alt-text#center](figures/32.webp) +![Azure Twilio SendGrid subscription progress page prompting the user to finish setup on the publisher's website with the Configure account now button.#center](figures/32.webp) 7. Accept any permissions required by SendGrid, and then enter your details to create a sender identity: -![img33 alt-text#center](figures/33.webp) +![SendGrid welcome page showing the Create sender identity button as the first step for sending email.#center](figures/33.webp) 8. Fill out the required details, such as your name and email address. 9. After the sender identity is verified, click API Keys in the left menu: -![img34 alt-text#center](figures/34.webp) +![SendGrid API Keys page with API Keys selected under Settings and the Create API Key button in the upper right.#center](figures/34.webp) 10. Click Create API Key. In the popup window, enter a key name (e.g., iot-api-key), and select Full Access. -![img35 alt-text#center](figures/35.webp) +![SendGrid Create API Key form with an example key name entered, Full Access selected, and the Create and View button ready to generate the key.#center](figures/35.webp) 11. Copy the generated API key securely. You will not be able to retrieve it later. ### Configure SendGrid API Key in your Azure Function diff --git a/content/learning-paths/embedded-and-microcontrollers/azure-iot/portal.md b/content/learning-paths/embedded-and-microcontrollers/azure-iot/portal.md index 760a6a4a3a..0aa5cd7001 100644 --- a/content/learning-paths/embedded-and-microcontrollers/azure-iot/portal.md +++ b/content/learning-paths/embedded-and-microcontrollers/azure-iot/portal.md @@ -143,7 +143,7 @@ Make sure you have saved all three files (index.html, main.js, and styles.css). You should now see real-time temperature readings displayed: -![img44 alt-text#center](figures/44.png) +![Locally opened IoT Solution web page with the Get temperature button and a returned temperature reading of 24.09 degrees Celsius.#center](figures/44.png) ## Deployment to Azure Blob Storage You will now deploy the web portal you have created to Azure Blob Storage, making it accessible online. @@ -153,21 +153,21 @@ You will now deploy the web portal you have created to Azure Blob Storage, makin 2. Create a Storage Account: * Click **Create a resource**. * Search for “Storage account”. -![img45 alt-text#center](figures/45.webp) +![Azure Marketplace search results for storage account, with the Microsoft Storage account tile among the results.#center](figures/45.webp) * Click **Create**. -![img46 alt-text#center](figures/46.png) +![Azure Marketplace Storage account product page with its plan selected and the Create button available.#center](figures/46.png) 3. Provide the required details: * Subscription, resource group, and storage account name (e.g. armiotstorage). * For Primary service, choose **Azure Blob Storage** or **Azure Data Lake Storage Gen 2**. * Select **Standard performance** and **Locally-redundant storage (LRS)**. -![img47 alt-text#center](figures/47.webp) +![Azure Create a storage account Basics form with an account name entered, Azure Blob Storage selected as the primary service, Standard performance, and locally redundant storage.#center](figures/47.webp) * Click "Review + create", then "Create". 3. Enable Static Website Hosting: * Navigate to your newly created storage account. * Under Data management, click **Static website**. * Select **Enabled**. * Set index.html as the index document name. -![img48 alt-text#center](figures/48.webp) +![Azure Storage account Static website settings with Enabled selected and index.html entered as the index document name.#center](figures/48.webp) * Click **Save**. After saving, Azure provides you with a URL like: https://.z22.web.core.windows.net/. @@ -180,14 +180,14 @@ You can upload your website files directly using the Azure Portal or via Azure S 3. Open the container named ”$web” (created automatically when enabling static websites). 4. Click **Upload** and select your three website files (index.html, main.js, styles.css), and upload them. -![img49 alt-text#center](figures/49.webp) +![Azure Storage $web container listing the uploaded index.html, main.js, and styles.css files for the IoT web portal.#center](figures/49.webp) ### Verify the Deployment After uploading your files, open a browser and navigate to https://.z22.web.core.windows.net/. Your static website should load, allowing you to test the **Get temperature** button (to see temperatures make sure to start the IoT simulator): -![img50 alt-text#center](figures/50.png) +![Deployed IoT Solution web page at an Azure Storage static website address, with the Get temperature button and a returned reading of 24.26 degrees Celsius.#center](figures/50.png) ## Summary In this Learning Path, you successfully built a complete, end-to-end prototype of an IoT solution. You started with a simulator streaming realistic telemetry data to Azure through IoT Hub. You used Azure Stream Analytics to process and route this streaming data directly into Cosmos DB, providing scalable and reliable storage. Additionally, you developed two Azure Functions: the first continuously monitors incoming temperature readings, sending email notifications whenever the temperature exceeds a predefined threshold, ensuring proactive alerts. The second Azure Function aggregates recent temperature data from the last minute and provides this information via an HTTP endpoint. Finally, you utilized this aggregation function within our user-friendly web portal, enabling real-time visualization of temperature data, thus building out a complete IoT solution. diff --git a/content/learning-paths/embedded-and-microcontrollers/azure-iot/stream-analytics-dynamo-db.md b/content/learning-paths/embedded-and-microcontrollers/azure-iot/stream-analytics-dynamo-db.md index 58f42c8660..444b50ee62 100644 --- a/content/learning-paths/embedded-and-microcontrollers/azure-iot/stream-analytics-dynamo-db.md +++ b/content/learning-paths/embedded-and-microcontrollers/azure-iot/stream-analytics-dynamo-db.md @@ -52,11 +52,11 @@ You will now configure the stream analytics job such that the telemetry data wil Start by creating the Create Cosmos DB account and database: 1. Log in to the Azure Portal. 2. Select **Create a resource**, search for “Azure Cosmos DB”, and click **Create**: -![img14 alt-text#center](figures/14.webp) -![img15 alt-text#center](figures/15.png) +![Azure Marketplace search results for Azure Cosmos DB, with the Microsoft Azure Cosmos DB tile visible for selection.#center](figures/14.webp) +![Azure Marketplace Azure Cosmos DB product page with the Azure Cosmos DB plan selected and the Create button available.#center](figures/15.png) 3. Select Azure Cosmos DB for NoSQL, then click **Create**. -![img16 alt-text#center](figures/16.webp) +![Azure Cosmos DB account creation page offering API choices, with the Create button under Azure Cosmos DB for NoSQL.#center](figures/16.webp) 4. Fill in the required details: * Subscription: select your subscription. @@ -67,7 +67,7 @@ Start by creating the Create Cosmos DB account and database: * Select serverless as capacity mode. * Apply Free Tier Discount: apply * Check Limit total account throughput. -![img17 alt-text#center](figures/17.webp) +![Azure Cosmos DB for NoSQL account Basics form with a resource group and account name entered, availability zones disabled, and Serverless selected as the capacity mode.#center](figures/17.webp) 5. Click **Review + create**, then click **Create**. Once the deployment completes: @@ -76,16 +76,16 @@ Once the deployment completes: * Select an appropriate partition key (recommended: /deviceId). * Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads: Off. * Click **OK**. -![img18 alt-text#center](figures/18.webp) +![Azure Cosmos DB Data Explorer New Container pane with a new IoTDatabase database, SensorReadings container, and /deviceId partition key entered.#center](figures/18.webp) ### Modify Stream Analytics Job Now update your query in Stream Analytics to write data from IoT Hub directly into Cosmos DB: 1. Go to `IoTStreamAnalyticsJob`. 2. Under Job topology, select **Outputs**. 3. Click **Add output**, and select **Cosmos DB**: -![img19 alt-text#center](figures/19.webp) +![Azure Stream Analytics job Outputs page with the Add output menu open and Cosmos DB available as an output destination.#center](figures/19.webp) 4. In the Cosmos DB pane, type "CosmosDBOutput" for the alias name, leave other fields at their default values, and click the **Save** button: -![img20 alt-text#center](figures/20.webp) +![Azure Stream Analytics Cosmos DB output form with alias CosmosDBOutput and the existing Cosmos DB account, IoTDatabase database, and SensorReadings container selected.#center](figures/20.webp) ### Update Your Stream Analytics Query Now that you have the output configured, lets modify the query. To do so, select Query under Job topology. Then, modify your existing query to explicitly specify your Cosmos DB output alias: @@ -103,16 +103,16 @@ FROM IoTHubInput ``` -![img21 alt-text#center](figures/21.webp) +![Azure Stream Analytics Query page showing IoT Hub sensor fields selected INTO CosmosDBOutput FROM IoTHubInput, so incoming telemetry is written to Cosmos DB.#center](figures/21.webp) Afterwards, click **Start job**, and then **Start**: -![img22 alt-text#center](figures/22.webp) +![Azure Stream Analytics Start job pane with Now selected as the job output start time and the Start button available.#center](figures/22.webp) ## Verify data flow in Cosmos DB To verify that your data pipeline is working correctly, first start your Python IoT simulator application `iot_simulator.py`. Ensure it's actively sending telemetry data. Next, open the Azure Portal and navigate to your Azure Cosmos DB resource. Under Data Explorer, select your database and then your container (e.g., SensorReadings). Once selected, click **Items** to view your stored data. Sensor readings streamed from your IoT device will appear on the right-hand side of the Data Explorer interface, similar to the screenshot below: -![img23 alt-text#center](figures/23.webp) +![Azure Cosmos DB Data Explorer with the SensorReadings Items list open and a stored JSON document showing device ID, temperature, pressure, humidity, and timestamp fields.#center](figures/23.webp) Azure Cosmos DB stores data as JSON documents within a NoSQL (document-based) structure, making it ideal for flexible and dynamic data, such as IoT telemetry. Each record (also called a document) is stored in a container (or collection) that doesn’t enforce a rigid schema. As a result, each document can contain different fields without requiring schema changes or migrations, which is particularly valuable when collecting data from diverse IoT devices with evolving attributes. diff --git a/content/learning-paths/embedded-and-microcontrollers/azure-iot/stream-analytics.md b/content/learning-paths/embedded-and-microcontrollers/azure-iot/stream-analytics.md index 6ec29d69f1..5fbd5c0f53 100644 --- a/content/learning-paths/embedded-and-microcontrollers/azure-iot/stream-analytics.md +++ b/content/learning-paths/embedded-and-microcontrollers/azure-iot/stream-analytics.md @@ -23,7 +23,7 @@ To process and analyze the telemetry data you are streaming to Azure IoT Hub, yo 1. Sign in to the Azure Portal. 2. Click **Create a resource**, type “Stream Analytics job” into the search box, and press **Enter**. 3. From the search results, select **Stream Analytics job**, then click **Create**: -![img9 alt-text#center](figures/09.webp) +![Azure Marketplace search results for Stream Analytics job, with the Microsoft Stream Analytics job tile and its Create menu visible.#center](figures/09.webp) 4. Provide the necessary information: * Subscription: choose the Azure subscription you want to use for this job. * Resource group: select the resource group you previously created (e.g., your IoT project's resource group). @@ -32,7 +32,7 @@ To process and analyze the telemetry data you are streaming to Azure IoT Hub, yo * Hosting environment: select Cloud for Azure-managed infrastructure. * Streaming units: set this to 1 (appropriate for initial testing and smaller workloads, you can scale up later). -![img10 alt-text#center](figures/10.png) +![Azure New Stream Analytics job Basics form with a resource group and job name entered, Cloud selected as the hosting environment, and one streaming unit set.#center](figures/10.png) 5. After reviewing your settings carefully, click **Review + create**, confirm that all details are correct, and then click **Create** to deploy your Azure Stream Analytics job. @@ -43,7 +43,7 @@ After successfully creating the Stream Analytics job, you will need to configure 1. Navigate to your newly created Stream Analytics job in the Azure Portal. 2. On the left-hand menu, under the **Job topology** section, select **Inputs**. 3. Click **Add input**, and choose **IoT Hub** as the input type. -![img11 alt-text#center](figures/11.png) +![Azure Stream Analytics job Inputs page with the Add input menu open and IoT Hub listed as a stream input source.#center](figures/11.png) 4. Enter the following configuration details: * Input Alias: provide a name, such as `IoTHubInput`. * IoT Hub: select the Azure IoT Hub you created earlier. @@ -55,7 +55,7 @@ After successfully creating the Stream Analytics job, you will need to configure * Encoding: Choose **UTF-8**. * Event compression type: Set this to None. -![img12 alt-text#center](figures/12.webp) +![Azure Stream Analytics IoT Hub input form with alias IoTHubInput, an IoT Hub and consumer group selected, deviceId as the partition key, and JSON with UTF-8 encoding.#center](figures/12.webp) 5. After entering these details, carefully verify them for accuracy and completeness. Click **Save** to apply the changes and successfully link your Azure IoT Hub as the input source for your Stream Analytics job. @@ -81,7 +81,7 @@ This straightforward query selects all relevant fields (deviceId, temperature, p Before running this query, ensure your Python IoT simulator `iot_simulator.py` is actively sending telemetry data. After the simulator begins transmitting data, you can test and verify your Stream Analytics query within the Azure Portal using the built-in Test query feature. Doing this allows you to view live-streamed sensor data in real-time and confirm that your streaming pipeline is working as expected: -![img13 alt-text#center](figures/13.webp) +![Azure Stream Analytics Query page showing a query that selects device ID, temperature, pressure, humidity, and timestamp from IoTHubInput, with sample sensor readings in the input preview below.#center](figures/13.webp) Constructing queries in Azure Stream Analytics involves using a straightforward SQL-like syntax specifically optimized for real-time stream processing. Typically, a query contains a `SELECT` statement to specify which fields from the incoming telemetry data to process, and a `FROM` statement to indicate the source stream. Queries can be expanded with advanced features such as filtering, aggregations, and temporal window functions to handle more complex scenarios. diff --git a/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/1-understand-the-c2pa.md b/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/1-understand-the-c2pa.md index 27b93dd0ea..d184a2daef 100644 --- a/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/1-understand-the-c2pa.md +++ b/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/1-understand-the-c2pa.md @@ -87,8 +87,9 @@ the signing credential against its trust policy. ## Understand trust in Content Credentials The [C2PA trust model](https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#_trust_model) distinguishes a *valid manifest* from a *trusted manifest*. A valid manifest is well-formed, has -not changed since it was signed, and passes the required signature, validity-period, and credential -revocation checks. A trusted manifest is valid and its signing credential is also trusted. A validator +not changed since it was signed, passes the required signature and validity-period checks, and has +not been rejected because its signing credential was revoked. A `Valid` result does not necessarily +mean that an online revocation check succeeded. A trusted manifest is valid and its signing credential is also trusted. A validator can establish credential trust through configured trust anchors, including anchors from the [C2PA Trust List](https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#_c2pa_trust_list), or through a credential that the user has explicitly added to a private credential store. diff --git a/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/2-prepare-example-files.md b/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/2-prepare-example-files.md index 67b1f88894..5dcd04c2e5 100644 --- a/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/2-prepare-example-files.md +++ b/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/2-prepare-example-files.md @@ -9,9 +9,18 @@ layout: learningpathall ## Create a working directory -Create a folder named `c2pa-demo` wherever you keep your projects, using your file manager or terminal. -Open a terminal in that folder and run the remaining commands there. Keep all files from this -Learning Path in the same folder so the commands can use their filenames without full paths. +Use Bash or Zsh on Linux or macOS, or PowerShell on Windows. Open your terminal in the directory +where you keep your projects, then create and enter a working directory: + +```console +mkdir c2pa-demo +cd c2pa-demo +``` + +These commands create the `c2pa-demo` folder and make it your terminal's working directory. If the +folder already exists, run only `cd c2pa-demo`. Run the remaining commands from this directory. +Keep all files from this Learning Path in the same folder so the commands can use their filenames +without full paths. ## Confirm C2PA Tool is available @@ -42,9 +51,15 @@ To focus on signing and validation without setting up a camera, use the For this exercise, assume the sample represents a digitally captured photograph whose capture details are available to the camera software when it adds Content Credentials. -Download the original JPEG from that GitHub page and save it as `image.jpg` in your `c2pa-demo` -working directory. If your browser displays the raw image, use **Save image as**. The file is -61,720 bytes. Keep this unsigned source file for the later steps. +From your `c2pa-demo` working directory, download the original JPEG as `image.jpg` using `wget`: + +```console +wget -O image.jpg https://raw.githubusercontent.com/contentauth/c2patool/v0.27.20/sample/image.jpg +``` + +The command saves the JPEG as `image.jpg`, replacing that file if it already exists. The URL points +to the raw image rather than the GitHub HTML page. The file is 61,720 bytes. Keep this unsigned +source file for the later steps. The upstream sample is provided under the [MIT license, © Copyright 2020 Adobe](https://github.com/contentauth/c2patool/blob/v0.27.20/LICENSE-MIT). Retain its copyright and permission notice if you redistribute the image. diff --git a/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/3-add-content-credentials.md b/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/3-add-content-credentials.md index a1eab6fa9b..63fa95d98c 100644 --- a/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/3-add-content-credentials.md +++ b/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/3-add-content-credentials.md @@ -24,8 +24,9 @@ to declare how the image was created. The [*claim generator*](https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#claim-generator-definition) is the software that assembles the claim and its references to assertions for signing. In this exercise, C2PA Tool performs that work using the example identity you supply below. -In your text editor, create a plain-text file named `manifest.json` in your `c2pa-demo` working directory -with these contents: +In your text editor, create a file named `manifest.json` in your `c2pa-demo` working +directory with these contents. On Windows, enable **File name extensions** in File Explorer and +confirm that the filename is `manifest.json`, rather than `manifest.json.txt`: ```json { diff --git a/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/4-inspect-content-credentials.md b/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/4-inspect-content-credentials.md index 375a1d2c3d..c05131cd19 100644 --- a/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/4-inspect-content-credentials.md +++ b/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/4-inspect-content-credentials.md @@ -84,6 +84,9 @@ and the image. `assertion.dataHash.match` reports that C2PA Tool successfully ch ## Inspect the signature and credential +Under `manifests`, open the object whose label matches `active_manifest`, then locate +`signature_info`. The expected output includes: + ```output "signature_info": { "alg": "Es256", @@ -92,7 +95,9 @@ and the image. `assertion.dataHash.match` reports that C2PA Tool successfully ch } ``` -The validation results also report: +Under `validation_results.activeManifest.success`, find `claimSignature.validated` to confirm +that the claim signature verifies. Under `validation_results.activeManifest.failure`, the +expected output includes: ```output { @@ -127,6 +132,12 @@ means that the signing certificate is not accepted by the tool's configured trus the integrity checks succeeded. The tool distinguishes `Valid` from `Trusted`, which additionally requires acceptance of the signing credential under that policy. +You may also find `signingCredential.ocsp.skipped` under +`validation_results.activeManifest.informational`. This means that online certificate-status +fetching through the Online Certificate Status Protocol (OCSP) was skipped. The `Valid` result +does not establish that an online revocation check succeeded; read it alongside the individual +status codes. + ## Locate the C2PA structures inside the JPEG The JSON report is a readable interpretation of the embedded data, not its storage format. In this diff --git a/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/6-detect-an-unauthorized-change.md b/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/6-detect-an-unauthorized-change.md index 452abf7e87..d61bc25e50 100644 --- a/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/6-detect-an-unauthorized-change.md +++ b/content/learning-paths/embedded-and-microcontrollers/c2pa-on-arm/6-detect-an-unauthorized-change.md @@ -129,6 +129,13 @@ original signing certificate requires the corresponding private key. Without tha the assertion and claim cannot make the altered content pass these checks under the original credential. The protection comes from the signature, not from preventing access to the manifest. +{{% notice Note %}} +The development private key bundled with C2PA Tool is publicly available. Anyone with that key can +re-sign a modified image using the same test credential. This exercise demonstrates detection of +an edit made without re-signing. Protection against an attacker re-signing under the original +credential depends on keeping a production private key secret and secure. +{{% /notice %}} + See the [C2PA validation rules](https://spec.c2pa.org/specifications/specifications/2.4/specs/ContentCredentials.html#_validation) for these checks. diff --git a/content/learning-paths/embedded-and-microcontrollers/deploy-ml-model-to-npu-with-topo/_index.md b/content/learning-paths/embedded-and-microcontrollers/deploy-ml-model-to-npu-with-topo/_index.md index 0f28d51c08..1937d4771b 100644 --- a/content/learning-paths/embedded-and-microcontrollers/deploy-ml-model-to-npu-with-topo/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/deploy-ml-model-to-npu-with-topo/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy a machine learning application to the Arm Ethos-U65 NPU on NXP FRDM i.MX 93 with Topo +title: Deploy an ML app with Topo to Arm Ethos-U65 on NXP FRDM i.MX 93 description: Use Topo to build and deploy a Cortex-A web application that sends MobileNetV2 image classification requests to Cortex-M33 firmware accelerated by the Ethos-U65 NPU. diff --git a/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/_index.md b/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/_index.md index 48cdb23715..566279fbb0 100644 --- a/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/_index.md @@ -15,6 +15,8 @@ learning_objectives: prerequisites: - A Linux development machine with glibc 2.28 or later, or an Apple silicon Mac running macOS 15 or later + - On Linux, libstdc++.so.6 providing GLIBCXX_3.4.26 or later for the Corstone-320 FVP + - Python 3.12 with development headers and virtual-environment support - Familiarity with PyTorch model export and embedded cross-compilation # START generated_summary_faq @@ -40,7 +42,7 @@ generated_summary_faq: - question: How do I know my host is supported before I start? answer: >- Run the preflight check to verify macOS 15 or later on Apple silicon, or glibc 2.28 or later on Linux. - You'll also check the required tools, including `python3.12`, `git`, `cmake`, `c++`, and either `Ninja` or `Make`. + You'll also check `python3.12`, `git`, `c++`, `ninja`, `curl`, `pkg-config`, and `xz`. - question: Which directory should I run commands from during export and build? answer: >- Run commands from the `ExecuTorch` repository root. Activate your Python environment and source @@ -91,7 +93,7 @@ operatingsystems: further_reading: - resource: title: ExecuTorch MobileSAM prompt segmentation example - link: https://github.com/pytorch/executorch/tree/main/examples/arm/mobilesam_prompt_segmentation_example_ethos_u + link: https://github.com/pytorch/executorch/tree/fe93a8b0fc8d363fbb3bfa2aa18e253507fd13f8/examples/arm/mobilesam_prompt_segmentation_example_ethos_u type: repository - resource: title: ExecuTorch Arm Ethos-U backend documentation diff --git a/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/prepare-the-environment.md b/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/prepare-the-environment.md index 2d49958e58..56b7056332 100644 --- a/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/prepare-the-environment.md +++ b/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/prepare-the-environment.md @@ -8,7 +8,7 @@ layout: "learningpathall" ## Check your development machine -Run the following preflight check before downloading the source: +Use Python 3.12 with development headers and virtual-environment support. On Linux, also install the GLib and OpenMP runtime packages (`libglib2.0-0` and `libgomp1` on Ubuntu). Run the following preflight check before downloading the source: ```bash case "$(uname -s)/$(uname -m)" in @@ -21,7 +21,7 @@ case "$(uname -s)/$(uname -m)" in ;; esac -for tool in python3.12 git cmake c++; do +for tool in python3.12 git c++ ninja curl pkg-config xz; do command -v "$tool" >/dev/null || { echo "Missing required tool: $tool" >&2 exit 1 @@ -44,14 +44,8 @@ elif sys.platform.startswith("linux"): ) PY -if ! command -v ninja >/dev/null && ! command -v make >/dev/null; then - echo "Install Ninja or Make before continuing." >&2 - exit 1 -fi - python3.12 --version git --version -cmake --version | head -n 1 c++ --version | head -n 1 ``` @@ -59,12 +53,14 @@ The check confirms that you are using a supported Linux host or Apple silicon Ma - Python 3.12 - Git -- CMake - A host C++ compiler -- Ninja or Make +- Ninja +- curl, pkg-config, and xz The Python export dependencies require macOS 15 or later on Apple silicon, or glibc 2.28 or later on Linux. The pinned [TOSA tools](https://pypi.org/project/tosa-tools/2026.5.0/#files) set the macOS minimum, and the [PyTorch wheels](https://pypi.org/project/torch/2.14.0/#files) require the Linux glibc version. The preflight check verifies these requirements before you install the dependencies. +On Linux, the Corstone-320 FVP also requires `libstdc++.so.6` providing `GLIBCXX_3.4.26` or later. + ## Clone the ExecuTorch source Clone ExecuTorch, select the revision used by this Learning Path, and initialize its submodules: @@ -72,38 +68,49 @@ Clone ExecuTorch, select the revision used by this Learning Path, and initialize ```bash git clone https://github.com/pytorch/executorch.git cd executorch -git checkout 5c4d2c4a0150a0809bc77be9a67093f80f60a60f +git fetch origin fe93a8b0fc8d363fbb3bfa2aa18e253507fd13f8 +git checkout fe93a8b0fc8d363fbb3bfa2aa18e253507fd13f8 git submodule sync git submodule update --init --recursive ``` -Run the remaining commands from the ExecuTorch repository root. The pinned revision keeps the commands aligned with the [MobileSAM example source](https://github.com/pytorch/executorch/tree/5c4d2c4a0150a0809bc77be9a67093f80f60a60f/examples/arm/mobilesam_prompt_segmentation_example_ethos_u). +Run the remaining commands from the ExecuTorch repository root. The pinned revision keeps the commands aligned with the [MobileSAM example source](https://github.com/pytorch/executorch/tree/fe93a8b0fc8d363fbb3bfa2aa18e253507fd13f8/examples/arm/mobilesam_prompt_segmentation_example_ethos_u). ## Create a Python environment -Create and activate a Python 3.12 virtual environment: +Create and activate a fresh Python 3.12 virtual environment for this checkout: ```bash python3.12 -m venv .venv source .venv/bin/activate -python -m pip install --upgrade pip ``` -Install the current ExecuTorch checkout with its Ethos-U dependencies: +## Install ExecuTorch + +Build the Python package from the same checkout used by the example and native runner: ```bash -./install_executorch.sh --optional-dependency ethos_u +CMAKE_GENERATOR=Ninja ./install_executorch.sh --optional-dependency ethos_u ``` -Using the checkout's installer keeps the Python package aligned with the example source. +The installer supplies CMake, the model export dependencies, and the example requirements. It selects released PyTorch and TorchAO packages for the CPU workflow. ## Install the Arm development tools {{% notice Note %}} -Before you run the Arm setup command on macOS, install [Docker Desktop](/install-guides/docker/docker-desktop/) and follow the [AVH FVPs on macOS install guide](/install-guides/fvps-on-macos/). Add the FVPs-on-Mac `bin` directory to `PATH`. The wrapper runs the Linux Corstone-320 FVP in a container. Confirm that Docker is running and that `FVP_Corstone_SSE-320` resolves to the wrapper: +Before you run the Arm setup command on macOS, install [Docker Desktop](/install-guides/docker/docker-desktop/) and start Docker. Install the [FVPs-on-Mac wrappers](https://github.com/Arm-Examples/FVPs-on-Mac) at the revision for FVP `11.31.28`. The wrapper runs the Linux Corstone-320 FVP in a container. Run these commands from the ExecuTorch repository root: ```bash docker info >/dev/null +mkdir -p examples/arm/arm-scratch "$HOME/.armlm" +git clone https://github.com/Arm-Examples/FVPs-on-Mac.git \ + examples/arm/arm-scratch/FVPs-on-Mac +git -C examples/arm/arm-scratch/FVPs-on-Mac checkout --detach \ + cc7a4be5456db085619647811fac29e9795f806e +bash examples/arm/arm-scratch/FVPs-on-Mac/build.sh +export PATH="$PWD/examples/arm/arm-scratch/FVPs-on-Mac/bin:$PATH" +export FVP_MOUNT_DIR="$PWD" +FVP_Corstone_SSE-320 --version command -v FVP_Corstone_SSE-320 ``` {{% /notice %}} @@ -117,16 +124,30 @@ source examples/arm/arm-scratch/setup_path.sh ## Verify the environment -Confirm that Python imports ExecuTorch: +Check that the Python dependencies are consistent after Arm setup: + +```bash +python -m pip check +``` + +Confirm that the output is `No broken requirements found.` + +Check the ExecuTorch and Ethos-U imports used by the exporter: ```bash -python -c "import executorch; print('ExecuTorch import succeeded')" +python - <<'PYTHON' +import executorch.kernels.quantized +from executorch.backends.arm.ethosu import EthosUPartitioner +from executorch.backends.arm.quantizer import EthosUQuantizer + +print("ExecuTorch Ethos-U imports succeeded") +PYTHON ``` The output is similar to: ```output -ExecuTorch import succeeded +ExecuTorch Ethos-U imports succeeded ``` Check the target compiler and FVP: @@ -160,6 +181,14 @@ MobileSAM example: PASS The comparison image, `fvp_comparison.png`, is saved under `arm_test/mobilesam/result/`. After the script completes, continue to [Validate the MobileSAM segmentation result](../validate-the-results/) to inspect the artifacts. +To run the same workflow with simulated NPU counters, use timing mode: + +```bash +./examples/arm/mobilesam_prompt_segmentation_example_ethos_u/run.sh --timed +``` + +Timing mode uses a 7,200-second timeout; the default fast mode uses 300 seconds. CPU timing and CPU/NPU ratios remain unavailable on FVP. + To work through each stage manually, skip this script and continue to the next page. The following pages explain the same preparation, export, build, and validation steps. Continue only after each command succeeds; files from an earlier run can remain after a failed command. ## What you've accomplished and what's next diff --git a/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/run-the-example.md b/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/run-the-example.md index 558afb5ce1..85f10c3008 100644 --- a/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/run-the-example.md +++ b/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/run-the-example.md @@ -26,7 +26,7 @@ backends/arm/scripts/build_executor_runner.sh \ '--extra_build_flags=-DSEMIHOSTING=ON -DET_COMPILED_PTE=ON' ``` -The script builds the required ExecuTorch libraries and the runner for Corstone-320. `ET_COMPILED_PTE=ON` embeds `mobilesam.pte` in the Executable and Linkable Format (ELF) file named `arm_executor_runner`, under `arm_test/mobilesam/runner/`. +The script builds the required ExecuTorch libraries and the runner for Corstone-320. The model selected by `--pte` is embedded in the Executable and Linkable Format (ELF) file named `arm_executor_runner`, under `arm_test/mobilesam/runner/`. `SEMIHOSTING=ON` lets the simulated target read input tensors from host files and write its output tensors back to the host. @@ -69,6 +69,8 @@ backends/arm/scripts/run_fvp.sh \ The runner reads the tensor specified by `-i input.bin` and writes its first output tensor to `output-0.bin` under `arm_test/mobilesam/io/`. The console transcript is saved to `arm_test/mobilesam/fvp.log`; validation reads the tensor file. +`--fast` selects functional validation and reports CPU timing, CPU/NPU ratios, and NPU performance monitoring unit (PMU) counters as `N/A`. To collect simulated NPU counters, omit `--fast` and set `--timeout=7200`. CPU timing and CPU/NPU ratios remain unavailable; the simulated NPU counters are not measurements of real-device latency. + Confirm that the run created the output file before continuing: ```bash diff --git a/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/validate-the-results.md b/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/validate-the-results.md index 03caa505f7..d4ee4e20e3 100644 --- a/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/validate-the-results.md +++ b/content/learning-paths/embedded-and-microcontrollers/deploy-mobilesam-on-ethos-u/validate-the-results.md @@ -36,15 +36,6 @@ python3 -m json.tool arm_test/mobilesam/export/metrics.json The `fp32_quantized_iou` value must also be at least `0.9`. -A reference run at the pinned commit produced these scores: - -| Comparison | IoU | -| --- | --- | -| Floating-point and quantized host masks | 0.9550 | -| FVP and quantized host masks | 0.9809 | - -This run used Python 3.12.13, PyTorch 2.14.0, Vela 5.1.0, and Corstone-320 FVP 11.31.28 on macOS 26.6.2 with Apple silicon. Treat these scores as examples; use the two `0.9` thresholds to check your own run. - ## Confirm Ethos-U delegation Display the delegation report written during export: @@ -53,16 +44,20 @@ Display the delegation report written during export: sed -n '1,120p' arm_test/mobilesam/export/delegation.txt ``` -Confirm that `Total delegated subgraphs` is `1`. The exporter checks this before writing the `.pte`. The reference run above produced: +Confirm that `Total delegated subgraphs` is `1`. The exporter checks this before writing the `.pte`. The output includes: ```output Total delegated subgraphs: 1 -Number of delegated nodes: 5076 -Number of non-delegated nodes: 3 ``` Node counts can vary with dependency versions. Use the values from your export when reporting delegation. +In `arm_test/mobilesam/fvp.log`, also confirm `NPU delegations: 1`. This count records a completed NPU invocation at runtime; the export report shows how the model was partitioned. + +{{% notice Note %}} +The FVP reports CPU timing and CPU/NPU ratios as `N/A`. Fast mode also marks NPU PMU counters as unavailable; timing mode reports simulated NPU counters. Use the delegation count and mask comparison to validate execution. Simulated counters are not measurements of real-device latency. +{{% /notice %}} + ## Inspect the visual result Open `fvp_comparison.png` under `arm_test/mobilesam/result/` in an image viewer. The image contains three panels: diff --git a/content/learning-paths/embedded-and-microcontrollers/device-connect-server/_index.md b/content/learning-paths/embedded-and-microcontrollers/device-connect-server/_index.md index e5a6a8042d..241795bd40 100644 --- a/content/learning-paths/embedded-and-microcontrollers/device-connect-server/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/device-connect-server/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy multi-network device meshes using Device Connect server and NATS +title: Deploy device meshes with Device Connect and NATS description: Connect an example edge device, secondary devices, and AI agents across networks using Device Connect server. Learn to provision NATS credentials, commission devices, manage persistent registry, and orchestrate multi-network IoT fleets with secure authentication. minutes_to_complete: 30 diff --git a/content/learning-paths/embedded-and-microcontrollers/edge_impulse_greengrass/_index.md b/content/learning-paths/embedded-and-microcontrollers/edge_impulse_greengrass/_index.md index c4147ae5ae..749d34d476 100644 --- a/content/learning-paths/embedded-and-microcontrollers/edge_impulse_greengrass/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/edge_impulse_greengrass/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy ML models to Arm edge devices using Edge Impulse and AWS IoT Greengrass +title: Deploy Edge Impulse models with AWS IoT Greengrass on Arm description: Learn how to deploy Edge Impulse ML models to Arm-based Linux edge devices using AWS IoT Greengrass custom components. diff --git a/content/learning-paths/embedded-and-microcontrollers/edge_impulse_greengrass/hardwaresetupec2.md b/content/learning-paths/embedded-and-microcontrollers/edge_impulse_greengrass/hardwaresetupec2.md index 0d10149e44..186cf02b14 100644 --- a/content/learning-paths/embedded-and-microcontrollers/edge_impulse_greengrass/hardwaresetupec2.md +++ b/content/learning-paths/embedded-and-microcontrollers/edge_impulse_greengrass/hardwaresetupec2.md @@ -1,6 +1,6 @@ --- hide_from_navpane: true -title: Set up an Amazon EC2 instance to simulate an Edge Impulse Greengrass device +title: Set up an Amazon EC2 instance as an Edge Impulse Greengrass device description: Launch an Arm-based Amazon EC2 instance running Ubuntu and install the dependencies needed to simulate an Edge Impulse Greengrass device. ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/embedded-and-microcontrollers/edge_impulse_greengrass/overview.md b/content/learning-paths/embedded-and-microcontrollers/edge_impulse_greengrass/overview.md index 68665313b9..43dc772c73 100644 --- a/content/learning-paths/embedded-and-microcontrollers/edge_impulse_greengrass/overview.md +++ b/content/learning-paths/embedded-and-microcontrollers/edge_impulse_greengrass/overview.md @@ -1,5 +1,5 @@ --- -title: Understand the Edge Impulse and AWS IoT Greengrass deployment architecture +title: Explore Edge Impulse and AWS IoT Greengrass architecture description: Understand how Edge Impulse models are packaged as AWS IoT Greengrass components for deployment to Arm-based edge devices. weight: 2 diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/_index.md b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/_index.md index e74a90fd57..37b1a7cfd6 100644 --- a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/_index.md @@ -1,5 +1,5 @@ --- -title: Build and run an image classification NN model on an STM32L4 Discovery board +title: Run an image classification model on STM32L4 Discovery description: Develop a image classification neural network model and deploy it on an STM32 B-L475E-IOT01A2 board. diff --git a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/build_nn.md b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/build_nn.md index 390f9818fc..18941c4d4f 100644 --- a/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/build_nn.md +++ b/content/learning-paths/embedded-and-microcontrollers/img_nn_stcube/build_nn.md @@ -1,6 +1,6 @@ --- # User change -title: Build an image classification NN model trained with the CIFAR-10 dataset +title: Train a CIFAR-10 image classifier for STM32L4 Discovery description: Train an image classification model with the CIFAR-10 dataset and prepare it for deployment with STM32 tools. weight: 3 # 1 is first, 2 is second, etc. diff --git a/content/learning-paths/embedded-and-microcontrollers/linux-on-fvp/_index.md b/content/learning-paths/embedded-and-microcontrollers/linux-on-fvp/_index.md index 2938a04c47..a1cb5e2bc9 100644 --- a/content/learning-paths/embedded-and-microcontrollers/linux-on-fvp/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/linux-on-fvp/_index.md @@ -1,5 +1,5 @@ --- -title: Debug Trusted Firmware-A and the Linux kernel on Arm FVP with Arm Development Studio +title: Debug TF-A and Linux on Arm FVP with Arm Development Studio description: Learn how to boot a Linux software stack on Arm Fixed Virtual Platforms (FVPs), then debug Trusted Firmware-A and the Linux kernel using Arm Development Studio. diff --git a/content/learning-paths/embedded-and-microcontrollers/observing-ethos-u-on-alif/5-prepare-firmware-artifacts.md b/content/learning-paths/embedded-and-microcontrollers/observing-ethos-u-on-alif/5-prepare-firmware-artifacts.md index 8e673a7f83..792805f286 100644 --- a/content/learning-paths/embedded-and-microcontrollers/observing-ethos-u-on-alif/5-prepare-firmware-artifacts.md +++ b/content/learning-paths/embedded-and-microcontrollers/observing-ethos-u-on-alif/5-prepare-firmware-artifacts.md @@ -1,5 +1,5 @@ --- -title: Prepare the ExecuTorch model and static libraries for the Alif E8 CMSIS project +title: Prepare ExecuTorch artifacts for the Alif E8 CMSIS project description: Prepare the ExecuTorch `.pte` model, C header, and static libraries for integration into an Alif E8 CMSIS project. weight: 6 layout: learningpathall diff --git a/content/learning-paths/embedded-and-microcontrollers/observing-ethos-u-on-alif/_index.md b/content/learning-paths/embedded-and-microcontrollers/observing-ethos-u-on-alif/_index.md index be5d666a8a..ebc1a94317 100644 --- a/content/learning-paths/embedded-and-microcontrollers/observing-ethos-u-on-alif/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/observing-ethos-u-on-alif/_index.md @@ -1,5 +1,5 @@ --- -title: Run MNIST on an Alif E8 Ensemble DevKit using ExecuTorch and Ethos-U85 +title: Run MNIST with ExecuTorch and Ethos-U85 on Alif E8 description: Run MNIST digit classification on an Alif Ensemble E8 DevKit with ExecuTorch and Ethos-U85 NPU acceleration, from setup through firmware deployment. minutes_to_complete: 45 diff --git a/content/learning-paths/embedded-and-microcontrollers/observing-ethos-u-on-nxp/_index.md b/content/learning-paths/embedded-and-microcontrollers/observing-ethos-u-on-nxp/_index.md index de1e68acf6..7b84159e6a 100644 --- a/content/learning-paths/embedded-and-microcontrollers/observing-ethos-u-on-nxp/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/observing-ethos-u-on-nxp/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy ExecuTorch firmware on NXP FRDM i.MX 93 for Ethos-U65 acceleration +title: Deploy ExecuTorch on NXP FRDM i.MX 93 with Ethos-U65 description: Learn how to bring up ExecuTorch executor_runner firmware on the NXP FRDM i.MX 93 Cortex-M33 using Linux RemoteProc, compile .pte models for Ethos-U65, and run inference with NPU acceleration. diff --git a/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/1-understand-the-pipeline.md b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/1-understand-the-pipeline.md new file mode 100644 index 0000000000..305bea8742 --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/1-understand-the-pipeline.md @@ -0,0 +1,75 @@ +--- +title: "Understand the object detection pipeline" +description: Understand the YOLOX detection pipeline and how to measure preprocessing with KleidiCV on arm64 Linux. + +weight: 2 + +layout: "learningpathall" +--- + +## Understand the components + +You'll deploy a YOLOX object detection application on an arm64 Linux board and measure its image operations and full preprocessing latency. You'll compare two OpenCV builds: a baseline without KleidiCV and a KleidiCV build using its default dispatch policy. A Radxa Orion O6 running 64-bit Debian is one suitable platform. + +The application combines four components: + +- **YOLOX** is the object detection model. +- **ONNX** is the file format used to store the trained model. +- **OpenCV DNN** loads the ONNX file and runs the neural network. +- **KleidiCV** accelerates supported OpenCV image-processing operations on Arm CPUs. + +## What is YOLOX? + +You'll use the pretrained YOLOX-S object detection model from OpenCV Zoo in Open Neural Network Exchange (ONNX) format. Trained on the COCO dataset, it recognizes 80 object classes and predicts a bounding box, class, and confidence score for each detected object. You run inference without training the model. + +## Understand the image-processing pipeline + +The application processes the image in these stages: + +![YOLOX pipeline grouped into image preprocessing, model inference, and output processing. Resize and the two conversions are marked as eligible for KleidiCV HAL acceleration, while padding and tensor packing remain part of full preprocessing.#center](pipeline.svg "YOLOX processing stages and KleidiCV-eligible operations") + +Non-maximum suppression removes duplicate detections before the application draws bounding boxes. + +The *letterbox* operation resizes the image while preserving its aspect ratio and pads the unused area so that the result is `640 x 640`, which is the YOLOX input size. The application then converts the OpenCV BGR image to RGB and changes the element type from unsigned 8-bit integers to 32-bit floating-point values. Pixel values remain in the `0` to `255` range expected by this YOLOX model. + +OpenCV packs the prepared image into an NCHW tensor with this shape: + +```text +[1, 3, 640, 640] +``` + +The dimensions represent one image, three color channels, a height of 640 pixels, and a width of 640 pixels. + +## Understand where KleidiCV is used + +OpenCV integrates KleidiCV through its Hardware Acceleration Layer (HAL), so you continue to call standard OpenCV functions. Acceleration depends on the build configuration, operation parameters, data type, and CPU features. + +The application uses explicit OpenCV image-processing calls so their costs can be measured separately: + +```cpp +cv::resize(image, resized, resizedSize, 0.0, 0.0, cv::INTER_LINEAR); +cv::copyMakeBorder(resized, padded, 0, bottom, 0, right, + cv::BORDER_CONSTANT, cv::Scalar::all(114)); +cv::cvtColor(padded, rgb, cv::COLOR_BGR2RGB); +rgb.convertTo(floatImage, CV_32F); +``` + +The final `blobFromImage()` call only packs the tensor; it does not resize, swap channels, or scale values. Tensor packing belongs to OpenCV DNN, not the KleidiCV HAL. + +KleidiCV does not directly run the neural network. The OpenCV DNN call remains: + +```cpp +network.setInput(blob); +network.forward(outputs, network.getUnconnectedOutLayersNames()); +``` + +The benchmark distinguishes two preprocessing metrics: + +- *Image operations subtotal* (primary): Resize, BGR-to-RGB conversion, and uint8-to-float32 conversion, the operations eligible for KleidiCV HAL acceleration +- *Full preprocessing*: All three operations plus padding and tensor packing + +Individual preprocessing operations and DNN inference are also timed separately. + +## What you've learned and what's next + +You've identified the role of each component and the explicit preprocessing, inference, and output stages of the application. Next, you'll build matching baseline and KleidiCV installations to measure preprocessing and pipeline behavior. diff --git a/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/2-build-opencv.md b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/2-build-opencv.md new file mode 100644 index 0000000000..05ffe925c2 --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/2-build-opencv.md @@ -0,0 +1,226 @@ +--- +title: "Build OpenCV with and without KleidiCV" +description: Compile two matching OpenCV installations on arm64 Linux to compare baseline preprocessing with the default KleidiCV configuration. + +weight: 3 + +layout: "learningpathall" +--- + +## Check the arm64 environment + +Confirm that the board runs a 64-bit Arm userspace: + +```bash +uname -m +``` + +The expected output is: + +```output +aarch64 +``` + +Record the operating system, kernel, CPU topology, and CPU features: + +```bash +cat /etc/os-release +uname -r +lscpu +``` + +Keep this information with your benchmark results. KleidiCV selects supported implementations for the available CPU features. + +{{% notice Reference platform %}} +The reference measurements in this Learning Path were collected on a +[Radxa Orion O6](https://radxa.com/products/orion/o6/) running Debian 12. +The board has eight Cortex-A720 CPUs and four Cortex-A520 CPUs. You can use +another arm64 Linux system, but CPU topology, supported extensions, and +performance results will differ. +{{% /notice %}} + +## Install the build tools + +Install the compiler, CMake, Ninja, Git, and image codec development packages: + +```bash +sudo apt update +sudo apt install -y \ + build-essential \ + ca-certificates \ + cmake \ + file \ + git \ + libjpeg-dev \ + libpng-dev \ + libtiff-dev \ + ninja-build \ + pkg-config \ + util-linux \ + wget +``` + +Record the compiler and CMake versions: + +```bash +c++ --version +cmake --version +``` + +Use GCC 10 or later, or Clang 12 or later. If compilation exhausts memory, reduce the `--parallel` value in the build commands. + +## Download OpenCV + +Create a workspace and check out OpenCV 4.13.0: + +```bash +mkdir -p ~/opencv-kleidicv-lab +cd ~/opencv-kleidicv-lab +git clone --branch 4.13.0 --depth 1 https://github.com/opencv/opencv.git +``` + +## Download KleidiCV 26.06 + +Clone the KleidiCV 26.06 release: + +```bash +cd ~/opencv-kleidicv-lab +git clone --branch 26.06 --depth 1 \ + https://gitlab.arm.com/kleidi/kleidicv.git +``` + +Confirm the selected release: + +```bash +git -C kleidicv describe --tags --exact-match +``` + +The expected output is: + +```output +26.06 +``` + +OpenCV 4.13.0 downloads KleidiCV 0.7.0 when `KLEIDICV_SOURCE_PATH` is not provided. This Learning Path selects KleidiCV 26.06 explicitly because it supports the three-channel, arbitrary-ratio resize used by the letterbox stage. + +Both builds use the same OpenCV source tree, compiler, and common CMake options. The intended difference is enabling KleidiCV with its default dispatch policy. + +{{% notice Experiment scope %}} +Leave OpenCV 4.13.0 unpatched in both builds. The KleidiCV 26.06 OpenCV integration patch adds a `copyMakeBorder()` HAL hook that is absent from this OpenCV release. This experiment compares preprocessing without that patch; padding therefore remains an OpenCV operation. Applying the patch creates a different experiment and requires new measurements. +{{% /notice %}} + +## Build the baseline OpenCV installation + +Configure OpenCV without KleidiCV: + +```bash +cd ~/opencv-kleidicv-lab + +cmake -S opencv -B build-baseline -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$HOME/opencv-kleidicv-lab/install-baseline \ + -DBUILD_LIST=core,imgproc,imgcodecs,dnn \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_opencv_apps=OFF \ + -DBUILD_PERF_TESTS=OFF \ + -DBUILD_TESTS=OFF \ + -DBUILD_SHARED_LIBS=ON \ + -DWITH_IPP=OFF \ + -DWITH_OPENCL=OFF \ + -DWITH_KLEIDICV=OFF +``` + +Build and install it: + +```bash +cmake --build build-baseline --parallel "$(nproc)" +cmake --install build-baseline +``` + +List the installed OpenCV libraries: + +```bash +find install-baseline/lib -maxdepth 1 -type l -name 'libopencv_*.so' \ + | sort +``` + +The expected output is: + +```output +install-baseline/lib/libopencv_core.so +install-baseline/lib/libopencv_dnn.so +install-baseline/lib/libopencv_imgcodecs.so +install-baseline/lib/libopencv_imgproc.so +``` + +Confirm the baseline setting: + +```bash +grep '^WITH_KLEIDICV' build-baseline/CMakeCache.txt +``` + +The expected output is: + +```output +WITH_KLEIDICV:BOOL=OFF +``` + +## Build OpenCV with KleidiCV + +Configure the second build with KleidiCV enabled. Leave its dispatch options at their defaults: + +```bash +cd ~/opencv-kleidicv-lab + +cmake -S opencv -B build-kleidicv-default -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$HOME/opencv-kleidicv-lab/install-kleidicv-default \ + -DBUILD_LIST=core,imgproc,imgcodecs,dnn \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_opencv_apps=OFF \ + -DBUILD_PERF_TESTS=OFF \ + -DBUILD_TESTS=OFF \ + -DBUILD_SHARED_LIBS=ON \ + -DWITH_IPP=OFF \ + -DWITH_OPENCL=OFF \ + -DWITH_KLEIDICV=ON \ + -DKLEIDICV_SOURCE_PATH=$HOME/opencv-kleidicv-lab/kleidicv +``` + +Build and install it: + +```bash +cmake --build build-kleidicv-default --parallel "$(nproc)" +cmake --install build-kleidicv-default +``` + +Confirm that KleidiCV is enabled: + +```bash +grep '^WITH_KLEIDICV' build-kleidicv-default/CMakeCache.txt +``` + +The expected output is: + +```output +WITH_KLEIDICV:BOOL=ON +``` + +Confirm that the KleidiCV libraries were built: + +```bash +find build-kleidicv-default/3rdparty/lib -maxdepth 1 -type f \ + -name 'libkleidicv*.a' -printf '%f\n' | sort +``` + +The expected output is: + +```output +libkleidicv.a +libkleidicv_hal.a +libkleidicv_thread.a +``` + +## What you've accomplished and what's next + +You've created baseline and default KleidiCV 26.06 OpenCV installations from the same unpatched source. Next, you'll download the YOLOX model and a static test image. diff --git a/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/3-prepare-inputs.md b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/3-prepare-inputs.md new file mode 100644 index 0000000000..8db1cb7099 --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/3-prepare-inputs.md @@ -0,0 +1,109 @@ +--- +title: "Prepare the YOLOX model and test image" +description: Download and verify the OpenCV Zoo YOLOX ONNX model, class labels, and a static image for object detection. + +weight: 4 + +layout: "learningpathall" +--- + +## Create the application directory + +Create a directory for the source code, model, labels, and test image: + +```bash +cd ~/opencv-kleidicv-lab +mkdir -p static-detection +cd static-detection +``` + +## Download the YOLOX model + +Download the FP32 YOLOX-S model from OpenCV Zoo: + +```bash +wget -O object_detection_yolox_2022nov.onnx \ + https://github.com/opencv/opencv_zoo/raw/main/models/object_detection_yolox/object_detection_yolox_2022nov.onnx +``` + +Verify that Git Large File Storage returned the model rather than its small pointer file: + +```bash +ls -lh object_detection_yolox_2022nov.onnx +sha256sum object_detection_yolox_2022nov.onnx +``` + +The model is approximately 35 MB. The expected SHA-256 value is: + +```output +c5c2d13e59ae883e6af3b45daea64af4833a4951c92d116ec270d9ddbe998063 object_detection_yolox_2022nov.onnx +``` + +{{% notice Note %}} +The model and the YOLOX files in OpenCV Zoo are licensed under the Apache License 2.0. See the model directory linked in the further reading resources for details. +{{% /notice %}} + +## Download the COCO class names + +The model recognizes 80 classes from the COCO dataset. Download the label file published with this Learning Path: + +```bash +wget -O coco.names \ + https://learn.arm.com/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/coco.names +``` + +If you're following a preview of this Learning Path, replace `https://learn.arm.com` in the attachment download commands with the preview site's origin, including its port if needed. Use the same site when downloading the application source in the next section. + +Count the labels: + +```bash +wc -l coco.names +``` + +The expected output is: + +```output +80 coco.names +``` + +## Download a static test image + +Download the original football photograph from the OpenCV 4.13.0 sample data: + +```bash +wget -O input.jpg \ + https://raw.githubusercontent.com/opencv/opencv/4.13.0/samples/data/messi5.jpg +``` + +This image has no segmentation overlay. Verify that you downloaded the same image used for the reference measurements: + +```bash +sha256sum input.jpg +``` + +The expected output is: + +```output +1d570e49654e84c7a943918537bd9e5e1ef82920152e147c834006e235be97c9 input.jpg +``` + +Confirm that all three inputs are present: + +```bash +file object_detection_yolox_2022nov.onnx coco.names input.jpg +``` + +The output identifies the ONNX model as data, the labels as text, and `input.jpg` as a JPEG image. You can replace `input.jpg` with your own photograph later. + +The supplied image contains a football player and ball: + +![Input photograph of a football player kicking a ball, used to test YOLOX object detection#center](detection-input.jpg "YOLOX static-image detection input") + +The image is distributed in the +[OpenCV sample data directory](https://github.com/opencv/opencv/tree/4.13.0/samples/data). +For licensing information, see the +[OpenCV license](https://github.com/opencv/opencv/blob/4.13.0/LICENSE). + +## What you've accomplished and what's next + +You've prepared a verified YOLOX ONNX model, its 80 COCO labels, and a repeatable input image. Next, you'll compile the static-image application and verify its detection output. diff --git a/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/4-run-detection.md b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/4-run-detection.md new file mode 100644 index 0000000000..6ecc79265c --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/4-run-detection.md @@ -0,0 +1,132 @@ +--- +title: "Compile and run static-image detection" +description: Run the YOLOX C++ application with baseline and default KleidiCV OpenCV builds and compare the labeled detection results. + +weight: 5 + +layout: "learningpathall" +--- + +## Download the application source + +The example application is stored with this Learning Path. Download the C++ source and CMake project: + +```bash +cd ~/opencv-kleidicv-lab/static-detection + +wget -O yolox_static.cpp \ + https://learn.arm.com/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/yolox_static.cpp + +wget -O CMakeLists.txt \ + https://learn.arm.com/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/cmakelists.txt +``` + +## Find the pipeline in the source + +Use these functions in `yolox_static.cpp` to connect the pipeline stages to their implementation: + +| Code | Role | +|---|---| +| `runPipeline()` | Calls the explicit preprocessing operations, then feeds the tensor to `network.setInput()` and runs inference with `network.forward()` | +| `postprocess()` | Decodes the model output, maps boxes to the original image, and applies non-maximum suppression | +| `renderDetections()` | Draws the retained boxes, class labels, and confidence scores | +| `main()` | Loads the inputs, selects the OpenCV CPU backend, runs warm-up and measured iterations, and prints the timing summaries | + +Within `runPipeline()`, pairs of `Clock::now()` calls surround each stage and store its elapsed time in `StageTimes`. The inference timer includes input setup and the forward pass. Model loading, image-file decoding, and the final JPEG write are outside these timers. + +## Build against the baseline OpenCV installation + +Configure the application with the baseline OpenCV package: + +```bash +cmake -S . -B app-build-baseline -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DOpenCV_DIR=$HOME/opencv-kleidicv-lab/install-baseline/lib/cmake/opencv4 +cmake --build app-build-baseline +``` + +Use `Release` for both application builds, not just for the OpenCV libraries. This enables compiler optimization for application code such as output decoding and postprocessing. + +Run one measured iteration after two warm-up iterations: + +```bash +LD_LIBRARY_PATH=$HOME/opencv-kleidicv-lab/install-baseline/lib \ + ./app-build-baseline/yolox_static \ + --model=object_detection_yolox_2022nov.onnx \ + --image=input.jpg \ + --labels=coco.names \ + --output=result-baseline.jpg \ + --build-name=baseline \ + --warmup=2 \ + --iterations=1 +``` + +The output is similar to: + +```output +Build label: baseline +OpenCV version: 4.13.0 +OpenCV threads: 12 +Warm-up iterations: 2 +Measured iterations: 1 +Detections: 3 +Resize: mean= 0.503 ms min= 0.503 ms stddev= 0.000 ms +Padding: mean= 0.048 ms min= 0.048 ms stddev= 0.000 ms +BGR to RGB: mean= 0.041 ms min= 0.041 ms stddev= 0.000 ms +Uint8 to float32: mean= 1.154 ms min= 1.154 ms stddev= 0.000 ms +Tensor packing: mean= 1.266 ms min= 1.266 ms stddev= 0.000 ms +Preprocessing: mean= 3.012 ms min= 3.012 ms stddev= 0.000 ms +DNN inference: mean= 356.052 ms min= 356.052 ms stddev= 0.000 ms +Postprocessing: mean= 0.484 ms min= 0.484 ms stddev= 0.000 ms +Rendering: mean= 0.149 ms min= 0.149 ms stddev= 0.000 ms +End-to-end: mean= 359.696 ms min= 359.696 ms stddev= 0.000 ms +Output image: result-baseline.jpg +``` + +Timing values depend on the board and input image. With one measured iteration, the mean and minimum are equal and the standard deviation is zero. + +## Inspect the result + +The reference output contains three retained detections: two `person` detections and one `sports ball` detection. One person box covers a partially visible person in the background. + +![YOLOX output with two person bounding boxes and one sports ball bounding box drawn on the original photograph. Each label includes the model confidence score.#center](detection-result.jpg "YOLOX detection output with bounding boxes and confidence scores") + +Check the generated image: + +```bash +file result-baseline.jpg +ls -lh result-baseline.jpg +``` + +## Build against the KleidiCV installation + +Build the same application source against the default KleidiCV OpenCV package: + +```bash +cmake -S . -B app-build-kleidicv-default -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DOpenCV_DIR=$HOME/opencv-kleidicv-lab/install-kleidicv-default/lib/cmake/opencv4 +cmake --build app-build-kleidicv-default +``` + +Run the functional check: + +```bash +LD_LIBRARY_PATH=$HOME/opencv-kleidicv-lab/install-kleidicv-default/lib \ + ./app-build-kleidicv-default/yolox_static \ + --model=object_detection_yolox_2022nov.onnx \ + --image=input.jpg \ + --labels=coco.names \ + --output=result-kleidicv-default.jpg \ + --build-name=kleidicv-default \ + --warmup=2 \ + --iterations=1 +``` + +Both builds should report three detections for the supplied image. Open `result-baseline.jpg` and `result-kleidicv-default.jpg` and compare the classes and bounding boxes with the reference image. If your board has no desktop, copy the files to your workstation to view them. + +The results should contain two `person` detections and one `sports ball` detection with equivalent bounding boxes. Small numerical and JPEG encoding differences are possible, so byte-for-byte image equality is not required. A matching detection count alone is not a complete correctness check. + +## What you've accomplished and what's next + +You've run YOLOX static-image detection with both OpenCV builds and checked the labeled output images. Next, you'll compare image-operation and full preprocessing latency and interpret the pipeline-level effect. diff --git a/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/5-benchmark.md b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/5-benchmark.md new file mode 100644 index 0000000000..e15de3b33a --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/5-benchmark.md @@ -0,0 +1,192 @@ +--- +title: "Evaluate KleidiCV preprocessing and pipeline performance" +description: Compare image-operation and full preprocessing latency between baseline and default KleidiCV builds, then relate the gains to overall YOLOX pipeline performance. + +weight: 6 + +layout: "learningpathall" +--- + +## Prepare the board for measurement + +Performance measurements on development boards can vary because of background activity, dynamic CPU frequency, and temperature. Close unnecessary applications and allow the board to cool before each run. + +Use the same: + +- Board and operating system +- OpenCV source revision and compiler +- Release build configuration for both OpenCV and the application +- YOLOX model and static image +- Warm-up and measured iteration counts +- OpenCV thread count +- CPU affinity +- CPU frequency governor + +The examples use four OpenCV threads. Change this value if your board has fewer than four available CPU cores. + +Inspect the available CPUs and choose one fixed set for every run: + +```bash +lscpu -e=CPU,MAXMHZ,ONLINE +``` + +Choose CPUs of the same core type using your board's topology information. +On a heterogeneous system, don't mix performance and efficiency cores. +The maximum frequency alone does not identify the core type. Set `CPU_LIST` +to your chosen CPUs; the reference measurement used four Cortex-A720 CPUs: + +```bash +export CPU_LIST=5,6,9,11 +``` + +Do not copy this value without checking your system. CPU numbering and heterogeneous core placement vary by platform. + +Set the benchmark parameters used throughout this section: + +```bash +set -o pipefail +export THREADS=4 +export WARMUP=10 +export ITERATIONS=100 +export ROUND=1 +``` + +Keep the CPU frequency governor unchanged across both builds and all rounds. The reference measurement used `performance`; use the same governor if you want to compare with those results. Only change the frequency policy with permission, and restore the original settings afterward. On platforms without Linux CPU frequency governors, keep the platform's frequency-management settings consistent instead. + +## Run the benchmarks + +Change to the application directory: + +```bash +cd ~/opencv-kleidicv-lab/static-detection +``` + +Run the baseline benchmark: + +```bash +LD_LIBRARY_PATH=$HOME/opencv-kleidicv-lab/install-baseline/lib \ + taskset -c "$CPU_LIST" ./app-build-baseline/yolox_static \ + --model=object_detection_yolox_2022nov.onnx \ + --image=input.jpg \ + --labels=coco.names \ + --output=benchmark-baseline.jpg \ + --build-name=baseline \ + --threads="$THREADS" \ + --warmup="$WARMUP" \ + --iterations="$ITERATIONS" | tee "baseline-round-$ROUND.txt" +``` + +Run the default KleidiCV benchmark: + +```bash +LD_LIBRARY_PATH=$HOME/opencv-kleidicv-lab/install-kleidicv-default/lib \ + taskset -c "$CPU_LIST" ./app-build-kleidicv-default/yolox_static \ + --model=object_detection_yolox_2022nov.onnx \ + --image=input.jpg \ + --labels=coco.names \ + --output=benchmark-kleidicv-default.jpg \ + --build-name=kleidicv-default \ + --threads="$THREADS" \ + --warmup="$WARMUP" \ + --iterations="$ITERATIONS" | tee "kleidicv-default-round-$ROUND.txt" +``` + +Each run reports the mean, minimum, and standard deviation for every stage. Confirm that both builds report three detections for the supplied image before comparing their performance. + +Use the image operations subtotal as your primary metric. Full preprocessing, printed as `Preprocessing`, also includes padding and tensor packing. Compare both with DNN inference and End-to-end latency to assess the application-level impact. + +For a more stable comparison, run six rounds, executing each build once per round and alternating their order: + +| Rounds | Execution order | +|---|---| +| 1, 3, and 5 | Baseline, KleidiCV | +| 2, 4, and 6 | KleidiCV, baseline | + +Before each new round, set `ROUND` to its number, for example `export ROUND=2`. This preserves each run's log. Keep all other settings unchanged. If a command fails, resolve the error and repeat the affected round before comparing results. + +After all six rounds, extract the detection counts and main stage timings from the logs: + +```bash +grep -E \ + '^(Build label|Detections|Preprocessing|Resize|BGR to RGB|Uint8 to float32|DNN inference|End-to-end)' \ + baseline-round-*.txt kleidicv-default-round-*.txt +``` + +The application does not print the image operations subtotal directly. For each run, add the reported means for `Resize`, `BGR to RGB`, and `Uint8 to float32`. Then take the median of those six per-run subtotals for each build. Don't add three independently calculated stage medians. + +For full preprocessing and the individual stages, take the median of the six reported means. Keep all 12 logs to examine run-to-run variation; a single minimum is not representative of the whole application. + +Calculate the KleidiCV build's latency reduction relative to baseline using those medians: + +```text +reduction (%) = (baseline time - optimized time) / baseline time * 100 +``` + +A positive result means the optimized build took less time; a negative result means it took longer. Report the measured values even when the difference is small or negative. + +## Review a reference measurement + +The table reports baseline and default KleidiCV results from a six-round measurement session on 29 October 2026. Both applications were built from the same source. + +The reference measurement used: + +- Unpatched OpenCV 4.13.0 and KleidiCV 26.06 +- A Radxa Orion O6 running Debian 12, with eight Cortex-A720 CPUs and four Cortex-A520 CPUs +- Linux kernel `6.12.111+deb12-arm64`, GCC `12.2.0`, and CMake `3.25.1` +- OpenCV and the application compiled in Release mode, with compiler optimizations enabled +- Four Cortex-A720 CPUs (`5,6,9,11`) with the `performance` frequency governor: 2.3 GHz for CPUs `5,6`, 2.4 GHz for CPU `9`, and 2.5 GHz for CPU `11` +- The original OpenCV `messi5.jpg` photograph supplied in this Learning Path, with no segmentation overlay: `548 x 342` pixels and three channels, resized to `640 x 399` and padded to `640 x 640` +- Six rounds per configuration, each run using four OpenCV threads, 10 warm-up iterations, and 100 measured iterations + +{{% notice Key result %}} +The image operations subtotal fell from 1.660 ms to 0.883 ms with default +KleidiCV dispatch, a 46.8% reduction. Full preprocessing fell from 2.494 ms +to 2.058 ms, a 17.5% reduction. The pipeline median fell by only 0.3%, +within run-to-run variation. +{{% /notice %}} + +The table compares median latencies across six runs per build: + +| Stage | Baseline | KleidiCV default | Latency reduction | +|---|---:|---:|---:| +| **Image operations subtotal** | **1.660 ms** | **0.883 ms** | **46.8%** | +| Full preprocessing | 2.494 ms | 2.058 ms | 17.5% | +| Resize | 0.380 ms | 0.149 ms | 60.8% | +| Padding | 0.048 ms | 0.049 ms | -2.1% | +| BGR to RGB | 0.039 ms | 0.043 ms | -10.3% | +| Uint8 to float32 | 1.241 ms | 0.692 ms | 44.3% | +| Tensor packing | 0.761 ms | 1.244 ms | -63.5% | +| DNN inference | 309.995 ms | 309.383 ms | 0.2% | +| Postprocessing | 0.488 ms | 0.486 ms | 0.3% | +| Rendering | 0.148 ms | 0.149 ms | -1.0% | +| End-to-end | 313.206 ms | 312.152 ms | 0.3% | + +Values are rounded for display; reductions use the unrounded medians. Each median is calculated independently, so stage medians do not necessarily sum to the median total. + +The six-run preprocessing means ranged from `2.337` to `3.002` ms for baseline and `1.643` to `2.267` ms for KleidiCV. Pipeline means ranged from `312.674` to `314.673` ms and `311.214` to `322.207` ms respectively. The pipeline ranges overlap, so the small median reduction is not evidence of a consistent pipeline-level speedup. All six runs were retained, and both builds detected three objects with equivalent classes and bounding boxes. + +## Interpret the measurements + +Resize and type conversion account for the image-operation gains. These API-stage timings include buffer allocation and memory access, so they do not measure kernel-only speedups. BGR-to-RGB conversion did not improve but remains included because it is eligible for KleidiCV HAL acceleration. + +Slower tensor packing partially offsets these gains in full preprocessing. Padding remains an OpenCV operation in this unpatched build, and OpenCV DNN's `blobFromImage()` performs tensor packing. Neither is a KleidiCV HAL operation here, so don't attribute the tensor-packing timing change to a KleidiCV kernel. + +DNN inference dominates the workload and is not directly accelerated by KleidiCV. Its timer includes input setup and `net.forward()`. + +The reported End-to-end value sums the timed in-memory stages, including rendering. It excludes model loading, image-file decoding, and output-file writing, so it is not file-to-file wall-clock latency. + +## Understand the resize speedup + +For the reference image, you resize three-channel uint8 data from `548 x 342` to `640 x 399` with bilinear interpolation. KleidiCV 26.06 selects a Neon implementation for this case under the default build settings. Although SVE2 support is enabled, `KLEIDICV_LIMIT_SVE2_TO_SELECTED_ALGORITHMS=ON` excludes this resize implementation from SVE2 dispatch. See the [KleidiCV build defaults](https://gitlab.arm.com/kleidi/kleidicv/-/blob/26.06/kleidicv/CMakeLists.txt) and [dispatch definitions](https://gitlab.arm.com/kleidi/kleidicv/-/blob/26.06/kleidicv/include/kleidicv/dispatch.h). + +The [OpenCV baseline implementation](https://github.com/opencv/opencv/blob/4.13.0/modules/imgproc/src/resize.cpp) already uses SIMD, but its three-channel horizontal loop advances one pixel at a time. The [KleidiCV Neon implementation](https://gitlab.arm.com/kleidi/kleidicv/-/blob/26.06/kleidicv/src/resize/resize_linear_generic_u8_neon.h) loads contiguous byte blocks and uses register-table lookups to select neighboring samples for 16 channel values at a time. It completes vertical and horizontal interpolation within each vector block, avoiding the intermediate 32-bit row-buffer writes and reads used by OpenCV. + +KleidiCV also uses interpolation weights with eight fractional bits and narrower intermediate arithmetic, compared with OpenCV's 11-bit coefficients. This supports compact vector calculations, but changes quantization and rounding. Equivalent detections do not prove pixel-identical resize output; compare image differences and detection accuracy on representative inputs if your application needs that assurance. + +These implementation differences explain how acceleration is possible, not how much each optimization contributes to the measured gain. Allocation and thread scheduling also affect the API-stage timing. You need runtime profiling and an isolated resize benchmark to separate their contributions. + +## What you've accomplished + +You've built and run a YOLOX detection pipeline on arm64 and compared baseline and KleidiCV performance. You can now distinguish image-operation gains from full preprocessing and overall pipeline latency. + +Profile your application's dominant stage before choosing the next optimization. diff --git a/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/_index.md b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/_index.md new file mode 100644 index 0000000000..0c92e1b9fd --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/_index.md @@ -0,0 +1,74 @@ +--- +title: Build and profile a YOLOX pipeline with KleidiCV on arm64 +description: Build a YOLOX object detection pipeline on arm64 Linux and compare baseline and KleidiCV preprocessing and pipeline latency. + +draft: true +cascade: + draft: true + +lastmod: 2026-10-29 + +minutes_to_complete: 90 + +who_is_this_for: This is an advanced topic for C++ developers who want to deploy ONNX computer vision models and measure image-processing performance on arm64 Linux boards. + +learning_objectives: + - Build matching baseline and default KleidiCV versions of OpenCV + - Deploy a YOLOX ONNX model and label objects in a static image + - Compare image-operation, full preprocessing, and pipeline latency to distinguish local optimization from application-level improvement + +prerequisites: + - An arm64 development board running 64-bit Debian or Ubuntu + - At least 12 GB of free storage for two OpenCV builds + - GCC 10 or later, or Clang 12 or later + - Familiarity with C++, CMake, and the Linux command line + - Time for source downloads and two OpenCV compilations in addition to the 90-minute hands-on estimate + +author: Jett Zhou + +generate_summary_faq: true +rerun_summary: false +rerun_faqs: false + +### Tags +skilllevels: Advanced +subjects: ML +armips: + - Cortex-A +operatingsystems: + - Linux +tools_software_languages: + - OpenCV + - ONNX + - KleidiCV + - C++ + - CMake + +further_reading: + - resource: + title: OpenCV DNN module + link: https://docs.opencv.org/4.x/d6/d0f/group__dnn.html + type: documentation + - resource: + title: OpenCV Zoo YOLOX model + link: https://github.com/opencv/opencv_zoo/tree/main/models/object_detection_yolox + type: website + - resource: + title: Accelerate an OpenCV-based Android application with KleidiCV + link: /learning-paths/mobile-graphics-and-gaming/android_opencv_kleidicv/ + type: website + - resource: + title: Profile ONNX model performance with SME2 using KleidiAI and ONNX Runtime + link: /learning-paths/mobile-graphics-and-gaming/performance_onnxruntime_kleidiai_sme2/ + type: website + - resource: + title: KleidiCV repository + link: https://gitlab.arm.com/kleidi/kleidicv + type: documentation + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 +layout: "learningpathall" +learning_path_main_page: "yes" +--- diff --git a/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/_next-steps.md b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/_next-steps.md new file mode 100644 index 0000000000..c3db0de5a2 --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/_next-steps.md @@ -0,0 +1,8 @@ +--- +# ================================================================================ +# FIXED, DO NOT MODIFY THIS FILE +# ================================================================================ +weight: 21 # Set to always be larger than the content in this path to be at the end of the navigation. +title: "Next Steps" # Always the same, html page title. +layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. +--- diff --git a/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/cmakelists.txt b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/cmakelists.txt new file mode 100644 index 0000000000..09af84611d --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/cmakelists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.16) +project(yolox_static LANGUAGES CXX) + +find_package(OpenCV 4.13 REQUIRED COMPONENTS core imgproc imgcodecs dnn) + +add_executable(yolox_static yolox_static.cpp) +target_compile_features(yolox_static PRIVATE cxx_std_17) +target_link_libraries(yolox_static PRIVATE ${OpenCV_LIBS}) diff --git a/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/coco.names b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/coco.names new file mode 100644 index 0000000000..941cb4e139 --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/coco.names @@ -0,0 +1,80 @@ +person +bicycle +car +motorcycle +airplane +bus +train +truck +boat +traffic light +fire hydrant +stop sign +parking meter +bench +bird +cat +dog +horse +sheep +cow +elephant +bear +zebra +giraffe +backpack +umbrella +handbag +tie +suitcase +frisbee +skis +snowboard +sports ball +kite +baseball bat +baseball glove +skateboard +surfboard +tennis racket +bottle +wine glass +cup +fork +knife +spoon +bowl +banana +apple +sandwich +orange +broccoli +carrot +hot dog +pizza +donut +cake +chair +couch +potted plant +bed +dining table +toilet +tv +laptop +mouse +remote +keyboard +cell phone +microwave +oven +toaster +sink +refrigerator +book +clock +vase +scissors +teddy bear +hair drier +toothbrush diff --git a/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/detection-input.jpg b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/detection-input.jpg new file mode 100644 index 0000000000..cd437610fe Binary files /dev/null and b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/detection-input.jpg differ diff --git a/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/detection-result.jpg b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/detection-result.jpg new file mode 100644 index 0000000000..6a706f6367 Binary files /dev/null and b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/detection-result.jpg differ diff --git a/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/pipeline.svg b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/pipeline.svg new file mode 100644 index 0000000000..eb82a52bbd --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/pipeline.svg @@ -0,0 +1,71 @@ + + YOLOX image-processing pipeline + A JPEG image passes through resize, letterbox padding, BGR-to-RGB conversion, uint8-to-float32 conversion, and NCHW tensor packing. YOLOX inference is followed by output decoding, non-maximum suppression, and drawing bounding boxes on the original image. Resize and the two conversions are marked as eligible for KleidiCV HAL acceleration; padding and tensor packing remain part of full preprocessing. + + + + + + + + + + + 1. Image preprocessing + 2. Model inference + 3. Output processing + + + + + + + + + + + + JPEG image + + + Resize (preserve aspect ratio) + KleidiCV HAL eligible + + + Letterbox padding + Constant value: 114 + + + BGR-to-RGB conversion + KleidiCV HAL eligible + + + Uint8-to-float32 conversion + KleidiCV HAL eligible + + + NCHW tensor packing + [1, 3, 640, 640] + + + YOLOX inference + net.forward() + + + Decode predictions + Non-maximum suppression + + + Draw bounding boxes + On the original image + diff --git a/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/yolox_static.cpp b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/yolox_static.cpp new file mode 100644 index 0000000000..3e8dcb1aba --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/opencv-onnx-kleidicv-static-detection/yolox_static.cpp @@ -0,0 +1,515 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace +{ + +constexpr int kInputWidth = 640; +constexpr int kInputHeight = 640; + +using Clock = std::chrono::steady_clock; + +struct GridAndStride +{ + int gridX; + int gridY; + int stride; +}; + +struct Detection +{ + cv::Rect box; + float score; + int classId; +}; + +struct StageTimes +{ + double resizeMs = 0.0; + double paddingMs = 0.0; + double colorConversionMs = 0.0; + double typeConversionMs = 0.0; + double blobMs = 0.0; + double inferenceMs = 0.0; + double postprocessMs = 0.0; + double renderMs = 0.0; + + double preprocessingMs() const + { + return resizeMs + paddingMs + colorConversionMs + typeConversionMs + + blobMs; + } + + double totalMs() const + { + return preprocessingMs() + inferenceMs + postprocessMs + renderMs; + } +}; + +struct Summary +{ + double mean; + double minimum; + double standardDeviation; +}; + +double elapsedMilliseconds(Clock::time_point start, Clock::time_point end) +{ + return std::chrono::duration(end - start).count(); +} + +std::vector loadLabels(const std::string& path) +{ + std::ifstream input(path); + if (!input) + { + throw std::runtime_error("Unable to open label file: " + path); + } + + std::vector labels; + std::string label; + while (std::getline(input, label)) + { + if (!label.empty()) + { + labels.push_back(label); + } + } + + if (labels.size() != 80) + { + throw std::runtime_error("Expected 80 COCO labels, found " + + std::to_string(labels.size())); + } + return labels; +} + +std::vector generateGridAndStrides() +{ + const std::vector strides = {8, 16, 32}; + std::vector gridAndStrides; + + for (int stride : strides) + { + const int gridWidth = kInputWidth / stride; + const int gridHeight = kInputHeight / stride; + for (int gridY = 0; gridY < gridHeight; ++gridY) + { + for (int gridX = 0; gridX < gridWidth; ++gridX) + { + gridAndStrides.push_back({gridX, gridY, stride}); + } + } + } + return gridAndStrides; +} + +cv::Size calculateResizedSize(const cv::Mat& image, float& scale) +{ + scale = std::min(kInputWidth / static_cast(image.cols), + kInputHeight / static_cast(image.rows)); + + return cv::Size( + std::max(1, static_cast(std::round(image.cols * scale))), + std::max(1, static_cast(std::round(image.rows * scale)))); +} + +std::vector postprocess( + const std::vector& outputs, + const std::vector& gridAndStrides, + const cv::Size& originalSize, + float scale, + float confidenceThreshold, + float nmsThreshold) +{ + if (outputs.empty()) + { + throw std::runtime_error("The network returned no output tensors"); + } + + cv::Mat output = outputs.front(); + if (!output.isContinuous()) + { + output = output.clone(); + } + if (output.depth() != CV_32F || output.dims < 2) + { + throw std::runtime_error("Unexpected YOLOX output type or dimensions"); + } + + const int dimensions = output.size[output.dims - 1]; + if (dimensions != 85) + { + throw std::runtime_error("Expected 85 values per YOLOX proposal, found " + + std::to_string(dimensions)); + } + + const int proposalCount = static_cast(output.total() / dimensions); + if (proposalCount != static_cast(gridAndStrides.size())) + { + throw std::runtime_error("YOLOX output proposal count does not match the 640x640 grid"); + } + + const cv::Mat proposals(proposalCount, dimensions, CV_32F, output.ptr()); + std::vector candidateBoxes; + std::vector candidateScores; + std::vector candidateClassIds; + + for (int row = 0; row < proposalCount; ++row) + { + const float* values = proposals.ptr(row); + const float objectness = values[4]; + if (objectness <= 0.0F) + { + continue; + } + + int classId = 0; + float classScore = values[5]; + for (int candidateClass = 1; candidateClass < 80; ++candidateClass) + { + if (values[5 + candidateClass] > classScore) + { + classScore = values[5 + candidateClass]; + classId = candidateClass; + } + } + + const float score = objectness * classScore; + if (score < confidenceThreshold) + { + continue; + } + + const GridAndStride& grid = gridAndStrides[row]; + const float centerX = (values[0] + grid.gridX) * grid.stride; + const float centerY = (values[1] + grid.gridY) * grid.stride; + const float width = std::exp(values[2]) * grid.stride; + const float height = std::exp(values[3]) * grid.stride; + + candidateBoxes.emplace_back( + static_cast(std::round(centerX - width * 0.5F)), + static_cast(std::round(centerY - height * 0.5F)), + static_cast(std::round(width)), + static_cast(std::round(height))); + candidateScores.push_back(score); + candidateClassIds.push_back(classId); + } + + std::vector retained; + cv::dnn::NMSBoxesBatched(candidateBoxes, candidateScores, candidateClassIds, + confidenceThreshold, nmsThreshold, retained); + + const cv::Rect imageBounds(0, 0, originalSize.width, originalSize.height); + std::vector detections; + detections.reserve(retained.size()); + + for (int index : retained) + { + const cv::Rect& modelBox = candidateBoxes[index]; + cv::Rect originalBox( + static_cast(std::round(modelBox.x / scale)), + static_cast(std::round(modelBox.y / scale)), + static_cast(std::round(modelBox.width / scale)), + static_cast(std::round(modelBox.height / scale))); + originalBox &= imageBounds; + if (originalBox.area() > 0) + { + detections.push_back( + {originalBox, candidateScores[index], candidateClassIds[index]}); + } + } + return detections; +} + +cv::Mat renderDetections( + const cv::Mat& image, + const std::vector& detections, + const std::vector& labels) +{ + cv::Mat rendered = image.clone(); + for (const Detection& detection : detections) + { + cv::rectangle(rendered, detection.box, cv::Scalar(0, 255, 0), 2); + + const std::string text = + labels.at(detection.classId) + cv::format(" %.2f", detection.score); + int baseline = 0; + const cv::Size textSize = + cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 0.55, 1, &baseline); + + const int textX = detection.box.x; + const int textY = std::max(textSize.height + 4, detection.box.y); + const cv::Rect background( + textX, + textY - textSize.height - 4, + std::min(textSize.width + 6, rendered.cols - textX), + textSize.height + baseline + 6); + + cv::rectangle(rendered, background, cv::Scalar(0, 255, 0), cv::FILLED); + cv::putText(rendered, text, cv::Point(textX + 3, textY), + cv::FONT_HERSHEY_SIMPLEX, 0.55, cv::Scalar(0, 0, 0), 1, + cv::LINE_AA); + } + return rendered; +} + +StageTimes runPipeline( + cv::dnn::Net& network, + const cv::Mat& image, + const std::vector& gridAndStrides, + const std::vector& labels, + float confidenceThreshold, + float nmsThreshold, + std::vector& detections, + cv::Mat& rendered) +{ + StageTimes times; + float scale = 1.0F; + const cv::Size resizedSize = calculateResizedSize(image, scale); + + auto start = Clock::now(); + cv::Mat resized; + cv::resize(image, resized, resizedSize, 0.0, 0.0, cv::INTER_LINEAR); + auto end = Clock::now(); + times.resizeMs = elapsedMilliseconds(start, end); + + start = Clock::now(); + cv::Mat padded; + cv::copyMakeBorder( + resized, padded, 0, kInputHeight - resized.rows, 0, + kInputWidth - resized.cols, cv::BORDER_CONSTANT, cv::Scalar::all(114)); + end = Clock::now(); + times.paddingMs = elapsedMilliseconds(start, end); + + start = Clock::now(); + cv::Mat rgb; + cv::cvtColor(padded, rgb, cv::COLOR_BGR2RGB); + end = Clock::now(); + times.colorConversionMs = elapsedMilliseconds(start, end); + + start = Clock::now(); + cv::Mat floatImage; + rgb.convertTo(floatImage, CV_32F); + end = Clock::now(); + times.typeConversionMs = elapsedMilliseconds(start, end); + + start = Clock::now(); + cv::Mat blob = cv::dnn::blobFromImage( + floatImage, 1.0, cv::Size(), cv::Scalar(), false, false, CV_32F); + end = Clock::now(); + times.blobMs = elapsedMilliseconds(start, end); + + start = Clock::now(); + network.setInput(blob); + std::vector outputs; + network.forward(outputs, network.getUnconnectedOutLayersNames()); + end = Clock::now(); + times.inferenceMs = elapsedMilliseconds(start, end); + + start = Clock::now(); + detections = postprocess(outputs, gridAndStrides, image.size(), scale, + confidenceThreshold, nmsThreshold); + end = Clock::now(); + times.postprocessMs = elapsedMilliseconds(start, end); + + start = Clock::now(); + rendered = renderDetections(image, detections, labels); + end = Clock::now(); + times.renderMs = elapsedMilliseconds(start, end); + return times; +} + +Summary summarize(const std::vector& values) +{ + const double sum = std::accumulate(values.begin(), values.end(), 0.0); + const double mean = sum / values.size(); + const double minimum = *std::min_element(values.begin(), values.end()); + + double squaredDifferenceSum = 0.0; + for (double value : values) + { + const double difference = value - mean; + squaredDifferenceSum += difference * difference; + } + return {mean, minimum, std::sqrt(squaredDifferenceSum / values.size())}; +} + +void printSummary(const std::string& name, const std::vector& values) +{ + const Summary summary = summarize(values); + std::cout << std::left << std::setw(20) << name + << " mean=" << std::right << std::fixed << std::setprecision(3) + << std::setw(9) << summary.mean + << " ms min=" << std::setw(9) << summary.minimum + << " ms stddev=" << std::setw(9) << summary.standardDeviation + << " ms\n"; +} + +} // namespace + +int main(int argc, char** argv) +{ + const cv::String keys = + "{help h usage ? | | Print this message }" + "{model | | Path to the YOLOX ONNX model }" + "{image | | Path to the input image }" + "{labels | | Path to the COCO label file }" + "{output |result.jpg| Path to the output image }" + "{build-name |unspecified| Label printed with the results }" + "{confidence |0.35 | Confidence threshold }" + "{nms |0.50 | Non-maximum suppression threshold }" + "{warmup |5 | Number of warm-up iterations }" + "{iterations |20 | Number of measured iterations }" + "{threads |0 | OpenCV thread count; 0 keeps the default }"; + + cv::CommandLineParser parser(argc, argv, keys); + parser.about("Run and benchmark YOLOX static-image detection with OpenCV DNN"); + if (parser.has("help")) + { + parser.printMessage(); + return 0; + } + const std::string modelPath = parser.get("model"); + const std::string imagePath = parser.get("image"); + const std::string labelPath = parser.get("labels"); + const std::string outputPath = parser.get("output"); + const std::string buildName = parser.get("build-name"); + const float confidenceThreshold = parser.get("confidence"); + const float nmsThreshold = parser.get("nms"); + const int warmupIterations = parser.get("warmup"); + const int measuredIterations = parser.get("iterations"); + const int threads = parser.get("threads"); + + if (!parser.check()) + { + parser.printErrors(); + return 1; + } + + if (modelPath.empty() || imagePath.empty() || labelPath.empty()) + { + std::cerr << "The --model, --image, and --labels arguments are required\n"; + return 1; + } + if (warmupIterations < 0 || measuredIterations <= 0 || threads < 0) + { + std::cerr << "Use non-negative warm-up and thread values, and at least one iteration\n"; + return 1; + } + + try + { + if (threads > 0) + { + cv::setNumThreads(threads); + } + + const cv::Mat image = cv::imread(imagePath, cv::IMREAD_COLOR); + if (image.empty()) + { + throw std::runtime_error("Unable to read input image: " + imagePath); + } + + const std::vector labels = loadLabels(labelPath); + const std::vector gridAndStrides = generateGridAndStrides(); + + cv::dnn::Net network = cv::dnn::readNetFromONNX(modelPath); + network.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV); + network.setPreferableTarget(cv::dnn::DNN_TARGET_CPU); + + std::vector detections; + cv::Mat rendered; + for (int iteration = 0; iteration < warmupIterations; ++iteration) + { + runPipeline(network, image, gridAndStrides, labels, + confidenceThreshold, nmsThreshold, detections, rendered); + } + + std::vector resizeTimes; + std::vector paddingTimes; + std::vector colorConversionTimes; + std::vector typeConversionTimes; + std::vector blobTimes; + std::vector inferenceTimes; + std::vector postprocessTimes; + std::vector renderTimes; + std::vector preprocessingTimes; + std::vector totalTimes; + resizeTimes.reserve(measuredIterations); + paddingTimes.reserve(measuredIterations); + colorConversionTimes.reserve(measuredIterations); + typeConversionTimes.reserve(measuredIterations); + blobTimes.reserve(measuredIterations); + inferenceTimes.reserve(measuredIterations); + postprocessTimes.reserve(measuredIterations); + renderTimes.reserve(measuredIterations); + preprocessingTimes.reserve(measuredIterations); + totalTimes.reserve(measuredIterations); + + for (int iteration = 0; iteration < measuredIterations; ++iteration) + { + const StageTimes times = + runPipeline(network, image, gridAndStrides, labels, + confidenceThreshold, nmsThreshold, detections, rendered); + resizeTimes.push_back(times.resizeMs); + paddingTimes.push_back(times.paddingMs); + colorConversionTimes.push_back(times.colorConversionMs); + typeConversionTimes.push_back(times.typeConversionMs); + blobTimes.push_back(times.blobMs); + inferenceTimes.push_back(times.inferenceMs); + postprocessTimes.push_back(times.postprocessMs); + renderTimes.push_back(times.renderMs); + preprocessingTimes.push_back(times.preprocessingMs()); + totalTimes.push_back(times.totalMs()); + } + + if (!cv::imwrite(outputPath, rendered)) + { + throw std::runtime_error("Unable to write output image: " + outputPath); + } + + std::cout << "Build label: " << buildName << '\n'; + std::cout << "OpenCV version: " << CV_VERSION << '\n'; + std::cout << "OpenCV threads: " << cv::getNumThreads() << '\n'; + std::cout << "Warm-up iterations: " << warmupIterations << '\n'; + std::cout << "Measured iterations: " << measuredIterations << '\n'; + std::cout << "Detections: " << detections.size() << '\n'; + printSummary("Resize:", resizeTimes); + printSummary("Padding:", paddingTimes); + printSummary("BGR to RGB:", colorConversionTimes); + printSummary("Uint8 to float32:", typeConversionTimes); + printSummary("Tensor packing:", blobTimes); + printSummary("Preprocessing:", preprocessingTimes); + printSummary("DNN inference:", inferenceTimes); + printSummary("Postprocessing:", postprocessTimes); + printSummary("Rendering:", renderTimes); + printSummary("End-to-end:", totalTimes); + std::cout << "Output image: " << outputPath << '\n'; + } + catch (const cv::Exception& error) + { + std::cerr << "OpenCV error: " << error.what() << '\n'; + return 1; + } + catch (const std::exception& error) + { + std::cerr << "Error: " << error.what() << '\n'; + return 1; + } + + return 0; +} diff --git a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/1-prepare-environment.md b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/1-prepare-environment.md index 8ad19ce477..94c5e2f4e3 100644 --- a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/1-prepare-environment.md +++ b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/1-prepare-environment.md @@ -23,7 +23,7 @@ Use the FVP for functional validation. The FVP's Ethos-U model is cycle accurate ## Check your development machine -Run the following preflight check before downloading the source: +Use Python 3.12 with development headers and virtual-environment support. The release wheels require glibc 2.28 or later on Linux, or macOS 15 or later on Apple silicon. On Linux, the FVP also requires `libstdc++.so.6` providing `GLIBCXX_3.4.26` or later. Run the following preflight check before downloading the source; CMake is installed in the virtual environment later: ```bash case "$(uname -s)/$(uname -m)" in @@ -36,7 +36,7 @@ case "$(uname -s)/$(uname -m)" in ;; esac -for tool in python3 git cmake c++ curl; do +for tool in python3.12 git c++ curl; do command -v "$tool" >/dev/null || { echo "Missing required tool: $tool" >&2 exit 1 @@ -51,34 +51,18 @@ fi printf 'int main() { return 0; }\n' | \ c++ -std=c++17 -x c++ -fsyntax-only - -python3 - <<'PY' -import re -import subprocess -import sys - -if not (3, 10) <= sys.version_info[:2] <= (3, 13): - raise SystemExit("Python 3.10 through 3.13 is required") - -output = subprocess.check_output(["cmake", "--version"], text=True) -version = tuple(map(int, re.search(r"\d+(?:\.\d+)+", output).group().split(".")[:2])) -if version < (3, 24): - raise SystemExit("CMake 3.24 or later is required") - -print(f"Python {sys.version.split()[0]}") -print(output.splitlines()[0]) -PY +python3.12 --version ``` ## Create an isolated ExecuTorch environment -The Silero VAD Ethos-U example is available in the upstream ExecuTorch repository. Use the tested main-branch commit so that the commands and generated artifacts match the Learning Path. - -Clone ExecuTorch and check out the tested revision: +Use the source revision from the Silero VAD end-to-end example with the released ExecuTorch `1.5.1` Python package: ```bash git clone https://github.com/pytorch/executorch.git cd executorch -git checkout 4fd161058ebe2b9d80d11242a9d21811c0e92dac +git fetch origin d78bc1b0ba1c33a90d2d93662de118577f37efd4 +git checkout d78bc1b0ba1c33a90d2d93662de118577f37efd4 git submodule sync --recursive git submodule update --init --recursive ``` @@ -92,121 +76,113 @@ git rev-parse --short=12 HEAD The expected output is: ```output -4fd161058ebe +d78bc1b0ba1c ``` Create and activate a Python virtual environment: ```bash -python3 -m venv .venv +python3.12 -m venv .venv source .venv/bin/activate python -m pip install --upgrade pip ``` -Install ExecuTorch and its Python dependencies: - -```bash -CMAKE_ARGS="-DEXECUTORCH_BUILD_MLX=OFF" \ - env -u DEBUG ./install_executorch.sh \ - --minimal --optional-dependency ethos_u - -python - <<'PY' -import executorch.codegen.tools.selective_build -from executorch.backends.arm.ethosu import EthosUPartitioner -from executorch.backends.arm.quantizer import EthosUQuantizer - -print("ExecuTorch installation verified") -PY -``` - -The installation script initializes the Git submodules that are needed by the build. The script also installs the matching PyTorch and ExecuTorch packages. The command omits the unrelated MLX backend and optional packages used by other examples. - ## Install the Arm backend tools -The Arm setup script downloads the Arm GNU Toolchain, Ethos-U Vela compiler, and supporting Python packages. On Linux, the script also installs the Corstone FVPs. Review the license terms presented by the script before using its EULA acceptance option. +The Arm setup script installs the Arm GNU Toolchain and, on Linux, the Corstone FVPs. Review the license terms presented by the script before using its EULA acceptance option. {{% notice macOS %}} -Before you run the Arm setup command on macOS, install [Docker Desktop](/install-guides/docker/docker-desktop/) and follow the [AVH FVPs on macOS install guide](/install-guides/fvps-on-macos/). Add the FVPs-on-Mac `bin` directory to `PATH`. Confirm that Docker is running and that `FVP_Corstone_SSE-320` resolves to the wrapper: +Before running Arm setup, install [Docker Desktop](/install-guides/docker/docker-desktop/) and start Docker. Install the [FVPs-on-Mac wrappers](https://github.com/Arm-Examples/FVPs-on-Mac) at the revision for FVP `11.31.28`: ```bash -docker info >/dev/null -command -v FVP_Corstone_SSE-320 +mkdir -p examples/arm/arm-scratch "$HOME/.armlm" +git clone https://github.com/Arm-Examples/FVPs-on-Mac.git \ + examples/arm/arm-scratch/FVPs-on-Mac +git -C examples/arm/arm-scratch/FVPs-on-Mac checkout --detach \ + cc7a4be5456db085619647811fac29e9795f806e +bash examples/arm/arm-scratch/FVPs-on-Mac/build.sh +export PATH="$PWD/examples/arm/arm-scratch/FVPs-on-Mac/bin:$PATH" +export FVP_MOUNT_DIR="$PWD" +FVP_Corstone_SSE-320 --version ``` {{% /notice %}} -Run the setup script from the ExecuTorch repository root: +Install released PyTorch and run Arm setup from the ExecuTorch repository root. The setup options select the cross-compiler and FVPs; the next section installs the Python export tools: ```bash -./examples/arm/setup.sh --i-agree-to-the-contained-eula -source examples/arm/arm-scratch/setup_path.sh +python -m pip install "torch==2.14.0" --index-url https://download.pytorch.org/whl/cpu +./examples/arm/setup.sh --i-agree-to-the-contained-eula \ + --disable-ethos-u-deps --enable-baremetal-toolchain --enable-fvps ``` -The second command adds the downloaded cross-compiler and, on Linux, FVP binaries to the current shell environment. If you start a new shell, you'll need to run the command again. +## Install ExecuTorch -Check that the two target tools are available: +Install the released ExecuTorch package and its Ethos-U dependencies: + +```bash +python -m pip install "executorch[ethos_u]==1.5.1" "torchao==0.18.0" "cmake<4" pytest +source examples/arm/arm-scratch/setup_path.sh +``` + +The path script adds the downloaded cross-compiler and, on Linux, FVP binaries to the current shell. Check the target tools and Python installation: ```bash command -v arm-none-eabi-g++ command -v FVP_Corstone_SSE-320 +python -m pip check +python - <<'PY' +import executorch.codegen.tools.selective_build +from executorch.backends.arm.ethosu import EthosUPartitioner +from executorch.backends.arm.quantizer import EthosUQuantizer + +print("ExecuTorch installation verified") +PY ``` -The compiler resolves under `examples/arm/arm-scratch/`. On Linux, the FVP also resolves under this directory. On macOS, the FVP resolves under the FVPs-on-Mac wrapper directory. If the compiler is missing, source `examples/arm/arm-scratch/setup_path.sh` again. If the FVP is missing on macOS, add the wrapper directory to `PATH`. +The Python checks should report `No broken requirements found.` and `ExecuTorch installation verified`. + +In each new shell, return to the ExecuTorch repository root and restore the environment and tool paths: + +```bash +source .venv/bin/activate +source examples/arm/arm-scratch/setup_path.sh +if [ "$(uname -s)" = Darwin ]; then + export PATH="$PWD/examples/arm/arm-scratch/FVPs-on-Mac/bin:$PATH" + export FVP_MOUNT_DIR="$PWD" +fi +``` ## Download the model and sample audio -Create one workspace for the files generated in this Learning Path: +Create a directory for the model and audio inputs: ```bash -mkdir -p silero-vad-work/{assets,export} +mkdir -p silero-vad ``` Download the model and sample audio from the tested Silero VAD revision: ```bash curl --fail --location \ - --output silero-vad-work/assets/silero_vad.jit \ + --output silero-vad/silero_vad.jit \ https://raw.githubusercontent.com/snakers4/silero-vad/dbacf536adadf42210f37ae50fbaf75f6235b3cf/src/silero_vad/data/silero_vad.jit curl --fail --location \ - --output silero-vad-work/assets/test.wav \ + --output silero-vad/test.wav \ https://raw.githubusercontent.com/snakers4/silero-vad/dbacf536adadf42210f37ae50fbaf75f6235b3cf/tests/data/test.wav ``` -## Create two audio clips +## Create the validation clip -The target processes 2.5 seconds of audio. Copy the following snippet once to create a calibration clip and a separate validation clip: +Use the example's helper to take the first 40,000 samples of `test.wav`. This creates a 2.5-second validation clip that fits the runtime's audio limit and produces 79 frames: ```bash -python3 - <<'PY' -import wave -from pathlib import Path - -source_path = Path("silero-vad-work/assets/test.wav") -clips = (("calibration.wav", 0.0), ("validation.wav", 2.5)) - -with wave.open(str(source_path), "rb") as source: - parameters = source.getparams() - if ( - parameters.nchannels, - parameters.sampwidth, - parameters.framerate, - parameters.comptype, - ) != (1, 2, 16000, "NONE"): - raise SystemExit("test.wav must be mono, 16 kHz, 16-bit PCM") - if source.getnframes() < 5 * parameters.framerate: - raise SystemExit("test.wav does not contain five seconds of audio") - for name, start_seconds in clips: - source.setpos(int(start_seconds * parameters.framerate)) - frames = source.readframes(int(2.5 * parameters.framerate)) - if len(frames) != 40000 * parameters.sampwidth: - raise SystemExit(f"Could not create a 2.5-second {name} clip") - with wave.open(str(source_path.with_name(name)), "wb") as target: - target.setparams(parameters) - target.writeframes(frames) -PY +python examples/arm/silero_vad_example_ethos_u/model_export/generate_smoke_audio.py \ + --source-wav silero-vad/test.wav \ + --output silero-vad/validation.wav ``` -Use `calibration.wav` to calibrate quantization. The FVP will process the separate `validation.wav` clip. +Use `test.wav` for calibration and `validation.wav` for the host reference and FVP. These recordings overlap to check deployment consistency. Use representative calibration audio and separate validation recordings when measuring model quality. ## What you've accomplished and what's next diff --git a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/2-run-example.md b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/2-run-example.md index a08911d488..e7f5462eb8 100644 --- a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/2-run-example.md +++ b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/2-run-example.md @@ -9,7 +9,7 @@ layout: learningpathall ## Prepare to export the model -You prepared the model and two audio clips earlier. Now, use the calibration clip to quantize Silero VAD and the validation clip to create a host reference. +You prepared the model and validation clip earlier. Now, use the source recording to calibrate Silero VAD and the validation clip to create a host reference. From the ExecuTorch repository root, activate the environment: @@ -18,29 +18,6 @@ source .venv/bin/activate source examples/arm/arm-scratch/setup_path.sh ``` -## Build the host quantized operators - -The exporter requires the host shared library that registers the quantized operator out variants. Build the operator before exporting the model: - -```bash -cmake \ - -S . \ - -B silero-vad-work/quantized_ops_aot \ - -DCMAKE_BUILD_TYPE=Release \ - -DEXECUTORCH_BUILD_KERNELS_QUANTIZED=ON \ - -DEXECUTORCH_BUILD_KERNELS_QUANTIZED_AOT=ON \ - -DEXECUTORCH_BUILD_XNNPACK=OFF \ - -DPYTHON_EXECUTABLE="$(command -v python)" - -cmake --build silero-vad-work/quantized_ops_aot \ - --target quantized_ops_aot_lib --parallel - -export EXECUTORCH_QUANTIZED_OPS_AOT_LIBRARY="$(find \ - silero-vad-work/quantized_ops_aot/kernels/quantized \ - -name 'libquantized_ops_aot_lib.*' -type f -print -quit)" -test -s "$EXECUTORCH_QUANTIZED_OPS_AOT_LIBRARY" -``` - ## Export the model Run the exporter and save its output for the delegation check: @@ -48,44 +25,41 @@ Run the exporter and save its output for the delegation check: ```bash set -o pipefail -python3 examples/arm/silero_vad_example_ethos_u/model_export/export_silero_vad_ethos_u.py \ - --jit-model silero-vad-work/assets/silero_vad.jit \ - --calibration-audio silero-vad-work/assets/calibration.wav \ - --validation-audio silero-vad-work/assets/validation.wav \ - --output-path silero-vad-work/export/silero_vad_ethos_u.pte \ - --expected-output-path silero-vad-work/export/expected_probs.bin \ - --num-calibration-frames 32 \ - --num-validation-frames 0 2>&1 | \ - tee silero-vad-work/export/export.log +python examples/arm/silero_vad_example_ethos_u/model_export/export_silero_vad_ethos_u.py \ + --jit-model silero-vad/silero_vad.jit \ + --calibration-audio silero-vad/test.wav \ + --validation-audio silero-vad/validation.wav 2>&1 | tee export.log ``` The output is similar to: ```output -Wrote expected probabilities to silero-vad-work/export/expected_probs.bin +Wrote expected probabilities to ./expected_probs.bin Lowering to Ethos-U85... -Exported model saved to silero-vad-work/export/silero_vad_ethos_u.pte +Exported model saved to ./silero_vad_ethos_u.pte ``` +The default export uses up to 256 calibration frames and all 79 validation frames. It writes the PTE and reference probabilities in the repository root. + Verify the generated outputs and the number of reference probabilities: ```bash for artifact in \ - silero-vad-work/export/silero_vad_ethos_u.pte \ - silero-vad-work/export/expected_probs.bin \ - silero-vad-work/export/export.log; do + silero_vad_ethos_u.pte \ + expected_probs.bin \ + export.log; do test -s "$artifact" || { echo "Missing export artifact: $artifact" >&2 exit 1 } done -test "$(wc -c < silero-vad-work/export/expected_probs.bin)" -eq $((79 * 4)) || { +test "$(wc -c < expected_probs.bin)" -eq $((79 * 4)) || { echo "Expected 79 float32 reference probabilities" >&2 exit 1 } -test "$(grep -c 'CPU operators = 0' silero-vad-work/export/export.log)" -eq 2 || { +test "$(grep -c 'CPU operators = 0' export.log)" -eq 2 || { echo "Expected both Vela subgraphs to contain no CPU operators" >&2 exit 1 } @@ -99,7 +73,7 @@ The application supplies one 512-sample audio frame at a time. The exported prog ![Runtime diagram showing 64 context samples and a 512-sample frame entering the ExecuTorch program, model operations delegated to Ethos-U85, an internal int8 hidden and cell state reused between calls, and one speech probability emitted every 32 ms.#center](silero-vad-streaming-delegation.svg "Silero VAD streaming state and Ethos-U delegation boundary") -The Vela output contains two subgraphs with no CPU operators: the main VAD network and a smaller state-quantization subgraph. Only small boundary conversions and the state update remain as portable ExecuTorch operations. +The Vela output contains two subgraphs with no CPU operators: the main VAD network and a smaller state-quantization subgraph. Quantization, layout copies, and recurrent-state writes between delegates run on the CPU. ## What you've accomplished and what's next diff --git a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/3-run-on-fvp.md b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/3-run-on-fvp.md index c768c17135..8f020a8b04 100644 --- a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/3-run-on-fvp.md +++ b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/3-run-on-fvp.md @@ -18,78 +18,68 @@ Continue from the ExecuTorch repository root. Activate the environment and build ```bash source .venv/bin/activate source examples/arm/arm-scratch/setup_path.sh +if [ "$(uname -s)" = Darwin ]; then + export PATH="$PWD/examples/arm/arm-scratch/FVPs-on-Mac/bin:$PATH" + export FVP_MOUNT_DIR="$PWD" +fi cmake --preset arm-baremetal \ -DCMAKE_BUILD_TYPE=Release \ - -B cmake-out-arm -cmake --build cmake-out-arm --target install --parallel + -DTARGET_CPU=cortex-m85 \ + -B cmake-out-arm -S . +cmake --build cmake-out-arm --target install --parallel 2 ``` The installed libraries provide the ExecuTorch runtime, portable operators, and Ethos-U backend used by the application. ## Build the Silero VAD application -Configure the application with the exported model, validation audio, and a speech threshold of `0.55`. +Configure the application with the exported model, validation audio, and the default speech threshold of `0.5`. The command selects the UART address required by FVPs-on-Mac: -Select your host operating system: +```bash +runtime_cmake_args=() +if [ "$(uname -s)" = Darwin ]; then + runtime_cmake_args+=(-DUART0_BASE=0x49303000) +fi -{{< tabpane code=true >}} - {{< tab header="Linux" language="bash" >}} -cmake \ - -S examples/arm/silero_vad_example_ethos_u/runtime \ - -B silero-vad-work/app \ - -DCMAKE_TOOLCHAIN_FILE="$PWD/examples/arm/ethos-u-setup/arm-none-eabi-gcc.cmake" \ - -DET_BUILD_DIR_PATH="$PWD/cmake-out-arm" \ - -DTARGET_CPU=cortex-m85 \ - -DET_PTE_FILE_PATH="$PWD/silero-vad-work/export/silero_vad_ethos_u.pte" \ - -DAUDIO_PATH="$PWD/silero-vad-work/assets/validation.wav" \ - -DVAD_THRESHOLD=0.55 \ - -DPYTHON_EXECUTABLE="$(command -v python)" - {{< /tab >}} - {{< tab header="macOS" language="bash" >}} cmake \ - -S examples/arm/silero_vad_example_ethos_u/runtime \ - -B silero-vad-work/app \ + "${runtime_cmake_args[@]}" \ -DCMAKE_TOOLCHAIN_FILE="$PWD/examples/arm/ethos-u-setup/arm-none-eabi-gcc.cmake" \ - -DET_BUILD_DIR_PATH="$PWD/cmake-out-arm" \ + -DCMAKE_BUILD_TYPE=Release \ -DTARGET_CPU=cortex-m85 \ - -DET_PTE_FILE_PATH="$PWD/silero-vad-work/export/silero_vad_ethos_u.pte" \ - -DAUDIO_PATH="$PWD/silero-vad-work/assets/validation.wav" \ - -DVAD_THRESHOLD=0.55 \ - -DUART0_BASE=0x49303000 \ - -DPYTHON_EXECUTABLE="$(command -v python)" - {{< /tab >}} -{{< /tabpane >}} + -DET_PTE_FILE_PATH="$PWD/silero_vad_ethos_u.pte" \ + -DAUDIO_PATH="$PWD/silero-vad/validation.wav" \ + -B examples/arm/silero_vad_ethos_u \ + -S examples/arm/silero_vad_example_ethos_u/runtime +``` Build the configured application: ```bash -cmake --build silero-vad-work/app \ - --target silero_vad_ethos_u --parallel +cmake --build examples/arm/silero_vad_ethos_u \ + --target silero_vad_ethos_u --parallel 2 ``` -The build creates `silero-vad-work/app/silero_vad_ethos_u`. This ELF image contains both the `.pte` model and the 2.5-second validation clip. +The build creates `examples/arm/silero_vad_ethos_u/silero_vad_ethos_u`. This ELF image contains both the `.pte` model and the 2.5-second validation clip. ## Run Silero VAD on the FVP Run the application and save its simulated UART output to `fvp.log`: ```bash -mkdir -p silero-vad-work/fvp set -o pipefail bash backends/arm/scripts/run_fvp.sh \ - --elf=silero-vad-work/app/silero_vad_ethos_u \ + --elf=examples/arm/silero_vad_ethos_u/silero_vad_ethos_u \ --target=ethos-u85-256 \ - --timeout=300 2>&1 | tee silero-vad-work/fvp/fvp.log + --timeout=300 2>&1 | tee fvp.log ``` -The application prints one speech probability every 32 ms. Near the end, it reports a summary and stops the simulation: - -The output is similar to: +The application processes one audio frame every 32 ms of recording time, then prints the probabilities and a summary. The output is similar to: ```output -1 segments, 79 frames, 2.5s -Speech: 57/79 frames (72.2%) +2 segments, 79 frames, 2.5s +Speech: 56/79 frames (70.9%) +Model executed successfully. Simulation complete, 0 No problems found! ``` @@ -100,8 +90,8 @@ Confirm that the application and its serial log exist: ```bash for artifact in \ - silero-vad-work/app/silero_vad_ethos_u \ - silero-vad-work/fvp/fvp.log; do + examples/arm/silero_vad_ethos_u/silero_vad_ethos_u \ + fvp.log; do test -s "$artifact" || { echo "Missing target artifact: $artifact" >&2 exit 1 diff --git a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/4-validate-output.md b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/4-validate-output.md index 0e9fa57e5d..77f004824f 100644 --- a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/4-validate-output.md +++ b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/4-validate-output.md @@ -15,27 +15,28 @@ Inspect the first probability records and the detected speech segments: ```bash grep -Eo 'PROB .*$' \ - silero-vad-work/fvp/fvp.log | head -n 10 + fvp.log | head -n 10 grep -Eo 'SEGMENT .*$' \ - silero-vad-work/fvp/fvp.log + fvp.log ``` The output is similar to: ```output -PROB 0.000 0.211681 silence -PROB 0.032 0.211681 silence +PROB 0.000 0.062716 silence +PROB 0.032 0.105834 silence ... -PROB 0.224 0.995684 speech +PROB 0.096 0.936824 speech ... -SEGMENT 0.224 2.048 speech +SEGMENT 0.096 0.224 speech +SEGMENT 0.288 1.952 speech ``` -Each `PROB` line contains the frame timestamp in seconds, the probability of speech, and the decision produced with a threshold of `0.55`. Your probability values and speech segments depend on the validation audio. +Each `PROB` line contains the frame timestamp in seconds, the probability of speech, and the decision produced with a threshold of `0.5`. Your probability values and speech segments depend on the validation audio. -The validation question is: Did the host and FVP label every frame the same way? In this run, both the host and the FVP found one speech segment from `0.224` to `2.048` seconds, with no decision mismatches. +The validation question is: Did the host and FVP label every frame the same way? In the reference run, both the host and FVP found speech from `0.096` to `0.224` seconds and from `0.288` to `1.952` seconds, with no decision mismatches. -![Two matching timelines for the same 2.5-second audio clip. The host reference and FVP output both show silence, speech from 0.224 to 2.048 seconds, then silence. All 79 frame decisions match.#center](silero-vad-validation-result.svg "Host and FVP produce the same speech decisions") +![Two matching timelines for the same 2.5-second audio clip. The host reference and FVP output show speech from 0.096 to 0.224 seconds and from 0.288 to 1.952 seconds. All 79 frame decisions match.#center](silero-vad-validation-result.svg "Host and FVP produce the same speech decisions") ## Compare the host and FVP results @@ -43,24 +44,22 @@ Compare the saved host reference with the probabilities in the FVP serial log: ```bash source .venv/bin/activate -python3 examples/arm/silero_vad_example_ethos_u/runtime/compare_vad_probs.py \ - --expected silero-vad-work/export/expected_probs.bin \ - --actual-log silero-vad-work/fvp/fvp.log \ - --threshold 0.55 \ +python examples/arm/silero_vad_example_ethos_u/runtime/compare_vad_probs.py \ + --expected expected_probs.bin \ + --actual-log fvp.log \ --atol 0.25 \ --mean-atol 0.02 ``` -The output is similar to: +The comparison must report 79 probability values and zero threshold mismatches at `0.500`. The expected final output is: ```output -Compared 79 probability values -Max abs error: 0.156800 at frame 22 -Mean abs error: 0.018459 -Threshold mismatches at 0.550: 0 +Validation passed ``` -The comparison confirms that the host and FVP produced the same number of finite probabilities, and the numerical differences stay within tolerance. It also confirms that every frame has the same speech or silence decision. +It checks that both outputs contain the same number of finite probabilities, that maximum and mean absolute errors stay within `0.25` and `0.02`, and that every frame has the same speech or silence decision. + +The exporter restores the recurrent state after generating the reference, so the host and FVP start from the same state. This checks deployment consistency against the quantized host model. Measuring speech-detection accuracy requires labelled audio. ## What you've accomplished diff --git a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/_index.md b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/_index.md index bba418a85b..2d1e5ae872 100644 --- a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/_index.md @@ -13,7 +13,9 @@ learning_objectives: - Validate simulated speech probabilities against a host-generated reference. prerequisites: - - A Linux host using `x86_64` or `arm64`, or an Apple silicon macOS host + - A Linux host using `x86_64` or `arm64` with glibc 2.28 or later, or an Apple silicon host running macOS 15 or later + - On Linux, libstdc++.so.6 providing GLIBCXX_3.4.26 or later for the Corstone-320 FVP + - Python 3.12 with development headers and virtual-environment support - Basic familiarity with PyTorch models and command-line development tools # START generated_summary_faq @@ -32,8 +34,8 @@ generated_summary_faq: faq_source_hash: f56e6a716c01c38d2eb7f19f379804a0bf8d1f3faac81c75121ddb3470cfb799 summary: >- You'll deploy the stateful Silero VAD model on an Arm Ethos-U85 virtual target. - First, you'll prepare ExecuTorch, the model, and audio clips. Then, you'll build host quantized operators, - export a `.pte` model, and create a host reference. Finally, you'll package the model and validation clip + First, you'll prepare ExecuTorch, the model, and validation audio. Then, you'll + export a `.pte` model and create a host reference. Finally, you'll package the model and validation clip into a Cortex-M85 application, run it on the Corstone-320 FVP, and compare its speech decisions with the host reference. faqs: @@ -95,7 +97,7 @@ further_reading: type: documentation - resource: title: Silero VAD Ethos-U example source - link: https://github.com/pytorch/executorch/tree/main/examples/arm/silero_vad_example_ethos_u + link: https://github.com/pytorch/executorch/tree/d78bc1b0ba1c33a90d2d93662de118577f37efd4/examples/arm/silero_vad_example_ethos_u type: repository - resource: title: Silero VAD project diff --git a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/silero-vad-deployment-lanes.svg b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/silero-vad-deployment-lanes.svg index 7843bc0b55..18a950b0d7 100644 --- a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/silero-vad-deployment-lanes.svg +++ b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/silero-vad-deployment-lanes.svg @@ -29,9 +29,9 @@ - Split audio - calibration | - validation + Prepare audio + test.wav + + validation clip diff --git a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/silero-vad-validation-result.svg b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/silero-vad-validation-result.svg index b633a569c4..95d0752c0f 100644 --- a/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/silero-vad-validation-result.svg +++ b/content/learning-paths/embedded-and-microcontrollers/silero-vad-ethos-u/silero-vad-validation-result.svg @@ -1,29 +1,29 @@ Host and FVP produce identical Silero VAD speech decisions - Two matching timelines show silence, then speech from 0.224 to 2.048 seconds, then silence. The host reference and FVP output agree on all 79 frames. + Two matching timelines show speech from 0.096 to 0.224 seconds and from 0.288 to 1.952 seconds. The host reference and FVP output agree on all 79 frames. Host and FVP found the same speech Both processed the same 2.5-second audio clip - Gray = silence • Green = speech + Green = speech: 0.096–0.224 s and 0.288–1.952 s Host reference FVP output 0 s - 0.224 s - 2.048 s + 1 s + 2 s 2.5 s - - - - - - + + + + + + SPEECH diff --git a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/_index.md b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/_index.md index 13cdbe5149..0ca83bb730 100644 --- a/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/tflow_nn_stcube/_index.md @@ -1,5 +1,5 @@ --- -title: Build and run a letter recognition NN model on an STM32L4 Discovery board +title: Run a neural network model on an STM32L4 Discovery board description: Build a letter recognition neural network model using TensorFlow and deploy it on an STM32 B-L475E-IOT01A2 board. diff --git a/content/learning-paths/embedded-and-microcontrollers/uv_debug/2_basics.md b/content/learning-paths/embedded-and-microcontrollers/uv_debug/2_basics.md index 3a4a9e5ae2..db7e31e790 100644 --- a/content/learning-paths/embedded-and-microcontrollers/uv_debug/2_basics.md +++ b/content/learning-paths/embedded-and-microcontrollers/uv_debug/2_basics.md @@ -22,12 +22,12 @@ To use hardware breakpoints in µVision follow the steps outlined below: 1. ![Run](./b_uv4_run.png) **Run (F5)** the application. 1. Bring `Blinky.c` in focus by clicking on its tab. If it is not visible, double-click on it in the **Project** window. 3. In `Blinky.c`, scroll down to the `while (1)` loop in the `main` function near line 42 as shown here: -![Inside the while loop](./mainwhileloop.png) +![Blinky.c source showing the while loop where hardware breakpoints can be set#center](./mainwhileloop.png) 4. Note the darker grey blocks on the left of the line numbers. They indicate assembly code being present and that you can set a hardware breakpoint on these lines. You can also see these blocks in the **Disassembly** window: -![while loop Disassembly](./mainwhileloopdisassembly.png) +![Disassembly window showing machine instructions interleaved with Blinky.c source lines and margin markers for breakpoints#center](./mainwhileloopdisassembly.png) 5. While the program is still running, click to the left of a suitable line in the `while` loop (for example at line 47) and a red circle will be created. This is a hardware breakpoint. The simulated Arm Cortex-M55 has eight hardware breakpoints. μVision will warn you if you exceed this limit. 6. The program will soon stop at the line where you have set the breakpoint on as shown below. The yellow arrow is the current program counter position. This will be the next instruction executed. The cyan arrow is a placeholder you can use to explore the relationship between a source window and the **Disassembly** window. -![Program execution stopped](./mainwhileloopstopped.png) +![Blinky.c stopped at a breakpoint in the while loop, with the yellow program counter arrow beside the next instruction#center](./mainwhileloopstopped.png) 7. Add another breakpoint in `while (1)` (for example at line 50). 8. Each time you click on ![Run](./b_uv4_run.png) **Run (F5)**, the program will cycle to the next breakpoint. @@ -42,7 +42,7 @@ To use hardware breakpoints in µVision follow the steps outlined below: ### Manage Breakpoints 1. Go to **Debug - Breakpoints (Ctrl-B)** to manage breakpoints: -![Manage Breakpoints](./managebkpt.png) +![Breakpoints dialog listing two enabled breakpoints in Blinky.c, with controls to disable or remove them#center](./managebkpt.png) 2. You can temporarily unselect, delete or create breakpoints in this window. It is easier to create a breakpoint by clicking in a source file or the **Disassembly** window. 3. [Watchpoints](#watchpoints) are also created in this window. 4. Select **Kill All** to remove all breakpoints. @@ -64,7 +64,7 @@ When the program is stopped, the list of stacked functions is displayed. This is 1. Stop the program with the ![Stop](./b_uv4_stop.png) **Stop** icon. The program will probably stop in the `Delay` function. 2. Click on the **Call Stack + Locals** window in the bottom right corner of μVision. 3. Inspect the various entries in the Call Stack + Locals window as shown below in this simple example. Local variables are displayed only when they are in scope: - ![Call Stack + Locals Window](./callstacklocals.png) + ![Call Stack and Locals window showing the active function calls and local variables while Blinky is stopped#center](./callstacklocals.png) 1. Set a breakpoint in the `while (1)` loop at line 50 on the `g_ledSet = 1;`. 5. ![Run](./b_uv4_run.png) **Run (F5)** the application. 6. Shortly after, the program will stop there. @@ -72,7 +72,7 @@ When the program is stopped, the list of stacked functions is displayed. This is 9. Note how the variables displayed change in the **Call Stack + Locals** window. 1. ![Step into](./b_uv4_stepinto.png) **Step (F11)** a few more times. 1. Right-click on a function and select either **Show Caller Code** or **Show Callee Code** and this will be highlighted in the **Disassembly** and **source code** windows: - ![Call Stack + Locals Window](./callstacklocals_caller_callee.png) + ![Call Stack and Locals context menu offering Show Caller Code and Show Callee Code for the selected function#center](./callstacklocals_caller_callee.png) 1. ![Step out](./b_uv4_stepout.png) **Step Out (Ctrl+F11)** to exit a function immediately. 1. Remove the **Breakpoint** (by clicking on its red circle ![Breakpoint](./bkpt.png)) to continue. @@ -95,9 +95,9 @@ There is a global variable `g_msTicks` located in `Blinky.c` near line 11 that y 1. ![Run](./b_uv4_run.png) **Run (F5)** the application. 2. Right click on `g_msTicks` in `Blinky.c` near line 11 and select **Add 'g_msTicks' to…** and select **Watch 1**. 3. Go to **View** in the main menu and enable **Periodic Window Update**: -![Periodic Window Update](./periodicwindowupdate.png) +![View menu with Periodic Window Update enabled so Watch values refresh while the program runs#center](./periodicwindowupdate.png) 4. `g_msTicks` will be displayed in **Watch 1**: -![g_msTicks in Watch 1 Window](./gmstickswatch.png) +![Watch 1 window displaying the changing g_msTicks value during program execution#center](./gmstickswatch.png) 5. The values of `g_msTicks` are updated in real-time if *Periodic Window Update* is enabled. 6. You can modify the value in a **Watch** window when the program is stopped or changing slowly. You can modify a variable in a **Memory** window anytime (see next section). @@ -105,7 +105,7 @@ There is a global variable `g_msTicks` located in `Blinky.c` near line 11 that y 1. Click on `` twice and enter: `SystemCoreClock` 2. Right click on the `Value` and deselect **Hexadecimal Display**. 32 MHz will be displayed: -![SystemCoreClock in Watch 1 Window](./systemcoreclockwatch.png) +![Watch 1 window showing SystemCoreClock in decimal as 32 MHz#center](./systemcoreclockwatch.png) {{% notice Note %}} You do not need to stop the program execution to enter variables, raw addresses or structures in a **Watch** or **Memory** window. @@ -118,7 +118,7 @@ You do not need to stop the program execution to enter variables, raw addresses 3. Right click in **Memory 1** and select **Unsigned Long** to see the data field as 32-bit numbers. 4. Add an ampersand `&` in front of the variable name `g_msTicks` and press Enter. Now the physical address is shown (0x2000_0008) in this case. This physical address could change with different compilation optimizations. 5. The data contents of `g_msTicks` is displayed as shown: -![g_msTicks in Memory 1 Window](./gmsticksmemory.png) +![Memory 1 window showing the g_msTicks address and its 32-bit value#center](./gmsticksmemory.png) 6. Right click on the memory data value and select **Modify Memory at 0x3000000C**. Enter a value and this will be pushed into `g_msTicks`. Since `g_msTicks` is updated often, you will only see the new value displayed for a very short time. 7. ![Stop](./b_uv4_stop.png) **Stop** the application. @@ -141,7 +141,7 @@ The example application uses the Arm Cortex-M system tick timer. 1. ![System Viewer](./b_uv4_systemviewer.png) Go to **Peripherals - Core Peripherals** and then select **System Tick Timer S (SysTick)**. 2. The **SysTick Timer** window opens: -![SysTick Timer Window](./systicktimerwindow.png) +![SysTick Timer window showing the LOAD register that controls the timer interval#center](./systicktimerwindow.png) 1. ![Run](./b_uv4_run.png) **Run (F5)** the application. 6. While the program is running, type `0x10000` in the `SysTick -> LOAD` register and click in another register or press Enter. 7. The program execution will speed up. This is the power of Arm CoreSight debugging. @@ -174,12 +174,12 @@ The following does not work with the FVP used in this example. Switch to a real 3. In the **Expression** box, enter g_msTicks==0x44. 4. Select both the **Read** and **Write Access**. 5. Click on **Define** and it will be accepted as shown here: -![Breakpoints Window](./watchpoint.png) +![Breakpoints dialog listing an enabled g_msTicks watchpoint that stops on read or write access at value 0x44#center](./watchpoint.png) 6. Click on Close. 7. ![Run](./b_uv4_run.png) **Run (F5)** the application. 8. When `g_msTicks` equals `0x44`, the program will halt. 9. You will see `g_msTicks` displayed with a value of 0x44 in the Watch window: -![Watch Window](./watchpoint_watch.png) +![Watch window showing g_msTicks at 0x44 after the watchpoint stops execution#center](./watchpoint_watch.png) 10. Delete the watchpoint by selecting **Kill All** in the **Breakpoints** window or use ![Kill all Breakpoints in Current Target](./b_uv4_kill_all_bpnt.png) **Kill all Breakpoints in Current Target**. 11. Select Close. 12. Leave Debug mode. @@ -191,7 +191,7 @@ If you put a RAM address as the expression with no value, the next read and/or w 1. In this example a watchpoint is created with address `0x2000_0008`. 2. Running the program, the first read or write will stop the processor. 3. The [**Command**](https://developer.arm.com/documentation/101407/latest/Debugging/Debug-Windows-and-Dialogs/Command-Window) window shows the setting of this Watchpoint and its execution including the approximate instruction location: -![Command Window](./command_wp_stop.png) +![Command window showing the watchpoint at a stack address and where execution stopped#center](./command_wp_stop.png) {{% notice Notes %}} - Skid: The instruction noted will not be the instruction causing the break as it has not been executed yet. It will be noted in **Disassembly**. diff --git a/content/learning-paths/embedded-and-microcontrollers/uv_debug/3_event_recorder.md b/content/learning-paths/embedded-and-microcontrollers/uv_debug/3_event_recorder.md index 7d27be3a8d..1a44945de6 100644 --- a/content/learning-paths/embedded-and-microcontrollers/uv_debug/3_event_recorder.md +++ b/content/learning-paths/embedded-and-microcontrollers/uv_debug/3_event_recorder.md @@ -22,11 +22,11 @@ layout: "learningpathall" 1. If it is running, ![Stop](./b_uv4_stop.png) **stop** the application and ![Start/Stop Debug](./b_uv4_debug.png) **exit** debug mode (Ctrl+F5). 2. ![Manage Run-Time Environment](./b_uv4_rte.png) Go to **Project - Manage - Run-Time Environment**. 3. Expand **Compiler** and enable **Event Recorder:DAP** and **I/O:STDOUT:EVR** as shown: -![Manage Run-Time Environment Window](./manage_rte.png) +![Manage Run-Time Environment dialog with Event Recorder Semihosting and STDOUT EVR selected under Compiler#center](./manage_rte.png) 5. The **Validation Output** window should be empty. If not, click on the **Resolve** button to enable other missing software components. 7. Click **OK** to close this window. 8. `retarget_io.c`, `EventRecorder.c`, and `EventRecorderConf.h` will be added to your project under the **Compiler** group in the **Project** window: -![New compiler component](./compiler_components.png) +![Project window showing retarget_io.c, EventRecorder.c, and EventRecorderConf.h added to the Compiler group#center](./compiler_components.png) 9. Right click near the top of `Blinky.c` (at line 7), choose **Insert '#include file'**, and select: ```c #include "EventRecorder.h" @@ -60,13 +60,13 @@ Now, create a global variable named `counter` whose value will be printed on **D ``` 3. Go to ![Save All](./b_uv4_save_all.png) **File - Save All**. 3. Go to ![Build](./b_uv4_build_target.png) **Project - Build Target (F7)**. -1. ![Build](./b_uv4_debug.png) **Start a Debug Session (Ctrl+F5)** to enter the µVision debugger. +1. ![Start Debug Session](./b_uv4_debug.png) **Start a Debug Session (Ctrl+F5)** to enter the µVision debugger. 1. ![Run](./b_uv4_run.png) **Run (F5)** the application. 3. ![Debug (printf)](./b_uv4_uart_window.png) Go to **View - Serial Windows** and select **Debug (printf) Viewer**. 4. The values of counter are displayed as seen here: - ![Output in the Debug (printf) Viewer window](./debug_printf_viewer_counter.png) + ![Debug printf Viewer displaying successive Hello messages with changing counter values#center](./debug_printf_viewer_counter.png) 5. Go to **View - Analysis Windows** and select **Event Recorder** to see information about the printf statements: - ![printf information in the Event Recorder window](./evtrec_window.png) + ![Event Recorder window listing stdout events generated by the printf statements#center](./evtrec_window.png) 8. ![Stop](./b_uv4_stop.png) **Stop** the program and ![Start/stop debug](./b_uv4_debug.png) **exit** debug mode (Ctrl+F5). {{% notice Notes %}} @@ -94,11 +94,11 @@ With Event Recorder, you can also annotate your source code which can be display ``` 3. Go to ![Save All](./b_uv4_save_all.png) **File - Save All**. 3. Go to ![Build](./b_uv4_build_target.png) **Project - Build Target (F7)**. -1. ![Build](./b_uv4_debug.png) **Start a Debug Session (Ctrl+F5)** to enter the µVision debugger. +1. ![Start Debug Session](./b_uv4_debug.png) **Start a Debug Session (Ctrl+F5)** to enter the µVision debugger. 5. Go to **View - Analysis Windows** and select ![Event Recorder](./b_uv4_event_recorder.png) **Event Recorder** to see the event coming in. 1. ![Run](./b_uv4_run.png) **Run (F5)** the application. 5. Events will start to display in the **Event Recorder** window as shown below: - ![Events shown in the Event Recorder window](./evtrec_evt_annotations.png) + ![Event Recorder window showing EventRecord2 annotations alongside stdout frames#center](./evtrec_evt_annotations.png) 6. You can see the results of the `EventRecord2(3, 44, counter);` event. 7. `printf` frames (stdout) are also displayed with the printf data in hexadecimal form. @@ -114,7 +114,7 @@ With Event Recorder, you can also annotate your source code which can be display 2. Right-click on the first in a sequence of stdout frames and select **Set Time Reference**. The selected frame will turn from blue to green. 4. Position your mouse pointer on the **Time (sec)** column on the next event frame. 5. A box will open displaying the elapsed time. It took about one second from the start of this printf to the next one: - ![Time reference in the Event Recorder window](./evtrec_time_ref.png) + ![Event Recorder window with a green time reference frame and elapsed time shown for the next stdout event#center](./evtrec_time_ref.png) 6. Enable the Event Recorder so the frames continue to be captured and displayed. 7. You can use this feature to time many different events in your code. @@ -129,13 +129,13 @@ It is possible to filter the window contents. Modify the information displayed i 1. If running, ![Stop](./b_uv4_stop.png) **stop** the program if necessary but stay in debug mode. 2. In the Event Recorder window, select ![Configure Target Event Recording](./b_uv4_er_funnel.png) **Configure Target Event Recording**. The **Show Event Levels** window opens up: - ![Filter event levels](./show_evt_lvls.png) + ![Show Event Levels dialog with STDIO event-level checkboxes cleared to filter printf frames#center](./show_evt_lvls.png) 3. Unselect all boxes opposite STDIO as shown above. 4. Click **OK** to close this window. 5. Click ![Clear](./b_uv4_er_killall.png) **Clear** to make it easier to see what is happening. 1. ![Run](./b_uv4_run.png) **Run (F5)** the application. 7. The Event Recorder window no longer contains printf frames as shown below: - ![Filtered Event Recorder window](./evtrec_window_filtered.png) + ![Event Recorder window after filtering, showing annotated events without stdout frames#center](./evtrec_window_filtered.png) In this case, you only need to unselect the Op column. The other frames do not exist in this simple example. #### Save Filter Settings @@ -188,11 +188,11 @@ The above is not required in simulation! ``` 3. Go to ![Save All](./b_uv4_save_all.png) **File - Save All**. 3. Go to ![Build](./b_uv4_build_target.png) **Project - Build Target (F7)**. -1. ![Build](./b_uv4_debug.png) **Start a Debug Session (Ctrl+F5)** to enter the µVision debugger. +1. ![Start Debug Session](./b_uv4_debug.png) **Start a Debug Session (Ctrl+F5)** to enter the µVision debugger. 5. Go to **View - Analysis Windows** and select ![Event Statistics](./b_uv4_event_statistics.png) **Event Statistics** to see the event coming in. 1. ![Run](./b_uv4_run.png) **Run (F5)** the application. 4. The **Event Statistics** window displays timing data between the start and stop function calls: - ![Event Statistics window](./evtstat_window.png) + ![Event Statistics window showing Group A slot 11 with minimum, maximum, and average execution times#center](./evtstat_window.png) 5. Event Group A is displayed, showing slot 11 as indicated. 6. The execution time statistics are displayed for minimum, maximum, and average execution times (do not differ in simulation). This makes it easy for you to determine these statistical values for locations in your sources. diff --git a/content/learning-paths/embedded-and-microcontrollers/uv_debug/4_swv.md b/content/learning-paths/embedded-and-microcontrollers/uv_debug/4_swv.md index fb9ccc55de..dacecb319a 100644 --- a/content/learning-paths/embedded-and-microcontrollers/uv_debug/4_swv.md +++ b/content/learning-paths/embedded-and-microcontrollers/uv_debug/4_swv.md @@ -40,7 +40,7 @@ layout: "learningpathall" 1. Go to ![Options for Target](./b_uv4_target_options.png) **Project - Options for Target... (Alt+F7)** and switch to the **Debug** tab. 2. On the right-hand side, select your debug adapter and click on **Settings**. The **Target Driver Setup** window opens. 3. Select the **Trace** tab: - ![Target Driver Setup Window](./target_driver_setup.png) + ![Target Driver Setup Trace tab with Core Clock, Trace Enable, and exception tracing settings for SWV#center](./target_driver_setup.png) 4. In **Core Clock:** enter the correct core clock of your target device. This value will be used for calculating timing values. 5. If your device runs a different trace clock, you might need to unselect **Use Core Clock** and enter the correct trace clock speed. The SWO clock speed will be calculated from this. This value is crucial for a ULINK2/ULINKplus or J-Link as a UART is used. ULINKpro does not use this for the connection. 4. Select Trace Enable. @@ -57,11 +57,11 @@ layout: "learningpathall" The [**Trace Exceptions**](https://developer.arm.com/documentation/101407/latest/Debugging/Debug-Windows-and-Dialogs/Trace-Exceptions) window displays statistical data about the traced exceptions and interrupts. -1. ![Build](./b_uv4_debug.png) **Start a Debug Session (Ctrl+F5)** to enter the µVision debugger. +1. ![Start Debug Session](./b_uv4_debug.png) **Start a Debug Session (Ctrl+F5)** to enter the µVision debugger. 5. Go to **View - Trace** and select **Trace Exceptions**. 1. ![Run](./b_uv4_run.png) **Run (F5)** the application. 3. In this window, click on the Count column name to bring 15 SysTick into view: - ![Trace Exceptions Window](./trace_exceptions_window.png) + ![Trace Exceptions window showing SysTick exception 15 counts and timing statistics during execution#center](./trace_exceptions_window.png) 4. The values are updating while the application runs. Unselect EXCTRC: box to stop the updates. 5. Various statistics are displayed. Any other active exceptions will automatically be displayed (not in this example). 6. *Period*=14.13/14122 = 1 ms. Thus, the SysTick is generating an interrupt every millisecond. @@ -73,7 +73,7 @@ The [**Trace Data**](https://developer.arm.com/documentation/101407/latest/Debug 1. Go to **View - Trace** and select **Trace Data**. 1. ![Run](./b_uv4_run.png) **Run (F5)** the application and stop is after a little while. 3. The **SysTick** exception is shown in Trace Data: - ![Trace Data Window](./trace_data_window.png) + ![Trace Data window showing SysTick exception entry, exit, and return records#center](./trace_data_window.png) 5. It is displayed with Entry, Exit and Return points: **Entry**: when the exception or interrupt is entered. **Exit**: when the exception or interrupt exits. @@ -89,13 +89,13 @@ LA shares the comparators in CoreSight with the **Watchpoints**. They are mutual 3. In the LA, click on **Setup**. In **Display Range** set **Max:** to 0x0F. Click on **Close**. The LA is now configured. You can also configure LA while the program is running. 4. ![Run](./b_uv4_run.png) **Run (F5)** the application. 5. `counter` will still be visible in Watch 1 but also shown in LA: - ![Logic Analyzer Window](./la_window.png) + ![Logic Analyzer window plotting changes to the counter variable while Blinky runs#center](./la_window.png) 6. Adjust the Zoom using **Out** or the **All** icon in the LA to provide a suitable scale. If no waveform is displayed in the LA, exit and re-enter debug mode to refresh the comparators. 7. Enable **Amplitude**, **Signal Info** and **Cursor** in the LA window. 8. Select ![Stop](./b_uv4_stop.png) **Stop** in the Update Screen to stop the LA from collecting data. The program keeps running. 9. Click on a spot in the LA and position the mouse to demonstrate the method used to measure timing values: - ![Logic Analyzer Window Measurements](./la_window_measure.png) + ![Logic Analyzer window with cursors positioned on the counter waveform to measure elapsed time#center](./la_window_measure.png) {{% notice Notes %}} - When you enter a variable in the Logic Analyzer window, it is also displayed in the Trace Records (or Trace Data for ULINKpro). A ULINK2, ULINKplus or ULINKpro displays the data writes. J-Link does not. ULINKplus and ULINKpro also display any LA waveforms in ![System Analyzer Window](./b_uv4_system_analyzer.png) **System Analyzer** window. @@ -114,13 +114,13 @@ timing values: 1. ![Run](./b_uv4_run.png) **Run (F5)** the application for a little while. The values are updating while the application runs. User the lock icon to stop the updates. 3. In the window, use the scroll wheel of your mouse to zoom in on **SysTick(15)** in the **Exceptions** section. 4. Use the green and red markers to verify that the SysTick interrupts are one millisecond apart and that the `counter` variable value is shown in the **Data Watch** section: - ![System Analyzer Window](./system_analyzer_window.png) + ![System Analyzer timeline with markers one millisecond apart, counter at 9 in Data Watch, and core clock at 100 MHz#center](./system_analyzer_window.png) 4. Depending on the debug adapter you are using, you can also verify the correct **Core Clock** setting under **System**. ULINKplus is capable of measuring this value while the application is running. #### Where are the other Events and printf? These events are there. In this simple program these events are occurring very close together. If you expand the X axis further, the events will be displayed as shown here: - ![System Analyzer Window](./sa_evtrec.png) + ![System Analyzer timeline zoomed in to show Event Recorder StartA and StopA annotations with stdout events#center](./sa_evtrec.png) You can activate the cursors and make various timing measurements. {{% notice Notes %}} diff --git a/content/learning-paths/embedded-and-microcontrollers/uv_debug/5_etm_trace.md b/content/learning-paths/embedded-and-microcontrollers/uv_debug/5_etm_trace.md index 87807faa84..78bc0e2e89 100644 --- a/content/learning-paths/embedded-and-microcontrollers/uv_debug/5_etm_trace.md +++ b/content/learning-paths/embedded-and-microcontrollers/uv_debug/5_etm_trace.md @@ -37,7 +37,7 @@ The Corstone-300 FVP can be used to collect instruction trace data with a two-st 2. On the right-hand side, select **Models ARMv8-M Debugger** and click on **Settings**. The **Models ARMv8-M Target Driver Setup** window opens. 3. Select a coverage file (enter a name for the file and it will be generated automatically). 1. Enable **Load Coverage on Connect** and **Store Coverage on Disconnect**: - ![Models ARMv8-M Target Driver Setup](./models_coverage_setup.png) + ![Models ARMv8-M Target Driver Setup with a coverage file selected and Load and Store Coverage options enabled#center](./models_coverage_setup.png) 1. Click **OK** twice. When you now start a debug session, the coverage information is recorded. When you leave the debug session, the data is written to the coverage file specified. Next time you start a debug session, the file is opened and the information is used. This method does not provide [Instruction Trace](#instruction-trace) data, so you can move directly to the [Code Coverage](#code-coverage) section. @@ -50,7 +50,7 @@ For the following, you need to connect a [ULINKpro](https://developer.arm.com/To 1. Remove any **Initialization File** that might be present. 2. On the right-hand side, select **ULINK Pro Cortex Debugger** and click on **Settings**. The **Target Driver Setup** window opens. 3. Select the **Trace** tab: - ![Target Driver Setup Window](./target_driver_setup_pro.png) + ![ULINKpro Target Driver Setup Trace tab with ETM tracing enabled and the 4-bit sync trace port selected#center](./target_driver_setup_pro.png) 4. In **Core Clock:** enter the correct core clock of your target device. This value will be used for calculating timing values. 5. If your device runs a different trace clock, you might need to unselect **Use Core Clock** and enter the correct trace clock speed. 4. Select **Trace Enable** for SWV data trace. @@ -68,7 +68,7 @@ Instruction trace enables the review of historical data of the application execu 1. **Start a Debug Session (Ctrl+F5)** to enter the µVision debugger. 1. ![Open Trace Data Window](./b_uv4_instructiontrace.png) Go to **View - Trace** and select **Trace Data**. 2. The Trace Data window will look similar to the one below if it is working correctly: - ![ETM Trace Data Window](./etm_trace_data_window.png) + ![Trace Data window listing captured ETM instruction frames with addresses, function names, and source lines#center](./etm_trace_data_window.png) 3. If you do not see trace frames in this window, go to **View - Trace** and select **Enable Trace Recording**. {{% notice Notes %}} @@ -80,7 +80,7 @@ Instruction trace enables the review of historical data of the application execu #### View ETM Frames starting at Reset 1. Scroll to the top of the **Trace Data** window to the first frame. This is the first instruction executed after the initial RESET sequence. In this case, it is a `LDR` instruction in the RESET_Handler function as shown below: - ![ETM Trace from Reset](./etm_trace_from_reset.png) + ![First ETM trace frame after reset showing an LDR instruction in RESET_Handler#center](./etm_trace_from_reset.png) 2. If you use the **Memory** window to look at location `0x04`, you will find the address of the first instruction there and this will match with that displayed in the first frame. In my case it is `0x1A00_1330 + 1 = 0x1A00_1331` (+1 says it is a [Thumb® instruction](https://developer.arm.com/documentation/ddi0210/c/Introduction/Architecture/The-Thumb-instruction-set)). The first occurrence in a function is highlighted in orange to make the start of functions easier to find. 3. Any source code associated with an instruction is displayed in the **Src Code / Trigger Addr** column. 4. If you double-click on any line, this will be highlighted in both the **Disassembly** and the relevant source window. @@ -96,7 +96,7 @@ Capturing all the instructions executed is possible in simulation and with ULINK *Trace Filters* In the **Trace Data** window, you can select various types of frames to be displayed. The **Display:** box shows the various options available: -![Trace Data Display Filtering](./trace_data_display.png) +![Trace Data Display menu showing the available frame filters for captured trace records#center](./trace_data_display.png) {{% notice Note %}} These filters are post collection. @@ -105,12 +105,12 @@ These filters are post collection. #### Find a Trace Record In the **Find a Trace Record** box, enter `bx` as shown here: -![Find a Trace Record Box](./find_trace_record_box.png) +![Find a Trace Record box with bx entered as the instruction to search for#center](./find_trace_record_box.png) You can select properties/columns where you want to search in the “in” box. Select the **Find a Trace Record** icon ![Find a Trace Record Icon](./b_uv4_find2.png) and the following screen opens: -![Find a Trace Record Window](./find_trace_record_window.png) +![Find a Trace Record dialog ready to search trace frames for the bx instruction#center](./find_trace_record_window.png) Click on **Find Next** and each time it will step through the trace records highlighting each occurrence of the instruction `bx`. Or you can press "Enter" to go to the next occurrence of the search term. @@ -124,7 +124,7 @@ The data stream capture can be controlled and filtered by using tracepoints. μV They are selected from the context menu by right-clicking on a valid assembly instruction in the **Disassembly** window or a **C source line**: -![Insert Trace Point](./insert_tracepoint.png) +![Source context menu offering TraceRun, TraceSuspend, and TraceHalt tracepoint commands#center](./insert_tracepoint.png) #### How it works @@ -146,15 +146,15 @@ Setup the Trace Triggers: 7. Select **Insert Tracepoint at or near line 1120** and select **TraceRun (ETM)**. A cyan T will appear next to that line. 8. Right-click on the gray (or green) block opposite line 1136 `return ret;`. 9. Select **Insert Tracepoint at line 1136** and select **TraceSuspend (ETM)**. A cyan T will appear: - ![Tracepoints set](./eventrecord2.png) + ![EventRecorder.c source with TraceRun at the start of EventRecord2 and TraceSuspend at its return statement#center](./eventrecord2.png) 10. Clear the **Trace Data** window for convenience. This is an optional step. 11. ![Run](./b_uv4_run.png) **Run (F5)** the application and after a few seconds click ![Stop](./b_uv4_stop.png) **Stop**. 12. Filter exceptions out by selecting **ETM – Code Exec** in the **Display:** in the **Trace Data** window: - ![Display Code Exec](./disp_code_exec.png) + ![Trace Data Display filter set to ETM Code Exec to show executed instructions without exception frames#center](./disp_code_exec.png) 13. Examine the **Trace Data** window: - ![Tracepoints](./tracepoints.png) + ![Trace Data window showing instruction frames captured between the TraceRun and TraceSuspend points#center](./tracepoints.png) 14. In the [**Command**](https://developer.arm.com/documentation/101407/latest/Debugging/Debug-Windows-and-Dialogs/Command-Window) window, enter `TL` and press "Enter" to display the two tracepoints: - ![Tracepoints List](./tracepoint_tl.png) + ![Command window listing the two tracepoints after the TL command#center](./tracepoint_tl.png) 15. Enter `TK*` in the **Command** window and press "Enter" to delete all tracepoints. #### Trace Skid @@ -171,7 +171,7 @@ The µVision debugger provides **Code Coverage** with the FVP or via ETM using U 1. In Blinky.c, set a breakpoint at `if (g_ledSet) {` (near line 56). 2. ![Run](./b_uv4_run.png) **Run (F5)** the application. Once it hits the breakpoint, it stops. Remove the breakpoint. 3. Examine the **Disassembly** and **Blinky.c** windows. Scroll and notice the different color blocks in the left margin: - ![Code Coverage in Disassembly](./cc_disassembly.png) + ![Disassembly window with green code-coverage marks beside executed instructions and grey marks beside unexecuted instructions#center](./cc_disassembly.png) This is **Code Coverage** provided by ETM trace. This indicates if an instruction has been executed or not. Color blocks indicate which assembly instructions have been executed: @@ -193,12 +193,12 @@ Code Coverage is visible in both the **Disassembly** and **source code** windows A separate [**Code Coverage**](https://developer.arm.com/documentation/101407/latest/Debugging/Debug-Windows-and-Dialogs/Code-Coverage) window is available that shows statistics. ![Open Code Coverage Window](./b_uv4_code_coverage.png) Go to **View - Analysis Windows** and select **Code Coverage**: -![Code Coverage Window](./cc_window.png) +![Code Coverage window showing instruction coverage statistics for the Blinky project#center](./cc_window.png) #### Code Coverage Data in the Command Window In the [**Command**](https://developer.arm.com/documentation/101407/latest/Debugging/Debug-Windows-and-Dialogs/Command-Window) window, you can show Code Coverage in many ways, for example, enter `coverage \Blinky.c\main details`: -![Code Coverage in Command Window](./cc_cmd_window.png) +![Command window displaying detailed code coverage results for the Blinky main function#center](./cc_cmd_window.png) {{% notice Note %}} You can copy and paste the result from the **Command** window by right-clicking into it. Save the data in a convenient format. @@ -237,13 +237,13 @@ It is useful to optimize your code for speed. [**Performance Analyzer**](https:/ The µVision debugger provides **Performance Analysis** with the FVP (number of calls only) or via ETM using ULINKpro. 1. ![Start/Stop Debug](./b_uv4_debug.png) **Start a Debug Session (Ctrl+F5)** to enter the µVision debugger. -2. ![Open Code Coverage Window](./b_uv4_performance.png) Go to **View - Analysis Windows** and select **Performance Analysis**: - ![Performance Analyzer Window](./pa_window.png) +2. ![Open Performance Analyzer](./b_uv4_performance.png) Go to **View - Analysis Windows** and select **Performance Analysis**: + ![Performance Analyzer window showing function call counts before the application starts running#center](./pa_window.png) 4. *Do not* click on ![Run](./b_uv4_run.png) **Run (F5)** yet! 4. Expand some of the module names as shown above. 5. Shown is the number of calls and percentage of total time in this short run from reset to the start of `main()`. 6. Now, ![Run](./b_uv4_run.png) **Run (F5)** the application. The **Performance Analyzer** window updates in real-time: - ![Performance Analyzer Updated](./pa_update.png) + ![Performance Analyzer window updated with function call counts and time percentages while Blinky runs#center](./pa_update.png) {{% notice Notes %}} - Double-click on any **Function** or **Module** name and this will be highlighted in the **Disassembly** or **source code** windows. @@ -262,7 +262,7 @@ The µVision debugger provides **Execution Profiling** with the FVP (number of c 3. ![Run](./b_uv4_run.png) **Run (F5)** the application. 4. In the left margin of the **Disassembly** and **source code** windows various time values will be displayed. 8. Hover the cursor over a time and more information appears as in the yellow box here: - ![Execution Profiling](./ep_window.png) + ![Source view with execution times in the margin and a tooltip showing profiling details for one line#center](./ep_window.png) 9. You can also select **Debug - Execution Profiling - Show Calls** to display this information rather than the execution times in the left margin. #### Outlining @@ -270,7 +270,7 @@ The µVision debugger provides **Execution Profiling** with the FVP (number of c At some places, there is an ![Outlining](./outlining.png) **Outlining** sign that can be used to collapse a code section and to compress the associated source files together. 1. In `Blinky.c`, click in the **Outlining** square near the `while (1) {` loop near line 46: - ![Outlining in action](./outlining_in_action.png) + ![Blinky.c source with the while loop collapsed and its execution time combined into one margin value#center](./outlining_in_action.png) 2. The C source in the while loop is now collapsed into one line and times are added together to 8.380 us in this case. 5. Click on ![Expand Outlining](./outlining_plus.png) to expand it. @@ -287,7 +287,7 @@ Some of the hardest problems to solve are those when a crash has occurred and yo Modern programs tend to be asynchronous with interrupts and RTOS thread switching plus unexpected and spurious events. Having a recording of the program flow is useful especially when a problem occurs and the consequences are not immediately visible. Another problem is detecting race conditions and determining how to fix them. ETM trace handles these problems and others easily and it is easy to use. In this example, a hard fault occurs and the CPU ends up at `0x1A00_133A` as shown in the **Disassembly** window (note that your address might not be the same as shown here): -![Hard Fault Handler](./hard_fault_handler.png) +![Disassembly window stopped at the branch to HardFault_Handler, with the current instruction highlighted#center](./hard_fault_handler.png) This is the `HardFault_Handler`. This is a branch to itself and will run this instruction forever. The trace buffer will save millions of the same branch instructions, which is not very useful. @@ -309,7 +309,7 @@ The hard fault handler exception vector is found in the startup file of your dev 9. In the **Trace Data** window, you will find the `EventRecord2` function with the `POP` instruction at the end. When the function tried to return, the bogus value of LR caused a hard fault. 1. The `B` instruction at the hard fault vector was not executed because CoreSight hardware breakpoints do not execute the instruction they are set to when they stop the program. They are no-skid breakpoints. 1. ![Step into](./b_uv4_stepinto.png) **Step (F11)** once. You will now see the hard fault branch as shown in the bottom screen: -![Trace Data Window with Hard Fault Handler](./trace_data_window_w_hf.png) +![Trace Data window showing the return from EventRecord2 followed by the branch to HardFault_Handler#center](./trace_data_window_w_hf.png) This example shows how quickly ETM trace can help debug program flow bugs. diff --git a/content/learning-paths/embedded-and-microcontrollers/uv_debug/6_power_ulplus.md b/content/learning-paths/embedded-and-microcontrollers/uv_debug/6_power_ulplus.md index 0d6b93c293..dbdae9c098 100644 --- a/content/learning-paths/embedded-and-microcontrollers/uv_debug/6_power_ulplus.md +++ b/content/learning-paths/embedded-and-microcontrollers/uv_debug/6_power_ulplus.md @@ -24,25 +24,25 @@ You can configure the ULINKplus adapter with a set of debug commands that you ca 1. Copy this file into your project directory (where the `*.uvprojx` file is located). 1. Go to ![Options for Target](./b_uv4_target_options.png) **Project - Options for Target... (Alt+F7)** and switch to the **Debug** tab. 6. Click on **...** in the **Initialization File:** box, and add the file: - ![Debug_UlinkPlus.ini added to the target options](./debug_ulpl_ini.png) + ![Options for Target Debug tab with Debug_UlinkPlus.ini selected as the initialization file#center](./debug_ulpl_ini.png) 6. Click **Edit...** to open the file. 9. Click **OK** to close the target options window and return to the main μVision window. 1. Click on the **Configuration Wizard** tab at the bottom of `Debug_UlinkPlus.ini` window. 1. Enable **ULINK Power Measurement** and select the value of your shunt resistor (in this case 2.55 Ohm/100 mA): - ![Debug_UlinkPlus.ini configuration](./debug_ulpl_ini_config.png) + ![Debug_UlinkPlus.ini Configuration Wizard with ULINK Power Measurement enabled and a 2.55 Ohm shunt selected#center](./debug_ulpl_ini_config.png) 3. Go to ![Save All](./b_uv4_save_all.png) **File - Save All**. ULINKplus is now configured and ready to use. #### Measure Power in System Analyzer -1. ![Build](./b_uv4_debug.png) **Start a Debug Session (Ctrl+F5)** to enter the µVision debugger. +1. ![Start Debug Session](./b_uv4_debug.png) **Start a Debug Session (Ctrl+F5)** to enter the µVision debugger. 1. ![Run](./b_uv4_run.png) **Run (F5)** the application. 1. If the window is not already open, go to **View - Analysis Windows** and select ![System Analyzer Window](./b_uv4_system_analyzer.png) **System Analyzer**. 6. Click the ![Lock](./b_uv4_unlock.png) lock symbol to stop the data collection. 9. Zoom in or out with or your mouse wheel. First, click in the waveform to set an anchor point. 5. You will have a window similar to the one below: - ![System Analyzer with Power Consumption](./sa_w_power.png) + ![System Analyzer Consumption section plotting current and voltage waveforms during Blinky execution#center](./sa_w_power.png) The **Current** and **Voltage** waveforms are shown in the **Consumption** section. @@ -54,7 +54,7 @@ The **Current** and **Voltage** waveforms are shown in the **Consumption** secti #### Power Consumption in the Event Statistics Window 1. Go to **View - Analysis Windows** and select ![Event Statistics](./b_uv4_event_statistics.png) **Event Statistics** to open the window: - ![Event Statistics with Power Consumption](./evtstat_window_w_power.png) + ![Event Statistics window showing Group A timing, voltage, current, and charge measurements for the selected slot#center](./evtstat_window_w_power.png) The various statistics are displayed and updated in real-time. Timings, voltage, current and charges consumed are recorded according to Groups and Slots. These are created by the `EventStartX()` and `EventStopX()` function calls in the application code. @@ -66,7 +66,7 @@ The CoreSight debug components consume some amount of power. This will be reflec 1. Go to ![Energy Measurement](./b_uv4_energy.png) **Debug - Energy Measurement without Debug**. 2. The debug session starts and you can ![Run](./b_uv4_run.png) **Run (F5)** the application as usual. 4. System Analyzer will display the waveform as shown below: - ![Energy Measurement without Debug](./energy_wo_debug.png) + ![System Analyzer in Energy Measurement without Debug mode showing current and voltage waveforms without code trace#center](./energy_wo_debug.png) {{% notice Note %}} Any features needing CoreSight will not be available. diff --git a/content/learning-paths/embedded-and-microcontrollers/zephyr_cs320_mps4/_index.md b/content/learning-paths/embedded-and-microcontrollers/zephyr_cs320_mps4/_index.md index e48adcc7d3..353bb94de3 100644 --- a/content/learning-paths/embedded-and-microcontrollers/zephyr_cs320_mps4/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/zephyr_cs320_mps4/_index.md @@ -1,5 +1,5 @@ --- -title: Port Zephyr RTOS and run applications on the Arm Corstone-320 MPS4 platform +title: Port Zephyr RTOS to the Arm Corstone-320 MPS4 description: Port Zephyr RTOS to the Arm Corstone-320 MPS4 FPGA platform by creating board support files and device tree configuration, then build and run a hello_world sample on the physical board. minutes_to_complete: 45 diff --git a/content/learning-paths/embedded-and-microcontrollers/zephyr_ml_cs320_mps4/_index.md b/content/learning-paths/embedded-and-microcontrollers/zephyr_ml_cs320_mps4/_index.md index fe04a6501b..6e8761cefb 100644 --- a/content/learning-paths/embedded-and-microcontrollers/zephyr_ml_cs320_mps4/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/zephyr_ml_cs320_mps4/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy a Zephyr-based machine learning application on Arm Corstone-320 MPS4 with ExecuTorch +title: Deploy a Zephyr ML app on Corstone-320 MPS4 with ExecuTorch description: Deploy a quantized PyTorch model with ExecuTorch in a Zephyr application on Corstone-320 MPS4 and verify inference. minutes_to_complete: 45 diff --git a/content/learning-paths/embedded-and-microcontrollers/zephyr_ml_cs320_mps4/how-to-2.md b/content/learning-paths/embedded-and-microcontrollers/zephyr_ml_cs320_mps4/how-to-2.md index 8aae9ab140..97e23d4e39 100644 --- a/content/learning-paths/embedded-and-microcontrollers/zephyr_ml_cs320_mps4/how-to-2.md +++ b/content/learning-paths/embedded-and-microcontrollers/zephyr_ml_cs320_mps4/how-to-2.md @@ -1,5 +1,5 @@ --- -title: Build and run a machine learning application on the Corstone-320 MPS4 platform +title: Build and run an ML app on Corstone-320 MPS4 description: Port and build the ExecuTorch sample on Corstone-320 MPS4, then run it to verify inference. weight: 3 diff --git a/content/learning-paths/laptops-and-desktops/dgx_persistent_agent/_index.md b/content/learning-paths/laptops-and-desktops/dgx_persistent_agent/_index.md index c09a179a75..89144b8c31 100644 --- a/content/learning-paths/laptops-and-desktops/dgx_persistent_agent/_index.md +++ b/content/learning-paths/laptops-and-desktops/dgx_persistent_agent/_index.md @@ -1,5 +1,5 @@ --- -title: Orchestrate a persistent local AI agent with Hermes on NVIDIA DGX Spark +title: Orchestrate a local Hermes AI agent on NVIDIA DGX Spark description: Learn how to build a continuously running local AI agent on NVIDIA DGX Spark by combining Hermes Agent, Ollama, and Qdrant to handle event-driven document ingestion, semantic memory, and contextual retrieval using Arm Grace CPUs. diff --git a/content/learning-paths/laptops-and-desktops/dgx_spark_isaac_robotics/_index.md b/content/learning-paths/laptops-and-desktops/dgx_spark_isaac_robotics/_index.md index fc3206bce5..2093769e73 100644 --- a/content/learning-paths/laptops-and-desktops/dgx_spark_isaac_robotics/_index.md +++ b/content/learning-paths/laptops-and-desktops/dgx_spark_isaac_robotics/_index.md @@ -1,5 +1,5 @@ --- -title: Build Robot Simulation and Reinforcement Learning Workflows with Isaac Sim and Isaac Lab on DGX Spark +title: Build robotics workflows with Isaac Sim and Isaac Lab on DGX Spark description: Learn how to build and deploy high-fidelity robotic simulations and reinforcement learning pipelines using Isaac Sim and Isaac Lab on Arm-based NVIDIA DGX Spark with Grace-Blackwell architecture. diff --git a/content/learning-paths/laptops-and-desktops/dgx_spark_isaac_robotics2/3_multiagent_policies.md b/content/learning-paths/laptops-and-desktops/dgx_spark_isaac_robotics2/3_multiagent_policies.md index 547692e865..d5c688ee4b 100644 --- a/content/learning-paths/laptops-and-desktops/dgx_spark_isaac_robotics2/3_multiagent_policies.md +++ b/content/learning-paths/laptops-and-desktops/dgx_spark_isaac_robotics2/3_multiagent_policies.md @@ -1,5 +1,5 @@ --- -title: Train multiple agents to coordinate two Shadow Hands in one simulation +title: Train agents to coordinate two Shadow Hands in one simulation description: Train and compare MAPPO and IPPO policies for coordinated Shadow Hand object transfer with Isaac Lab on DGX Spark. weight: 4 diff --git a/content/learning-paths/laptops-and-desktops/dgx_spark_llamacpp/1_gb10_introduction.md b/content/learning-paths/laptops-and-desktops/dgx_spark_llamacpp/1_gb10_introduction.md index e3902ca04b..6a2b94bee6 100644 --- a/content/learning-paths/laptops-and-desktops/dgx_spark_llamacpp/1_gb10_introduction.md +++ b/content/learning-paths/laptops-and-desktops/dgx_spark_llamacpp/1_gb10_introduction.md @@ -1,5 +1,5 @@ --- -title: Explore Grace Blackwell architecture for efficient quantized LLM inference +title: Explore Grace Blackwell for quantized LLM inference weight: 2 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/1-serve.md b/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/1-serve.md index 36dae9751e..61081f9df8 100644 --- a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/1-serve.md +++ b/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/1-serve.md @@ -45,22 +45,32 @@ sudo apt install build-essential cmake git curl python3.12-venv Docker Engine must already be running and accessible to your SSH account. Follow the [Docker Engine install guide](/install-guides/docker/docker-engine/) if needed, and reconnect after changing group membership. -Download the six Learning Path files from the Arm Learning Paths GitHub repository once into `~/muse-agent`: the prerequisite checker, dependency constraints, Hermes setup helper, broken release checker, acceptance tests, and release-summary image. Download these files before downloading source or model weights: +Download the code archive from the Arm Learning Code Examples GitLab repository and the release-summary image from the Learning Path repository once into `~/muse-agent`. The archive contains the prerequisite checker, dependency constraints, Hermes setup helper, broken release checker, acceptance tests, README, and licence. Download these files before downloading source or model weights: ```bash mkdir -p ~/muse-agent cd ~/muse-agent -LP_FILES=https://raw.githubusercontent.com/ArmDeveloperEcosystem/arm-learning-paths/main/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files ( set -e + EXAMPLES_REV=d0905fd84ac1e23c8c0ecb5c8052bc6ac06523ce + EXAMPLES_PATH=learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes + EXAMPLES_URL="https://gitlab.arm.com/learning-code-examples/code-examples/-/archive/$EXAMPLES_REV/code-examples-$EXAMPLES_REV.tar.gz?path=$EXAMPLES_PATH" + LP_FILES=https://raw.githubusercontent.com/ArmDeveloperEcosystem/arm-learning-paths/main/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files + download_dir=$(mktemp -d) + trap 'rm -rf "$download_dir"' EXIT + curl --fail --location --remove-on-error "$EXAMPLES_URL" -o "$download_dir/examples.tar.gz" + tar -xzf "$download_dir/examples.tar.gz" -C "$download_dir" + checker=$(find "$download_dir" -type f -name check-prereqs.sh -print -quit) + test -n "$checker" + cp "$(dirname "$checker")"/* . + curl --fail --location --remove-on-error "$LP_FILES/release-summary.png" -o release-summary.png for file in check-prereqs.sh executorch-constraints.txt setup-hermes-cli.sh release_tool.py test_release_tool.py release-summary.png; do - curl --fail --location --remove-on-error "$LP_FILES/$file" -o "$file" test -s "$file" done ) ``` -The parenthesized block stops at the first failed download or empty file without closing your SSH session. All six downloads must finish successfully. If a download fails, resolve the error and retry this download step before continuing. +The parenthesized block stops at the first failed download or extraction without closing your SSH session. The archive is pinned to a tested commit. All files must download successfully; resolve any error and retry this step before continuing. Run the prerequisite checker: diff --git a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/_index.md b/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/_index.md index 179e35bef5..1435d7c483 100644 --- a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/_index.md +++ b/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/_index.md @@ -1,5 +1,5 @@ --- -title: Build a local Muse Glimmer 30B agent with Hermes and ExecuTorch on NVIDIA DGX Spark +title: Build Muse Glimmer 30B with Hermes and ExecuTorch on DGX Spark draft: true cascade: diff --git a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/check-prereqs.sh b/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/check-prereqs.sh deleted file mode 100644 index 65372b0adf..0000000000 --- a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/check-prereqs.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -fail() { printf 'Prerequisite check failed: %s\n' "$*" >&2; exit 1; } -[[ $(uname -s) == Linux && $(uname -m) == aarch64 ]] || fail "Run this on the Spark over SSH, not on your Mac." -export PATH="/usr/local/cuda/bin:$PATH" -for command in curl git python3 docker cmake make c++ nvcc nvidia-smi; do - command -v "$command" >/dev/null || fail "$command is missing. Install it before continuing." -done -# This recipe targets the tested stock Ubuntu/Python/CUDA combination. -source /etc/os-release -[[ "$ID" == ubuntu && "$VERSION_ID" == 24.04 ]] || fail "This recipe was tested on Ubuntu 24.04-based DGX OS." -python3 -c 'import sys; assert sys.version_info[:2] == (3, 12)' || fail "Use Python 3.12 for the ExecuTorch environment." -python3 -m venv --help >/dev/null || fail "Python venv support is missing. Install python3.12-venv." -nvcc --version | grep -q 'release 13.0,' || fail "Use the CUDA 13.0 development toolkit tested by this recipe." -nvidia-smi --query-gpu=name --format=csv,noheader | grep -q 'GB10' || fail "No GB10 GPU is visible. Check the NVIDIA driver." -docker info >/dev/null 2>&1 || fail "Docker is unavailable to this account. Fix access and reconnect before continuing." -driver=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader | head -n 1) -[[ ${driver%%.*} -ge 580 ]] || fail "CUDA 13 requires NVIDIA driver 580 or newer." -docker_arch=$(docker info --format '{{.Architecture}}') -[[ "$docker_arch" == aarch64 || "$docker_arch" == arm64 ]] || fail "Docker must use an Arm64 daemon." -[[ $(docker info --format '{{.OSType}}') == linux ]] || fail "Docker must run Linux containers." - -python3 - "${1:-$HOME}" <<'PY' -from pathlib import Path -import re, shutil, subprocess, sys, tempfile, venv -path = Path(sys.argv[1]).expanduser().resolve() -while not path.exists(): - path = path.parent -if shutil.disk_usage(path).free < 100 * 1024**3: - sys.exit('Prerequisite check failed: allow 100 GiB free before downloading/building the model stack.') -memory = Path('/proc/meminfo').read_text() -available = int(re.search(r'^MemAvailable:\s+(\d+)', memory, re.M).group(1)) * 1024 -if available < 80 * 1024**3: - sys.exit('Prerequisite check failed: free at least 80 GiB for export. Coordinate with owners of other model services before stopping them.') -version = subprocess.check_output(['cmake', '--version'], text=True).splitlines()[0].split()[-1] -if tuple(map(int, version.split('.')[:2])) < (3, 26): - sys.exit('Prerequisite check failed: CMake 3.26 or newer is required.') -with tempfile.TemporaryDirectory(prefix='muse-venv-check-') as directory: - venv.EnvBuilder(with_pip=True).create(directory) - source = Path(directory) / 'check.cpp' - source.write_text('#include \nint main() { std::optional n = 0; return *n; }\n') - binary = str(Path(directory) / 'check') - subprocess.run(['c++', '-std=c++17', str(source), '-o', binary], check=True) - subprocess.run([binary], check=True) -print('PASS: host architecture, OS, Python/venv/pip, compiler, CUDA, GPU, Docker, disk and export memory checks') -PY diff --git a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/executorch-constraints.txt b/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/executorch-constraints.txt deleted file mode 100644 index 9c2115b542..0000000000 --- a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/executorch-constraints.txt +++ /dev/null @@ -1,143 +0,0 @@ -# Tested on DGX Spark, Linux ARM64, Python 3.12, CUDA 13.0. -# ExecuTorch source: 0a4a05e84d9642460d87ec2bbbd0e397667ec9c8 -# Version constraints, not an installation manifest or a hash lock. -aiohappyeyeballs==2.7.1 -aiohttp==3.14.5 -aiosignal==1.4.0 -annotated-doc==0.0.5 -annotated-types==0.8.0 -antlr4-python3-runtime==4.9.3 -anyio==4.15.1 -attrs==26.1.0 -blobfile==3.3.0 -certifi==2026.7.22 -charset-normalizer==3.5.2 -click==8.5.0 -cloudpickle==3.1.2 -cmake==3.31.10 -cuda-bindings==13.4.3 -cuda-pathfinder==1.8.3 -cuda-toolkit==13.0.3.0 -datasets==3.6.0 -dill==0.3.8 -execnet==2.1.2 -expecttest==0.3.0 -fastapi==0.143.0 -filelock==4.1.1 -flatbuffers==25.12.19 -frozenlist==1.8.0 -fsspec==2025.3.0 -gguf==0.19.0 -h11==0.16.0 -hf-xet==1.7.0 -httpcore==1.0.9 -httpcore2==2.13.1 -httptools==0.9.0 -httpx==0.28.1 -httpx2==2.13.1 -huggingface_hub==1.33.0 -hydra-core==1.3.7 -hypothesis==6.169.1 -idna==3.20 -iniconfig==2.3.1 -Jinja2==3.1.6 -joblib==1.6.0 -kagglehub==1.0.2 -kagglesdk==0.1.37 -kgb==7.3 -lintrunner==0.12.7 -lintrunner-adapters==0.14.1 -lxml==6.1.3 -markdown-it-py==4.2.0 -MarkupSafe==3.0.4 -mdurl==0.1.2 -mpmath==1.3.0 -multidict==7.1.0 -multiprocess==0.70.16 -narwhals==2.27.1 -networkx==3.7 -numpy==2.5.4 -nvidia-cublas==13.1.1.3 -nvidia-cuda-cccl==13.3.4.3.1 -nvidia-cuda-cupti==13.0.85 -nvidia-cuda-nvrtc==13.0.88 -nvidia-cuda-runtime==13.0.96 -nvidia-cudnn-cu13==9.26.0.51 -nvidia-cufft==12.0.0.61 -nvidia-cufile==1.15.1.6 -nvidia-cusolver==12.0.4.66 -nvidia-cusparse==12.6.3.3 -nvidia-cusparselt-cu13==0.8.1 -nvidia-nccl-cu13==2.30.7 -nvidia-nvjitlink==13.4.92 -nvidia-nvshmem-cu13==3.7.2 -nvidia-nvtx==13.0.85 -omegaconf==2.3.1 -opentelemetry-api==1.45.1 -packaging==26.3 -pandas==3.0.6 -parameterized==0.9.0 -patchelf==0.19.1.0 -pillow==12.3.0 -pluggy==1.6.0 -propcache==0.5.4 -protobuf==7.36.2 -psutil==7.2.2 -py-cpuinfo==9.0.0 -pyarrow==20.0.0 -pycryptodomex==3.24.1 -pydantic==2.14.1 -pydantic_core==2.50.1 -Pygments==2.21.0 -pytest==8.4.2 -pytest-json-report==1.5.0 -pytest-metadata==3.1.1 -pytest-rerunfailures==15.1 -pytest-xdist==3.8.0 -python-dateutil==2.9.0.post0 -python-dotenv==1.2.4 -PyYAML==6.0.3 -regex==2026.9.29 -requests==2.34.2 -rich==15.0.0 -ruamel.yaml==0.19.1 -ruff==0.15.4 -safetensors==0.8.0 -scikit-learn==1.9.1 -scipy==1.18.1 -sentencepiece==0.2.2 -shellingham==1.5.4 -six==1.17.0 -sortedcontainers==2.4.0 -starlette==1.8.0 -sympy==1.14.0 -tabulate==0.10.0 -threadpoolctl==3.7.0 -tiktoken==0.14.0 -timm==1.0.7 -tokenizers==0.22.2 -torch==2.15.0+cu130 -torchao==0.19.0.dev20260907+cpu -torchaudio==2.11.1+cu130 -torchdata==0.11.0 -torchsr==1.0.4 -torchvision==0.29.2+cu130 -tqdm==4.70.1 -transformers==5.0.0rc1 -triton==3.9.0+gitfc8d31a5 -truststore==0.10.5 -typer==0.27.3 -typer-slim==0.24.0 -typing-inspection==0.4.4 -typing_extensions==4.16.0 -urllib3==2.8.0 -uvicorn==0.54.0 -uvloop==0.23.0 -watchfiles==1.3.0 -websockets==17.2 -xxhash==4.0.1 -yarl==1.25.1 -zstd==1.5.7.2 -pip==24.0 -setuptools==84.0.0 -wheel==0.48.0 diff --git a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/release_tool.py b/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/release_tool.py deleted file mode 100644 index 9ffa7a52b8..0000000000 --- a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/release_tool.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Decide whether a release can proceed from a local test-results file.""" -import json -import sys - - -def check_release(results): - counts = { - status: sum(test["status"] == status for test in results["tests"]) - for status in ("passed", "failed", "skipped") - } - return {**counts, "ready": counts["passed"] > 0} - - -if __name__ == "__main__": - with open(sys.argv[1]) as source: - result = check_release(json.load(source)) - print(json.dumps(result)) - sys.exit(1 if result["ready"] else 0) diff --git a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/setup-hermes-cli.sh b/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/setup-hermes-cli.sh deleted file mode 100644 index 54418100b1..0000000000 --- a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/setup-hermes-cli.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Install the tested upstream release and configure this isolated Learning Path. -revision=0e427d788d253630d38a3a51cd76849e73cd6925 -sandbox_image=python:3.11-slim@sha256:e88e9763f943ec1834f992a4b51e0f24500486803e8bc534e5767af9ea65f6ce -work_dir=${1:-"$HOME/muse-agent"} - -fail() { printf 'Setup prerequisite failed: %s\n' "$*" >&2; exit 1; } -[[ $(uname -s) == Linux && $(uname -m) == aarch64 ]] || fail "Run on the Spark's Linux Arm64 host." -for command in curl git python3 docker; do - command -v "$command" >/dev/null || fail "$command is missing. Install it before running setup." -done -python3 -c 'import sys; assert sys.version_info >= (3, 12)' || fail "Python 3.12 or newer is required for this helper." -docker info >/dev/null 2>&1 || fail "Docker is unavailable. Start Docker and grant this account Docker access, then reconnect." -docker_arch=$(docker info --format '{{.Architecture}}') -[[ "$docker_arch" == aarch64 || "$docker_arch" == arm64 ]] || fail "Docker must use an Arm64 daemon." -if [[ -e "$work_dir" && ! -d "$work_dir" ]]; then - fail "$work_dir is not a directory. Choose another work directory." -fi -python3 - "$work_dir" <<'PY' -from pathlib import Path -import shutil, sys -path = Path(sys.argv[1]).expanduser().resolve() -while not path.exists(): - path = path.parent -if shutil.disk_usage(path).free < 10 * 1024**3: - sys.exit('Setup prerequisite failed: allow at least 10 GiB free for the Hermes installation.') -PY -mkdir -p "$work_dir" -work_dir=$(cd "$work_dir" && pwd) -export HERMES_HOME="$work_dir/hermes-home" -# Hermes uses its own upstream locks, independent of the model environment. -unset PIP_CONSTRAINT PIP_BUILD_CONSTRAINT -checkout="$work_dir/hermes-agent" -installer="$work_dir/install-hermes-cli.sh" - -curl --fail --location --connect-timeout 20 --retry 3 \ - "https://raw.githubusercontent.com/NousResearch/hermes-agent/$revision/scripts/install.sh" \ - -o "$installer" - -if [[ -d "$checkout/.git" ]] && [[ $(git -C "$checkout" rev-parse HEAD) != "$revision" ]]; then - echo "Existing checkout differs from the pinned revision. Choose another work directory." >&2 - exit 1 -fi -if [[ -d "$checkout/.git" ]] && [[ -n $(git -C "$checkout" status --porcelain) ]]; then - fail "Existing Hermes checkout has edits. Choose another work directory to preserve them." -fi - -docker pull --platform linux/arm64 "$sandbox_image" - -bash "$installer" --dir "$checkout" --hermes-home "$HERMES_HOME" \ - --commit "$revision" --non-interactive --skip-browser --skip-computer-use -test -x "$checkout/.hermes/bin/hermes" - -mkdir -p "$work_dir/bin" -mkdir -p "$work_dir/demo/input" "$work_dir/demo/output" -python3 - "$work_dir" "$sandbox_image" <<'PY' -import json, shlex, sys -from pathlib import Path -root = Path(sys.argv[1]) -sandbox_image = sys.argv[2] -launcher = root / 'bin/hermes' -launcher.write_text( - '#!/usr/bin/env bash\n' - f'export HERMES_HOME={shlex.quote(str(root / "hermes-home"))}\n' - f'export TERMINAL_DOCKER_IMAGE={shlex.quote(sandbox_image)}\n' - f'exec {shlex.quote(str(root / "hermes-agent/.hermes/bin/hermes"))} "$@"\n' -) -launcher.chmod(0o755) -marker = root / 'hermes-home/.lp-configured' -if not marker.exists(): - config = { - 'model': {'provider': 'spark-local', 'default': 'muse-glimmer-30B', - 'base_url': 'http://127.0.0.1:8000/v1', 'context_length': 65536, - 'streaming': False}, - 'providers': {'spark-local': { - 'api': 'http://127.0.0.1:8000/v1', 'transport': 'chat_completions', - 'default_model': 'muse-glimmer-30B', 'context_length': 65536, - 'extra_body': {'temperature': 1.0, 'top_p': 0.95, 'top_k': 64, - 'reasoning_effort': None}}}, - 'agent': {'reasoning_effort': None, 'max_turns': 40, - 'image_input_mode': 'native'}, - 'approvals': {'mode': 'manual'}, - 'terminal': {'backend': 'docker', 'docker_image': sandbox_image, - 'docker_network': False, 'docker_forward_env': [], - 'docker_mount_cwd_to_workspace': False, - 'docker_volumes': [str(root / 'demo/input') + ':/demo/input:ro', - str(root / 'demo/output') + ':/demo/output'], - 'container_cpu': 2, 'container_memory': 2048}, - } - (root / 'hermes-home/config.yaml').write_text(json.dumps(config, indent=2)) - marker.touch() -elif not (root / 'hermes-home/config.yaml').is_file(): - sys.exit('Configuration marker exists but config.yaml is missing. Restore it or use another work directory.') -PY -# Apply this vision setting on reruns too; preserve other existing settings. -"$work_dir/bin/hermes" config set agent.image_input_mode native -"$work_dir/bin/hermes" chat --help >/dev/null -printf 'Hermes installed: %s/bin/hermes\n' "$work_dir" -printf 'Next: verify the local model connection and Docker sandbox using the Learning Path checks.\n' diff --git a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/test_release_tool.py b/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/test_release_tool.py deleted file mode 100644 index ff9599ddca..0000000000 --- a/content/learning-paths/laptops-and-desktops/dgx_spark_muse_glimmer_executorch_hermes/files/test_release_tool.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Acceptance tests. Keep this file outside the agent's writable directory.""" -import json -import subprocess -import sys -import tempfile -import unittest -from pathlib import Path - -import release_tool - - -class ReleaseTests(unittest.TestCase): - def run_cli(self, statuses): - with tempfile.TemporaryDirectory() as directory: - source = Path(directory) / "results.json" - source.write_text(json.dumps({"tests": [{"status": s} for s in statuses]})) - completed = subprocess.run( - [sys.executable, release_tool.__file__, str(source)], - capture_output=True, text=True, check=False, - ) - return completed.returncode, json.loads(completed.stdout) - - def test_all_passed_can_release(self): - code, result = self.run_cli(["passed", "passed"]) - self.assertTrue(result["ready"]) - self.assertEqual(code, 0) - - def test_failure_blocks_release(self): - code, result = self.run_cli(["passed", "failed", "skipped"]) - self.assertEqual((result["passed"], result["failed"], result["skipped"]), (1, 1, 1)) - self.assertFalse(result["ready"]) - self.assertEqual(code, 1) - - def test_only_skipped_blocks_release(self): - code, result = self.run_cli(["skipped"]) - self.assertFalse(result["ready"]) - self.assertEqual(code, 1) - - def test_no_tests_blocks_release(self): - code, result = self.run_cli([]) - self.assertFalse(result["ready"]) - self.assertEqual(code, 1) - - -if __name__ == "__main__": - unittest.main() diff --git a/content/learning-paths/laptops-and-desktops/dgx_spark_voicechatbot/_index.md b/content/learning-paths/laptops-and-desktops/dgx_spark_voicechatbot/_index.md index d4af07e4c4..a51faeb717 100644 --- a/content/learning-paths/laptops-and-desktops/dgx_spark_voicechatbot/_index.md +++ b/content/learning-paths/laptops-and-desktops/dgx_spark_voicechatbot/_index.md @@ -1,5 +1,5 @@ --- -title: Build an offline voice chatbot with faster-whisper and vLLM on DGX Spark +title: Build an offline chatbot with faster-whisper and vLLM description: Learn how to build an offline voice assistant combining speech-to-text via faster-whisper and text generation via vLLM on Arm-based DGX Spark for privacy-focused deployments. diff --git a/content/learning-paths/laptops-and-desktops/disassemble-arm64-binaries/_index.md b/content/learning-paths/laptops-and-desktops/disassemble-arm64-binaries/_index.md index 493a65d9ed..039ebe3203 100644 --- a/content/learning-paths/laptops-and-desktops/disassemble-arm64-binaries/_index.md +++ b/content/learning-paths/laptops-and-desktops/disassemble-arm64-binaries/_index.md @@ -86,7 +86,7 @@ further_reading: type: documentation - resource: title: SME Insights - link: /insights/sme/ + link: https://learn.arm.com/insights/sme type: website ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/laptops-and-desktops/electron/_index.md b/content/learning-paths/laptops-and-desktops/electron/_index.md index a44f6def73..e1c26ddbe4 100644 --- a/content/learning-paths/laptops-and-desktops/electron/_index.md +++ b/content/learning-paths/laptops-and-desktops/electron/_index.md @@ -1,5 +1,5 @@ --- -title: Develop cross-platform desktop applications with Electron on Windows on Arm +title: Develop Electron desktop apps for Windows on Arm description: Learn how to develop and build cross-platform desktop applications using the Electron Framework on Windows on Arm devices. diff --git a/content/learning-paths/laptops-and-desktops/finetune-smolvla-lerobot/_index.md b/content/learning-paths/laptops-and-desktops/finetune-smolvla-lerobot/_index.md index 144aa9274c..9fe8ec2aaa 100644 --- a/content/learning-paths/laptops-and-desktops/finetune-smolvla-lerobot/_index.md +++ b/content/learning-paths/laptops-and-desktops/finetune-smolvla-lerobot/_index.md @@ -1,5 +1,5 @@ --- -title: Fine-tune SmolVLA for an SO-101 pick-and-place task on an NVIDIA DGX Spark +title: Fine-tune SmolVLA for SO-101 on NVIDIA DGX Spark description: Record SO-101 demonstrations, fine-tune SmolVLA on an Arm-based NVIDIA DGX Spark, and evaluate the model with LeRobot. diff --git a/content/learning-paths/laptops-and-desktops/win-resource-ps1/_index.md b/content/learning-paths/laptops-and-desktops/win-resource-ps1/_index.md index fa8d51cc64..5c8555f90a 100644 --- a/content/learning-paths/laptops-and-desktops/win-resource-ps1/_index.md +++ b/content/learning-paths/laptops-and-desktops/win-resource-ps1/_index.md @@ -1,5 +1,5 @@ --- -title: Measure application resource and power usage on Windows on Arm with FFmpeg and PowerShell +title: Measure Windows on Arm resource usage with FFmpeg and PowerShell description: Learn how to measure application resource usage, benchmark video encoding tasks, and monitor CPU, memory, and power consumption on Windows on Arm using FFmpeg and PowerShell. diff --git a/content/learning-paths/laptops-and-desktops/win11-vm-automation/_index.md b/content/learning-paths/laptops-and-desktops/win11-vm-automation/_index.md index 0101507bea..1f31e2214a 100644 --- a/content/learning-paths/laptops-and-desktops/win11-vm-automation/_index.md +++ b/content/learning-paths/laptops-and-desktops/win11-vm-automation/_index.md @@ -1,5 +1,5 @@ --- -title: Automate Windows on Arm virtual machine deployment with QEMU and KVM on Arm Linux +title: Automate Windows on Arm virtual machine deployment with QEMU and KVM description: Learn how to automate Windows on Arm VM creation on Arm Linux systems using QEMU, KVM, and Bash scripts for development and testing. diff --git a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/_index.md b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/_index.md index 271a363b35..41f399778b 100644 --- a/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/_index.md +++ b/content/learning-paths/laptops-and-desktops/win_aws_iot_lambda_dynamodb/_index.md @@ -1,5 +1,5 @@ --- -title: Integrate AWS Lambda with DynamoDB for IoT applications running Windows on Arm +title: Integrate AWS Lambda and DynamoDB for Windows on Arm IoT apps description: Learn how to implement AWS Lambda functions that process and aggregate IoT data stored in DynamoDB tables from Windows on Arm devices. diff --git a/content/learning-paths/laptops-and-desktops/win_cef/_index.md b/content/learning-paths/laptops-and-desktops/win_cef/_index.md index 52f2a448d3..420ecaa4f7 100644 --- a/content/learning-paths/laptops-and-desktops/win_cef/_index.md +++ b/content/learning-paths/laptops-and-desktops/win_cef/_index.md @@ -1,11 +1,11 @@ --- -title: Develop desktop applications with Chromium Embedded Framework on Windows on Arm +title: Develop CEF desktop apps for Windows on Arm description: Learn how to create and build Chromium Embedded Framework desktop applications using CMake and web technologies on Windows on Arm. minutes_to_complete: 30 -who_is_this_for: This learning path is for developers who want to learn how to use web technologies for developing Desktop apps on Windows on Arm (WoA). +who_is_this_for: This learning path is for developers who want to learn how to use web technologies for developing Chromium Embedded Framework (CEF) desktop apps on Windows on Arm (WoA). learning_objectives: - Create and build a Chromium Embedded Framework (CEF) project using CMake diff --git a/content/learning-paths/laptops-and-desktops/win_net/win_net.md b/content/learning-paths/laptops-and-desktops/win_net/win_net.md index 33b2c11c92..a9912ebf36 100644 --- a/content/learning-paths/laptops-and-desktops/win_net/win_net.md +++ b/content/learning-paths/laptops-and-desktops/win_net/win_net.md @@ -1,6 +1,6 @@ --- # User change -title: "Build a native windows application using .NET 6 framework for Windows on Arm" +title: "Build a native .NET 6 app for Windows on Arm" weight: 2 diff --git a/content/learning-paths/laptops-and-desktops/win_profile_guided_optimisation/_index.md b/content/learning-paths/laptops-and-desktops/win_profile_guided_optimisation/_index.md index 12b6dca66f..b19680c239 100644 --- a/content/learning-paths/laptops-and-desktops/win_profile_guided_optimisation/_index.md +++ b/content/learning-paths/laptops-and-desktops/win_profile_guided_optimisation/_index.md @@ -1,5 +1,5 @@ --- -title: Optimize C++ applications on Windows on Arm using profile-guided optimization +title: Optimize Windows on Arm C++ apps with profile-guided optimization description: Learn how to apply Profile-Guided Optimization (PGO) to build performance-tuned C++ binaries and measure improvements using Google Benchmark on Windows on Arm. diff --git a/content/learning-paths/laptops-and-desktops/win_sandbox_dot_net_cicd/win_sandbox_net.md b/content/learning-paths/laptops-and-desktops/win_sandbox_dot_net_cicd/win_sandbox_net.md index 4b66c37413..79de2d99d8 100644 --- a/content/learning-paths/laptops-and-desktops/win_sandbox_dot_net_cicd/win_sandbox_net.md +++ b/content/learning-paths/laptops-and-desktops/win_sandbox_dot_net_cicd/win_sandbox_net.md @@ -1,6 +1,6 @@ --- # User change -title: "Configure Windows Sandbox as your GitHub Actions self-hosted Arm64 runner" +title: "Configure Windows Sandbox as an Arm64 GitHub Actions runner" weight: 2 diff --git a/content/learning-paths/laptops-and-desktops/win_wpf/_index.md b/content/learning-paths/laptops-and-desktops/win_wpf/_index.md index 6355661056..260b63cc1f 100644 --- a/content/learning-paths/laptops-and-desktops/win_wpf/_index.md +++ b/content/learning-paths/laptops-and-desktops/win_wpf/_index.md @@ -1,5 +1,5 @@ --- -title: Develop applications with Windows Presentation Foundation (WPF) on Windows on Arm +title: Develop WPF apps for Windows on Arm description: Learn how to create and build Windows Presentation Foundation (WPF) applications and measure code execution performance uplift on Arm64. diff --git a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/_index.md b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/_index.md index 1b72727e83..4559930505 100644 --- a/content/learning-paths/laptops-and-desktops/win_xamarin_forms/_index.md +++ b/content/learning-paths/laptops-and-desktops/win_xamarin_forms/_index.md @@ -1,5 +1,5 @@ --- -title: Develop cross-platform applications with Xamarin Forms on Windows on Arm +title: Develop Xamarin Forms apps for Windows on Arm description: Learn how to create and build Xamarin Forms applications using the MVVM pattern and measure code execution performance uplift on Arm64. diff --git a/content/learning-paths/laptops-and-desktops/windowsperf-vs-extension/_index.md b/content/learning-paths/laptops-and-desktops/windowsperf-vs-extension/_index.md index eb483e1b74..b6bdc5d639 100644 --- a/content/learning-paths/laptops-and-desktops/windowsperf-vs-extension/_index.md +++ b/content/learning-paths/laptops-and-desktops/windowsperf-vs-extension/_index.md @@ -1,5 +1,5 @@ --- -title: Analyze performance data with the Visual Studio extension for WindowsPerf +title: Analyze WindowsPerf data with its Visual Studio extension description: Learn how to install and use the WindowsPerf Visual Studio extension to generate counting and sampling reports and analyze performance data in Windows Performance Analyzer. diff --git a/content/learning-paths/laptops-and-desktops/windowsperf_wpa_plugin/_index.md b/content/learning-paths/laptops-and-desktops/windowsperf_wpa_plugin/_index.md index 438e853f8c..ea89349a0d 100644 --- a/content/learning-paths/laptops-and-desktops/windowsperf_wpa_plugin/_index.md +++ b/content/learning-paths/laptops-and-desktops/windowsperf_wpa_plugin/_index.md @@ -1,5 +1,5 @@ --- -title: Get started with the Windows Performance Analyzer plugin for WindowsPerf +title: Use the Windows Performance Analyzer plugin for WindowsPerf description: Learn how to import WindowsPerf data in Windows Performance Analyzer (WPA) and visualize timeline and telemetry data using the WPA plugin. diff --git a/content/learning-paths/mobile-graphics-and-gaming/ai-camera-pipelines/_index.md b/content/learning-paths/mobile-graphics-and-gaming/ai-camera-pipelines/_index.md index 786d29a901..e3d0e55da2 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/ai-camera-pipelines/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/ai-camera-pipelines/_index.md @@ -1,5 +1,5 @@ --- -title: Accelerate Denoising, Background Blur and Low-Light Camera Effects with SME2 +title: Accelerate camera effects with SME2 on Arm description: Learn how to build and optimize AI-powered camera pipeline applications on Arm Linux using KleidiAI, KleidiCV, and SME2 to accelerate denoising, background blur, and low-light effects. diff --git a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-audio-to-text/_index.md b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-audio-to-text/_index.md index 9275e4e857..417338aea9 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-audio-to-text/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-audio-to-text/_index.md @@ -1,5 +1,5 @@ --- -title: Run optimized Whisper speech transcription models from the Arm AI Portal on Arm-powered Android devices +title: Run Arm AI Portal Whisper models on Android description: Run Arm-optimized Whisper models with LiteRT and ExecuTorch locally on Android, then extend Whisper Journal for another compatible model or adapter. diff --git a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-image-classification/_index.md b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-image-classification/_index.md index 6850739064..fbcca9068e 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-image-classification/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-image-classification/_index.md @@ -1,5 +1,5 @@ --- -title: Run optimized image classification models from the Arm AI Portal on Android +title: Run Arm AI Portal image classification models on Android description: Run Arm-optimized LiteRT, ExecuTorch, and CLIP image-classification models on Android, then extend Photo Insight for another compatible model or adapter. diff --git a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-object-detection/5-plan-detection-workflow.md b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-object-detection/5-plan-detection-workflow.md index d27ce9e5eb..aa974b2322 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-object-detection/5-plan-detection-workflow.md +++ b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-object-detection/5-plan-detection-workflow.md @@ -1,5 +1,5 @@ --- -title: (Optional) Use an object-detection model not currently supported by Scene Detector +title: (Optional) Use an unsupported model with Scene Detector description: Register a compatible mobile object detector or generate and validate support for a model that needs another adapter. weight: 6 diff --git a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-object-detection/_index.md b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-object-detection/_index.md index 7ab362d65e..aa24536648 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-object-detection/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-object-detection/_index.md @@ -1,5 +1,5 @@ --- -title: Run optimized object-detection models from the Arm AI Portal on Android +title: Run Arm AI Portal object detection models on Android description: Run Arm-optimized YOLO models with ExecuTorch or LiteRT on Android to detect objects in saved images or a live camera feed. diff --git a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-vision-language/_index.md b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-vision-language/_index.md index 8bc97117d1..da0967c784 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-vision-language/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-mobile-vision-language/_index.md @@ -1,5 +1,5 @@ --- -title: Run an optimized vision-language model from the Arm AI Portal on Android +title: Run an Arm AI Portal vision-language model on Android description: Run the Arm-optimized Qwen3-VL model locally with llama.cpp to generate text from an image and prompt on Android. minutes_to_complete: 35 diff --git a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-tinysd-android/5-plan-mobile-image-generation-workflow.md b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-tinysd-android/5-plan-mobile-image-generation-workflow.md index 445855025e..747c2c8c08 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-tinysd-android/5-plan-mobile-image-generation-workflow.md +++ b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-tinysd-android/5-plan-mobile-image-generation-workflow.md @@ -1,5 +1,5 @@ --- -title: (Optional) Use an image-generation model not currently supported by TinySD Studio +title: (Optional) Use an unsupported model with TinySD Studio description: Register a compatible mobile image-generation model or generate and validate support for a model that needs another adapter. weight: 6 diff --git a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-tinysd-android/_index.md b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-tinysd-android/_index.md index b5c2ee5cd6..1b50712a8e 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/ai-portal-tinysd-android/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/ai-portal-tinysd-android/_index.md @@ -1,5 +1,5 @@ --- -title: Run optimized TinySD image generation from the Arm AI Portal on Arm-powered Android devices +title: Run Arm AI Portal TinySD image generation on Android description: Run an Arm-optimized TinySD image-generation model on Android with the supplied ExecuTorch adapter. minutes_to_complete: 60 diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_halide/aot-and-cross-compilation.md b/content/learning-paths/mobile-graphics-and-gaming/android_halide/aot-and-cross-compilation.md index 56cb1ffa3e..a00fb65cca 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/android_halide/aot-and-cross-compilation.md +++ b/content/learning-paths/mobile-graphics-and-gaming/android_halide/aot-and-cross-compilation.md @@ -1,6 +1,6 @@ --- # User change -title: "Generate optimized Halide pipelines for Android using ahead-of-time cross-compilation" +title: "Cross-compile optimized Halide pipelines for Android" weight: 5 diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/5-render-a-simple-3d-object-part-1.md b/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/5-render-a-simple-3d-object-part-1.md index c6e3f9f544..6cb2c96390 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/5-render-a-simple-3d-object-part-1.md +++ b/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/5-render-a-simple-3d-object-part-1.md @@ -36,7 +36,7 @@ WebGPU is a simple system. It runs three types of functions on the GPU: Here is a simplified diagram of a WebGPU setup to draw triangles by using a vertex shader and a fragment shader: -!["Triangle using WebGPU" #center](images/webgpu-draw-high-level.svg "Figure 8: Triangle using WebGPU") +![Triangle using WebGPU#center](images/webgpu-draw-high-level.svg "Figure 8: Triangle using WebGPU") The main things to notice in the above image are: @@ -61,7 +61,7 @@ In order to achieve high performance real-time 3D rendering, the GPU processes s To do so, WebGPU provides a Render Pipeline object. The figure below illustrates the sequence of data processing stages executed by the render pipeline. -!["Render Pipeline" #center](images/render-pipeline.svg "Figure 9: Render Pipeline") +![Render Pipeline#center](images/render-pipeline.svg "Figure 9: Render Pipeline") The Render Pipeline has two main types of stages, **fixed-function** and **programmable**. @@ -151,4 +151,4 @@ wgpu::RenderPipelineDescriptor pipelineDesc; wgpu::RenderPipeline pipeline = device.createRenderPipeline(pipelineDesc); ``` -Continue to the next section to learn more about rendering a 3D object. \ No newline at end of file +Continue to the next section to learn more about rendering a 3D object. diff --git a/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/7-profiling-app-using-streamline.md b/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/7-profiling-app-using-streamline.md index 87a2ae3b62..608bb61285 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/7-profiling-app-using-streamline.md +++ b/content/learning-paths/mobile-graphics-and-gaming/android_webgpu_dawn/7-profiling-app-using-streamline.md @@ -36,13 +36,13 @@ Wait until Streamline completes processing the data. Switch to **Mali Timeline** view as shown below: -!["Mali Timeline Streamline" #center](images/streamline-mali-timeline.png "Figure 13: Mali Timeline Streamline") +![Mali Timeline Streamline#center](images/streamline-mali-timeline.png "Figure 13: Mali Timeline Streamline") You might have to zoom into the data up to the maximum (**500 us**), as you are rendering a simple 3D object. You can analyze two consecutive frames as shown below: -!["Two Consecutive Frames" #center](./images/streamline-mali-analysis.png "Figure 14: Two Consecutive Frames") +![Two Consecutive Frames#center](./images/streamline-mali-analysis.png "Figure 14: Two Consecutive Frames") Arm has worked with the Dawn team to optimize data uploading to GPU buffers for Mali GPUs. diff --git a/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/1-ray-tracing.md b/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/1-ray-tracing.md index 35114a8685..62c6c23120 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/1-ray-tracing.md +++ b/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/1-ray-tracing.md @@ -15,9 +15,9 @@ The demo is named **Steel Arms**. Created with Unreal Engine 5.3, Steel Arms bri The following screenshots are from scenes in **Steel Arms** which is powered by Unreal Lumen. Several optimization tips and techniques were used in the development of **Steel Arms** for achieving the best performance with Lumen. This learning path will start with an introduction to ray tracing and then cover the best practices for hardware ray tracing in Lumen. -![](images/garage.webp) +![Steel Arms garage scene with a boxing ring beneath a lattice ceiling and light illuminating the wall and floor around it, showing the detailed indoor lighting used in the Lumen demo.#center](images/garage.webp) -![](images/garage2.webp) +![Steel Arms scene with a character beside a neon-lit window and colored light reflecting across the floor, showing another lighting effect in the Lumen demo.#center](images/garage2.webp) ## What is Ray Tracing? diff --git a/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/2-acceleration-structure.md b/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/2-acceleration-structure.md index c511ba3cd7..a5d5382483 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/2-acceleration-structure.md +++ b/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/2-acceleration-structure.md @@ -12,12 +12,12 @@ An acceleration structure is a data structure designed to improve ray traversal As shown in the image below, the scene is divided into boxes, and the acceleration structure stores the hierarchical tree of all these smaller boxes. During ray traversal, the hardware can use this structure to quickly locate the boxes that intersect with a ray. Subsequently, the hardware can then find the intersections between the ray and the triangles within those volumes. -![](images/as2.png "Figure1. The acceleration structure used to represent a scene.") +![Diagram of a scene divided into nested bounding boxes, with a ray passing through the boxes toward the geometry. The hierarchy lets ray tracing test relevant regions instead of every shape.#center](images/as2.png "Figure1. The acceleration structure used to represent a scene.") In a typical game scene, there are often many duplicated objects that share the same geometry data but have different instance attributes, such as position or color. Therefore, the acceleration structure is divided into two levels: the Top Level Acceleration Structure (TLAS) and the Bottom Level Acceleration Structure (BLAS), as shown below. -![](images/as.png "Figure2. The acceleration structure tree.") +![Acceleration-structure tree with a TLAS above four instances, three BLAS nodes, and geometry beneath them. Two instances reference the same BLAS, showing how duplicated objects share geometry data.#center](images/as.png "Figure2. The acceleration structure tree.") ### TLAS  The TLAS only stores the instancing data of the BLAS, such as the transform data of each instance. It does not store any geometry data. Instead, it references the relevant BLAS. This approach allows the hardware to save memory by storing a single set of geometry data for many duplicated objects. diff --git a/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/4-add-object.md b/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/4-add-object.md index df1bbac877..cfcdd3d0c1 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/4-add-object.md +++ b/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/4-add-object.md @@ -10,6 +10,6 @@ As explained in the previous section, the acceleration structure stores all geom It's important to exclude actors that do not contribute to lighting from ray tracing. Additionally, exclude small actors from ray tracing since they contribute very little to the final lighting and may cause noise in indirect lighting. To do this, in the actor details panel, uncheck **Visible in Ray Tracing** to exclude these actors from ray tracing. -![](images/add_object.png) +![Unreal Editor actor details panel under Rendering > Advanced, showing the Visible in Ray Tracing checkbox cleared to exclude the actor from the acceleration structure.#center](images/add_object.png) diff --git a/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/5-instancing.md b/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/5-instancing.md index 79b62019ba..90841f119c 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/5-instancing.md +++ b/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/5-instancing.md @@ -12,13 +12,13 @@ Here are the steps for checking the instancing status in Unreal Editor: 1. Use command `r.RayTracing.Debug.PickerDomain 1` to select the instance mode for picker: -![](images/picker-command.png) +![Unreal Editor command field containing r.RayTracing.Debug.PickerDomain 1, which sets the ray tracing picker to instance mode.#center](images/picker-command.png) 2. In the Unreal editor, under **Ray Tracing Debug**, select **Picker**: -![](images/picker-view.png) +![Unreal Editor Ray Tracing Debug menu with Picker selected, ready to inspect an instance in the scene.#center](images/picker-view.png) 3. Use the mouse cursor to select the instance you want to check. The acceleration structure information for this instance will be displayed on the screen. Use the detailed information under **[BLAS]** to verify if two instances share the same BLAS data: -![](images/blas.png) +![Unreal Editor Ray Tracing Debug view with a selected scene object and an on-screen information panel listing its instance ID and BLAS name. Compare the BLAS name for two objects to check whether they share geometry data.#center](images/blas.png) diff --git a/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/8-lumen-general.md b/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/8-lumen-general.md index b16b19a49b..bf786a5ae7 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/8-lumen-general.md +++ b/content/learning-paths/mobile-graphics-and-gaming/best-practices-for-hwrt-lumen-performance/8-lumen-general.md @@ -9,7 +9,7 @@ layout: learningpathall We now look at how best to optimize with the Global Illumination settings. To use Lumen in your game scene, you can add a **Post Process Volume** actor (found under Place Actors > Visual Effects). Within the details panel of **Post Process Volume**, there are several options that you can change and amend. During the development of the Lumen content, **“Steel Arms”**, we found a few recommended values for these options that are suitable for Android devices. The following images show all of the option values that were used in **“Steel Arms”** with some additional explanation provided. ## Global Illumination Settings -![](images/gl-setting.png "Figure1. These global illumination parameters are used in our Lumen content - Steel Arms.") +![Unreal Editor Global Illumination settings for Steel Arms with Lumen selected, Final Gather Quality set to 1.0, Max Trace Distance to 20000, and both lighting update speeds to 0.5. These values illustrate the mobile-focused settings discussed below.#center](images/gl-setting.png "Figure1. These global illumination parameters are used in our Lumen content - Steel Arms.") ### Lumen Scene Detail • Higher values ensure that smaller objects contribute to Lumen lighting but they do increase the GPU cost. @@ -40,7 +40,7 @@ We now look at how best to optimize with the Global Illumination settings. To us • **0.5 ~ 1.0** strikes a good balance between performance and quality for mobile games. ## Lumen Reflection Settings -![](images/reflection-setting.png "Figure 2. These reflection parameters are used in our Lumen content - Steel Arms.") +![Unreal Editor Reflections settings for Steel Arms with Lumen selected, Quality set to 0.25, Ray Lighting Mode set to Surface Cache, and Max Reflection Bounces set to 1.#center](images/reflection-setting.png "Figure 2. These reflection parameters are used in our Lumen content - Steel Arms.") ### Reflection Quality • This controls the reflection tracing quality, essentially the resolution of the reflection. diff --git a/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/_index.md b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/_index.md index b2001ead59..1d853cd163 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/build-llama3-chat-android-app-using-executorch-and-xnnpack/_index.md @@ -1,5 +1,5 @@ --- -title: Build an Android chat app with Llama, KleidiAI, ExecuTorch, and XNNPACK +title: Build an Android Llama chat app with ExecuTorch and XNNPACK description: Learn how to build an Android chat application with Llama models using ExecuTorch, XNNPACK, and KleidiAI for accelerated performance on Arm smartphones. minutes_to_complete: 60 diff --git a/content/learning-paths/mobile-graphics-and-gaming/explore-kleidicv-gaussian-blur-on-android/_index.md b/content/learning-paths/mobile-graphics-and-gaming/explore-kleidicv-gaussian-blur-on-android/_index.md index 2e47e2a6f8..4256ca4c05 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/explore-kleidicv-gaussian-blur-on-android/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/explore-kleidicv-gaussian-blur-on-android/_index.md @@ -1,5 +1,5 @@ --- -title: Compare KleidiCV Gaussian blur performance across Neon, SVE2, and SME on Android +title: Compare KleidiCV Gaussian blur with Neon, SVE2, and SME on Android description: Build a KleidiCV Gaussian blur benchmark for Android and evaluate how Neon, SVE2, and SME performance changes with image resolution and kernel size. minutes_to_complete: 45 diff --git a/content/learning-paths/mobile-graphics-and-gaming/how-to-enable-hwrt-on-lumen-for-android-devices/1-what-is-lumen.md b/content/learning-paths/mobile-graphics-and-gaming/how-to-enable-hwrt-on-lumen-for-android-devices/1-what-is-lumen.md index 6ebb075850..a68023bffc 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/how-to-enable-hwrt-on-lumen-for-android-devices/1-what-is-lumen.md +++ b/content/learning-paths/mobile-graphics-and-gaming/how-to-enable-hwrt-on-lumen-for-android-devices/1-what-is-lumen.md @@ -12,8 +12,8 @@ Lumen introduces a new ray-tracing based solution that allows developers to rend You can see the improvements in the rendering quality by comparing the following two images of the same scene. The first image uses only direct lighting where details in the areas beyond the direct lighting range (such as the background) are not visible. In contrast, the second image utilizes Lumen lighting, incorporating both direct and indirect lighting. Now, you can discern many more details in the background that were previously hidden, as Lumen takes light bounces into account. -![](images/no_lumen.webp "Figure 1. The scene without Lumen has only dirct lighting.") +![Indoor scene lit only by direct lighting: the foreground platform is bright, while the back of the room is nearly black and its details are difficult to see.#center](images/no_lumen.webp "Figure 1. The scene without Lumen has only dirct lighting.") -![](images/lumen.webp "Figure 2. The scene with Lumen has both direct and indirect lighting.") +![The same indoor scene with Lumen enabled: bounced light reveals shelves, lockers, and other details at the back of the room that are hidden in the direct-lighting image.#center](images/lumen.webp "Figure 2. The scene with Lumen has both direct and indirect lighting.") Lumen supports both software and hardware ray tracing. Software ray tracing uses a simplified Lumen scene to replace actual geometries in the scene. In contrast, hardware ray tracing uses actual geometries to trace rays, resulting in better lighting quality. diff --git a/content/learning-paths/mobile-graphics-and-gaming/how-to-enable-hwrt-on-lumen-for-android-devices/2-what-is-gi.md b/content/learning-paths/mobile-graphics-and-gaming/how-to-enable-hwrt-on-lumen-for-android-devices/2-what-is-gi.md index ac1a80b6ee..f094fa450f 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/how-to-enable-hwrt-on-lumen-for-android-devices/2-what-is-gi.md +++ b/content/learning-paths/mobile-graphics-and-gaming/how-to-enable-hwrt-on-lumen-for-android-devices/2-what-is-gi.md @@ -18,6 +18,6 @@ Global Illumination (GI) is a sophisticated lighting technique that considers th For a practical example, consider the following Cornell box scene which has been rendered using Unreal Engine's Lumen lighting system. In this image, you can easily observe how all four attributes have been successfully captured. -![](images/cornell_box.png "Figure 1. Cornell box scene generated by Unreal Lumen system.") +![Cornell box rendered with Lumen: a bright ceiling light illuminates a red wall, a green wall, and two blocks. Bounced colored light tints nearby surfaces and creates soft shadows.#center](images/cornell_box.png "Figure 1. Cornell box scene generated by Unreal Lumen system.") -![](images/cornell_box_4.png "Figure 2. The 4 critical attributes of global illumination.") +![Annotated Cornell box identifying direct lighting on the red and green walls, color bleeding onto the blocks, indirect lighting near the floor, and an indirect shadow beside the taller block.#center](images/cornell_box_4.png "Figure 2. The 4 critical attributes of global illumination.") diff --git a/content/learning-paths/mobile-graphics-and-gaming/how-to-enable-hwrt-on-lumen-for-android-devices/3-how-to-enable-lumen.md b/content/learning-paths/mobile-graphics-and-gaming/how-to-enable-hwrt-on-lumen-for-android-devices/3-how-to-enable-lumen.md index 86d5b77720..aae6bf3a7c 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/how-to-enable-hwrt-on-lumen-for-android-devices/3-how-to-enable-lumen.md +++ b/content/learning-paths/mobile-graphics-and-gaming/how-to-enable-hwrt-on-lumen-for-android-devices/3-how-to-enable-lumen.md @@ -10,8 +10,8 @@ To enable Lumen in your Unreal project, open the Project Settings window, select Next, select **Lumen** as the reflection method in the **Reflections** section, as shown below. -![](images/enable_lumen.png "Figure 1. Select Lumen as Global Illumination and Reflections method.") +![Unreal Editor Project Settings under Engine > Rendering, with Dynamic Global Illumination Method and Reflection Method both set to Lumen.#center](images/enable_lumen.png "Figure 1. Select Lumen as Global Illumination and Reflections method.") Alternatively, another way to enable Lumen is by creating a **Post Process Volume** actor in the scene and selecting **Lumen** in the **Global Illumination** sections in the Post Process Volume details panel, as shown in the following image. You can also select **Lumen** as the reflection method here as well. -![](images/postprocess.png "Figure 2. Select Lumen as Global Illumination and Reflections method in Post Process Volume details panel.") +![Unreal Editor Post Process Volume details panel with the Global Illumination and Reflections Method checkboxes enabled and both methods set to Lumen.#center](images/postprocess.png "Figure 2. Select Lumen as Global Illumination and Reflections method in Post Process Volume details panel.") diff --git a/content/learning-paths/mobile-graphics-and-gaming/libgpuinfo/gpu.md b/content/learning-paths/mobile-graphics-and-gaming/libgpuinfo/gpu.md index a0c06eda88..9b535c15cf 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/libgpuinfo/gpu.md +++ b/content/learning-paths/mobile-graphics-and-gaming/libgpuinfo/gpu.md @@ -1,6 +1,6 @@ --- # User change -title: "Build and run an example application to obtain Arm GPU configuration information" +title: "Build an app to inspect Arm GPU configuration" weight: 2 # 1 is first, 2 is second, etc. diff --git a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/1-introduction.md b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/1-introduction.md index d414b18abd..1e4d15a911 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/1-introduction.md +++ b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/1-introduction.md @@ -13,7 +13,9 @@ Arm ML Inference Advisor (MLIA) helps you evaluate a model for an Arm target bef You'll use the `mlia-neural-technology` package to evaluate two pretrained, quantized MobileNet V1 image classifiers from the TensorFlow public model catalog. You'll compare a baseline with a smaller-input variant under the same Neural Technology configuration. You don't need a model stored in this repository or a device to run the estimator. -The tested example reduces estimated total cycles from `136,962` to `79,824`. These are compiler estimates, not device measurements or an accuracy result. You'll learn about the model and configuration assumptions behind this comparison in [Compare a smaller model under the same target profile](/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/6-iterate-and-compare/). +The reference example, recorded with Neural Technology `0.2.0` and LiteRT converter `0.1.2`, reduces estimated total cycles from `136,962` to `79,824`. These are compiler estimates, not device measurements or an accuracy result. You'll learn about the model and configuration assumptions behind this comparison in [Compare a smaller model under the same target profile](/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/6-iterate-and-compare/). + +The setup installs Neural Technology `0.3.0` and LiteRT converter `0.1.3`, so your generated estimates can differ from this reference. Neural Technology `0.3.0` is required for the optional ETRecord and ETDump VGF profiling workflow. If you have a neural accelerator (NX)-capable Android device and a compatible native Vulkan driver, you can execute the model on the device. You can then bring a measured capture back to MLIA. For more information, see [(Optional) Run and profile the model on an NX device](/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/7-run-on-nx-device/). @@ -21,7 +23,9 @@ If you have a neural accelerator (NX)-capable Android device and a compatible na The [Analyze Ethos-U models with MLIA Learning Path](/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/) applies the same workflow to Arm Ethos-U: select a target profile, check compatibility, inspect performance, and use the advice to guide your next change. It uses Vela for compiler estimates and Corstone for whole-model NPU performance counters. In this Learning Path, you'll use the Neural Technology plugin and NX Performance Estimator, with optional measurements from an Android device. -The report-reading approach carries across targets, but their backends, supported operators, and performance metrics differ. Don't compare cycle counts across targets assuming that they describe the same hardware configuration. +The report-reading approach carries across targets, but their backends, supported operators, and performance metrics differ. Don't compare cycle counts across targets assuming that they describe the same hardware configuration. For an editor-based visual workflow, see the [Ethos-U VS Code and Model Explorer walkthrough](/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/7-vscode-plugin/). + +If you already have an ExecuTorch ETRecord and matching ETDump from a deployed program, you can also [analyze recorded runtime timings and VGF profiling](/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/8-executorch-profiling/). This optional workflow uses different artifacts from the Scenario Runner capture directory. ## Understand the conversion and analysis flow diff --git a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/2-devenv.md b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/2-devenv.md index 47fd8d8eed..1c6671f9c2 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/2-devenv.md +++ b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/2-devenv.md @@ -9,7 +9,7 @@ layout: learningpathall ## Perform an environment check -Use an x86-64 Ubuntu 24.04 LTS development host with CPython 3.12. The `mlia-neural-technology` version `0.2.0` wheel is published for CPython 3.12 on x86-64 Linux with glibc version 2.39 or later. Ubuntu 22.04, other Python versions, native macOS, and native Linux on Arm aren't supported by this wheel. +Use an x86-64 Ubuntu 24.04 LTS development host with CPython 3.12. The `mlia-neural-technology` version `0.3.0` wheel is published for CPython 3.12 on x86-64 Linux with glibc version 2.39 or later. Ubuntu 22.04, other Python versions, native macOS, and native Linux on Arm aren't supported by this wheel. Check your architecture, Python version, and glibc version: @@ -40,7 +40,7 @@ cd mlia-demo python3.12 -m venv mlia-venv source mlia-venv/bin/activate python -m pip install --upgrade pip -python -m pip install mlia-neural-technology==0.2.0 mlia-converters-litert==0.1.2 +python -m pip install mlia==0.13.0 mlia-neural-technology==0.3.0 mlia-converters-litert==0.1.3 python -m pip check ``` @@ -52,6 +52,8 @@ These are public PyPI packages. The Neural Technology plugin installs the follow The LiteRT converter plugin lets MLIA analyze `.tflite` files directly. You don't need TensorFlow, a GPU driver, or a separate model-training environment for this example. +Neural Technology `0.3.0` requires MLIA core `0.13.x`. LiteRT converter `0.1.3` supports this core version. Upgrade these packages together when reusing an existing environment. + The expected output is: ```output @@ -76,6 +78,19 @@ The estimator executable is bundled with the public wheel. MLIA offers to instal If `pip` reports that no matching distribution exists for `mlia-neural-technology`, check the three host requirements again and upgrade `pip` inside the environment. A virtual environment doesn't change the host architecture or glibc version. Don't substitute an older Python version or Ubuntu 22.04 for this release. {{% /notice %}} +## (Optional) Prepare Model Explorer visualizations + +The [MLIA Model Explorer plugin](https://pypi.org/project/mlia-model-explorer/) adds graph visualizations with performance overlays. Version `0.2.0` supports MLIA core `0.13.x`. The `vgf` extra includes the adapter for the compiled model you'll inspect later. The plugin generates a standalone HTML page that you can open directly in your browser. You don't need to install Model Explorer separately or run additional services. + +Install the plugin in the same environment: + +```bash +python -m pip install "mlia-model-explorer[vgf]==0.2.0" +python -m pip check +``` + +The expected output from `pip check` is `No broken requirements found.` The [ExecuTorch profiling steps](/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/8-executorch-profiling/) install an additional extra for ETRecord and PTE adapters. + ## What you've accomplished and what's next You've installed MLIA with the Neural Technology and LiteRT converter plugins. diff --git a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/4-install-backend-compatibility-check.md b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/4-install-backend-compatibility-check.md index bdd02a8700..16b4ff9796 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/4-install-backend-compatibility-check.md +++ b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/4-install-backend-compatibility-check.md @@ -40,7 +40,7 @@ assert not failed_checks, "Review the non-passing operator checks." PY ``` -The expected output for the pinned packages and baseline is: +The reference output for the baseline is: ```output Compatibility status: ok diff --git a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/5-performance-check.md b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/5-performance-check.md index 69801dc89a..ce09e13c27 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/5-performance-check.md +++ b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/5-performance-check.md @@ -15,7 +15,7 @@ Run a performance check with the same model and target profile: mlia check models/baseline.tflite --target-profile neural-technology --performance --output-dir runs/baseline ``` -With the tested packages and `neural-technology` profile, expect an estimated `136,962` total cycles and about `0.09131 ms` per inference. +The reference run with Neural Technology `0.2.0`, LiteRT converter `0.1.2`, and the `neural-technology` profile produced an estimated `136,962` total cycles and about `0.09131 ms` per inference. The setup now uses Neural Technology `0.3.0` and LiteRT converter `0.1.3`; use your generated report for the current result. These figures are compiler estimates, rather than timings measured on a device. ## Locate and read the reports @@ -48,7 +48,7 @@ for name in ("total_cycles", "compute_cycles", "dram_cycles", "inference_time", PY ``` -The expected output is: +The reference output is: ```output total_cycles: 136962 cycles @@ -64,6 +64,30 @@ For the baseline, `dram_cycles` is `94,396`, compared with `34,976` compute cycl Use `results[].breakdowns` and their `entity_id` links to `results[].entities` to inspect individual chains and cascades. These are compiler scheduling groups. They're not necessarily one framework layer each. Source-operator provenance helps you relate those groups to the original graph. Some metrics overlap or use non-additive aggregation policies, so don't sum every breakdown to recreate the model total. +## (Optional) Explore results in Model Explorer + +After installing the compatible Model Explorer plugin in [Set up your MLIA environment](/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/2-devenv/#optional-prepare-model-explorer-visualizations), rerun the check into a separate directory with visualization enabled: + +```bash +mlia check models/baseline.tflite \ + --target-profile neural-technology \ + --performance \ + --output-dir runs/baseline-visualized \ + --model-explorer open +``` + +`--model-explorer open` creates a self-contained HTML visualization and opens it in your browser. Use `--model-explorer files` to generate the files without opening a browser. If the browser doesn't open, open `runs/baseline-visualized/mlia-output/model-explorer/bundle/index.html` manually. + +Inspect the generated MLIA hierarchy alongside **Source Model**: + +1. Select a cycle metric in the node-data controls to color the hierarchy by cost. +2. Expand the hierarchy and select a chain or cascade to read its reported values. +3. Follow its mapping to **Source Model**, where available, to find the related model operations. + +Use these overlays to find scheduling groups worth investigating. A group can contain several operations, so its cost isn't a separate measurement for each operation. Keep using the model-level report for whole-model totals. + +For the editor workflow, follow the [Ethos-U VS Code and Model Explorer walkthrough](/learning-paths/embedded-and-microcontrollers/analyze-ethos-u-models-with-mlia/7-vscode-plugin/). Its example uses Ethos-U and Vela; use the NX metrics from this analysis when exploring your results. + ## (Optional) Compare a different built-in profile Keep the model unchanged when exploring a different target configuration. For example, run the peak profile into its own directory: diff --git a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/6-iterate-and-compare.md b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/6-iterate-and-compare.md index c0722b5588..b83b1e906b 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/6-iterate-and-compare.md +++ b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/6-iterate-and-compare.md @@ -63,7 +63,7 @@ for name in ("total_cycles", "inference_time", "inferences_per_second", "dram_cy print(f"{name}: {before:.5f} -> {after:.5f} ({change:+.2f}%)") PY ``` -The following table includes example results: +The following table contains reference results recorded with Neural Technology `0.2.0` and LiteRT converter `0.1.2`. Compare your own reports after installing the updated packages: | Metric | Baseline, 224 × 224 | Smaller, 128 × 128 | Change | | --- | ---: | ---: | ---: | diff --git a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/7-run-on-nx-device.md b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/7-run-on-nx-device.md index 462f83f0b9..af0c1f71a5 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/7-run-on-nx-device.md +++ b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/7-run-on-nx-device.md @@ -236,6 +236,47 @@ Your cycle counts will depend on your device and its configuration. To inspect measured NX performance in Model Explorer and trace costs to Python source, follow [Capture and analyze NX profiling data with MLIA for VS Code](/learning-paths/mobile-graphics-and-gaming/profile-nx-with-mlia-vscode/). +## (Optional) Explore measured performance and TOSA operations + +After installing the compatible Model Explorer plugin in [Set up your MLIA environment](/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/2-devenv/#optional-prepare-model-explorer-visualizations), rerun the measured analysis with the same VGF and complete capture directory: + +```bash +mlia check nx-demo/baseline.vgf \ + --target-profile neural-technology \ + --performance \ + --profiling-data "nx-demo/captures/$CAPTURE_NAME" \ + --output-dir runs/baseline-measured-visualized \ + --model-explorer open +``` + +### View measured cycle counts + +The command opens Model Explorer with the capture's results already loaded. In the generated MLIA hierarchy, select a cycle metric using the node-data controls. Expand the hierarchy and select an entity to inspect its reported cycle count. Follow the source mapping, where available, to see related operations in **Source Model**. + +A source mapping associates operations with measured execution groups. It doesn't necessarily provide an independent cycle measurement for each operation. Several operations can share the same group measurement, so don't sum their displayed cycle counts as separate costs. + +These overlays show the capture's measurements. Only entities with reported metrics have values; a missing value doesn't mean zero cycles. The `951,154` cycles in the reference CLI output describe the whole-model result, rather than the selected convolution. + +MLIA saves the visualization in `runs/baseline-measured-visualized/mlia-output/model-explorer/bundle/index.html`. Open this self-contained file to revisit or share the results without rerunning the capture or starting a server. + +### Inspect a TOSA convolution + +In **Source Model**, inspect `nx-demo/baseline.vgf`. The reference graph contains 103 nodes. Expand `graph_segment_0` and select the first `CONV2D` node, between two `RESCALE` nodes. Your graph can differ with compiler or package changes. + +This view exposes the TOSA operations in the VGF graph. You can see how the original MobileNet layer is represented after MLIA's LiteRT-to-TOSA conversion and VGF generation. The screenshot shows the operation's attributes. + +![Model Explorer shows a TOSA CONV2D operation between two RESCALE nodes in baseline.vgf. The Attributes panel links to the TOSA CONV2D specification and shows the MobileNet source location, connecting the converted operation to its definition and original model.#center](model-explorer-conv2d.png "Inspect a TOSA convolution in the VGF graph") + +In **ATTRIBUTES**, **opcode** identifies the TOSA operation as `CONV2D`. Select **TOSA spec: CONV2D** in the **spec** field to open the operator definition. Compare it with the values shown for this node: + +| Field | Value in the reference graph | What it tells you | +| --- | --- | --- | +| **result type** | `uint32[1, 112, 112, 8]` | The intermediate tensor has a batch size of one, 112 × 112 spatial dimensions, and eight channels. | +| **arg1 stride** | `int32[2, 2]` | A stride of two reduces the 224 × 224 input to 112 × 112. | +| **debug info** | `MobilenetV1/MobilenetV1/Conv2d_0/Relu6` | Relates the TOSA operation to its source location in MobileNet. Expand the field to read the full name. | + +Select the following `RESCALE` node to inspect how TOSA scales the convolution's intermediate integer result into the output's quantized range. Search for `DEPTHWISE_CONV2D` and follow its specification link to compare the two convolution operators. + ## Use the measurements @@ -249,3 +290,5 @@ Check the predictions on real images as well. A faster model isn't useful if it You've run the VGF file on an NX-capable device, checked that Scenario Runner completed, and confirmed that the neural statistics capture contains data. You've also returned the capture to MLIA for measured analysis. Use this evidence alongside model accuracy tests and end-to-end application profiling before making deployment decisions for your own use cases. + +If your deployment uses ExecuTorch, continue with [Analyze ExecuTorch ETRecord and ETDump profiling](/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/8-executorch-profiling/) using artifacts from that deployed program. diff --git a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/8-executorch-profiling.md b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/8-executorch-profiling.md new file mode 100644 index 0000000000..0c062544f9 --- /dev/null +++ b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/8-executorch-profiling.md @@ -0,0 +1,140 @@ +--- +title: (Optional) Analyze ExecuTorch ETRecord and ETDump profiling +description: Analyze matching ExecuTorch ETRecord and ETDump artifacts with MLIA, inspect runtime timings and VGF cycles, and optionally explore a companion PTE graph. +weight: 9 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Prepare artifacts from your ExecuTorch deployment + +Use this workflow when your application runs an ExecuTorch program and you've already captured its profiling data in an ETDump file. MLIA reads the captured data and reports measured performance. The earlier LiteRT conversion and Scenario Runner steps don't generate these artifacts. + +Collect the following files from the same deployed program and model build: + +| Artifact | Contents | +| --- | --- | +| `model.etrecord` or `model.bin` | Recorded model graphs and debug mappings generated during export | +| `run.etdump` or `run.etdp` | One matching runtime capture, including VGF profiling when enabled | +| `model.pte` (optional) | The deployed program containing a VGF delegate, for companion graph associations | + +For artifact generation, see the [ExecuTorch 1.5 profiling tutorial](https://docs.pytorch.org/executorch/1.5/tutorials/devtools-integration-tutorial.html). For VGF-specific guidance, see [VGF neural accelerator statistics in ETDump](https://docs.pytorch.org/executorch/stable/backends/arm-vgf/vgf-neural-statistics.html) to enable statistics collection. The [ExecuTorch 1.0 Arm VGF backend tutorial](https://docs.pytorch.org/executorch/1.0/tutorial-arm-vgf.html) demonstrates building and running a VGF-delegated model with `executor_runner`. Use the compatible toolchain version specified in the installation steps when generating your artifacts. + +VGF cycle analysis additionally requires VGF profiling data in the ETDump. An ordinary runtime-only capture can provide source timings, but it can't provide VGF counters or companion VGF associations. + +Run the remaining commands from `mlia-demo`. Copy your artifacts into a new `et-profile` directory and use the example filenames above, or substitute their paths in the commands. Keep the original files together so that you can confirm which export and execution produced them. + +## Install the ExecuTorch analysis support + +Activate the environment from the setup page and install the Neural Technology `executorch` extra: + +```bash +source mlia-venv/bin/activate +python -m pip install "mlia-neural-technology[executorch]==0.3.0" mlia-converters-pytorch==0.1.4 +python -m pip check +``` + +This extra installs the shared ExecuTorch artifact readers from `mlia-converters-pytorch`. Neural Technology provides the runtime analysis, VGF decoding, and NX metrics. The converter package requires ExecuTorch `1.5.1`; use compatible artifacts from that toolchain. + +The expected output from `pip check` is `No broken requirements found.` Model Explorer is optional for the analysis itself. + +## Analyze the matching ETRecord and ETDump + +Run a measured performance check: + +```bash +mlia check et-profile/model.etrecord \ + --target-profile neural-technology \ + --performance \ + --profiling-data et-profile/run.etdump \ + --output-dir runs/executorch-measured +``` + +`--profiling-data` selects the measured backend automatically. Use performance analysis for this workflow; compatibility analysis belongs to the earlier model-based checks. + +For an ETRecord with several methods, select the method that produced the dump. For example, append the following option for a method named `decode`: + +```text +--neural-technology-profiling-data.etrecord-method decode +``` + +Single-method selection is automatic. If method selection or graph mapping fails, check that the artifacts come from the same deployed program and selected method. + +Inspect the saved results and warnings: + +```bash +python - <<'PY' +import json +from pathlib import Path + +report_path = Path("runs/executorch-measured/mlia-output/mlia-output.json") +report = json.loads(report_path.read_text()) +results = [result for result in report["results"] if result["kind"] == "performance"] +assert results, "No performance results found. Review the MLIA log." +for result in results: + assert result["status"] == "ok", "Review the performance result for errors." + assert result.get("mode") == "measured", "Expected measured profiling data." + print(json.dumps(result, indent=2)) +PY +``` + +The check confirms that MLIA produced successful measured performance results and prints their metrics, breakdowns, and any warnings. Values depend on your capture. Check the reported execution coverage and decoder assumptions before treating cycle summaries as full-inference costs. + +## (Optional) Open the profiling views in Model Explorer + +Use MLIA Model Explorer `0.2.0`, which supports MLIA core `0.13.x`. Install its `executorch` extra in the same environment, even if you installed the `vgf` extra during setup: + +```bash +python -m pip install "mlia-model-explorer[executorch]==0.2.0" +python -m pip check +``` + +The extra includes ETRecord, PTE, and VGF adapters. After `pip check` succeeds, rerun the analysis into a separate directory and request a visualization: + +```bash +mlia check et-profile/model.etrecord \ + --target-profile neural-technology \ + --performance \ + --profiling-data et-profile/run.etdump \ + --output-dir runs/executorch-visualized \ + --model-explorer open +``` + +If you also have the matching deployed VGF-backed PTE and VGF profiling in the dump, append `--companion-model et-profile/model.pte` to add its compiler graph. Supplying a companion alone doesn't enable visualization. Runtime-only captures support visualization without a companion. + +Use `--model-explorer files` to generate the files without opening a browser. The standalone page is `runs/executorch-visualized/mlia-output/model-explorer/bundle/index.html`. + +In Model Explorer, inspect the views available for your capture: + +| View | What to inspect | +| --- | --- | +| **Source Model** | Source operators and available runtime timings | +| **segment_cascade_chain** | Captured NX hierarchy and cycle measurements | +| **ETRecord Chain Mapping** | Chains associated with source operators; the initial detailed view when available | +| **ETDump Backend Trace** | Compiler operations and their measured chain groups | +| **PTE - VGF Backend Hierarchy** | Companion compiler graph and associated chain groups, when supplied | + +Select a mapped operator or group to navigate between related views. Compiler transformations can rename, insert, combine, or reorder operations, so some source links can be incomplete or absent. Unmatched compiler operations remain visible. A missing link doesn't establish that an operation wasn't executed or that its chain has no profiling data. + +## Interpret runtime timings and NX counters + +Ordinary runtime timings use milliseconds. VGF counters use cycles and describe recorded accelerator activity. Cycle counters don't reconstruct idle gaps or establish elapsed latency. + +For older captures missing decoder metadata, MLIA assumes statistics mode `1` and the Mali-G2 device layout. Check the reported assumptions against your capture settings. + +Repeated executions provide averages, minimum, maximum, P50, and execution counts where supported. Independent observations are combined within each recorded execution before summaries are calculated. Don't add minimum, maximum, P50, or execution counts across operators. Averages can be combined only when their measurements are independent and cover the same executions. + +A runtime event can cover several source operators. Each associated operator can display the same shared timing; that value isn't an independent cost for each operator. VGF chain or cascade measurements can also cover several compiler operations. Use the authoritative hierarchy measurements and model-level results instead of summing every overlay or hierarchy level. + +Mixed-backend dumps keep ordinary runtime timings separate from VGF counters. Instructions with VGF profiling are excluded from ordinary runtime aggregation to avoid counting the same work twice. Runtime event counts describe contributing event records, rather than the number of complete inferences. + +MLIA doesn't estimate missing measurements. If instructions or segments cover different executions, model cycle summaries can be unavailable with a warning while individual measurements remain available. Even matching coverage doesn't prove every expected stripe was captured. Review warnings and, for older captures, any assumed statistics mode or device layout. + +Companion matching checks structural compatibility and assumes you supplied the matching PTE. It doesn't prove that the PTE produced the capture. Model Explorer checks that the companion file hasn't changed since analysis; rerun analysis if it has. + +## What you've accomplished + +You've analyzed an ExecuTorch ETRecord/ETDump pair and checked its measured results. With a compatible Model Explorer installation, you can also inspect runtime timings, NX hierarchy measurements, and optional companion graph associations. + +Use the measured costs to select a model change to investigate. Repeat the capture under the same conditions and validate accuracy before drawing deployment conclusions. diff --git a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/_index.md b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/_index.md index f663372071..7c253f7ac7 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/_index.md @@ -10,7 +10,7 @@ who_is_this_for: This Learning Path is for machine learning (ML) engineers and g learning_objectives: - Install the public Arm ML Inference Advisor (MLIA) Neural Technology plugin and discover its target profiles and backend. - Analyze public quantized MobileNet models with automatic model conversion. - - Check operator compatibility and interpret NX Performance Estimator reports. + - Check operator compatibility and interpret NX estimates, measured profiling results, and optional Model Explorer overlays. - Compare model variants under the same target configuration and identify accuracy tradeoffs. prerequisites: diff --git a/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/model-explorer-conv2d.png b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/model-explorer-conv2d.png new file mode 100644 index 0000000000..d68dbed138 Binary files /dev/null and b/content/learning-paths/mobile-graphics-and-gaming/mlia-nx-performance-estimator/model-explorer-conv2d.png differ diff --git a/content/learning-paths/mobile-graphics-and-gaming/neural-graphics-data-capture-unreal/_index.md b/content/learning-paths/mobile-graphics-and-gaming/neural-graphics-data-capture-unreal/_index.md index 747d9e7703..69bf4a8f76 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/neural-graphics-data-capture-unreal/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/neural-graphics-data-capture-unreal/_index.md @@ -1,5 +1,5 @@ --- -title: Generate neural graphics datasets with Neural Graphics Data Capture in Unreal Engine +title: Capture neural graphics datasets with Neural Graphics Data Capture description: Learn how to capture high-quality frame datasets from Unreal Engine 5.5 gameplay for training and evaluating neural graphics models like Neural Super Sampling. minutes_to_complete: 30 diff --git a/content/learning-paths/mobile-graphics-and-gaming/performance_llama_cpp_sme2/introduction.md b/content/learning-paths/mobile-graphics-and-gaming/performance_llama_cpp_sme2/introduction.md index 8d4c229fd7..249447f9c9 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/performance_llama_cpp_sme2/introduction.md +++ b/content/learning-paths/mobile-graphics-and-gaming/performance_llama_cpp_sme2/introduction.md @@ -1,5 +1,5 @@ --- -title: Understand how SME2 and KleidiAI accelerate LLM inference in llama.cpp +title: Explore SME2 and KleidiAI acceleration in llama.cpp weight: 2 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/mobile-graphics-and-gaming/performance_llama_cpp_sme2/kleidiai_integration.md b/content/learning-paths/mobile-graphics-and-gaming/performance_llama_cpp_sme2/kleidiai_integration.md index a506dcd95b..a0ab1ed493 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/performance_llama_cpp_sme2/kleidiai_integration.md +++ b/content/learning-paths/mobile-graphics-and-gaming/performance_llama_cpp_sme2/kleidiai_integration.md @@ -1,5 +1,5 @@ --- -title: Trace how KleidiAI and SME2 accelerate llama.cpp from model load to token decode +title: Trace KleidiAI and SME2 acceleration in llama.cpp weight: 3 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/mobile-graphics-and-gaming/performance_onnxruntime_kleidiai_sme2/_index.md b/content/learning-paths/mobile-graphics-and-gaming/performance_onnxruntime_kleidiai_sme2/_index.md index 1a8df3aedd..558a7964b0 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/performance_onnxruntime_kleidiai_sme2/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/performance_onnxruntime_kleidiai_sme2/_index.md @@ -1,5 +1,5 @@ --- -title: Profile ONNX model performance with SME2 using KleidiAI and ONNX Runtime +title: Profile ONNX models with SME2, KleidiAI, and ONNX Runtime description: Learn how to build ONNX Runtime with KleidiAI and SME2 support for Android and profile ONNX model performance to compare acceleration improvements. minutes_to_complete: 40 diff --git a/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/08-training-our-ml-agents.md b/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/08-training-our-ml-agents.md index 7589fdc271..8bc479b6ca 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/08-training-our-ml-agents.md +++ b/content/learning-paths/mobile-graphics-and-gaming/using_unity_machine_learning_agents/08-training-our-ml-agents.md @@ -534,7 +534,7 @@ Within this yaml you have the ability to set-up 2 separate brains. One named _Pa 1. Next to the _Easy Battle Brain_ there is a circle. Click it to pop-up the _Select NN Model_ menu, as per Figure 2. - !["NN Model Pop-up"](images/battle-enviroment-controller-battle-brain.png "Figure 2. Select an NN Model asset for each difficulty") + ![NN Model Pop-up#center](images/battle-enviroment-controller-battle-brain.png "Figure 2. Select an NN Model asset for each difficulty") Note that you can use the slider in the pop-up to change the icon size. Drag fully to the left to reduce to list detail view if needed. 1. For each brain there is already a pre-trained version you can use, or if your training has completed use that. Match up each Battle Brain property to an appropriate NN Model. We have used the name "BossBattle" in this learning path. diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-assistant/_index.md b/content/learning-paths/mobile-graphics-and-gaming/voice-assistant/_index.md index 7d4229144d..7e59fcf01c 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/voice-assistant/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/voice-assistant/_index.md @@ -1,5 +1,5 @@ --- -title: Accelerate multimodal Voice Assistant performance with KleidiAI and SME2 +title: Accelerate a voice assistant with KleidiAI and SME2 description: Learn how to build and optimize a multimodal Voice Assistant application on Android using KleidiAI and SME2 for accelerated performance. minutes_to_complete: 30 diff --git a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/1-introduction.md b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/1-introduction.md index 278715d573..f5883f98cf 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/1-introduction.md +++ b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/1-introduction.md @@ -12,7 +12,13 @@ Neural graphics combines real-time rendering with machine learning to improve vi ## How do ML Extensions for Vulkan support neural graphics workloads? -The `VK_ARM_data_graph` and `VK_ARM_tensors` extensions bring structured compute graph execution and tensor processing to the Vulkan API. Their data graph pipelines use SPIR-V-defined graphs to execute ML inference workloads efficiently. +Vulkan extensions add functionality to the Vulkan API. Arm's ML Extensions for Vulkan introduce support for tensor processing and machine learning data graphs. + +To make these extensions available on Vulkan-capable hardware that does not support them natively, Arm provides **ML Emulation Layers for Vulkan**, part of the [ML SDK for Vulkan](https://arm.github.io/ai-ml-sdk-for-vulkan/). A Vulkan layer is a software component that sits between a Vulkan application and the Vulkan driver. The ML Emulation Layers expose Arm's ML extensions and emulate their functionality using existing Vulkan compute capabilities. + +The `VK_ARM_tensors` extension introduces support for multidimensional tensor data in Vulkan, while `VK_ARM_data_graph` enables the execution of ML computational graphs using Vulkan data graph pipelines. These pipelines use SPIR-V, Vulkan's intermediate representation for GPU programs, to define and execute ML computational graphs. + +![Arm ML Emulation Layers architecture#center](images/ml-emulation-layers.png) To help developers adopt these features, the Tensor and Data Graph Vulkan Samples offer hands-on demonstrations. @@ -25,17 +31,16 @@ The samples range from basic configurations to more advanced features, such as t This Learning Path walks you through setting up and running the first sample, `simple_tensor_and_data_graph`. -### Why use ML Extensions for Vulkan in game and graphics development? +## Why use ML Extensions for Vulkan in game and graphics development? As a game developer, you've probably noticed the rising demand for smarter, more immersive graphics — but also the increasing strain on GPU resources, especially on mobile. Vulkan's traditional pipelines give you fine-grained control, but finding the right tooling to integrate machine learning has been a challenge. That’s where the new ML extensions for Vulkan come in. -Arm’s `VK_ARM_tensors` and `VK_ARM_data_graph` extensions give you native Vulkan support for executing neural networks on the GPU — using structured tensors and data graph pipelines. Instead of chaining compute shaders to simulate ML models, you can now express them as dataflow graphs in SPIR-V and run them more efficiently. This opens the door to using AI techniques right alongside the graphics pipeline. - +Instead of coordinating individual compute shaders to implement ML workloads, developers can express computations as dataflow graphs in SPIR-V. This provides a more structured approach to integrating machine learning alongside traditional Vulkan graphics workloads and creates opportunities for graph-level optimization. -Although ML is widely used for image classification and large language models (LLMs), these extensions target game and graphics workloads. They prioritize predictable execution, GPU compatibility, and memory efficiency. Built-in support for tensor formats and pipeline sessions helps you combine traditional Vulkan rendering with machine learning. +Although ML is widely used for image classification and large language models (LLMs), these extensions are particularly useful for game and graphics workloads. They prioritize predictable execution, GPU compatibility, and memory efficiency. Built-in support for tensor formats and pipeline sessions helps you combine traditional Vulkan rendering with machine learning. -Arm provides emulation layers for development on any modern Vulkan-capable hardware, and PyTorch support is available for model conversion workflows. +## What's next? -For an example of real-time upscaling, see the Learning Path [**Neural Super Sampling with Unreal Engine**](/learning-paths/mobile-graphics-and-gaming/nss-unreal/). +For an example of neural graphics in practice, see the Learning Path [**Neural Super Sampling with Unreal Engine**](/learning-paths/mobile-graphics-and-gaming/nss-unreal/). -With the Vulkan Samples, you can experiment directly with these ideas. Move on to the next section to set up your machine for running the samples. +Continue to the next section to install and configure the ML Emulation Layers for Vulkan, then build and run the Vulkan Samples. diff --git a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/2-ml-ext-for-vulkan.md b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/2-ml-ext-for-vulkan.md index 3ae16a03d4..2d897573ea 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/2-ml-ext-for-vulkan.md +++ b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/2-ml-ext-for-vulkan.md @@ -10,38 +10,28 @@ layout: learningpathall To run the Vulkan Samples, you first need to set up your development environment. -This setup involves two main steps: +This setup involves three main steps: -- Install the required tools on your development machine. -- Download the ML Emulation Layers for Vulkan, which simulate the `VK_ARM_data_graph` and `VK_ARM_tensors` extensions. +- Create a Python virtual environment and install the ML Emulation Layers for Vulkan. +- Install the LunarG Vulkan SDK. +- Enable the ML Emulation Layers using Vulkan Configurator. -## Install required tools for development +## Set up the development environment -Before building and running the samples, install these tools on your development machine: +Open **Windows PowerShell** from the Windows 11 Start menu. Verify that Python 3.10 or later is installed. Use a standard CPython build rather than a free-threaded build, then navigate to your user home directory: -- CMake (version 3.25 or later) -- Python 3 -- Git - -Install Git with the following command: - -```bash -winget install --id Git.Git -e +```powershell +python --version +cd $HOME ``` -In Visual Studio Code, create a folder for the project and install these Microsoft extensions: - -- C/C++ -- CMake Tools - -![C/C++ tools extension](images/c-ext.png) +Create a Python virtual environment and install the following packages: -![CMake Tools extension](images/cmake-ext.png) +- **ML Emulation Layers for Vulkan:** Provides the emulation layers introduced in the previous section. +- **CMake:** A build tool used to configure and build the Vulkan Samples. -Create a Python virtual environment and install the required packages: - -```bash -py -m venv .venv +```powershell +python -m venv .venv .\.venv\Scripts\Activate.ps1 python -m pip install --upgrade pip python -m pip install ai-ml-emulation-layer-for-vulkan cmake @@ -49,7 +39,7 @@ python -m pip install ai-ml-emulation-layer-for-vulkan cmake To verify your installation, run the following commands: -```bash +```powershell cmake --version python --version git --version @@ -58,39 +48,60 @@ python -m pip show ai-ml-emulation-layer-for-vulkan Each command prints the installed version of the tool or shows where the appropriate files are located. -### Install the Vulkan Software Development Kit -Download the Windows installer from [Getting Started with the Windows Vulkan SDK](https://vulkan.lunarg.com/sdk/home). The Vulkan Software Development Kit (SDK) includes **Vulkan Configurator**, which you use to run the emulation layers. +## Install the Vulkan Software Development Kit -{{% notice Note %}} +Arm's ML SDK for Vulkan and the LunarG Vulkan SDK serve different purposes. -Use Vulkan SDK version 1.4.321 or later. +Arm's ML SDK for Vulkan provides the ML Emulation Layers installed earlier in this Learning Path. The LunarG Vulkan SDK provides Vulkan development tools, including Vulkan Configurator, which you'll use to enable those layers. -{{% /notice %}} +The two SDKs work together to provide a development environment where you can build and run Vulkan applications using the emulated ML extensions. + +Download and install the Vulkan SDK from [Getting Started with the Windows Vulkan SDK](https://vulkan.lunarg.com/sdk/home). + +1. Under **Windows**, select **x64 / x86**. +2. Download the **SDK Installer** for version 1.4.321 or later. Do not select the Runtime Installer. +3. Run the installer and accept the default installation directory. +4. Keep the default component selections and accept the license agreement. +5. Complete the installation. +6. On the final screen, select **Run the Vulkan Configurator and Hardware Capabilities Viewer**, then click **Finish**. +Verify that Vulkan Configurator opens successfully. You will use it in the next section to enable the ML Emulation Layers for Vulkan. ## Enable the emulation layers in Vulkan Configurator -Open **Vulkan Configurator** to enable the layers that emulate the `VK_ARM_data_graph` and `VK_ARM_tensors` extensions. +Installing the ML Emulation Layers makes them available on your system, but they must also be enabled so that Vulkan applications can discover and use the emulated ML extensions. -On the **Vulkan Layers Available** tab, browse to the emulation layer folder in your virtual environment's `site-packages` directory: +If Vulkan Configurator is not already open, launch it from the Windows Start menu. -```output -.venv/Lib/site-packages/emulation_layer/deploy/bin -``` +In **Vulkan Configurator**, configure the emulation layers for the `VK_ARM_data_graph` and `VK_ARM_tensors` extensions. -![VL available](images/vl-available.png) +1. In the **Vulkan Layers Available** tab, add the directory containing the emulation layer manifests from your Python virtual environment: -On the **Vulkan Layers Configuration** tab, set **Vulkan Loader Configuration Scope** to **Any Running Vulkan Executable**. Select **New Configuration**, then enable the **Graph Emulation** and **Tensor Emulation** layers. + ```text + .venv\Lib\site-packages\emulation_layer\deploy\bin + ``` -Ensure that the **Graph** layer is listed above the **Tensor** layer. + For example, if you created the virtual environment in your Windows user home directory, the path is: -![VL Config](images/vl-layers.png) + ```text + C:\Users\\.venv\Lib\site-packages\emulation_layer\deploy\bin + ``` -{{% notice Important %}} +2. Verify that the following layers appear in the available layers list: + - `VK_LAYER_ML_Graph_Emulation` + - `VK_LAYER_ML_Tensor_Emulation` -Keep Vulkan Configurator running while you run the Vulkan samples. +![Vulkan Layers Available](images/vl-available.png) + +3. In the **Vulkan Layers Configuration** tab, enable system Vulkan layer configuration and ensure both emulation layers are set to **Enable**. +4. Verify that the **Graph Emulation** layer is listed above the **Tensor Emulation** layer. + +![Vulkan Layers Configuration](images/vl-layers.png) + +{{% notice Important %}} +Keep Vulkan Configurator running while you run the Vulkan samples. {{% /notice %}} -With the emulation layers configured, you're ready to build the Vulkan Samples. +With the emulation layers configured, you're ready to build the Vulkan Samples. Continue to the next section to get started. diff --git a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/3-first-sample.md b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/3-first-sample.md index 527214db76..1bd3d8fe43 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/3-first-sample.md +++ b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/3-first-sample.md @@ -14,7 +14,7 @@ The **Simple Tensor and Data Graph** sample is a starting point for working with With the environment set up, clone the sample code from the Khronos Group repository: -```bash +```powershell git clone --recurse-submodules https://github.com/KhronosGroup/Vulkan-Samples.git cd Vulkan-Samples ``` @@ -31,13 +31,13 @@ Enable [Developer Mode](https://learn.microsoft.com/en-us/windows/apps/get-start Generate Visual Studio project files using CMake: -```bash +```powershell cmake -G "Visual Studio 17 2022" -A x64 -S . -Bbuild/windows ``` Compile the `vulkan_samples` target in Release mode: -```bash +```powershell cmake --build build/windows --config Release --target vulkan_samples ``` @@ -45,7 +45,7 @@ cmake --build build/windows --config Release --target vulkan_samples Run the built executable using the following command: -```bash +```powershell build\windows\app\bin\Release\AMD64\vulkan_samples.exe sample simple_tensor_and_data_graph ``` @@ -55,7 +55,7 @@ The sample creates input and output tensors, binds them with descriptor sets and ## Summary and next steps -By running this sample, you've stepped through a complete Vulkan data graph pipeline powered by ML Extensions for Vulkan. You've created tensors, set up descriptors, built a SPIR-V-encoded ML graph, and dispatched inference without custom shaders. This workflow provides a foundation for neural graphics and extends to more complex graphics scenarios. +By running this sample, you've seen how a Vulkan application creates tensors, configures a data graph pipeline, and dispatches an ML workload using the ML Extensions for Vulkan. This provides a foundation for exploring more complex ML and graphics workloads. You can also explore the remaining data graph pipeline samples. Each sample's documentation is in its directory under `samples/extensions/tensor_and_data_graph/`. diff --git a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/4-scenario-runner.md b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/4-scenario-runner.md index 3aa090de86..7cbf57019d 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/4-scenario-runner.md +++ b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/4-scenario-runner.md @@ -1,5 +1,5 @@ --- -title: Running a test with the Scenario Runner +title: Run an ML workload with Scenario Runner weight: 5 ### FIXED, DO NOT MODIFY @@ -8,13 +8,16 @@ layout: learningpathall ## Overview -You'll learn how to run a complete inference test with the **Scenario Runner** from Arm's ML SDK for Vulkan. You'll also explore the downloadable binaries and assets on Arm's Hugging Face page that demonstrate ML Extensions for Vulkan. +You've already configured your Vulkan development environment and run the Simple Tensor and Data Graph sample using the ML Emulation Layers. -## About the ML SDK for Vulkan +In this section, you'll use **Scenario Runner**, a tool from Arm's ML SDK for Vulkan, to execute a prebuilt Neural Super Sampling (NSS) workload and verify its output. -The SDK provides tools and runtime components that help you integrate neural networks into Vulkan-based applications. ML Extensions for Vulkan (`VK_ARM_data_graph` and `VK_ARM_tensors`) define the runtime interface. The SDK provides a workflow for converting, packaging, and deploying ML models in real-time applications such as games. +## Scenario Runner in the ML SDK for Vulkan + +Arm's ML SDK for Vulkan provides tools and libraries for preparing and executing ML workloads. You've already installed and configured the ML Emulation Layers, one of the SDK components. + +The following table summarizes the main SDK components and their roles. -### SDK component summary | Component | Description | Usage context | GitHub link | |------------------|------------------------------------------------------------------------------------------------------|-------------------------------------|--------------| @@ -22,12 +25,117 @@ The SDK provides tools and runtime components that help you integrate neural net | **VGF Library** | Lightweight runtime decoder for `.vgf` files containing graphs, constants, and shaders. | Load and use graphs in a game engine | https://github.com/arm/ai-ml-sdk-vgf-library | | **Scenario Runner** | Executes ML workloads declaratively using JSON-based scenario descriptions. | Prototype and validate workloads | https://github.com/arm/ai-ml-sdk-scenario-runner | | **Emulation Layer** | Vulkan layer that emulates data graph and tensor extensions using compute shaders. | Test on devices without native support for ML Extensions for Vulkan | https://github.com/arm/ai-ml-emulation-layer-for-vulkan | +| **ML Workload Library for Vulkan** | C++ library for creating and executing ML workloads using Vulkan. | Integrate ML workloads into Vulkan applications | https://github.com/arm/ai-ml-workload-library-for-vulkan | + +VGF is a format for packaging machine learning graphs and their associated data for execution with Vulkan. It brings together the graph definition, shaders, and model constants in a single file, making it easier to distribute and load prebuilt ML workloads. + +For this exercise, Scenario Runner uses the VGF Library to load a prebuilt `.vgf` model and executes the workload described in a JSON scenario file through the configured ML Emulation Layers. + +The following diagram shows how these components fit together during execution. + +![Scenario Runner architecture#center](images/scenario-runner-architecture-400.png) + +## Run a Neural Super Sampling scenario + +You'll use the [Neural Super Sampling example on Hugging Face](https://huggingface.co/Arm/neural-super-sampling) to run an inference workload with Scenario Runner. The repository provides the model, sample data, and additional documentation. + +### Download the example + +The repository uses Git Large File Storage (Git LFS) to manage large model and data files. + +Open Windows PowerShell and navigate to your home directory: + +```powershell +cd $HOME +``` + +Verify that Git LFS is installed. If it is not installed, download it from the [Git LFS website](https://git-lfs.com/) before continuing. + +Verify and initialize Git LFS: + +```powershell +git lfs version +git lfs install +``` + +Clone the Neural Super Sampling repository: + +```powershell +git clone https://huggingface.co/Arm/neural-super-sampling +cd neural-super-sampling +git lfs pull +``` + +### Install Scenario Runner + +Use the Python virtual environment you created earlier when setting up the ML Emulation Layers for Vulkan. + +If the virtual environment is not already active, activate it: + +```powershell +& "$HOME\.venv\Scripts\Activate.ps1" +``` +Install Scenario Runner: -## About the Hugging Face release +```powershell +python -m pip install "ai-ml-sdk-scenario-runner>=0.11.0" +``` -The [NSS model page on Hugging Face](https://huggingface.co/Arm/neural-super-sampling) provides a minimal example, called a *scenario*, for running NSS on a sample frame. It includes a Windows-compatible Scenario Runner binary, the VGF model, and one frame of input and expected output data. You can use these assets to run an end-to-end workflow and explore the VGF model in more detail. +This installs the latest compatible version of Scenario Runner, starting from v0.11.0. If no compatible version is available for your Python environment, the installation will fail rather than selecting an older release. + +Verify the installed version: + +```powershell +python -m pip show ai-ml-sdk-scenario-runner +``` + +Verify that Scenario Runner is available: + +```powershell +scenario_runner --help +``` + +### Run the Neural Super Sampling scenario + +Navigate to the `scenario` directory in the cloned repository: + +```powershell +cd "$HOME\neural-super-sampling\scenario" +``` + +Run Scenario Runner using the supplied JSON configuration: + +```powershell +scenario_runner --scenario configs/960x540_1920x1080_high_fragment.json --output out_high +``` + +The configuration specifies a Neural Super Sampling workload that upscales an input frame from 960 × 540 to 1920 × 1080. + +Scenario Runner loads the prebuilt VGF model, executes the workload through the configured ML Emulation Layers, and writes the results to the `out_high` directory. + +### Examine the results + +When execution completes, Scenario Runner displays: + +```text +[Scenario-Runner] INFO: Results stored +``` + +The generated files are saved in the `out_high` directory. + +Verify the output files using PowerShell: + +```powershell +Get-ChildItem .\out_high +``` + +The output includes DDS image files (`.dds`) and NumPy tensor files (`.npy`). + +You'll use RenderDoc in the next section to view the generated images. ## Next steps -Next, you'll use RenderDoc to debug and inspect the workloads in this Learning Path. +You've successfully executed a Neural Super Sampling workload using Scenario Runner. + +Next, you'll install RenderDoc for Arm GPUs to view the generated images and explore how to capture and inspect Vulkan workloads. diff --git a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/5-renderdoc.md b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/5-renderdoc.md index fb9e148b0d..752698d62d 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/5-renderdoc.md +++ b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/5-renderdoc.md @@ -6,26 +6,17 @@ weight: 6 layout: learningpathall --- -## Debug and profile workloads with RenderDoc +## Inspect and debug Vulkan workloads with RenderDoc -Integrating machine learning into real-time rendering makes frame-level inspection and performance analysis critical. RenderDoc helps you visualize and debug the workloads by letting you step through frames, examine tensors, and inspect Vulkan API calls. +Integrating machine learning into real-time rendering requires tools to inspect and debug Vulkan workloads. RenderDoc is a GPU frame capture tool that lets developers examine Vulkan API calls, shaders, and GPU resources. -RenderDoc is a powerful GPU frame capture tool that lets you: +With RenderDoc, you can: -- Step through a frame’s rendering process +- Step through a frame's rendering process - Inspect Vulkan API calls - View shader inputs and outputs - Examine GPU resource states and memory usage -## When to use RenderDoc with the samples - -RenderDoc can help in scenarios such as: - -- Diagnosing unexpected visual output by stepping through draw calls -- Analyzing the order and behavior of Vulkan API calls -- Investigating memory consumption or GPU resource states -- Validating data graph pipeline execution or identifying synchronization issues - ## Install Arm Performance Studio To use RenderDoc with ML Extensions for Vulkan, install the Arm-customized version included with Arm Performance Studio: @@ -35,15 +26,25 @@ To use RenderDoc with ML Extensions for Vulkan, install the Arm-customized versi `Arm_Performance_Studio__windows_x86-64.exe` 3. Follow the installation instructions. -After installation, launch RenderDoc for Arm GPUs from the Windows Start menu. +## View the Neural Super Sampling output -## Capture Vulkan frames with RenderDoc +You can use RenderDoc for Arm GPUs to view the DDS images generated by Scenario Runner in the previous section. -You can capture and inspect Vulkan Samples that use ML Extensions for Vulkan, including API calls such as `vkCreateTensorARM` and structures such as `VK_STRUCTURE_TYPE_TENSOR_DESCRIPTION_ARM`. +The `out_colour.dds` image uses the `B10G11R11_UFLOAT` format, which is not widely supported by standard image viewers. + +1. Launch **RenderDoc for Arm GPUs** from the Windows Start menu. +2. Select **File → Open Capture**. +3. Set the file filter to **All Files**. +4. Navigate to the `neural-super-sampling\scenario\out_high` directory and open `out_colour.dds`. +5. Select the **Texture Viewer** tab to inspect the image. + +The output image has a resolution of 1920 × 1080. + +## Capture Vulkan frames with RenderDoc -RenderDoc is especially useful for visualizing tensor operations, inspecting resource bindings, and verifying correct data graph pipeline execution. +So far, you've used RenderDoc to view the output of the Neural Super Sampling workload. You can also use RenderDoc to capture and inspect Vulkan API calls during application execution. -## Capture with RenderDoc +To explore this functionality, return to the Simple Tensor and Data Graph sample you ran earlier in this Learning Path. By capturing a frame, you can inspect Vulkan operations, resource bindings, and data graph pipeline execution. 1. **Open RenderDoc**, and in the main window, go to the **Launch Application** section. 2. Configure the following fields: diff --git a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/_index.md b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/_index.md index c453eca4bd..6f75d1718e 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/_index.md @@ -1,9 +1,9 @@ --- -title: Enable neural graphics using ML Extensions for Vulkan +title: Enable Neural Graphics using ML Extensions for Vulkan® description: Learn how to set up ML Emulation Layers for Vulkan, run sample applications with ML Extensions for Vulkan, and debug the workflow with RenderDoc. minutes_to_complete: 30 -who_is_this_for: This advanced Learning Path is for engine developers who want to create neural graphics with ML Extensions for Vulkan. +who_is_this_for: This advanced Learning Path is for developers who want to create neural graphics and AI/ML workloads using ML Extensions for Vulkan. learning_objectives: - Explain the purpose of neural graphics and the role of ML Extensions for Vulkan @@ -12,11 +12,11 @@ learning_objectives: - Debug the flow using RenderDoc prerequisites: - - Windows 11 development machine - - Visual Studio 2022 - - Visual Studio workload - Desktop development with C++ - - Visual Studio workload - .NET desktop build tools - - Visual Studio Code + - Windows 11 x86-64 computer (physical machine or virtual machine) + - Visual Studio 2022 with Desktop development with C++ and .NET desktop build tools workloads + - Visual Studio Code with C/C++ and CMake Tools extensions (Microsoft) + - Python 3.10 or later + - Git # START generated_summary_faq generated_summary_faq: @@ -71,6 +71,7 @@ generated_summary_faq: author: - Annie Tallund - Joshua Marshall-Law + - Barbara Corriero generate_summary_faq: false rerun_summary: false diff --git a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/images/ml-emulation-layers.png b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/images/ml-emulation-layers.png new file mode 100644 index 0000000000..b176aeb861 Binary files /dev/null and b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/images/ml-emulation-layers.png differ diff --git a/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/images/scenario-runner-architecture-400.png b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/images/scenario-runner-architecture-400.png new file mode 100644 index 0000000000..0835e5ccec Binary files /dev/null and b/content/learning-paths/mobile-graphics-and-gaming/vulkan-ml-sample/images/scenario-runner-architecture-400.png differ diff --git a/content/learning-paths/servers-and-cloud-computing/adler32-kiro/_index.md b/content/learning-paths/servers-and-cloud-computing/adler32-kiro/_index.md index b2ed93b09e..934066b05e 100644 --- a/content/learning-paths/servers-and-cloud-computing/adler32-kiro/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/adler32-kiro/_index.md @@ -1,5 +1,5 @@ --- -title: Optimize an Adler-32 checksum function with SVE intrinsics using the Arm MCP server +title: Optimize Adler-32 with SVE and the Arm MCP server description: Use the Arm MCP server with an AI coding assistant to incrementally optimize a scalar C Adler-32 checksum function using SVE intrinsics on Arm Neoverse servers. diff --git a/content/learning-paths/servers-and-cloud-computing/ai-agent-on-cpu/_index.md b/content/learning-paths/servers-and-cloud-computing/ai-agent-on-cpu/_index.md index 2dc13abeef..de4f3a6ea0 100644 --- a/content/learning-paths/servers-and-cloud-computing/ai-agent-on-cpu/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/ai-agent-on-cpu/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy an AI Agent on Arm with llama.cpp and llama-cpp-agent using KleidiAI +title: Deploy an AI agent on Arm with llama.cpp and KleidiAI description: Learn how to build and deploy an AI agent application on Arm servers using llama.cpp and llama-cpp-agent with KleidiAI optimization for efficient LLM inference and function calling. minutes_to_complete: 45 diff --git a/content/learning-paths/servers-and-cloud-computing/ai-portal-cloud-text-to-text/_index.md b/content/learning-paths/servers-and-cloud-computing/ai-portal-cloud-text-to-text/_index.md index 5b90571c46..383462b920 100644 --- a/content/learning-paths/servers-and-cloud-computing/ai-portal-cloud-text-to-text/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/ai-portal-cloud-text-to-text/_index.md @@ -1,5 +1,5 @@ --- -title: Run optimized LLMs from the Arm AI Portal on Arm Neoverse-based instances +title: Run optimized Arm AI Portal LLMs on Neoverse instances description: Download and run an Arm-optimized LLM using the supplied ONNX Runtime GenAI workflow, or use a coding agent to adapt the application for a compatible alternative model runtime or format. diff --git a/content/learning-paths/servers-and-cloud-computing/ai-portal-text-to-speech/_index.md b/content/learning-paths/servers-and-cloud-computing/ai-portal-text-to-speech/_index.md index b02b3e3b11..73efa6fec7 100644 --- a/content/learning-paths/servers-and-cloud-computing/ai-portal-text-to-speech/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/ai-portal-text-to-speech/_index.md @@ -1,5 +1,5 @@ --- -title: Run an optimized text-to-speech model from the Arm AI Portal on an Arm Neoverse-based instance +title: Run Arm AI Portal text-to-speech on Neoverse description: Run an optimized text-to-speech model with ONNX Runtime on an Arm Neoverse-based machine. minutes_to_complete: 40 diff --git a/content/learning-paths/servers-and-cloud-computing/aks/_index.md b/content/learning-paths/servers-and-cloud-computing/aks/_index.md index f28e316654..929f376f2d 100644 --- a/content/learning-paths/servers-and-cloud-computing/aks/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/aks/_index.md @@ -1,5 +1,5 @@ --- -title: Create an Arm-based Kubernetes cluster on Microsoft Azure Kubernetes Service +title: Create an Arm-based cluster on Azure Kubernetes Service description: Learn how to automate the deployment of an Arm-based Kubernetes cluster on Azure AKS using Terraform and deploy a sample WordPress application as a workload. diff --git a/content/learning-paths/servers-and-cloud-computing/alluxio-cobalt/_index.md b/content/learning-paths/servers-and-cloud-computing/alluxio-cobalt/_index.md index ed8bcfc414..f0a5a36f83 100644 --- a/content/learning-paths/servers-and-cloud-computing/alluxio-cobalt/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/alluxio-cobalt/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy Alluxio on Azure Cobalt 100 Arm64 virtual machines for data orchestration and caching +title: Deploy Alluxio on Azure Cobalt 100 Arm64 VMs description: Learn how to install and configure Alluxio on an Azure Cobalt 100 Arm64 virtual machine, integrate it with Apache Spark, enable data caching, and benchmark performance improvements for analytics workloads. diff --git a/content/learning-paths/servers-and-cloud-computing/apache_arrow_and_flight/_index.md b/content/learning-paths/servers-and-cloud-computing/apache_arrow_and_flight/_index.md index 9c8e6822a6..6479851c78 100644 --- a/content/learning-paths/servers-and-cloud-computing/apache_arrow_and_flight/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/apache_arrow_and_flight/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy High-Performance Analytics with Apache Arrow and Arrow Flight on Google Cloud C4A Axion processors +title: Deploy Apache Arrow and Arrow Flight on Google Axion description: Learn how to deploy Apache Arrow and Arrow Flight on Google Cloud Axion C4A processors for high-throughput columnar data processing and low-latency data transport with MinIO integration. minutes_to_complete: 30 diff --git a/content/learning-paths/servers-and-cloud-computing/apache_arrow_and_flight/firewall-setup.md b/content/learning-paths/servers-and-cloud-computing/apache_arrow_and_flight/firewall-setup.md index 65a0186090..6554be0d62 100644 --- a/content/learning-paths/servers-and-cloud-computing/apache_arrow_and_flight/firewall-setup.md +++ b/content/learning-paths/servers-and-cloud-computing/apache_arrow_and_flight/firewall-setup.md @@ -1,5 +1,5 @@ --- -title: Create firewall rules on GCP for Apache Arrow, MinIO, and Arrow Flight +title: Set up GCP firewall rules for Arrow Flight and MinIO weight: 3 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/servers-and-cloud-computing/arm-soc-migration-learning-path/_index.md b/content/learning-paths/servers-and-cloud-computing/arm-soc-migration-learning-path/_index.md index 1da09ee795..880e2f100a 100644 --- a/content/learning-paths/servers-and-cloud-computing/arm-soc-migration-learning-path/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/arm-soc-migration-learning-path/_index.md @@ -1,5 +1,5 @@ --- -title: Migrate applications between Arm platforms using Kiro Arm SoC Migration Power +title: Migrate apps between Arm platforms with Kiro powers description: Learn how to migrate C applications between Arm platforms using Kiro's AI-assisted tooling to identify hardware dependencies and implement abstraction layers for cross-platform compatibility. minutes_to_complete: 60 diff --git a/content/learning-paths/servers-and-cloud-computing/arm_linux_page_size/_index.md b/content/learning-paths/servers-and-cloud-computing/arm_linux_page_size/_index.md index 8e8f7825ab..fc64ce6f34 100644 --- a/content/learning-paths/servers-and-cloud-computing/arm_linux_page_size/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/arm_linux_page_size/_index.md @@ -1,5 +1,5 @@ --- -title: Explore performance gains by increasing the Linux kernel page size on Arm +title: Explore performance gains from larger Linux pages on Arm description: Learn how to install and configure Linux kernels with 16K or 64K page sizes on Arm systems, then evaluate the memory and performance trade-offs. minutes_to_complete: 30 diff --git a/content/learning-paths/servers-and-cloud-computing/aws-cdk/_index.md b/content/learning-paths/servers-and-cloud-computing/aws-cdk/_index.md index 039b8660b3..b505ff9134 100644 --- a/content/learning-paths/servers-and-cloud-computing/aws-cdk/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/aws-cdk/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy containers on Arm-based compute using Amazon ECS and the AWS CDK +title: Deploy Arm containers to Amazon ECS with the AWS CDK description: Learn how to define and deploy a containerized application on Arm-based compute using the AWS Cloud Development Kit. minutes_to_complete: 45 diff --git a/content/learning-paths/servers-and-cloud-computing/azure-arm-template/_index.md b/content/learning-paths/servers-and-cloud-computing/azure-arm-template/_index.md index 6a23c392ba..c3310f4da7 100644 --- a/content/learning-paths/servers-and-cloud-computing/azure-arm-template/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/azure-arm-template/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy Arm-based Cobalt 100 VMs using Azure Resource Manager templates +title: Deploy Azure Cobalt 100 VMs with Azure Resource Manager templates description: Learn how to create and deploy Azure Resource Manager templates to provision Arm64-based Cobalt 100 virtual machines on Azure using the Azure CLI. minutes_to_complete: 45 diff --git a/content/learning-paths/servers-and-cloud-computing/benchmark-nlp/_index.md b/content/learning-paths/servers-and-cloud-computing/benchmark-nlp/_index.md index bc514b2a9f..5433bdcfcd 100644 --- a/content/learning-paths/servers-and-cloud-computing/benchmark-nlp/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/benchmark-nlp/_index.md @@ -1,5 +1,5 @@ --- -title: Accelerate Natural Language Processing (NLP) models from Hugging Face on Arm servers +title: Accelerate Hugging Face NLP models on Arm servers description: Learn how to deploy and accelerate PyTorch NLP sentiment analysis models from Hugging Face on Arm servers with BFloat16 fast math kernel optimization on Graviton3 processors. minutes_to_complete: 20 diff --git a/content/learning-paths/servers-and-cloud-computing/benchmark-nlp/benchmark-nlp-hf.md b/content/learning-paths/servers-and-cloud-computing/benchmark-nlp/benchmark-nlp-hf.md index 96675c5735..b3f9a4bcdb 100644 --- a/content/learning-paths/servers-and-cloud-computing/benchmark-nlp/benchmark-nlp-hf.md +++ b/content/learning-paths/servers-and-cloud-computing/benchmark-nlp/benchmark-nlp-hf.md @@ -1,5 +1,5 @@ --- -title: Measure and accelerate the performance of Natural Language Processing (NLP) models from Hugging Face on Arm servers +title: Benchmark and accelerate Hugging Face NLP models on Arm weight: 2 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/01-introduction.md b/content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/01-introduction.md index 135202d128..88443efb9b 100644 --- a/content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/01-introduction.md +++ b/content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/01-introduction.md @@ -1,6 +1,6 @@ --- # User change -title: "Optimize bitmap scanning in databases with SVE and Neon on Arm servers" +title: "Optimize database bitmap scanning with SVE and Neon" weight: 2 diff --git a/content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/_index.md b/content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/_index.md index 6d6f1873bd..87e742e3ca 100644 --- a/content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/_index.md @@ -1,5 +1,5 @@ --- -title: Accelerate Bitmap Scanning with Neon and SVE Instructions on Arm servers +title: Accelerate bitmap scanning with Neon and SVE on Arm description: Learn how to implement and benchmark bitmap scanning operations for database workloads using scalar, Neon, and SVE instructions on Arm-based cloud instances. minutes_to_complete: 20 diff --git a/content/learning-paths/servers-and-cloud-computing/buildkite-gcp/_index.md b/content/learning-paths/servers-and-cloud-computing/buildkite-gcp/_index.md index 77b3be6eff..843941a791 100644 --- a/content/learning-paths/servers-and-cloud-computing/buildkite-gcp/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/buildkite-gcp/_index.md @@ -1,5 +1,5 @@ --- -title: Create multi-architecture Docker images with Buildkite on Google Axion +title: Build multi-architecture images with Buildkite on Google Axion description: Learn how to configure Buildkite agents on Google Axion C4A VMs to build and publish multi-architecture Docker images using Docker Buildx for Arm and x86 platforms. minutes_to_complete: 40 diff --git a/content/learning-paths/servers-and-cloud-computing/cca-bootsync/_index.md b/content/learning-paths/servers-and-cloud-computing/cca-bootsync/_index.md index 3670c6dc07..30549296fb 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-bootsync/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-bootsync/_index.md @@ -1,10 +1,10 @@ --- -title: Secure Realms during boot using Arm Confidential Compute Architecture BootSync +title: Secure Realm boot with Arm CCA BootSync description: Use Arm CCA BootSync on an RME-enabled FVP to inject UEFI variables and secrets to unlock the disk during boot, then verify Secure Boot and encrypted root file system startup. minutes_to_complete: 60 -who_is_this_for: This Learning Path is for developers who want to understand how Arm CCA BootSync supports early Realm boot workflows such as UEFI Secure Boot and encrypted disk boot. +who_is_this_for: This Learning Path is for developers who want to understand how Arm Confidential Compute Architecture (CCA) BootSync supports early Realm boot workflows such as UEFI Secure Boot and encrypted disk boot. learning_objectives: - Understand why BootSync is needed before the Realm guest operating system has networking. @@ -14,7 +14,7 @@ learning_objectives: prerequisites: - A cloud-based instance or an AArch64 or x86_64 computer running Linux. For more information about using cloud-based instances, see the [Arm cloud service providers](/learning-paths/servers-and-cloud-computing/csp/) Learning Path. - - Completion of the [Run an application in a Realm using the Arm Confidential Compute Architecture (CCA)](/learning-paths/servers-and-cloud-computing/cca-container/) Learning Path + - Completion of the [Run an application in a Realm using Arm CCA](/learning-paths/servers-and-cloud-computing/cca-container/) Learning Path # START generated_summary_faq generated_summary_faq: diff --git a/content/learning-paths/servers-and-cloud-computing/cca-container/_index.md b/content/learning-paths/servers-and-cloud-computing/cca-container/_index.md index d41b31b17e..5bb148b152 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-container/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-container/_index.md @@ -1,5 +1,5 @@ --- -title: Run an application in a Realm using the Arm Confidential Compute Architecture (CCA) +title: Run an app in a Realm with Arm CCA description: Learn how to run the Arm CCA reference software stack on an FVP with RME support, create a Realm virtual machine, and obtain attestation tokens for confidential computing. minutes_to_complete: 120 diff --git a/content/learning-paths/servers-and-cloud-computing/cca-kata/_index.md b/content/learning-paths/servers-and-cloud-computing/cca-kata/_index.md index d45a332c31..5e0683ce50 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-kata/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-kata/_index.md @@ -1,5 +1,5 @@ --- -title: Run Confidential Containers with encrypted images using Arm CCA and Trustee +title: Run encrypted Confidential Containers with Arm CCA and Trustee description: Learn how to deploy Confidential Containers from encrypted images inside Arm CCA Realms using Trustee services for attestation-based authorization on an FVP with RME support. minutes_to_complete: 60 diff --git a/content/learning-paths/servers-and-cloud-computing/cca-kata/cca-kata.md b/content/learning-paths/servers-and-cloud-computing/cca-kata/cca-kata.md index ec3de4aa38..25b3b691d4 100644 --- a/content/learning-paths/servers-and-cloud-computing/cca-kata/cca-kata.md +++ b/content/learning-paths/servers-and-cloud-computing/cca-kata/cca-kata.md @@ -1,6 +1,6 @@ --- # User change -title: "Overview of Confidential Containers and Arm CCA Attestation with Trustee" +title: "Explore Confidential Containers and Arm CCA attestation" weight: 2 # 1 is first, 2 is second, etc. diff --git a/content/learning-paths/servers-and-cloud-computing/clickhouse-gcp/_index.md b/content/learning-paths/servers-and-cloud-computing/clickhouse-gcp/_index.md index 457910305c..3910067463 100644 --- a/content/learning-paths/servers-and-cloud-computing/clickhouse-gcp/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/clickhouse-gcp/_index.md @@ -1,5 +1,5 @@ --- -title: Build a real-time analytics pipeline with ClickHouse on Google Cloud Axion +title: Build a ClickHouse analytics pipeline on Google Axion description: Learn how to deploy ClickHouse on Google Cloud Axion C4A processors and build a streaming ETL pipeline using Apache Beam, Dataflow, and Pub/Sub for real-time analytics. minutes_to_complete: 50 diff --git a/content/learning-paths/servers-and-cloud-computing/clickhouse-gcp/pub_sub_creation.md b/content/learning-paths/servers-and-cloud-computing/clickhouse-gcp/pub_sub_creation.md index eb4ba49802..9d0c5caac8 100644 --- a/content/learning-paths/servers-and-cloud-computing/clickhouse-gcp/pub_sub_creation.md +++ b/content/learning-paths/servers-and-cloud-computing/clickhouse-gcp/pub_sub_creation.md @@ -1,5 +1,5 @@ --- -title: Set up GCP Pub/Sub and IAM for ClickHouse real-time analytics on Axion +title: Set up Pub/Sub and IAM for ClickHouse on Google Axion weight: 5 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/servers-and-cloud-computing/codebuild/codebuild_run.md b/content/learning-paths/servers-and-cloud-computing/codebuild/codebuild_run.md index 960129eced..710b4dc1cb 100644 --- a/content/learning-paths/servers-and-cloud-computing/codebuild/codebuild_run.md +++ b/content/learning-paths/servers-and-cloud-computing/codebuild/codebuild_run.md @@ -1,6 +1,6 @@ --- # User change -title: "Run Docker images from Docker Hub and AWS Elastic Container Registry (ECR)" +title: "Run Docker Hub and Amazon ECR images with AWS CodeBuild" weight: 3 # 1 is first, 2 is second, etc. diff --git a/content/learning-paths/servers-and-cloud-computing/cpp-profile-guided-optimisation/_index.md b/content/learning-paths/servers-and-cloud-computing/cpp-profile-guided-optimisation/_index.md index fec81b3304..89ca38d927 100644 --- a/content/learning-paths/servers-and-cloud-computing/cpp-profile-guided-optimisation/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/cpp-profile-guided-optimisation/_index.md @@ -1,10 +1,10 @@ --- -title: Optimize C++ performance with Profile-Guided Optimization and Google Benchmark +title: Optimize C++ with PGO and Google Benchmark description: Learn how to apply profile-guided optimization to C++ applications on Arm systems and measure performance improvements using Google Benchmark. minutes_to_complete: 15 -who_is_this_for: Developers looking to optimize C++ performance based on runtime behavior. +who_is_this_for: Developers looking to optimize C++ performance based on runtime behavior with profile-guided optimization (PGO). learning_objectives: - Microbenchmark a function using Google Benchmark. diff --git a/content/learning-paths/servers-and-cloud-computing/deepspeed-on-axion/_index.md b/content/learning-paths/servers-and-cloud-computing/deepspeed-on-axion/_index.md index 831831e9f5..f8b39a4d7f 100644 --- a/content/learning-paths/servers-and-cloud-computing/deepspeed-on-axion/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/deepspeed-on-axion/_index.md @@ -1,5 +1,5 @@ --- -title: Train and benchmark AI workloads with DeepSpeed on Google Cloud C4A Axion VMs +title: Train AI workloads with DeepSpeed on Google Axion description: Set up PyTorch and DeepSpeed on Google Cloud C4A Axion Arm VMs running SUSE Linux to train neural network models, benchmark AI workloads, and validate scalable CPU-based AI execution on Arm64 processors. diff --git a/content/learning-paths/servers-and-cloud-computing/deepspeed-on-axion/train-benchmark-deepspeed-arm.md b/content/learning-paths/servers-and-cloud-computing/deepspeed-on-axion/train-benchmark-deepspeed-arm.md index df473f5ccd..6eddf286ee 100644 --- a/content/learning-paths/servers-and-cloud-computing/deepspeed-on-axion/train-benchmark-deepspeed-arm.md +++ b/content/learning-paths/servers-and-cloud-computing/deepspeed-on-axion/train-benchmark-deepspeed-arm.md @@ -1,5 +1,5 @@ --- -title: Train and benchmark AI workloads on an Arm-based Google Axion virtual machine +title: Train and benchmark DeepSpeed on an Arm Axion VM weight: 5 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/servers-and-cloud-computing/distributed-inference-with-llama-cpp/_index.md b/content/learning-paths/servers-and-cloud-computing/distributed-inference-with-llama-cpp/_index.md index 490406cf0a..65f19e87c9 100644 --- a/content/learning-paths/servers-and-cloud-computing/distributed-inference-with-llama-cpp/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/distributed-inference-with-llama-cpp/_index.md @@ -1,5 +1,5 @@ --- -title: Run distributed inference with llama.cpp on Arm-based AWS Graviton4 instances +title: Run distributed llama.cpp inference on AWS Graviton4 description: Run distributed LLM inference with llama.cpp across multiple AWS Graviton4 instances, covering multi-node setup, coordination, and performance trade-offs. minutes_to_complete: 30 diff --git a/content/learning-paths/servers-and-cloud-computing/docker-mcp-toolkit/2-setup.md b/content/learning-paths/servers-and-cloud-computing/docker-mcp-toolkit/2-setup.md index 3c2a9730d0..d2cf2c7ca1 100644 --- a/content/learning-paths/servers-and-cloud-computing/docker-mcp-toolkit/2-setup.md +++ b/content/learning-paths/servers-and-cloud-computing/docker-mcp-toolkit/2-setup.md @@ -1,5 +1,5 @@ --- -title: Set up Docker MCP Toolkit with Arm, GitHub, and Sequential Thinking servers +title: Set up Docker MCP Toolkit with Arm and GitHub servers weight: 3 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/servers-and-cloud-computing/docker-mcp-toolkit/_index.md b/content/learning-paths/servers-and-cloud-computing/docker-mcp-toolkit/_index.md index 7b017445c1..30162b86b6 100644 --- a/content/learning-paths/servers-and-cloud-computing/docker-mcp-toolkit/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/docker-mcp-toolkit/_index.md @@ -1,5 +1,5 @@ --- -title: Automate x86 to Arm Migration with Docker MCP Toolkit, VS Code and GitHub Copilot +title: Migrate x86 apps to Arm with Docker MCP Toolkit and Copilot description: Learn how to use the Docker MCP Toolkit with the Arm MCP Server and GitHub Copilot to automate container and code migration from x86 to Arm64. Through a hands-on example, migrate a legacy C++ application with AVX2 intrinsics to Arm Neon. diff --git a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md index 772f86751c..c6629968c3 100644 --- a/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/dotnet-migration-nopcommerce/_index.md @@ -1,5 +1,5 @@ --- -title: Migrate and optimize a .NET nopCommerce application on Microsoft Azure +title: Migrate nopCommerce to Arm on Microsoft Azure description: Migrate, containerize, benchmark, and tune a .NET nopCommerce application on Azure Cobalt so you can validate Arm-based performance before rollout. minutes_to_complete: 75 diff --git a/content/learning-paths/servers-and-cloud-computing/dynatrace-azure/_index.md b/content/learning-paths/servers-and-cloud-computing/dynatrace-azure/_index.md index b736e39972..53bd4b51a0 100644 --- a/content/learning-paths/servers-and-cloud-computing/dynatrace-azure/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/dynatrace-azure/_index.md @@ -1,5 +1,5 @@ --- -title: Monitor Azure Cobalt 100 Arm64 virtual machines using Dynatrace OneAgent +title: Monitor Azure Cobalt 100 virtual machines with Dynatrace OneAgent description: Learn how to deploy Dynatrace OneAgent on Azure Cobalt 100 Arm64 virtual machines and configure ActiveGate for secure infrastructure and application monitoring. minutes_to_complete: 30 diff --git a/content/learning-paths/servers-and-cloud-computing/ecs-managed-instances/_index.md b/content/learning-paths/servers-and-cloud-computing/ecs-managed-instances/_index.md index 47759c7c9f..da5828012a 100644 --- a/content/learning-paths/servers-and-cloud-computing/ecs-managed-instances/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/ecs-managed-instances/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy a container on AWS Graviton processors with Amazon ECS Managed Instances +title: Deploy a container on AWS Graviton with ECS Managed Instances description: Deploy an NGINX container with Amazon ECS Managed Instances and verify that your task runs on AWS Graviton-based compute. minutes_to_complete: 60 diff --git a/content/learning-paths/servers-and-cloud-computing/ecs/_index.md b/content/learning-paths/servers-and-cloud-computing/ecs/_index.md index cea42d0dad..4304da1617 100644 --- a/content/learning-paths/servers-and-cloud-computing/ecs/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/ecs/_index.md @@ -1,10 +1,10 @@ --- -title: Deploy containers on Amazon ECS with AWS Fargate on Graviton processors +title: Deploy containers on Graviton with AWS Fargate description: Learn how to create an Amazon ECS cluster with Fargate and AWS Graviton processors, then create and run containerized tasks on Arm infrastructure. minutes_to_complete: 60 -who_is_this_for: This is an introductory topic for developers who want to use AWS Graviton processors with Amazon Elastic Container Service (ECS). +who_is_this_for: This is an introductory topic for developers who want to use AWS Graviton processors with Amazon Elastic Container Service (ECS) and AWS Fargate. learning_objectives: - Create an Amazon ECS cluster with Fargate and AWS Graviton processors diff --git a/content/learning-paths/servers-and-cloud-computing/eks-multi-arch/_index.md b/content/learning-paths/servers-and-cloud-computing/eks-multi-arch/_index.md index 7032fd8797..52a4e54050 100644 --- a/content/learning-paths/servers-and-cloud-computing/eks-multi-arch/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/eks-multi-arch/_index.md @@ -1,5 +1,5 @@ --- -title: Learn how to build and deploy a multi-architecture application on Amazon EKS +title: Build and deploy a multi-architecture app on Amazon EKS description: Learn how to use docker buildx and docker manifest to build and deploy multi-architecture container images with x86/amd64 and arm64 support on Amazon EKS. minutes_to_complete: 60 diff --git a/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/background.md b/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/background.md index 6822ae5c97..5e53a9e1cc 100644 --- a/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/background.md +++ b/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/background.md @@ -1,5 +1,5 @@ --- -title: Understand Azure Cobalt 100 VMs and Elasticsearch benchmarking with ESRally +title: Explore Elasticsearch benchmarking on Azure Cobalt 100 weight: 2 diff --git a/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/benchmarking.md b/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/benchmarking.md index e0803a437d..806757620a 100644 --- a/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/benchmarking.md +++ b/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/benchmarking.md @@ -1,5 +1,5 @@ --- -title: Benchmark Elasticsearch using ESRally on the Cobalt 100 virtual machine instance +title: Benchmark Elasticsearch with ESRally on Azure Cobalt 100 weight: 5 diff --git a/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/create-instance.md b/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/create-instance.md index d03071eabe..ddee9e5308 100644 --- a/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/create-instance.md +++ b/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/create-instance.md @@ -1,5 +1,5 @@ --- -title: Create an Azure Cobalt 100 Arm virtual machine for Elasticsearch deployment +title: Create an Azure Cobalt 100 VM for Elasticsearch weight: 3 ### FIXED, DO NOT MODIFY layout: learningpathall diff --git a/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/prepare-instance.md b/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/prepare-instance.md index dbca516ee5..a790f1e64b 100644 --- a/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/prepare-instance.md +++ b/content/learning-paths/servers-and-cloud-computing/elasticsearch-on-azure/prepare-instance.md @@ -1,5 +1,5 @@ --- -title: Install Elasticsearch and ESRally on the Cobalt 100 virtual machine instance +title: Install Elasticsearch and ESRally on Azure Cobalt 100 weight: 4 diff --git a/content/learning-paths/servers-and-cloud-computing/flyte-with-grpc/_index.md b/content/learning-paths/servers-and-cloud-computing/flyte-with-grpc/_index.md index 6fd15ef02b..54e45a2499 100644 --- a/content/learning-paths/servers-and-cloud-computing/flyte-with-grpc/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/flyte-with-grpc/_index.md @@ -1,5 +1,5 @@ --- -title: Build ML Workflow Pipelines with Flyte and gRPC on Google Cloud C4A Axion processors +title: Build Flyte and gRPC ML workflows on Google Axion description: Learn how to build scalable machine learning workflow pipelines on Google Cloud C4A Axion processors using Flyte for workflow orchestration and gRPC for distributed service communication. minutes_to_complete: 30 diff --git a/content/learning-paths/servers-and-cloud-computing/from-iot-to-the-cloud-part1/_index.md b/content/learning-paths/servers-and-cloud-computing/from-iot-to-the-cloud-part1/_index.md index b484153ad5..4be7a2d00c 100644 --- a/content/learning-paths/servers-and-cloud-computing/from-iot-to-the-cloud-part1/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/from-iot-to-the-cloud-part1/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy .NET applications to Arm virtual machines and Azure Container Registry +title: Deploy .NET apps to Arm VMs and Azure Container Registry description: Learn how to create an Arm64 Azure VM, install .NET SDK, containerize .NET applications, and push Docker images to Azure Container Registry. minutes_to_complete: 30 diff --git a/content/learning-paths/servers-and-cloud-computing/from-iot-to-the-cloud-part3/_index.md b/content/learning-paths/servers-and-cloud-computing/from-iot-to-the-cloud-part3/_index.md index 2f9465361e..1428defd02 100644 --- a/content/learning-paths/servers-and-cloud-computing/from-iot-to-the-cloud-part3/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/from-iot-to-the-cloud-part3/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy an application to Azure Kubernetes Service on Arm-based virtual machines +title: Deploy an app to Azure Kubernetes Service on Arm VMs description: Learn how to create an Azure Kubernetes Service cluster with Arm64 virtual machines and deploy a containerized application to AKS. minutes_to_complete: 45 diff --git a/content/learning-paths/servers-and-cloud-computing/github-actions-runner/_index.md b/content/learning-paths/servers-and-cloud-computing/github-actions-runner/_index.md index 2b178fc71e..913e2d6440 100644 --- a/content/learning-paths/servers-and-cloud-computing/github-actions-runner/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/github-actions-runner/_index.md @@ -1,5 +1,5 @@ --- -title: Use RunsOn to deploy managed, self-hosted Arm runners for GitHub Actions +title: Deploy Arm GitHub Actions runners with RunsOn description: Learn how to install RunsOn self-hosted runner manager in your AWS account to execute GitHub Actions workflows on Arm runners. minutes_to_complete: 15 diff --git a/content/learning-paths/servers-and-cloud-computing/github-on-arm/_index.md b/content/learning-paths/servers-and-cloud-computing/github-on-arm/_index.md index d5d2084a31..464a87ecdf 100644 --- a/content/learning-paths/servers-and-cloud-computing/github-on-arm/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/github-on-arm/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy a GitHub Actions self-hosted runner on a Google Axion C4A virtual machine +title: Deploy a GitHub Actions runner on a Google Axion VM description: Learn how to provision a Google Axion C4A Arm virtual machine and set up a GitHub Actions self-hosted runner for CI/CD workflows. minutes_to_complete: 15 diff --git a/content/learning-paths/servers-and-cloud-computing/gke-multi-arch-axion/_index.md b/content/learning-paths/servers-and-cloud-computing/gke-multi-arch-axion/_index.md index f4586112dd..5d208446de 100644 --- a/content/learning-paths/servers-and-cloud-computing/gke-multi-arch-axion/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/gke-multi-arch-axion/_index.md @@ -1,5 +1,5 @@ --- -title: Migrate x86 workloads to Arm on Google Kubernetes Engine with Axion processors +title: Migrate x86 workloads to Arm on GKE with Google Axion minutes_to_complete: 90 description: Learn how to create dual-architecture GKE clusters with arm64 and amd64 node pools, build multi-architecture Docker images, and migrate services to Google Axion processors. diff --git a/content/learning-paths/servers-and-cloud-computing/gke-multi-arch/_index.md b/content/learning-paths/servers-and-cloud-computing/gke-multi-arch/_index.md index 2672398f8d..1bf1d083a4 100644 --- a/content/learning-paths/servers-and-cloud-computing/gke-multi-arch/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/gke-multi-arch/_index.md @@ -1,5 +1,5 @@ --- -title: Learn how to migrate an x86 application to multi-architecture with Arm-based on Google Axion Processor on GKE +title: Migrate an x86 app to multi-architecture on GKE with Axion description: Learn how to add Arm-based Google Axion nodes to an existing x86 GKE cluster and rebuild applications for multi-architecture support. minutes_to_complete: 30 diff --git a/content/learning-paths/servers-and-cloud-computing/go-gc-default-settings/_index.md b/content/learning-paths/servers-and-cloud-computing/go-gc-default-settings/_index.md index b69a4a25dc..7a5c78cde4 100644 --- a/content/learning-paths/servers-and-cloud-computing/go-gc-default-settings/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/go-gc-default-settings/_index.md @@ -1,5 +1,5 @@ --- -title: Measure and modify Go garbage collection behavior on AWS Graviton-based compute +title: Tune Go garbage collection on AWS Graviton description: Learn how to run Go benchmarks on AWS Graviton-based compute, capture GC metrics and pprof profiles with Benchstat, establish a reproducible default garbage collection baseline for memory-intensive workloads on Arm, and experiment with modifying garbage collection behavior. diff --git a/content/learning-paths/servers-and-cloud-computing/go-gc-default-settings/choose_aws_instance.md b/content/learning-paths/servers-and-cloud-computing/go-gc-default-settings/choose_aws_instance.md index d05b5bf6c8..eca5e4d075 100644 --- a/content/learning-paths/servers-and-cloud-computing/go-gc-default-settings/choose_aws_instance.md +++ b/content/learning-paths/servers-and-cloud-computing/go-gc-default-settings/choose_aws_instance.md @@ -1,5 +1,5 @@ --- -title: Choose an AWS Graviton-based instance for Go garbage collection benchmarking +title: Choose an AWS Graviton instance for Go GC benchmarking description: Choose and launch an AWS Graviton-based Amazon EC2 instance for repeatable Go garbage collection benchmark measurements. weight: 2 diff --git a/content/learning-paths/servers-and-cloud-computing/java-on-azure/create-instance.md b/content/learning-paths/servers-and-cloud-computing/java-on-azure/create-instance.md index 7e5e1bdd03..9517f1651d 100644 --- a/content/learning-paths/servers-and-cloud-computing/java-on-azure/create-instance.md +++ b/content/learning-paths/servers-and-cloud-computing/java-on-azure/create-instance.md @@ -1,5 +1,5 @@ --- -title: Create an Arm-based cloud virtual machine using Microsoft Cobalt 100 CPU +title: Create an Azure Cobalt 100 Arm VM weight: 3 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/servers-and-cloud-computing/kafka-azure/_index.md b/content/learning-paths/servers-and-cloud-computing/kafka-azure/_index.md index b219cf2313..a57c255c12 100644 --- a/content/learning-paths/servers-and-cloud-computing/kafka-azure/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/kafka-azure/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy Apache Kafka on Arm-based Microsoft Azure Cobalt 100 virtual machines +title: Deploy Kafka on Azure Cobalt 100 Arm64 VMs minutes_to_complete: 30 diff --git a/content/learning-paths/servers-and-cloud-computing/kafka-azure/create-instance.md b/content/learning-paths/servers-and-cloud-computing/kafka-azure/create-instance.md index 987f943a7d..d08b0ccfb5 100644 --- a/content/learning-paths/servers-and-cloud-computing/kafka-azure/create-instance.md +++ b/content/learning-paths/servers-and-cloud-computing/kafka-azure/create-instance.md @@ -1,5 +1,5 @@ --- -title: Create an Arm-based cloud virtual machine using Microsoft Cobalt 100 CPU +title: Create an Azure Cobalt 100 Arm VM for Kafka weight: 3 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/servers-and-cloud-computing/keycloak-cobalt/_index.md b/content/learning-paths/servers-and-cloud-computing/keycloak-cobalt/_index.md index a70a64ec0c..cd906892b3 100644 --- a/content/learning-paths/servers-and-cloud-computing/keycloak-cobalt/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/keycloak-cobalt/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy Keycloak on Azure Cobalt 100-based Arm64 virtual machines for identity and access management +title: Deploy Keycloak on Azure Cobalt 100 Arm64 VMs description: Learn how to install and configure Keycloak on an Azure Cobalt 100 Arm64 virtual machine, integrate it with PostgreSQL, configure OAuth2/OpenID Connect authentication, and secure applications using centralized identity management. diff --git a/content/learning-paths/servers-and-cloud-computing/keycloak-cobalt/background.md b/content/learning-paths/servers-and-cloud-computing/keycloak-cobalt/background.md index 70c4af9cf2..1a15e190d7 100644 --- a/content/learning-paths/servers-and-cloud-computing/keycloak-cobalt/background.md +++ b/content/learning-paths/servers-and-cloud-computing/keycloak-cobalt/background.md @@ -1,5 +1,5 @@ --- -title: Understand Keycloak for identity and access management on Azure Cobalt 100-based virtual machines +title: Explore Keycloak identity management on Azure Cobalt 100 description: Learn how Keycloak provides IAM, OAuth2/OpenID Connect, and single sign-on for applications running on Arm-based Azure infrastructure. weight: 2 diff --git a/content/learning-paths/servers-and-cloud-computing/keycloak-cobalt/keycloak_flask_oauth_usecase.md b/content/learning-paths/servers-and-cloud-computing/keycloak-cobalt/keycloak_flask_oauth_usecase.md index 6827532650..1ee175e640 100644 --- a/content/learning-paths/servers-and-cloud-computing/keycloak-cobalt/keycloak_flask_oauth_usecase.md +++ b/content/learning-paths/servers-and-cloud-computing/keycloak-cobalt/keycloak_flask_oauth_usecase.md @@ -1,5 +1,5 @@ --- -title: Integrate a Flask OAuth2 application with Keycloak on an Azure Cobalt 100-based Arm64 virtual machine +title: Integrate Flask OAuth2 with Keycloak on Azure Cobalt 100 description: Learn how to configure a Keycloak realm, create an OpenID Connect client, build a Flask OAuth2 demo application, and validate user login. weight: 6 diff --git a/content/learning-paths/servers-and-cloud-computing/llama-cpu/_index.md b/content/learning-paths/servers-and-cloud-computing/llama-cpu/_index.md index b8a75e499b..ff8850892f 100644 --- a/content/learning-paths/servers-and-cloud-computing/llama-cpu/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/llama-cpu/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy a Large Language Model (LLM) chatbot with llama.cpp using KleidiAI on Arm servers +title: Deploy a llama.cpp chatbot with KleidiAI on Arm servers description: Serve the llama.cpp chatbot through an OpenAI-compatible API, enabling existing OpenAI-style clients and applications to run against a persistent Arm-hosted LLM. minutes_to_complete: 30 diff --git a/content/learning-paths/servers-and-cloud-computing/llama-vision/_index.md b/content/learning-paths/servers-and-cloud-computing/llama-vision/_index.md index 7d494c1215..dca09ad645 100644 --- a/content/learning-paths/servers-and-cloud-computing/llama-vision/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/llama-vision/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy a LLM-based vision chatbot with PyTorch and Hugging Face transformers on Google Axion processors +title: Deploy a PyTorch vision chatbot on Google Axion minutes_to_complete: 45 diff --git a/content/learning-paths/servers-and-cloud-computing/llama_cpp_streamline/5_operator_deepdive.md b/content/learning-paths/servers-and-cloud-computing/llama_cpp_streamline/5_operator_deepdive.md index 1096e451cb..6648a8536b 100644 --- a/content/learning-paths/servers-and-cloud-computing/llama_cpp_streamline/5_operator_deepdive.md +++ b/content/learning-paths/servers-and-cloud-computing/llama_cpp_streamline/5_operator_deepdive.md @@ -1,5 +1,5 @@ --- -title: Implement operator-level performance analysis with channel annotations +title: Analyze operators with channel annotations weight: 6 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/servers-and-cloud-computing/llama_cpp_streamline/_index.md b/content/learning-paths/servers-and-cloud-computing/llama_cpp_streamline/_index.md index 78fa4db303..00151e0806 100644 --- a/content/learning-paths/servers-and-cloud-computing/llama_cpp_streamline/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/llama_cpp_streamline/_index.md @@ -1,5 +1,5 @@ --- -title: Profile llama.cpp performance with Arm Streamline and KleidiAI LLM kernels +title: Profile llama.cpp with Arm Streamline and KleidiAI minutes_to_complete: 60 diff --git a/content/learning-paths/servers-and-cloud-computing/llamaindex-rag-axion/_index.md b/content/learning-paths/servers-and-cloud-computing/llamaindex-rag-axion/_index.md index 04717d529a..2100b5d7ed 100644 --- a/content/learning-paths/servers-and-cloud-computing/llamaindex-rag-axion/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/llamaindex-rag-axion/_index.md @@ -1,5 +1,5 @@ --- -title: Build RAG applications with LlamaIndex on a Google Cloud C4A virtual machine +title: Build LlamaIndex RAG apps on Google Axion C4A description: Set up LlamaIndex on Google Axion-based C4A Arm64 VMs running SUSE Linux to build browser-based Retrieval-Augmented Generation (RAG) applications using local LLMs, vector databases, and FastAPI. diff --git a/content/learning-paths/servers-and-cloud-computing/llamaindex-rag-axion/setup-llamaindex-rag.md b/content/learning-paths/servers-and-cloud-computing/llamaindex-rag-axion/setup-llamaindex-rag.md index d2ae296ac1..04f8afbb55 100644 --- a/content/learning-paths/servers-and-cloud-computing/llamaindex-rag-axion/setup-llamaindex-rag.md +++ b/content/learning-paths/servers-and-cloud-computing/llamaindex-rag-axion/setup-llamaindex-rag.md @@ -1,5 +1,5 @@ --- -title: Install and configure LlamaIndex on a Google Cloud C4A virtual machine +title: Install LlamaIndex on a Google Axion C4A VM description: Learn how to install Python, Ollama, LlamaIndex, ChromaDB, and FastAPI on an Arm-based Google Cloud C4A virtual machine for a browser-based RAG application. weight: 5 diff --git a/content/learning-paths/servers-and-cloud-computing/longhorn-cobalt/_index.md b/content/learning-paths/servers-and-cloud-computing/longhorn-cobalt/_index.md index b4fda1a73f..bc45ebed7e 100644 --- a/content/learning-paths/servers-and-cloud-computing/longhorn-cobalt/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/longhorn-cobalt/_index.md @@ -1,5 +1,5 @@ --- -title: Use Longhorn to deploy persistent storage for Kubernetes workloads on Arm-based Azure virtual machines +title: Deploy Longhorn storage for Kubernetes on Azure Arm VMs description: Learn how to install and configure Longhorn on an Arm64 Azure virtual machine powered by Azure Cobalt 100, deploy Kubernetes persistent storage using Longhorn on K3s, create persistent volumes, and benchmark storage performance for cloud-native workloads. diff --git a/content/learning-paths/servers-and-cloud-computing/longhorn-cobalt/longhorn-storage-validation-and-benchmark.md b/content/learning-paths/servers-and-cloud-computing/longhorn-cobalt/longhorn-storage-validation-and-benchmark.md index 0130a8103d..2c47f1d219 100644 --- a/content/learning-paths/servers-and-cloud-computing/longhorn-cobalt/longhorn-storage-validation-and-benchmark.md +++ b/content/learning-paths/servers-and-cloud-computing/longhorn-cobalt/longhorn-storage-validation-and-benchmark.md @@ -1,5 +1,5 @@ --- -title: Validate persistent Kubernetes storage and benchmark Longhorn with fio +title: Validate and benchmark Longhorn storage with fio description: Create a Longhorn-backed PersistentVolumeClaim, verify data persistence after pod recreation, and run a fio benchmark on an Arm64 Azure VM powered by Azure Cobalt 100. weight: 6 diff --git a/content/learning-paths/servers-and-cloud-computing/memcached_cache/_index.md b/content/learning-paths/servers-and-cloud-computing/memcached_cache/_index.md index 3ab8b2386c..baece86a90 100644 --- a/content/learning-paths/servers-and-cloud-computing/memcached_cache/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/memcached_cache/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy Memcached as a cache for MySQL and PostgreSQL on Arm-based servers +title: Cache MySQL and PostgreSQL with Memcached on Arm description: Deploy Memcached as a cache for MySQL and PostgreSQL on Arm servers diff --git a/content/learning-paths/servers-and-cloud-computing/memcached_cache/memcached_mysql_gcp.md b/content/learning-paths/servers-and-cloud-computing/memcached_cache/memcached_mysql_gcp.md index 61fbb4f701..0da860ce15 100644 --- a/content/learning-paths/servers-and-cloud-computing/memcached_cache/memcached_mysql_gcp.md +++ b/content/learning-paths/servers-and-cloud-computing/memcached_cache/memcached_mysql_gcp.md @@ -1,6 +1,6 @@ --- # User change -title: "Deploy Memcached as a cache for MySQL on a Google Cloud Arm based Instance" +title: "Deploy Memcached for MySQL on a Google Cloud Arm VM" weight: 4 # 1 is first, 2 is second, etc. diff --git a/content/learning-paths/servers-and-cloud-computing/memcached_cache/memcached_psql_azure.md b/content/learning-paths/servers-and-cloud-computing/memcached_cache/memcached_psql_azure.md index 94e5ccb711..b5a3905c02 100644 --- a/content/learning-paths/servers-and-cloud-computing/memcached_cache/memcached_psql_azure.md +++ b/content/learning-paths/servers-and-cloud-computing/memcached_cache/memcached_psql_azure.md @@ -1,6 +1,6 @@ --- # User change -title: "Deploy Memcached as a cache for Postgres on an Azure Arm based Instance" +title: "Deploy Memcached for PostgreSQL on an Azure Arm VM" weight: 6 # 1 is first, 2 is second, etc. diff --git a/content/learning-paths/servers-and-cloud-computing/memcached_cache/memcached_psql_gcp.md b/content/learning-paths/servers-and-cloud-computing/memcached_cache/memcached_psql_gcp.md index af6ba5d180..dffe5c1a62 100644 --- a/content/learning-paths/servers-and-cloud-computing/memcached_cache/memcached_psql_gcp.md +++ b/content/learning-paths/servers-and-cloud-computing/memcached_cache/memcached_psql_gcp.md @@ -1,6 +1,6 @@ --- # User change -title: "Deploy Memcached as a cache for Postgres on a Google Cloud Arm based Instance" +title: "Deploy Memcached for PostgreSQL on a Google Cloud Arm VM" weight: 7 # 1 is first, 2 is second, etc. diff --git a/content/learning-paths/servers-and-cloud-computing/microbenchmark-network-iperf3/_index.md b/content/learning-paths/servers-and-cloud-computing/microbenchmark-network-iperf3/_index.md index 12c750d24e..e8ff60fb16 100644 --- a/content/learning-paths/servers-and-cloud-computing/microbenchmark-network-iperf3/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/microbenchmark-network-iperf3/_index.md @@ -1,5 +1,5 @@ --- -title: Microbenchmark and tune network performance with iPerf3 and Linux traffic control +title: Tune network performance with iPerf3 on Arm Linux minutes_to_complete: 30 diff --git a/content/learning-paths/servers-and-cloud-computing/microbenchmark-network-iperf3/setup.md b/content/learning-paths/servers-and-cloud-computing/microbenchmark-network-iperf3/setup.md index 3495acf973..e845ae5f66 100644 --- a/content/learning-paths/servers-and-cloud-computing/microbenchmark-network-iperf3/setup.md +++ b/content/learning-paths/servers-and-cloud-computing/microbenchmark-network-iperf3/setup.md @@ -1,5 +1,5 @@ --- -title: Set up Arm-based Linux systems for network performance testing with iPerf3 +title: Set up Arm Linux for network testing with iPerf3 weight: 2 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/servers-and-cloud-computing/minecraft-on-oci/_index.md b/content/learning-paths/servers-and-cloud-computing/minecraft-on-oci/_index.md index bc13cb0561..6597f32629 100644 --- a/content/learning-paths/servers-and-cloud-computing/minecraft-on-oci/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/minecraft-on-oci/_index.md @@ -1,5 +1,5 @@ --- -title: Run a Minecraft server on an Arm-based Oracle Cloud Infrastructure instance +title: Run Minecraft on an Arm-based Oracle Cloud Infrastructure instance description: Deploy a Minecraft Java Edition server on an Arm-based OCI A1 instance, open port `25565`, and connect from the Minecraft client. diff --git a/content/learning-paths/servers-and-cloud-computing/minecraft-on-oci/oci-instance-start.md b/content/learning-paths/servers-and-cloud-computing/minecraft-on-oci/oci-instance-start.md index 3f55385d51..faaf262827 100644 --- a/content/learning-paths/servers-and-cloud-computing/minecraft-on-oci/oci-instance-start.md +++ b/content/learning-paths/servers-and-cloud-computing/minecraft-on-oci/oci-instance-start.md @@ -1,5 +1,5 @@ --- -title: Create and configure an Arm-based virtual machine instance on Oracle Cloud Infrastructure +title: Create an Arm VM on Oracle Cloud Infrastructure description: Create an Arm-based OCI A1 virtual machine, assign a public IPv4 address, open TCP port 25565 in the security list, and connect over SSH. weight: 2 diff --git a/content/learning-paths/servers-and-cloud-computing/mlflow-axion/_index.md b/content/learning-paths/servers-and-cloud-computing/mlflow-axion/_index.md index d2c37fe9a4..bdf9acbc3d 100644 --- a/content/learning-paths/servers-and-cloud-computing/mlflow-axion/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/mlflow-axion/_index.md @@ -1,5 +1,5 @@ --- -title: Manage the machine learning lifecycle with MLflow on a Google Cloud C4A Axion virtual machine +title: Manage ML workflows with MLflow on Google Axion C4A description: Set up MLflow on Google Cloud C4A Axion Arm VMs running SUSE Linux to track ML experiments, version models with the Model Registry, and deploy a trained model as a REST API. diff --git a/content/learning-paths/servers-and-cloud-computing/mongodb/automate_setup_pulumi.md b/content/learning-paths/servers-and-cloud-computing/mongodb/automate_setup_pulumi.md index 3a5962bbb6..dc4a91bfb8 100644 --- a/content/learning-paths/servers-and-cloud-computing/mongodb/automate_setup_pulumi.md +++ b/content/learning-paths/servers-and-cloud-computing/mongodb/automate_setup_pulumi.md @@ -1,6 +1,6 @@ --- # User change -title: "Automate MongoDB Performance Benchmarking Infrastructure Setup with Pulumi" +title: "Automate MongoDB benchmark setup with Pulumi" weight: 8 # (intro is 1), 2 is first, 3 is second, etc. diff --git a/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/_index.md b/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/_index.md index d3c63269c6..aa17dad65e 100644 --- a/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/_index.md @@ -1,5 +1,5 @@ --- -title: Build a multi-architecture Kubernetes cluster running nginx on Azure AKS +title: Build a multi-architecture NGINX cluster on Azure Kubernetes Service minutes_to_complete: 60 diff --git a/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/deploy-arm.md b/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/deploy-arm.md index d48f546e1c..485964cb94 100644 --- a/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/deploy-arm.md +++ b/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/deploy-arm.md @@ -1,5 +1,5 @@ --- -title: Deploy nginx on Arm +title: Deploy NGINX on Arm weight: 6 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/deploy-intel.md b/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/deploy-intel.md index e9ec0b3594..77816fc39a 100644 --- a/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/deploy-intel.md +++ b/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/deploy-intel.md @@ -1,5 +1,5 @@ --- -title: Deploy nginx on Intel x86 +title: Deploy NGINX on Intel x86 weight: 5 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/deploy-multiarch.md b/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/deploy-multiarch.md index c77218bad8..98fc4418ce 100644 --- a/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/deploy-multiarch.md +++ b/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/deploy-multiarch.md @@ -1,5 +1,5 @@ --- -title: Deploy nginx multiarch service on AKS +title: Deploy an NGINX multi-architecture service on AKS weight: 8 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/overview.md b/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/overview.md index 8a660b11b2..03f4bb1214 100644 --- a/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/overview.md +++ b/content/learning-paths/servers-and-cloud-computing/multiarch_nginx_on_aks/overview.md @@ -1,5 +1,5 @@ --- -title: Start your journey with Arm and x86 nginx workloads on a single Kubernetes cluster +title: Explore Arm and x86 NGINX workloads on a Kubernetes cluster weight: 2 ### FIXED, DO NOT MODIFY layout: learningpathall diff --git a/content/learning-paths/servers-and-cloud-computing/multiarch_ollama_on_gke/_index.md b/content/learning-paths/servers-and-cloud-computing/multiarch_ollama_on_gke/_index.md index a1e16b5bd1..bd582a0641 100644 --- a/content/learning-paths/servers-and-cloud-computing/multiarch_ollama_on_gke/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/multiarch_ollama_on_gke/_index.md @@ -1,5 +1,5 @@ --- -title: Add Arm nodes to your GKE cluster using a multi-architecture Ollama container image +title: Add Arm nodes to GKE with a multi-architecture Ollama image minutes_to_complete: 30 diff --git a/content/learning-paths/servers-and-cloud-computing/mysql-lns-azure/_index.md b/content/learning-paths/servers-and-cloud-computing/mysql-lns-azure/_index.md index 07d9d51c58..3703e4366e 100644 --- a/content/learning-paths/servers-and-cloud-computing/mysql-lns-azure/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/mysql-lns-azure/_index.md @@ -1,5 +1,5 @@ --- -title: Migrate MySQL from on-premises x64 to Arm-based Azure virtual machines +title: Migrate MySQL from on-premises x64 to Arm on Azure virtual machines description: Learn how to migrate a MySQL database from an on-premises x64 environment to an Arm-based Azure Cobalt 100 VM and validate performance with sysbench. diff --git a/content/learning-paths/servers-and-cloud-computing/nginx/build_from_source.md b/content/learning-paths/servers-and-cloud-computing/nginx/build_from_source.md index 360bbaa229..14d8def10c 100644 --- a/content/learning-paths/servers-and-cloud-computing/nginx/build_from_source.md +++ b/content/learning-paths/servers-and-cloud-computing/nginx/build_from_source.md @@ -1,5 +1,5 @@ --- -title: "Build Nginx from source" +title: "Build NGINX from source" weight: 3 layout: "learningpathall" --- diff --git a/content/learning-paths/servers-and-cloud-computing/nginx/install_from_package.md b/content/learning-paths/servers-and-cloud-computing/nginx/install_from_package.md index 0c94540840..d6455d719c 100644 --- a/content/learning-paths/servers-and-cloud-computing/nginx/install_from_package.md +++ b/content/learning-paths/servers-and-cloud-computing/nginx/install_from_package.md @@ -1,5 +1,5 @@ --- -title: "Install Nginx using a package manager and check the build configuration" +title: "Install NGINX and inspect its build configuration" weight: 2 layout: "learningpathall" --- diff --git a/content/learning-paths/servers-and-cloud-computing/nlp-hugging-face/_index.md b/content/learning-paths/servers-and-cloud-computing/nlp-hugging-face/_index.md index 2ed9338ae2..1f3f610b2a 100644 --- a/content/learning-paths/servers-and-cloud-computing/nlp-hugging-face/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/nlp-hugging-face/_index.md @@ -1,5 +1,5 @@ --- -title: Run a Natural Language Processing (NLP) model from Hugging Face on Arm servers +title: Run a Hugging Face Natural Language Processing model on Arm minutes_to_complete: 20 diff --git a/content/learning-paths/servers-and-cloud-computing/nlp-hugging-face/pytorch-nlp-hf.md b/content/learning-paths/servers-and-cloud-computing/nlp-hugging-face/pytorch-nlp-hf.md index 5640d0c701..d896eace62 100644 --- a/content/learning-paths/servers-and-cloud-computing/nlp-hugging-face/pytorch-nlp-hf.md +++ b/content/learning-paths/servers-and-cloud-computing/nlp-hugging-face/pytorch-nlp-hf.md @@ -1,5 +1,5 @@ --- -title: Run a Natural Language Processing (NLP) model from Hugging Face on Arm servers +title: Run a Hugging Face NLP model on Arm servers weight: 2 ### FIXED, DO NOT MODIFY diff --git a/content/learning-paths/servers-and-cloud-computing/onnx-on-azure/_index.md b/content/learning-paths/servers-and-cloud-computing/onnx-on-azure/_index.md index 2c6c7e0aca..42c39c2b19 100644 --- a/content/learning-paths/servers-and-cloud-computing/onnx-on-azure/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/onnx-on-azure/_index.md @@ -1,5 +1,5 @@ --- -title: Deploy SqueezeNet 1.0 INT8 model with ONNX Runtime on Azure Cobalt 100 +title: Deploy INT8 SqueezeNet with ONNX Runtime on Azure Cobalt 100 minutes_to_complete: 60 diff --git a/content/learning-paths/servers-and-cloud-computing/opencv-on-axion/_index.md b/content/learning-paths/servers-and-cloud-computing/opencv-on-axion/_index.md index c76f20853a..74e0239b0f 100644 --- a/content/learning-paths/servers-and-cloud-computing/opencv-on-axion/_index.md +++ b/content/learning-paths/servers-and-cloud-computing/opencv-on-axion/_index.md @@ -1,5 +1,5 @@ --- -title: Build computer vision pipelines with OpenCV on a Google Cloud C4A Axion VM +title: Build computer vision pipelines with OpenCV on Google Axion description: Deploy and run OpenCV-based computer vision pipelines on Google Cloud Axion C4A Arm-based VMs, covering image processing, video pipelines, browser-based visualization, and integration with machine learning models. minutes_to_complete: 45 diff --git a/content/learning-paths/servers-and-cloud-computing/opencv-on-axion/opencv-foundation-pipelines.md b/content/learning-paths/servers-and-cloud-computing/opencv-on-axion/opencv-foundation-pipelines.md index 8a27951aac..cb6ae248ad 100644 --- a/content/learning-paths/servers-and-cloud-computing/opencv-on-axion/opencv-foundation-pipelines.md +++ b/content/learning-paths/servers-and-cloud-computing/opencv-on-axion/opencv-foundation-pipelines.md @@ -283,7 +283,7 @@ cat > index.html <<'EOF'

Live Video Feed

- +Latest frame from the live video pipeline +{{ end }} diff --git a/themes/arm-design-system-hugo-theme/layouts/_default/baseof.html b/themes/arm-design-system-hugo-theme/layouts/_default/baseof.html index b993d3b630..fbc9eb7cab 100644 --- a/themes/arm-design-system-hugo-theme/layouts/_default/baseof.html +++ b/themes/arm-design-system-hugo-theme/layouts/_default/baseof.html @@ -9,6 +9,7 @@ {{partial "head/analytics.html" .}} {{partial "head/conditional-redirect.html" .}} {{partial "head/hotjar-surveys.html" .}} + {{block "page-head" .}}{{end}} {{ if .Param "math" }} {{ partialCached "math.html" . }} diff --git a/themes/arm-design-system-hugo-theme/layouts/partials/footer/script-includes.html b/themes/arm-design-system-hugo-theme/layouts/partials/footer/script-includes.html index aaa627eba8..dd265291c9 100644 --- a/themes/arm-design-system-hugo-theme/layouts/partials/footer/script-includes.html +++ b/themes/arm-design-system-hugo-theme/layouts/partials/footer/script-includes.html @@ -48,8 +48,7 @@ {{ if eq (getenv "HUGO_CHATBOT_ENABLED") "true" }} -{{ $chatbotComponentURL := .Site.Params.chatbot_component_url | default "https://content.ipexplorer.arm.com/VirtualFAE/learnarm/chat-ai.js" }} - + {{end}} diff --git a/themes/arm-design-system-hugo-theme/static/js/authentication.js b/themes/arm-design-system-hugo-theme/static/js/authentication.js index e4deb33e7c..ef30989e14 100644 --- a/themes/arm-design-system-hugo-theme/static/js/authentication.js +++ b/themes/arm-design-system-hugo-theme/static/js/authentication.js @@ -89,6 +89,15 @@ function getEmailClaimValue(claims) { return undefined; } +function isExpectedAccount(account) { + const claims = account?.idTokenClaims; + const policy = claims?.tfp ?? claims?.acr; + + return claims?.aud === CLIENT_ID && + typeof policy === "string" && + policy.toLowerCase() === POLICY.toLowerCase(); +} + async function getIdTokenClaimsForAccount(account) { if (!account) return null; @@ -109,9 +118,7 @@ async function getIdTokenClaimsForAccount(account) { } async function updateDigitalDataForCurrentUser() { - const account = - msalInstance.getActiveAccount() || - msalInstance.getAllAccounts()[0]; + const account = getAccount(); if (!account) { clearDigitalDataUser(); @@ -143,7 +150,7 @@ async function initAuth() { const result = await msalInstance.handleRedirectPromise(); // safe to call every load - if (result?.account) { + if (isExpectedAccount(result?.account)) { msalInstance.setActiveAccount(result.account); } else { getAccount(); @@ -164,12 +171,17 @@ async function initAuth() { // Helper function function getAccount() { - const accounts = msalInstance.getAllAccounts(); - if (accounts.length) { - // Optional: pick a deterministic account if multiple - msalInstance.setActiveAccount(accounts[0]); - return accounts[0]; + const activeAccount = msalInstance.getActiveAccount(); + if (isExpectedAccount(activeAccount)) return activeAccount; + + const matchingAccounts = msalInstance.getAllAccounts().filter(isExpectedAccount); + if (matchingAccounts.length === 1) { + msalInstance.setActiveAccount(matchingAccounts[0]); + return matchingAccounts[0]; } + + // A missing or ambiguous account requires an explicit sign-in choice. + msalInstance.setActiveAccount(null); return null; } @@ -179,9 +191,7 @@ function getAccount() { function getSignedInNavData() { if (!msalInstance) return null; - const account = - msalInstance.getActiveAccount() || - msalInstance.getAllAccounts()[0]; + const account = getAccount(); if (!account) return null; diff --git a/tools/search-lab/README.md b/tools/search-lab/README.md new file mode 100644 index 0000000000..60649292bd --- /dev/null +++ b/tools/search-lab/README.md @@ -0,0 +1,39 @@ +# Search lab + +`/search/` searches a static JSON index in a browser worker. Queries are stored in the page URL but are not sent to a search service. The page requests `noindex,nofollow` and is excluded from the sitemap; it remains publicly accessible. + +## Shared layout changes + +The `page-head` block in the theme's `baseof.html` lets the search layout load its scoped stylesheet. The `browser_search` front-matter flag tells `footer/script-includes.html` to skip legacy list-filter scripts on this page. Existing search entry points are unchanged. + +## Index and ranking + +`layouts/search-lab/list.html` generates the index and fingerprinted assets on each build. It includes Learning Path landing pages and install-guide entry points, with multi-part guides represented by their entry point. Draft, future, and expired content is excluded, including content with hidden ancestors, even with `--buildDrafts`. + +`assets/search-lab/engine.mjs` uses weighted BM25 keyword scoring: title > description/authors > taxonomy/objectives/extra search terms/prerequisites > summary/FAQ text. Full tutorial steps and guide body text are not indexed. FAQ text comes from `generated_summary_faq.faqs`. Identifiers such as `llama.cpp` retain punctuation. + +Relevance is the default for the page and agent tool. Results matching every significant query word exactly come first, followed by complete matches using word expansion, then partial matches. Common words such as “and” are ignored. Within each group, results sort by score regardless of content type. Scores express ranking, not confidence. + +Alphabetic prefixes of at least three letters match token beginnings (`grav` matches `Graviton`). If a word has no exact token or prefix completion, words of at least five letters allow one insertion, deletion, substitution, or adjacent letter swap (`dokcer` matches `Docker`). Typo matching applies only to alphabetic tokens of at least five letters; identifiers with punctuation or digits are not corrected. Expanded matches receive lower weights. Language aliases are normalized in both indexed text and queries: `cpp` and `C++` are equivalent, as are `csharp` and `C#`. Other identifiers such as `llama.cpp` remain intact. There is no arbitrary substring matching, stemming, general synonym expansion, or embedding search. + +The initial blank page shows no results, including when filters are restored from its URL. Submitting a blank or whitespace-only query with Search or Return lists all content matching the filters, newest first (including when Relevance is selected). Typing keeps live search enabled; clearing the field waits for another explicit submission before listing all content. Selecting Install guides clears and disables the Learning Path category filter, including when restoring filters from a URL. The search engine also ignores category filters for install-guide searches. + +After an empty search, a small clear control inside the field appears on hover or focus and returns to the initial empty view while preserving filters. For nonempty input, the browser provides its native clear control. + +Show more loads batches of 10 with no overall result cap. The agent tool accepts an `offset` to skip results, with up to 100 results per call. + +Newest-first remains available and sorts by date instead of match completeness: the latest page date across a Learning Path, or the entry-page date for an install guide. Missing dates sort last; ties use score. Sorting and content filters apply before pagination. Existing URLs with `sort=updated` retain newest-first sorting. + +`assets/search-lab/worker.js` loads and prepares the index once per page load. The WebMCP adapter and tester in `assets/search-lab/app.js` invoke the same callback without changing the human's current search. The tester works without native WebMCP support; it does not prove an agent connection. See the [CLI WebMCP demo](WEBMCP_DEMO.md) for native tool invocation. + +## Verify + +Run from the repository root: + +```sh +node --test tools/search-lab/engine.test.mjs tools/search-lab/app.test.mjs +hugo --destination /private/tmp/arm-search-lab-build +node tools/search-lab/check-index.mjs /private/tmp/arm-search-lab-build +``` + +The index checker expects unminified HTML. To check draft exclusion, also build with `--buildDrafts` into a separate directory and compare index record IDs. Before rollout, check mobile widths, light/dark themes, keyboard navigation, slow loading, and browsers with and without native WebMCP support. diff --git a/tools/search-lab/WEBMCP_DEMO.md b/tools/search-lab/WEBMCP_DEMO.md new file mode 100644 index 0000000000..822c5ac33c --- /dev/null +++ b/tools/search-lab/WEBMCP_DEMO.md @@ -0,0 +1,55 @@ +# Call search through the Chrome DevTools CLI + +The CLI bridges an agent to the page's native WebMCP tool: + +```text +Agent → Chrome DevTools CLI → Chrome → search_arm_content → browser search worker +``` + +Requires Chrome, Node.js, and npm. No WebMCP Inspector extension or Gemini API key is needed. + +## Start the browser + +Start the site from the repository root: + +```sh +hugo server --bind 127.0.0.1 --port 1313 +``` + +In another terminal, define the CLI shortcut and launch an isolated browser. The first invocation may download the pinned npm package. + +```sh +devtools() { + npx --yes --package=chrome-devtools-mcp@1.12.0 chrome-devtools "$@" +} + +devtools start --isolated --headless \ + --categoryExperimentalWebmcp \ + --chromeArg=--enable-features=WebMCP \ + --no-usage-statistics --no-performance-crux + +devtools new_page http://localhost:1313/search/ +``` + +## Discover and call the tool + +Replace `PAGE_ID` with the ID returned by `new_page`: + +```sh +devtools list_webmcp_tools PAGE_ID + +devtools execute_webmcp_tool PAGE_ID search_arm_content \ + --input '{"query":"Jason Andrews","type":"learning-path","sort":"updated","limit":5}' +``` + +Discovery lists the tool and its input schema. Execution returns matching content with source URLs and metadata. The example searches author names and indexed text; it is not an exact author-only filter. + +If discovery returns no tools, check the URL, allow the page to initialize, and confirm both experimental options were supplied to `devtools start`. These options apply to the isolated browser session and leave your everyday Chrome profile unchanged. Other agents need their own WebMCP bridge. + +Stop the browser when finished: + +```sh +devtools stop +``` + +References: [Chrome WebMCP automation and agents](https://developer.chrome.com/docs/devtools/application/webmcp#automation-and-agents), [Chrome DevTools CLI documentation](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/docs/cli.md). diff --git a/tools/search-lab/app.test.mjs b/tools/search-lab/app.test.mjs new file mode 100644 index 0000000000..6693325d13 --- /dev/null +++ b/tools/search-lab/app.test.mjs @@ -0,0 +1,220 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import vm from 'node:vm'; +import { createEngine } from '../../assets/search-lab/engine.mjs'; + +const source = fs.readFileSync(new URL('../../assets/search-lab/app.js', import.meta.url), 'utf8'); +const flush = () => new Promise(resolve => setImmediate(resolve)); + +// Run the actual app with a small DOM and controllable worker to exercise failure paths. +async function startApp({ startupFailure = false, initFailure = false, total = 472, query = '', filters = '', records } = {}) { + const elements = new Map(); + const element = id => { + if (!elements.has(id)) elements.set(id, { + value: id === 'sort' ? 'relevance' : '', textContent: '', hidden: false, children: [], + focus() { this.focused = true; }, + append(...children) { this.children.push(...children); }, + replaceChildren(...children) { this.children = children; } + }); + return elements.get(id); + }; + const search = records && createEngine(records); + let worker, tool; + const requests = [], urls = []; + const context = vm.createContext({ + document: { + getElementById: element, createElement: () => element(Symbol()), + querySelector: () => ({ dataset: { worker: '/worker.js', index: '/index.json' }, querySelectorAll: () => [] }), + modelContext: { registerTool(value) { tool = value; } } + }, + navigator: {}, + Worker: class { + constructor() { + if (startupFailure) throw Error('Worker blocked'); + worker = this; + } + terminate() { this.terminated = true; } + postMessage(message) { + requests.push(message); + if (this.throwOnPost) throw Error('Cannot post message'); + if (this.pause) return; + queueMicrotask(() => { + if (message.action === 'init' && initFailure) return this.onerror(); + const result = message.action === 'init' + ? { categories: ['cross-platform'], count: total, bytes: 1, loadMs: 1 } + : { total, searchMs: 1, results: Array.from({ length: Math.max(0, Math.min(total - (message.input.offset || 0), message.input.limit || 10)) }, (_, i) => ({ + title: `Result ${i + (message.input.offset || 0)}`, url: `/result/${i + (message.input.offset || 0)}/`, type: 'learning-path' + })) }; + this.onmessage({ data: { id: message.id, result: search && message.action === 'search' ? { ...search(message.input), searchMs: 1 } : result } }); + }); + } + }, + URLSearchParams, history: { replaceState(_state, _title, url) { urls.push(url); } }, + location: { search: `?q=${encodeURIComponent(query)}&${filters}`, pathname: '/search/', hash: '' }, + performance, setTimeout, clearTimeout, queueMicrotask + }); + await vm.runInContext(`(async () => {\n${source}\n})()`, context); + await flush(); + return { element, worker, tool, requests, urls }; +} + +test('worker startup failures display an error and tool calls settle', { timeout: 1000 }, async () => { + for (const options of [{ startupFailure: true }, { initFailure: true }]) { + const { element, tool } = await startApp(options); + assert.match(element('status').textContent, /Reload/); + assert.equal((await tool.execute({ query: 'gcc' })).isError, true); + element('search-form').onsubmit({ preventDefault() {} }); + await flush(); + assert.match(element('status').textContent, /Reload/); + } +}); + +test('worker errors reject outstanding and future searches', { timeout: 1000 }, async () => { + for (const event of ['onerror', 'onmessageerror']) { + const { element, worker, tool } = await startApp(); + worker.pause = true; + const outstanding = tool.execute({ query: 'gcc' }); + await flush(); + worker[event](); + assert.equal((await outstanding).isError, true); + assert.equal((await tool.execute({ query: 'docker' })).isError, true); + assert.equal(worker.terminated, true); + assert.equal(element('more').hidden, true); + assert.match(element('status').textContent, /Reload/); + } +}); + +test('postMessage failures settle current and future searches', { timeout: 1000 }, async () => { + const { worker, tool } = await startApp(); + worker.throwOnPost = true; + assert.equal((await tool.execute({ query: 'gcc' })).isError, true); + assert.equal((await tool.execute({ query: 'gcc' })).isError, true); +}); + +test('Show more reaches every result beyond 100 without duplicates and filters reset pagination', async () => { + const { element } = await startApp({ total: 123 }); + element('search-form').onsubmit({ preventDefault() {} }); + await flush(); + assert.match(element('status').textContent, /123 results.*Showing 10 of 123/); + for (let i = 0; i < 12; i++) { + assert.equal(element('more').hidden, false); + element('more').onclick(); + await flush(); + } + assert.equal(element('results').children.length, 123); + const titles = element('results').children.map(li => li.children[1].children[0].textContent); + assert.equal(new Set(titles).size, 123); + assert.match(element('status').textContent, /Showing 123 of 123/); + assert.equal(element('more').hidden, true); + element('type').value = 'install-guide'; + element('type').onchange(); + await flush(); + assert.equal(element('results').children.length, 10); +}); + +test('blank searches browse all content and respect type, category, and restored URL filters', async () => { + const records = [ + { id: '/guide/', title: 'Docker guide', type: 'install-guide', category: '' }, + { id: '/path/', title: 'Docker path', type: 'learning-path', category: 'cross-platform' }, + { id: '/other/', title: 'Other path', type: 'learning-path', category: 'servers-and-cloud-computing' } + ]; + const { element } = await startApp({ records }); + element('search-form').onsubmit({ preventDefault() {} }); + await flush(); + assert.match(element('status').textContent, /3 results.*newest first/); + for (const [type, category, total] of [['install-guide', '', 1], ['learning-path', '', 2], ['', 'cross-platform', 1]]) { + element('type').value = type; + element('category').value = category; + element('type').onchange(); + await flush(); + assert.equal(element('results').children.length, total); + } + element('query').value = 'unfindable'; + element('search-form').onsubmit({ preventDefault() {} }); + await flush(); + assert.equal(element('results').children.length, 0); + element('query').value = ' '; + element('search-form').onsubmit({ preventDefault() {} }); + await flush(); + assert.equal(element('results').children.length, 1); + const restored = await startApp({ records, filters: 'category=cross-platform' }); + assert.equal(restored.element('results').children.length, 0); + restored.element('search-form').onsubmit({ preventDefault() {} }); + await flush(); + assert.equal(restored.element('results').children.length, 1); +}); + +test('install guides clear and disable categories on selection and URL restoration', async () => { + for (const filters of ['category=cross-platform', 'type=install-guide&category=cross-platform']) { + const { element, requests, urls } = await startApp({ query: 'Docker', filters }); + element('type').value = 'install-guide'; + element('type').onchange(); + await flush(); + assert.equal(element('category').disabled, true); + assert.equal(element('category').value, ''); + assert.equal(requests.at(-1).input.category, ''); + assert.equal(new URLSearchParams(urls.at(-1).split('?')[1]).has('category'), false); + if (filters.startsWith('type=')) assert.equal(requests.find(r => r.action === 'search').input.category, ''); + element('type').value = 'learning-path'; + element('type').onchange(); + await flush(); + assert.equal(element('category').disabled, false); + element('category').value = 'cross-platform'; + element('category').onchange(); + await flush(); + assert.equal(requests.at(-1).input.category, 'cross-platform'); + } +}); + +test('initial blank page and filter changes wait for submission; clearing live search waits again', async () => { + const { element, requests } = await startApp(); + assert.equal(element('results').children.length, 0); + assert.equal(element('more').hidden, true); + assert.equal(requests.filter(r => r.action === 'search').length, 0); + element('type').value = 'install-guide'; + element('type').onchange(); + await flush(); + assert.equal(requests.filter(r => r.action === 'search').length, 0); + element('search-form').onsubmit({ preventDefault() {} }); + await flush(); + assert.equal(element('results').children.length, 10); + assert.equal(requests.at(-1).input.query, ''); + assert.equal(requests.at(-1).input.type, 'install-guide'); + element('query').value = 'Docker'; + element('query').oninput(); + await new Promise(resolve => setTimeout(resolve, 150)); + assert.equal(requests.at(-1).input.query, 'Docker'); + const count = requests.length; + element('query').value = ''; + element('query').oninput(); + await new Promise(resolve => setTimeout(resolve, 150)); + assert.equal(element('results').children.length, 0); + assert.equal(requests.length, count); +}); + +test('inline clear is available only for an active empty search and returns to no results', async () => { + const { element, requests, worker } = await startApp({ filters: 'type=install-guide' }); + assert.equal(element('clear-blank-search').hidden, true); + element('search-form').onsubmit({ preventDefault() {} }); + await flush(); + assert.equal(element('clear-blank-search').hidden, false); + assert.equal(element('results').children.length, 10); + worker.pause = true; + element('more').onclick(); + await flush(); + const pendingId = requests.at(-1).id; + element('clear-blank-search').onclick(); + worker.onmessage({ data: { id: pendingId, result: { total: 1, searchMs: 1, results: [{ title: 'Stale result', url: '/stale/' }] } } }); + await flush(); + assert.equal(element('results').children.length, 0); + assert.equal(element('more').hidden, true); + assert.equal(element('clear-blank-search').hidden, true); + assert.equal(element('query').focused, true); + assert.equal(element('type').value, 'install-guide'); + worker.pause = false; + element('query').value = 'Docker'; + element('search-form').onsubmit({ preventDefault() {} }); + await flush(); + assert.equal(element('clear-blank-search').hidden, true); +}); diff --git a/tools/search-lab/check-index.mjs b/tools/search-lab/check-index.mjs new file mode 100644 index 0000000000..2c796d42c2 --- /dev/null +++ b/tools/search-lab/check-index.mjs @@ -0,0 +1,43 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import assert from 'node:assert/strict'; +import { gzipSync } from 'node:zlib'; +import { createEngine } from '../../assets/search-lab/engine.mjs'; +const root = process.argv[2]; +if (!root) throw Error('Pass the Hugo output directory.'); +const html = fs.readFileSync(path.join(root, 'search/index.html'), 'utf8'); +const assetPath = url => path.join(root, decodeURI(new URL(url, 'https://preview.invalid').pathname)); +const indexURL = html.match(/data-index="([^"]+)"/)[1]; +const workerURL = html.match(/data-worker="([^"]+)"/)[1]; +const bytes = fs.readFileSync(assetPath(indexURL)); +const { records } = JSON.parse(bytes); +assert(records.length > 0); +assert.equal(new Set(records.map(r => r.id)).size, records.length, 'Duplicate entries'); +for (const record of records) { + assert(record.title && record.description !== undefined); + assert.equal(typeof record.prerequisites, 'string'); + assert.equal(typeof record.faq, 'string'); + assert(record.id.startsWith('/learning-paths/') || record.id.startsWith('/install-guides/')); + assert(fs.existsSync(path.join(root, decodeURI(record.id), 'index.html')), `Missing page: ${record.id}`); +} +for (const url of [indexURL, workerURL, ...[...html.matchAll(/(?:src|href)="([^"]*\/search-lab\/[^"]+\.(?:js|css))"/g)].map(match => match[1])]) { + assert(fs.existsSync(assetPath(url)), `Missing asset: ${url}`); + assert(/\.[a-f0-9]{64}\./.test(url), `Asset lacks content fingerprint: ${url}`); +} +const worker = fs.readFileSync(assetPath(workerURL), 'utf8'); +const engineURL = worker.match(/import .* from '([^']+)'/)[1]; +assert(/\.[a-f0-9]{64}\.mjs$/.test(engineURL)); +assert(fs.existsSync(assetPath(engineURL)), 'Missing fingerprinted worker dependency'); +const search = createEngine(records); +const performix = records.find(r => r.id === '/learning-paths/servers-and-cloud-computing/performix-get-started/'); +assert(performix?.prerequisites.includes('Performance Monitor Unit (PMU)'), 'Missing Learning Path prerequisites'); +assert(performix?.faq.includes('Which path should I use for my binary'), 'Missing FAQ question'); +assert(performix?.faq.includes('performix-analysis/dot_scalar'), 'Missing FAQ answer'); +assert(records.find(r => r.id === '/install-guides/arduino-pico/')?.prerequisites.includes('Arduino IDE'), 'Missing install guide prerequisites'); +console.log(JSON.stringify({ documents: records.length, withPrerequisites: records.filter(r => r.prerequisites).length, withFaq: records.filter(r => r.faq).length, bytes: bytes.length, gzipBytes: gzipSync(bytes).length })); +for (const query of ['gcc', 'docker', 'llama.cpp Graviton', 'SVE2', 'Kubernetes', 'Jason Andrews']) { + const start = performance.now(); + const result = search({ query, limit: 3 }); + assert(result.total > 0, `No results: ${query}`); + console.log(JSON.stringify({ query, ms: +(performance.now() - start).toFixed(2), top: result.results.map(r => r.title) })); +} diff --git a/tools/search-lab/engine.test.mjs b/tools/search-lab/engine.test.mjs new file mode 100644 index 0000000000..34e698b389 --- /dev/null +++ b/tools/search-lab/engine.test.mjs @@ -0,0 +1,170 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { createEngine, tokenize } from '../../assets/search-lab/engine.mjs'; +const records = [ + { id: '/gcc/', title: 'GNU Compiler', authors: ['Jason Andrews'], description: 'Install GCC on Linux', type: 'install-guide', category: '' }, + { id: '/llama/', title: 'Deploy llama.cpp', authors: ['Pareena Verma', 'Jason Andrews'], description: 'Deploy a chatbot on AWS Graviton', type: 'learning-path', category: 'servers-and-cloud-computing' }, + { id: '/other/', title: 'Python chatbot', summary: 'Example mentions llama.cpp', type: 'learning-path', category: 'cross-platform' } +]; +const search = createEngine(records); +test('technical punctuation survives tokenization', () => { + assert.deepEqual(tokenize('llama.cpp SVE2 C++'), ['llama.cpp', 'sve2', 'c++']); +}); +test('prerequisites and FAQ questions and answers can independently find content', () => { + const expandedSearch = createEngine([ + { id: '/path/', title: 'Application tutorial', type: 'learning-path', prerequisites: 'SSH access with PMU counters', faq: 'Which binary path should I use? Use performix-analysis/dot_scalar.' }, + { id: '/guide/', title: 'Setup guide', type: 'install-guide', prerequisites: 'Arduino IDE' }, + { id: '/empty/', title: 'Another tutorial', type: 'learning-path' } + ]); + for (const query of ['PMU', 'binary path', 'dot_scalar']) { + assert.equal(expandedSearch({ query }).results[0].id, '/path/'); + } + assert.equal(expandedSearch({ query: 'Arduino' }).results[0].id, '/guide/'); + assert.equal(expandedSearch({ query: 'Arduino', type: 'learning-path' }).total, 0); +}); +test('exact title and query coverage win', () => { + assert.equal(search({ query: 'How do I deploy llama.cpp on Graviton?' }).results[0].id, '/llama/'); +}); +test('filters apply before limiting and total is independent of limit', () => { + assert.equal(search({ query: 'chatbot', limit: 1 }).total, 2); + assert.equal(search({ query: 'chatbot', category: 'cross-platform' }).results[0].id, '/other/'); + assert.equal(search({ query: 'GCC', type: 'learning-path' }).total, 0); +}); +test('reject invalid tool arguments', () => { + for (const input of [null, [], {}, { query: 'gcc', offset: -1 }, { query: 'gcc', offset: 1.5 }, { query: 'gcc', limit: 101 }, { query: 'gcc', limit: 1.5 }, { query: 'gcc', category: 'fake' }, { query: 'gcc', type: 'fake' }]) assert.throws(() => search(input)); +}); +test('stop-word-only or irrelevant queries produce no matches', () => { + assert.equal(search({ query: 'the and' }).total, 0); + assert.equal(search({ query: 'unfindable' }).total, 0); +}); + +test('author names are searchable alongside topic terms and returned in results', () => { + assert.equal(search({ query: 'Jason Andrews' }).total, 2); + assert.equal(search({ query: 'pArEeNa VeRmA' }).results[0].id, '/llama/'); + assert.equal(search({ query: 'Jason GCC' }).results[0].id, '/gcc/'); + assert.deepEqual(search({ query: 'Pareena' }).results[0].authors, ['Pareena Verma', 'Jason Andrews']); +}); + +test('newest sorting applies to all matches before limiting, with relevance as an alternative', () => { + const datedSearch = createEngine([ + { id: '/old/', title: 'Docker Docker Docker', lastUpdated: '2025-01-01', type: 'install-guide' }, + { id: '/new/', title: 'Containers', description: 'Docker', lastUpdated: '2026-10-29', type: 'install-guide' }, + { id: '/missing/', title: 'Docker', type: 'learning-path' } + ]); + assert.deepEqual(datedSearch({ query: 'docker', sort: 'updated' }).results.map(r => r.id), ['/new/', '/old/', '/missing/']); + assert.equal(datedSearch({ query: 'docker', sort: 'updated', limit: 1 }).results[0].id, '/new/'); + assert.equal(datedSearch({ query: 'docker', limit: 1 }).results[0].id, '/old/'); + assert.equal(datedSearch({ query: 'docker', sort: 'relevance' }).results[0].id, '/old/'); + assert.throws(() => datedSearch({ query: 'docker', sort: 'invalid' })); +}); + +test('relevance ranks all content by score, with content type available as a filter', () => { + const prioritize = createEngine([ + { id: '/path/', title: 'Docker', type: 'learning-path', lastUpdated: '2026-10-29' }, + { id: '/weak-guide/', title: 'Containers', summary: 'Docker', type: 'install-guide', lastUpdated: '2025-01-01' }, + { id: '/strong-guide/', title: 'Docker', type: 'install-guide', lastUpdated: '2025-01-01' }, + { id: '/weak-path/', title: 'Containers', summary: 'Docker', type: 'learning-path', lastUpdated: '2026-10-28' }, + { id: '/unmatched/', title: 'GCC', type: 'install-guide' } + ]); + assert.deepEqual(prioritize({ query: 'docker', sort: 'relevance' }).results.map(r => r.id), + ['/path/', '/strong-guide/', '/weak-guide/', '/weak-path/']); + assert.equal(prioritize({ query: 'docker', sort: 'relevance', limit: 1 }).results[0].id, '/path/'); + assert.equal(prioritize({ query: 'docker', sort: 'updated' }).results[0].id, '/path/'); + assert.deepEqual(prioritize({ query: 'docker', sort: 'relevance', type: 'learning-path' }).results.map(r => r.id), ['/path/', '/weak-path/']); + assert.deepEqual(prioritize({ query: 'docker', sort: 'relevance', type: 'install-guide' }).results.map(r => r.id), ['/strong-guide/', '/weak-guide/']); +}); + +test('a title and author match outranks install guides matching only the topic', () => { + const search = createEngine([ + { id: '/llvm/', title: 'LLVM toolchain', summary: 'BOLT', type: 'install-guide' }, + { id: '/bolt/', title: 'Optimize applications with BOLT', authors: ['Gayathri'], type: 'learning-path' } + ]); + assert.equal(search({ query: 'bolt Gayathri', sort: 'relevance', limit: 1 }).results[0].id, '/bolt/'); +}); + +test('all significant words rank before stronger partial matches, across fields', () => { + const search = createEngine([ + { id: '/partial/', title: 'BOLT BOLT BOLT', description: 'BOLT', lastUpdated: '2026-10-01' }, + { id: '/complete/', title: 'Other topic', faq: 'BOLT', authors: ['Gayathri'], lastUpdated: '2025-01-01' } + ]); + const input = { query: 'BOLT and Gayathri' }; + assert.equal(search(input).results[0].id, '/complete/'); + assert.equal(search({ ...input, limit: 1 }).results[0].id, '/complete/'); + assert.equal(search({ ...input, sort: 'updated' }).results[0].id, '/partial/'); +}); + +test('prefix completions rank below exact matches and cover every query word', () => { + const search = createEngine([ + { id: '/prefix/', title: 'Graviton Docker' }, + { id: '/exact/', summary: 'grav dock' }, + { id: '/partial/', title: 'Graviton' } + ]); + assert.deepEqual(search({ query: 'grav dock' }).results.map(r => r.id), ['/exact/', '/prefix/', '/partial/']); + assert.equal(search({ query: 'viton' }).total, 0, 'No arbitrary substring matches'); + assert.equal(search({ query: 'gr' }).total, 0, 'Prefixes need at least three letters'); +}); + +test('typo fallback supports one edit including adjacent transposition', () => { + const search = createEngine([{ id: '/docker/', title: 'Docker', authors: ['Gayathri'] }]); + for (const query of ['dokcer', 'doker', 'dockerr', 'dockar', 'dokcer Gayathri']) { + assert.equal(search({ query }).results[0]?.id, '/docker/', query); + } + assert.equal(search({ query: 'dakkar' }).total, 0, 'Two edits do not match'); + assert.equal(search({ query: 'dockar', type: 'install-guide' }).total, 0, 'Filters still apply'); +}); + +test('typos do not expand valid tokens, short words, or technical identifiers', () => { + const search = createEngine([ + { id: '/valid/', title: 'dockar' }, + { id: '/other/', title: 'Docker GCC SVE2 llama.cpp' } + ]); + assert.deepEqual(search({ query: 'dockar' }).results.map(r => r.id), ['/valid/']); + for (const query of ['gdc', 'SVE3', 'llama.cop']) assert.equal(search({ query }).total, 0, query); +}); + + +test('language aliases match in both queries and indexed fields without changing identifiers', () => { + const search = createEngine([ + { id: '/cpp-tag/', title: 'Qt application', metadata: ['CPP'] }, + { id: '/cpp-title/', title: 'C++ application' }, + { id: '/sharp-tag/', title: 'Application', metadata: ['csharp'] }, + { id: '/sharp-title/', title: 'C# application' }, + { id: '/identifier/', title: 'llama.cpp' }, + { id: '/c/', title: 'C application' } + ]); + for (const [alias, language] of [['CPP', 'C++'], ['csharp', 'C#']]) { + assert.deepEqual(search({ query: alias }), search({ query: language })); + assert.equal(search({ query: language }).total, 2); + } + assert.deepEqual(tokenize('C C# C++ cpp csharp llama.cpp'), ['c', 'c#', 'c++', 'c++', 'c#', 'llama.cpp']); + assert.equal(search({ query: 'llama.cpp' }).total, 1); +}); + +test('install guides ignore Learning Path categories while other searches keep them', () => { + assert.deepEqual(search({ query: 'GCC', type: 'install-guide', category: 'cross-platform' }), + search({ query: 'GCC', type: 'install-guide' })); + assert.equal(search({ query: 'GCC', category: 'cross-platform' }).total, 0); +}); + +test('blank browsing filters before pagination and sorts newest first with missing dates last', () => { + const records = [ + { id: '/old/', title: 'Old', type: 'learning-path', category: 'cross-platform', lastUpdated: '2025-01-01' }, + { id: '/new/', title: 'New', type: 'install-guide', category: '', lastUpdated: '2026-01-01' }, + { id: '/missing/', title: 'Missing date', type: 'learning-path', category: 'cross-platform' } + ]; + const search = createEngine(records); + for (const query of ['', ' ']) { + assert.deepEqual(search({ query }).results.map(r => r.id), ['/new/', '/old/', '/missing/']); + assert.equal(search({ query, offset: 1, limit: 1 }).results[0].id, '/old/'); + assert.equal(search({ query, offset: 10 }).total, 3); + assert.equal(search({ query, offset: 10 }).results.length, 0); + assert.equal(search({ query, type: 'install-guide', category: 'cross-platform' }).total, 1); + assert.equal(search({ query, type: 'learning-path' }).total, 2); + assert.equal(search({ query, category: 'cross-platform', offset: 1, limit: 1 }).results[0].id, '/missing/'); + } +}); + +test('offset pagination preserves keyword rankings', () => { + const all = search({ query: 'chatbot' }); + assert.deepEqual(search({ query: 'chatbot', offset: 1, limit: 1 }).results, all.results.slice(1, 2)); +});