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

[JS] Add GenAI Node.js bindings #1193

Open
wants to merge 58 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
0d73757
Add genai nodejs bindings
vishniakov-nikolai Nov 11, 2024
2d71b6b
Include js bindings into package build
vishniakov-nikolai Nov 13, 2024
0c0b41c
Put binaries at the top level of NPM package
vishniakov-nikolai Nov 15, 2024
7e80306
Skip samples for NPM package
vishniakov-nikolai Nov 15, 2024
67d5a40
Add model downloading into js unit tests
vishniakov-nikolai Nov 19, 2024
689e352
Merge branch 'master' into feature/nodejs-bindings
vishniakov-nikolai Nov 19, 2024
4e3405a
Merge branch 'master' into feature/nodejs-bindings
vishniakov-nikolai Nov 20, 2024
6853446
Set openvino_tokenizers on latest master
vishniakov-nikolai Nov 20, 2024
e433a19
Merge branch 'master' into feature/nodejs-bindings
vishniakov-nikolai Nov 25, 2024
ae806da
Temporary disable most of gha wf to debug
vishniakov-nikolai Nov 26, 2024
8632882
Minify linux wf
vishniakov-nikolai Nov 26, 2024
9ceb3b5
Fix wf file
vishniakov-nikolai Nov 26, 2024
599713b
Fix wf file
vishniakov-nikolai Nov 26, 2024
222940d
Set cpack generator as NPM
vishniakov-nikolai Nov 26, 2024
901dd90
Set ENABLE_JS on
vishniakov-nikolai Nov 26, 2024
09356ef
Add genai_node_addon component if ENABLE_JS is ON
vishniakov-nikolai Nov 26, 2024
96d53b2
Add wf to check genai js bindings compilation
vishniakov-nikolai Nov 27, 2024
785bf8d
Remove setupvars.sh run
vishniakov-nikolai Nov 27, 2024
e8c53f2
Fix ov + genai workflow
vishniakov-nikolai Nov 27, 2024
82ae75f
Fix ov + genai workflow
vishniakov-nikolai Nov 27, 2024
963fc1d
Fix path to genai folder
vishniakov-nikolai Nov 27, 2024
1d952b6
Change OV branch hash
vishniakov-nikolai Nov 28, 2024
e909f03
Align openvino_tokenizers version with master
vishniakov-nikolai Dec 11, 2024
c5f5f85
Move rpath setting to libopenvino_genai part
vishniakov-nikolai Dec 11, 2024
6d7cbcd
Add ENABLE_JS option to features.cmake
vishniakov-nikolai Dec 11, 2024
d999569
Rollback worflow files
vishniakov-nikolai Dec 11, 2024
4479e1b
Merge branch 'master' into feature/nodejs-bindings
vishniakov-nikolai Dec 11, 2024
b722d39
Add genai_nodejs_bindings linux workflow job
vishniakov-nikolai Dec 11, 2024
3b4b3c0
Specify rpath for genai_node_addon.node
vishniakov-nikolai Dec 12, 2024
17f2069
Fix offsets
vishniakov-nikolai Dec 12, 2024
27a97d8
Fix path to js samples in readme
vishniakov-nikolai Dec 12, 2024
b3eba5e
Merge branch 'master' into feature/nodejs-bindings
vishniakov-nikolai Dec 12, 2024
6f0e0b4
Align API
vishniakov-nikolai Dec 12, 2024
855c893
Align chat sample
vishniakov-nikolai Dec 12, 2024
df51af9
Add README.md into js chat sample
vishniakov-nikolai Dec 12, 2024
a365ef8
Update build instructions
vishniakov-nikolai Dec 12, 2024
6d0b501
Fix review comments
vishniakov-nikolai Dec 16, 2024
f241cda
Add run js api test in linux workflow
vishniakov-nikolai Dec 16, 2024
1e97e70
Merge branch 'master' into feature/nodejs-bindings
vishniakov-nikolai Dec 16, 2024
1f2324b
Fix workflow
vishniakov-nikolai Dec 16, 2024
dda494d
Add test for js sample
vishniakov-nikolai Dec 17, 2024
394d425
Extend linux workflow to run js samples test
vishniakov-nikolai Dec 17, 2024
3186142
Change runner for js samples tests
vishniakov-nikolai Dec 17, 2024
2fbd888
Fix repo path
vishniakov-nikolai Dec 17, 2024
2daa0f3
Merge branch 'master' into feature/nodejs-bindings
vishniakov-nikolai Dec 17, 2024
0a0c399
Fix path
vishniakov-nikolai Dec 17, 2024
35c1c61
Add debug output
vishniakov-nikolai Dec 17, 2024
b2cb5dd
Fix js samples test
vishniakov-nikolai Dec 18, 2024
13c0558
Merge branch 'master' into feature/nodejs-bindings
vishniakov-nikolai Dec 18, 2024
432e3de
Merge branch 'master' into feature/nodejs-bindings
vishniakov-nikolai Dec 23, 2024
30803b4
Fix run sample cmd
vishniakov-nikolai Dec 23, 2024
b04ee6c
Fix js README.md
vishniakov-nikolai Dec 23, 2024
d7e0d2b
Replace AUTO device to CPU
vishniakov-nikolai Dec 23, 2024
d1ce555
Fix js samples README
vishniakov-nikolai Dec 23, 2024
ea0ff4a
Align linux workflow with js samples README
vishniakov-nikolai Dec 23, 2024
7f8ee36
Remove tbb specification from linux js workflow
vishniakov-nikolai Dec 23, 2024
6434daf
Merge branch 'master' into feature/nodejs-bindings
vishniakov-nikolai Dec 23, 2024
d03b5cd
Merge branch 'master' into feature/nodejs-bindings
vishniakov-nikolai Dec 27, 2024
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
204 changes: 171 additions & 33 deletions .github/workflows/linux.yml

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ set(CPACK_COMPONENTS_ALL core_genai core_genai_dev cpp_samples_genai licensing_g
if(ENABLE_PYTHON)
list(APPEND CPACK_COMPONENTS_ALL pygenai_${Python3_VERSION_MAJOR}_${Python3_VERSION_MINOR})
endif()
if(ENABLE_JS)
list(APPEND CPACK_COMPONENTS_ALL genai_node_addon)
Wovchena marked this conversation as resolved.
Show resolved Hide resolved
endif()
if(WIN32 AND NOT DEFINED CPACK_GENERATOR)
set(CPACK_GENERATOR "ZIP")
endif()
Expand Down
1 change: 1 addition & 0 deletions cmake/features.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
#

option(ENABLE_PYTHON "Enable Python API build" ON)
option(ENABLE_JS "Enable JS API build" OFF)
5 changes: 5 additions & 0 deletions samples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
# SPDX-License-Identifier: Apache-2.0
#

# Samples do not need to be built for NPM package
if(CPACK_GENERATOR STREQUAL "NPM")
return()
endif()

add_subdirectory(cpp/beam_search_causal_lm)
add_subdirectory(cpp/benchmark_genai)
add_subdirectory(cpp/chat_sample)
Expand Down
1 change: 1 addition & 0 deletions samples/js/chat_sample/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
48 changes: 48 additions & 0 deletions samples/js/chat_sample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# JavaScript chat_sample that supports most popular models like LLaMA 3

This example showcases inference of text-generation Large Language Models (LLMs): `chatglm`, `LLaMA`, `Qwen` and other models with the same signature. The application doesn't have many configuration options to encourage the reader to explore and modify the source code. For example, change the device for inference to GPU. The sample fearures `Pipeline.LLMPipeline` and configures it for the chat scenario.

## Download and convert the model and tokenizers

To convert model you have to use python package `optimum-intel`.
The `--upgrade-strategy eager` option is needed to ensure `optimum-intel` is upgraded to the latest version.

Install [../../export-requirements.txt](../../export-requirements.txt) to convert a model.

```sh
pip install --upgrade-strategy eager -r ../../export-requirements.txt
optimum-cli export openvino --trust-remote-code --model TinyLlama/TinyLlama-1.1B-Chat-v1.0 TinyLlama-1.1B-Chat-v1.0
```

## Run:

Compile GenAI JavaScript bindings archive first using the instructions in [../../../src/js/README.md](../../../src/js/README.md#build-bindings).

Run `npm install` in current folder and then run the sample:

`node chat_sample.js TinyLlama-1.1B-Chat-v1.0`

Discrete GPUs (dGPUs) usually provide better performance compared to CPUs. It is recommended to run larger models on a dGPU with 32GB+ RAM. For example, the model meta-llama/Llama-2-13b-chat-hf can benefit from being run on a dGPU. Modify the source code to change the device for inference to the GPU.

See https://github.com/openvinotoolkit/openvino.genai/blob/master/src/README.md#supported-models for the list of supported models.

### Troubleshooting

#### Unicode characters encoding error on Windows

Example error:
```
UnicodeEncodeError: 'charmap' codec can't encode character '\u25aa' in position 0: character maps to <undefined>
```

If you encounter the error described in the example when sample is printing output to the Windows console, it is likely due to the default Windows encoding not supporting certain Unicode characters. To resolve this:
1. Enable Unicode characters for Windows cmd - open `Region` settings from `Control panel`. `Administrative`->`Change system locale`->`Beta: Use Unicode UTF-8 for worldwide language support`->`OK`. Reboot.
2. Enable UTF-8 mode by setting environment variable `PYTHONIOENCODING="utf8"`.

#### Missing chat template

If you encounter an exception indicating a missing "chat template" when launching the `ov::genai::LLMPipeline` in chat mode, it likely means the model was not tuned for chat functionality. To work this around, manually add the chat template to tokenizer_config.json of your model.
The following template can be used as a default, but it may not work properly with every model:
```
"chat_template": "{% for message in messages %}{% if (message['role'] == 'user') %}{{'<|im_start|>user\n' + message['content'] + '<|im_end|>\n<|im_start|>assistant\n'}}{% elif (message['role'] == 'assistant') %}{{message['content'] + '<|im_end|>\n'}}{% endif %}{% endfor %}",
```
54 changes: 54 additions & 0 deletions samples/js/chat_sample/chat_sample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import readline from 'readline';
import { Pipeline } from 'genai-node';

main();

function streamer(subword) {
process.stdout.write(subword);
}

async function main() {
const MODEL_PATH = process.argv[2];

if (!MODEL_PATH) {
console.error('Please specify path to model directory\n'
+ 'Run command must be: `node chat_sample.js *path_to_model_dir*`');
process.exit(1);
}

const device = 'CPU'; // GPU can be used as well

// Create interface for reading user input from stdin
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

const pipe = await Pipeline.LLMPipeline(MODEL_PATH, device);
const config = { 'max_new_tokens': 100 };

await pipe.startChat();
Wovchena marked this conversation as resolved.
Show resolved Hide resolved
promptUser();

// Function to prompt the user for input
function promptUser() {
rl.question('question:\n', handleInput);
}

// Function to handle user input
async function handleInput(input) {
input = input.trim();

// Check for exit command
if (!input) {
await pipe.finishChat();
rl.close();
process.exit(0);
}

await pipe.generate(input, config, streamer);
console.log('\n----------');

if (!rl.closed) promptUser();
}
}
42 changes: 42 additions & 0 deletions samples/js/chat_sample/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions samples/js/chat_sample/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "genai-node-demo",
"version": "1.0.0",
"license": "Apache-2.0",
"type": "module",
"devDependencies": {
"genai-node": "../../../src/js/"
},
"engines": {
"node": ">=21.0.0"
},
"scripts": {
"test": "node tests/usage.test.js"
}
}
63 changes: 63 additions & 0 deletions samples/js/chat_sample/tests/usage.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { env } from 'process';
import { spawn } from 'child_process';

const MODEL_PATH = env.MODEL_PATH;
const prompt = 'Tell me exactly, no changes, print as is: "Hello world"';
const expected = 'Hello world';

if (!MODEL_PATH)
throw new Error(
'Please environment variable MODEL_PATH to the path of the model directory'
);

const runTest = async () => {
return new Promise((resolve, reject) => {
const script = spawn('node', ['chat_sample.js', MODEL_PATH]);
let output = '';

// Collect output from stdout
script.stdout.on('data', (data) => {
output += data.toString();
});

// Capture errors
script.stderr.on('data', (data) => {
reject(data.toString());
});

// Send input after detecting the question prompt
script.stdout.once('data', (data) => {
if (data.toString().startsWith('question:')) {
script.stdin.write(`${prompt}\n`); // Provide input
script.stdin.end(); // Close stdin to signal EOF
}
});

// Check results when the process exits
script.on('close', (code) => {
if (code !== 0) {
return reject(`Process exited with code ${code}`);
}

// Log the output
console.log(`Result output: ${output}`);

// Validate the output
if (output.includes(expected)) {
resolve('Test passed!');
} else {
reject('Test failed: Output did not match expected result.');
}
});
});
};

runTest()
.then((message) => {
console.log(message);
process.exit(0);
})
.catch((err) => {
console.error(err);
process.exit(1);
});
4 changes: 4 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ add_subdirectory(cpp)
if(ENABLE_PYTHON)
add_subdirectory(python)
endif()

if(ENABLE_JS)
add_subdirectory(js)
endif()
35 changes: 32 additions & 3 deletions src/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,42 @@ if(MSVC OR APPLE)
set(ARCH_DIR ${ARCH_DIR}/${CMAKE_BUILD_TYPE})
endif()

# Put binaries at the top level for NPM package
if(CPACK_GENERATOR STREQUAL "NPM")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to use js bindings from build tree directly? If not, you should enforce NPM package generator if ENABLE_JS is true.

set(LIBRARY_DESTINATION .)
set(ARCHIVE_DESTINATION .)
set(RUNTIME_DESTINATION .)

# setting RPATH / LC_RPATH depending on platform
if(LINUX)
# to find libopenvino.so in the same folder
set(rpaths "$ORIGIN")
elseif(APPLE)
# to find libopenvino.dylib in the same folder
set(rpaths "@loader_path")
endif()

if(rpaths)
set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "${rpaths}")
endif()
else()
set(LIBRARY_DESTINATION runtime/lib/${ARCH_DIR})
set(ARCHIVE_DESTINATION runtime/lib/${ARCH_DIR})
set(RUNTIME_DESTINATION runtime/bin/${ARCH_DIR})
endif()

install(TARGETS ${TARGET_NAME} EXPORT OpenVINOGenAITargets
LIBRARY DESTINATION runtime/lib/${ARCH_DIR} COMPONENT core_genai
LIBRARY DESTINATION ${LIBRARY_DESTINATION} COMPONENT core_genai
NAMELINK_COMPONENT core_genai_dev
ARCHIVE DESTINATION runtime/lib/${ARCH_DIR} COMPONENT core_genai_dev
RUNTIME DESTINATION runtime/bin/${ARCH_DIR} COMPONENT core_genai
ARCHIVE DESTINATION ${ARCHIVE_DESTINATION} COMPONENT core_genai_dev
RUNTIME DESTINATION ${RUNTIME_DESTINATION} COMPONENT core_genai
INCLUDES DESTINATION runtime/include)

# samples do not need to be built for NPM package
if(CPACK_GENERATOR STREQUAL "NPM")
return()
endif()

install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION runtime/include COMPONENT core_genai_dev)
install(EXPORT OpenVINOGenAITargets FILE OpenVINOGenAITargets.cmake
Expand Down
7 changes: 7 additions & 0 deletions src/js/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.vscode
bin
bin.*
build
thirdparty
node_modules
tests/models
15 changes: 15 additions & 0 deletions src/js/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.vscode
bin.*
build
include
src
tests

.eslintrc.js
CMakeLists.txt
tsconfig.json
TODO.md
build.sh

**/*.tsbuildinfo
*.tgz
Loading
Loading