@@ -702,6 +702,143 @@ Napi::Value AddonContext::SetThreads(const Napi::CallbackInfo& info) {
702702 return info.Env ().Undefined ();
703703}
704704
705+ class AddonContextSaveSequenceStateToFileWorker : public Napi ::AsyncWorker {
706+ public:
707+ AddonContext* context;
708+ std::string filepath;
709+ llama_seq_id sequenceId;
710+ std::vector<llama_token> tokens;
711+ size_t savedFileSize = 0 ;
712+
713+ AddonContextSaveSequenceStateToFileWorker (const Napi::CallbackInfo& info, AddonContext* context)
714+ : Napi::AsyncWorker(info.Env(), " AddonContextSaveSequenceStateToFileWorker" ),
715+ context (context),
716+ deferred(Napi::Promise::Deferred::New(info.Env())) {
717+ context->Ref ();
718+
719+ filepath = info[0 ].As <Napi::String>().Utf8Value ();
720+ sequenceId = info[1 ].As <Napi::Number>().Int32Value ();
721+ Napi::Uint32Array inputTokens = info[2 ].As <Napi::Uint32Array>();
722+
723+ tokens.resize (inputTokens.ElementLength ());
724+ for (size_t i = 0 ; i < tokens.size (); i++) {
725+ tokens[i] = inputTokens[i];
726+ }
727+ }
728+ ~AddonContextSaveSequenceStateToFileWorker () {
729+ context->Unref ();
730+ }
731+
732+ Napi::Promise GetPromise () {
733+ return deferred.Promise ();
734+ }
735+
736+ protected:
737+ Napi::Promise::Deferred deferred;
738+
739+ void Execute () {
740+ try {
741+ savedFileSize = llama_state_seq_save_file (context->ctx , filepath.c_str (), sequenceId, tokens.data (), tokens.size ());
742+ if (savedFileSize == 0 ) {
743+ SetError (" Failed to save state to file" );
744+ return ;
745+ }
746+ } catch (const std::exception& e) {
747+ SetError (e.what ());
748+ } catch (...) {
749+ SetError (" Unknown error when calling \" llama_state_seq_save_file\" " );
750+ }
751+ }
752+ void OnOK () {
753+ deferred.Resolve (Napi::Number::New (Env (), savedFileSize));
754+ }
755+ void OnError (const Napi::Error& err) {
756+ deferred.Reject (err.Value ());
757+ }
758+ };
759+ Napi::Value AddonContext::SaveSequenceStateToFile (const Napi::CallbackInfo& info) {
760+ if (disposed) {
761+ Napi::Error::New (info.Env (), " Context is disposed" ).ThrowAsJavaScriptException ();
762+ return info.Env ().Undefined ();
763+ }
764+
765+ AddonContextSaveSequenceStateToFileWorker* worker = new AddonContextSaveSequenceStateToFileWorker (info, this );
766+ worker->Queue ();
767+ return worker->GetPromise ();
768+ }
769+
770+ class AddonContextLoadSequenceStateFromFileWorker : public Napi ::AsyncWorker {
771+ public:
772+ AddonContext* context;
773+ std::string filepath;
774+ llama_seq_id sequenceId;
775+ size_t maxContextSize;
776+ std::vector<llama_token> tokens;
777+
778+ AddonContextLoadSequenceStateFromFileWorker (const Napi::CallbackInfo& info, AddonContext* context)
779+ : Napi::AsyncWorker(info.Env(), " AddonContextLoadSequenceStateFromFileWorker" ),
780+ context (context),
781+ deferred(Napi::Promise::Deferred::New(info.Env())) {
782+ context->Ref ();
783+
784+ filepath = info[0 ].As <Napi::String>().Utf8Value ();
785+ sequenceId = info[1 ].As <Napi::Number>().Int32Value ();
786+ maxContextSize = info[2 ].As <Napi::Number>().Uint32Value ();
787+
788+ tokens.resize (maxContextSize);
789+ }
790+ ~AddonContextLoadSequenceStateFromFileWorker () {
791+ context->Unref ();
792+ }
793+
794+ Napi::Promise GetPromise () {
795+ return deferred.Promise ();
796+ }
797+
798+ protected:
799+ Napi::Promise::Deferred deferred;
800+
801+ void Execute () {
802+ try {
803+ size_t tokenCount = 0 ;
804+ const size_t fileSize = llama_state_seq_load_file (context->ctx , filepath.c_str (), sequenceId, tokens.data (), tokens.size (), &tokenCount);
805+ if (fileSize == 0 ) {
806+ SetError (" Failed to load state from file. Current context sequence size may be smaller that the state of the file" );
807+ return ;
808+ }
809+
810+ tokens.resize (tokenCount);
811+ } catch (const std::exception& e) {
812+ SetError (e.what ());
813+ } catch (...) {
814+ SetError (" Unknown error when calling \" llama_state_seq_load_file\" " );
815+ }
816+ }
817+ void OnOK () {
818+ size_t tokenCount = tokens.size ();
819+ Napi::Uint32Array result = Napi::Uint32Array::New (Env (), tokenCount);
820+
821+ for (size_t i = 0 ; i < tokenCount; i++) {
822+ result[i] = tokens[i];
823+ }
824+
825+ deferred.Resolve (result);
826+ }
827+ void OnError (const Napi::Error& err) {
828+ deferred.Reject (err.Value ());
829+ }
830+ };
831+ Napi::Value AddonContext::LoadSequenceStateFromFile (const Napi::CallbackInfo& info) {
832+ if (disposed) {
833+ Napi::Error::New (info.Env (), " Context is disposed" ).ThrowAsJavaScriptException ();
834+ return info.Env ().Undefined ();
835+ }
836+
837+ AddonContextLoadSequenceStateFromFileWorker* worker = new AddonContextLoadSequenceStateFromFileWorker (info, this );
838+ worker->Queue ();
839+ return worker->GetPromise ();
840+ }
841+
705842Napi::Value AddonContext::PrintTimings (const Napi::CallbackInfo& info) {
706843 llama_perf_context_print (ctx);
707844 llama_perf_context_reset (ctx);
@@ -797,6 +934,8 @@ void AddonContext::init(Napi::Object exports) {
797934 InstanceMethod (" setThreads" , &AddonContext::SetThreads),
798935 InstanceMethod (" printTimings" , &AddonContext::PrintTimings),
799936 InstanceMethod (" ensureDraftContextIsCompatibleForSpeculative" , &AddonContext::EnsureDraftContextIsCompatibleForSpeculative),
937+ InstanceMethod (" saveSequenceStateToFile" , &AddonContext::SaveSequenceStateToFile),
938+ InstanceMethod (" loadSequenceStateFromFile" , &AddonContext::LoadSequenceStateFromFile),
800939 InstanceMethod (" setLora" , &AddonContext::SetLora),
801940 InstanceMethod (" dispose" , &AddonContext::Dispose),
802941 }
0 commit comments