Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: cortex post-install script - init silently on install #642

Merged
merged 1 commit into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,7 @@ To install Cortex CLI, follow the steps below:
npm i -g @janhq/cortex
```

2. Initialize a compatible engine:
``` bash
cortex init
```

3. Download a GGUF model from Hugging Face:
2. Download a GGUF model from Hugging Face:
``` bash
# Pull a model most compatible with your hardware
cortex pull llama3
Expand All @@ -84,12 +79,12 @@ cortex pull llama3:7b
# Pull a model with the HuggingFace `model_id`
cortex pull microsoft/Phi-3-mini-4k-instruct-gguf
```
4. Load the model:
3. Load the model:
``` bash
cortex models start llama3:7b
```

5. Start chatting with the model:
4. Start chatting with the model:
``` bash
cortex chat tell me a joke
```
Expand Down
11 changes: 3 additions & 8 deletions cortex-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,7 @@ To install Cortex CLI, follow the steps below:
npm i -g @janhq/cortex
```

2. Initialize a compatible engine:
``` bash
cortex init
```

3. Download a GGUF model from Hugging Face:
2. Download a GGUF model from Hugging Face:
``` bash
# Pull a model most compatible with your hardware
cortex pull llama3
Expand All @@ -84,12 +79,12 @@ cortex pull llama3:7b
# Pull a model with the HuggingFace `model_id`
cortex pull microsoft/Phi-3-mini-4k-instruct-gguf
```
4. Load the model:
3. Load the model:
``` bash
cortex models start llama3:7b
```

5. Start chatting with the model:
4. Start chatting with the model:
``` bash
cortex chat tell me a joke
```
Expand Down
3 changes: 2 additions & 1 deletion cortex-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"typeorm": "typeorm-ts-node-esm",
"build:dev": "npx nest build && chmod +x ./dist/src/command.js && npm link"
"build:dev": "npx nest build && chmod +x ./dist/src/command.js && npm link",
"postinstall": "cortex init -s"
},
"dependencies": {
"@huggingface/gguf": "^0.1.5",
Expand Down
49 changes: 48 additions & 1 deletion cortex-js/src/infrastructure/commanders/init.command.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { CommandRunner, InquirerService, SubCommand } from 'nest-commander';
import {
CommandRunner,
InquirerService,
SubCommand,
Option,
} from 'nest-commander';
import { InitCliUsecases } from './usecases/init.cli.usecases';
import { InitOptions } from './types/init-options.interface';

Expand All @@ -16,6 +21,39 @@ export class InitCommand extends CommandRunner {
}

async run(input: string[], options?: InitOptions): Promise<void> {
if (options?.silent) {
return this.initSilently(input);
} else {
return this.initPrompts(input, options);
}
}

private initSilently = async (input: string[], options: InitOptions = {}) => {
const version = input[0] ?? 'latest';
if (process.platform === 'darwin') {
const engineFileName = this.initUsecases.parseEngineFileName(options);
return this.initUsecases.installEngine(engineFileName, version);
}
// If Nvidia Driver is installed -> GPU
options.runMode = (await this.initUsecases.checkNvidiaGPUExist())
? 'GPU'
: 'CPU';
// CPU Instructions detection
options.gpuType = 'Nvidia';
options.instructions = await this.initUsecases.detectInstructions();
const engineFileName = this.initUsecases.parseEngineFileName(options);
return this.initUsecases
.installEngine(engineFileName, version)
.then(() => this.initUsecases.installCudaToolkitDependency(options));
};

/**
* Manual initalization
* To setup cortex's dependencies
* @param input
* @param options GPU | CPU / Nvidia | Others (Vulkan) / AVX | AVX2 | AVX512
*/
private initPrompts = async (input: string[], options?: InitOptions) => {
options = await this.inquirerService.ask(
'init-run-mode-questions',
options,
Expand All @@ -33,5 +71,14 @@ export class InitCommand extends CommandRunner {
if (options.installCuda === 'Yes') {
await this.initUsecases.installCudaToolkitDependency(options);
}
};

@Option({
flags: '-s, --silent',
description: 'Init without asking questions',
defaultValue: false,
})
parseSilent() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export interface InitOptions {
instructions?: 'AVX' | 'AVX2' | 'AVX512' | undefined;
cudaVersion?: '11' | '12';
installCuda?: 'Yes' | string;
silent?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import { Injectable } from '@nestjs/common';
import { firstValueFrom } from 'rxjs';
import { FileManagerService } from '@/file-manager/file-manager.service';
import { rm } from 'fs/promises';
import { exec } from 'child_process';

@Injectable()
export class InitCliUsecases {
CORTEX_RELEASES_URL = 'https://api.github.com/repos/janhq/cortex/releases';
CUDA_DOWNLOAD_URL =
private readonly CORTEX_RELEASES_URL =
'https://api.github.com/repos/janhq/cortex/releases';
private readonly CUDA_DOWNLOAD_URL =
'https://catalog.jan.ai/dist/cuda-dependencies/<version>/<platform>/cuda.tar.gz';

constructor(
Expand Down Expand Up @@ -109,7 +111,7 @@ export class InitCliUsecases {
await rm(destination, { force: true });
};

parseEngineFileName = (options: InitOptions) => {
parseEngineFileName = (options?: InitOptions) => {
const platform =
process.platform === 'win32'
? 'windows'
Expand All @@ -118,12 +120,14 @@ export class InitCliUsecases {
: process.platform;
const arch = process.arch === 'arm64' ? process.arch : 'amd64';
const cudaVersion =
options.runMode === 'GPU'
options?.runMode === 'GPU'
? options.gpuType === 'Nvidia'
? '-cuda-' + (options.cudaVersion === '11' ? '11-7' : '12-0')
: '-vulkan'
: '';
const instructions = options.instructions ? `-${options.instructions}` : '';
const instructions = options?.instructions
? `-${options.instructions}`
: '';
const engineName = `${platform}-${arch}${instructions.toLowerCase()}${cudaVersion}`;
return `${engineName}.tar.gz`;
};
Expand Down Expand Up @@ -173,10 +177,6 @@ export class InitCliUsecases {
return undefined; // No CUDA Toolkit found
};

checkFileExistenceInPaths = (file: string, paths: string[]): boolean => {
return paths.some((p) => existsSync(join(p, file)));
};

installCudaToolkitDependency = async (options: InitOptions) => {
const platform = process.platform === 'win32' ? 'windows' : 'linux';

Expand Down Expand Up @@ -232,4 +232,61 @@ export class InitCliUsecases {
}
await rm(destination, { force: true });
};

// Function to check for NVIDIA GPU
checkNvidiaGPUExist = (): Promise<boolean> => {
return new Promise<boolean>((resolve) => {
// Execute the nvidia-smi command
exec('nvidia-smi', (error) => {
if (error) {
// If there's an error, it means nvidia-smi is not installed or there's no NVIDIA GPU
console.log('NVIDIA GPU not detected or nvidia-smi not installed.');
resolve(false);
} else {
// If the command executes successfully, NVIDIA GPU is present
console.log('NVIDIA GPU detected.');
resolve(true);
}
});
});
};

detectInstructions = (): Promise<'AVX' | 'AVX2' | 'AVX512' | undefined> => {
return new Promise<'AVX' | 'AVX2' | 'AVX512' | undefined>((res) => {
// Execute the cpuinfo command

exec(
join(
__dirname,
`../../../../bin/cpuinfo${process.platform !== 'linux' ? '.exe' : ''}`,
),
(error, stdout) => {
if (error) {
// If there's an error, it means lscpu is not installed
console.log('CPUInfo is not installed.');
res('AVX');
} else {
// If the command executes successfully, parse the output to detect CPU instructions
if (stdout.includes('"AVX512": "true"')) {
console.log('AVX-512 instructions detected.');
res('AVX512');
} else if ('"AVX2": "true"') {
console.log('AVX2 instructions detected.');
res('AVX2');
} else {
console.log('AVXs instructions detected.');
res('AVX');
}
}
},
);
});
};

private checkFileExistenceInPaths = (
file: string,
paths: string[],
): boolean => {
return paths.some((p) => existsSync(join(p, file)));
};
}