Skip to content
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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ add_library(engine_core OBJECT
src/framework/runtime/host_ops.cpp
src/framework/runtime/kv_cache.cpp
src/framework/runtime/bounded_static_kv_decode.cpp
src/framework/runtime/greedy_qwen_decoder.cpp
src/framework/runtime/options.cpp
src/framework/runtime/session_base.cpp
src/framework/runtime/workspace.cpp
Expand Down Expand Up @@ -500,6 +501,7 @@ add_library(engine_core OBJECT
src/framework/modules/optimizations/fast_kv_modules.cpp
src/framework/tokenizers/hf_tokenizer_json.cpp
src/framework/tokenizers/llama_bpe.cpp
src/framework/tokenizers/qwen_bpe_bundle.cpp
external/llama_tokenizer/bpe-core.cpp
external/llama_tokenizer/unicode.cpp
external/llama_tokenizer/unicode-data.cpp
Expand Down
11 changes: 6 additions & 5 deletions include/engine/community_models/audio8_asr/thinker.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

#include "engine/framework/assets/tensor_source.h"
#include "engine/framework/core/execution_context.h"
#include "engine/framework/runtime/greedy_qwen_decoder.h"
#include "engine/community_models/audio8_asr/types.h"

#include <cstddef>
#include <memory>

namespace engine::community_models::audio8_asr {

// Greedy causal decoder for the Audio8 8-layer Qwen2-style LM. Audio
// embeddings are injected into the token embedding sequence at the prompt's
// audio placeholder positions before prefill.
// The Audio8 8-layer Qwen2-style decoder, expressed through the framework's
// shared greedy Qwen decoder runtime (prefill with audio-embedding injection
// plus static-cache step decode). Owns only the family-specific spec.
class Audio8ThinkerRuntime {
public:
Audio8ThinkerRuntime(
Expand All @@ -33,8 +34,8 @@ class Audio8ThinkerRuntime {
const Audio8ASRGenerationOptions & options);

private:
struct Impl;
std::unique_ptr<Impl> impl_;
runtime::GreedyQwenDecoderRuntime runtime_;
std::shared_ptr<const Audio8ASRDecoderConfig> config_;
};

} // namespace engine::community_models::audio8_asr
72 changes: 72 additions & 0 deletions include/engine/framework/runtime/greedy_qwen_decoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#pragma once

#include "engine/framework/assets/tensor_source.h"
#include "engine/framework/core/execution_context.h"
#include "engine/framework/modules/transformers/qwen_causal_decoder.h"

#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>

namespace engine::runtime {

// Specification for a greedy Qwen-family causal decoder: how to find its
// tensors in a weight source and how the shared QwenCausalDecoder stack is
// configured. Covers Qwen2-style decoders (attention biases, no Q/K norms,
// as in Audio8-ASR) and Qwen3-style decoders (Q/K norms, no attention
// biases, as in the qwen3_asr thinker), with separate or packed QKV
// projections and tied or separate LM heads.
struct GreedyQwenDecoderSpec {
modules::QwenCausalDecoderConfig decoder;
int64_t vocab_size = 0;
int64_t max_position_embeddings = 0;
bool tie_word_embeddings = false;
bool attention_bias = false;
bool packed_qkv = false;
std::string token_embedding_tensor;
std::string lm_head_tensor; // used when !tie_word_embeddings
std::string final_norm_tensor;
std::string layer_prefix; // e.g. "language_model.model.layers"
std::vector<int64_t> eos_token_ids;
};

// Greedy autoregressive decoding over a Qwen-style decoder stack: prefill
// with optional audio-embedding injection (ggml_set_rows at prompt
// positions) and static-cache step decode, hiding the graph lifetime and
// K/V state handoff that model families otherwise duplicate.
class GreedyQwenDecoderRuntime {
public:
struct Injection {
std::vector<float> values; // tokens * hidden, token-major
int64_t tokens = 0;
std::vector<int32_t> positions; // prompt positions to replace
};

struct Prompt {
std::vector<int32_t> input_ids;
Injection injection; // optional
};

GreedyQwenDecoderRuntime(
std::shared_ptr<const assets::TensorSource> weights_source,
const GreedyQwenDecoderSpec & spec,
core::ExecutionContext & execution,
size_t prefill_graph_arena_bytes,
size_t decode_graph_arena_bytes,
size_t weight_context_bytes,
assets::TensorStorageType weight_storage_type);
~GreedyQwenDecoderRuntime();

GreedyQwenDecoderRuntime(const GreedyQwenDecoderRuntime &) = delete;
GreedyQwenDecoderRuntime & operator=(const GreedyQwenDecoderRuntime &) = delete;

std::vector<int32_t> generate(const Prompt & prompt, int64_t max_new_tokens);

private:
struct Impl;
std::unique_ptr<Impl> impl_;
};

} // namespace engine::runtime
22 changes: 22 additions & 0 deletions include/engine/framework/tokenizers/qwen_bpe_bundle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include "engine/framework/assets/resource_bundle.h"
#include "engine/framework/tokenizers/llama_bpe.h"

#include <memory>
#include <string_view>

namespace engine::tokenizers {

// Load the Qwen2-pretokenized BPE tokenizer referenced by a model bundle:
// tokenizer_config.json plus vocab.json/merges.txt or tokenizer.json.
std::shared_ptr<LlamaBpeTokenizer> load_qwen_bpe_tokenizer(
const engine::assets::ResourceBundle & bundle);

// Look up a special token's id from the bundle's tokenizer.json
// added_tokens list; throws when the token is absent.
int64_t require_added_token_id(
const engine::assets::ResourceBundle & bundle,
std::string_view content);

} // namespace engine::tokenizers
38 changes: 7 additions & 31 deletions src/community_models/audio8_asr/assets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "engine/framework/assets/tensor_source.h"
#include "engine/framework/io/json.h"
#include "engine/framework/model_spec/package.h"
#include "engine/framework/tokenizers/qwen_bpe_bundle.h"

#include <algorithm>
#include <stdexcept>
Expand Down Expand Up @@ -126,19 +127,6 @@ qwen3_asr::Qwen3ASRAudioEncoderConfig parse_audio_encoder_config(const json::Val
return config;
}

int64_t require_added_token_id(const assets::ResourceBundle & resources, std::string_view content) {
const auto tokenizer = resources.parse_json("tokenizer_json");
for (const auto & item : tokenizer.require("added_tokens").as_array()) {
const auto * token_content = item.find("content");
const auto * token_id = item.find("id");
if (token_content != nullptr && token_content->is_string() &&
token_id != nullptr && token_id->is_number() && token_content->as_string() == content) {
return token_id->as_i64();
}
}
throw std::runtime_error("Audio8 ASR tokenizer.json is missing token: " + std::string(content));
}

Audio8ASRConfig parse_config(const assets::ResourceBundle & resources) {
const auto root = resources.parse_json("config");

Expand Down Expand Up @@ -199,11 +187,11 @@ Audio8ASRConfig parse_config(const assets::ResourceBundle & resources) {

// Prompt special tokens live in tokenizer.json added_tokens; the audio
// token id from the config must match the tokenizer entry.
config.user_token_id = require_added_token_id(resources, "<|user|>");
config.begin_audio_token_id = require_added_token_id(resources, "<|begin_of_audio|>");
config.end_audio_token_id = require_added_token_id(resources, "<|end_of_audio|>");
config.assistant_token_id = require_added_token_id(resources, "<|assistant|>");
config.text_decoder.audio_token_id = require_added_token_id(resources, "<|audio|>");
config.user_token_id = engine::tokenizers::require_added_token_id(resources, "<|user|>");
config.begin_audio_token_id = engine::tokenizers::require_added_token_id(resources, "<|begin_of_audio|>");
config.end_audio_token_id = engine::tokenizers::require_added_token_id(resources, "<|end_of_audio|>");
config.assistant_token_id = engine::tokenizers::require_added_token_id(resources, "<|assistant|>");
config.text_decoder.audio_token_id = engine::tokenizers::require_added_token_id(resources, "<|audio|>");

config.supported_languages = {
"Chinese", "English", "Cantonese", "French", "German", "Japanese", "Korean"};
Expand Down Expand Up @@ -267,19 +255,7 @@ std::shared_ptr<const Audio8ASRAssets> load_audio8_asr_assets(const std::filesys
encoder_assets->model_weights = std::move(encoder_source);
assets->encoder_assets = std::move(encoder_assets);

engine::tokenizers::LlamaBpeTokenizerSpec tokenizer_spec;
tokenizer_spec.tokenizer_config_path = assets->resources.require_file("tokenizer_config");
if (const auto * path = assets->resources.find_file("vocab")) {
tokenizer_spec.vocab_path = *path;
}
if (const auto * path = assets->resources.find_file("merges")) {
tokenizer_spec.merges_path = *path;
}
if (const auto * path = assets->resources.find_file("tokenizer_json")) {
tokenizer_spec.tokenizer_json_path = *path;
}
tokenizer_spec.pre_type = engine::tokenizers::LlamaBpePreTokenizer::Qwen2;
assets->tokenizer = engine::tokenizers::load_llama_bpe_tokenizer(tokenizer_spec);
assets->tokenizer = engine::tokenizers::load_qwen_bpe_tokenizer(assets->resources);
return assets;
}

Expand Down
Loading
Loading