Skip to content
Closed
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
39 changes: 22 additions & 17 deletions bindings/android/app/src/main/cpp/android_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
#define MAX_PATH_LEN 512
namespace geniex_android_sdk {

void throw_runtime_exception(JNIEnv *env, const char *_Nonnull format, ...) {
void throw_runtime_exception(JNIEnv* env, const char* _Nonnull format, ...) {
va_list va;
va_start(va, format);
char errMsg[128];
Expand All @@ -29,12 +29,12 @@ void throw_runtime_exception(JNIEnv *env, const char *_Nonnull format, ...) {
}
}

jobject create_java_profile_data(JNIEnv *env, geniex_ProfileData data) {
jobject create_java_profile_data(JNIEnv* env, geniex_ProfileData data) {
jclass cls = env->FindClass("com/geniex/sdk/bean/ProfilingData");
if (!cls) return nullptr;

// (DDDJJJDDDLjava/lang/String;)V
jmethodID ctor = env->GetMethodID(cls, "<init>", "(DDDJJJDDDLjava/lang/String;)V");
// (DDDJJJDDDJJLjava/lang/String;)V
jmethodID ctor = env->GetMethodID(cls, "<init>", "(DDDJJJDDDJJLjava/lang/String;)V");
if (!ctor) return nullptr;

const auto ttft_ms = static_cast<jdouble>(data.ttft / 1000.0);
Expand All @@ -60,6 +60,9 @@ jobject create_java_profile_data(JNIEnv *env, geniex_ProfileData data) {

LOGd("prefill_speed=%.6f tok/s, decoding_speed=%.6f tok/s, rtf=%.4f", prefill_speed, decoding_speed, rtf);

const auto draft_n_total = static_cast<jlong>(data.draft_n_total);
const auto draft_n_accepted = static_cast<jlong>(data.draft_n_accepted);

jstring jStopReason = env->NewStringUTF(data.stop_reason ? data.stop_reason : "");

jobject obj = env->NewObject(cls,
Expand All @@ -72,15 +75,17 @@ jobject create_java_profile_data(JNIEnv *env, geniex_ProfileData data) {
audio_ms, // J J J
prefill_speed,
decoding_speed,
rtf, // D D D
jStopReason // String
rtf, // D D D
draft_n_total,
draft_n_accepted, // J J
jStopReason // String
);

env->DeleteLocalRef(jStopReason);
return obj;
}

bool check_jni_exception(JNIEnv *env, const char *where) {
bool check_jni_exception(JNIEnv* env, const char* where) {
if (env->ExceptionCheck()) {
LOGe("Exception at %s", where);
env->ExceptionDescribe();
Expand All @@ -98,7 +103,7 @@ bool check_jni_exception(JNIEnv *env, const char *where) {
* @param fieldName
* @return
*/
const char *getStringField(JNIEnv *env, jclass cls, jobject inputObj, const char *fieldName) {
const char* getStringField(JNIEnv* env, jclass cls, jobject inputObj, const char* fieldName) {
jfieldID fid = env->GetFieldID(cls, fieldName, "Ljava/lang/String;");
if (check_jni_exception(env, "GetFieldID failed") || !fid) {
LOGe("field '%s' not found", fieldName);
Expand All @@ -111,7 +116,7 @@ const char *getStringField(JNIEnv *env, jclass cls, jobject inputObj, const char
}
std::string s = jniutils::jstring2str(env, jstr);
env->DeleteLocalRef(jstr);
const char *c = jniutils::hold_c_str(s);
const char* c = jniutils::hold_c_str(s);
LOGd("%s = %s", fieldName, c);
return c;
}
Expand All @@ -124,7 +129,7 @@ const char *getStringField(JNIEnv *env, jclass cls, jobject inputObj, const char
* @param fieldName
* @return
*/
jint getIntField(JNIEnv *env, jclass cls, jobject obj, const char *fieldName) {
jint getIntField(JNIEnv* env, jclass cls, jobject obj, const char* fieldName) {
jfieldID fieldId = env->GetFieldID(cls, fieldName, "Ljava/lang/Integer;");
if (check_jni_exception(env, "GetFieldID failed") || !fieldId) {
LOGe("field '%s' not found", fieldName);
Expand Down Expand Up @@ -155,7 +160,7 @@ jint getIntField(JNIEnv *env, jclass cls, jobject obj, const char *fieldName) {
* @param fieldName
* @return
*/
jfloat getFloatField(JNIEnv *env, jclass cls, jobject obj, const char *fieldName) {
jfloat getFloatField(JNIEnv* env, jclass cls, jobject obj, const char* fieldName) {
jfieldID fieldId = env->GetFieldID(cls, fieldName, "Ljava/lang/Float;");
if (!fieldId) {
LOGe("field '%s' not found", fieldName);
Expand All @@ -178,7 +183,7 @@ jfloat getFloatField(JNIEnv *env, jclass cls, jobject obj, const char *fieldName
return result;
}

jobject getObjectField(JNIEnv *env, jclass cls, jobject obj, const char *name, const char *sig) {
jobject getObjectField(JNIEnv* env, jclass cls, jobject obj, const char* name, const char* sig) {
jfieldID configId = env->GetFieldID(cls, name, sig);
if (check_jni_exception(env, "GetFieldID failed") || !configId) {
LOGe("field '%s' not found", name);
Expand All @@ -194,7 +199,7 @@ jobject getObjectField(JNIEnv *env, jclass cls, jobject obj, const char *name, c
}
}

jboolean getBoolField(JNIEnv *env, jclass cls, jobject obj, const char *fieldName) {
jboolean getBoolField(JNIEnv* env, jclass cls, jobject obj, const char* fieldName) {
jfieldID fieldId = env->GetFieldID(cls, fieldName, "Z");
if (check_jni_exception(env, "GetFieldID failed") || !fieldId) {
LOGe("field '%s' not found", fieldName);
Expand All @@ -213,7 +218,7 @@ jboolean getBoolField(JNIEnv *env, jclass cls, jobject obj, const char *fieldNam
* Get primitive float field (for non-nullable Kotlin Float)
* JNI signature: "F"
*/
jfloat getPrimitiveFloatField(JNIEnv *env, jclass cls, jobject obj, const char *fieldName) {
jfloat getPrimitiveFloatField(JNIEnv* env, jclass cls, jobject obj, const char* fieldName) {
jfieldID fieldId = env->GetFieldID(cls, fieldName, "F");
if (check_jni_exception(env, "GetFieldID failed") || !fieldId) {
LOGe("primitive float field '%s' not found", fieldName);
Expand All @@ -232,7 +237,7 @@ jfloat getPrimitiveFloatField(JNIEnv *env, jclass cls, jobject obj, const char *
* Get primitive int field (for non-nullable Kotlin Int)
* JNI signature: "I"
*/
jint getPrimitiveIntField(JNIEnv *env, jclass cls, jobject obj, const char *fieldName) {
jint getPrimitiveIntField(JNIEnv* env, jclass cls, jobject obj, const char* fieldName) {
jfieldID fieldId = env->GetFieldID(cls, fieldName, "I");
if (check_jni_exception(env, "GetFieldID failed") || !fieldId) {
LOGe("primitive int field '%s' not found", fieldName);
Expand All @@ -247,7 +252,7 @@ jint getPrimitiveIntField(JNIEnv *env, jclass cls, jobject obj, const char *fiel
return result;
}

jobject create_string_list(JNIEnv *env, const char **strings, int count) {
jobject create_string_list(JNIEnv* env, const char** strings, int count) {
jclass array_list_class = env->FindClass("java/util/ArrayList");
jmethodID array_list_constructor = env->GetMethodID(array_list_class, "<init>", "()V");
jmethodID array_list_add = env->GetMethodID(array_list_class, "add", "(Ljava/lang/Object;)Z");
Expand All @@ -265,7 +270,7 @@ jobject create_string_list(JNIEnv *env, const char **strings, int count) {
return list;
}

jobject create_float_list(JNIEnv *env, float *floats, int count) {
jobject create_float_list(JNIEnv* env, float* floats, int count) {
jclass array_list_class = env->FindClass("java/util/ArrayList");
jmethodID array_list_constructor = env->GetMethodID(array_list_class, "<init>", "()V");
jmethodID array_list_add = env->GetMethodID(array_list_class, "add", "(Ljava/lang/Object;)Z");
Expand Down
22 changes: 18 additions & 4 deletions bindings/android/app/src/main/cpp/jniutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,14 +258,23 @@ geniex_ModelConfig extract_model_config(JNIEnv* env, jobject configObj) {
fid = env->GetFieldID(cls, "verbose", "Z");
config.verbose = env->GetBooleanField(configObj, fid);

// spec_draft_model
fid = env->GetFieldID(cls, "spec_draft_model", "Ljava/lang/String;");
jstr = (jstring)env->GetObjectField(configObj, fid);
config.spec_draft_model = jstr ? hold_c_str(jstring2str(env, jstr)) : nullptr;

// spec_n_draft
fid = env->GetFieldID(cls, "spec_n_draft", "I");
config.spec_n_draft = env->GetIntField(configObj, fid);

return config;
}
jobject extract_profiling_data(JNIEnv* env, const geniex_ProfileData& data) {
jclass cls = env->FindClass("com/geniex/sdk/bean/ProfilingData");
if (!cls) return nullptr;

// (DDDJJJDDDLjava/lang/String;)V
jmethodID ctor = env->GetMethodID(cls, "<init>", "(DDDJJJDDDLjava/lang/String;)V");
// (DDDJJJDDDJJLjava/lang/String;)V
jmethodID ctor = env->GetMethodID(cls, "<init>", "(DDDJJJDDDJJLjava/lang/String;)V");
if (!ctor) return nullptr;

const jdouble ttft_ms = static_cast<jdouble>(data.ttft / 1000.0);
Expand All @@ -291,6 +300,9 @@ jobject extract_profiling_data(JNIEnv* env, const geniex_ProfileData& data) {

LOGe("prefill_speed=%.6f tok/s, decoding_speed=%.6f tok/s, rtf=%.4f", prefill_speed, decoding_speed, rtf);

const jlong draft_n_total = static_cast<jlong>(data.draft_n_total);
const jlong draft_n_accepted = static_cast<jlong>(data.draft_n_accepted);

jstring jStopReason = env->NewStringUTF(data.stop_reason ? data.stop_reason : "");

jobject obj = env->NewObject(cls,
Expand All @@ -303,8 +315,10 @@ jobject extract_profiling_data(JNIEnv* env, const geniex_ProfileData& data) {
audio_ms, // J J J
prefill_speed,
decoding_speed,
rtf, // D D D
jStopReason // String
rtf, // D D D
draft_n_total,
draft_n_accepted, // J J
jStopReason // String
);

env->DeleteLocalRef(jStopReason);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,10 @@ data class ModelConfig(
val enable_thinking: Boolean = false,

val verbose: Boolean = false,

/** Path to a draft / MTP model GGUF to enable speculative decoding (llama_cpp only; "" = disabled) */
val spec_draft_model: String = "",

/** Draft tokens per step for speculative decoding (0 = plugin default) */
val spec_n_draft: Int = 0,
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ data class ProfilingData(
val prefillSpeed: Double, /* Prefill speed (tokens/sec) */
val decodingSpeed: Double, /* Decoding speed (tokens/sec) */
val realTimeFactor: Double, /* Real-Time Factor(RTF) (1.0 = real-time, >1.0 = faster, <1.0 = slower) */
val draftNTotal: Long, /* Speculative decoding: draft tokens generated (0 when disabled) */
val draftNAccepted: Long, /* Speculative decoding: draft tokens accepted by the target model */
val stopReason: String /* Stop reason: "eos", "length", "user", "stop_sequence" */
)
9 changes: 9 additions & 0 deletions bindings/go/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ type ProfileData struct {
PrefillSpeed float64
DecodingSpeed float64
RealTimeFactor float64
DraftNTotal int64
DraftNAccepted int64
StopReason string
}

Expand All @@ -46,6 +48,8 @@ func newProfileDataFromCPtr(c C.geniex_ProfileData) ProfileData {
PrefillSpeed: float64(c.prefill_speed),
DecodingSpeed: float64(c.decoding_speed),
RealTimeFactor: float64(c.real_time_factor),
DraftNTotal: int64(c.draft_n_total),
DraftNAccepted: int64(c.draft_n_accepted),
StopReason: C.GoString(c.stop_reason),
}
}
Expand Down Expand Up @@ -163,6 +167,8 @@ type ModelConfig struct {
NGpuLayers int32
ChatTemplatePath string
ChatTemplateContent string
SpecDraftModel string
SpecNDraft int32
}

// fillC writes mc into an embedded C struct; pair with freeCModelConfig to
Expand All @@ -179,6 +185,8 @@ func (mc ModelConfig) fillC(out *C.geniex_ModelConfig) {
n_gpu_layers: C.int32_t(mc.NGpuLayers),
chat_template_path: cStringIfSet(mc.ChatTemplatePath),
chat_template_content: cStringIfSet(mc.ChatTemplateContent),
spec_draft_model: cStringIfSet(mc.SpecDraftModel),
spec_n_draft: C.int32_t(mc.SpecNDraft),
}
}

Expand All @@ -188,6 +196,7 @@ func freeCModelConfig(c *C.geniex_ModelConfig) {
}
cFreeIfSet(unsafe.Pointer(c.chat_template_path))
cFreeIfSet(unsafe.Pointer(c.chat_template_content))
cFreeIfSet(unsafe.Pointer(c.spec_draft_model))
}

// LCOV_EXCL_STOP
4 changes: 4 additions & 0 deletions bindings/python/geniex/_ffi/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class geniex_ProfileData(Structure):
('prefill_speed', c_double),
('decoding_speed', c_double),
('real_time_factor', c_double),
('draft_n_total', c_int64),
('draft_n_accepted', c_int64),
('stop_reason', c_char_p),
]

Expand Down Expand Up @@ -109,6 +111,8 @@ class geniex_ModelConfig(Structure):
('max_tokens', c_int32),
('enable_thinking', c_bool),
('verbose', c_bool),
('spec_draft_model', c_char_p),
('spec_n_draft', c_int32),
]


Expand Down
4 changes: 2 additions & 2 deletions bindings/python/geniex/auto.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,9 @@ def _build_model_config(plugin_id: str | None, n_ctx: int, n_gpu_layers: int, **
_logger.warning('qairt runtime does not consume n_ctx=%d; forcing 0', n_ctx)
n_ctx = 0
cfg = geniex_ModelConfig(n_ctx=n_ctx, n_gpu_layers=n_gpu_layers)
_int_fields = {'n_threads', 'n_threads_batch', 'n_batch', 'n_ubatch', 'n_seq_max', 'max_tokens'}
_int_fields = {'n_threads', 'n_threads_batch', 'n_batch', 'n_ubatch', 'n_seq_max', 'max_tokens', 'spec_n_draft'}
_bool_fields = {'enable_thinking', 'verbose'}
_str_fields = {'chat_template_path', 'chat_template_content', 'system_prompt'}
_str_fields = {'chat_template_path', 'chat_template_content', 'system_prompt', 'spec_draft_model'}
for k, v in kwargs.items():
if k in _int_fields:
setattr(cfg, k, int(v))
Expand Down
7 changes: 6 additions & 1 deletion bindings/python/geniex/generation/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class ProfileData:
generated_tokens: int = 0
prefill_speed: float = 0.0
decode_speed: float = 0.0
draft_n_total: int = 0
draft_n_accepted: int = 0
stop_reason: str | None = None
backend: str | None = None
device: str | None = None
Expand All @@ -47,6 +49,8 @@ def from_c(cls, c: geniex_ProfileData) -> 'ProfileData':
generated_tokens=c.generated_tokens,
prefill_speed=c.prefill_speed,
decode_speed=c.decoding_speed,
draft_n_total=c.draft_n_total,
draft_n_accepted=c.draft_n_accepted,
stop_reason=stop,
)

Expand All @@ -60,7 +64,8 @@ def __repr__(self) -> str:
f'generated_tokens={self.generated_tokens} tok, '
f'prefill_speed={self.prefill_speed:.1f} tok/s, '
f'decode_speed={self.decode_speed:.1f} tok/s, '
f'stop_reason={self.stop_reason}, '
+ (f'draft_accept={self.draft_n_accepted}/{self.draft_n_total}, ' if self.draft_n_total > 0 else '')
+ f'stop_reason={self.stop_reason}, '
f'backend={self.backend}, '
f'device={self.device}, '
f'quant={self.quant}, '
Expand Down
19 changes: 15 additions & 4 deletions cli/cmd/geniex/common/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,10 @@ func (p *Processor) fsmInit() {
{STATE_START, "assistant"}: {STATE_ASSISTANT, nil},

// gemma4
{STATE_ASSISTANT, "<|channel>"}: {STATE_GEMMA_CHANNEL, nil},
{STATE_GEMMA_CHANNEL, "thought"}: {STATE_GEMMA_CHANNEL_THOUGHT, nil},
{STATE_ASSISTANT, "<|channel>"}: {STATE_GEMMA_CHANNEL, nil},
{STATE_GEMMA_CHANNEL, "thought"}: {STATE_GEMMA_CHANNEL_THOUGHT, nil},
{STATE_GEMMA_CHANNEL_THOUGHT, "\n"}: {STATE_THINK, thinkStart(true)},
{STATE_THINK, "<channel|>"}: {STATE_NORMAL, thinkEnd(true)},
{STATE_THINK, "<channel|>"}: {STATE_NORMAL, thinkEnd(true)},
}
p.fsmState = STATE_ASSISTANT
}
Expand Down Expand Up @@ -292,6 +292,13 @@ stop reason: %s
pd.StopReason,
)

if pd.DraftNTotal > 0 {
text += fmt.Sprintf("\ndraft accept: %d/%d (%.1f%%)",
pd.DraftNAccepted,
pd.DraftNTotal,
100.0*float64(pd.DraftNAccepted)/float64(pd.DraftNTotal))
}

} else {
if pd.AudioDuration > 0 { // ASR TTS
text = fmt.Sprintf("processing_time %.2fs | audio_duration %.2fs | RTF %.2f (%.1fx realtime)",
Expand All @@ -301,10 +308,14 @@ stop reason: %s
1.0/pd.RealTimeFactor)

} else if pd.DecodingSpeed != 0 {
text = fmt.Sprintf("— %.1f tok/s • %d tok • %.1f s first token",
text = fmt.Sprintf("— %.1f tok/s • %d tok • %.1f s first token",
pd.DecodingSpeed,
pd.GeneratedTokens,
float64(pd.TTFT)/1e6)
if pd.DraftNTotal > 0 {
text += fmt.Sprintf(" • %.0f%% accept", 100.0*float64(pd.DraftNAccepted)/float64(pd.DraftNTotal))
}
text += " —"

} else {
if pd.TotalTimeUs() != 0 {
Expand Down
Loading
Loading