|
1 | | -import { createWriteStream, existsSync, rmSync } from 'fs'; |
2 | | -import { CommandRunner, SubCommand, InquirerService } from 'nest-commander'; |
3 | | -import { resolve, delimiter, join } from 'path'; |
4 | | -import { HttpService } from '@nestjs/axios'; |
5 | | -import { Presets, SingleBar } from 'cli-progress'; |
6 | | -import decompress from 'decompress'; |
7 | | -import { exit } from 'node:process'; |
8 | 1 |
|
9 | | -interface InitOptions { |
10 | | - runMode?: 'CPU' | 'GPU'; |
11 | | - gpuType?: 'Nvidia' | 'Others (Vulkan)'; |
12 | | - instructions?: 'AVX' | 'AVX2' | 'AVX512' | undefined; |
13 | | - cudaVersion?: '11' | '12'; |
14 | | - installCuda?: 'Yes' | string |
15 | | -} |
| 2 | +import { CommandRunner, InquirerService, SubCommand } from 'nest-commander'; |
| 3 | +import { InitCliUsecases } from './usecases/init.cli.usecases'; |
| 4 | +import { InitOptions } from './types/init-options.interface'; |
| 5 | + |
16 | 6 |
|
17 | 7 | @SubCommand({ |
18 | 8 | name: 'init', |
19 | 9 | aliases: ['setup'], |
20 | 10 | description: "Init settings and download cortex's dependencies", |
21 | 11 | }) |
22 | 12 | export class InitCommand extends CommandRunner { |
23 | | - CORTEX_RELEASES_URL = 'https://api.github.com/repos/janhq/cortex/releases'; |
24 | | - CUDA_DOWNLOAD_URL = 'https://catalog.jan.ai/dist/cuda-dependencies/<version>/<platform>/cuda.tar.gz' |
25 | 13 |
|
26 | 14 | constructor( |
27 | | - private readonly httpService: HttpService, |
28 | 15 | private readonly inquirerService: InquirerService, |
| 16 | + private readonly initUsecases: InitCliUsecases, |
29 | 17 | ) { |
30 | 18 | super(); |
31 | 19 | } |
32 | 20 |
|
33 | 21 | async run(input: string[], options?: InitOptions): Promise<void> { |
34 | 22 | options = await this.inquirerService.ask('create-init-questions', options); |
35 | 23 |
|
36 | | - if (options.runMode === 'GPU' && !(await this.cudaVersion())) { |
| 24 | + if (options.runMode === 'GPU' && !(await this.initUsecases.cudaVersion())) { |
37 | 25 | options = await this.inquirerService.ask('init-cuda-questions', options); |
38 | 26 | } |
39 | 27 |
|
40 | 28 | const version = input[0] ?? 'latest'; |
41 | 29 |
|
42 | | - await this.installEngine(this.parseEngineFileName(options), version); |
| 30 | + const engineFileName = this.initUsecases.parseEngineFileName(options) |
| 31 | + await this.initUsecases.installEngine(engineFileName, version); |
43 | 32 |
|
44 | 33 | if (options.installCuda === 'Yes') { |
45 | | - await this.installCudaToolkitDependency(options); |
46 | | - } |
47 | | - } |
48 | | - |
49 | | - installEngine = async ( |
50 | | - engineFileName: string, |
51 | | - version: string = 'latest', |
52 | | - ): Promise<any> => { |
53 | | - const res = await this.httpService |
54 | | - .get( |
55 | | - this.CORTEX_RELEASES_URL + `${version === 'latest' ? '/latest' : ''}`, |
56 | | - { |
57 | | - headers: { |
58 | | - 'X-GitHub-Api-Version': '2022-11-28', |
59 | | - Accept: 'application/vnd.github+json', |
60 | | - }, |
61 | | - }, |
62 | | - ) |
63 | | - .toPromise(); |
64 | | - |
65 | | - if (!res?.data) { |
66 | | - console.log('Failed to fetch releases'); |
67 | | - exit(1); |
68 | | - } |
69 | | - |
70 | | - let release = res?.data; |
71 | | - if (Array.isArray(res?.data)) { |
72 | | - release = Array(res?.data)[0].find( |
73 | | - (e) => e.name === version.replace('v', ''), |
74 | | - ); |
75 | | - } |
76 | | - const toDownloadAsset = release.assets.find((s: any) => |
77 | | - s.name.includes(engineFileName), |
78 | | - ); |
79 | | - |
80 | | - if (!toDownloadAsset) { |
81 | | - console.log(`Could not find engine file ${engineFileName}`); |
82 | | - exit(1); |
83 | | - } |
84 | | - |
85 | | - console.log(`Downloading engine file ${engineFileName}`); |
86 | | - const engineDir = resolve(this.rootDir(), 'cortex-cpp'); |
87 | | - if (existsSync(engineDir)) rmSync(engineDir, { recursive: true }); |
88 | | - |
89 | | - const download = await this.httpService |
90 | | - .get(toDownloadAsset.browser_download_url, { |
91 | | - responseType: 'stream', |
92 | | - }) |
93 | | - .toPromise(); |
94 | | - if (!download) { |
95 | | - console.log('Failed to download model'); |
96 | | - process.exit(1) |
97 | | - } |
98 | | - |
99 | | - const destination = resolve(this.rootDir(), toDownloadAsset.name); |
100 | | - |
101 | | - await new Promise((resolve, reject) => { |
102 | | - const writer = createWriteStream(destination); |
103 | | - let receivedBytes = 0; |
104 | | - const totalBytes = download.headers['content-length']; |
105 | | - |
106 | | - writer.on('finish', () => { |
107 | | - bar.stop(); |
108 | | - resolve(true); |
109 | | - }); |
110 | | - |
111 | | - writer.on('error', (error) => { |
112 | | - bar.stop(); |
113 | | - reject(error); |
114 | | - }); |
115 | | - |
116 | | - const bar = new SingleBar({}, Presets.shades_classic); |
117 | | - bar.start(100, 0); |
118 | | - |
119 | | - download.data.on('data', (chunk: any) => { |
120 | | - receivedBytes += chunk.length; |
121 | | - bar.update(Math.floor((receivedBytes / totalBytes) * 100)); |
122 | | - }); |
123 | | - |
124 | | - download.data.pipe(writer); |
125 | | - }); |
126 | | - |
127 | | - try { |
128 | | - await decompress( |
129 | | - resolve(this.rootDir(), destination), |
130 | | - resolve(this.rootDir()), |
131 | | - ); |
132 | | - } catch (e) { |
133 | | - console.log(e); |
134 | | - exit(1); |
135 | | - } |
136 | | - }; |
137 | | - |
138 | | - parseEngineFileName = (options: InitOptions) => { |
139 | | - const platform = |
140 | | - process.platform === 'win32' |
141 | | - ? 'windows' |
142 | | - : process.platform === 'darwin' |
143 | | - ? 'mac' |
144 | | - : process.platform; |
145 | | - const arch = process.arch === 'arm64' ? process.arch : 'amd64'; |
146 | | - const cudaVersion = |
147 | | - options.runMode === 'GPU' |
148 | | - ? options.gpuType === 'Nvidia' |
149 | | - ? '-cuda-' + (options.cudaVersion === '11' ? '11-7' : '12-0') |
150 | | - : '-vulkan' |
151 | | - : ''; |
152 | | - const instructions = options.instructions ? `-${options.instructions}` : ''; |
153 | | - const engineName = `${platform}-${arch}${instructions.toLowerCase()}${cudaVersion}`; |
154 | | - return `${engineName}.tar.gz`; |
155 | | - }; |
156 | | - |
157 | | - rootDir = () => resolve(__dirname, `../../../`); |
158 | | - |
159 | | - cudaVersion = async () => { |
160 | | - let filesCuda12: string[] |
161 | | - let filesCuda11: string[] |
162 | | - let paths: string[] |
163 | | - |
164 | | - if (process.platform === 'win32') { |
165 | | - filesCuda12 = ['cublas64_12.dll', 'cudart64_12.dll', 'cublasLt64_12.dll'] |
166 | | - filesCuda11 = ['cublas64_11.dll', 'cudart64_110.dll', 'cublasLt64_11.dll'] |
167 | | - paths = process.env.PATH ? process.env.PATH.split(delimiter) : [] |
168 | | - } else { |
169 | | - filesCuda12 = ['libcudart.so.12', 'libcublas.so.12', 'libcublasLt.so.12'] |
170 | | - filesCuda11 = ['libcudart.so.11.0', 'libcublas.so.11', 'libcublasLt.so.11'] |
171 | | - paths = process.env.LD_LIBRARY_PATH |
172 | | - ? process.env.LD_LIBRARY_PATH.split(delimiter) |
173 | | - : [] |
174 | | - paths.push('/usr/lib/x86_64-linux-gnu/') |
175 | | - } |
176 | | - |
177 | | - if (filesCuda12.every( |
178 | | - (file) => existsSync(file) || this.checkFileExistenceInPaths(file, paths) |
179 | | - )) return '12' |
180 | | - |
181 | | - |
182 | | - if (filesCuda11.every( |
183 | | - (file) => existsSync(file) || this.checkFileExistenceInPaths(file, paths) |
184 | | - )) return '11' |
185 | | - |
186 | | - return undefined // No CUDA Toolkit found |
187 | | - } |
188 | | - |
189 | | - checkFileExistenceInPaths = (file: string, paths: string[]): boolean => { |
190 | | - return paths.some((p) => existsSync(join(p, file))) |
191 | | - } |
192 | | - |
193 | | - installCudaToolkitDependency = async (options: InitOptions) => { |
194 | | - const platform = process.platform === 'win32' ? 'windows' : 'linux' |
195 | | - |
196 | | - const url = this.CUDA_DOWNLOAD_URL |
197 | | - .replace('<version>', options.cudaVersion === '11' ? '11.7' : '12.0') |
198 | | - .replace('<platform>', platform) |
199 | | - const destination = resolve(this.rootDir(), 'cuda-toolkit.tar.gz'); |
200 | | - |
201 | | - const download = await this.httpService |
202 | | - .get(url, { |
203 | | - responseType: 'stream', |
204 | | - }) |
205 | | - .toPromise(); |
206 | | - |
207 | | - if (!download) { |
208 | | - console.log('Failed to download dependency'); |
209 | | - process.exit(1) |
210 | | - } |
211 | | - |
212 | | - await new Promise((resolve, reject) => { |
213 | | - const writer = createWriteStream(destination); |
214 | | - let receivedBytes = 0; |
215 | | - const totalBytes = download.headers['content-length']; |
216 | | - |
217 | | - writer.on('finish', () => { |
218 | | - bar.stop(); |
219 | | - resolve(true); |
220 | | - }); |
221 | | - |
222 | | - writer.on('error', (error) => { |
223 | | - bar.stop(); |
224 | | - reject(error); |
225 | | - }); |
226 | | - |
227 | | - const bar = new SingleBar({}, Presets.shades_classic); |
228 | | - bar.start(100, 0); |
229 | | - |
230 | | - download.data.on('data', (chunk: any) => { |
231 | | - receivedBytes += chunk.length; |
232 | | - bar.update(Math.floor((receivedBytes / totalBytes) * 100)); |
233 | | - }); |
234 | | - |
235 | | - download.data.pipe(writer); |
236 | | - }); |
237 | | - |
238 | | - try { |
239 | | - await decompress( |
240 | | - resolve(this.rootDir(), destination), |
241 | | - resolve(this.rootDir(), 'cortex-cpp'), |
242 | | - ); |
243 | | - } catch (e) { |
244 | | - console.log(e); |
245 | | - exit(1); |
| 34 | + await this.initUsecases.installCudaToolkitDependency(options); |
246 | 35 | } |
247 | 36 | } |
248 | 37 | } |
0 commit comments