187 lines
5.0 KiB
JavaScript
187 lines
5.0 KiB
JavaScript
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
|
|
import {
|
|
NVIDIA_LIBS_PATH,
|
|
PYTORCH_PATH,
|
|
PYTORCH_TORCH_PATH,
|
|
PYTORCH_NVIDIA_PATH,
|
|
PYTORCH_VERSION,
|
|
PYTORCH_MANIFEST_PATH
|
|
} from '@/constants'
|
|
import { FileHelper } from '@/helpers/file-helper'
|
|
import { SystemHelper } from '@/helpers/system-helper'
|
|
|
|
import { createSetupStatus } from './setup-status'
|
|
|
|
const { type: OS_TYPE, cpuArchitecture: CPU_ARCH } =
|
|
SystemHelper.getInformation()
|
|
const PYTORCH_SETUP_TEXT = 'Setting up PyTorch...'
|
|
|
|
async function ensureDirectoryLink(linkPath, targetPath) {
|
|
if (!fs.existsSync(targetPath)) {
|
|
return
|
|
}
|
|
|
|
await fs.promises.rm(linkPath, { recursive: true, force: true })
|
|
await fs.promises.mkdir(path.dirname(linkPath), { recursive: true })
|
|
|
|
const relativeTarget = path.relative(path.dirname(linkPath), targetPath)
|
|
const linkType = SystemHelper.isWindows() ? 'junction' : 'dir'
|
|
|
|
await fs.promises.symlink(relativeTarget, linkPath, linkType)
|
|
}
|
|
|
|
/**
|
|
* Map OS and architecture to PyTorch wheel platform identifiers
|
|
*/
|
|
function getPyTorchPlatform() {
|
|
const isMacOS = SystemHelper.isMacOS()
|
|
const isWindows = SystemHelper.isWindows()
|
|
const isLinux = SystemHelper.isLinux()
|
|
|
|
if (isLinux) {
|
|
if (CPU_ARCH === 'x64' || CPU_ARCH === 'x86_64') {
|
|
return 'linux-x86_64'
|
|
} else if (CPU_ARCH === 'arm64' || CPU_ARCH === 'aarch64') {
|
|
return 'linux-aarch64'
|
|
}
|
|
} else if (isWindows) {
|
|
return 'windows-x86_64'
|
|
} else if (isMacOS) {
|
|
if (CPU_ARCH === 'arm64') {
|
|
return 'macos-arm64'
|
|
} else {
|
|
return 'macos-x86_64'
|
|
}
|
|
}
|
|
|
|
throw new Error(`Unsupported platform: ${OS_TYPE} ${CPU_ARCH}`)
|
|
}
|
|
|
|
/**
|
|
* Get PyTorch wheel download URL based on platform
|
|
*/
|
|
function getPyTorchDownloadURL(version) {
|
|
const platform = getPyTorchPlatform()
|
|
|
|
const urls = {
|
|
'linux-x86_64': `https://download.pytorch.org/whl/cu129/torch-${version}%2Bcu129-cp311-cp311-manylinux_2_28_x86_64.whl`,
|
|
'linux-aarch64': `https://download.pytorch.org/whl/cu129/torch-${version}%2Bcu129-cp311-cp311-manylinux_2_28_aarch64.whl`,
|
|
'windows-x86_64': `https://download.pytorch.org/whl/cu129/torch-${version}%2Bcu129-cp311-cp311-win_amd64.whl`,
|
|
'macos-arm64': `https://download.pytorch.org/whl/cpu/torch-${version}-cp311-none-macosx_11_0_arm64.whl`,
|
|
// Use 2.2.0 as it is the latest available pre-built package for Python 3.11
|
|
'macos-x86_64': 'https://download.pytorch.org/whl/cpu/torch-2.2.0-cp311-none-macosx_10_9_x86_64.whl'
|
|
}
|
|
|
|
const url = urls[platform]
|
|
if (!url) {
|
|
throw new Error(`No PyTorch wheel available for platform: ${platform}`)
|
|
}
|
|
|
|
return url
|
|
}
|
|
|
|
/**
|
|
* Read manifest file to get installed version
|
|
*/
|
|
function readManifest(manifestPath) {
|
|
if (!fs.existsSync(manifestPath)) {
|
|
return null
|
|
}
|
|
|
|
try {
|
|
const content = fs.readFileSync(manifestPath, 'utf-8')
|
|
|
|
return JSON.parse(content)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Install PyTorch wheel if needed
|
|
*/
|
|
async function installPyTorch(requiredVersion, targetPath, manifestPath) {
|
|
const manifest = readManifest(manifestPath)
|
|
|
|
if (!manifest || manifest.version !== requiredVersion) {
|
|
const wheelPath = path.join(PYTORCH_PATH, `torch-${requiredVersion}.whl`)
|
|
|
|
// Clean up old version
|
|
await fs.promises.rm(targetPath, { recursive: true, force: true })
|
|
await fs.promises.rm(wheelPath, { force: true })
|
|
|
|
// Create target directory
|
|
await fs.promises.mkdir(targetPath, { recursive: true })
|
|
|
|
try {
|
|
const downloadURL = getPyTorchDownloadURL(requiredVersion)
|
|
|
|
await FileHelper.downloadFile(downloadURL, wheelPath, {
|
|
cliProgress: true,
|
|
parallelStreams: 3,
|
|
skipExisting: false
|
|
})
|
|
|
|
// Extract wheel (wheels are just ZIP files)
|
|
await FileHelper.extractArchive(wheelPath, targetPath, {
|
|
stripComponents: 0
|
|
})
|
|
|
|
// Clean up and create manifest
|
|
await Promise.all([
|
|
fs.promises.rm(wheelPath, { force: true }),
|
|
FileHelper.createManifestFile(manifestPath, 'torch', requiredVersion, {
|
|
os: SystemHelper.getInformation().type,
|
|
architecture: SystemHelper.getInformation().cpuArchitecture
|
|
})
|
|
])
|
|
|
|
if (!SystemHelper.isMacOS()) {
|
|
await ensureDirectoryLink(PYTORCH_NVIDIA_PATH, NVIDIA_LIBS_PATH)
|
|
}
|
|
} catch (error) {
|
|
throw new Error(
|
|
`PyTorch may require manual download from https://pytorch.org/get-started/locally/: ${error}`
|
|
)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Main setup function
|
|
*/
|
|
async function setupPyTorch() {
|
|
const status = createSetupStatus(PYTORCH_SETUP_TEXT).start()
|
|
|
|
try {
|
|
const installed = await installPyTorch(
|
|
PYTORCH_VERSION,
|
|
PYTORCH_TORCH_PATH,
|
|
PYTORCH_MANIFEST_PATH
|
|
)
|
|
|
|
if (!SystemHelper.isMacOS()) {
|
|
await ensureDirectoryLink(PYTORCH_NVIDIA_PATH, NVIDIA_LIBS_PATH)
|
|
}
|
|
|
|
if (installed) {
|
|
status.succeed(`PyTorch: ${PYTORCH_VERSION}`)
|
|
} else {
|
|
status.succeed(`PyTorch: ${PYTORCH_VERSION}`)
|
|
}
|
|
} catch (error) {
|
|
if (status.isSpinning) {
|
|
status.fail('Failed to set up PyTorch')
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export default setupPyTorch
|