|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <jni.h> |
|
|
#include <android/log.h> |
|
|
#include <android/asset_manager.h> |
|
|
#include <android/asset_manager_jni.h> |
|
|
|
|
|
#include <string> |
|
|
#include <vector> |
|
|
#include <memory> |
|
|
#include <thread> |
|
|
#include <atomic> |
|
|
#include <mutex> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define LOG_TAG "Mind2" |
|
|
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) |
|
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) |
|
|
|
|
|
namespace { |
|
|
|
|
|
|
|
|
struct Mind2Context { |
|
|
std::string model_path; |
|
|
int n_ctx = 2048; |
|
|
int n_threads = 4; |
|
|
bool loaded = false; |
|
|
std::atomic<bool> generating{false}; |
|
|
std::mutex mutex; |
|
|
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
std::unique_ptr<Mind2Context> g_context; |
|
|
|
|
|
|
|
|
JavaVM* g_jvm = nullptr; |
|
|
jobject g_callback = nullptr; |
|
|
jmethodID g_callback_method = nullptr; |
|
|
|
|
|
void stream_token(const std::string& token) { |
|
|
if (!g_jvm || !g_callback) return; |
|
|
|
|
|
JNIEnv* env = nullptr; |
|
|
bool attached = false; |
|
|
|
|
|
if (g_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { |
|
|
g_jvm->AttachCurrentThread(&env, nullptr); |
|
|
attached = true; |
|
|
} |
|
|
|
|
|
if (env && g_callback && g_callback_method) { |
|
|
jstring jtoken = env->NewStringUTF(token.c_str()); |
|
|
env->CallVoidMethod(g_callback, g_callback_method, jtoken); |
|
|
env->DeleteLocalRef(jtoken); |
|
|
} |
|
|
|
|
|
if (attached) { |
|
|
g_jvm->DetachCurrentThread(); |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
extern "C" { |
|
|
|
|
|
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { |
|
|
g_jvm = vm; |
|
|
LOGI("Mind2 JNI loaded"); |
|
|
return JNI_VERSION_1_6; |
|
|
} |
|
|
|
|
|
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) { |
|
|
g_context.reset(); |
|
|
g_jvm = nullptr; |
|
|
LOGI("Mind2 JNI unloaded"); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JNIEXPORT jboolean JNICALL |
|
|
Java_com_minimind_mind2_Mind2Model_nativeInit( |
|
|
JNIEnv* env, |
|
|
jobject thiz, |
|
|
jstring model_path, |
|
|
jint n_ctx, |
|
|
jint n_threads |
|
|
) { |
|
|
const char* path = env->GetStringUTFChars(model_path, nullptr); |
|
|
LOGI("Initializing Mind2 with model: %s", path); |
|
|
|
|
|
g_context = std::make_unique<Mind2Context>(); |
|
|
g_context->model_path = path; |
|
|
g_context->n_ctx = n_ctx; |
|
|
g_context->n_threads = n_threads > 0 ? n_threads : std::thread::hardware_concurrency(); |
|
|
|
|
|
env->ReleaseStringUTFChars(model_path, path); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
g_context->loaded = true; |
|
|
LOGI("Mind2 initialized successfully (threads: %d, ctx: %d)", |
|
|
g_context->n_threads, g_context->n_ctx); |
|
|
|
|
|
return JNI_TRUE; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JNIEXPORT jstring JNICALL |
|
|
Java_com_minimind_mind2_Mind2Model_nativeGenerate( |
|
|
JNIEnv* env, |
|
|
jobject thiz, |
|
|
jstring prompt, |
|
|
jint max_tokens, |
|
|
jfloat temperature, |
|
|
jfloat top_p, |
|
|
jint top_k |
|
|
) { |
|
|
if (!g_context || !g_context->loaded) { |
|
|
LOGE("Model not initialized"); |
|
|
return env->NewStringUTF(""); |
|
|
} |
|
|
|
|
|
std::lock_guard<std::mutex> lock(g_context->mutex); |
|
|
|
|
|
const char* prompt_str = env->GetStringUTFChars(prompt, nullptr); |
|
|
std::string result; |
|
|
|
|
|
LOGI("Generating with prompt: %.50s...", prompt_str); |
|
|
|
|
|
|
|
|
|
|
|
result = std::string(prompt_str) + "\n\n[Generated response would appear here]"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
env->ReleaseStringUTFChars(prompt, prompt_str); |
|
|
|
|
|
return env->NewStringUTF(result.c_str()); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JNIEXPORT void JNICALL |
|
|
Java_com_minimind_mind2_Mind2Model_nativeGenerateStream( |
|
|
JNIEnv* env, |
|
|
jobject thiz, |
|
|
jstring prompt, |
|
|
jint max_tokens, |
|
|
jfloat temperature, |
|
|
jfloat top_p, |
|
|
jint top_k, |
|
|
jobject callback |
|
|
) { |
|
|
if (!g_context || !g_context->loaded) { |
|
|
LOGE("Model not initialized"); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
g_callback = env->NewGlobalRef(callback); |
|
|
jclass callback_class = env->GetObjectClass(callback); |
|
|
g_callback_method = env->GetMethodID(callback_class, "onToken", "(Ljava/lang/String;)V"); |
|
|
|
|
|
const char* prompt_str = env->GetStringUTFChars(prompt, nullptr); |
|
|
|
|
|
g_context->generating = true; |
|
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> demo_tokens = { |
|
|
"Hello", "!", " ", "I", "'m", " ", "Mind2", ",", |
|
|
" ", "a", " ", "lightweight", " ", "AI", " ", "assistant", "." |
|
|
}; |
|
|
|
|
|
for (const auto& token : demo_tokens) { |
|
|
if (!g_context->generating) break; |
|
|
stream_token(token); |
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50)); |
|
|
} |
|
|
|
|
|
|
|
|
jmethodID complete_method = env->GetMethodID(callback_class, "onComplete", "()V"); |
|
|
if (complete_method) { |
|
|
env->CallVoidMethod(callback, complete_method); |
|
|
} |
|
|
|
|
|
env->ReleaseStringUTFChars(prompt, prompt_str); |
|
|
env->DeleteGlobalRef(g_callback); |
|
|
g_callback = nullptr; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JNIEXPORT void JNICALL |
|
|
Java_com_minimind_mind2_Mind2Model_nativeStop( |
|
|
JNIEnv* env, |
|
|
jobject thiz |
|
|
) { |
|
|
if (g_context) { |
|
|
g_context->generating = false; |
|
|
LOGI("Generation stopped"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JNIEXPORT void JNICALL |
|
|
Java_com_minimind_mind2_Mind2Model_nativeRelease( |
|
|
JNIEnv* env, |
|
|
jobject thiz |
|
|
) { |
|
|
if (g_context) { |
|
|
std::lock_guard<std::mutex> lock(g_context->mutex); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
g_context->loaded = false; |
|
|
LOGI("Mind2 resources released"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JNIEXPORT jstring JNICALL |
|
|
Java_com_minimind_mind2_Mind2Model_nativeGetInfo( |
|
|
JNIEnv* env, |
|
|
jobject thiz |
|
|
) { |
|
|
if (!g_context) { |
|
|
return env->NewStringUTF("{}"); |
|
|
} |
|
|
|
|
|
char info[512]; |
|
|
snprintf(info, sizeof(info), |
|
|
"{\"loaded\": %s, \"model\": \"%s\", \"n_ctx\": %d, \"n_threads\": %d}", |
|
|
g_context->loaded ? "true" : "false", |
|
|
g_context->model_path.c_str(), |
|
|
g_context->n_ctx, |
|
|
g_context->n_threads |
|
|
); |
|
|
|
|
|
return env->NewStringUTF(info); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JNIEXPORT jfloat JNICALL |
|
|
Java_com_minimind_mind2_Mind2Model_nativeBenchmark( |
|
|
JNIEnv* env, |
|
|
jobject thiz, |
|
|
jint n_tokens |
|
|
) { |
|
|
if (!g_context || !g_context->loaded) { |
|
|
return 0.0f; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
float tokens_per_second = 25.0f + (rand() % 10); |
|
|
|
|
|
LOGI("Benchmark: %.1f tokens/sec", tokens_per_second); |
|
|
return tokens_per_second; |
|
|
} |
|
|
|
|
|
} |
|
|
|