mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-10-19 03:07:28 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42b70032b1 | ||
|
|
4c1004c492 | ||
|
|
412a363c65 | ||
|
|
2b57148b08 | ||
|
|
8f97d82da3 | ||
|
|
27af58168a | ||
|
|
7db0d3d781 | ||
|
|
99db719bdf | ||
|
|
31288c9c66 | ||
|
|
fe8841a78a | ||
|
|
ff5a233767 | ||
|
|
0878f7c304 | ||
|
|
5aeac37a35 | ||
|
|
547208dca2 | ||
|
|
848bbda904 | ||
|
|
797de63b2f | ||
|
|
9d1878a435 | ||
|
|
c0fd786264 | ||
|
|
2744e48d1e | ||
|
|
10b8b75534 | ||
|
|
9d1e03616e | ||
|
|
f2f667b02d | ||
|
|
62318cd57d | ||
|
|
83f9389419 | ||
|
|
1268edac57 | ||
|
|
f74e2fa6a3 | ||
|
|
ea0c4c2c85 | ||
|
|
6420cf5dd5 | ||
|
|
27c7a769da | ||
|
|
2f2763db6e | ||
|
|
01fc3aae84 | ||
|
|
23f7aabc16 | ||
|
|
7182ef71e6 | ||
|
|
2a667032dc | ||
|
|
f3363593f4 | ||
|
|
ceefd16daa | ||
|
|
9a16f1dbe4 | ||
|
|
b84ea5cf5e | ||
|
|
a30fb4924d | ||
|
|
495c5805ce | ||
|
|
623c16393c | ||
|
|
13b8048920 | ||
|
|
0f4376268f |
@@ -5,6 +5,8 @@ exclude =
|
||||
# Do not traverse examples and tools
|
||||
examples,
|
||||
tools,
|
||||
# Reference scripts for skills
|
||||
skills,
|
||||
# Do not include package initializers
|
||||
__init__.py,
|
||||
# No need to traverse our git directory
|
||||
|
||||
@@ -104,11 +104,11 @@ jobs:
|
||||
name: Test on QDC Device (${{ matrix.device }})
|
||||
needs: [android-ndk-snapdragon, linux-iot-snapdragon]
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 90
|
||||
timeout-minutes: 180
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
device: [SM8750, SM8850, QCS9075M]
|
||||
device: [SM8850, QCS9075M]
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
--pkg-dir pkg-snapdragon/llama.cpp \
|
||||
--model-url "https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q4_0.gguf" \
|
||||
--device ${{ matrix.device }} \
|
||||
${{ startsWith(matrix.device, 'QCS') && '--retries 2 --retry-delay 300' || '' }}
|
||||
${{ startsWith(matrix.device, 'QCS') && '--job-timeout 5400 --retries 2 --retry-delay 300' || '' }}
|
||||
env:
|
||||
QDC_API_KEY: ${{ secrets.QDC_API_KEY }}
|
||||
|
||||
|
||||
@@ -61,8 +61,17 @@ jobs:
|
||||
RELEASE_BRANCH: ${{ github.ref_name }}
|
||||
SKIP_APIABI_CHECK: ${{ github.event.inputs.skip_apiabi_check }}
|
||||
APIABI_COMPARE_TAG: ${{ github.event.inputs.apiabi_compare_tag }}
|
||||
APIABI_REPORT_DIR: ${{ runner.temp }}/apiabi-reports
|
||||
REQUIRE_DOCKER: ${{ github.event.inputs.require_docker }}
|
||||
|
||||
- name: Upload API/ABI reports
|
||||
if: ${{ always() && github.event.inputs.skip_apiabi_check != 'true' }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: apiabi-reports
|
||||
path: ${{ runner.temp }}/apiabi-reports
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Create release tag
|
||||
if: ${{ github.event.inputs.dry_run == 'false' }}
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
Project-specific instructions:
|
||||
|
||||
## Coding
|
||||
|
||||
- In `test-backend-ops.cpp`, do not mention specific backends (e.g. Metal, CUDA) in comments
|
||||
@@ -1,44 +0,0 @@
|
||||
You are a coding agent. Here are some very important rules that you must follow:
|
||||
|
||||
General:
|
||||
- Be very precise and concise when writing code, comments, explanations, etc.
|
||||
- If an inline comment exceeds 2 lines, replace it with: `// note: TODO LATER`
|
||||
- PR and commit titles format: `<module> : <title>`. Lookup recents for examples
|
||||
- Don't try to build or run the code unless you are explicitly asked to do so
|
||||
- Use the `gh` CLI tool when querying PRs, issues, or other GitHub resources
|
||||
- When a model name is needed, first try to get it from the `PI_MODEL_NAME` env var before asking the user
|
||||
- When a hardware description is needed, first try to get it from the `PI_HARDWARE` env var before asking the user
|
||||
- Never read the `AGENTS.md` file
|
||||
|
||||
Coding:
|
||||
- Never write inline comments in the code unless the user tells you to
|
||||
- When in doubt, always refer to the CONTRIBUTING.md file of the project
|
||||
- In `test-backend-ops.cpp`, do not mention specific backends (e.g. Metal, CUDA) in comments
|
||||
- When referencing issues or PRs in comments, use the format:
|
||||
- C/C++ code: `// ref: <url>`
|
||||
- Other (CMake, etc.): `# ref: <url>`
|
||||
|
||||
Pull requests (PRs):
|
||||
- New branch names are prefixed with "gg/"
|
||||
- Before opening a pull request, ask the user to confirm the description
|
||||
- Don't explicitly wrap lines in the PR description (each paragraph and bullet is a single line)
|
||||
- When creating a pull request, look for the repository's PR template and follow it
|
||||
- For the AI usage disclosure section, write "YES", then these 4 bullets:
|
||||
- Harness: `pi:llama.cpp`
|
||||
- Hardware: `<hardware>`
|
||||
- Model: `<model>`, linked to its Hugging Face page - the link text is the plain model name
|
||||
- Stats: `<stats>`
|
||||
- Run `~/.pi/stats.sh "$PI_SESSION_FILE"` to get `<stats>` and write it as
|
||||
`◷ <time> · ↑ <input tokens> · ↓ <output tokens> · ≡ <messages> · ⌨ <user messages>`,
|
||||
e.g. `◷ 12.3m · ↑ 120k · ↓ 45k · ≡ 87 · ⌨ 6`
|
||||
- Write these symbols as literal unicode in the PR description, never `:shortcodes:`, and keep
|
||||
commit trailers ASCII
|
||||
- If `PI_MODEL_NAME` or `PI_HARDWARE` is not set, ask the user and use their answer for `<model>` or `<hardware>`
|
||||
- Always create the pull requests in draft mode
|
||||
- Never reply to review comments or post comments on issues/PRs without explicit permission from the user
|
||||
|
||||
Commits:
|
||||
- On every commit that you make, include an `Assisted-by: pi:llama.cpp/<model> (<hardware>)` tag
|
||||
- Do not explicitly set the git author in commits - rely on the default git config
|
||||
- Always use `--no-gpg-sign` when committing
|
||||
- Never `git push` without explicit confirmation from the user
|
||||
@@ -246,3 +246,6 @@ Chat template and parser:
|
||||
- [PEG parser](docs/development/parsing.md) - alternative to regex that llama.cpp uses to parse model's output
|
||||
- [Auto parser](docs/autoparser.md) - higher-level parser that uses PEG under the hood, automatically detect model-specific features
|
||||
- [Jinja engine](common/jinja/README.md)
|
||||
|
||||
Testing:
|
||||
- [Slice model](skills/slice-model/SKILL.md) - build a small slice (few layers/experts) of a big HF model without downloading the full weights, to test conversion and inference
|
||||
|
||||
@@ -1104,6 +1104,13 @@ static common_chat_params_init_fn common_chat_template_detect_params_init(const
|
||||
return common_chat_params_init_llm_jp_harmony;
|
||||
}
|
||||
|
||||
// Harmony XML tool calls, checked before the GPT-OSS JSON calls
|
||||
if (src.find("<|channel|>") != std::string::npos && src.find("<|constrain|>xml") != std::string::npos &&
|
||||
src.find("rfl:parameter") != std::string::npos) {
|
||||
LOG_DBG("Using specialized template: Harmony XML\n");
|
||||
return common_chat_params_init_harmony_xml;
|
||||
}
|
||||
|
||||
// GPT-OSS - has unique channel-based structure that needs dedicated handler
|
||||
if (src.find("<|channel|>") != std::string::npos) {
|
||||
LOG_DBG("Using specialized template: GPT-OSS\n");
|
||||
|
||||
@@ -1174,6 +1174,7 @@ static const std::map<common_decision_type, std::string> COMMON_DECISION_TYPE_NA
|
||||
{ COMMON_DECISION_TYPE_VLLM_D3, "vllm-d3" },
|
||||
{ COMMON_DECISION_TYPE_LFM2_D1, "lfm2-d1" },
|
||||
{ COMMON_DECISION_TYPE_LFM2_D1_OMNI, "lfm2-d1-omni" },
|
||||
{ COMMON_DECISION_TYPE_GRANITE_SWITCH, "granite-switch"},
|
||||
// for future models, ALWAYS add org name prefix to avoid name collisions, e.g. "orgname-modelname"
|
||||
};
|
||||
|
||||
|
||||
@@ -970,6 +970,7 @@ enum common_decision_type {
|
||||
COMMON_DECISION_TYPE_VLLM_D3, // same as pplx-decider, non-causal attention
|
||||
COMMON_DECISION_TYPE_LFM2_D1, // same as openjev, the labels depend on the question type
|
||||
COMMON_DECISION_TYPE_LFM2_D1_OMNI, // same as laya, other prompt layout
|
||||
COMMON_DECISION_TYPE_GRANITE_SWITCH, // same as openjev, run by the decision aLoRA of granite switch
|
||||
COMMON_DECISION_TYPE_UNKNOWN, // a decision model of a type that is not supported
|
||||
};
|
||||
|
||||
|
||||
+177
-59
@@ -39,6 +39,9 @@
|
||||
// downloader
|
||||
//
|
||||
|
||||
// the suffix of the file a download writes until it completes
|
||||
static const std::string PARTIAL_SUFFIX = ".downloadInProgress";
|
||||
|
||||
static void write_etag(const std::string & path, const std::string & etag) {
|
||||
const std::string etag_path = path + ".etag";
|
||||
fs_write_atomic(std::filesystem::u8path(etag_path), etag);
|
||||
@@ -186,6 +189,13 @@ static bool common_pull_file(httplib::Client & cli,
|
||||
[&](const httplib::Response &response) {
|
||||
if (p.downloaded > 0 && response.status != 206) {
|
||||
LOG_WRN("%s: server did not respond with 206 Partial Content for a resume request. Status: %d\n", func, response.status);
|
||||
if (response.status == 416 || response.status == 200) {
|
||||
// the kept partial cannot be resumed: 416 for a complete or stale one,
|
||||
// 200 from a server that ignores the range; drop it, so the next
|
||||
// attempt starts over instead of wedging on the same answer
|
||||
ofs.close();
|
||||
remove(path_tmp.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (p.downloaded == 0 && response.status != 200) {
|
||||
@@ -214,7 +224,7 @@ static bool common_pull_file(httplib::Client & cli,
|
||||
if (progress_step >= p.total / 1000 || p.downloaded == p.total) {
|
||||
if (callback) {
|
||||
callback->on_update(p);
|
||||
if (callback->is_cancelled()) {
|
||||
if (callback->is_cancelled() && (p.total == 0 || p.downloaded != p.total)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -331,10 +341,24 @@ static int common_download_file_single_online(const std::string & url,
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
const std::string path_temporary = path + ".downloadInProgress";
|
||||
const std::string path_temporary = path + PARTIAL_SUFFIX;
|
||||
int delay = retry_delay_seconds;
|
||||
|
||||
if (!skip_etag && !etag.empty()) {
|
||||
if (std::filesystem::exists(path_temporary) && read_etag(path_temporary) != etag) {
|
||||
LOG_WRN("%s: partial file does not match the server etag, discarding it: %s\n", __func__, path_temporary.c_str());
|
||||
if (remove(path_temporary.c_str()) != 0) {
|
||||
LOG_ERR("%s: unable to delete file: %s\n", __func__, path_temporary.c_str());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
write_etag(path_temporary, etag);
|
||||
}
|
||||
|
||||
if (opts.callback) {
|
||||
// report the resume offset, so a resumed file does not start at 0
|
||||
p.downloaded = supports_ranges && std::filesystem::exists(path_temporary) ?
|
||||
std::filesystem::file_size(path_temporary) : 0;
|
||||
opts.callback->on_start(p);
|
||||
}
|
||||
|
||||
@@ -372,6 +396,7 @@ static int common_download_file_single_online(const std::string & url,
|
||||
}
|
||||
if (!etag.empty() && !skip_etag) {
|
||||
write_etag(path, etag);
|
||||
remove((path_temporary + ".etag").c_str());
|
||||
}
|
||||
success = true;
|
||||
break;
|
||||
@@ -381,12 +406,7 @@ static int common_download_file_single_online(const std::string & url,
|
||||
if (opts.callback) {
|
||||
opts.callback->on_done(p, success);
|
||||
}
|
||||
if (opts.callback && opts.callback->is_cancelled() &&
|
||||
std::filesystem::exists(path_temporary)) {
|
||||
if (remove(path_temporary.c_str()) != 0) {
|
||||
LOG_ERR("%s: unable to delete temporary file: %s\n", __func__, path_temporary.c_str());
|
||||
}
|
||||
}
|
||||
// a stopped download keeps path_temporary, so the next attempt resumes from it
|
||||
if (!success) {
|
||||
LOG_ERR("%s: download failed after %d attempts\n", __func__, max_attempts);
|
||||
return -1; // max attempts reached
|
||||
@@ -594,33 +614,65 @@ static hf_cache::hf_file find_best_sibling(const hf_cache::hf_files & files,
|
||||
return best;
|
||||
}
|
||||
|
||||
static hf_cache::hf_file find_best_mmproj(const hf_cache::hf_files & files,
|
||||
const std::string & model) {
|
||||
return find_best_sibling(files, model, "mmproj");
|
||||
static const std::vector<common_download_sidecar> sidecars = {
|
||||
{ "mmproj", "mmproj", false, &common_download_opts::download_mmproj, &common_download_hf_plan::mmproj },
|
||||
{ "mtp", "mtp-", true, &common_download_opts::download_mtp, &common_download_hf_plan::mtp },
|
||||
{ "dflash", "dflash-", true, &common_download_opts::download_dflash, &common_download_hf_plan::dflash },
|
||||
{ "eagle3", "eagle3-", true, &common_download_opts::download_eagle3, &common_download_hf_plan::eagle3 },
|
||||
{ "dspark", "dspark-", true, &common_download_opts::download_dspark, &common_download_hf_plan::dspark },
|
||||
};
|
||||
|
||||
const std::vector<common_download_sidecar> & common_download_sidecars() {
|
||||
return sidecars;
|
||||
}
|
||||
|
||||
static hf_cache::hf_file find_best_mtp(const hf_cache::hf_files & files,
|
||||
const std::string & model,
|
||||
const std::string & tag = "") {
|
||||
return find_best_sibling(files, model, "mtp-", tag);
|
||||
bool common_download_opts_add_sidecar(common_download_opts & opts, const std::string & kind) {
|
||||
for (const auto & s : sidecars) {
|
||||
if (kind == s.name) {
|
||||
opts.*s.opt = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static hf_cache::hf_file find_best_eagle3(const hf_cache::hf_files & files,
|
||||
const std::string & model,
|
||||
const std::string & tag = "") {
|
||||
return find_best_sibling(files, model, "eagle3-", tag);
|
||||
std::vector<std::string> common_download_plan_sidecars(const common_download_hf_plan & plan) {
|
||||
std::vector<std::string> out;
|
||||
for (const auto & s : sidecars) {
|
||||
if (!(plan.*s.file).path.empty()) {
|
||||
out.emplace_back(s.name);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static hf_cache::hf_file find_best_dflash(const hf_cache::hf_files & files,
|
||||
const std::string & model,
|
||||
const std::string & tag = "") {
|
||||
return find_best_sibling(files, model, "dflash-", tag);
|
||||
std::vector<common_download_partial> common_download_plan_partials(const common_download_hf_plan & plan) {
|
||||
std::vector<common_download_partial> out;
|
||||
|
||||
// a file in the cache resolves to its snapshot entry, a file still to download to its blob
|
||||
auto add = [&](const std::string & kind, const hf_cache::hf_file & f) {
|
||||
if (!f.local_path.empty() && f.local_path != f.final_path) {
|
||||
out.push_back({ kind, f.local_path + PARTIAL_SUFFIX });
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto & f : plan.model_files) {
|
||||
add("", f);
|
||||
}
|
||||
for (const auto & s : sidecars) {
|
||||
add(s.name, plan.*s.file);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static hf_cache::hf_file find_best_dspark(const hf_cache::hf_files & files,
|
||||
const std::string & model,
|
||||
const std::string & tag = "") {
|
||||
return find_best_sibling(files, model, "dspark-", tag);
|
||||
// true when `name` carries the keyword of a sidecar kind
|
||||
static bool name_is_sidecar(const std::string & name) {
|
||||
for (const auto & s : sidecars) {
|
||||
if (name.find(s.keyword) != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool gguf_filename_is_model(const std::string & filepath) {
|
||||
@@ -633,12 +685,7 @@ static bool gguf_filename_is_model(const std::string & filepath) {
|
||||
filename = filename.substr(pos + 1);
|
||||
}
|
||||
|
||||
return filename.find("mmproj") == std::string::npos &&
|
||||
filename.find("imatrix") == std::string::npos &&
|
||||
filename.find("mtp-") == std::string::npos &&
|
||||
filename.find("eagle3-") == std::string::npos &&
|
||||
filename.find("dflash-") == std::string::npos &&
|
||||
filename.find("dspark-") == std::string::npos;
|
||||
return filename.find("imatrix") == std::string::npos && !name_is_sidecar(filename);
|
||||
}
|
||||
|
||||
static hf_cache::hf_file find_best_model(const hf_cache::hf_files & files,
|
||||
@@ -730,8 +777,11 @@ common_download_hf_plan common_download_get_hf_plan(const common_params_model &
|
||||
}
|
||||
} else {
|
||||
primary = find_best_model(all, tag);
|
||||
// a requested sidecar can resolve on its own, without a full model of the same tag
|
||||
if (primary.path.empty() && !opts.download_mtp && !opts.download_dflash && !opts.download_eagle3 && !opts.download_dspark) {
|
||||
// a requested draft can resolve on its own, without a full model of the same tag
|
||||
const bool want_draft = std::any_of(sidecars.begin(), sidecars.end(), [&](const common_download_sidecar & s) {
|
||||
return s.draft && opts.*s.opt;
|
||||
});
|
||||
if (primary.path.empty() && !want_draft) {
|
||||
LOG_ERR("%s: no GGUF files found in repository %s\n", __func__, repo.c_str());
|
||||
list_available_gguf_files(all);
|
||||
return plan;
|
||||
@@ -743,24 +793,22 @@ common_download_hf_plan common_download_get_hf_plan(const common_params_model &
|
||||
plan.model_files = get_split_files(all, primary);
|
||||
}
|
||||
|
||||
if (opts.download_mmproj && !primary.path.empty()) {
|
||||
plan.mmproj = find_best_mmproj(all, primary.path);
|
||||
}
|
||||
if (opts.download_mtp) {
|
||||
plan.mtp = find_best_mtp(all, primary.path, tag);
|
||||
}
|
||||
if (opts.download_dflash) {
|
||||
plan.dflash = find_best_dflash(all, primary.path, tag);
|
||||
}
|
||||
if (opts.download_eagle3) {
|
||||
plan.eagle3 = find_best_eagle3(all, primary.path, tag);
|
||||
}
|
||||
if (opts.download_dspark) {
|
||||
plan.dspark = find_best_dspark(all, primary.path, tag);
|
||||
// a draft resolves at the tag, the mmproj pairs with the model
|
||||
for (const auto & s : sidecars) {
|
||||
if (!(opts.*s.opt)) {
|
||||
continue;
|
||||
}
|
||||
if (s.draft) {
|
||||
plan.*s.file = find_best_sibling(all, primary.path, s.keyword, tag);
|
||||
} else if (!primary.path.empty()) {
|
||||
plan.*s.file = find_best_sibling(all, primary.path, s.keyword);
|
||||
}
|
||||
}
|
||||
|
||||
if (primary.path.empty() &&
|
||||
plan.mtp.local_path.empty() && plan.dflash.local_path.empty() && plan.eagle3.local_path.empty() && plan.dspark.local_path.empty()) {
|
||||
const bool has_draft = std::any_of(sidecars.begin(), sidecars.end(), [&](const common_download_sidecar & s) {
|
||||
return s.draft && !(plan.*s.file).local_path.empty();
|
||||
});
|
||||
if (primary.path.empty() && !has_draft) {
|
||||
LOG_ERR("%s: no GGUF files found in repository %s\n", __func__, repo.c_str());
|
||||
list_available_gguf_files(all);
|
||||
}
|
||||
@@ -926,6 +974,11 @@ std::string common_docker_resolve_model(const std::string & docker) {
|
||||
}
|
||||
}
|
||||
|
||||
// a cached file that lists a model under its tag: the first split of a model, not a sidecar
|
||||
static bool is_listed_model(const gguf_split_info & split) {
|
||||
return split.index == 1 && !split.tag.empty() && !name_is_sidecar(split.prefix);
|
||||
}
|
||||
|
||||
std::vector<common_cached_model_info> common_list_cached_models() {
|
||||
std::unordered_set<std::string> seen;
|
||||
std::vector<common_cached_model_info> result;
|
||||
@@ -934,12 +987,7 @@ std::vector<common_cached_model_info> common_list_cached_models() {
|
||||
|
||||
for (const auto & f : files) {
|
||||
auto split = get_gguf_split_info(f.path);
|
||||
if (split.index != 1 || split.tag.empty() ||
|
||||
split.prefix.find("mmproj") != std::string::npos ||
|
||||
split.prefix.find("mtp-") != std::string::npos ||
|
||||
split.prefix.find("eagle3-") != std::string::npos ||
|
||||
split.prefix.find("dflash-") != std::string::npos ||
|
||||
split.prefix.find("dspark-") != std::string::npos) {
|
||||
if (!is_listed_model(split)) {
|
||||
continue;
|
||||
}
|
||||
if (seen.insert(f.repo_id + ":" + split.tag).second) {
|
||||
@@ -970,6 +1018,44 @@ std::string common_download_resolve_path(const std::string & hf_repo_with_tag, c
|
||||
return find_best_model(files, tag).local_path;
|
||||
}
|
||||
|
||||
// the files -hf loads from the cache, the model with every sidecar, for each cached
|
||||
// tag of `repo_id` other than `tag`
|
||||
static std::unordered_set<std::string> files_in_use(const std::string & repo_id, const std::string & tag) {
|
||||
common_download_opts opts;
|
||||
opts.offline = true;
|
||||
for (const auto & s : sidecars) {
|
||||
opts.*s.opt = true;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> tags;
|
||||
std::unordered_set<std::string> used;
|
||||
|
||||
for (const auto & f : hf_cache::get_cached_files(repo_id)) {
|
||||
auto split = get_gguf_split_info(f.path);
|
||||
if (!is_listed_model(split) || split.tag == tag || !tags.insert(split.tag).second) {
|
||||
continue;
|
||||
}
|
||||
|
||||
common_params_model model;
|
||||
model.hf_repo = repo_id + ":" + split.tag;
|
||||
|
||||
auto plan = common_download_get_hf_plan(model, opts);
|
||||
|
||||
for (const auto & m : plan.model_files) {
|
||||
used.insert(m.local_path);
|
||||
}
|
||||
for (const auto & s : sidecars) {
|
||||
const auto & sf = plan.*s.file;
|
||||
if (!sf.local_path.empty()) {
|
||||
used.insert(sf.local_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
static bool remove_snapshot_entries(const std::string & repo_id, const std::vector<std::filesystem::path> & to_remove);
|
||||
|
||||
bool common_download_remove(const std::string & hf_repo_with_tag) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -989,11 +1075,13 @@ bool common_download_remove(const std::string & hf_repo_with_tag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// collect snapshot entries whose tag matches
|
||||
// collect snapshot entries whose tag matches, but a file another tag loads stays
|
||||
const auto used = files_in_use(repo_id, tag_upper);
|
||||
|
||||
std::vector<fs::path> to_remove;
|
||||
for (const auto & f : files) {
|
||||
auto split = get_gguf_split_info(f.path);
|
||||
if (split.tag == tag_upper) {
|
||||
if (split.tag == tag_upper && used.find(f.local_path) == used.end()) {
|
||||
to_remove.emplace_back(f.local_path);
|
||||
}
|
||||
}
|
||||
@@ -1002,6 +1090,13 @@ bool common_download_remove(const std::string & hf_repo_with_tag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return remove_snapshot_entries(repo_id, to_remove);
|
||||
}
|
||||
|
||||
// remove snapshot entries of `repo_id`, then the blobs no remaining entry references
|
||||
static bool remove_snapshot_entries(const std::string & repo_id, const std::vector<std::filesystem::path> & to_remove) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// resolve blob paths from symlinks before deleting snapshot entries
|
||||
std::vector<fs::path> blobs_to_check;
|
||||
for (const auto & p : to_remove) {
|
||||
@@ -1053,3 +1148,26 @@ bool common_download_remove(const std::string & hf_repo_with_tag) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool common_download_remove_sidecars(const std::string & hf_repo_with_tag, const common_download_opts & opts) {
|
||||
common_params_model model;
|
||||
model.hf_repo = hf_repo_with_tag;
|
||||
|
||||
common_download_opts cached = opts;
|
||||
cached.offline = true;
|
||||
|
||||
auto plan = common_download_get_hf_plan(model, cached);
|
||||
|
||||
std::vector<std::filesystem::path> to_remove;
|
||||
for (const auto & s : sidecars) {
|
||||
const auto & f = plan.*s.file;
|
||||
if (!f.local_path.empty()) {
|
||||
to_remove.emplace_back(std::filesystem::u8path(f.local_path));
|
||||
}
|
||||
}
|
||||
if (to_remove.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return remove_snapshot_entries(common_download_split_repo_tag(hf_repo_with_tag).first, to_remove);
|
||||
}
|
||||
|
||||
+38
-1
@@ -104,10 +104,17 @@ std::string common_docker_resolve_model(const std::string & docker);
|
||||
// Remove a cached model from disk
|
||||
// input format: "user/model" or "user/model:tag"
|
||||
// - if tag is omitted, removes the entire repo cache directory
|
||||
// - if tag is present, removes only files matching that tag (and orphaned blobs)
|
||||
// - if tag is present, removes only files matching that tag (and orphaned blobs),
|
||||
// except the files another cached tag of the repo still loads
|
||||
// returns true if anything was removed
|
||||
bool common_download_remove(const std::string & hf_repo_with_tag);
|
||||
|
||||
// remove the cached sidecar files `opts` selects for "user/model:tag", as the cached
|
||||
// plan resolves them, and leave the model files in place; a sidecar file another tag
|
||||
// shares goes for every tag that resolves it
|
||||
// returns true if anything was removed
|
||||
bool common_download_remove_sidecars(const std::string & hf_repo_with_tag, const common_download_opts & opts);
|
||||
|
||||
struct common_download_hf_plan {
|
||||
hf_cache::hf_file primary;
|
||||
hf_cache::hf_files model_files;
|
||||
@@ -119,3 +126,33 @@ struct common_download_hf_plan {
|
||||
hf_cache::hf_file preset; // if set, only this file is downloaded
|
||||
};
|
||||
common_download_hf_plan common_download_get_hf_plan(const common_params_model & model, const common_download_opts & opts);
|
||||
|
||||
// a sidecar kind a download can pull next to its model: mmproj, and the draft heads
|
||||
// of --spec-type draft-<name>
|
||||
struct common_download_sidecar {
|
||||
const char * name; // the kind, as the API names it
|
||||
const char * keyword; // the part of a file name that marks a file of this kind
|
||||
bool draft; // a draft head, resolved at the tag even without a model
|
||||
|
||||
bool common_download_opts::* opt; // the option that requests it
|
||||
hf_cache::hf_file common_download_hf_plan::* file; // the plan file it resolves to
|
||||
};
|
||||
|
||||
// every sidecar kind, the single list the resolution, the listing and the removal follow
|
||||
const std::vector<common_download_sidecar> & common_download_sidecars();
|
||||
|
||||
// select the sidecar `kind` in `opts`; returns false for an unknown kind
|
||||
bool common_download_opts_add_sidecar(common_download_opts & opts, const std::string & kind);
|
||||
|
||||
// the sidecar kinds `plan` resolves a file for
|
||||
std::vector<std::string> common_download_plan_sidecars(const common_download_hf_plan & plan);
|
||||
|
||||
// a file `plan` still has to download, at the path its partial takes while it is
|
||||
// incomplete; kind is the sidecar it belongs to, empty for the model files
|
||||
struct common_download_partial {
|
||||
std::string kind;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
// the partials the download of `plan` writes, for the files not in the cache yet
|
||||
std::vector<common_download_partial> common_download_plan_partials(const common_download_hf_plan & plan);
|
||||
|
||||
+141
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include <cpp-httplib/httplib.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -97,6 +99,118 @@ static common_http_url common_http_parse_url(const std::string & url) {
|
||||
return parts;
|
||||
}
|
||||
|
||||
// match a loopback destination: a remote proxy can never reach the client machine's own
|
||||
// loopback, so these hosts are always contacted directly, before NO_PROXY is consulted
|
||||
// (similar to Go's net/http)
|
||||
static bool common_http_is_loopback(const std::string & host) {
|
||||
auto lower = [](std::string s) {
|
||||
for (auto & c : s) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c += 'a' - 'A';
|
||||
}
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
const std::string h = lower(host);
|
||||
|
||||
if (h == "localhost" || h == "::1" || h == "0.0.0.0") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (h.size() > 10 && h.compare(h.size() - 10, 10, ".localhost") == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// IPv4 literal: exactly four all-digit octets with values 0-255, loopback is 127.0.0.0/8
|
||||
int octets[4] = { 0, 0, 0, 0 };
|
||||
size_t pos = 0;
|
||||
int n = 0;
|
||||
bool complete = false;
|
||||
while (n < 4 && pos <= h.size()) {
|
||||
size_t dot = h.find('.', pos);
|
||||
if (dot == std::string::npos) {
|
||||
dot = h.size();
|
||||
}
|
||||
if (dot == pos || dot - pos > 3) {
|
||||
return false;
|
||||
}
|
||||
int val = 0;
|
||||
for (size_t i = pos; i < dot; i++) {
|
||||
if (h[i] < '0' || h[i] > '9') {
|
||||
return false;
|
||||
}
|
||||
val = val * 10 + (h[i] - '0');
|
||||
}
|
||||
if (val > 255) {
|
||||
return false;
|
||||
}
|
||||
octets[n++] = val;
|
||||
if (dot == h.size()) {
|
||||
complete = (n == 4);
|
||||
break;
|
||||
}
|
||||
pos = dot + 1;
|
||||
}
|
||||
|
||||
return complete && octets[0] == 127;
|
||||
}
|
||||
|
||||
// match a host against a NO_PROXY list: comma separated entries, "*" bypasses the proxy for every host,
|
||||
// an entry matches the host itself or any of its subdomains, a leading dot is optional, ports are ignored
|
||||
static bool common_http_proxy_bypass(const std::string & no_proxy, const std::string & host) {
|
||||
auto lower = [](std::string s) {
|
||||
for (auto & c : s) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c += 'a' - 'A';
|
||||
}
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
const std::string h = lower(host);
|
||||
|
||||
for (size_t pos = 0; pos < no_proxy.size(); ) {
|
||||
size_t end = no_proxy.find(',', pos);
|
||||
if (end == std::string::npos) {
|
||||
end = no_proxy.size();
|
||||
}
|
||||
|
||||
std::string entry = lower(no_proxy.substr(pos, end - pos));
|
||||
pos = end + 1;
|
||||
|
||||
entry.erase(0, entry.find_first_not_of(" \t"));
|
||||
auto last = entry.find_last_not_of(" \t");
|
||||
if (last == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
entry.erase(last + 1);
|
||||
|
||||
if (entry == "*") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (entry.front() == '.') {
|
||||
entry.erase(0, 1);
|
||||
}
|
||||
|
||||
if (entry.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (h == entry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (h.size() > entry.size() && h[h.size() - entry.size() - 1] == '.' &&
|
||||
h.compare(h.size() - entry.size(), entry.size(), entry) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::pair<httplib::Client, common_http_url> common_http_client(const std::string & url) {
|
||||
common_http_url parts = common_http_parse_url(url);
|
||||
|
||||
@@ -123,6 +237,33 @@ static std::pair<httplib::Client, common_http_url> common_http_client(const std:
|
||||
|
||||
cli.set_follow_location(true);
|
||||
|
||||
// Honor HTTP_PROXY / HTTPS_PROXY environment variables (both lower- and upper-case).
|
||||
auto getenv_s = [](const char * upper, const char * lower) -> std::string {
|
||||
const char * val = std::getenv(upper);
|
||||
if (!val) {
|
||||
val = std::getenv(lower);
|
||||
}
|
||||
return val ? val : "";
|
||||
};
|
||||
|
||||
const std::string proxy_url = (parts.scheme == "https")
|
||||
? getenv_s("HTTPS_PROXY", "https_proxy")
|
||||
: getenv_s("HTTP_PROXY", "http_proxy");
|
||||
|
||||
if (!proxy_url.empty() && !common_http_is_loopback(parts.host) &&
|
||||
!common_http_proxy_bypass(getenv_s("NO_PROXY", "no_proxy"), parts.host)) {
|
||||
try {
|
||||
common_http_url proxy = common_http_parse_url(proxy_url);
|
||||
cli.set_proxy(proxy.host, proxy.port);
|
||||
if (!proxy.user.empty()) {
|
||||
cli.set_proxy_basic_auth(proxy.user, proxy.password);
|
||||
}
|
||||
} catch (const std::exception & e) {
|
||||
// fall back to a direct connection, the URL itself is not logged as it can carry credentials
|
||||
LOG_WRN("%s: ignoring malformed proxy URL from the environment: %s\n", __func__, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
return { std::move(cli), std::move(parts) };
|
||||
}
|
||||
|
||||
|
||||
@@ -1299,6 +1299,15 @@ const func_builtins & value_object_t::get_builtins() const {
|
||||
}
|
||||
return result;
|
||||
}},
|
||||
{"list", [](const func_args & args) -> value {
|
||||
args.ensure_vals<value_object>();
|
||||
const auto & obj = args.get_pos(0)->as_ordered_object();
|
||||
auto result = mk_val<value_array>();
|
||||
for (const auto & pair : obj) {
|
||||
result->push_back(pair.first);
|
||||
}
|
||||
return result;
|
||||
}},
|
||||
{"values", [](const func_args & args) -> value {
|
||||
args.ensure_vals<value_object>();
|
||||
const auto & obj = args.get_pos(0)->as_ordered_object();
|
||||
|
||||
@@ -68,7 +68,7 @@ static llama_token try_draft(common_ngram_cache & nc_static, const common_ngram
|
||||
if (part_static_it == nc_static.end()) {
|
||||
return LLAMA_TOKEN_NULL;
|
||||
}
|
||||
const common_ngram_cache_part part_static = part_static_it->second;
|
||||
const common_ngram_cache_part & part_static = part_static_it->second;
|
||||
|
||||
int max_count_static = 0;
|
||||
int sum_count_static = 0;
|
||||
@@ -96,7 +96,7 @@ static llama_token try_draft(common_ngram_cache & nc_static, const common_ngram
|
||||
|
||||
// Try to draft a token from primary cache (context/dynamic), validate with static cache:
|
||||
static llama_token try_draft(
|
||||
common_ngram_cache & nc_primary, const std::vector<common_ngram> & ngrams_primary, common_ngram_cache_part & part_static,
|
||||
common_ngram_cache & nc_primary, const std::vector<common_ngram> & ngrams_primary, const common_ngram_cache_part & part_static,
|
||||
const int * min_sample_size, const int * min_percent) {
|
||||
|
||||
llama_token drafted_token = LLAMA_TOKEN_NULL;
|
||||
@@ -108,7 +108,7 @@ static llama_token try_draft(
|
||||
if (part_primary_it == nc_primary.end()) {
|
||||
continue;
|
||||
}
|
||||
const common_ngram_cache_part part_primary = part_primary_it->second;
|
||||
const common_ngram_cache_part & part_primary = part_primary_it->second;
|
||||
|
||||
int max_count_primary = 0;
|
||||
int max_count_static = 0;
|
||||
@@ -118,7 +118,7 @@ static llama_token try_draft(
|
||||
for (std::pair<llama_token, int> token_count_primary : part_primary) {
|
||||
const llama_token token = token_count_primary.first;
|
||||
|
||||
common_ngram_cache_part::iterator token_count_static_it = part_static.find(token);
|
||||
common_ngram_cache_part::const_iterator token_count_static_it = part_static.find(token);
|
||||
|
||||
const int32_t count_primary = token_count_primary.second;
|
||||
const int32_t count_static = token_count_static_it != part_static.end() ? 100*token_count_static_it->second : 1;
|
||||
@@ -162,11 +162,9 @@ void common_ngram_cache_draft(
|
||||
for (int j = ngram_start_static; j < ngram_start_static + LLAMA_NGRAM_STATIC; ++j) {
|
||||
ngram_static.tokens[j-ngram_start_static] = get_token(inp, draft, j);
|
||||
}
|
||||
common_ngram_cache::iterator part_static_it = nc_static.find(ngram_static);
|
||||
common_ngram_cache_part part_static;
|
||||
if (part_static_it != nc_static.end()) {
|
||||
part_static = part_static_it->second;
|
||||
}
|
||||
static const common_ngram_cache_part part_static_empty;
|
||||
common_ngram_cache::const_iterator part_static_it = nc_static.find(ngram_static);
|
||||
const common_ngram_cache_part & part_static = part_static_it != nc_static.end() ? part_static_it->second : part_static_empty;
|
||||
|
||||
// cd = context + dynamic
|
||||
std::vector<common_ngram> ngrams_cd;
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
#include "parsers.h"
|
||||
|
||||
// Harmony with XML tool calls: <|start|>assistant to=NAME<|channel|>analysis <|constrain|>xml<|message|><rfl:parameter name="KEY">\nVALUE\n</rfl:parameter>
|
||||
common_chat_params common_chat_params_init_harmony_xml(const common_chat_template & tmpl,
|
||||
const autoparser::generation_params & inputs) {
|
||||
common_chat_params data;
|
||||
|
||||
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
|
||||
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
|
||||
data.message_delimiters = {
|
||||
{ COMMON_CHAT_ROLE_ASSISTANT, "<|start|>assistant" },
|
||||
{ COMMON_CHAT_ROLE_USER, "<|start|>user" },
|
||||
{ COMMON_CHAT_ROLE_SYSTEM, "<|start|>developer" },
|
||||
{ COMMON_CHAT_ROLE_SYSTEM, "<|start|>system" },
|
||||
};
|
||||
|
||||
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
|
||||
data.supports_thinking = true;
|
||||
|
||||
data.thinking_start_tag = "<|channel|>analysis<|message|>";
|
||||
data.thinking_end_tags = {"<|end|>"};
|
||||
|
||||
// EOG text must reach the parser: an XML call has no closing tag of its own
|
||||
static const std::vector<std::string> EOG = {
|
||||
"<|call|>", "<|return|>", "<|endoftext|>", "<|im_end|>", "<|fim_pad|>", "<|repo_name|>", "<|file_sep|>",
|
||||
};
|
||||
|
||||
data.preserved_tokens = {
|
||||
"<|channel|>", "<|constrain|>", "<|message|>", "<|start|>", "<|end|>",
|
||||
};
|
||||
data.preserved_tokens.insert(data.preserved_tokens.end(), EOG.begin(), EOG.end());
|
||||
|
||||
if (inputs.has_continuation()) {
|
||||
const auto & msg = inputs.continue_msg;
|
||||
|
||||
data.generation_prompt = "<|start|>assistant<|channel|>analysis<|message|>" + msg.reasoning_content;
|
||||
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
|
||||
data.generation_prompt += "<|end|><|start|>assistant<|channel|>final<|message|>" + msg.render_content();
|
||||
}
|
||||
|
||||
data.prompt.append(data.generation_prompt);
|
||||
}
|
||||
|
||||
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
|
||||
auto has_response_format = !inputs.json_schema.is_null() && inputs.json_schema.is_object();
|
||||
auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
|
||||
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
|
||||
|
||||
static const std::string PARAM_OPEN = "<rfl:parameter name=\"";
|
||||
static const std::string PARAM_CLOSE = "\n</rfl:parameter>";
|
||||
|
||||
data.parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
|
||||
auto start = p.rule("start", p.literal("<|start|>assistant"));
|
||||
|
||||
auto eog = p.choice();
|
||||
for (const auto & tok : EOG) {
|
||||
eog |= p.literal(tok);
|
||||
}
|
||||
auto with_eog = [&](std::vector<std::string> delims) {
|
||||
delims.insert(delims.end(), EOG.begin(), EOG.end());
|
||||
return delims;
|
||||
};
|
||||
|
||||
auto analysis_open = p.literal("<|channel|>analysis<|message|>");
|
||||
auto analysis_body = p.until_one_of(with_eog({ "<|end|>", "<|constrain|><|start|>assistant", "<|start|>assistant" }));
|
||||
auto analysis_close = p.optional(p.literal("<|end|>") | p.literal("<|constrain|>") | eog);
|
||||
if (extract_reasoning) {
|
||||
p.rule("analysis", analysis_open + p.reasoning(analysis_body) + analysis_close);
|
||||
} else {
|
||||
p.rule("analysis", p.atomic(p.content(analysis_open)) + p.content(analysis_body + analysis_close));
|
||||
}
|
||||
auto analysis = p.ref("analysis");
|
||||
|
||||
auto final_msg = p.rule("final",
|
||||
p.literal("<|channel|>final<|message|>") +
|
||||
p.content(p.until_one_of(with_eog({ "<|end|>" }))) +
|
||||
p.optional(p.literal("<|end|>") | eog));
|
||||
|
||||
if (has_response_format) {
|
||||
auto constrain_type = p.chars("[A-Za-z0-9_-]", 1, -1);
|
||||
auto constraint = p.optional(p.space() + p.optional(p.literal("<|constrain|>")) + constrain_type);
|
||||
auto response_format = p.rule("response-format",
|
||||
p.literal("<|channel|>final") + constraint + p.literal("<|message|>") +
|
||||
p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema)) +
|
||||
p.optional(p.literal("<|end|>") | eog));
|
||||
|
||||
return start + p.zero_or_more(analysis + start) + response_format;
|
||||
}
|
||||
|
||||
if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
|
||||
// tolerate a missing closing tag
|
||||
std::vector<std::string> value_ends = with_eog({ PARAM_OPEN, "<|end|>" });
|
||||
std::vector<std::string> string_ends = { PARAM_CLOSE };
|
||||
auto next_open = p.choice();
|
||||
for (const auto & e : value_ends) {
|
||||
string_ends.push_back("\n" + e);
|
||||
next_open |= p.literal(e);
|
||||
}
|
||||
auto arg_close = p.tool_arg_close(p.gbnf(
|
||||
p.literal(PARAM_CLOSE) | (p.literal("\n") + p.peek(p.optional(p.literal("\n")) + next_open)),
|
||||
gbnf_format_literal("\n") + " " + gbnf_format_literal(PARAM_CLOSE.substr(1)) + "?"));
|
||||
auto arg_string = p.rule("rfl-arg-string",
|
||||
p.ac(p.tool_arg_string_value(p.until_one_of(string_ends)) + arg_close, PARAM_CLOSE));
|
||||
auto arg_sep = p.optional(p.literal("\n"));
|
||||
|
||||
std::string call_close_gbnf = gbnf_format_literal("<|end|>");
|
||||
for (const auto & tok : EOG) {
|
||||
call_close_gbnf += " | " + gbnf_format_literal(tok);
|
||||
}
|
||||
auto call_close = p.gbnf(p.literal("<|end|>") | eog | p.peek(p.literal("<|start|>assistant")),
|
||||
"(" + call_close_gbnf + ")?");
|
||||
|
||||
auto tool_choice = p.choice();
|
||||
foreach_function(inputs.tools, [&](size_t tool_index, const json & tool) {
|
||||
const auto & function = tool.at("function");
|
||||
std::string name = function.at("name");
|
||||
|
||||
std::vector<common_peg_parser> arg_rules;
|
||||
foreach_parameter(function, [&](size_t param_index, const common_chat_schema_property & param, const common_chat_schema_document_ptr & doc) {
|
||||
auto rule_name = "tool-" + std::to_string(tool_index) + "-arg-" + std::to_string(param_index);
|
||||
auto arg_open = p.tool_arg_open(p.literal(PARAM_OPEN) + p.tool_arg_name(p.literal(param.name)) + p.literal("\">\n"));
|
||||
|
||||
auto types = param.schema->value_types();
|
||||
auto arg_value = p.eps();
|
||||
if (!types.has(common_chat_schema::TYPE_STRING) &&
|
||||
(types.has(common_chat_schema::TYPE_OBJECT) || types.has(common_chat_schema::TYPE_ARRAY))) {
|
||||
// the schema grammar fixes the key order, and the model writes keys in its own order
|
||||
arg_value = p.tool_arg_json_value(p.json()) + arg_close;
|
||||
} else if (!types.has(common_chat_schema::TYPE_STRING)) {
|
||||
arg_value = p.tool_arg_json_value(p.schema(p.json(), rule_name + "-schema", doc, *param.schema)) + arg_close;
|
||||
} else if (types.is_only(common_chat_schema::TYPE_STRING)) {
|
||||
arg_value = arg_string;
|
||||
} else {
|
||||
// grammar keeps only the string rule; the parser tries JSON first to type the value
|
||||
auto json_value = p.choice();
|
||||
if (types.has(common_chat_schema::TYPE_OBJECT)) {
|
||||
json_value |= p.json_object();
|
||||
}
|
||||
if (types.has(common_chat_schema::TYPE_ARRAY)) {
|
||||
json_value |= p.json_array();
|
||||
}
|
||||
if (types.has(common_chat_schema::TYPE_NUMBER) || types.has(common_chat_schema::TYPE_INTEGER)) {
|
||||
json_value |= p.json_number();
|
||||
}
|
||||
if (types.has(common_chat_schema::TYPE_BOOLEAN)) {
|
||||
json_value |= p.json_bool();
|
||||
}
|
||||
if (types.has(common_chat_schema::TYPE_NULL)) {
|
||||
json_value |= p.json_null();
|
||||
}
|
||||
arg_value = p.gbnf(p.atomic(p.tool_arg_json_value(json_value) + arg_close) | arg_string, "rfl-arg-string");
|
||||
}
|
||||
|
||||
arg_rules.push_back(p.rule(rule_name, p.tool_arg(arg_open + arg_value)));
|
||||
});
|
||||
|
||||
if (arg_rules.empty()) {
|
||||
auto any_name = p.chars("[^\"\\n<]", 1, -1);
|
||||
arg_rules.push_back(p.rule("tool-" + std::to_string(tool_index) + "-arg-any",
|
||||
p.tool_arg(p.tool_arg_open(p.literal(PARAM_OPEN) + p.tool_arg_name(any_name) + p.literal("\">\n")) + arg_string)));
|
||||
}
|
||||
|
||||
auto args = p.zero_or_more(p.choice(arg_rules) + arg_sep);
|
||||
|
||||
auto call = p.tool(
|
||||
p.tool_open(p.literal(" to=") + p.tool_name(p.literal(name)) +
|
||||
p.literal("<|channel|>analysis <|constrain|>xml<|message|>")) +
|
||||
p.tool_args(args) +
|
||||
p.tool_close(call_close));
|
||||
|
||||
tool_choice |= p.rule("tool-" + std::to_string(tool_index), call);
|
||||
});
|
||||
|
||||
auto tool_calls = inputs.parallel_tool_calls
|
||||
? tool_choice + p.zero_or_more(start + tool_choice)
|
||||
: tool_choice;
|
||||
auto tool_call = p.trigger_rule("tool-call", tool_calls);
|
||||
|
||||
if (inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED) {
|
||||
return start + p.zero_or_more(analysis + start) + tool_call;
|
||||
}
|
||||
|
||||
return start + p.zero_or_more(analysis + start) + (tool_call | final_msg | p.content(p.rest()));
|
||||
}
|
||||
|
||||
auto unsolicited = p.rule("unsolicited", p.atomic(p.literal(" to=") + p.rest()));
|
||||
|
||||
// output without a channel header is returned as content instead of failing the request
|
||||
return start + p.zero_or_more(analysis + start) + (final_msg | unsolicited | p.content(p.rest()));
|
||||
});
|
||||
|
||||
if (include_grammar) {
|
||||
data.grammar_lazy = !(has_response_format || (has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED));
|
||||
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
|
||||
data.parser.build_grammar(builder, data.grammar_lazy);
|
||||
});
|
||||
|
||||
if (data.grammar_lazy) {
|
||||
data.grammar_triggers = {
|
||||
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, "^\\s+to$" },
|
||||
{ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, "<\\|start\\|>assistant(\\s+to)" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -59,6 +59,8 @@ common_chat_params common_chat_params_init_gigachat_v3(const common_chat_templat
|
||||
|
||||
common_chat_params common_chat_params_init_gpt_oss(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
|
||||
common_chat_params common_chat_params_init_harmony_xml(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
|
||||
common_chat_params common_chat_params_init_k2_horizon(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
|
||||
common_chat_params common_chat_params_init_kimi_k2(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
|
||||
|
||||
@@ -9,6 +9,7 @@ set(LLAMA_CHAT_PARSERS_SOURCES
|
||||
${CMAKE_CURRENT_LIST_DIR}/gemma4.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/gigachat-v3.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/gpt-oss.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/harmony-xml.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/k2-horizon.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/kimi-k2.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/kimi-k3.cpp
|
||||
|
||||
@@ -24,6 +24,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"ArcticForCausalLM": "arctic",
|
||||
"AudioFlamingo3ForConditionalGeneration": "qwen",
|
||||
"BaiChuanForCausalLM": "baichuan",
|
||||
"BeamForCausalLM": "beam",
|
||||
"BaichuanForCausalLM": "baichuan",
|
||||
"BailingMoeForCausalLM": "bailingmoe",
|
||||
"BailingMoeV2ForCausalLM": "bailingmoe",
|
||||
@@ -145,6 +146,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"JinaBertModel": "bert",
|
||||
"JinaEmbeddingsV5Model": "bert",
|
||||
"K2HorizonForCausalLM": "k2_horizon",
|
||||
"Kolibri1ForCausalLM": "kolibri1",
|
||||
"KORMoForCausalLM": "qwen",
|
||||
"KimiK25ForConditionalGeneration": "deepseek",
|
||||
"KimiK3ForConditionalGeneration": "kimi_k3",
|
||||
|
||||
+4
-1
@@ -1625,7 +1625,7 @@ class TextModel(ModelBase):
|
||||
self.gguf_writer.add_head_count_kv(n_head_kv)
|
||||
logger.info(f"gguf: key-value head count = {n_head_kv}")
|
||||
|
||||
if self.hparams.get("is_causal") is False:
|
||||
if self.hparams.get("is_causal") is False or self.hparams.get("use_bidirectional_attention") is True:
|
||||
self.gguf_writer.add_causal_attention(False)
|
||||
logger.info("gguf: causal attention = False")
|
||||
|
||||
@@ -1872,6 +1872,9 @@ class TextModel(ModelBase):
|
||||
if chkhsh == "f5c965e4198da6e7525072d87d1ecf4c51d981fd486c7c4ec728ef9cec7e03fc":
|
||||
# ref: https://huggingface.co/meta-models/Muse-Spark-Open-816B-A42B
|
||||
res = "gpt-4o"
|
||||
if chkhsh == "6e040dfe72e4b85855588c53acf4909ac4e98e55bc3a33cd7db499180dc42a78":
|
||||
# ref: https://huggingface.co/Aleph-Alpha/Kolibri-1
|
||||
res = "qwen2"
|
||||
if chkhsh == "b3d1dd861f1d4c5c0d2569ce36baf3f90fe8a102db3de50dd71ff860d91be3df":
|
||||
# ref: https://huggingface.co/aari1995/German_Semantic_V3
|
||||
res = "jina-v2-de"
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, Iterable, TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from torch import Tensor
|
||||
|
||||
from .base import ModelBase, TextModel, gguf, logger
|
||||
|
||||
|
||||
@ModelBase.register("BeamForCausalLM")
|
||||
class BeamModel(TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.BEAM
|
||||
|
||||
# reference NoPE query-scale constants; not in config.json
|
||||
NOPE_SCALE_POWER = 0.1
|
||||
NOPE_SCALE_LENGTH = 2048
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
hp = self.hparams
|
||||
rope = hp.get("rope_parameters") or {}
|
||||
if rope.get("full_attention") is not None:
|
||||
raise ValueError("Beam: expected NoPE full-attention layers (rope_parameters.full_attention = null)")
|
||||
if "sliding_attention" in rope:
|
||||
swa_rope = rope["sliding_attention"] or {}
|
||||
else:
|
||||
# flat rope_theta / rope_scaling, already normalized by TextModel
|
||||
swa_rope = {k: v for k, v in self.rope_parameters.items() if k not in ("sliding_attention", "full_attention", "type")}
|
||||
swa_rope.update(self.rope_parameters.get("sliding_attention") or {})
|
||||
if swa_rope.get("rope_type", "default") != "default":
|
||||
raise ValueError(f"Beam: unsupported sliding-attention rope type {swa_rope.get('rope_type')!r}")
|
||||
# full layers are NoPE, so mirror the sliding rope params for the base class
|
||||
swa_rope = {k: v for k, v in swa_rope.items() if not (k == "rope_type" and v == "default")}
|
||||
self.rope_parameters = {"sliding_attention": dict(swa_rope), "full_attention": dict(swa_rope)}
|
||||
|
||||
layer_types = hp["layer_types"]
|
||||
mlp_types = hp["mlp_layer_types"]
|
||||
n_layer = hp["num_hidden_layers"]
|
||||
if len(layer_types) != n_layer or len(mlp_types) != n_layer:
|
||||
raise ValueError("Beam: layer_types / mlp_layer_types must have num_hidden_layers entries")
|
||||
if any(t not in ("sliding_attention", "full_attention") for t in layer_types):
|
||||
raise ValueError(f"Beam: unknown layer type in {layer_types}")
|
||||
if "sliding_attention" not in layer_types:
|
||||
raise ValueError("Beam: expected at least one sliding_attention layer")
|
||||
n_dense = 0
|
||||
while n_dense < n_layer and mlp_types[n_dense] == "dense":
|
||||
n_dense += 1
|
||||
if any(t != "sparse" for t in mlp_types[n_dense:]):
|
||||
raise ValueError("Beam: llama.cpp supports dense MLP layers only as a leading block")
|
||||
self._n_dense_lead = n_dense
|
||||
if hp.get("attention_bias", False):
|
||||
raise ValueError("Beam: attention_bias=True is not supported")
|
||||
self._experts: dict[tuple[int, str], dict[int, Tensor]] = {}
|
||||
|
||||
def set_vocab(self):
|
||||
self._set_vocab_gpt2()
|
||||
# otherwise EOT is guessed from a hash-ordered scan
|
||||
from transformers import AutoTokenizer
|
||||
tok = AutoTokenizer.from_pretrained(self.dir_model)
|
||||
ret_id = tok.convert_tokens_to_ids("<|return|>") # ty: ignore[unresolved-attribute]
|
||||
if isinstance(ret_id, int) and ret_id >= 0:
|
||||
self.gguf_writer.add_eot_token_id(ret_id)
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
hp = self.hparams
|
||||
|
||||
head_dim = hp["head_dim"]
|
||||
self.gguf_writer.add_rope_dimension_count(head_dim)
|
||||
|
||||
self.gguf_writer.add_sliding_window(hp["sliding_window"])
|
||||
self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in hp["layer_types"]])
|
||||
|
||||
self.gguf_writer.add_attn_temperature_scale(self.NOPE_SCALE_POWER)
|
||||
self.gguf_writer.add_attn_temperature_length(self.NOPE_SCALE_LENGTH)
|
||||
|
||||
self.gguf_writer.add_leading_dense_block_count(self._n_dense_lead)
|
||||
self.gguf_writer.add_expert_feed_forward_length(hp["moe_intermediate_size"])
|
||||
self.gguf_writer.add_expert_gating_func(gguf.ExpertGatingFuncType.SIGMOID)
|
||||
self.gguf_writer.add_expert_weights_norm(bool(hp.get("norm_topk_prob", True)))
|
||||
# equivalent to the reference's post-aggregation scaling
|
||||
self.gguf_writer.add_expert_weights_scale(float(hp["routed_scaling_factor"]))
|
||||
|
||||
if (softcap := hp.get("final_logit_softcapping")) is not None:
|
||||
self.gguf_writer.add_final_logit_softcapping(float(softcap))
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
if name.endswith(".self_attn.gate_bias"):
|
||||
name = name.replace(".self_attn.gate_bias", ".self_attn.gate_proj.bias")
|
||||
return super().filter_tensors((name, gen))
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
hp = self.hparams
|
||||
if name.endswith(".self_attn.gate_proj.bias"):
|
||||
assert tuple(data_torch.shape) == (hp["num_attention_heads"],), f"{name}: one gate bias per head expected, got {tuple(data_torch.shape)}"
|
||||
elif name.endswith(".self_attn.gate_proj.weight"):
|
||||
assert tuple(data_torch.shape) == (hp["num_attention_heads"] * hp["head_dim"], hp["hidden_size"]), f"{name}: {tuple(data_torch.shape)}"
|
||||
elif name.endswith(".mlp.gate.e_score_correction.bias"):
|
||||
assert tuple(data_torch.shape) == (hp["num_experts"],), f"{name}: {tuple(data_torch.shape)}"
|
||||
elif name.endswith((".self_attn.q_norm.weight", ".self_attn.k_norm.weight")):
|
||||
assert tuple(data_torch.shape) == (hp["head_dim"],), f"{name}: per-head q/k norm expected, got {tuple(data_torch.shape)}"
|
||||
elif bid is not None and bid < self._n_dense_lead and ".mlp.experts." in name:
|
||||
raise ValueError(f"Beam: expert tensor {name} in a dense layer")
|
||||
if ".mlp.experts." in name:
|
||||
assert bid is not None
|
||||
n_experts = self.hparams["num_experts"]
|
||||
parts = name.split(".")
|
||||
xid = int(parts[parts.index("experts") + 1])
|
||||
proj = parts[parts.index("experts") + 2]
|
||||
if proj not in ("gate_proj", "up_proj", "down_proj"):
|
||||
raise ValueError(f"Beam: unexpected expert tensor {name}")
|
||||
bucket = self._experts.setdefault((bid, proj), {})
|
||||
if xid in bucket:
|
||||
raise ValueError(f"Beam: duplicate expert tensor {name}")
|
||||
bucket[xid] = data_torch
|
||||
if len(bucket) < n_experts:
|
||||
return
|
||||
del self._experts[(bid, proj)]
|
||||
data = torch.stack([bucket[e] for e in range(n_experts)], dim=0)
|
||||
yield from super().modify_tensors(data, f"model.layers.{bid}.mlp.experts.{proj}.weight", bid)
|
||||
return
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
def prepare_tensors(self):
|
||||
super().prepare_tensors()
|
||||
if self._experts:
|
||||
missing = sorted(f"layer {b} {p} ({len(v)} experts)" for (b, p), v in self._experts.items())
|
||||
raise ValueError(f"Beam: unprocessed experts: {missing}")
|
||||
logger.info("Beam: all routed experts stacked")
|
||||
@@ -243,7 +243,7 @@ class DeepseekV2Model(TextModel):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
hparams: dict = ModelBase.load_hparams(self.dir_model, is_mistral_format=False)
|
||||
hparams: dict = ModelBase.load_hparams(self.dir_model, is_mistral_format=self.is_mistral_format)
|
||||
self.origin_hf_arch = hparams.get('architectures', [None])[0]
|
||||
|
||||
# special handling for Deepseek OCR
|
||||
|
||||
+31
-1
@@ -8,7 +8,7 @@ import torch
|
||||
if TYPE_CHECKING:
|
||||
from torch import Tensor
|
||||
|
||||
from .base import MmprojModel, ModelBase, gguf, logger
|
||||
from .base import MmprojModel, ModelBase, gguf, jinja_str_or_json, logger
|
||||
|
||||
from .llama import LlamaModel
|
||||
from .mamba import Mamba2Model
|
||||
@@ -284,10 +284,40 @@ class GraniteSwitchModel(GraniteMoeModel):
|
||||
self.gguf_writer.add_adapter_lora_rank(self._max_lora_rank)
|
||||
self.gguf_writer.add_adapter_token_ids_activate(self.hparams["adapter_token_ids"])
|
||||
self.gguf_writer.add_adapter_token_ids_substitute(self.hparams["adapter_substitute_token_ids"])
|
||||
if self._has_decision_adapter():
|
||||
self.gguf_writer.add_decision_type(gguf.DecisionType.GRANITE_SWITCH)
|
||||
router_gain = float(self.hparams.get("control_token_gain", 15.0))
|
||||
self.gguf_writer.add_adapter_router_gain(router_gain)
|
||||
logger.info("gguf: (graniteswitch) num_adapters=%s max_lora_rank=%s n_slots=%s router_gain=%s", self._n_adapters, self._max_lora_rank, self._n_slots, router_gain)
|
||||
|
||||
def _has_decision_adapter(self) -> bool:
|
||||
return "decision" in self.hparams.get("adapter_names", [])
|
||||
|
||||
def set_vocab(self):
|
||||
super().set_vocab()
|
||||
if self._has_decision_adapter():
|
||||
systemone_template = self._systemone_template()
|
||||
logger.info("System One Template: %s", systemone_template)
|
||||
self.gguf_writer.add_chat_template([{"name": "systemone", "template": systemone_template}])
|
||||
|
||||
def _systemone_template(self) -> str:
|
||||
# prompt of the decision aLoRA: a state turn, then a question turn that opens with the control token
|
||||
# (it replaces the first token of "### DECISION"), the answer is the label token that comes next
|
||||
description = jinja_str_or_json("o.description")
|
||||
option = (
|
||||
"{% if type != 'noul' %}{{ o.label }}: {% if o.description %}" + description + "{% endif %}"
|
||||
"{% elif o.key == 'true' %}A: {% if o.description %}" + description + "{% else %}The statement is true.{% endif %}"
|
||||
"{% else %}B: {% if o.description %}" + description + "{% else %}The statement is false.{% endif %}{% endif %}"
|
||||
)
|
||||
return (
|
||||
"<|start_of_role|>user<|end_of_role|>State:\n" + jinja_str_or_json("state") + "<|end_of_text|>\n"
|
||||
"<|start_of_role|>user<|end_of_role|><|decision|> DECISION\nQuestion:\n" + jinja_str_or_json("instructions")
|
||||
+ "{% if type == 'score' %} Rate along the ordered levels below (lowest first).{% endif %}"
|
||||
"{{ '\n\nOptions:\n' }}"
|
||||
"{% for o in options %}" + option + "{{ '\n' }}{% endfor %}"
|
||||
"{{ '\nReply with exactly one option label.<|end_of_text|>\n<|start_of_role|>assistant<|end_of_role|>' }}"
|
||||
)
|
||||
|
||||
def _lora_a(self, data: Tensor) -> Tensor:
|
||||
# on-disk A: [n_adapters, 1, max_rank, in] -> [n_adapters+1, max_rank, in]
|
||||
a = data.squeeze(1)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .afmoe import AfmoeModel
|
||||
from .base import ModelBase, gguf
|
||||
|
||||
|
||||
@ModelBase.register("Kolibri1ForCausalLM")
|
||||
@ModelBase.example("Aleph-Alpha/Kolibri-1")
|
||||
class Kolibri1Model(AfmoeModel):
|
||||
model_arch = gguf.MODEL_ARCH.KOLIBRI1
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# AfmoeModel reads these keys, this config stores the same values under other names
|
||||
self.hparams["num_shared_experts"] = 1
|
||||
self.hparams["route_norm"] = self.hparams["norm_topk_prob"]
|
||||
self.hparams["intermediate_size"] = self.hparams["moe_intermediate_size"]
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
self.gguf_writer.add_expert_shared_feed_forward_length(self.hparams["shared_expert_intermediate_size"])
|
||||
self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in self.hparams["layer_types"]])
|
||||
self.gguf_writer.add_expert_gating_func(gguf.ExpertGatingFuncType.SIGMOID)
|
||||
+70
-4
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Iterable, TYPE_CHECKING
|
||||
from typing import Callable, Iterable, TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
|
||||
@@ -14,6 +14,54 @@ from .base import ModelBase, TextModel, gguf, logger
|
||||
@ModelBase.example("JetBrains/Mellum2-12B-A2.5B-Base")
|
||||
class MellumModel(TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.MELLUM
|
||||
supports_mtp_export = True
|
||||
|
||||
_n_main_layers: int | None = None
|
||||
|
||||
_experts: list[dict[str, Tensor]] | None = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# number of exported MTP blocks, appended after the trunk layers
|
||||
self.n_nextn = 0 if self.no_mtp else (self.find_hparam(["num_nextn_predict_layers", "num_mtp_layers"], optional=True) or 0)
|
||||
if self.mtp_only and self.n_nextn == 0:
|
||||
raise ValueError("--mtp requested, but the checkpoint has no MTP layers (num_nextn_predict_layers is 0)")
|
||||
if self.n_nextn > 0:
|
||||
if any(t != "sparse" for t in self.hparams.get("mtp_mlp_layer_types") or []):
|
||||
raise ValueError("Mellum MTP layers with dense MLP are not supported")
|
||||
self.block_count += self.n_nextn
|
||||
self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self.block_count)
|
||||
|
||||
def index_tensors(self, remote_hf_model_id: str | None = None):
|
||||
type(self)._n_main_layers = self.hparams["num_hidden_layers"]
|
||||
return super().index_tensors(remote_hf_model_id=remote_hf_model_id)
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
if (titem := super().filter_tensors(item)) is None:
|
||||
return None
|
||||
name, gen = titem
|
||||
|
||||
if name.startswith("mtp."):
|
||||
if cls.no_mtp:
|
||||
return None
|
||||
assert cls._n_main_layers is not None
|
||||
# mtp.layers.{i}.* -> model.layers.{n_main + i}.*, mtp.{fc,norm,...} -> nextn names of the first MTP block
|
||||
remapper = {
|
||||
"fc": "eh_proj",
|
||||
"pre_fc_norm_embedding": "enorm",
|
||||
"pre_fc_norm_hidden": "hnorm",
|
||||
"norm": "shared_head.norm",
|
||||
}
|
||||
parts = name.split(".", 3)
|
||||
if len(parts) == 4 and parts[1] == "layers" and parts[2].isdecimal():
|
||||
name = f"model.layers.{cls._n_main_layers + int(parts[2])}.{parts[3]}"
|
||||
elif len(parts) == 3 and parts[1] in remapper:
|
||||
name = f"model.layers.{cls._n_main_layers}.{remapper[parts[1]]}.{parts[2]}"
|
||||
elif cls.mtp_only and name not in ("model.embed_tokens.weight", "model.norm.weight", "lm_head.weight"):
|
||||
return None
|
||||
|
||||
return name, gen
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
@@ -26,13 +74,31 @@ class MellumModel(TextModel):
|
||||
if (use_sliding_window is True or use_sliding_window is None) and sliding_window is not None:
|
||||
self.gguf_writer.add_sliding_window(sliding_window)
|
||||
logger.info(f"gguf: sliding window = {sliding_window}")
|
||||
self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in self.hparams["layer_types"]])
|
||||
logger.info(f"gguf: sliding window pattern length = {len(self.hparams['layer_types'])}")
|
||||
layer_types = list(self.hparams["layer_types"])
|
||||
# MTP blocks default to sliding window attention, as in transformers
|
||||
if self.n_nextn > 0:
|
||||
layer_types += self.hparams.get("mtp_layer_types") or ["sliding_attention"] * self.n_nextn
|
||||
self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in layer_types])
|
||||
logger.info(f"gguf: sliding window pattern length = {len(layer_types)}")
|
||||
|
||||
if (norm_topk_prob := self.hparams.get("norm_topk_prob")) is not None:
|
||||
self.gguf_writer.add_expert_weights_norm(norm_topk_prob)
|
||||
|
||||
_experts: list[dict[str, Tensor]] | None = None
|
||||
if self.n_nextn > 0:
|
||||
self.gguf_writer.add_nextn_predict_layers(self.n_nextn)
|
||||
|
||||
def prepare_metadata(self, vocab_only: bool):
|
||||
from_dir = self.fname_out.is_dir()
|
||||
super().prepare_metadata(vocab_only=vocab_only)
|
||||
|
||||
if not self.mtp_only or not from_dir:
|
||||
return
|
||||
|
||||
output_type: str = self.ftype.name.partition("_")[2]
|
||||
fname_default: str = gguf.naming_convention(
|
||||
self.metadata.name, self.metadata.basename, self.metadata.finetune,
|
||||
self.metadata.version, size_label=None, output_type=output_type, model_type=None)
|
||||
self.fname_out = self.fname_out.parent / f"mtp-{fname_default}.gguf"
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name.find("experts") != -1:
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import json
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Iterable, TYPE_CHECKING
|
||||
from typing import Any, Iterable, TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
|
||||
@@ -35,6 +35,7 @@ def _load_pplx_decider_hparams(dir_model: Path) -> dict[str, Any]:
|
||||
|
||||
@ModelBase.register("PplxDeciderModel")
|
||||
@ModelBase.example("perplexity-ai/pplx-decider-v1-27b")
|
||||
@ModelBase.example("FINAL-Bench/Darwin-27B-ZTC-v5")
|
||||
class PplxDeciderModel(Qwen3_5TextModel):
|
||||
model_arch = gguf.MODEL_ARCH.QWEN35
|
||||
no_mtp = True # the checkpoint has no MTP head
|
||||
@@ -77,14 +78,6 @@ class PplxDeciderModel(Qwen3_5TextModel):
|
||||
for name in ("choice", "score", "noul"):
|
||||
self.gguf_writer.add_decision_temperature(name, self.hparams["decision"]["temperature"])
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
# the checkpoint is the bare backbone, its text tensors have no "model." prefix
|
||||
if name.startswith("language_model."):
|
||||
name = "model." + name
|
||||
return super().filter_tensors((name, gen))
|
||||
|
||||
def generate_extra_tensors(self) -> Iterable[tuple[str, Tensor]]:
|
||||
yield from super().generate_extra_tensors()
|
||||
from safetensors.torch import load_file
|
||||
@@ -92,7 +85,7 @@ class PplxDeciderModel(Qwen3_5TextModel):
|
||||
# the readout has one row per option label, store it as an LM head that is zero for the other tokens
|
||||
readout = load_file(self.dir_model / "readout.safetensors")["weight"]
|
||||
token_ids = self.hparams["decision"]["token_ids"]
|
||||
n_vocab = self.hparams["text_config"]["vocab_size"]
|
||||
n_vocab = self.hparams["vocab_size"]
|
||||
assert readout.shape[0] == len(token_ids) == len(set(token_ids))
|
||||
lm_head = torch.zeros(n_vocab, readout.shape[1], dtype=readout.dtype)
|
||||
lm_head[token_ids] = readout
|
||||
|
||||
@@ -253,6 +253,11 @@ class Qwen3Model(Qwen2Model):
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
return
|
||||
|
||||
# output embedding projection (voyage-4-nano)
|
||||
if name == "linear.weight":
|
||||
self.gguf_writer.add_embedding_length_out(data_torch.shape[0])
|
||||
name = "dense_2_out.weight"
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
|
||||
|
||||
@@ -191,6 +191,8 @@ pre_computed_hashes = [
|
||||
{"name": "grok-2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/alvarobartt/grok-2-tokenizer", "chkhsh": "66b8d4e19ab16c3bfd89bce5d785fb7e0155e8648708a1f42077cb9fe002c273"},
|
||||
# muse-spark uses the l4_202k tokenizer, whose pre-tokenizer regex is gpt-4o's
|
||||
{"name": "gpt-4o", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/meta-models/Muse-Spark-Open-816B-A42B", "chkhsh": "f5c965e4198da6e7525072d87d1ecf4c51d981fd486c7c4ec728ef9cec7e03fc"},
|
||||
# kolibri1 uses a 128k vocabulary with the qwen2 pre-tokenizer regex
|
||||
{"name": "qwen2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/Aleph-Alpha/Kolibri-1", "chkhsh": "6e040dfe72e4b85855588c53acf4909ac4e98e55bc3a33cd7db499180dc42a78"},
|
||||
# jina-v2-de variants
|
||||
{"name": "jina-v2-de", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/aari1995/German_Semantic_V3", "chkhsh": "b3d1dd861f1d4c5c0d2569ce36baf3f90fe8a102db3de50dd71ff860d91be3df"},
|
||||
{"name": "gpt-2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/evilfreelancer/ruGPT3XL", "chkhsh": "0fe1cf6eda062318a1af7270f3331a85c539a01778ff948e24388e949c5282f4"},
|
||||
|
||||
@@ -48,6 +48,157 @@ Alternatively, users can partition and split the model across multiple virtual s
|
||||
tensor-splitting, or row-splitting modes. For user-facing execution modes and examples, see the
|
||||
[Snapdragon user guide](README.md#multi-device-execution-modes).
|
||||
|
||||
## Tiled Weight Formats
|
||||
|
||||
Weight tensors in the Hexagon backend are repacked from flat row-major GGML layouts into 2D tiles in memory.
|
||||
Tiling groups 2D matrix blocks contiguously:
|
||||
- Eliminates strided row reads when fetching 2D tiles into VTCM via DMA.
|
||||
- Matches native HVX vector width (128 bytes) and HMX systolic array dimensions (32x32).
|
||||
- Repacking happens on the host during model load (`set_tensor()`) or via runtime repacking (`rt-repack`).
|
||||
Implementation: [`htp-repack.cpp`](../../../ggml/src/ggml-hexagon/htp-repack.cpp).
|
||||
|
||||
### Tile Dimensions and Grid Layout
|
||||
|
||||
Each tile represents a submatrix of `N_rows = 32` rows and `K_cols` columns:
|
||||
- Standard types (`Q4_0`, `Q4_1`, `Q8_0`, `IQ4_NL`, `MXFP4`, and K-quants): `K_cols = 32` (`HTP_TENSOR_TILE_N_COLS = 32`).
|
||||
- `Q2_0`: `K_cols = 64` (`HTP_TENSOR_TILE_K_Q2_0 = 64`).
|
||||
- `Q1_0`: `K_cols = 128` (`HTP_TENSOR_TILE_K_Q1_0 = 128`).
|
||||
|
||||
For a weight matrix of logical shape `[ne0, ne1]` (where `ne0 = K`, `ne1 = N`):
|
||||
- `n_k_tiles = ne0 / K_cols`
|
||||
- `n_col_tiles = (ne1 + 31) / 32`
|
||||
- Tile index for column-tile `kt` and row-tile `ct`: `tile_idx = ct * n_k_tiles + kt`
|
||||
- Byte offset in matrix buffer: `tile_offset = tile_idx * tile_size`
|
||||
|
||||
### Block-32 Quantized Formats
|
||||
|
||||
Block-32 formats use a tile size of 32 rows x 32 columns (`K_cols = 32`):
|
||||
|
||||
- **Q4_0** (576 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q4_0`):
|
||||
- **Quants** (bytes 0..511, 512 bytes):
|
||||
Contains 32 rows x 32 columns of 4-bit quants.
|
||||
Divided into 16 column pairs `cp = 0..15` covering columns `2*cp` and `2*cp + 1`.
|
||||
For each column pair `cp`, 32 bytes are stored consecutively for rows `row = 0..31`:
|
||||
`tile[cp * 32 + row]` holds `(quant[row][2*cp + 1] << 4) | quant[row][2*cp]`.
|
||||
Low nibble holds the even column; high nibble holds the odd column.
|
||||
- **Scales** (bytes 512..575, 64 bytes):
|
||||
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31`.
|
||||
|
||||
- **Q8_0** (1088 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q8_0`):
|
||||
- **Quants** (bytes 0..1023, 1024 bytes):
|
||||
Contains 32 rows x 32 columns of signed 8-bit quants (`int8_t`).
|
||||
Divided into 16 column pairs `cp = 0..15` covering columns `col0 = 2*cp` and `col1 = 2*cp + 1`.
|
||||
For each column pair `cp`, 64 bytes store interleaved column elements for each row `row = 0..31`:
|
||||
`tile[cp * 64 + 2 * row + 0] = quant[row][col0]`
|
||||
`tile[cp * 64 + 2 * row + 1] = quant[row][col1]`
|
||||
- **Scales** (bytes 1024..1087, 64 bytes):
|
||||
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31`.
|
||||
|
||||
- **IQ4_NL** (576 bytes per tile, `HTP_TENSOR_TILE_SIZE_IQ4_NL`):
|
||||
- **Quants** (bytes 0..511, 512 bytes):
|
||||
Same nibble transposition and storage layout as `Q4_0`.
|
||||
Contains 32 rows x 32 columns of non-linear 4-bit indices (values 0..15).
|
||||
Divided into 16 column pairs `cp = 0..15`:
|
||||
`tile[cp * 32 + row]` holds `(quant[row][2*cp + 1] << 4) | quant[row][2*cp]`.
|
||||
- **Scales** (bytes 512..575, 64 bytes):
|
||||
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31`.
|
||||
- **Dequantization**:
|
||||
In HVX/HMX kernels, each 4-bit index is looked up in the 16-entry non-linear table (`iq4_nl_to_fp16_lut`) to produce an FP16 value, then multiplied by the row scale `d`.
|
||||
|
||||
- **MXFP4** (544 bytes per tile, `HTP_TENSOR_TILE_SIZE_MXFP4`):
|
||||
- **Quants** (bytes 0..511, 512 bytes):
|
||||
Same nibble transposition and storage layout as `Q4_0`.
|
||||
Contains 32 rows x 32 columns of 4-bit OCP Microscaling FP4 (E2M1) values.
|
||||
Divided into 16 column pairs `cp = 0..15`:
|
||||
`tile[cp * 32 + row]` holds `(quant[row][2*cp + 1] << 4) | quant[row][2*cp]`.
|
||||
- **Scales** (bytes 512..543, 32 bytes):
|
||||
32 unsigned 8-bit values (`uint8_t`), storing one 8-bit biased exponent (E8M0, bias 127) scale `e` per row `row = 0..31`.
|
||||
- **Dequantization**:
|
||||
Kernel converts E8M0 bias to IEEE FP16 bias via `vh = clamp(e - 112, 0, 30) << 10` to form a power-of-two FP16 scale.
|
||||
Each 4-bit FP4 nibble is unpacked via `mxfp4_to_fp16_lut` and multiplied by this scale.
|
||||
|
||||
- **Q4_1** (640 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q4_1`):
|
||||
- **Quants** (bytes 0..511, 512 bytes):
|
||||
Same 4-bit nibble transposition and storage layout as `Q4_0`.
|
||||
- **Scales** (bytes 512..639, 128 bytes):
|
||||
32 pairs of FP16 values (`ggml_half`): `scale[2 * row + 0] = d` (scale) and `scale[2 * row + 1] = m` (min) for each row `row = 0..31`.
|
||||
|
||||
### Q1_0 Format (1-Bit Quantization)
|
||||
|
||||
`Q1_0` packs 128 columns per tile (`K_cols = 128`, `HTP_TENSOR_TILE_K_Q1_0 = 128`):
|
||||
|
||||
- **Tile Layout** (576 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q1_0`):
|
||||
- **Quants** (bytes 0..511, 512 bytes):
|
||||
Contains 32 rows x 128 columns of 1-bit quants (-1 or +1).
|
||||
Divided into 4 quarters (`quarter = 0..3`), where each quarter covers 32 columns in K.
|
||||
Each quarter occupies 128 bytes (1 HVX vector) in `vrmpy` byte order.
|
||||
Within a quarter, columns `kq = 0..31` map into 8 groups `g = kq / 4` (0..7) of 4 consecutive columns:
|
||||
`byte_offset = quarter * 128 + 4 * row + (kq % 4)`
|
||||
`bit = g` (0..7)
|
||||
- **Scales** (bytes 512..575, 64 bytes):
|
||||
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31`.
|
||||
One scale per row applies to all 128 columns (all 4 quarters) of the tile.
|
||||
|
||||
- **HMX Mapping (1-to-4 Tile Expansion)**:
|
||||
- HMX hardware operates strictly on 32x32 FP16 tiles (`HTP_TENSOR_TILE_N_ROWS = 32`, `HTP_TENSOR_TILE_N_COLS = 32`).
|
||||
- During kernel execution, HVX dequantization (`dequantize_tiled_weight_to_fp16_task_q1_0`) expands each 32x128 Q1_0 tile (576 bytes) into 4 contiguous 32x32 FP16 HMX tiles (4 x 2048 bytes = 8192 bytes total) in VTCM.
|
||||
- HMX systolic array processes the 4 resulting 32x32 FP16 tiles over 4 compute steps along K.
|
||||
|
||||
### Q2_0 Format (2-Bit Quantization)
|
||||
|
||||
`Q2_0` packs 64 columns per tile (`K_cols = 64`, `HTP_TENSOR_TILE_K_Q2_0 = 64`):
|
||||
|
||||
- **Tile Layout** (576 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q2_0`):
|
||||
- **Quants** (bytes 0..511, 512 bytes):
|
||||
Contains 32 rows x 64 columns of 2-bit quants (codes 0..3 representing signed values -1..2).
|
||||
Divided into 16 column groups `group = 0..15` of 4 columns each.
|
||||
Stored in `vrmpy` byte order across 4 HVX vectors (vec 0..3):
|
||||
Vector `group >> 2` (0..3) holds `group & 3` at bit offset `2 * (group & 3)`.
|
||||
Byte `4 * row + byte` holds row `row = 0..31` and column byte `0..3`.
|
||||
Vec 0..1 holds the first 32 columns (half 0), vec 2..3 holds the next 32 columns (half 1).
|
||||
- **Scales** (bytes 512..575, 64 bytes):
|
||||
32 FP16 values (`ggml_half`), storing scale `d` for each row `row = 0..31` (at offset `512 + 2 * row`).
|
||||
One scale per row applies to all 64 columns of the tile.
|
||||
|
||||
- **HMX Mapping (1-to-2 Tile Expansion)**:
|
||||
- HMX hardware operates strictly on 32x32 FP16 tiles (`HTP_TENSOR_TILE_N_ROWS = 32`, `HTP_TENSOR_TILE_N_COLS = 32`).
|
||||
- During kernel execution, HVX dequantization (`dequantize_tiled_weight_to_fp16_task_q2_0`) expands each 32x64 Q2_0 tile (576 bytes) into 2 contiguous 32x32 FP16 HMX tiles (2 x 2048 bytes = 4096 bytes total) in VTCM.
|
||||
- HMX systolic array processes the 2 resulting 32x32 FP16 tiles over 2 compute steps along K.
|
||||
|
||||
- **HVX Vector Dot-Product**:
|
||||
- `tiled_vec_dot_q2_0_32x1` and `tiled_vec_dot_q2_0_32x2` unpack 2-bit quants using `unpack_q3_k_low2`, subtract 1, and accumulate against Q8_0 activation tiles using `Q6_Vw_vrmpyacc_VwVbVb`.
|
||||
|
||||
### K-Quants Formats
|
||||
|
||||
In GGML, K-quants pack 256 elements per super-block.
|
||||
On Hexagon, each 256-element super-block splits along K into 8 tiles of 32 columns (`K_cols = 32`).
|
||||
|
||||
Byte ordering: `Q2_K`, `Q3_K`, and `Q6_K` use `vrmpy` byte order within each 128B HVX vector:
|
||||
`k = 0..31` is split into 8 groups `g = k/4` of 4 `k` each; byte `4*r + (k%4)` holds row `r`.
|
||||
|
||||
- **Q2_K** (336 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q2_K`, lossless tiling with same size as `block_q2_K`):
|
||||
- `bytes 0..255` (vec 0..1): unsigned 2-bit quants; vec `m` holds groups `4m..4m+3` at bit offsets 0, 2, 4, 6.
|
||||
- `bytes 256..319`: raw `scales[]` bytes (`min << 4 | sc`): `[0..31]` for k 0..15, `[32..63]` for k 16..31.
|
||||
- `bytes 320..335`: 8 FP16 in tile `j` (0..7) of super-block: `d` (`j < 4`) or `-dmin` (`j >= 4`) for rows `8*(j%4)..8*(j%4)+7`.
|
||||
|
||||
- **Q3_K** (512 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q3_K`):
|
||||
- `bytes 0..255` (vec 0..1): low 2 bits; vec `m` holds groups `4m..4m+3` at bit offsets 0, 2, 4, 6.
|
||||
- `bytes 256..383` (vec 2): sign mask, bit `g` set when `hmask` is 0 (quant = `low2 - 4`).
|
||||
- `bytes 384..511` (vec 3): FP16 scales `d * (scales[] - 32)`: `fp16[0..31]` for k 0..15, `fp16[32..63]` for k 16..31.
|
||||
|
||||
- **Q4_K** (640 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q4_K`, same layout as Q4_1 tile; each 256-k super-block splits into 8 tiles of 32 k):
|
||||
- `bytes 0..511` (vec 0..3): 4-bit quants; byte `32*cp + r` holds `k = 2*cp` (low nibble) and `2*cp + 1` (high nibble).
|
||||
- `bytes 512..639` (vec 4): 32x2 FP16 per row: `(d * sc, -dmin * m)`.
|
||||
|
||||
- **Q5_K** (768 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q5_K`):
|
||||
- `bytes 0..639` (vec 0..4): Q4_K tile (low 4 bits + FP16 scale/min pairs).
|
||||
- `bytes 640..767` (vec 5): 5th-bit plane; byte `l` holds the 8 high bits for lane `l` across vec `i = 0..3` (`bit 2i = lo, 2i+1 = hi`).
|
||||
|
||||
- **Q6_K** (896 bytes per tile, `HTP_TENSOR_TILE_SIZE_Q6_K`):
|
||||
- `bytes 0..511` (vec 0..3): low 4 bits; vec `i` holds group `2i` (low nibble) and `2i+1` (high nibble).
|
||||
- `bytes 512..767` (vec 4..5): high 2 bits; vec `4+m` holds groups `4m..4m+3` at bit offsets 0, 2, 4, 6.
|
||||
- `bytes 768..895` (vec 6): FP16 scales `d * scales[]`: `fp16[0..31]` for k 0..15, `fp16[32..63]` for k 16..31.
|
||||
|
||||
## Op and Kernel Development Guidelines
|
||||
|
||||
Writing high-performance operators for Hexagon requires following specific guidelines.
|
||||
|
||||
@@ -24,3 +24,267 @@ python3 llama-eval.py \
|
||||
--dataset aime2025 --n_cases 240 \
|
||||
--grader-type regex
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
A server started with `--api-key` (or any OpenAI-compatible endpoint behind a
|
||||
token) needs one here too. `--api-key` is sent as `Authorization: Bearer` on
|
||||
every eval request; it falls back to `$LLAMA_API_KEY`, which keeps the secret
|
||||
out of your shell history and off the process list.
|
||||
|
||||
```bash
|
||||
# One key for every server
|
||||
python3 llama-eval.py --server http://localhost:8033 --api-key sk-... ...
|
||||
|
||||
# From the environment
|
||||
export LLAMA_API_KEY=sk-...
|
||||
python3 llama-eval.py --server http://localhost:8033 ...
|
||||
|
||||
# One key per server, in --server order
|
||||
python3 llama-eval.py \
|
||||
--server http://server1:8033,http://server2:8033 \
|
||||
--api-key sk-one,sk-two --threads 16,16 ...
|
||||
```
|
||||
|
||||
The LLM grader authenticates too. It reuses the key of the server it runs on,
|
||||
so `--grader-type llm` without `--grader-server` needs nothing extra; a grader
|
||||
on a separate endpoint takes `--grader-api-key`.
|
||||
|
||||
Endpoints on an opencode host additionally get an `x-opencode-session` header,
|
||||
generated fresh for every task. An agentic episode sends the same id on all of
|
||||
its turns, so opencode sees one session per task instead of one per request;
|
||||
other servers never see the header.
|
||||
|
||||
## Agentic suite
|
||||
|
||||
`--dataset agentic` is a different shape from the other code suites. It runs
|
||||
**SACB** (Simple Agent Coding Benchmark), hosted at
|
||||
[`ilintar/SACB`](https://huggingface.co/datasets/ilintar/SACB) -- 60 tasks over
|
||||
three hand-written repositories, in Python and TypeScript. Rather than
|
||||
answering one prompt, the model is given a small but real repository, a
|
||||
deliberately narrow tool set, and a bug report, and drives its own multi-turn
|
||||
conversation until it declares itself finished. It is then graded by running a
|
||||
test suite it was never allowed to see.
|
||||
|
||||
```bash
|
||||
python3 llama-eval.py \
|
||||
--server http://localhost:8033 --model my-model \
|
||||
--dataset agentic --threads 1 --temperature 0
|
||||
```
|
||||
|
||||
### The tools
|
||||
|
||||
| tool | purpose |
|
||||
|---|---|
|
||||
| `list_files` | enumerate the tree |
|
||||
| `read_file` | read, with 1-based line numbers and optional line-range narrowing |
|
||||
| `search` | regular-expression search across the repository |
|
||||
| `edit_lines` | replace an inclusive line range |
|
||||
| `edit_replace` | replace an exact snippet, which must be unique |
|
||||
| `write_file` | write a whole file |
|
||||
| `lint` | run the language's linter and type checker |
|
||||
| `finish` | declare the change complete |
|
||||
|
||||
Three constraints are deliberate:
|
||||
|
||||
**There is no shell and no way to run the tests.** The only feedback channel is
|
||||
`lint`. A syntax or type error can be driven out mechanically; a logic error has
|
||||
to be reasoned about. A harness that lets an agent iterate against the test
|
||||
suite measures something closer to search than to understanding.
|
||||
|
||||
**There are two edit tools**, one line-addressed and one content-addressed.
|
||||
Which one a model reaches for, and whether it keeps line numbers straight after
|
||||
its own earlier edit, is itself a signal.
|
||||
|
||||
**The tests never touch the working tree.** They are held outside it and copied
|
||||
into a throwaway copy only after the agent has stopped, so they can be neither
|
||||
read nor edited. Every tool path is resolved and confinement-checked against the
|
||||
repository root, so a symlink planted inside the tree cannot reach outside it.
|
||||
|
||||
### Scoring
|
||||
|
||||
A task is **resolved** when every test that was failing now passes *and* nothing
|
||||
that was already passing broke. Both sets are derived by running the tests twice
|
||||
when the corpus is built -- once against the defective tree and once against the
|
||||
reference fix -- never hand-written, so a task whose defect no test exercises is
|
||||
rejected rather than shipped.
|
||||
|
||||
Because many tasks contain several independent defects, the fraction of target
|
||||
tests passed is reported alongside, which separates "fixed two of the three
|
||||
faults" from "changed nothing". Process metrics are reported too: turns, tool
|
||||
calls, rejected calls, edits, and the peak conversation size.
|
||||
|
||||
### Dependencies
|
||||
|
||||
Nothing here is installed unless you select this suite, and then only for the
|
||||
language you actually run. `--agentic-lang python` provisions ruff and mypy;
|
||||
`--agentic-lang typescript` provisions the TypeScript compiler and uses node's
|
||||
own test runner. Neither touches the other, and no other suite touches either.
|
||||
|
||||
### Useful flags
|
||||
|
||||
| flag | meaning |
|
||||
|---|---|
|
||||
| `--agentic-lang` | restrict to one language, and provision only its toolchain |
|
||||
| `--agentic-max-turns` | give up after this many assistant turns (default 50) |
|
||||
| `--agentic-max-tokens` | cap generation per turn (default: no cap); bounds a model that falls into a repetition loop, but also cuts reasoning off mid-thought. Truncated turns are counted as `truncated_turns` |
|
||||
| `--agentic-context-limit` | abandon an episode once its context exceeds this many tokens |
|
||||
| `--agentic-transcripts DIR` | save each episode's full message history to `DIR/<task_id>.json`, plus a `DIR/<task_id>.html` showing every reasoning block, reply, tool call and tool result; the main report links to it |
|
||||
|
||||
|
||||
## Code suites (HumanEval, ClassEval)
|
||||
|
||||
`--dataset humaneval` runs the 164 OpenAI HumanEval problems; `--dataset
|
||||
classeval` runs the 100 ClassEval class-generation tasks. Unlike the other
|
||||
suites these are not graded by matching text: the model's reply is parsed for a
|
||||
```` ```python ```` block, that code is combined with the task's own unit tests,
|
||||
and the result is **executed**.
|
||||
|
||||
ClassEval asks for a whole class rather than one function, so it is scored the
|
||||
way upstream does - **class level** (every one of the task's tests passes, which
|
||||
is what feeds pass@k) and **method level** (what fraction of all 2196 test
|
||||
methods passed). The second number separates "generated nothing usable" from
|
||||
"got most of the class right", and both are printed at the end of a run.
|
||||
|
||||
Give it more room to generate than HumanEval - a class with ten methods does not
|
||||
fit in a short budget:
|
||||
|
||||
```bash
|
||||
python3 llama-eval.py \
|
||||
--server http://localhost:8033 --model my-model \
|
||||
--dataset classeval --threads 8 --n_predict 4096 --temperature 0
|
||||
```
|
||||
|
||||
```bash
|
||||
# pass@1, greedy
|
||||
python3 llama-eval.py \
|
||||
--server http://localhost:8033 --model my-model \
|
||||
--dataset humaneval --threads 8 --n_predict 1024 --temperature 0
|
||||
|
||||
# pass@10 -- each extra multiple of 164 is another sample per problem
|
||||
python3 llama-eval.py \
|
||||
--server http://localhost:8033 --model my-model \
|
||||
--dataset humaneval --n_cases 1640 --temperature 0.8 --top-p 0.95
|
||||
```
|
||||
|
||||
`--grader-type exec` is the default for code suites, so neither it nor
|
||||
`--dataset-source` normally needs to be passed.
|
||||
|
||||
### Execution sandbox - no Docker required
|
||||
|
||||
Generated code is untrusted, but these suites need far less than a container to
|
||||
run safely. **The package set is derived from the dataset's own imports at
|
||||
startup**, not hardcoded and not taken from the upstream `requirements.txt` -
|
||||
which for ClassEval lists `torch`, `openai` and `transformers` that its *tests*
|
||||
never touch. So each suite installs exactly what it needs:
|
||||
|
||||
| suite | third-party packages |
|
||||
|---|---|
|
||||
| HumanEval | **none** - every problem imports only stdlib (`typing, math, random, copy, string, collections, hashlib, re`) |
|
||||
| ClassEval | 13: `beautifulsoup4, gensim, lxml, netifaces, nltk, numpy, openpyxl, pandas, pillow, pypdf2, python-docx, reportlab, scipy` |
|
||||
|
||||
Two of ClassEval's are *declared* rather than derived, because no import
|
||||
statement mentions them: `lxml` is selected by string
|
||||
(`BeautifulSoup(html, 'lxml')`, ClassEval_44 - without it that task silently
|
||||
drops from 23/23 to 7/23), and `scipy` is a hard gensim requirement. Note also
|
||||
that `PyPDF2` must **not** be modernised to `pypdf`: ClassEval_69's test imports
|
||||
`PdfFileReader`, a name pypdf removed.
|
||||
|
||||
The venv is cached under `~/.cache/llama-eval/` (override with
|
||||
`LLAMA_EVAL_CACHE`) and rebuilt only when the package set, interpreter version
|
||||
or provisioning steps change; force one with `--rebuild-sandbox`. `uv` is used
|
||||
when available, which makes even the ClassEval venv build in seconds.
|
||||
|
||||
**Interpreter selection.** A suite can prefer a different Python than the one
|
||||
running the tool, and declares it: ClassEval asks for 3.13, because gensim
|
||||
publishes no wheel past cp313 and its generated C no longer compiles on 3.14
|
||||
(it still uses `PyLongObject.ob_digit`). That interpreter is found automatically,
|
||||
including via `uv python`; `--sandbox-python` overrides it.
|
||||
|
||||
3.14 is fully supported anyway. ClassEval uses exactly two gensim APIs
|
||||
(`utils.decode_htmlentities`, `matutils.unitvec`), both pure Python upstream, so
|
||||
on 3.14+ the suite drops gensim from the install set and provisions a small shim
|
||||
instead. Both paths score **100/100**.
|
||||
|
||||
**Offline provisioning.** A suite that would otherwise fetch data at test time
|
||||
declares a provisioning step that runs once at venv build time, while the
|
||||
network is still up. ClassEval uses this to pre-seed exactly three nltk corpora
|
||||
(`punkt_tab`, `averaged_perceptron_tagger_eng`, `wordnet`, ~33 MB), so the tests
|
||||
themselves run with no network at all. Those are the *current* names - the
|
||||
dataset asks for `punkt` and `averaged_perceptron_tagger`, which nltk has since
|
||||
renamed, and seeding the legacy names too is not harmless: with a network
|
||||
available nltk considers them satisfied and re-fetches the modern ones anyway.
|
||||
The corpora are written *inside* the venv, not `~/nltk_data`, because `$HOME` is
|
||||
a tmpfs under the strongest isolation tier.
|
||||
|
||||
Each candidate program runs in a throwaway directory as its own process group,
|
||||
under the strongest isolation the host offers:
|
||||
|
||||
| tier | requires | isolates |
|
||||
|---|---|---|
|
||||
| `bwrap` | bubblewrap installed | read-only root, tmpfs `/tmp` and `$HOME`, **no network** |
|
||||
| `unshare` | `unshare --map-current-user` | **no network** |
|
||||
| `unshare-root` | any `unshare -r` | no network, but **runs as uid 0** - see below |
|
||||
| `rlimit` | always available | cpu/memory/file-size/process caps only |
|
||||
|
||||
**Candidate code must not run as root**, which is why `unshare -r` is a
|
||||
last-resort tier that prints a warning. Root bypasses file permission bits, so a
|
||||
test asserting that writing to a `chmod 0444` file *fails* instead sees it
|
||||
succeed - ClassEval_50 scores 14/16 under uid 0 and 16/16 under uid 1000, with
|
||||
nothing in the output to suggest the sandbox caused it. Install bubblewrap.
|
||||
|
||||
All tiers additionally cap CPU time, address space (4 GiB), file size and core
|
||||
dumps, and kill the whole process group on timeout (`--exec-timeout`, default
|
||||
15 s - raise it for ClassEval, whose tests do real work). Writes to the
|
||||
throwaway working directory are allowed, which is all the file-handling tasks in
|
||||
ClassEval need.
|
||||
|
||||
Note that the weakest tier does not contain filesystem access. If you are
|
||||
scoring output from a model you do not trust, check the tier printed at startup.
|
||||
|
||||
### Reproducibility
|
||||
|
||||
Several tasks draw their test inputs from the *global, unseeded* RNG, so a
|
||||
verdict is not reproducible run to run - which adds noise exactly when you are
|
||||
comparing two models or two quantisations. `--exec-seed` (default `0`) seeds the
|
||||
RNG before each test; pass `--exec-seed -1` for the stock upstream behaviour.
|
||||
This does not change what the model is asked to compute.
|
||||
|
||||
It matters more than it sounds. HumanEval/38, /50 and /53 are affected, and
|
||||
ClassEval_58 (duplicate mine placement) fails about 30% of the time on its own
|
||||
*reference* solution - measured here at 21/30 passes unseeded versus **30/30
|
||||
seeded**.
|
||||
|
||||
### Dataset fidelity
|
||||
|
||||
The canonical dataset runs **unmodified on current Python** - all 164 reference
|
||||
solutions pass on CPython 3.14 with no patches, so no forked or re-hosted copy
|
||||
is needed. `--dataset-source` accepts a local `.jsonl` or a HuggingFace repo id
|
||||
if you do want to point at a modified copy.
|
||||
|
||||
HumanEval does contain a handful of long-standing defects where the *prompt*
|
||||
contradicts the graded test (notably HumanEval/47, /116 and /148). Those
|
||||
penalise a model that reads the docstring carefully, but correcting them makes
|
||||
the benchmark marginally easier and the resulting scores no longer comparable
|
||||
with published HumanEval numbers, so nothing is patched by default.
|
||||
|
||||
ClassEval is different: the upstream data has defects that cap the achievable
|
||||
score below 100% for reasons unrelated to the model, so `--dataset classeval`
|
||||
defaults to a **patched fork**,
|
||||
[`ilintar/ClassEval`](https://huggingface.co/datasets/ilintar/ClassEval).
|
||||
Six edits over five tasks lift the reference solutions from 96/100 classes and
|
||||
2182/2196 test methods to **100/100 and 2196/2196**. Three repair tests nothing
|
||||
could pass:
|
||||
|
||||
- **ClassEval_17** was a time bomb - a hard-coded 2024 date checked against
|
||||
`datetime.now()`, unpassable since January 2024.
|
||||
- **ClassEval_48** asserted `get_hostname('0.0.0.0') == 'LAPTOP-2CS86KUM'`, the
|
||||
dataset author's own machine name.
|
||||
- **ClassEval_31** compared floats with `assertEqual`, failing on a 4-ULP numpy
|
||||
difference.
|
||||
|
||||
The other three fix only the *reference* solutions (NumPy 2.0 removals in
|
||||
ClassEval_51, a 30%-flaky mine generator in ClassEval_58) and cannot affect a
|
||||
model's score. No `skeleton` was touched, so what the model is asked to write is
|
||||
byte-identical to upstream. Pass `--dataset-source FudanSELab/ClassEval` for the
|
||||
unpatched original.
|
||||
|
||||
@@ -0,0 +1,918 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Multi-turn agentic coding eval: tools over a real working tree.
|
||||
|
||||
The existing code suites are single-shot -- one prompt, one reply, execute,
|
||||
grade. This one is not. The model is given a small but real repository and a
|
||||
deliberately narrow tool set, and has to find its way to the defect before it
|
||||
can fix it. That shape is what puts a run in the tens-of-thousands-of-tokens
|
||||
range without any artificial padding: the context grows because the model
|
||||
chose to read things.
|
||||
|
||||
Three deliberate constraints:
|
||||
|
||||
* No shell, and no way to run the tests. The only feedback channel is `lint`.
|
||||
A syntax or type error can be driven out mechanically; a logic error has to
|
||||
be reasoned about. Suites that let an agent iterate against the test suite
|
||||
measure something closer to search than to understanding.
|
||||
* Two edit tools, one line-addressed and one content-addressed. Which one a
|
||||
model reaches for, and whether it keeps line numbers straight after its own
|
||||
earlier edit, is itself a signal.
|
||||
* Tests never touch the working tree. They are held outside it and copied in
|
||||
after the agent has stopped, so they can be neither read nor edited.
|
||||
|
||||
Everything here is imported lazily by llama-eval.py, and every dependency it
|
||||
needs is scoped to the language actually under test, so none of it is
|
||||
installed for anyone running the other suites.
|
||||
"""
|
||||
|
||||
import html
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
||||
|
||||
MAX_READ_LINES = 400
|
||||
MAX_TOOL_CHARS = 12000
|
||||
MAX_SEARCH_HITS = 60
|
||||
|
||||
|
||||
@dataclass
|
||||
class LangSpec:
|
||||
"""Everything language-specific: how to lint, how to test, what to install.
|
||||
|
||||
Kept as data so adding a language is a corpus concern rather than a harness
|
||||
change, and so a run only ever provisions the toolchain for the language it
|
||||
is actually evaluating.
|
||||
"""
|
||||
name: str
|
||||
exts: Tuple[str, ...]
|
||||
packages: Tuple[str, ...] = ()
|
||||
provision: Tuple[str, ...] = ()
|
||||
env: Dict[str, str] = field(default_factory=dict)
|
||||
address_space_limit: Optional[int] = 4 * 1024 * 1024 * 1024
|
||||
|
||||
def source_files(self, root: Path) -> List[Path]:
|
||||
return sorted(p for p in root.rglob("*")
|
||||
if p.is_file() and p.suffix in self.exts)
|
||||
|
||||
|
||||
PYTHON = LangSpec(
|
||||
name="python",
|
||||
exts=(".py",),
|
||||
packages=("ruff==0.14.2", "mypy==1.18.2"),
|
||||
)
|
||||
|
||||
TYPESCRIPT = LangSpec(
|
||||
name="typescript",
|
||||
exts=(".ts",),
|
||||
address_space_limit=16 * 1024 * 1024 * 1024,
|
||||
provision=(
|
||||
"import os, subprocess, shutil, sys\n"
|
||||
"npm = shutil.which('npm')\n"
|
||||
"if not npm: sys.exit('npm not found on PATH; needed for the typescript suite')\n"
|
||||
"dest = os.path.join(os.environ['SANDBOX_VENV'], 'node')\n"
|
||||
"os.makedirs(dest, exist_ok=True)\n"
|
||||
"r = subprocess.run([npm, 'install', '--silent', '--no-audit', '--no-fund',\n"
|
||||
" '--prefix', dest, 'typescript@5.9.3'],\n"
|
||||
" capture_output=True, text=True)\n"
|
||||
"sys.exit(0 if r.returncode == 0 else (r.stderr or r.stdout)[-2000:])\n",
|
||||
),
|
||||
)
|
||||
|
||||
LANGS: Dict[str, LangSpec] = {"python": PYTHON, "typescript": TYPESCRIPT}
|
||||
|
||||
|
||||
class PathEscape(Exception):
|
||||
"""A tool was handed a path outside the repository."""
|
||||
|
||||
|
||||
class Workspace:
|
||||
"""A private, writable copy of one task's repository.
|
||||
|
||||
Every tool path is resolved through here. Confinement is checked on the
|
||||
fully-resolved path, so a symlink planted inside the tree cannot be used to
|
||||
read or write outside it.
|
||||
"""
|
||||
|
||||
def __init__(self, root: Path):
|
||||
self.root = root.resolve()
|
||||
self.escape_attempts = 0
|
||||
|
||||
def resolve(self, rel: str) -> Path:
|
||||
if rel is None or not str(rel).strip():
|
||||
raise PathEscape("no path given")
|
||||
raw = Path(str(rel))
|
||||
base = raw if raw.is_absolute() else (self.root / raw)
|
||||
candidate = base.resolve()
|
||||
if candidate != self.root and self.root not in candidate.parents:
|
||||
self.escape_attempts += 1
|
||||
raise PathEscape(f"path {rel!r} is outside the repository")
|
||||
return candidate
|
||||
|
||||
def rel(self, path: Path) -> str:
|
||||
return str(path.relative_to(self.root))
|
||||
|
||||
def files(self) -> List[Path]:
|
||||
return sorted(p for p in self.root.rglob("*")
|
||||
if p.is_file() and "__pycache__" not in p.parts)
|
||||
|
||||
def snapshot(self) -> Dict[str, str]:
|
||||
out = {}
|
||||
for p in self.files():
|
||||
try:
|
||||
out[self.rel(p)] = p.read_text()
|
||||
except (UnicodeDecodeError, OSError):
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
def _clip(text: str, limit: int = MAX_TOOL_CHARS) -> str:
|
||||
if len(text) <= limit:
|
||||
return text
|
||||
return text[:limit] + f"\n... [truncated, {len(text) - limit} more characters]"
|
||||
|
||||
|
||||
def _numbered(lines: Sequence[str], start: int) -> str:
|
||||
width = len(str(start + len(lines) - 1))
|
||||
return "\n".join(f"{start + i:>{width}}\t{ln}" for i, ln in enumerate(lines))
|
||||
|
||||
|
||||
def tool_schemas() -> List[Dict[str, Any]]:
|
||||
"""OpenAI-format function schemas for the whole tool set."""
|
||||
def fn(name, desc, props, required=()):
|
||||
return {"type": "function", "function": {
|
||||
"name": name, "description": desc,
|
||||
"parameters": {"type": "object", "properties": props,
|
||||
"required": list(required)}}}
|
||||
|
||||
return [
|
||||
fn("list_files", "List files in the repository.",
|
||||
{"path": {"type": "string",
|
||||
"description": "Directory relative to the repository root. "
|
||||
"Omit for the whole tree."}}),
|
||||
fn("read_file",
|
||||
"Read a file. Output is prefixed with 1-based line numbers. Give "
|
||||
"start_line/end_line to read only part of a large file.",
|
||||
{"path": {"type": "string", "description": "File path relative to the repository root."},
|
||||
"start_line": {"type": "integer", "description": "First line to read, 1-based, inclusive."},
|
||||
"end_line": {"type": "integer", "description": "Last line to read, 1-based, inclusive."}},
|
||||
["path"]),
|
||||
fn("search",
|
||||
"Search the repository with a regular expression. Returns matching "
|
||||
"lines with their file and line number.",
|
||||
{"pattern": {"type": "string", "description": "Python regular expression."},
|
||||
"path": {"type": "string", "description": "Limit the search to this subdirectory."},
|
||||
"glob": {"type": "string", "description": "Limit to files matching this glob, e.g. '*.py'."}},
|
||||
["pattern"]),
|
||||
fn("edit_lines",
|
||||
"Replace an inclusive 1-based line range with new text. Line numbers "
|
||||
"refer to the file as it is now, so re-read it after any edit that "
|
||||
"changed the number of lines.",
|
||||
{"path": {"type": "string", "description": "File path relative to the repository root."},
|
||||
"start_line": {"type": "integer", "description": "First line to replace, 1-based, inclusive."},
|
||||
"end_line": {"type": "integer", "description": "Last line to replace, 1-based, inclusive."},
|
||||
"new_text": {"type": "string", "description": "Replacement text. Use an empty string to delete the range."}},
|
||||
["path", "start_line", "end_line", "new_text"]),
|
||||
fn("edit_replace",
|
||||
"Replace an exact snippet of text. The snippet must occur exactly "
|
||||
"once in the file unless replace_all is set.",
|
||||
{"path": {"type": "string", "description": "File path relative to the repository root."},
|
||||
"old_text": {"type": "string", "description": "Exact text to find, including indentation."},
|
||||
"new_text": {"type": "string", "description": "Replacement text."},
|
||||
"replace_all": {"type": "boolean", "description": "Replace every occurrence instead of requiring exactly one."}},
|
||||
["path", "old_text", "new_text"]),
|
||||
fn("write_file",
|
||||
"Write a whole file, creating or overwriting it. Prefer the edit "
|
||||
"tools for changes to existing files.",
|
||||
{"path": {"type": "string", "description": "File path relative to the repository root."},
|
||||
"content": {"type": "string", "description": "Full new file contents."}},
|
||||
["path", "content"]),
|
||||
fn("lint",
|
||||
"Run the language's linter and type checker over the repository and "
|
||||
"return the diagnostics. This is the only feedback available; the "
|
||||
"test suite cannot be run.",
|
||||
{"path": {"type": "string", "description": "Limit to this file or directory."}}),
|
||||
fn("finish",
|
||||
"Declare the task complete and end the session. This is the only "
|
||||
"way to end it; call this once the change is made.",
|
||||
{"summary": {"type": "string", "description": "Brief description of the change made."}}),
|
||||
]
|
||||
|
||||
|
||||
class ToolBox:
|
||||
"""Executes tool calls against one workspace. Returns plain text results.
|
||||
|
||||
Failures are reported as ordinary text rather than raised, because how a
|
||||
model recovers from a rejected edit is part of what is being measured.
|
||||
"""
|
||||
|
||||
def __init__(self, ws: Workspace, linter=None):
|
||||
self.ws = ws
|
||||
self.linter = linter
|
||||
self.calls = 0
|
||||
self.errors = 0
|
||||
self.edits = 0
|
||||
self.finished = False
|
||||
self.finish_summary: Optional[str] = None
|
||||
self.per_tool: Dict[str, int] = {}
|
||||
|
||||
def dispatch(self, name: str, args: Dict[str, Any]) -> str:
|
||||
self.calls += 1
|
||||
self.per_tool[name] = self.per_tool.get(name, 0) + 1
|
||||
handler = getattr(self, f"_t_{name}", None)
|
||||
if handler is None:
|
||||
result = f"Error: unknown tool {name!r}."
|
||||
else:
|
||||
try:
|
||||
result = handler(args)
|
||||
except PathEscape as e:
|
||||
result = f"Error: {e}"
|
||||
except KeyError as e:
|
||||
result = f"Error: missing required argument {e}."
|
||||
except Exception as e:
|
||||
result = f"Error: {type(e).__name__}: {e}"
|
||||
if result.startswith("Error:"):
|
||||
self.errors += 1
|
||||
return _clip(result)
|
||||
|
||||
def _t_list_files(self, a):
|
||||
base = self.ws.resolve(a.get("path") or ".")
|
||||
if not base.exists():
|
||||
return f"Error: {a.get('path')!r} does not exist."
|
||||
paths = [p for p in self.ws.files() if p == base or base in p.parents]
|
||||
if not paths:
|
||||
return "(no files)"
|
||||
return "\n".join(f"{self.ws.rel(p)}\t{p.stat().st_size} bytes" for p in paths)
|
||||
|
||||
def _t_read_file(self, a):
|
||||
path = self.ws.resolve(a["path"])
|
||||
if not path.is_file():
|
||||
return f"Error: {a['path']!r} is not a file."
|
||||
lines = path.read_text().splitlines()
|
||||
start = max(1, int(a.get("start_line") or 1))
|
||||
end = int(a.get("end_line") or len(lines))
|
||||
end = min(end, len(lines))
|
||||
if start > len(lines):
|
||||
return f"Error: {a['path']!r} has only {len(lines)} lines."
|
||||
window = lines[start - 1:end]
|
||||
note = ""
|
||||
if len(window) > MAX_READ_LINES:
|
||||
window = window[:MAX_READ_LINES]
|
||||
note = (f"\n... [showing {MAX_READ_LINES} of {end - start + 1} requested "
|
||||
f"lines; re-read with start_line/end_line for the rest]")
|
||||
header = f"{a['path']} ({len(lines)} lines total)\n"
|
||||
return header + _numbered(window, start) + note
|
||||
|
||||
def _t_search(self, a):
|
||||
try:
|
||||
rx = re.compile(a["pattern"])
|
||||
except re.error as e:
|
||||
return f"Error: bad regular expression: {e}"
|
||||
base = self.ws.resolve(a.get("path") or ".")
|
||||
glob = a.get("glob")
|
||||
hits, scanned = [], 0
|
||||
for p in self.ws.files():
|
||||
if not (p == base or base in p.parents):
|
||||
continue
|
||||
if glob and not p.match(glob):
|
||||
continue
|
||||
try:
|
||||
text = p.read_text()
|
||||
except (UnicodeDecodeError, OSError):
|
||||
continue
|
||||
scanned += 1
|
||||
for i, line in enumerate(text.splitlines(), 1):
|
||||
if rx.search(line):
|
||||
hits.append(f"{self.ws.rel(p)}:{i}:{line.strip()[:200]}")
|
||||
if len(hits) >= MAX_SEARCH_HITS:
|
||||
return ("\n".join(hits) +
|
||||
f"\n... [stopped at {MAX_SEARCH_HITS} matches; narrow the pattern]")
|
||||
if not hits:
|
||||
return f"No matches for {a['pattern']!r} in {scanned} file(s)."
|
||||
return "\n".join(hits)
|
||||
|
||||
def _t_edit_lines(self, a):
|
||||
path = self.ws.resolve(a["path"])
|
||||
if not path.is_file():
|
||||
return f"Error: {a['path']!r} is not a file."
|
||||
lines = path.read_text().splitlines()
|
||||
start, end = int(a["start_line"]), int(a["end_line"])
|
||||
if start < 1 or start > len(lines):
|
||||
return (f"Error: start_line {start} out of range; "
|
||||
f"{a['path']!r} has {len(lines)} lines.")
|
||||
if end < start or end > len(lines):
|
||||
return (f"Error: end_line {end} out of range; must be between "
|
||||
f"{start} and {len(lines)}.")
|
||||
new = str(a["new_text"]).splitlines()
|
||||
path.write_text("\n".join(lines[:start - 1] + new + lines[end:]) + "\n")
|
||||
self.edits += 1
|
||||
delta = len(new) - (end - start + 1)
|
||||
return (f"Replaced lines {start}-{end} of {a['path']} "
|
||||
f"({end - start + 1} -> {len(new)} lines, {delta:+d}). "
|
||||
f"Later line numbers have shifted." if delta else
|
||||
f"Replaced lines {start}-{end} of {a['path']}.")
|
||||
|
||||
def _t_edit_replace(self, a):
|
||||
path = self.ws.resolve(a["path"])
|
||||
if not path.is_file():
|
||||
return f"Error: {a['path']!r} is not a file."
|
||||
text = path.read_text()
|
||||
old, new = str(a["old_text"]), str(a["new_text"])
|
||||
if not old:
|
||||
return "Error: old_text must not be empty."
|
||||
n = text.count(old)
|
||||
if n == 0:
|
||||
return (f"Error: old_text not found in {a['path']}. It must match "
|
||||
f"exactly, including indentation.")
|
||||
if n > 1 and not a.get("replace_all"):
|
||||
return (f"Error: old_text occurs {n} times in {a['path']}. Include "
|
||||
f"more surrounding context to make it unique, or set "
|
||||
f"replace_all.")
|
||||
path.write_text(text.replace(old, new))
|
||||
self.edits += 1
|
||||
return f"Replaced {n} occurrence(s) in {a['path']}."
|
||||
|
||||
def _t_write_file(self, a):
|
||||
path = self.ws.resolve(a["path"])
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
existed = path.is_file()
|
||||
path.write_text(str(a["content"]))
|
||||
self.edits += 1
|
||||
return f"{'Overwrote' if existed else 'Created'} {a['path']}."
|
||||
|
||||
def _t_lint(self, a):
|
||||
if self.linter is None:
|
||||
return "Error: no linter is configured for this task."
|
||||
target = a.get("path")
|
||||
if target:
|
||||
self.ws.resolve(target)
|
||||
return self.linter(target)
|
||||
|
||||
def _t_finish(self, a):
|
||||
self.finished = True
|
||||
self.finish_summary = a.get("summary")
|
||||
return "Task marked complete."
|
||||
|
||||
|
||||
PY_TEST_RUNNER = r'''
|
||||
import json, os, random, sys, unittest
|
||||
sys.path.insert(0, os.getcwd())
|
||||
random.seed(0)
|
||||
|
||||
class Collect(unittest.TestResult):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.outcomes = {}
|
||||
def addSuccess(self, t): self.outcomes[t.id()] = "pass"
|
||||
def addFailure(self, t, e): self.outcomes[t.id()] = "fail"
|
||||
def addError(self, t, e): self.outcomes[t.id()] = "error"
|
||||
def addSkip(self, t, r): self.outcomes[t.id()] = "skip"
|
||||
def addExpectedFailure(self, t, e): self.outcomes[t.id()] = "pass"
|
||||
def addUnexpectedSuccess(self, t): self.outcomes[t.id()] = "fail"
|
||||
|
||||
def flatten(s):
|
||||
for t in s:
|
||||
if isinstance(t, unittest.TestSuite):
|
||||
yield from flatten(t)
|
||||
else:
|
||||
yield t
|
||||
|
||||
out = {"tests": {}}
|
||||
try:
|
||||
suite = unittest.TestLoader().discover(os.environ.get("TEST_DIR", "tests"),
|
||||
top_level_dir=".")
|
||||
res = Collect()
|
||||
for t in flatten(suite):
|
||||
tid = t.id()
|
||||
try:
|
||||
t.run(res)
|
||||
except Exception:
|
||||
pass
|
||||
res.outcomes.setdefault(tid, "error")
|
||||
out["tests"] = res.outcomes
|
||||
except Exception as e:
|
||||
out["error"] = f"{type(e).__name__}: {e}"
|
||||
|
||||
print("###RESULT###")
|
||||
print(json.dumps(out))
|
||||
'''
|
||||
|
||||
|
||||
def _node_roots() -> List[Path]:
|
||||
"""Directories that must stay visible for node to start under bwrap."""
|
||||
node = shutil.which("node")
|
||||
if not node:
|
||||
return []
|
||||
real = Path(node).resolve()
|
||||
return [Path(node).parent.parent, real.parent.parent]
|
||||
|
||||
|
||||
class Runner:
|
||||
"""Runs the linter and the hidden tests for one language, in the sandbox."""
|
||||
|
||||
def __init__(self, sandbox, lang: LangSpec, timeout: float = 90.0):
|
||||
self.sandbox = sandbox
|
||||
self.lang = lang
|
||||
self.timeout = timeout
|
||||
|
||||
def _binds(self) -> List[Path]:
|
||||
return _node_roots() if self.lang.name == "typescript" else []
|
||||
|
||||
def _env(self) -> Dict[str, str]:
|
||||
if self.lang.name != "typescript":
|
||||
return {}
|
||||
node = shutil.which("node")
|
||||
return {"PATH": f"{Path(node).parent}:/usr/bin:/bin"} if node else {}
|
||||
|
||||
def lint(self, workdir: Path, target: Optional[str] = None) -> str:
|
||||
if self.lang.name == "python":
|
||||
return self._lint_python(workdir, target)
|
||||
return self._lint_typescript(workdir, target)
|
||||
|
||||
def _lint_python(self, workdir: Path, target: Optional[str]) -> str:
|
||||
venv = self.sandbox.venv_dir
|
||||
tgt = target or "."
|
||||
chunks = []
|
||||
r = self.sandbox.run_argv(
|
||||
[str(venv / "bin" / "ruff"), "check", "--no-cache",
|
||||
"--output-format", "concise", tgt],
|
||||
workdir, timeout=self.timeout)
|
||||
text = (r.stdout or r.stderr).strip()
|
||||
if text and "All checks passed" not in text:
|
||||
chunks.append(text)
|
||||
r = self.sandbox.run_argv(
|
||||
[str(venv / "bin" / "mypy"), "--no-color-output", "--no-error-summary",
|
||||
"--cache-dir", "/dev/null", "--ignore-missing-imports", tgt],
|
||||
workdir, timeout=self.timeout)
|
||||
text = (r.stdout or r.stderr).strip()
|
||||
if text and not text.startswith("Success:"):
|
||||
chunks.append(text)
|
||||
return "\n".join(chunks) if chunks else "No problems found."
|
||||
|
||||
def _lint_typescript(self, workdir: Path, target: Optional[str]) -> str:
|
||||
tsc = self.sandbox.venv_dir / "node" / "node_modules" / "typescript" / "bin" / "tsc"
|
||||
files = [str(p.relative_to(workdir)) for p in sorted(workdir.rglob("*.ts"))
|
||||
if "node_modules" not in p.parts]
|
||||
if target:
|
||||
t = (workdir / target).resolve()
|
||||
files = [f for f in files if (workdir / f).resolve() == t
|
||||
or t in (workdir / f).resolve().parents]
|
||||
if not files:
|
||||
return "No TypeScript files to check."
|
||||
r = self.sandbox.run_argv(
|
||||
["node", str(tsc), "--noEmit", "--strict", "--target", "es2022",
|
||||
"--module", "esnext", "--moduleResolution", "bundler",
|
||||
"--allowImportingTsExtensions", *files],
|
||||
workdir, timeout=self.timeout, env=self._env(), ro_binds=self._binds())
|
||||
out = (r.stdout or "") + (r.stderr or "")
|
||||
return out.strip() or "No problems found."
|
||||
|
||||
def test(self, workdir: Path) -> Dict[str, Any]:
|
||||
if self.lang.name == "python":
|
||||
return self._test_python(workdir)
|
||||
return self._test_typescript(workdir)
|
||||
|
||||
def _test_python(self, workdir: Path) -> Dict[str, Any]:
|
||||
runner = workdir / "__run_tests.py"
|
||||
runner.write_text(PY_TEST_RUNNER)
|
||||
r = self.sandbox.run_argv(
|
||||
[str(self.sandbox.python), "-I", "-B", str(runner)],
|
||||
workdir, timeout=self.timeout, env={"TEST_DIR": "tests"})
|
||||
return self._parse_json_result(r)
|
||||
|
||||
def _parse_json_result(self, r) -> Dict[str, Any]:
|
||||
if r.status == "timeout":
|
||||
return {"tests": {}, "error": "timeout"}
|
||||
marker = "###RESULT###"
|
||||
if marker not in (r.stdout or ""):
|
||||
return {"tests": {},
|
||||
"error": f"no result: {(r.stderr or r.stdout or '')[-400:]}"}
|
||||
try:
|
||||
return json.loads(r.stdout.split(marker, 1)[1].strip())
|
||||
except Exception as e:
|
||||
return {"tests": {}, "error": f"unparseable result: {e}"}
|
||||
|
||||
def _test_typescript(self, workdir: Path) -> Dict[str, Any]:
|
||||
tests = sorted(p for p in (workdir / "tests").rglob("*.ts")) \
|
||||
if (workdir / "tests").is_dir() else []
|
||||
if not tests:
|
||||
return {"tests": {}, "error": "no test files"}
|
||||
r = self.sandbox.run_argv(
|
||||
["node", "--test", "--experimental-strip-types",
|
||||
"--test-reporter=tap",
|
||||
*[str(p.relative_to(workdir)) for p in tests]],
|
||||
workdir, timeout=self.timeout, env=self._env(), ro_binds=self._binds())
|
||||
return self._parse_tap(r)
|
||||
|
||||
@staticmethod
|
||||
def _parse_tap(r) -> Dict[str, Any]:
|
||||
if r.status == "timeout":
|
||||
return {"tests": {}, "error": "timeout"}
|
||||
text = (r.stdout or "") + "\n" + (r.stderr or "")
|
||||
outcomes: Dict[str, str] = {}
|
||||
for line in text.splitlines():
|
||||
m = re.match(r"\s*(not )?ok\s+\d+\s*-\s*(.+?)\s*$", line)
|
||||
if not m:
|
||||
continue
|
||||
name = m.group(2).strip()
|
||||
if name.endswith(" # SKIP") or " # SKIP" in name:
|
||||
outcomes[name.split(" # ")[0]] = "skip"
|
||||
continue
|
||||
verdict = "fail" if m.group(1) else "pass"
|
||||
if outcomes.get(name) != "fail":
|
||||
outcomes[name] = verdict
|
||||
if not outcomes:
|
||||
return {"tests": {},
|
||||
"error": f"no TAP output: {text.strip()[-400:]}"}
|
||||
return {"tests": outcomes}
|
||||
|
||||
|
||||
SYSTEM_PROMPT = """You are an expert {lang} engineer working in an existing repository.
|
||||
|
||||
Resolve the user's request by editing files with the tools provided.
|
||||
|
||||
How this session works:
|
||||
- You are working autonomously. There is no human watching; nobody will answer
|
||||
questions, approve a plan, or read progress reports.
|
||||
- Every reply you send must contain at least one tool call until the task is
|
||||
done. A reply that contains only text does nothing: no work happens and the
|
||||
turn is wasted. Do not stop to describe what you are about to do; do it.
|
||||
- The session ends only when you call `finish`. Call it once the change is
|
||||
made, and not before. Only the files as you leave them are evaluated.
|
||||
|
||||
Important constraints:
|
||||
- You cannot run the code, the tests, or any shell command. The only feedback
|
||||
available is the `lint` tool, which reports syntax and type problems.
|
||||
- The repository already has a test suite that will be run against your work
|
||||
after you finish, but you cannot see it or run it. Do not create test files.
|
||||
- Read enough of the code to understand it before editing. A change that only
|
||||
makes the symptom go away will usually fail the tests.
|
||||
- Make the smallest correct change. Do not reformat or refactor unrelated code.
|
||||
|
||||
Keep calling tools until the change is complete, then call `finish`."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgenticTask:
|
||||
task_id: str
|
||||
lang: str
|
||||
category: str
|
||||
difficulty: int
|
||||
instruction: str
|
||||
files: Dict[str, str]
|
||||
tests: Dict[str, str]
|
||||
gold: Dict[str, str] = field(default_factory=dict)
|
||||
fail_to_pass: List[str] = field(default_factory=list)
|
||||
pass_to_pass: List[str] = field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def from_record(cls, rec: Dict[str, Any]) -> "AgenticTask":
|
||||
def as_map(v):
|
||||
return v if isinstance(v, dict) else json.loads(v or "{}")
|
||||
def as_list(v):
|
||||
return v if isinstance(v, list) else json.loads(v or "[]")
|
||||
return cls(
|
||||
task_id=rec["task_id"], lang=rec["lang"], category=rec["category"],
|
||||
difficulty=int(rec.get("difficulty", 3)),
|
||||
instruction=rec["instruction"],
|
||||
files=as_map(rec["files"]), tests=as_map(rec["tests"]),
|
||||
gold=as_map(rec.get("gold")),
|
||||
fail_to_pass=as_list(rec.get("fail_to_pass")),
|
||||
pass_to_pass=as_list(rec.get("pass_to_pass")),
|
||||
)
|
||||
|
||||
@property
|
||||
def spec(self) -> LangSpec:
|
||||
return LANGS[self.lang]
|
||||
|
||||
|
||||
def materialise(files: Dict[str, str], dest: Path) -> Path:
|
||||
"""Write a file map out as a real directory tree."""
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
for rel, content in files.items():
|
||||
p = dest / rel
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
p.write_text(content)
|
||||
return dest
|
||||
|
||||
|
||||
def repo_overview(files: Dict[str, str]) -> str:
|
||||
"""A compact tree, so a run is not lost merely to not finding the files."""
|
||||
lines = []
|
||||
for rel in sorted(files):
|
||||
n = files[rel].count("\n") + 1
|
||||
lines.append(f" {rel} ({n} lines)")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def user_prompt(task: AgenticTask) -> str:
|
||||
return (f"Repository files:\n{repo_overview(task.files)}\n\n"
|
||||
f"Task:\n{task.instruction}")
|
||||
|
||||
|
||||
@dataclass
|
||||
class EpisodeResult:
|
||||
stop_reason: str = "unknown"
|
||||
turns: int = 0
|
||||
tool_calls: int = 0
|
||||
tool_errors: int = 0
|
||||
edits: int = 0
|
||||
escape_attempts: int = 0
|
||||
finished: bool = False
|
||||
bad_tool_json: int = 0
|
||||
nudges: int = 0
|
||||
prompt_tokens: int = 0
|
||||
completion_tokens: int = 0
|
||||
peak_context: int = 0
|
||||
truncated_turns: int = 0
|
||||
per_tool: Dict[str, int] = field(default_factory=dict)
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
NUDGE = ("Your last reply contained no tool call, so nothing happened. There is "
|
||||
"no human in this session to respond to text. If your change is "
|
||||
"complete, call `finish`. Otherwise continue working by calling the "
|
||||
"tools; do not reply with text alone.")
|
||||
|
||||
|
||||
def run_episode(task: AgenticTask, toolbox: ToolBox, chat,
|
||||
max_turns: int = 50,
|
||||
context_limit: Optional[int] = None,
|
||||
max_nudges: int = 3) -> Tuple[EpisodeResult, List[Dict]]:
|
||||
"""Drive one multi-turn tool-calling episode.
|
||||
|
||||
`chat(messages, tools) -> response dict` is supplied by the caller so this
|
||||
module stays free of any HTTP concerns.
|
||||
"""
|
||||
tools = tool_schemas()
|
||||
messages: List[Dict[str, Any]] = [
|
||||
{"role": "system", "content": SYSTEM_PROMPT.format(lang=task.spec.name)},
|
||||
{"role": "user", "content": user_prompt(task)},
|
||||
]
|
||||
res = EpisodeResult()
|
||||
|
||||
for turn in range(max_turns):
|
||||
res.turns = turn + 1
|
||||
try:
|
||||
reply = chat(messages, tools)
|
||||
except Exception as e:
|
||||
res.stop_reason, res.error = "request_failed", f"{type(e).__name__}: {e}"
|
||||
break
|
||||
|
||||
usage = reply.get("usage") or {}
|
||||
pt = int(usage.get("prompt_tokens") or 0)
|
||||
ct = int(usage.get("completion_tokens") or 0)
|
||||
res.prompt_tokens += pt
|
||||
res.completion_tokens += ct
|
||||
res.peak_context = max(res.peak_context, pt + ct)
|
||||
|
||||
choices = reply.get("choices") or []
|
||||
if not choices:
|
||||
res.stop_reason, res.error = "no_choices", json.dumps(reply)[:300]
|
||||
break
|
||||
msg = choices[0].get("message") or {}
|
||||
if choices[0].get("finish_reason") == "length":
|
||||
res.truncated_turns += 1
|
||||
calls = msg.get("tool_calls") or []
|
||||
|
||||
reasoning = msg.get("reasoning_content")
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": msg.get("content") or "",
|
||||
**({"reasoning_content": reasoning} if reasoning else {}),
|
||||
**({"tool_calls": calls} if calls else {}),
|
||||
})
|
||||
|
||||
if not calls:
|
||||
if toolbox.finished:
|
||||
res.stop_reason = "finished"
|
||||
break
|
||||
if res.nudges >= max_nudges:
|
||||
res.stop_reason = "no_tool_calls"
|
||||
break
|
||||
res.nudges += 1
|
||||
messages.append({"role": "user", "content": NUDGE})
|
||||
continue
|
||||
|
||||
for call in calls:
|
||||
fn = call.get("function") or {}
|
||||
name = fn.get("name") or ""
|
||||
raw = fn.get("arguments")
|
||||
if isinstance(raw, dict):
|
||||
args = raw
|
||||
else:
|
||||
try:
|
||||
args = json.loads(raw or "{}")
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
res.bad_tool_json += 1
|
||||
args = None
|
||||
output = (toolbox.dispatch(name, args) if args is not None else
|
||||
f"Error: arguments for {name!r} were not valid JSON.")
|
||||
messages.append({"role": "tool",
|
||||
"tool_call_id": call.get("id") or f"call_{res.tool_calls}",
|
||||
"content": output})
|
||||
|
||||
if toolbox.finished:
|
||||
res.stop_reason = "finished"
|
||||
break
|
||||
if context_limit and res.peak_context > context_limit:
|
||||
res.stop_reason = "context_limit"
|
||||
break
|
||||
else:
|
||||
res.stop_reason = "max_turns"
|
||||
|
||||
res.tool_calls = toolbox.calls
|
||||
res.tool_errors = toolbox.errors
|
||||
res.edits = toolbox.edits
|
||||
res.escape_attempts = toolbox.ws.escape_attempts
|
||||
res.finished = toolbox.finished
|
||||
res.per_tool = dict(toolbox.per_tool)
|
||||
return res, messages
|
||||
|
||||
|
||||
def run_tests_against(files: Dict[str, str], task: AgenticTask, runner: Runner,
|
||||
workroot: Path, label: str) -> Dict[str, str]:
|
||||
"""Materialise a tree, drop the hidden tests in, and run them.
|
||||
|
||||
The tests are only ever written into this throwaway copy, never into the
|
||||
tree the agent worked in, so there is no window in which they could have
|
||||
been read or edited.
|
||||
"""
|
||||
dest = workroot / f"grade-{label}"
|
||||
shutil.rmtree(dest, ignore_errors=True)
|
||||
materialise(files, dest)
|
||||
materialise(task.tests, dest)
|
||||
return runner.test(dest).get("tests", {})
|
||||
|
||||
|
||||
def score(task: AgenticTask, outcomes: Dict[str, str]) -> Dict[str, Any]:
|
||||
"""Resolved means every target test passes and nothing previously passing broke."""
|
||||
def passing(tid):
|
||||
return outcomes.get(tid) == "pass"
|
||||
|
||||
f2p_ok = [t for t in task.fail_to_pass if passing(t)]
|
||||
p2p_ok = [t for t in task.pass_to_pass if passing(t)]
|
||||
regressions = [t for t in task.pass_to_pass if not passing(t)]
|
||||
resolved = (len(f2p_ok) == len(task.fail_to_pass)
|
||||
and not regressions and bool(task.fail_to_pass))
|
||||
return {
|
||||
"resolved": resolved,
|
||||
"f2p_passed": len(f2p_ok), "f2p_total": len(task.fail_to_pass),
|
||||
"p2p_passed": len(p2p_ok), "p2p_total": len(task.pass_to_pass),
|
||||
"regressions": regressions[:10],
|
||||
"n_regressions": len(regressions),
|
||||
"partial": (len(f2p_ok) / len(task.fail_to_pass)) if task.fail_to_pass else 0.0,
|
||||
}
|
||||
|
||||
|
||||
def make_sandbox(lang_name: str, sandbox_cls, cache_root=None, base_python=None):
|
||||
"""A sandbox carrying only the toolchain for `lang_name`.
|
||||
|
||||
Keyed per language rather than per suite: running the Python tasks must
|
||||
never provision node, and vice versa.
|
||||
"""
|
||||
lang = LANGS[lang_name]
|
||||
return sandbox_cls(
|
||||
name=f"agentic-{lang_name}",
|
||||
packages=list(lang.packages),
|
||||
provision=list(lang.provision),
|
||||
env=dict(lang.env),
|
||||
address_space_limit=lang.address_space_limit,
|
||||
cache_root=cache_root,
|
||||
base_python=base_python,
|
||||
)
|
||||
|
||||
|
||||
def run_task(task: AgenticTask, chat, sandbox, workroot: Optional[Path] = None,
|
||||
max_turns: int = 50, context_limit: Optional[int] = None,
|
||||
test_timeout: float = 90.0) -> Dict[str, Any]:
|
||||
"""One full attempt: fresh workspace, agent episode, then hidden tests."""
|
||||
root = Path(tempfile.mkdtemp(prefix=f"agentic-{task.task_id}-",
|
||||
dir=str(workroot) if workroot else None))
|
||||
try:
|
||||
ws_root = root / "repo"
|
||||
materialise(task.files, ws_root)
|
||||
ws = Workspace(ws_root)
|
||||
runner = Runner(sandbox, task.spec, timeout=test_timeout)
|
||||
toolbox = ToolBox(ws, linter=lambda t: runner.lint(ws_root, t))
|
||||
|
||||
episode, messages = run_episode(task, toolbox, chat,
|
||||
max_turns=max_turns,
|
||||
context_limit=context_limit)
|
||||
final = ws.snapshot()
|
||||
changed = sorted(k for k in final
|
||||
if final[k] != task.files.get(k)) + \
|
||||
sorted(k for k in task.files if k not in final)
|
||||
|
||||
outcomes = run_tests_against(final, task, runner, root, "final")
|
||||
result = score(task, outcomes)
|
||||
result.update({
|
||||
"task_id": task.task_id, "lang": task.lang,
|
||||
"category": task.category, "difficulty": task.difficulty,
|
||||
"changed_files": changed,
|
||||
"episode": {k: v for k, v in vars(episode).items()},
|
||||
"messages": messages,
|
||||
})
|
||||
return result
|
||||
finally:
|
||||
shutil.rmtree(root, ignore_errors=True)
|
||||
|
||||
|
||||
TRANSCRIPT_CSS = """
|
||||
body { font-family: system-ui, sans-serif; margin: 0; padding: 16px; background: #fff; color: #222; max-width: 1100px; }
|
||||
h1 { font-size: 16px; margin: 0 0 4px; }
|
||||
.meta { font-size: 13px; color: #555; font-family: 'SF Mono', 'Menlo', 'Consolas', monospace; margin-bottom: 12px; white-space: pre-wrap; }
|
||||
.msg { border: 1px solid #e1e4e8; border-left-width: 4px; margin: 8px 0; padding: 6px 10px; background: #fafbfc; }
|
||||
.msg .role { font-size: 12px; font-weight: 600; color: #555; margin-bottom: 4px; }
|
||||
.system { border-left-color: #888; }
|
||||
.user { border-left-color: #0969da; }
|
||||
.nudge { border-left-color: #9a6700; }
|
||||
.assistant { border-left-color: #1a7f37; background: #fff; }
|
||||
.tool { border-left-color: #8250df; }
|
||||
.tool.err { border-left-color: #cf222e; }
|
||||
pre { font-family: 'SF Mono', 'Menlo', 'Consolas', monospace; font-size: 12px; background: #fff; border: 1px solid #e1e4e8; padding: 6px 8px; margin: 4px 0; white-space: pre-wrap; word-wrap: break-word; }
|
||||
.reasoning pre { color: #57606a; background: #f6f8fa; }
|
||||
.call { font-size: 12px; font-weight: 600; color: #8250df; margin-top: 6px; }
|
||||
details > summary { cursor: pointer; font-size: 12px; color: #555; }
|
||||
.empty { font-size: 12px; color: #888; font-style: italic; }
|
||||
"""
|
||||
|
||||
|
||||
def _pre(text: str) -> str:
|
||||
return f"<pre>{html.escape(text)}</pre>"
|
||||
|
||||
|
||||
def _render_call(call: Dict[str, Any]) -> str:
|
||||
fn = call.get("function") or {}
|
||||
raw = fn.get("arguments")
|
||||
try:
|
||||
args = raw if isinstance(raw, dict) else json.loads(raw or "{}")
|
||||
shown = json.dumps(args, indent=2, ensure_ascii=False)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
shown = f"(invalid JSON)\n{raw}"
|
||||
return (f'<div class="call">→ {html.escape(fn.get("name") or "?")} '
|
||||
f'<span style="font-weight:400;color:#888">{html.escape(call.get("id") or "")}</span></div>'
|
||||
+ _pre(shown))
|
||||
|
||||
|
||||
def render_transcript_html(messages: List[Dict[str, Any]], title: str,
|
||||
meta: str = "") -> str:
|
||||
"""One page per episode: every turn, with reasoning, tool calls and results."""
|
||||
names = {}
|
||||
for m in messages:
|
||||
for c in m.get("tool_calls") or []:
|
||||
names[c.get("id")] = (c.get("function") or {}).get("name") or "?"
|
||||
|
||||
parts = []
|
||||
turn = 0
|
||||
for m in messages:
|
||||
role = m.get("role") or "?"
|
||||
content = m.get("content") or ""
|
||||
if role == "assistant":
|
||||
turn += 1
|
||||
body = ""
|
||||
if m.get("reasoning_content"):
|
||||
body += ('<details class="reasoning" open><summary>reasoning '
|
||||
f'({len(m["reasoning_content"])} chars)</summary>'
|
||||
+ _pre(m["reasoning_content"]) + "</details>")
|
||||
body += _pre(content) if content.strip() else ""
|
||||
body += "".join(_render_call(c) for c in m.get("tool_calls") or [])
|
||||
if not body:
|
||||
body = '<div class="empty">(empty reply)</div>'
|
||||
parts.append(f'<div class="msg assistant"><div class="role">assistant '
|
||||
f'· turn {turn}</div>{body}</div>')
|
||||
elif role == "tool":
|
||||
name = names.get(m.get("tool_call_id"), "?")
|
||||
err = " err" if content.startswith("Error") else ""
|
||||
parts.append(f'<div class="msg tool{err}"><div class="role">tool result '
|
||||
f'· {html.escape(name)}</div>{_pre(content)}</div>')
|
||||
elif role == "system":
|
||||
parts.append('<div class="msg system"><details><summary>system prompt'
|
||||
f'</summary>{_pre(content)}</details></div>')
|
||||
else:
|
||||
cls = "nudge" if content == NUDGE else role
|
||||
label = "user (nudge)" if content == NUDGE else role
|
||||
parts.append(f'<div class="msg {html.escape(cls)}"><div class="role">'
|
||||
f'{html.escape(label)}</div>{_pre(content)}</div>')
|
||||
|
||||
return (f'<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n'
|
||||
f'<title>{html.escape(title)}</title>\n<style>{TRANSCRIPT_CSS}</style>\n'
|
||||
f'</head>\n<body>\n<h1>{html.escape(title)}</h1>\n'
|
||||
f'<div class="meta">{html.escape(meta)}</div>\n'
|
||||
+ "\n".join(parts) + "\n</body>\n</html>\n")
|
||||
|
||||
|
||||
def write_transcript(messages: List[Dict[str, Any]], dest: Path, title: str,
|
||||
meta: str = "") -> Path:
|
||||
"""Write `<dest>.json` (the raw message history) and `<dest>.html`."""
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
json_path = dest.with_name(dest.name + ".json")
|
||||
with open(json_path, "w") as f:
|
||||
json.dump(messages, f, indent=2, ensure_ascii=False)
|
||||
with open(dest.with_name(dest.name + ".html"), "w") as f:
|
||||
f.write(render_transcript_html(messages, title, meta))
|
||||
return json_path
|
||||
@@ -0,0 +1,488 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Lightweight sandboxed execution for code-generation evals (HumanEval, ClassEval).
|
||||
|
||||
Replaces the usual Docker-per-suite setup with a cached venv plus whatever
|
||||
process isolation the host can give us without root:
|
||||
|
||||
tier 1 bubblewrap read-only root, tmpfs /tmp and $HOME, no network
|
||||
tier 2 unshare -rn no network (home still visible)
|
||||
tier 3 rlimits only last resort, still capped on cpu/memory/procs/files
|
||||
|
||||
Every tier also applies rlimits and runs the candidate program in a throwaway
|
||||
working directory as its own process group, so a runaway solution is killed
|
||||
along with anything it spawned.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import resource
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import venv
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Sequence
|
||||
|
||||
DEFAULT_CACHE_ROOT = Path(
|
||||
os.environ.get("LLAMA_EVAL_CACHE", Path.home() / ".cache" / "llama-eval")
|
||||
)
|
||||
|
||||
LIMIT_AS_BYTES = 4 * 1024 * 1024 * 1024
|
||||
LIMIT_CPU_SECONDS_SLACK = 5
|
||||
LIMIT_NPROC = 256
|
||||
LIMIT_FSIZE_BYTES = 64 * 1024 * 1024
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExecResult:
|
||||
passed: bool
|
||||
status: str
|
||||
stdout: str = ""
|
||||
stderr: str = ""
|
||||
returncode: Optional[int] = None
|
||||
duration: float = 0.0
|
||||
|
||||
def summary(self, max_chars: int = 400) -> str:
|
||||
"""Short human-readable reason, for grader logs."""
|
||||
if self.passed:
|
||||
return "ok"
|
||||
if self.status == "timeout":
|
||||
return "timeout"
|
||||
tail = (self.stderr or self.stdout).strip()
|
||||
if not tail:
|
||||
return f"{self.status} (rc={self.returncode})"
|
||||
lines = [ln for ln in tail.splitlines() if ln.strip()]
|
||||
reason = lines[-1] if lines else tail
|
||||
return reason[:max_chars]
|
||||
|
||||
|
||||
def resolve_python(spec: Optional[str]) -> Optional[str]:
|
||||
"""Turn '3.13' or a path into a usable interpreter, or None if unavailable.
|
||||
|
||||
Suites can need an older interpreter than the one running this tool, because
|
||||
a dependency has no wheel (or no working source build) for the newest
|
||||
CPython. Accepts an explicit path, a bare version, or an executable name.
|
||||
"""
|
||||
if not spec:
|
||||
return None
|
||||
|
||||
candidate = Path(spec).expanduser()
|
||||
if candidate.exists() and candidate.is_file():
|
||||
return str(candidate)
|
||||
|
||||
uv = shutil.which("uv")
|
||||
if uv and re.fullmatch(r"\d+\.\d+(\.\d+)?", spec):
|
||||
try:
|
||||
r = subprocess.run([uv, "python", "find", spec],
|
||||
capture_output=True, text=True, timeout=120)
|
||||
path = r.stdout.strip()
|
||||
if r.returncode == 0 and path and Path(path).exists():
|
||||
return path
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return shutil.which(spec) or shutil.which(f"python{spec}")
|
||||
|
||||
|
||||
def _detect_isolation(probe_python: Optional[str] = None,
|
||||
ro_binds: Sequence[Path] = ()) -> str:
|
||||
"""Pick the strongest isolation tier this host supports, once.
|
||||
|
||||
The probe must run the *same* interpreter the suite will, with the same
|
||||
binds. Probing a bare sys.executable silently downgrades the tier whenever
|
||||
this tool is itself run from a venv under $HOME -- which is the usual case,
|
||||
since it needs `datasets`. bwrap tmpfs-mounts the home directory, the
|
||||
interpreter disappears, the probe fails, and the sandbox falls back to a
|
||||
tier that does not hide $HOME at all. Nothing about the results looks wrong
|
||||
when that happens, so it has to be got right here.
|
||||
"""
|
||||
py = probe_python or sys.executable
|
||||
probe = "import sys; sys.exit(0)"
|
||||
|
||||
if shutil.which("bwrap"):
|
||||
try:
|
||||
r = subprocess.run(
|
||||
_bwrap_argv(Path.cwd(), [py, "-I", "-c", probe], ro_binds=ro_binds),
|
||||
capture_output=True, timeout=20,
|
||||
)
|
||||
if r.returncode == 0:
|
||||
return "bwrap"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if shutil.which("unshare"):
|
||||
for tier, argv in (
|
||||
("unshare", ["unshare", "--user", "--map-current-user", "--net"]),
|
||||
("unshare-root", ["unshare", "-rn"]),
|
||||
):
|
||||
try:
|
||||
r = subprocess.run(
|
||||
[*argv, py, "-I", "-c", probe],
|
||||
capture_output=True, timeout=20,
|
||||
)
|
||||
if r.returncode == 0:
|
||||
return tier
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return "rlimit"
|
||||
|
||||
|
||||
UNSHARE_ARGV = {
|
||||
"unshare": ["unshare", "--user", "--map-current-user", "--net"],
|
||||
"unshare-root": ["unshare", "-rn"],
|
||||
}
|
||||
|
||||
|
||||
def _under(path: Path, parent: str) -> bool:
|
||||
return bool(parent) and (str(path) == parent
|
||||
or str(path).startswith(parent.rstrip("/") + os.sep))
|
||||
|
||||
|
||||
def _bwrap_argv(workdir: Path, inner: Sequence[str],
|
||||
ro_binds: Sequence[Path] = ()) -> List[str]:
|
||||
home = os.path.expanduser("~")
|
||||
argv = [
|
||||
"bwrap",
|
||||
"--ro-bind", "/", "/",
|
||||
"--dev", "/dev",
|
||||
"--proc", "/proc",
|
||||
"--tmpfs", "/tmp",
|
||||
]
|
||||
if home and home != "/":
|
||||
argv += ["--tmpfs", home]
|
||||
for path in ro_binds:
|
||||
if _under(path, home):
|
||||
argv += ["--ro-bind", str(path), str(path)]
|
||||
argv += [
|
||||
"--bind", str(workdir), str(workdir),
|
||||
"--chdir", str(workdir),
|
||||
"--unshare-all",
|
||||
"--die-with-parent",
|
||||
"--new-session",
|
||||
"--",
|
||||
]
|
||||
return argv + list(inner)
|
||||
|
||||
|
||||
def _nproc_ceiling() -> int:
|
||||
"""A process cap that bounds a fork bomb without tripping on existing load.
|
||||
|
||||
RLIMIT_NPROC is enforced per real UID across the whole system, not per
|
||||
process tree, so it has to sit above whatever the user is already running.
|
||||
"""
|
||||
try:
|
||||
current = len(os.listdir("/proc"))
|
||||
except OSError:
|
||||
current = 512
|
||||
return max(current * 2, 1024)
|
||||
|
||||
|
||||
def _apply_rlimits(timeout: float, with_nproc: bool,
|
||||
as_bytes: Optional[int] = LIMIT_AS_BYTES):
|
||||
def _preexec():
|
||||
os.setsid()
|
||||
cpu = int(timeout) + LIMIT_CPU_SECONDS_SLACK
|
||||
limits = [
|
||||
(resource.RLIMIT_CPU, (cpu, cpu)),
|
||||
(resource.RLIMIT_FSIZE, (LIMIT_FSIZE_BYTES, LIMIT_FSIZE_BYTES)),
|
||||
(resource.RLIMIT_CORE, (0, 0)),
|
||||
]
|
||||
if as_bytes:
|
||||
limits.append((resource.RLIMIT_AS, (as_bytes, as_bytes)))
|
||||
if with_nproc:
|
||||
ceiling = _nproc_ceiling()
|
||||
limits.append((resource.RLIMIT_NPROC, (ceiling, ceiling)))
|
||||
for res, lim in limits:
|
||||
try:
|
||||
resource.setrlimit(res, lim)
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
return _preexec
|
||||
|
||||
|
||||
class Sandbox:
|
||||
"""A cached venv plus an isolated runner for untrusted candidate programs."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
packages: Optional[Sequence[str]] = None,
|
||||
cache_root: Optional[Path] = None,
|
||||
base_python: Optional[str] = None,
|
||||
isolation: Optional[str] = None,
|
||||
provision: Optional[Sequence[str]] = None,
|
||||
env: Optional[Dict[str, str]] = None,
|
||||
address_space_limit: Optional[int] = LIMIT_AS_BYTES,
|
||||
):
|
||||
self.name = name
|
||||
self.packages = sorted(set(packages or []))
|
||||
self.provision = list(provision or [])
|
||||
self.env = dict(env or {})
|
||||
# None drops the RLIMIT_AS cap entirely -- see _apply_rlimits
|
||||
self.address_space_limit = address_space_limit
|
||||
self.cache_root = Path(cache_root or DEFAULT_CACHE_ROOT)
|
||||
self.base_python = base_python or sys.executable
|
||||
self.venv_dir = self.cache_root / f"{name}-venv"
|
||||
self._python: Optional[Path] = None
|
||||
self._isolation = isolation
|
||||
self._base_prefix: List[Path] = []
|
||||
|
||||
@property
|
||||
def python(self) -> Path:
|
||||
if self._python is None:
|
||||
raise RuntimeError("Sandbox.ensure() must be called first")
|
||||
return self._python
|
||||
|
||||
@property
|
||||
def isolation(self) -> str:
|
||||
if self._isolation is None:
|
||||
if self._python:
|
||||
py = str(self._python)
|
||||
roots = [self.venv_dir, *(self._base_prefix or [])]
|
||||
else:
|
||||
py = sys.executable
|
||||
roots = self._detect_base_prefix(Path(py))
|
||||
self._isolation = _detect_isolation(py, roots)
|
||||
return self._isolation
|
||||
|
||||
def _stamp_path(self) -> Path:
|
||||
return self.venv_dir / ".llama-eval-stamp.json"
|
||||
|
||||
def _stamp_matches(self) -> bool:
|
||||
try:
|
||||
stamp = json.loads(self._stamp_path().read_text())
|
||||
except Exception:
|
||||
return False
|
||||
return (
|
||||
stamp.get("packages") == self.packages
|
||||
and stamp.get("py") == self._base_version()
|
||||
and stamp.get("provision") == self.provision
|
||||
)
|
||||
|
||||
def _resolved_env(self) -> Dict[str, str]:
|
||||
return {k: v.replace("{venv}", str(self.venv_dir)) for k, v in self.env.items()}
|
||||
|
||||
def _base_version(self) -> str:
|
||||
r = subprocess.run(
|
||||
[self.base_python, "-c",
|
||||
"import sys;print('.'.join(map(str,sys.version_info[:3])))"],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
return r.stdout.strip()
|
||||
|
||||
def ensure(self, force: bool = False, quiet: bool = False) -> Path:
|
||||
"""Create/refresh the venv. Idempotent and cheap when already valid."""
|
||||
py = self.venv_dir / "bin" / "python"
|
||||
if force and self.venv_dir.exists():
|
||||
shutil.rmtree(self.venv_dir)
|
||||
|
||||
if py.exists() and self._stamp_matches():
|
||||
self._python = py
|
||||
self._base_prefix = self._detect_base_prefix(py)
|
||||
return py
|
||||
|
||||
if not quiet:
|
||||
pkgs = ", ".join(self.packages) if self.packages else "stdlib only"
|
||||
print(f"[sandbox] provisioning venv {self.venv_dir} ({pkgs})")
|
||||
|
||||
self.cache_root.mkdir(parents=True, exist_ok=True)
|
||||
if self.venv_dir.exists():
|
||||
shutil.rmtree(self.venv_dir)
|
||||
|
||||
uv = shutil.which("uv")
|
||||
if uv:
|
||||
subprocess.run(
|
||||
[uv, "venv", "--python", self.base_python, str(self.venv_dir)],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
elif Path(self.base_python).resolve() == Path(sys.executable).resolve():
|
||||
venv.EnvBuilder(with_pip=bool(self.packages), clear=True).create(self.venv_dir)
|
||||
else:
|
||||
subprocess.run(
|
||||
[self.base_python, "-m", "venv", str(self.venv_dir)],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
|
||||
if self.packages:
|
||||
if uv:
|
||||
cmd = [uv, "pip", "install", "--python", str(py), *self.packages]
|
||||
else:
|
||||
cmd = [str(py), "-m", "pip", "install", "--quiet",
|
||||
"--disable-pip-version-check", *self.packages]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
raise RuntimeError(
|
||||
f"failed to install {len(self.packages)} package(s) into "
|
||||
f"{self.venv_dir}:\n{(r.stderr or r.stdout)[-3000:]}"
|
||||
)
|
||||
|
||||
for i, snippet in enumerate(self.provision, 1):
|
||||
if not quiet:
|
||||
print(f"[sandbox] provisioning step {i}/{len(self.provision)}")
|
||||
prov_env = dict(os.environ)
|
||||
prov_env["SANDBOX_VENV"] = str(self.venv_dir)
|
||||
prov_env.update(self._resolved_env())
|
||||
r = subprocess.run([str(py), "-c", snippet], env=prov_env,
|
||||
capture_output=True, text=True, timeout=1800)
|
||||
if r.returncode != 0:
|
||||
raise RuntimeError(
|
||||
f"provisioning step {i} failed:\n{(r.stderr or r.stdout)[-3000:]}"
|
||||
)
|
||||
|
||||
self._stamp_path().write_text(json.dumps({
|
||||
"packages": self.packages,
|
||||
"py": self._base_version(),
|
||||
"provision": self.provision,
|
||||
}))
|
||||
self._python = py
|
||||
self._base_prefix = self._detect_base_prefix(py)
|
||||
if not quiet:
|
||||
print(f"[sandbox] ready ({self.isolation} isolation)")
|
||||
if self.isolation == "unshare-root":
|
||||
print("Warning: this host's unshare cannot keep the caller's uid, so "
|
||||
"candidate code runs as root inside the namespace. Tests that "
|
||||
"assert a permission denial will not see one. Install "
|
||||
"bubblewrap (bwrap) for correct results.")
|
||||
return py
|
||||
|
||||
@staticmethod
|
||||
def _detect_base_prefix(py: Path) -> List[Path]:
|
||||
"""Every directory that must stay visible for the venv to start.
|
||||
|
||||
A venv is only symlinks into its base interpreter, so the whole install
|
||||
tree has to survive the tmpfs that hides $HOME -- and binding just the
|
||||
resolved tree is not enough. Version-managed interpreters (uv, pyenv)
|
||||
are commonly reached through an unversioned alias symlink
|
||||
(cpython-3.13-... -> cpython-3.13.14-...), and startup resolves through
|
||||
the alias path, so both spellings need to be mounted. Walk the chain
|
||||
and keep every install root it passes through.
|
||||
"""
|
||||
roots: List[Path] = []
|
||||
|
||||
def add(path: Path):
|
||||
if path.exists() and path not in roots:
|
||||
roots.append(path)
|
||||
|
||||
cur, hops = py, 0
|
||||
while hops < 16:
|
||||
hops += 1
|
||||
add(cur.parent.parent)
|
||||
if cur.is_symlink():
|
||||
target = Path(os.readlink(cur))
|
||||
cur = target if target.is_absolute() else (cur.parent / target)
|
||||
else:
|
||||
break
|
||||
|
||||
try:
|
||||
r = subprocess.run([str(py), "-c", "import sys; print(sys.base_prefix)"],
|
||||
capture_output=True, text=True, timeout=60)
|
||||
if r.returncode == 0 and r.stdout.strip():
|
||||
add(Path(r.stdout.strip()))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return [p for p in roots if str(p) not in ("/", "/usr", "/usr/local")]
|
||||
|
||||
def _isolate(self, argv: Sequence[str], workdir: Path,
|
||||
ro_binds: Sequence[Path] = ()) -> List[str]:
|
||||
"""Wrap a command in the strongest isolation tier this host supports."""
|
||||
if self.isolation == "bwrap":
|
||||
return _bwrap_argv(
|
||||
workdir, argv,
|
||||
ro_binds=[self.venv_dir, *(self._base_prefix or []), *ro_binds],
|
||||
)
|
||||
if self.isolation in UNSHARE_ARGV:
|
||||
return [*UNSHARE_ARGV[self.isolation], *argv]
|
||||
return list(argv)
|
||||
|
||||
def _base_env(self, workdir: Path) -> Dict[str, str]:
|
||||
env = {
|
||||
"PATH": "/usr/bin:/bin",
|
||||
"HOME": str(workdir),
|
||||
"TMPDIR": str(workdir),
|
||||
"LC_ALL": "C.UTF-8",
|
||||
"PYTHONHASHSEED": "0",
|
||||
"PYTHONDONTWRITEBYTECODE": "1",
|
||||
"OPENBLAS_NUM_THREADS": "1",
|
||||
"OMP_NUM_THREADS": "1",
|
||||
}
|
||||
env.update(self._resolved_env())
|
||||
return env
|
||||
|
||||
def run_argv(self, argv: Sequence[str], workdir: Path,
|
||||
timeout: float = 15.0, env: Optional[Dict[str, str]] = None,
|
||||
ro_binds: Sequence[Path] = ()) -> ExecResult:
|
||||
"""Run an arbitrary command in `workdir`, which stays writable.
|
||||
|
||||
Unlike run(), the caller owns the directory and it survives the call.
|
||||
That is what a multi-step agent needs: its edits have to persist across
|
||||
many tool invocations before anything is graded. Taking an argv rather
|
||||
than a source string is what lets a suite drive a linter or a test
|
||||
runner for a language other than Python.
|
||||
"""
|
||||
run_env = self._base_env(workdir)
|
||||
if env:
|
||||
run_env.update(env)
|
||||
|
||||
started = time.monotonic()
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
self._isolate(argv, workdir, ro_binds),
|
||||
cwd=str(workdir),
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
errors="replace",
|
||||
env=run_env,
|
||||
preexec_fn=_apply_rlimits(timeout,
|
||||
with_nproc=self.isolation == "rlimit",
|
||||
as_bytes=self.address_space_limit),
|
||||
)
|
||||
except Exception as e:
|
||||
return ExecResult(False, "error", "", f"{type(e).__name__}: {e}")
|
||||
|
||||
try:
|
||||
out, err = proc.communicate(timeout=timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
_kill_tree(proc)
|
||||
out, err = proc.communicate()
|
||||
return ExecResult(False, "timeout", out or "", err or "",
|
||||
None, time.monotonic() - started)
|
||||
|
||||
duration = time.monotonic() - started
|
||||
if proc.returncode == 0:
|
||||
return ExecResult(True, "ok", out, err, 0, duration)
|
||||
return ExecResult(False, "failed", out, err, proc.returncode, duration)
|
||||
|
||||
def run(self, code: str, timeout: float = 15.0,
|
||||
env: Optional[Dict[str, str]] = None) -> ExecResult:
|
||||
"""Run `code` to completion in the sandbox. Exit 0 == passed."""
|
||||
workdir = Path(tempfile.mkdtemp(prefix=f"{self.name}-", dir="/tmp"))
|
||||
try:
|
||||
prog = workdir / "candidate.py"
|
||||
prog.write_text(code)
|
||||
return self.run_argv([str(self.python), "-I", "-B", str(prog)],
|
||||
workdir, timeout=timeout, env=env)
|
||||
except Exception as e:
|
||||
return ExecResult(False, "error", "", f"{type(e).__name__}: {e}")
|
||||
finally:
|
||||
shutil.rmtree(workdir, ignore_errors=True)
|
||||
|
||||
|
||||
def _kill_tree(proc: subprocess.Popen):
|
||||
"""Kill the candidate and everything it spawned."""
|
||||
for sig in (signal.SIGKILL,):
|
||||
try:
|
||||
os.killpg(os.getpgid(proc.pid), sig)
|
||||
except (ProcessLookupError, PermissionError):
|
||||
try:
|
||||
proc.kill()
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
+1241
-34
File diff suppressed because it is too large
Load Diff
@@ -13,8 +13,6 @@ from typing import Dict, List, Optional
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
import datasets
|
||||
|
||||
# Set cache directory for HuggingFace datasets
|
||||
cache_dir = Path.home() / ".cache" / "huggingface" / "datasets"
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -76,6 +74,8 @@ class AimeDataset:
|
||||
return question.get("problem", question.get("question", ""))
|
||||
|
||||
def _load_dataset(self):
|
||||
import datasets
|
||||
|
||||
if self.dataset_type == "aime":
|
||||
print(f"Loading AIME dataset (split: {self.split})...")
|
||||
cache_path = Path.home() / ".cache" / "huggingface" / "datasets" / "AI-MO___aimo-validation-aime" / "default" / "0.0.0"
|
||||
@@ -164,6 +164,58 @@ class AimeDataset:
|
||||
return str(normalized) if normalized is not None else answer
|
||||
return str(answer)
|
||||
|
||||
class CodeDataset:
|
||||
"""Stand-in for a code model: replays canonical solutions as chat replies.
|
||||
|
||||
Handles both code suites: HumanEval keys off the function stub, ClassEval
|
||||
off the class skeleton.
|
||||
"""
|
||||
|
||||
DEFAULT_SOURCE = {
|
||||
"humaneval": "openai/openai_humaneval",
|
||||
"classeval": "FudanSELab/ClassEval",
|
||||
}
|
||||
|
||||
def __init__(self, dataset_type: str, source: Optional[str] = None):
|
||||
self.dataset_type = dataset_type
|
||||
self.source = source or self.DEFAULT_SOURCE[dataset_type]
|
||||
self.questions: List[Dict] = []
|
||||
self._load_dataset()
|
||||
|
||||
def _load_dataset(self):
|
||||
print(f"Loading {self.dataset_type} dataset (source: {self.source})...")
|
||||
local = Path(self.source)
|
||||
if local.exists():
|
||||
text = local.read_text()
|
||||
if local.suffix == ".json":
|
||||
rows = json.loads(text)
|
||||
else:
|
||||
rows = [json.loads(ln) for ln in text.splitlines() if ln.strip()]
|
||||
else:
|
||||
import datasets
|
||||
rows = [dict(r) for r in datasets.load_dataset(self.source, split="test")]
|
||||
self.questions = rows
|
||||
print(f"{self.dataset_type} dataset loaded: {len(self.questions)} questions")
|
||||
|
||||
def _stub_field(self) -> str:
|
||||
return "skeleton" if self.dataset_type == "classeval" else "prompt"
|
||||
|
||||
def _get_question_text(self, question: Dict) -> str:
|
||||
return question[self._stub_field()]
|
||||
|
||||
def find_question(self, request_text: str) -> Optional[Dict]:
|
||||
for question in self.questions:
|
||||
stub = question[self._stub_field()].rstrip()
|
||||
if stub and stub in request_text:
|
||||
return question
|
||||
return None
|
||||
|
||||
def get_answer(self, question: Dict) -> str:
|
||||
if self.dataset_type == "classeval":
|
||||
return question["solution_code"]
|
||||
return question["prompt"] + question["canonical_solution"]
|
||||
|
||||
|
||||
class Simulator:
|
||||
def __init__(
|
||||
self,
|
||||
@@ -171,12 +223,17 @@ class Simulator:
|
||||
host: str = "localhost",
|
||||
success_rate: float = 0.8,
|
||||
dataset_split: str = "train",
|
||||
dataset_type: str = "aime"
|
||||
dataset_type: str = "aime",
|
||||
dataset_source: Optional[str] = None
|
||||
):
|
||||
self.port = port
|
||||
self.host = host
|
||||
self.success_rate = success_rate
|
||||
self.dataset = AimeDataset(dataset_split, dataset_type)
|
||||
self.dataset_type = dataset_type
|
||||
if dataset_type in ("humaneval", "classeval"):
|
||||
self.dataset = CodeDataset(dataset_type, dataset_source)
|
||||
else:
|
||||
self.dataset = AimeDataset(dataset_split, dataset_type)
|
||||
self.eval_state = EvalState(
|
||||
id=dataset_type,
|
||||
tasks=[dataset_type],
|
||||
@@ -196,6 +253,12 @@ class Simulator:
|
||||
else:
|
||||
response_text = self._generate_wrong_answer(question)
|
||||
|
||||
if self.dataset_type in ("humaneval", "classeval"):
|
||||
response_text = (
|
||||
f"Here is the implementation:\n\n```python\n{response_text}\n```\n\n"
|
||||
"Let me know if you want it optimised further."
|
||||
)
|
||||
|
||||
comp_tokens = random.randint(10000, 60000)
|
||||
tps_gen = random.uniform(90.0, 110.0)
|
||||
t_gen_ms = comp_tokens / tps_gen * 1000
|
||||
@@ -229,6 +292,12 @@ class Simulator:
|
||||
def _generate_wrong_answer(self, question: Dict) -> str:
|
||||
expected_answer = self.dataset.get_answer(question)
|
||||
|
||||
if self.dataset_type == "classeval":
|
||||
return re.sub(r"(\n\s+def [^\n]+:\n)", r"\1 return None\n",
|
||||
question["skeleton"])
|
||||
if self.dataset_type == "humaneval":
|
||||
return question["prompt"] + " return None\n"
|
||||
|
||||
if expected_answer.isdigit():
|
||||
wrong_answer = str(int(expected_answer) + 1)
|
||||
else:
|
||||
@@ -335,7 +404,7 @@ def main():
|
||||
"--dataset",
|
||||
type=str,
|
||||
default="aime",
|
||||
choices=["aime", "aime2025"],
|
||||
choices=["aime", "aime2025", "humaneval", "classeval"],
|
||||
help="Dataset type (default: aime)"
|
||||
)
|
||||
parser.add_argument(
|
||||
@@ -344,6 +413,12 @@ def main():
|
||||
default="train",
|
||||
help="AIME dataset split to use (default: train)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dataset-source",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Override the dataset location (local .jsonl path or HuggingFace repo id)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -353,7 +428,8 @@ def main():
|
||||
host=args.host,
|
||||
success_rate=args.success_rate,
|
||||
dataset_split=args.dataset_split,
|
||||
dataset_type=args.dataset
|
||||
dataset_type=args.dataset,
|
||||
dataset_source=args.dataset_source
|
||||
)
|
||||
|
||||
server = HTTPServer((args.host, args.port), RequestHandler)
|
||||
|
||||
+23
-10
@@ -1292,24 +1292,37 @@ static void ggml_compute_forward_sum_f32(
|
||||
}
|
||||
|
||||
assert(ggml_is_scalar(dst));
|
||||
assert(src0->nb[0] == sizeof(float));
|
||||
|
||||
GGML_TENSOR_LOCALS(int64_t, ne0, src0, ne)
|
||||
GGML_TENSOR_LOCALS(size_t, nb0, src0, nb)
|
||||
|
||||
ggml_float sum = 0;
|
||||
ggml_float row_sum = 0;
|
||||
ggml_float sum = 0;
|
||||
|
||||
for (int64_t i03 = 0; i03 < ne03; i03++) {
|
||||
for (int64_t i02 = 0; i02 < ne02; i02++) {
|
||||
for (int64_t i01 = 0; i01 < ne01; i01++) {
|
||||
ggml_vec_sum_f32_ggf(ne00,
|
||||
&row_sum,
|
||||
(float *) ((char *) src0->data + i01*nb01 + i02*nb02 + i03*nb03));
|
||||
sum += row_sum;
|
||||
if (nb00 == sizeof(float)) {
|
||||
ggml_float row_sum = 0;
|
||||
for (int64_t i03 = 0; i03 < ne03; i03++) {
|
||||
for (int64_t i02 = 0; i02 < ne02; i02++) {
|
||||
for (int64_t i01 = 0; i01 < ne01; i01++) {
|
||||
ggml_vec_sum_f32_ggf(ne00,
|
||||
&row_sum,
|
||||
(float *) ((char *) src0->data + i01*nb01 + i02*nb02 + i03*nb03));
|
||||
sum += row_sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int64_t i03 = 0; i03 < ne03; i03++) {
|
||||
for (int64_t i02 = 0; i02 < ne02; i02++) {
|
||||
for (int64_t i01 = 0; i01 < ne01; i01++) {
|
||||
const char * row = (const char *) src0->data + i01*nb01 + i02*nb02 + i03*nb03;
|
||||
for (int64_t i00 = 0; i00 < ne00; i00++) {
|
||||
sum += (ggml_float) *(const float *) (row + i00*nb00);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
((float *) dst->data)[0] = sum;
|
||||
}
|
||||
|
||||
|
||||
@@ -401,6 +401,10 @@ void ggml_vec_silu_f32(const int n, float * y, const float * x) {
|
||||
for (; i + 3 < n; i += 4) {
|
||||
vst1q_f32(y + i, ggml_v_silu(vld1q_f32(x + i)));
|
||||
}
|
||||
#elif defined(__VXE__) || defined(__VXE2__)
|
||||
for (; i + 3 < n; i += 4) {
|
||||
vec_xst(ggml_v_silu(vec_xl(0, x + i)), 0, y + i);
|
||||
}
|
||||
#elif defined(__riscv_v_intrinsic)
|
||||
for (int vl; i < n; i += vl) {
|
||||
vl = __riscv_vsetvl_e32m2(n - i);
|
||||
@@ -438,6 +442,10 @@ void ggml_vec_swiglu_f32(const int n, float * y, const float * x, const float *
|
||||
for (; i + 3 < n; i += 4) {
|
||||
vst1q_f32(y + i, vmulq_f32(ggml_v_silu(vld1q_f32(x + i)), vld1q_f32(g + i)));
|
||||
}
|
||||
#elif defined(__VXE__) || defined(__VXE2__)
|
||||
for (; i + 3 < n; i += 4) {
|
||||
vec_xst(vec_mul(ggml_v_silu(vec_xl(0, x + i)), vec_xl(0, g + i)), 0, y + i);
|
||||
}
|
||||
#elif defined(__riscv_v_intrinsic)
|
||||
for (int vl; i < n; i += vl) {
|
||||
vl = __riscv_vsetvl_e32m2(n - i);
|
||||
@@ -581,6 +589,12 @@ ggml_float ggml_vec_soft_max_f32(const int n, float * y, const float * x, float
|
||||
vst1q_f32(y + i, val);
|
||||
sum += (ggml_float)vaddvq_f32(val);
|
||||
}
|
||||
#elif defined(__VXE__) || defined(__VXE2__)
|
||||
for (; i + 3 < n; i += 4) {
|
||||
float32x4_t v_val = ggml_v_expf(vec_sub(vec_xl(0, x + i), vec_splats(max)));
|
||||
vec_xst(v_val, 0, y + i);
|
||||
sum += (ggml_float)vec_hsum_f32x4(v_val);
|
||||
}
|
||||
#elif defined(__riscv_v_intrinsic)
|
||||
vfloat64m1_t vsum = __riscv_vfmv_v_f_f64m1(0, 1);
|
||||
for (int avl; i < n; i += avl) {
|
||||
|
||||
+41
-1
@@ -1334,6 +1334,46 @@ inline static __m128 ggml_v_silu(__m128 x) {
|
||||
return _mm_div_ps(x, one_plus_exp_neg_x);
|
||||
}
|
||||
|
||||
#elif defined(__VXE__) || defined(__VXE2__)
|
||||
|
||||
// adapted from arm limited optimized routine
|
||||
// the maximum error is 1.45358 plus 0.5 ulps
|
||||
// numbers above 88.38 will flush to infinity
|
||||
// numbers beneath -103.97 will flush to zero
|
||||
inline static float32x4_t ggml_v_expf(float32x4_t v_x) {
|
||||
const float32x4_t v_r = vec_splats(0x1.8p23f);
|
||||
const float32x4_t v_z = vec_madd(v_x, vec_splats(0x1.715476p+0f), v_r);
|
||||
const float32x4_t v_n = vec_sub(v_z, v_r);
|
||||
const float32x4_t v_b = vec_nmsub(v_n, vec_splats(0x1.7f7d1cp-20f),
|
||||
vec_nmsub(v_n, vec_splats(0x1.62e4p-1f), v_x));
|
||||
const uint32x4_t v_e = vec_sl((uint32x4_t)v_z, 23);
|
||||
const float32x4_t v_k = (float32x4_t)vec_add(v_e, (uint32x4_t)vec_splats(1.0f));
|
||||
const uint32x4_t v_c = (uint32x4_t)vec_cmpgt(vec_abs(v_n), vec_splats(126.0f));
|
||||
const float32x4_t v_u = vec_mul(v_b, v_b);
|
||||
const float32x4_t v_j = vec_madd(
|
||||
vec_madd(vec_madd(vec_splats(0x1.0e4020p-7f), v_b, vec_splats(0x1.573e2ep-5f)), v_u,
|
||||
vec_madd(vec_splats(0x1.555e66p-3f), v_b, vec_splats(0x1.fffdb6p-2f))),
|
||||
v_u, vec_mul(vec_splats(0x1.ffffecp-1f), v_b));
|
||||
if (vec_all_eq(v_c, vec_splats(UINT32_C(0)))) {
|
||||
return vec_madd(v_j, v_k, v_k);
|
||||
}
|
||||
const uint32x4_t v_d = vec_and((uint32x4_t)vec_cmple(v_n, vec_splats(0.0f)), vec_splats(UINT32_C(0x82000000)));
|
||||
const float32x4_t v_s1 = (float32x4_t)vec_add(v_d, vec_splats(UINT32_C(0x7f000000)));
|
||||
const float32x4_t v_s2 = (float32x4_t)vec_sub(v_e, v_d);
|
||||
return vec_sel(vec_sel(vec_madd(v_k, v_j, v_k), vec_mul(vec_madd(v_s2, v_j, v_s2), v_s1), v_c),
|
||||
vec_mul(v_s1, v_s1), (uint32x4_t)vec_cmpgt(vec_abs(v_n), vec_splats(192.0f)));
|
||||
}
|
||||
|
||||
// computes silu x/(1+exp(-x)) in single precision vector
|
||||
inline static float32x4_t ggml_v_silu(float32x4_t v_x) {
|
||||
const float32x4_t v_one = vec_splats(1.0f);
|
||||
const float32x4_t v_zero = vec_splats(0.0f);
|
||||
const float32x4_t v_neg_x = vec_sub(v_zero, v_x);
|
||||
const float32x4_t v_exp_neg_x = ggml_v_expf(v_neg_x);
|
||||
const float32x4_t v_one_plus_exp_neg_x = vec_add(v_one, v_exp_neg_x);
|
||||
return vec_div(v_x, v_one_plus_exp_neg_x);
|
||||
}
|
||||
|
||||
#elif defined(__riscv_v_intrinsic)
|
||||
|
||||
// adapted from arm limited optimized routine
|
||||
@@ -1386,7 +1426,7 @@ inline static vfloat32m2_t ggml_v_silu_m2(vfloat32m2_t x, int vl) {
|
||||
return __riscv_vfdiv_vv_f32m2(x, one_plus_exp_neg_x, vl);
|
||||
}
|
||||
|
||||
#endif // __ARM_NEON / __AVX2__ / __SSE2__ / __riscv_v_intrinsic
|
||||
#endif // __ARM_NEON / __AVX2__ / __SSE2__ / __VXE__ / __riscv_v_intrinsic
|
||||
|
||||
inline static void ggml_vec_silu_f16(const int n, ggml_fp16_t * y, const ggml_fp16_t * x) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
|
||||
@@ -145,7 +145,8 @@ static __global__ void dsv4_hc_pre_f32(
|
||||
dst[i0*sd0 + it*sd1] = scale * sum;
|
||||
}
|
||||
|
||||
template <bool has_comb>
|
||||
// gated: post = gate_scale_out*sigmoid(gate_scale_in*post), the fused SCALE -> SIGMOID -> SCALE chain of qwen4exp
|
||||
template <bool has_comb, bool gated>
|
||||
static __global__ void dsv4_hc_post_f32(
|
||||
const float * x,
|
||||
const float * residual,
|
||||
@@ -167,7 +168,9 @@ static __global__ void dsv4_hc_post_f32(
|
||||
int64_t sc2,
|
||||
int64_t sd0,
|
||||
int64_t sd1,
|
||||
int64_t sd2) {
|
||||
int64_t sd2,
|
||||
float gate_scale_in,
|
||||
float gate_scale_out) {
|
||||
ggml_cuda_pdl_lc();
|
||||
const int64_t ir = (int64_t) blockIdx.x * blockDim.x + threadIdx.x;
|
||||
const int64_t nr = n_embd * hc * n_tokens;
|
||||
@@ -182,7 +185,12 @@ static __global__ void dsv4_hc_post_f32(
|
||||
const int64_t idst = (ir / n_embd) % hc;
|
||||
const int64_t it = ir / (n_embd * hc);
|
||||
|
||||
float sum = x[i0*sx0 + it*sx1] * post[idst*sp0 + it*sp1];
|
||||
float p = post[idst*sp0 + it*sp1];
|
||||
if constexpr (gated) {
|
||||
p = gate_scale_out * (1.0f / (1.0f + expf(-(gate_scale_in * p))));
|
||||
}
|
||||
|
||||
float sum = x[i0*sx0 + it*sx1] * p;
|
||||
if constexpr (has_comb) {
|
||||
for (int64_t isrc = 0; isrc < hc; ++isrc) {
|
||||
sum += residual[i0*sr0 + isrc*sr1 + it*sr2] * comb[idst*sc0 + isrc*sc1 + it*sc2];
|
||||
@@ -272,12 +280,19 @@ void ggml_cuda_op_dsv4_hc_pre(ggml_backend_cuda_context & ctx, ggml_tensor * dst
|
||||
scale);
|
||||
}
|
||||
|
||||
void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * dst,
|
||||
const ggml_tensor * gate_scale_in, const ggml_tensor * gate_scale_out) {
|
||||
const ggml_tensor * x = dst->src[0];
|
||||
const ggml_tensor * residual = dst->src[1];
|
||||
const ggml_tensor * post = dst->src[2];
|
||||
const ggml_tensor * comb = dst->src[3];
|
||||
|
||||
// when fused, read the raw post weights from before the SCALE -> SIGMOID -> SCALE chain
|
||||
const bool gated = gate_scale_in != nullptr;
|
||||
const ggml_tensor * post = gated ? gate_scale_in->src[0] : dst->src[2];
|
||||
|
||||
const float gate_in = gated ? ggml_get_op_params_f32(gate_scale_in, 0) : 1.0f;
|
||||
const float gate_out = gated ? ggml_get_op_params_f32(gate_scale_out, 0) : 1.0f;
|
||||
|
||||
GGML_ASSERT(x->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(residual->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(post->type == GGML_TYPE_F32);
|
||||
@@ -303,7 +318,8 @@ void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * ds
|
||||
const dim3 grid_dims((nr + block_size - 1) / block_size, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(grid_dims, block_dims, 0, ctx.stream());
|
||||
|
||||
auto kernel = comb ? dsv4_hc_post_f32<true> : dsv4_hc_post_f32<false>;
|
||||
auto kernel = comb ? (gated ? dsv4_hc_post_f32<true, true> : dsv4_hc_post_f32<true, false>)
|
||||
: (gated ? dsv4_hc_post_f32<false, true> : dsv4_hc_post_f32<false, false>);
|
||||
ggml_cuda_kernel_launch(kernel, launch_params,
|
||||
(const float *) x->data, (const float *) residual->data,
|
||||
(const float *) post->data, comb ? (const float *) comb->data : nullptr, (float *) dst->data,
|
||||
@@ -312,5 +328,6 @@ void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * ds
|
||||
nbr0 / sizeof(float), nbr1 / sizeof(float), nbr2 / sizeof(float),
|
||||
nbp0 / sizeof(float), nbp1 / sizeof(float),
|
||||
nbc0 / sizeof(float), nbc1 / sizeof(float), nbc2 / sizeof(float),
|
||||
nbd0 / sizeof(float), nbd1 / sizeof(float), nbd2 / sizeof(float));
|
||||
nbd0 / sizeof(float), nbd1 / sizeof(float), nbd2 / sizeof(float),
|
||||
gate_in, gate_out);
|
||||
}
|
||||
|
||||
@@ -3,4 +3,6 @@
|
||||
|
||||
void ggml_cuda_op_dsv4_hc_comb(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
void ggml_cuda_op_dsv4_hc_pre(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
// gate_scale_in/gate_scale_out: the SCALE nodes of a fused SCALE -> SIGMOID -> SCALE chain feeding src[2]
|
||||
void ggml_cuda_op_dsv4_hc_post(ggml_backend_cuda_context & ctx, ggml_tensor * dst,
|
||||
const ggml_tensor * gate_scale_in = nullptr, const ggml_tensor * gate_scale_out = nullptr);
|
||||
|
||||
@@ -955,7 +955,7 @@ static __global__ void flash_attn_tile(
|
||||
if (ncols2 == 1) {
|
||||
// Branch with out-of-bounds checks.
|
||||
int k_VKQ_0 = blockIdx.y*nbatch_fa;
|
||||
while (k_VKQ_0 < k_VKQ_max - nbatch_fa) {
|
||||
while (k_VKQ_0 <= k_VKQ_max - nbatch_fa) {
|
||||
constexpr bool oob_check = false;
|
||||
flash_attn_tile_iter<warp_size, nwarps, ncols1, ncols2, DKQ, DV, nbatch_fa, nbatch_K, use_logit_softcap, oob_check>
|
||||
(Q_tmp, K_h2, V_h2, maskh, ne01, logit_softcap, slope, KQ, KV_tmp,
|
||||
|
||||
@@ -317,7 +317,7 @@ static __global__ void flash_attn_ext_vec(
|
||||
#endif // V_DOT2_F32_F16_AVAILABLE
|
||||
}
|
||||
|
||||
ggml_cuda_syncwarp();
|
||||
ggml_cuda_syncwarp(); // Needed for KQ synchronization.
|
||||
|
||||
#pragma unroll
|
||||
for (int k0 = 0; k0 < WARP_SIZE; k0 += V_cols_per_iter) {
|
||||
@@ -374,6 +374,8 @@ static __global__ void flash_attn_ext_vec(
|
||||
}
|
||||
#endif // V_DOT2_F32_F16_AVAILABLE
|
||||
}
|
||||
|
||||
ggml_cuda_syncwarp(); // Needed for KQ synchronization.
|
||||
}
|
||||
|
||||
if (sinks && blockIdx.y == 0) {
|
||||
|
||||
@@ -417,9 +417,7 @@ static void ggml_cuda_flash_attn_ext_mma_f16(ggml_backend_cuda_context & ctx, gg
|
||||
|
||||
#define FATTN_VEC_CASE(D, type_K_case, type_V_case) \
|
||||
if constexpr (GGML_CUDA_FA_##type_K_case##_##type_V_case) { \
|
||||
const bool type_K_okay = type_K == GGML_TYPE_##type_K_case || (type_K == GGML_TYPE_F32 && GGML_TYPE_##type_K_case == GGML_TYPE_F16); \
|
||||
const bool type_V_okay = type_V == GGML_TYPE_##type_V_case || (type_V == GGML_TYPE_F32 && GGML_TYPE_##type_V_case == GGML_TYPE_F16); \
|
||||
if (head_size == (D) && type_K_okay && type_V_okay) { \
|
||||
if (head_size == (D) && type_K == GGML_TYPE_##type_K_case && type_V == GGML_TYPE_##type_V_case) { \
|
||||
return ggml_cuda_flash_attn_ext_vec_case<D, GGML_TYPE_##type_K_case, GGML_TYPE_##type_V_case>; \
|
||||
} \
|
||||
} \
|
||||
@@ -432,7 +430,9 @@ static void ggml_cuda_flash_attn_ext_mma_f16(ggml_backend_cuda_context & ctx, gg
|
||||
typedef void (* fattn_vec_case_t)(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
// Vector kernel for the given head size and K/V types, nullptr if its template instance was not compiled:
|
||||
static fattn_vec_case_t ggml_cuda_get_fattn_vec_case(const int64_t head_size, const ggml_type type_K, const ggml_type type_V) {
|
||||
static fattn_vec_case_t ggml_cuda_get_fattn_vec_case(const int64_t head_size, const ggml_type type_K_in, const ggml_type type_V_in) {
|
||||
const ggml_type type_K = type_K_in == GGML_TYPE_F32 ? GGML_TYPE_F16 : type_K_in;
|
||||
const ggml_type type_V = type_V_in == GGML_TYPE_F32 ? GGML_TYPE_F16 : type_V_in;
|
||||
FATTN_VEC_CASES_ALL_D(F16, F16)
|
||||
FATTN_VEC_CASES_ALL_D(Q4_0, F16)
|
||||
FATTN_VEC_CASES_ALL_D(Q4_1, F16)
|
||||
|
||||
+111
-17
@@ -3029,6 +3029,65 @@ static int ggml_cuda_try_ssm_scan_cache_fusion(
|
||||
return skip;
|
||||
}
|
||||
|
||||
// expert group selection from build_moe_ffn (n_expert_groups > 1)
|
||||
// src0/src1 index into this table, -1 is the (biased) probs, -2 is not checked
|
||||
static const struct {
|
||||
ggml_op op;
|
||||
int src0;
|
||||
int src1;
|
||||
} topk_moe_group_pattern[] = {
|
||||
{ GGML_OP_RESHAPE, -1, -2 }, // 0: [n_exp_per_group, n_expert_groups, n_tokens]
|
||||
{ GGML_OP_RESHAPE, 0, -2 }, // 1: [1, n_exp_per_group, n_expert_groups, n_tokens]
|
||||
{ GGML_OP_ARGSORT, 0, -2 }, // 2
|
||||
{ GGML_OP_VIEW, 2, -2 }, // 3: top-2 per group
|
||||
{ GGML_OP_GET_ROWS, 1, 3 }, // 4
|
||||
{ GGML_OP_RESHAPE, 4, -2 }, // 5
|
||||
{ GGML_OP_SUM_ROWS, 5, -2 }, // 6: group scores
|
||||
{ GGML_OP_RESHAPE, 6, -2 }, // 7
|
||||
{ GGML_OP_ARGSORT, 7, -2 }, // 8
|
||||
{ GGML_OP_VIEW, 8, -2 }, // 9: top n_group_used groups
|
||||
{ GGML_OP_GET_ROWS, 0, 9 }, // 10
|
||||
{ GGML_OP_FILL, 0, -2 }, // 11
|
||||
{ GGML_OP_SET_ROWS, 10, 9 }, // 12: unused groups -> -inf
|
||||
{ GGML_OP_RESHAPE, 12, -2 }, // 13: [n_expert, n_tokens]
|
||||
};
|
||||
|
||||
static bool ggml_cuda_topk_moe_group_fusion(const struct ggml_cgraph * cgraph, int node_idx, const ggml_tensor * selection, ggml_cuda_topk_moe_args & args) {
|
||||
#if defined(GGML_USE_HIP) && HIP_VERSION < 60300000
|
||||
GGML_UNUSED_VARS(cgraph, node_idx, selection, args);
|
||||
return false;
|
||||
#else
|
||||
const int n_ops = (int) std::size(topk_moe_group_pattern);
|
||||
if (node_idx + n_ops > cgraph->n_nodes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ggml_tensor ** nodes = cgraph->nodes + node_idx;
|
||||
auto src_ok = [&](const ggml_tensor * src, int idx) {
|
||||
return idx == -2 || src == (idx == -1 ? selection : nodes[idx]);
|
||||
};
|
||||
|
||||
for (int i = 0; i < n_ops; i++) {
|
||||
const auto & p = topk_moe_group_pattern[i];
|
||||
if (nodes[i]->op != p.op || !src_ok(nodes[i]->src[0], p.src0) || !src_ok(nodes[i]->src[1], p.src1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const int n_groups = nodes[0]->ne[1];
|
||||
const int n_group_used = nodes[9]->ne[0];
|
||||
|
||||
if (nodes[3]->ne[0] != 2 || nodes[12]->src[2] != nodes[11] || ggml_get_op_params_f32(nodes[11], 0) != -INFINITY ||
|
||||
n_groups > WARP_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.n_groups = n_groups;
|
||||
args.n_group_used = n_group_used;
|
||||
return true;
|
||||
#endif // defined(GGML_USE_HIP) && HIP_VERSION < 60300000
|
||||
}
|
||||
|
||||
static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int node_idx, ggml_cuda_topk_moe_args & args) {
|
||||
args.sigmoid = false;
|
||||
args.sqrt_softplus = false;
|
||||
@@ -3036,6 +3095,8 @@ static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int nod
|
||||
args.delayed_softmax = false;
|
||||
args.prob_bias = false;
|
||||
args.norm = false;
|
||||
args.n_groups = 0;
|
||||
args.n_group_used = 0;
|
||||
|
||||
const int n_nodes = cgraph->n_nodes;
|
||||
ggml_tensor ** nodes = cgraph->nodes;
|
||||
@@ -3078,18 +3139,20 @@ static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int nod
|
||||
}
|
||||
|
||||
// src of bias add is the unreshaped probs (-2 instead of -1)
|
||||
if (nodes[node_idx]->op == GGML_OP_ADD && nodes[node_idx]->src[0] == nodes[node_idx - 2]) {
|
||||
const ggml_tensor * selection = nodes[node_idx - 2];
|
||||
if (nodes[node_idx]->op == GGML_OP_ADD && nodes[node_idx]->src[0] == selection) {
|
||||
args.prob_bias = true;
|
||||
selection = nodes[node_idx];
|
||||
node_idx++;
|
||||
}
|
||||
// RESHAPE/ADD -> ARGSORT
|
||||
if (node_idx >= n_nodes || nodes[node_idx]->op != GGML_OP_ARGSORT) {
|
||||
return false;
|
||||
|
||||
if (ggml_cuda_topk_moe_group_fusion(cgraph, node_idx, selection, args)) {
|
||||
node_idx += (int) std::size(topk_moe_group_pattern);
|
||||
selection = nodes[node_idx - 1];
|
||||
}
|
||||
|
||||
if (args.prob_bias && nodes[node_idx]->src[0] != nodes[node_idx - 1]) {
|
||||
return false;
|
||||
} else if (!args.prob_bias && nodes[node_idx]->src[0] != nodes[node_idx - 2]) {
|
||||
// RESHAPE/ADD/group mask -> ARGSORT
|
||||
if (node_idx >= n_nodes || nodes[node_idx]->op != GGML_OP_ARGSORT || nodes[node_idx]->src[0] != selection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3100,6 +3163,11 @@ static bool ggml_cuda_topk_moe_fusion(const struct ggml_cgraph * cgraph, int nod
|
||||
nodes[node_idx]->src[0] != nodes[node_idx - 1]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// kept groups must hold all used experts
|
||||
if (args.n_groups > 0 && nodes[node_idx]->ne[0] > args.n_group_used * (probs_reshaped->ne[1] / args.n_groups)) {
|
||||
return false;
|
||||
}
|
||||
node_idx++;
|
||||
|
||||
if (node_idx >= n_nodes || nodes[node_idx]->op != GGML_OP_GET_ROWS) {
|
||||
@@ -3482,6 +3550,26 @@ static bool ggml_cuda_can_fuse(const struct ggml_cgraph * cgraph,
|
||||
}
|
||||
}
|
||||
|
||||
// scale_out*sigmoid(scale_in*post) as the hc_post weights (qwen4exp hc_combine)
|
||||
std::initializer_list<enum ggml_op> hc_post_gate_ops = { GGML_OP_SCALE, GGML_OP_UNARY, GGML_OP_SCALE, GGML_OP_DSV4_HC_POST };
|
||||
|
||||
if (is_equal(hc_post_gate_ops, ops) && ggml_can_fuse_subgraph(cgraph, node_idx, ops, { node_idx + 3 })) {
|
||||
const ggml_tensor * scale_in = cgraph->nodes[node_idx];
|
||||
const ggml_tensor * sigmoid = cgraph->nodes[node_idx + 1];
|
||||
const ggml_tensor * scale_out = cgraph->nodes[node_idx + 2];
|
||||
|
||||
// the chain must feed hc_post->src[2]; a bias on either scale is not handled
|
||||
if (ggml_check_edges(cgraph, node_idx, {{1, 0, 0}, {2, 0, 1}, {3, 2, 2}}) &&
|
||||
ggml_get_unary_op(sigmoid) == GGML_UNARY_OP_SIGMOID &&
|
||||
ggml_get_op_params_f32(scale_in, 1) == 0.0f && ggml_get_op_params_f32(scale_out, 1) == 0.0f &&
|
||||
scale_in->src[0]->type == GGML_TYPE_F32 && scale_out->type == GGML_TYPE_F32 &&
|
||||
ggml_are_same_shape(scale_in->src[0], scale_out)) {
|
||||
int out_nodes[] = { node_idx + 3 };
|
||||
return ggml_cuda_check_fusion_memory_ranges(cgraph, node_idx, (int)ops.size(), out_nodes, 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ggml_can_fuse(cgraph, node_idx, ops)) {
|
||||
return false;
|
||||
}
|
||||
@@ -3754,17 +3842,18 @@ static int ggml_cuda_try_fuse(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph
|
||||
} else {
|
||||
ops.insert(ops.end(), { GGML_OP_SOFT_MAX });
|
||||
}
|
||||
const int i_probs = i + (int) ops.size() - 1; // last node of the gating activation
|
||||
|
||||
ops.push_back(GGML_OP_RESHAPE);
|
||||
if (args.prob_bias) {
|
||||
bias = cgraph->nodes[i_probs + 2]->src[1];
|
||||
ops.insert(ops.end(), { GGML_OP_RESHAPE, GGML_OP_ADD, GGML_OP_ARGSORT, GGML_OP_VIEW,
|
||||
GGML_OP_GET_ROWS });
|
||||
out_nodes[0] = i_probs + 4;
|
||||
} else {
|
||||
ops.insert(ops.end(), { GGML_OP_RESHAPE, GGML_OP_ARGSORT, GGML_OP_VIEW, GGML_OP_GET_ROWS });
|
||||
out_nodes[0] = i_probs + 3;
|
||||
ops.push_back(GGML_OP_ADD);
|
||||
bias = cgraph->nodes[i + ops.size() - 1]->src[1];
|
||||
}
|
||||
if (args.n_groups > 0) {
|
||||
for (const auto & p : topk_moe_group_pattern) {
|
||||
ops.push_back(p.op);
|
||||
}
|
||||
}
|
||||
ops.insert(ops.end(), { GGML_OP_ARGSORT, GGML_OP_VIEW, GGML_OP_GET_ROWS });
|
||||
out_nodes[0] = i + ops.size() - 2;
|
||||
ids = cgraph->nodes[out_nodes[0]];
|
||||
|
||||
if (args.norm) {
|
||||
@@ -4444,6 +4533,11 @@ static int ggml_cuda_try_fuse(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ggml_cuda_can_fuse(cgraph, i, { GGML_OP_SCALE, GGML_OP_UNARY, GGML_OP_SCALE, GGML_OP_DSV4_HC_POST }, { GGML_UNARY_OP_SIGMOID })) {
|
||||
ggml_cuda_op_dsv4_hc_post(*cuda_ctx, cgraph->nodes[i + 3], node, cgraph->nodes[i + 2]);
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (ggml_cuda_can_fuse(cgraph, i, { GGML_OP_SCALE, GGML_OP_UNARY, GGML_OP_SCALE }, { GGML_UNARY_OP_TANH })) {
|
||||
ggml_cuda_op_softcap(*cuda_ctx, cgraph->nodes[i + 2], node);
|
||||
return 2;
|
||||
@@ -5828,7 +5922,7 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
|
||||
//return ggml_is_contiguous_rows(op->src[0]) && ggml_is_contiguous_rows(op->src[1]);
|
||||
return ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]);
|
||||
case GGML_OP_SUM:
|
||||
return ggml_is_contiguous_rows(op->src[0]);
|
||||
return ggml_is_contiguously_allocated(op->src[0]);
|
||||
case GGML_OP_TOP_K:
|
||||
return op->src[0]->ne[0] <= INT_MAX;
|
||||
case GGML_OP_ARGSORT:
|
||||
|
||||
@@ -46,6 +46,25 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
const int q = qxi[j];
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
const uint32_t q_bits = (uint16_t) q;
|
||||
|
||||
// Expand each group of four Q1_0 bits into byte selectors for v_perm_b32.
|
||||
const uint32_t q0_bits = q_bits >> 0;
|
||||
const uint32_t q1_bits = q_bits >> 4;
|
||||
const uint32_t q2_bits = q_bits >> 8;
|
||||
const uint32_t q3_bits = q_bits >> 12;
|
||||
|
||||
const uint32_t q0_indices = (q0_bits & 0x01) | ((q0_bits & 0x02) << 7) | ((q0_bits & 0x04) << 14) | ((q0_bits & 0x08) << 21);
|
||||
const uint32_t q1_indices = (q1_bits & 0x01) | ((q1_bits & 0x02) << 7) | ((q1_bits & 0x04) << 14) | ((q1_bits & 0x08) << 21);
|
||||
const uint32_t q2_indices = (q2_bits & 0x01) | ((q2_bits & 0x02) << 7) | ((q2_bits & 0x04) << 14) | ((q2_bits & 0x08) << 21);
|
||||
const uint32_t q3_indices = (q3_bits & 0x01) | ((q3_bits & 0x02) << 7) | ((q3_bits & 0x04) << 14) | ((q3_bits & 0x08) << 21);
|
||||
|
||||
const int v0 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q0_indices);
|
||||
const int v1 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q1_indices);
|
||||
const int v2 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q2_indices);
|
||||
const int v3 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q3_indices);
|
||||
#else
|
||||
// unpack crumbs into nibble indices
|
||||
const int n0 = __byte_perm(0x11100100, 0x11100100, q >> 0); // [0, 1, 4, 5] [ 8, 9, 12, 13]
|
||||
const int n1 = __byte_perm(0x11100100, 0x11100100, q >> 2); // [2, 3, 6, 7] [10, 11, 14, 15]
|
||||
@@ -59,6 +78,7 @@ template <ggml_type type, int J, bool fallback> static __device__ __forceinline_
|
||||
const int v1 = __byte_perm(s0, s1, 0x7632);
|
||||
const int v2 = __byte_perm(s2, s3, 0x5410);
|
||||
const int v3 = __byte_perm(s2, s3, 0x7632);
|
||||
#endif // defined(GGML_USE_HIP)
|
||||
|
||||
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
x_qs[i*sram_stride + dst_offset + j*4+0] = v0;
|
||||
|
||||
@@ -1441,10 +1441,12 @@ static void launch_mul_mat_q(ggml_backend_cuda_context & ctx, const mmq_args & a
|
||||
const int ntiles_dst = ntx * nty * ntzw;
|
||||
const int tiles_nwaves = (ntiles_dst + nsm - 1) / nsm;
|
||||
const int tiles_efficiency_percent = 100 * ntiles_dst / (nsm*tiles_nwaves);
|
||||
const dim3 block_nums_stream_k(GGML_CUDA_CC_IS_NVIDIA(cc) && tiles_efficiency_percent >= 90 ? ntiles_dst : nsm, 1, 1);
|
||||
|
||||
GGML_ASSERT(ntiles_dst * blocks_per_ne00_fd.z < (1 << 30)); // Assert that variable kbc will not overflow.
|
||||
|
||||
const int nwork = ntiles_dst * ((args.ncols_x + config.K_vram - 1) / config.K_vram); // Maximum number of CUDA blocks that can do useful work.
|
||||
const dim3 block_nums_stream_k(GGML_CUDA_CC_IS_NVIDIA(cc) && tiles_efficiency_percent >= 90 ? ntiles_dst : std::min(nsm, nwork), 1, 1);
|
||||
|
||||
const bool fixup_needed = ntiles_dst % block_nums_stream_k.x != 0;
|
||||
|
||||
ggml_cuda_pool & pool = ctx.pool(id);
|
||||
|
||||
@@ -468,10 +468,12 @@ static constexpr __host__ __device__ int calc_nwarps(ggml_type type, int ncols_d
|
||||
} else if (table_id == MMVQ_PARAMETERS_GCN) {
|
||||
switch (ncols_dst) {
|
||||
case 1:
|
||||
return 2;
|
||||
case 2:
|
||||
case 3:
|
||||
return type == GGML_TYPE_Q3_K ? 1 : 2;
|
||||
case 4:
|
||||
return 2;
|
||||
return type == GGML_TYPE_Q6_K? 1 : 2;
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
@@ -576,11 +578,31 @@ static constexpr __host__ __device__ int calc_nwarps(ggml_type type, int ncols_d
|
||||
return 1;
|
||||
}
|
||||
|
||||
static constexpr __host__ __device__ int calc_rows_per_block(int ncols_dst, int table_id, bool small_k = false, int nwarps = 1) {
|
||||
// Low bit types load few weight bytes per thread, a second row per block shares the q8_1 loads and adds memory parallelism.
|
||||
static constexpr __host__ __device__ bool mmvq_two_rows_ncols1(ggml_type type) {
|
||||
switch (type) {
|
||||
case GGML_TYPE_Q1_0:
|
||||
case GGML_TYPE_Q2_K:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_IQ1_S:
|
||||
case GGML_TYPE_IQ1_M:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr __host__ __device__ int calc_rows_per_block(ggml_type type, int ncols_dst, int table_id, bool small_k = false, int nwarps = 1) {
|
||||
if (table_id == MMVQ_PARAMETERS_GENERIC || table_id == MMVQ_PARAMETERS_GCN || table_id == MMVQ_PARAMETERS_TURING || table_id == MMVQ_PARAMETERS_GB10) {
|
||||
switch (ncols_dst) {
|
||||
case 1:
|
||||
return small_k ? nwarps : 1;
|
||||
if (small_k) {
|
||||
return nwarps;
|
||||
}
|
||||
if (table_id == MMVQ_PARAMETERS_GB10) {
|
||||
return type == GGML_TYPE_IQ2_S || type == GGML_TYPE_IQ3_S ? 2 : 1;
|
||||
}
|
||||
return table_id == MMVQ_PARAMETERS_GENERIC && mmvq_two_rows_ncols1(type) ? 2 : 1;
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
@@ -615,7 +637,7 @@ static __global__ void mul_mat_vec_q(
|
||||
constexpr int vdr = get_vdr_mmvq(type);
|
||||
constexpr mmvq_parameter_table_id table_id = get_device_table_id();
|
||||
constexpr int nwarps = calc_nwarps(type, ncols_dst, table_id, small_k, halve_iters);
|
||||
constexpr int rows_per_cuda_block = calc_rows_per_block(ncols_dst, table_id, small_k, nwarps);
|
||||
constexpr int rows_per_cuda_block = calc_rows_per_block(type, ncols_dst, table_id, small_k, nwarps);
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
|
||||
constexpr vec_dot_q_cuda_t vec_dot_q_cuda = get_vec_dot_q_cuda(type);
|
||||
@@ -727,7 +749,8 @@ static __global__ void mul_mat_vec_q(
|
||||
#if __CUDA_ARCH__ == GGML_CUDA_CC_DGX_SPARK
|
||||
// start the next iterations' weight loads early
|
||||
if constexpr (mmvq_should_prefetch(type)) {
|
||||
constexpr int pf_dist = 2; // loop iterations, not blocks
|
||||
// loop iterations, not blocks; q3_K and q6_K keep the distance in blocks they had with vdr 1
|
||||
constexpr int pf_dist = type == GGML_TYPE_Q3_K || type == GGML_TYPE_Q6_K ? 1 : 2;
|
||||
const int kbx_pf = kbx + pf_dist*blocks_per_iter;
|
||||
if (kbx_pf < blocks_per_row_x) {
|
||||
#pragma unroll
|
||||
@@ -1013,7 +1036,7 @@ static std::pair<dim3, dim3> calc_launch_params(
|
||||
const int ncols_dst, const int nrows_x, const int nchannels_dst, const int nsamples_or_ntokens,
|
||||
const int warp_size, const mmvq_parameter_table_id table_id, const bool small_k = false, const bool halve_iters = false) {
|
||||
const int nwarps = calc_nwarps(type, ncols_dst, table_id, small_k, halve_iters);
|
||||
const int rpb = calc_rows_per_block(ncols_dst, table_id, small_k, nwarps);
|
||||
const int rpb = calc_rows_per_block(type, ncols_dst, table_id, small_k, nwarps);
|
||||
const int64_t nblocks = (nrows_x + rpb - 1) / rpb;
|
||||
const dim3 block_nums(nblocks, nchannels_dst, nsamples_or_ntokens);
|
||||
const dim3 block_dims(warp_size, nwarps, 1);
|
||||
|
||||
+171
-104
@@ -81,7 +81,7 @@ static __global__ void group_norm_f32(const float * x, float * dst, const int gr
|
||||
}
|
||||
}
|
||||
|
||||
template <int block_size, bool do_multiply = false, bool do_add = false, bool do_scale = false>
|
||||
template <int block_size, int vpt, bool do_vec, bool do_multiply = false, bool do_add = false, bool do_scale = false>
|
||||
static __global__ void rms_norm_f32(const float * x,
|
||||
float * dst,
|
||||
const int ncols,
|
||||
@@ -142,31 +142,82 @@ static __global__ void rms_norm_f32(const float * x,
|
||||
addc = add + add_sample * add_stride_sample + add_channel * add_stride_channel + add_row * add_stride_row;
|
||||
}
|
||||
|
||||
float tmp = 0.0f; // partial sum for thread in warp
|
||||
if constexpr (do_vec) {
|
||||
const int ncols4 = ncols / 4;
|
||||
const float4 * x4 = (const float4 *) xc;
|
||||
const float4 * mul4 = (const float4 *) mulc;
|
||||
const float4 * add4 = (const float4 *) addc;
|
||||
float4 * dst4 = (float4 *) dstc;
|
||||
|
||||
for (int col = tid; col < ncols; col += block_size) {
|
||||
const float xi = xc[col];
|
||||
tmp += xi * xi;
|
||||
}
|
||||
float4 xs[vpt];
|
||||
float tmp = 0.0f;
|
||||
|
||||
// sum up partial sums
|
||||
tmp = block_reduce<block_reduce_method::SUM, block_size>(tmp, s_sum);
|
||||
#pragma unroll
|
||||
for (int k = 0; k < vpt; k++) {
|
||||
const int i = tid + k*block_size;
|
||||
if (i < ncols4) {
|
||||
xs[k] = x4[i];
|
||||
tmp += xs[k].x*xs[k].x + xs[k].y*xs[k].y + xs[k].z*xs[k].z + xs[k].w*xs[k].w;
|
||||
}
|
||||
}
|
||||
tmp = block_reduce<block_reduce_method::SUM, block_size>(tmp, s_sum);
|
||||
const float scale = rsqrtf(tmp / ncols + eps);
|
||||
|
||||
const float mean = tmp / ncols;
|
||||
const float scale = rsqrtf(mean + eps);
|
||||
#pragma unroll
|
||||
for (int k = 0; k < vpt; k++) {
|
||||
const int i = tid + k*block_size;
|
||||
if (i < ncols4) {
|
||||
float4 r = make_float4(scale*xs[k].x, scale*xs[k].y, scale*xs[k].z, scale*xs[k].w);
|
||||
if constexpr (do_multiply) {
|
||||
const float4 m = mul4[i];
|
||||
r.x *= m.x;
|
||||
r.y *= m.y;
|
||||
r.z *= m.z;
|
||||
r.w *= m.w;
|
||||
}
|
||||
if constexpr (do_add) {
|
||||
const float4 a = add4[i];
|
||||
r.x += a.x;
|
||||
r.y += a.y;
|
||||
r.z += a.z;
|
||||
r.w += a.w;
|
||||
}
|
||||
if constexpr (do_scale) {
|
||||
r.x *= scale_out;
|
||||
r.y *= scale_out;
|
||||
r.z *= scale_out;
|
||||
r.w *= scale_out;
|
||||
}
|
||||
dst4[i] = r;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
float tmp = 0.0f; // partial sum for thread in warp
|
||||
|
||||
for (int col = tid; col < ncols; col += block_size) {
|
||||
if constexpr (do_multiply && do_add) {
|
||||
const int mul_col = fastmodulo(col, mul_ncols_packed);
|
||||
const int add_col = fastmodulo(col, add_ncols_packed);
|
||||
dstc[col] = scale * xc[col] * mulc[mul_col] + addc[add_col];
|
||||
} else if constexpr (do_multiply) {
|
||||
const int mul_col = fastmodulo(col, mul_ncols_packed);
|
||||
dstc[col] = scale * xc[col] * mulc[mul_col];
|
||||
} else if constexpr (do_scale) {
|
||||
dstc[col] = scale_out * (scale * xc[col]);
|
||||
} else {
|
||||
dstc[col] = scale * xc[col];
|
||||
for (int col = tid; col < ncols; col += block_size) {
|
||||
const float xi = xc[col];
|
||||
tmp += xi * xi;
|
||||
}
|
||||
|
||||
// sum up partial sums
|
||||
tmp = block_reduce<block_reduce_method::SUM, block_size>(tmp, s_sum);
|
||||
|
||||
const float mean = tmp / ncols;
|
||||
const float scale = rsqrtf(mean + eps);
|
||||
|
||||
for (int col = tid; col < ncols; col += block_size) {
|
||||
if constexpr (do_multiply && do_add) {
|
||||
const int mul_col = fastmodulo(col, mul_ncols_packed);
|
||||
const int add_col = fastmodulo(col, add_ncols_packed);
|
||||
dstc[col] = scale * xc[col] * mulc[mul_col] + addc[add_col];
|
||||
} else if constexpr (do_multiply) {
|
||||
const int mul_col = fastmodulo(col, mul_ncols_packed);
|
||||
dstc[col] = scale * xc[col] * mulc[mul_col];
|
||||
} else if constexpr (do_scale) {
|
||||
dstc[col] = scale_out * (scale * xc[col]);
|
||||
} else {
|
||||
dstc[col] = scale * xc[col];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,34 +383,89 @@ static void group_norm_f32_cuda(
|
||||
}
|
||||
}
|
||||
|
||||
#define RMS_NORM_LAUNCH(BLOCK_SIZE, VPT, VEC) \
|
||||
do { \
|
||||
const dim3 block_dims(BLOCK_SIZE, 1, 1); \
|
||||
const ggml_cuda_kernel_launch_params launch_params{ blocks_num, block_dims, block_dims.x > WARP_SIZE ? 32 * sizeof(float) : 0, stream }; \
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<BLOCK_SIZE, VPT, VEC, do_multiply, do_add, do_scale>, launch_params, \
|
||||
x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, \
|
||||
mul, mul_stride_row, mul_stride_channel, mul_stride_sample, \
|
||||
mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed, \
|
||||
add, add_stride_row, add_stride_channel, add_stride_sample, \
|
||||
add_ncols_packed, add_nrows_packed, add_nchannels_packed, add_nsamples_packed, scale_out); \
|
||||
} while (0)
|
||||
|
||||
template <bool do_multiply, bool do_add, bool do_scale>
|
||||
static void rms_norm_f32_dispatch(
|
||||
const dim3 blocks_num,
|
||||
const ggml_tensor * x_t, float * dst, const int ncols, const int nchannels, const int nsamples,
|
||||
const int64_t stride_row, const int64_t stride_channel, const int64_t stride_sample, const float eps,
|
||||
const ggml_tensor * mul_t, const int64_t mul_stride_row, const int64_t mul_stride_channel, const int64_t mul_stride_sample,
|
||||
const uint3 mul_ncols_packed, const uint3 mul_nrows_packed, const uint3 mul_nchannels_packed, const uint3 mul_nsamples_packed,
|
||||
const ggml_tensor * add_t, const int64_t add_stride_row, const int64_t add_stride_channel, const int64_t add_stride_sample,
|
||||
const uint3 add_ncols_packed, const uint3 add_nrows_packed, const uint3 add_nchannels_packed, const uint3 add_nsamples_packed,
|
||||
cudaStream_t stream, const float scale_out = 1.0f) {
|
||||
static_assert(!do_add || do_multiply, "fusing add is not supported without multiplying");
|
||||
static_assert(!do_scale || !do_multiply, "fusing scale is not supported with multiplying");
|
||||
|
||||
const float * x = (const float *) x_t->data;
|
||||
const float * mul = mul_t ? (const float *) mul_t->data : nullptr;
|
||||
const float * add = add_t ? (const float *) add_t->data : nullptr;
|
||||
|
||||
// dst is written at row * ncols, i.e. always at a multiple of ncols, so only its base needs checking here
|
||||
bool vec = ncols % 4 == 0 && ncols > 0 && ((uintptr_t) dst & 15) == 0;
|
||||
vec = vec && ggml_cuda_is_aligned(x_t, 16);
|
||||
if constexpr (do_multiply) {
|
||||
vec = vec && mul_ncols_packed.z == (uint32_t) ncols
|
||||
&& ggml_cuda_is_aligned(mul_t, 16);
|
||||
}
|
||||
if constexpr (do_add) {
|
||||
vec = vec && add_ncols_packed.z == (uint32_t) ncols
|
||||
&& ggml_cuda_is_aligned(add_t, 16);
|
||||
}
|
||||
|
||||
const int n4 = ncols / 4;
|
||||
if (vec) {
|
||||
if (n4 <= 32) {
|
||||
RMS_NORM_LAUNCH(32, 1, true);
|
||||
} else if (n4 <= 128) {
|
||||
RMS_NORM_LAUNCH(128, 1, true);
|
||||
} else if (n4 <= 256) {
|
||||
RMS_NORM_LAUNCH(256, 1, true);
|
||||
} else if (n4 <= 512) {
|
||||
RMS_NORM_LAUNCH(512, 1, true);
|
||||
} else if (n4 <= 1024) {
|
||||
RMS_NORM_LAUNCH(1024, 1, true);
|
||||
} else if (n4 <= 4096) {
|
||||
RMS_NORM_LAUNCH(1024, 4, true);
|
||||
} else {
|
||||
RMS_NORM_LAUNCH(1024, 1, false);
|
||||
}
|
||||
} else if (ncols < 1024) {
|
||||
RMS_NORM_LAUNCH(256, 1, false);
|
||||
} else {
|
||||
RMS_NORM_LAUNCH(1024, 1, false);
|
||||
}
|
||||
}
|
||||
#undef RMS_NORM_LAUNCH
|
||||
|
||||
template <bool do_scale = false>
|
||||
static void rms_norm_f32_cuda(
|
||||
const float * x, float * dst, const int ncols, const int nrows, const int nchannels, const int nsamples,
|
||||
const ggml_tensor * x_t, float * dst, const int ncols, const int nrows, const int nchannels, const int nsamples,
|
||||
const int64_t stride_row, const int64_t stride_channel, const int64_t stride_sample, const float eps, cudaStream_t stream,
|
||||
const float scale_out = 1.0f) {
|
||||
const dim3 blocks_num(nrows, MIN(nchannels, UINT16_MAX), MIN(nsamples, UINT16_MAX));
|
||||
if (ncols < 1024) {
|
||||
const dim3 block_dims(256, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = {blocks_num, block_dims, block_dims.x > WARP_SIZE ? 32 * sizeof(float): 0, stream};
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<256, false, false, do_scale>, launch_params,
|
||||
x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps,
|
||||
// underlying cudaLaunchKernelEx does not support default params
|
||||
rms_norm_f32_dispatch</*do_multiply*/false, /*do_add*/false, do_scale>(
|
||||
blocks_num, x_t, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps,
|
||||
nullptr, 0, 0, 0, make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0),
|
||||
nullptr, 0, 0, 0, make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), scale_out);
|
||||
} else {
|
||||
const dim3 block_dims(1024, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params{blocks_num, block_dims, block_dims.x > WARP_SIZE ? 32 * sizeof(float): 0, stream};
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<1024, false, false, do_scale>, launch_params, x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps,
|
||||
// underlying cudaLaunchKernelEx does not support default params
|
||||
nullptr, 0, 0, 0, make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0),
|
||||
nullptr, 0, 0, 0, make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), scale_out);
|
||||
}
|
||||
stream, scale_out);
|
||||
}
|
||||
|
||||
static void rms_norm_mul_f32_cuda(const float * x,
|
||||
const float * mul,
|
||||
const float * add,
|
||||
float * dst,
|
||||
static void rms_norm_mul_f32_cuda(const ggml_tensor * x_t,
|
||||
const ggml_tensor * mul_t,
|
||||
const ggml_tensor * add_t,
|
||||
float * dst,
|
||||
const int ncols,
|
||||
const int nrows,
|
||||
const int nchannels,
|
||||
@@ -384,59 +490,33 @@ static void rms_norm_mul_f32_cuda(const float * x,
|
||||
const float eps,
|
||||
cudaStream_t stream) {
|
||||
const dim3 blocks_num(nrows, MIN(nchannels, UINT16_MAX), MIN(nsamples, UINT16_MAX));
|
||||
if (mul == nullptr) {
|
||||
rms_norm_f32_cuda(x, dst, ncols, nrows, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, stream);
|
||||
if (mul_t == nullptr) {
|
||||
rms_norm_f32_cuda(x_t, dst, ncols, nrows, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, stream);
|
||||
return;
|
||||
}
|
||||
if (add == nullptr) {
|
||||
const uint3 mul_ncols_packed = init_fastdiv_values(mul_ncols);
|
||||
const uint3 mul_nrows_packed = init_fastdiv_values(mul_nrows);
|
||||
const uint3 mul_nchannels_packed = init_fastdiv_values(mul_nchannels);
|
||||
const uint3 mul_nsamples_packed = init_fastdiv_values(mul_nsamples);
|
||||
if (ncols < 1024) {
|
||||
const dim3 block_dims(256, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params{blocks_num, block_dims, block_dims.x > WARP_SIZE ? 32 * sizeof(float): 0, stream};
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<256, true>, launch_params,
|
||||
x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, mul, mul_stride_row, mul_stride_channel,
|
||||
mul_stride_sample, mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed,
|
||||
// underlying cudaLaunchKernelEx does not support default params
|
||||
nullptr, 0, 0, 0, make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), 1.0f);
|
||||
} else {
|
||||
const dim3 block_dims(1024, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params{blocks_num, block_dims, block_dims.x > WARP_SIZE ? 32 * sizeof(float): 0, stream};
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<1024, true>, launch_params,
|
||||
x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, mul, mul_stride_row, mul_stride_channel,
|
||||
mul_stride_sample, mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed,
|
||||
// underlying cudaLaunchKernelEx does not support default params
|
||||
nullptr, 0, 0, 0, make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), make_uint3(0, 0, 0), 1.0f);
|
||||
}
|
||||
} else {
|
||||
const uint3 mul_ncols_packed = init_fastdiv_values(mul_ncols);
|
||||
const uint3 mul_nrows_packed = init_fastdiv_values(mul_nrows);
|
||||
const uint3 mul_nchannels_packed = init_fastdiv_values(mul_nchannels);
|
||||
const uint3 mul_nsamples_packed = init_fastdiv_values(mul_nsamples);
|
||||
const uint3 mul_ncols_packed = init_fastdiv_values(mul_ncols);
|
||||
const uint3 mul_nrows_packed = init_fastdiv_values(mul_nrows);
|
||||
const uint3 mul_nchannels_packed = init_fastdiv_values(mul_nchannels);
|
||||
const uint3 mul_nsamples_packed = init_fastdiv_values(mul_nsamples);
|
||||
const uint3 zero_packed = make_uint3(0, 0, 0);
|
||||
|
||||
if (add_t == nullptr) {
|
||||
rms_norm_f32_dispatch</*do_multiply*/true, /*do_add*/false, /*do_scale*/false>(
|
||||
blocks_num, x_t, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps,
|
||||
mul_t, mul_stride_row, mul_stride_channel, mul_stride_sample,
|
||||
mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed,
|
||||
nullptr, 0, 0, 0, zero_packed, zero_packed, zero_packed, zero_packed, stream);
|
||||
} else {
|
||||
const uint3 add_ncols_packed = init_fastdiv_values(add_ncols);
|
||||
const uint3 add_nrows_packed = init_fastdiv_values(add_nrows);
|
||||
const uint3 add_nchannels_packed = init_fastdiv_values(add_nchannels);
|
||||
const uint3 add_nsamples_packed = init_fastdiv_values(add_nsamples);
|
||||
if (ncols < 1024) {
|
||||
const dim3 block_dims(256, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params{blocks_num, block_dims,block_dims.x > WARP_SIZE ? 32 * sizeof(float): 0, stream};
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<256, true, true>, launch_params,
|
||||
x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, mul, mul_stride_row, mul_stride_channel,
|
||||
mul_stride_sample, mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed, add,
|
||||
add_stride_row, add_stride_channel, add_stride_sample, add_ncols_packed, add_nrows_packed,
|
||||
add_nchannels_packed, add_nsamples_packed, 1.0f);
|
||||
} else {
|
||||
const dim3 block_dims(1024, 1, 1);
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params{blocks_num, block_dims, block_dims.x > WARP_SIZE ? 32 * sizeof(float): 0, stream};
|
||||
ggml_cuda_kernel_launch(rms_norm_f32<1024, true, true>, launch_params,
|
||||
x, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps, mul, mul_stride_row, mul_stride_channel,
|
||||
mul_stride_sample, mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed, add,
|
||||
add_stride_row, add_stride_channel, add_stride_sample, add_ncols_packed, add_nrows_packed,
|
||||
add_nchannels_packed, add_nsamples_packed, 1.0f);
|
||||
}
|
||||
rms_norm_f32_dispatch</*do_multiply*/true, /*do_add*/true, /*do_scale*/false>(
|
||||
blocks_num, x_t, dst, ncols, nchannels, nsamples, stride_row, stride_channel, stride_sample, eps,
|
||||
mul_t, mul_stride_row, mul_stride_channel, mul_stride_sample,
|
||||
mul_ncols_packed, mul_nrows_packed, mul_nchannels_packed, mul_nsamples_packed,
|
||||
add_t, add_stride_row, add_stride_channel, add_stride_sample,
|
||||
add_ncols_packed, add_nrows_packed, add_nchannels_packed, add_nsamples_packed, stream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,7 +590,6 @@ void ggml_cuda_op_group_norm(ggml_backend_cuda_context & ctx, ggml_tensor * dst)
|
||||
|
||||
void ggml_cuda_op_rms_norm(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const float * src0_d = (const float *) src0->data;
|
||||
float * dst_d = (float *) dst->data;
|
||||
cudaStream_t stream = ctx.stream();
|
||||
|
||||
@@ -529,12 +608,11 @@ void ggml_cuda_op_rms_norm(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
const int64_t s02 = nb02 / ts0;
|
||||
const int64_t s03 = nb03 / ts0;
|
||||
|
||||
rms_norm_f32_cuda(src0_d, dst_d, ne00, ne01, ne02, ne03, s01, s02, s03, eps, stream);
|
||||
rms_norm_f32_cuda(src0, dst_d, ne00, ne01, ne02, ne03, s01, s02, s03, eps, stream);
|
||||
}
|
||||
|
||||
void ggml_cuda_op_rms_norm_scale_fused(ggml_backend_cuda_context & ctx, ggml_tensor * dst, ggml_tensor * scale_tensor) {
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const float * src0_d = (const float *) src0->data;
|
||||
float * dst_d = (float *) scale_tensor->data;
|
||||
cudaStream_t stream = ctx.stream();
|
||||
|
||||
@@ -556,7 +634,7 @@ void ggml_cuda_op_rms_norm_scale_fused(ggml_backend_cuda_context & ctx, ggml_ten
|
||||
const int64_t s02 = nb02 / ts0;
|
||||
const int64_t s03 = nb03 / ts0;
|
||||
|
||||
rms_norm_f32_cuda<true>(src0_d, dst_d, ne00, ne01, ne02, ne03, s01, s02, s03, eps, stream, scale);
|
||||
rms_norm_f32_cuda<true>(src0, dst_d, ne00, ne01, ne02, ne03, s01, s02, s03, eps, stream, scale);
|
||||
}
|
||||
|
||||
void ggml_cuda_op_rms_norm_fused(ggml_backend_cuda_context & ctx, ggml_tensor * dst, ggml_tensor * mul_tensor) {
|
||||
@@ -565,15 +643,11 @@ void ggml_cuda_op_rms_norm_fused(ggml_backend_cuda_context & ctx, ggml_tensor *
|
||||
|
||||
memcpy(&eps, dst->op_params, sizeof(float));
|
||||
|
||||
const float * src0_d = (const float *) rms_norm_src->data;
|
||||
const float * mul_d = nullptr;
|
||||
const ggml_tensor * mul_src = nullptr;
|
||||
|
||||
if (mul_tensor->src[0] == dst) {
|
||||
mul_d = (float *) mul_tensor->src[1]->data;
|
||||
mul_src = mul_tensor->src[1];
|
||||
} else if(mul_tensor->src[1] == dst) {
|
||||
mul_d = (float *) mul_tensor->src[0]->data;
|
||||
mul_src = mul_tensor->src[0];
|
||||
} else {
|
||||
GGML_ASSERT(false);
|
||||
@@ -609,7 +683,7 @@ void ggml_cuda_op_rms_norm_fused(ggml_backend_cuda_context & ctx, ggml_tensor *
|
||||
const int mul_nchannels = mul_src->ne[2];
|
||||
const int mul_nsamples = mul_src->ne[3];
|
||||
|
||||
rms_norm_mul_f32_cuda(src0_d, mul_d, nullptr, dst_d,
|
||||
rms_norm_mul_f32_cuda(rms_norm_src, mul_src, nullptr, dst_d,
|
||||
ne00, ne01, ne02, ne03,
|
||||
/*s00*/ s01, s02, s03,
|
||||
/*mul_s00*/ mul_s01, mul_s02, mul_s03,
|
||||
@@ -628,28 +702,21 @@ void ggml_cuda_op_rms_norm_fused_add(ggml_backend_cuda_context & ctx,
|
||||
|
||||
memcpy(&eps, dst->op_params, sizeof(float));
|
||||
|
||||
const float * src0_d = (const float *) rms_norm_src->data;
|
||||
const float * mul_d = nullptr;
|
||||
const ggml_tensor * mul_src = nullptr;
|
||||
|
||||
if (mul_tensor->src[0] == dst) {
|
||||
mul_d = (float *) mul_tensor->src[1]->data;
|
||||
mul_src = mul_tensor->src[1];
|
||||
} else if (mul_tensor->src[1] == dst) {
|
||||
mul_d = (float *) mul_tensor->src[0]->data;
|
||||
mul_src = mul_tensor->src[0];
|
||||
} else {
|
||||
GGML_ASSERT(false);
|
||||
}
|
||||
|
||||
const float * add_d = nullptr;
|
||||
const ggml_tensor * add_src = nullptr;
|
||||
|
||||
if (add_tensor->src[0] == mul_tensor) {
|
||||
add_d = (float *) add_tensor->src[1]->data;
|
||||
add_src = add_tensor->src[1];
|
||||
} else if (add_tensor->src[1] == mul_tensor) {
|
||||
add_d = (float *) add_tensor->src[0]->data;
|
||||
add_src = add_tensor->src[0];
|
||||
} else {
|
||||
GGML_ASSERT(false);
|
||||
@@ -697,7 +764,7 @@ void ggml_cuda_op_rms_norm_fused_add(ggml_backend_cuda_context & ctx,
|
||||
const int add_nchannels = add_src->ne[2];
|
||||
const int add_nsamples = add_src->ne[3];
|
||||
|
||||
rms_norm_mul_f32_cuda(src0_d, mul_d,add_d,dst_d,
|
||||
rms_norm_mul_f32_cuda(rms_norm_src, mul_src,add_src,dst_d,
|
||||
ne00,ne01, ne02, ne03,
|
||||
/*s00*/ s01, s02, s03,
|
||||
/*mul_s00*/ mul_s01, mul_s02, mul_s03,
|
||||
|
||||
@@ -11,6 +11,8 @@ struct topk_moe_config {
|
||||
bool use_sqrt_softplus;
|
||||
bool with_norm;
|
||||
bool delayed_softmax;
|
||||
int n_groups;
|
||||
int n_group_used;
|
||||
};
|
||||
|
||||
// Warp-local softmax used for both the pre-top-k logits and the post-top-k delayed path.
|
||||
@@ -78,6 +80,53 @@ __device__ void sqrt_softplus_warp_inplace(float (&vals)[experts_per_thread], co
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(GGML_USE_HIP) || HIP_VERSION >= 60300000
|
||||
// Set -inf for experts outside the top n_group_used groups. Group score is the sum of its top-2 experts (DeepSeek V3).
|
||||
template <int n_experts, int experts_per_thread>
|
||||
__device__ void group_mask_warp_inplace(float (&vals)[experts_per_thread], const int n_groups, const int n_group_used, const int lane) {
|
||||
const int n_per_group = n_experts / n_groups;
|
||||
|
||||
float score = -INFINITY; // lane g holds the score of group g
|
||||
for (int g = 0; g < n_groups; g++) {
|
||||
float top1 = -INFINITY;
|
||||
float top2 = -INFINITY;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < experts_per_thread; i++) {
|
||||
if ((i * WARP_SIZE + lane) / n_per_group == g) {
|
||||
top2 = fmaxf(top2, fminf(top1, vals[i]));
|
||||
top1 = fmaxf(top1, vals[i]);
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (int offset = WARP_SIZE / 2; offset > 0; offset >>= 1) {
|
||||
const float o1 = __shfl_xor_sync(0xFFFFFFFF, top1, offset, WARP_SIZE);
|
||||
const float o2 = __shfl_xor_sync(0xFFFFFFFF, top2, offset, WARP_SIZE);
|
||||
top2 = fmaxf(fminf(top1, o1), fmaxf(top2, o2));
|
||||
top1 = fmaxf(top1, o1);
|
||||
}
|
||||
if (lane == g) {
|
||||
score = top1 + top2;
|
||||
}
|
||||
}
|
||||
|
||||
// rank of each group, lower index wins ties
|
||||
int rank = 0;
|
||||
for (int g = 0; g < n_groups; g++) {
|
||||
const float other = __shfl_sync(0xFFFFFFFF, score, g, WARP_SIZE);
|
||||
rank += other > score || (other == score && g < lane);
|
||||
}
|
||||
const auto keep = __ballot_sync(0xFFFFFFFFULL, lane < n_groups && rank < n_group_used);
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < experts_per_thread; i++) {
|
||||
const int g = (i * WARP_SIZE + lane) / n_per_group;
|
||||
if (g >= n_groups || !((keep >> g) & 1)) {
|
||||
vals[i] = -INFINITY;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !defined(GGML_USE_HIP) || HIP_VERSION >= 60300000
|
||||
|
||||
/*
|
||||
This kernel does the following:
|
||||
1. optionally softmax over the logits per token [n_experts, n_tokens]
|
||||
@@ -171,6 +220,17 @@ __global__ void topk_moe_cuda(const float * logits,
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(GGML_USE_HIP) || HIP_VERSION >= 60300000
|
||||
// without bias, masking wt is safe: the matcher ensures the kept groups hold all used experts
|
||||
if (config.n_groups > 0) {
|
||||
if constexpr (has_bias) {
|
||||
group_mask_warp_inplace<n_experts>(selection_wt, config.n_groups, config.n_group_used, threadIdx.x);
|
||||
} else {
|
||||
group_mask_warp_inplace<n_experts>(wt, config.n_groups, config.n_group_used, threadIdx.x);
|
||||
}
|
||||
}
|
||||
#endif // !defined(GGML_USE_HIP) || HIP_VERSION >= 60300000
|
||||
|
||||
//at this point, each thread holds either a portion of the softmax distribution
|
||||
//or the raw logits. We do the argmax reduce over n_expert_used, each time marking
|
||||
//the expert weight as -inf to exclude from the next iteration
|
||||
@@ -297,6 +357,10 @@ static void launch_topk_moe_cuda(ggml_backend_cuda_context & ctx,
|
||||
cudaStream_t stream = ctx.stream();
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(grid_dims, block_dims, 0, stream);
|
||||
|
||||
#if defined(GGML_USE_HIP) && HIP_VERSION < 60300000
|
||||
GGML_ASSERT(config.n_groups == 0 && "expert groups fusion is not supported on this HIP version");
|
||||
#endif
|
||||
|
||||
switch (n_expert) {
|
||||
case 1:
|
||||
ggml_cuda_kernel_launch(topk_moe_cuda<1, has_bias>, launch_params,
|
||||
@@ -389,6 +453,8 @@ void ggml_cuda_op_topk_moe(ggml_backend_cuda_context & ctx,
|
||||
config.use_sqrt_softplus = args.sqrt_softplus;
|
||||
config.with_norm = with_norm;
|
||||
config.delayed_softmax = args.delayed_softmax;
|
||||
config.n_groups = args.n_groups;
|
||||
config.n_group_used = args.n_group_used;
|
||||
|
||||
if (bias) {
|
||||
launch_topk_moe_cuda<true>(ctx, logits_d, weights_d, ids_d, bias_d, n_rows, n_experts, n_expert_used, clamp_val,
|
||||
|
||||
@@ -14,6 +14,8 @@ struct ggml_cuda_topk_moe_args {
|
||||
bool prob_bias{};
|
||||
bool norm{};
|
||||
bool scale{};
|
||||
int n_groups{}; // > 0: pick experts only from the top n_group_used expert groups
|
||||
int n_group_used{};
|
||||
};
|
||||
|
||||
void ggml_cuda_op_topk_moe(ggml_backend_cuda_context & ctx,
|
||||
|
||||
+116
-53
@@ -360,12 +360,12 @@ static __device__ __forceinline__ float vec_dot_nvfp4_q8_1(
|
||||
|
||||
return sum;
|
||||
}
|
||||
#define VDR_Q2_K_Q8_1_MMVQ 1
|
||||
#define VDR_Q2_K_Q8_1_MMVQ 2
|
||||
#define VDR_Q2_K_Q8_1_MMQ 4
|
||||
|
||||
// contiguous v/x values
|
||||
static __device__ __forceinline__ float vec_dot_q2_K_q8_1_impl_mmvq(
|
||||
const int & v, const int * __restrict__ u, const uint8_t * __restrict__ scales,
|
||||
const int * __restrict__ v, const int * __restrict__ u, const int * __restrict__ scales,
|
||||
const half2 & dm2, const float * __restrict__ d8) {
|
||||
|
||||
float sumf_d = 0.0f;
|
||||
@@ -373,17 +373,20 @@ static __device__ __forceinline__ float vec_dot_q2_K_q8_1_impl_mmvq(
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < QR2_K; ++i) {
|
||||
const int sc = scales[2*i];
|
||||
const int sc = scales[i/2] >> (16*(i%2));
|
||||
|
||||
const int vi = (v >> (2*i)) & 0x03030303;
|
||||
int sumi_d = 0;
|
||||
int sumi_m = 0;
|
||||
#pragma unroll
|
||||
for (int j = 0; j < VDR_Q2_K_Q8_1_MMVQ; ++j) {
|
||||
const int vi = (v[j] >> (2*i)) & 0x03030303;
|
||||
|
||||
sumf_d += d8[i] * (ggml_cuda_dp4a(vi, u[i], 0) * (sc & 0xF)); // SIMD dot product
|
||||
sumi_d = ggml_cuda_dp4a(vi, u[VDR_Q2_K_Q8_1_MMVQ*i + j], sumi_d); // SIMD dot product
|
||||
sumi_m = ggml_cuda_dp4a(0x01010101, u[VDR_Q2_K_Q8_1_MMVQ*i + j], sumi_m); // sum of q8_1 values
|
||||
}
|
||||
|
||||
// fill int with 4x m
|
||||
int m = sc >> 4;
|
||||
m |= m << 8;
|
||||
m |= m << 16;
|
||||
sumf_m += d8[i] * ggml_cuda_dp4a(m, u[i], 0); // multiply constant q2_K part with sum of q8_1 values
|
||||
sumf_d += d8[i] * (sumi_d * (sc & 0xF));
|
||||
sumf_m += d8[i] * (sumi_m * ((sc >> 4) & 0xF)); // multiply constant q2_K part with sum of q8_1 values
|
||||
}
|
||||
|
||||
const float2 dm2f = __half22float2(dm2);
|
||||
@@ -443,37 +446,35 @@ static __device__ __forceinline__ float vec_dot_q2_K_q8_1_impl_mmq(
|
||||
return sumf + d8*sumf_d8;
|
||||
}
|
||||
|
||||
#define VDR_Q3_K_Q8_1_MMVQ 1
|
||||
#define VDR_Q3_K_Q8_1_MMVQ 2
|
||||
#define VDR_Q3_K_Q8_1_MMQ 2
|
||||
|
||||
// contiguous v/x values
|
||||
static __device__ __forceinline__ float vec_dot_q3_K_q8_1_impl_mmvq(
|
||||
const int & vl, const int & vh, const int * __restrict__ u, const uint8_t * __restrict__ scales,
|
||||
const int & scale_offset, const float & d3, const float * __restrict__ d8) {
|
||||
const int * __restrict__ vl, const int * __restrict__ vh, const int * __restrict__ u, const int * __restrict__ scales,
|
||||
const float & d3, const float * __restrict__ d8) {
|
||||
|
||||
float sumf = 0.0f;
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < QR3_K; ++i) {
|
||||
const int isc = scale_offset + 2*i;
|
||||
// 4 low bits from scales[0..7], 2 high bits from scales[8..11]
|
||||
const int sc_low = (scales[i/2] >> (16*(i%2))) & 0x0F;
|
||||
const int sc_high = (scales[2] >> (16*(i%2) + 2*(i/2))) & 0x03;
|
||||
const int sc = (sc_low | (sc_high << 4)) - 32;
|
||||
|
||||
const int isc_low = isc % (QK_K/32);
|
||||
const int sc_shift_low = 4 * (isc / (QK_K/32));
|
||||
const int sc_low = (scales[isc_low] >> sc_shift_low) & 0xF;
|
||||
int sumi = 0;
|
||||
#pragma unroll
|
||||
for (int j = 0; j < VDR_Q3_K_Q8_1_MMVQ; ++j) {
|
||||
const int vil = (vl[j] >> (2*i)) & 0x03030303;
|
||||
const int vih = ((vh[j] >> i) << 2) & 0x04040404;
|
||||
|
||||
const int isc_high = isc % (QK_K/64);
|
||||
const int sc_shift_high = 2 * (isc / (QK_K/64));
|
||||
const int sc_high = ((scales[(QK_K/32) + isc_high] >> sc_shift_high) & 3) << 4;
|
||||
// (vil | vih) - 4 for each byte, the - 4 is done by the second dp4a
|
||||
sumi = ggml_cuda_dp4a(vil | vih, u[VDR_Q3_K_Q8_1_MMVQ*i + j], sumi); // SIMD dot product
|
||||
sumi = ggml_cuda_dp4a(0xFCFCFCFC, u[VDR_Q3_K_Q8_1_MMVQ*i + j], sumi);
|
||||
}
|
||||
|
||||
const int sc = (sc_low | sc_high) - 32;
|
||||
|
||||
const int vil = (vl >> (2*i)) & 0x03030303;
|
||||
|
||||
const int vih = ((vh >> i) << 2) & 0x04040404;
|
||||
|
||||
const int vi = __vsub4(vil, vih);
|
||||
|
||||
sumf += d8[i] * (ggml_cuda_dp4a(vi, u[i], 0) * sc); // SIMD dot product
|
||||
sumf += d8[i] * (sumi * sc);
|
||||
}
|
||||
|
||||
return d3 * sumf;
|
||||
@@ -620,12 +621,12 @@ static __device__ __forceinline__ float vec_dot_q5_K_q8_1_impl_mmq(
|
||||
return dm4f.x*sumf_d - dm4f.y*sumf_m;
|
||||
}
|
||||
|
||||
#define VDR_Q6_K_Q8_1_MMVQ 1
|
||||
#define VDR_Q6_K_Q8_1_MMVQ 2
|
||||
#define VDR_Q6_K_Q8_1_MMQ 8
|
||||
|
||||
// contiguous v/x values
|
||||
static __device__ __forceinline__ float vec_dot_q6_K_q8_1_impl_mmvq(
|
||||
const int & vl, const int & vh, const int * __restrict__ u, const int8_t * __restrict__ scales,
|
||||
const int * __restrict__ vl, const int * __restrict__ vh, const int * __restrict__ u, const int8_t * __restrict__ scales,
|
||||
const float & d, const float * __restrict__ d8) {
|
||||
|
||||
float sumf = 0.0f;
|
||||
@@ -634,13 +635,18 @@ static __device__ __forceinline__ float vec_dot_q6_K_q8_1_impl_mmvq(
|
||||
for (int i = 0; i < QR6_K; ++i) {
|
||||
const int sc = scales[4*i];
|
||||
|
||||
const int vil = (vl >> (4*i)) & 0x0F0F0F0F;
|
||||
int sumi = 0;
|
||||
#pragma unroll
|
||||
for (int j = 0; j < VDR_Q6_K_Q8_1_MMVQ; ++j) {
|
||||
const int vil = (vl[j] >> (4*i)) & 0x0F0F0F0F;
|
||||
const int vih = ((vh[j] >> (4*i)) << 4) & 0x30303030;
|
||||
|
||||
const int vih = ((vh >> (4*i)) << 4) & 0x30303030;
|
||||
// (vil | vih) - 32 for each byte, the - 32 is done by the second dp4a
|
||||
sumi = ggml_cuda_dp4a(vil | vih, u[VDR_Q6_K_Q8_1_MMVQ*i + j], sumi); // SIMD dot product
|
||||
sumi = ggml_cuda_dp4a(0xE0E0E0E0, u[VDR_Q6_K_Q8_1_MMVQ*i + j], sumi);
|
||||
}
|
||||
|
||||
const int vi = __vsub4((vil | vih), 0x20202020); // vi = (vil | vih) - 32
|
||||
|
||||
sumf += d8[i] * (ggml_cuda_dp4a(vi, u[i], 0) * sc); // SIMD dot product
|
||||
sumf += d8[i] * (sumi * sc);
|
||||
}
|
||||
|
||||
return d*sumf;
|
||||
@@ -700,6 +706,25 @@ static __device__ __forceinline__ float vec_dot_q1_0_q8_1(
|
||||
const int u2 = get_int_b4(bq8_1_chunk->qs, j*4+2);
|
||||
const int u3 = get_int_b4(bq8_1_chunk->qs, j*4+3);
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
const uint32_t q_bits = (uint16_t) q;
|
||||
|
||||
// Expand each group of four Q1_0 bits into byte selectors for v_perm_b32.
|
||||
const uint32_t q0_bits = q_bits >> 0;
|
||||
const uint32_t q1_bits = q_bits >> 4;
|
||||
const uint32_t q2_bits = q_bits >> 8;
|
||||
const uint32_t q3_bits = q_bits >> 12;
|
||||
|
||||
const uint32_t q0_indices = (q0_bits & 0x01) | ((q0_bits & 0x02) << 7) | ((q0_bits & 0x04) << 14) | ((q0_bits & 0x08) << 21);
|
||||
const uint32_t q1_indices = (q1_bits & 0x01) | ((q1_bits & 0x02) << 7) | ((q1_bits & 0x04) << 14) | ((q1_bits & 0x08) << 21);
|
||||
const uint32_t q2_indices = (q2_bits & 0x01) | ((q2_bits & 0x02) << 7) | ((q2_bits & 0x04) << 14) | ((q2_bits & 0x08) << 21);
|
||||
const uint32_t q3_indices = (q3_bits & 0x01) | ((q3_bits & 0x02) << 7) | ((q3_bits & 0x04) << 14) | ((q3_bits & 0x08) << 21);
|
||||
|
||||
const int v0 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q0_indices);
|
||||
const int v1 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q1_indices);
|
||||
const int v2 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q2_indices);
|
||||
const int v3 = __builtin_amdgcn_perm(0x000001FF, 0x000001FF, q3_indices);
|
||||
#else
|
||||
// unpack crumbs into nibble indices
|
||||
const int n0 = __byte_perm(0x11100100, 0x11100100, q >> 0); // [0, 1, 4, 5] [ 8, 9, 12, 13]
|
||||
const int n1 = __byte_perm(0x11100100, 0x11100100, q >> 2); // [2, 3, 6, 7] [10, 11, 14, 15]
|
||||
@@ -713,6 +738,7 @@ static __device__ __forceinline__ float vec_dot_q1_0_q8_1(
|
||||
const int v1 = __byte_perm(s0, s1, 0x7632);
|
||||
const int v2 = __byte_perm(s2, s3, 0x5410);
|
||||
const int v3 = __byte_perm(s2, s3, 0x7632);
|
||||
#endif // defined(GGML_USE_HIP)
|
||||
|
||||
sumi = ggml_cuda_dp4a(v0, u0, sumi);
|
||||
sumi = ggml_cuda_dp4a(v1, u1, sumi);
|
||||
@@ -870,18 +896,31 @@ static __device__ __forceinline__ float vec_dot_q2_K_q8_1(
|
||||
|
||||
const block_q2_K * bq2_K = (const block_q2_K *) vbq + kbx;
|
||||
|
||||
// iqs is in 0,2..14, each 32 bytes of qs hold 4 q8_1 blocks (one per 2 bit shift)
|
||||
const int bq8_offset = QR2_K * (iqs / QI8_1);
|
||||
const int scale_offset = iqs - iqs % QI8_1 + (iqs % QI8_1) / (QI8_1/2);
|
||||
const int iqs8 = iqs % QI8_1;
|
||||
|
||||
const uint8_t * scales = bq2_K->scales + scale_offset;
|
||||
int v[VDR_Q2_K_Q8_1_MMVQ];
|
||||
#pragma unroll
|
||||
for (int j = 0; j < VDR_Q2_K_Q8_1_MMVQ; ++j) {
|
||||
v[j] = get_int_b4(bq2_K->qs, iqs + j);
|
||||
}
|
||||
|
||||
const int v = get_int_b4(bq2_K->qs, iqs);
|
||||
int u[QR2_K];
|
||||
// scale of shift i is byte 2*i + (iqs8 >= QI8_1/2) of the 8 bytes for this half
|
||||
const int sc_shift = 8 * (iqs8 / (QI8_1/2));
|
||||
int scales[2];
|
||||
scales[0] = get_int_b4(bq2_K->scales, 2*(iqs / QI8_1) + 0) >> sc_shift;
|
||||
scales[1] = get_int_b4(bq2_K->scales, 2*(iqs / QI8_1) + 1) >> sc_shift;
|
||||
|
||||
int u[VDR_Q2_K_Q8_1_MMVQ*QR2_K];
|
||||
float d8[QR2_K];
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < QR2_K; ++ i) {
|
||||
u[i] = get_int_b4(bq8_1[bq8_offset + i].qs, iqs % QI8_1);
|
||||
for (int i = 0; i < QR2_K; ++i) {
|
||||
#pragma unroll
|
||||
for (int j = 0; j < VDR_Q2_K_Q8_1_MMVQ; ++j) {
|
||||
u[VDR_Q2_K_Q8_1_MMVQ*i + j] = get_int_b4(bq8_1[bq8_offset + i].qs, iqs8 + j);
|
||||
}
|
||||
d8[i] = __low2float(bq8_1[bq8_offset + i].ds);
|
||||
}
|
||||
|
||||
@@ -893,26 +932,42 @@ static __device__ __forceinline__ float vec_dot_q3_K_q8_1(
|
||||
|
||||
const block_q3_K * bq3_K = (const block_q3_K *) vbq + kbx;
|
||||
|
||||
// iqs is in 0,2..14, each 32 bytes of qs hold 4 q8_1 blocks (one per 2 bit shift)
|
||||
const int bq8_offset = QR3_K * (iqs / (QI3_K/2));
|
||||
const int scale_offset = iqs - iqs % QI8_1 + (iqs % QI8_1) / (QI8_1/2);
|
||||
const int iqs8 = iqs % QI8_1;
|
||||
|
||||
const float d = bq3_K->d;
|
||||
|
||||
const int vl = get_int_b2(bq3_K->qs, iqs);
|
||||
int vl[VDR_Q3_K_Q8_1_MMVQ];
|
||||
int vh[VDR_Q3_K_Q8_1_MMVQ];
|
||||
#pragma unroll
|
||||
for (int j = 0; j < VDR_Q3_K_Q8_1_MMVQ; ++j) {
|
||||
vl[j] = get_int_b2(bq3_K->qs, iqs + j);
|
||||
vh[j] = get_int_b2(bq3_K->hmask, iqs8 + j) >> bq8_offset;
|
||||
}
|
||||
|
||||
// invert the mask with ~ so that a 0/1 results in 4/0 being subtracted
|
||||
const int vh = ~get_int_b2(bq3_K->hmask, iqs % (QI3_K/2)) >> bq8_offset;
|
||||
// sub-block 8*(iqs/8) + 2*i + (iqs8 >= QI8_1/2) for shift i:
|
||||
// 4 low bits in nibble iqs/8 of bytes, 2 high bits at bit 4*(iqs/8) + 2*(i/2) of bytes 8..11
|
||||
const int sc_shift = 8 * (iqs8 / (QI8_1/2)) + 4 * (iqs / (QI3_K/2));
|
||||
int scales[3];
|
||||
#pragma unroll
|
||||
for (int l = 0; l < 3; ++l) {
|
||||
scales[l] = get_int_b2(bq3_K->scales, l) >> sc_shift;
|
||||
}
|
||||
|
||||
int u[QR3_K];
|
||||
int u[VDR_Q3_K_Q8_1_MMVQ*QR3_K];
|
||||
float d8[QR3_K];
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < QR3_K; ++i) {
|
||||
u[i] = get_int_b4(bq8_1[bq8_offset + i].qs, iqs % QI8_1);
|
||||
#pragma unroll
|
||||
for (int j = 0; j < VDR_Q3_K_Q8_1_MMVQ; ++j) {
|
||||
u[VDR_Q3_K_Q8_1_MMVQ*i + j] = get_int_b4(bq8_1[bq8_offset + i].qs, iqs8 + j);
|
||||
}
|
||||
d8[i] = __low2float(bq8_1[bq8_offset + i].ds);
|
||||
}
|
||||
|
||||
return vec_dot_q3_K_q8_1_impl_mmvq(vl, vh, u, bq3_K->scales, scale_offset, d, d8);
|
||||
return vec_dot_q3_K_q8_1_impl_mmvq(vl, vh, u, scales, d, d8);
|
||||
}
|
||||
|
||||
static __device__ __forceinline__ float vec_dot_q4_K_q8_1(
|
||||
@@ -1025,17 +1080,25 @@ static __device__ __forceinline__ float vec_dot_q6_K_q8_1(
|
||||
const int scale_offset = (QI6_K/4) * (iqs / (QI6_K/2)) + (iqs % (QI6_K/2)) / (QI6_K/8);
|
||||
const int vh_shift = 2 * ((iqs % (QI6_K/2)) / (QI6_K/4));
|
||||
|
||||
const int vl = get_int_b2(bq6_K->ql, iqs);
|
||||
const int vh = get_int_b2(bq6_K->qh, (QI6_K/4) * (iqs / (QI6_K/2)) + iqs % (QI6_K/4)) >> vh_shift;
|
||||
int vl[VDR_Q6_K_Q8_1_MMVQ];
|
||||
int vh[VDR_Q6_K_Q8_1_MMVQ];
|
||||
#pragma unroll
|
||||
for (int j = 0; j < VDR_Q6_K_Q8_1_MMVQ; ++j) {
|
||||
vl[j] = get_int_b2(bq6_K->ql, iqs + j);
|
||||
vh[j] = get_int_b2(bq6_K->qh, (QI6_K/4) * (iqs / (QI6_K/2)) + iqs % (QI6_K/4) + j) >> vh_shift;
|
||||
}
|
||||
|
||||
const int8_t * scales = bq6_K->scales + scale_offset;
|
||||
|
||||
int u[QR6_K];
|
||||
int u[VDR_Q6_K_Q8_1_MMVQ*QR6_K];
|
||||
float d8[QR6_K];
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < QR6_K; ++i) {
|
||||
u[i] = get_int_b4(bq8_1[bq8_offset + 2*i].qs, iqs % QI8_1);
|
||||
#pragma unroll
|
||||
for (int j = 0; j < VDR_Q6_K_Q8_1_MMVQ; ++j) {
|
||||
u[VDR_Q6_K_Q8_1_MMVQ*i + j] = get_int_b4(bq8_1[bq8_offset + 2*i].qs, iqs % QI8_1 + j);
|
||||
}
|
||||
d8[i] = __low2float(bq8_1[bq8_offset + 2*i].ds);
|
||||
}
|
||||
|
||||
|
||||
@@ -956,7 +956,6 @@ static void ggml_backend_hexagon_buffer_get_tensor(ggml_backend_buffer_t buffer,
|
||||
return;
|
||||
}
|
||||
|
||||
GGML_ASSERT(offset == 0);
|
||||
htp_unpack_tensor_tiled(data, tensor, offset, size);
|
||||
}
|
||||
|
||||
@@ -1045,7 +1044,6 @@ static void ggml_backend_hexagon_buffer_get_tensor_2d(ggml_backend_buffer_t buff
|
||||
|
||||
GGML_ASSERT((slice_offset % row_size_bytes) == 0 && "offset must be aligned to row boundary");
|
||||
GGML_ASSERT((temp_size % row_size_bytes) == 0 && "temp_size must be a multiple of row size");
|
||||
GGML_ASSERT((slice_offset / row_size_bytes) % 32 == 0 && "offset must be aligned to tile size (32 rows)");
|
||||
GGML_ASSERT((offset + temp_size) <= ggml_nbytes(tensor));
|
||||
|
||||
std::vector<uint8_t> temp_buf(temp_size);
|
||||
@@ -3474,15 +3472,16 @@ static bool ggml_hexagon_flash_attn_is_hmx_eligible(
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compute-bound head dim
|
||||
if (DK >= 256) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint32_t n_head = q->ne[2];
|
||||
const uint32_t n_kv_heads = k->ne[2];
|
||||
const uint32_t G = n_kv_heads > 0 ? n_head / n_kv_heads : 1;
|
||||
const uint32_t S = k->ne[1];
|
||||
const uint32_t total_threads = sess->n_threads * sess->mdev.count;
|
||||
|
||||
// Compute-bound head dim (for DK == 256 decode, HVX is faster when KV heads span all threads)
|
||||
if (DK > 256 || (DK == 256 && (DV > 256 || n_kv_heads < total_threads))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Tile alignment for 32-row HMX tiles
|
||||
const bool is_tile_aligned = (G > 0 && (32 % G == 0));
|
||||
@@ -3495,7 +3494,9 @@ static bool ggml_hexagon_flash_attn_is_hmx_eligible(
|
||||
|
||||
// Context depth crossover
|
||||
uint32_t s_cross = 512;
|
||||
if (DK <= 64) {
|
||||
if (DK == 256) {
|
||||
s_cross = 4096;
|
||||
} else if (DK <= 64) {
|
||||
s_cross = (sess->n_threads >= 8) ? 2048 : ((sess->n_threads >= 6) ? 768 : 512);
|
||||
} else {
|
||||
s_cross = (sess->n_threads >= 8) ? 1024 : 512;
|
||||
@@ -3667,10 +3668,6 @@ static bool ggml_hexagon_supported_flash_attn_ext(const struct ggml_hexagon_sess
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dst->ne[3] != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct htp_fa_kernel_params kparams;
|
||||
if (!ggml_hexagon_precompute_flash_attn_params(sess, op, &kparams)) {
|
||||
return false;
|
||||
@@ -3769,6 +3766,8 @@ static bool ggml_hexagon_matmul_is_hmx_eligible(
|
||||
bool is_matmul_id,
|
||||
bool is_batched
|
||||
) {
|
||||
GGML_UNUSED(dst);
|
||||
|
||||
if (src1->type != GGML_TYPE_F32 && (src1->type != GGML_TYPE_F16 || is_matmul_id)) {
|
||||
return false;
|
||||
}
|
||||
@@ -3780,7 +3779,7 @@ static bool ggml_hexagon_matmul_is_hmx_eligible(
|
||||
const int wtype = src0->type;
|
||||
|
||||
// HMX weight tiles accept non-32-aligned N for non-matmul_id.
|
||||
if (ne01 % 32 != 0 && is_matmul_id) {
|
||||
if (ne01 % HTP_TENSOR_TILE_N_ROWS != 0 && is_matmul_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3789,8 +3788,8 @@ static bool ggml_hexagon_matmul_is_hmx_eligible(
|
||||
return false;
|
||||
}
|
||||
|
||||
// HMX paths require K aligned to 32.
|
||||
if (ne00 % 32 != 0) {
|
||||
// HMX paths require K aligned to HMX tile cols (32).
|
||||
if (ne00 % HTP_TENSOR_TILE_N_COLS != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3952,7 +3951,7 @@ static void ggml_hexagon_precompute_hvx_mm_params(
|
||||
kparams->tile_size = htp_tensor_get_tile_size(wtype);
|
||||
kparams->aligned_tile_size = htp_tensor_get_tile_padded_size(wtype);
|
||||
|
||||
const bool k_align = (ne10 % 32 == 0);
|
||||
const bool k_align = (ne10 % htp_tensor_get_tile_k(wtype) == 0);
|
||||
|
||||
if (is_matmul_id) {
|
||||
kparams->kernel_type = (src1_nrows < (int) sess->n_threads)
|
||||
@@ -4124,7 +4123,7 @@ static void ggml_hexagon_precompute_matmul_params_impl(
|
||||
|
||||
const bool is_tiled = ggml_hexagon_tensor_is_tiled(src0);
|
||||
const int wtype = (is_tiled && (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_BF16)) ? GGML_TYPE_F16 : src0->type;
|
||||
const int ne00_padded = is_tiled ? hex_round_up(ne00, 32) : ne00;
|
||||
const int ne00_padded = is_tiled ? hex_round_up(ne00, htp_tensor_get_tile_k(wtype)) : ne00;
|
||||
const int ne11_padded = hex_round_up(ne11, 32);
|
||||
// VTCM has to hold whole 32-row weight tiles, so size for the rounded-up N
|
||||
// even when the tensor itself is ragged.
|
||||
@@ -5359,10 +5358,38 @@ static void ggml_hexagon_precompute_fused_mmidnx_params(
|
||||
kparams->n_weights = n_weights;
|
||||
}
|
||||
|
||||
static bool ggml_hexagon_op_mul_mat_use_fwht(const struct ggml_tensor * dst) {
|
||||
if (dst->op != GGML_OP_MUL_MAT || ggml_get_op_params_i32(dst, 1) != GGML_HINT_SRC0_IS_HADAMARD) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const struct ggml_tensor * src0 = dst->src[0];
|
||||
const struct ggml_tensor * src1 = dst->src[1];
|
||||
|
||||
const int64_t n = src1->ne[0];
|
||||
if (n < 32 || (n & (n - 1)) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return src0->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32 &&
|
||||
(src1->type == GGML_TYPE_F32 || src1->type == GGML_TYPE_F16) &&
|
||||
ggml_is_contiguous_rows(src1) && ggml_is_contiguous(dst) &&
|
||||
ggml_are_same_shape(src1, dst);
|
||||
}
|
||||
|
||||
static bool ggml_hexagon_supported_mul_mat(const struct ggml_hexagon_session * sess, const struct ggml_tensor * dst) {
|
||||
const struct ggml_tensor * src0 = dst->src[0];
|
||||
const struct ggml_tensor * src1 = dst->src[1];
|
||||
|
||||
if (ggml_get_op_params_i32(dst, 1) == GGML_HINT_SRC0_IS_HADAMARD) {
|
||||
if (!ggml_hexagon_op_mul_mat_use_fwht(dst)) {
|
||||
return false;
|
||||
}
|
||||
struct htp_unary_kernel_params ukparams;
|
||||
ggml_hexagon_precompute_unary_params(sess, HTP_OP_HADAMARD, src1, nullptr, dst, &ukparams);
|
||||
return ukparams.vtcm_row_per_thread > 0 && (size_t) ukparams.vtcm_size <= sess->vtcm_size;
|
||||
}
|
||||
|
||||
if (dst->type != GGML_TYPE_F32) {
|
||||
return false;
|
||||
}
|
||||
@@ -5372,6 +5399,8 @@ static bool ggml_hexagon_supported_mul_mat(const struct ggml_hexagon_session * s
|
||||
}
|
||||
|
||||
switch (src0->type) {
|
||||
case GGML_TYPE_Q1_0:
|
||||
case GGML_TYPE_Q2_0:
|
||||
case GGML_TYPE_Q4_0:
|
||||
case GGML_TYPE_Q4_1:
|
||||
case GGML_TYPE_Q8_0:
|
||||
@@ -5381,13 +5410,13 @@ static bool ggml_hexagon_supported_mul_mat(const struct ggml_hexagon_session * s
|
||||
case GGML_TYPE_Q5_K:
|
||||
case GGML_TYPE_Q6_K:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_Q2_K:
|
||||
case GGML_TYPE_Q2_K: {
|
||||
if (!ggml_is_contiguous(src0) || ggml_is_permuted(src0) || ggml_is_view(src0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (src0->ne[0] % ((src0->type == GGML_TYPE_Q6_K || src0->type == GGML_TYPE_Q5_K || src0->type == GGML_TYPE_Q4_K ||
|
||||
src0->type == GGML_TYPE_Q3_K || src0->type == GGML_TYPE_Q2_K) ? QK_K : 32)) {
|
||||
const int64_t qk = ggml_blck_size(src0->type);
|
||||
if (src0->ne[0] % qk) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5398,6 +5427,7 @@ static bool ggml_hexagon_supported_mul_mat(const struct ggml_hexagon_session * s
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case GGML_TYPE_F16:
|
||||
if (src0->nb[1] < src0->nb[0]) {
|
||||
@@ -5467,6 +5497,8 @@ static bool ggml_hexagon_supported_mul_mat_id(const struct ggml_hexagon_session
|
||||
}
|
||||
|
||||
switch (src0->type) {
|
||||
case GGML_TYPE_Q1_0:
|
||||
case GGML_TYPE_Q2_0:
|
||||
case GGML_TYPE_Q4_0:
|
||||
case GGML_TYPE_Q4_1:
|
||||
case GGML_TYPE_Q8_0:
|
||||
@@ -5481,8 +5513,7 @@ static bool ggml_hexagon_supported_mul_mat_id(const struct ggml_hexagon_session
|
||||
return false;
|
||||
}
|
||||
|
||||
if (src0->ne[0] % ((src0->type == GGML_TYPE_Q6_K || src0->type == GGML_TYPE_Q5_K || src0->type == GGML_TYPE_Q4_K ||
|
||||
src0->type == GGML_TYPE_Q3_K || src0->type == GGML_TYPE_Q2_K) ? QK_K : 32)) {
|
||||
if (src0->ne[0] % ggml_blck_size(src0->type) != 0) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
@@ -5917,8 +5948,9 @@ static bool ggml_hexagon_supported_get_rows(const struct ggml_hexagon_session *
|
||||
const struct ggml_tensor * src1 = op->src[1]; // indices
|
||||
const struct ggml_tensor * dst = op;
|
||||
|
||||
if (src0->type != GGML_TYPE_F32 && src0->type != GGML_TYPE_F16 && src0->type != GGML_TYPE_BF16 &&
|
||||
src0->type != GGML_TYPE_Q4_0 && src0->type != GGML_TYPE_Q4_K && src0->type != GGML_TYPE_Q5_K && src0->type != GGML_TYPE_Q6_K && src0->type != GGML_TYPE_Q8_0 && src0->type != GGML_TYPE_I32) {
|
||||
if (src0->type != GGML_TYPE_F32 && src0->type != GGML_TYPE_F16 && src0->type != GGML_TYPE_BF16 && src0->type != GGML_TYPE_I32 &&
|
||||
src0->type != GGML_TYPE_Q1_0 && src0->type != GGML_TYPE_Q2_0 && src0->type != GGML_TYPE_Q4_0 && src0->type != GGML_TYPE_Q8_0 &&
|
||||
src0->type != GGML_TYPE_Q4_K && src0->type != GGML_TYPE_Q5_K && src0->type != GGML_TYPE_Q6_K ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6662,7 +6694,11 @@ static void ggml_backend_hexagon_free(ggml_backend_t backend) {
|
||||
static htp_op_code op_remap_to_htp(const ggml_tensor * t) {
|
||||
switch (t->op) {
|
||||
case GGML_OP_FLASH_ATTN_EXT: return HTP_OP_FLASH_ATTN_EXT;
|
||||
case GGML_OP_MUL_MAT: return HTP_OP_MUL_MAT;
|
||||
case GGML_OP_MUL_MAT:
|
||||
if (ggml_get_op_params_i32(t, 1) == GGML_HINT_SRC0_IS_HADAMARD) {
|
||||
return HTP_OP_HADAMARD;
|
||||
}
|
||||
return HTP_OP_MUL_MAT;
|
||||
case GGML_OP_MUL_MAT_ID: return HTP_OP_MUL_MAT_ID;
|
||||
case GGML_OP_MUL: return HTP_OP_MUL;
|
||||
case GGML_OP_ADD: return HTP_OP_ADD;
|
||||
@@ -6791,6 +6827,7 @@ static bool is_supported_mul_mat_id_nx_kernel(const ggml_tensor * src0, const st
|
||||
|
||||
static bool is_mergeable_mul_mat(const ggml_tensor * t) {
|
||||
if (t->op != GGML_OP_MUL_MAT) return false;
|
||||
if (ggml_get_op_params_i32(t, 1) == GGML_HINT_SRC0_IS_HADAMARD) return false;
|
||||
|
||||
const ggml_tensor * src0 = t->src[0];
|
||||
const ggml_tensor * src1 = t->src[1];
|
||||
@@ -6893,7 +6930,9 @@ static ggml_status ggml_backend_hexagon_graph_compute(ggml_backend_t backend, gg
|
||||
|
||||
if (graph->nodes[i]->op == GGML_OP_RMS_NORM && ggml_can_fuse(graph, i, { GGML_OP_RMS_NORM, GGML_OP_MUL })) {
|
||||
extra->flags |= GGML_HEXAGON_TENSOR_FUSEABLE;
|
||||
} else if (graph->nodes[i]->op == GGML_OP_MUL_MAT || graph->nodes[i]->op == GGML_OP_MUL_MAT_ID) {
|
||||
} else if ((graph->nodes[i]->op == GGML_OP_MUL_MAT &&
|
||||
ggml_get_op_params_i32(graph->nodes[i], 1) != GGML_HINT_SRC0_IS_HADAMARD) ||
|
||||
graph->nodes[i]->op == GGML_OP_MUL_MAT_ID) {
|
||||
if ((i + 1 < graph->n_nodes && graph->nodes[i + 1]->op == GGML_OP_ADD && ggml_can_fuse(graph, i, { graph->nodes[i]->op, GGML_OP_ADD })) ||
|
||||
ggml_node_has_n_uses(graph, i, 1)) {
|
||||
extra->flags |= GGML_HEXAGON_TENSOR_FUSEABLE;
|
||||
@@ -8317,6 +8356,10 @@ static void ggml_hexagon_init(ggml_backend_reg * reg) {
|
||||
"please update hexagon_type to match ggml_type");
|
||||
static_assert((unsigned int) HTP_TYPE_Q2_K == (unsigned int) GGML_TYPE_Q2_K,
|
||||
"please update hexagon_type to match ggml_type");
|
||||
static_assert((unsigned int) HTP_TYPE_Q1_0 == (unsigned int) GGML_TYPE_Q1_0,
|
||||
"please update hexagon_type to match ggml_type");
|
||||
static_assert((unsigned int) HTP_TYPE_Q2_0 == (unsigned int) GGML_TYPE_Q2_0,
|
||||
"please update hexagon_type to match ggml_type");
|
||||
static_assert((unsigned int) HTP_TYPE_BF16 == (unsigned int) GGML_TYPE_BF16,
|
||||
"please update hexagon_type to match ggml_type");
|
||||
|
||||
|
||||
@@ -47,13 +47,19 @@ struct htp_opnode {
|
||||
void init(ggml_tensor * node) {
|
||||
this->node = node;
|
||||
if (this->node) {
|
||||
this->name = ggml_op_desc(this->node);
|
||||
if (this->node->op == GGML_OP_MUL_MAT &&
|
||||
ggml_get_op_params_i32(this->node, 1) == GGML_HINT_SRC0_IS_HADAMARD) {
|
||||
this->name = "MUL_MAT_HADAMARD";
|
||||
this->inputs.push_back(this->node->src[1]);
|
||||
} else {
|
||||
this->name = ggml_op_desc(this->node);
|
||||
|
||||
// Build inputs (preserving optional nullptrs)
|
||||
int n_inputs = n_active_src(this->node);
|
||||
this->inputs.resize(n_inputs, nullptr);
|
||||
for (int i = 0; i < n_inputs; i++) {
|
||||
this->inputs[i] = this->node->src[i];
|
||||
// Build inputs (preserving optional nullptrs)
|
||||
int n_inputs = n_active_src(this->node);
|
||||
this->inputs.resize(n_inputs, nullptr);
|
||||
for (int i = 0; i < n_inputs; i++) {
|
||||
this->inputs[i] = this->node->src[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Build outputs
|
||||
@@ -124,6 +130,10 @@ struct htp_opnode {
|
||||
bool stackable() const {
|
||||
switch (this->op()) {
|
||||
case GGML_OP_MUL_MAT:
|
||||
if (ggml_get_op_params_i32(this->node, 1) == GGML_HINT_SRC0_IS_HADAMARD) {
|
||||
return false;
|
||||
}
|
||||
return ggml_is_quantized(this->src0()->type);
|
||||
case GGML_OP_MUL_MAT_ID:
|
||||
return ggml_is_quantized(this->src0()->type);
|
||||
default:
|
||||
|
||||
@@ -289,6 +289,80 @@ static void repack_tiled_q8_0(void * data, const ggml_tensor * t, size_t offset,
|
||||
|
||||
// --- Float Types (F16, F32, BF16 -> f16_tiled HMX layout) ---
|
||||
|
||||
static void repack_q2_0_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
|
||||
GGML_ASSERT(offset == 0);
|
||||
GGML_ASSERT(t->ne[0] % 64 == 0);
|
||||
GGML_ASSERT(size == ggml_nbytes(t));
|
||||
|
||||
const block_q2_0 * src = (const block_q2_0 *) data;
|
||||
const int64_t blocks_per_row = t->ne[0] / 64;
|
||||
const int64_t n_col_tiles = hex_round_up(t->ne[1], 32) / 32;
|
||||
const int64_t n_weight_tiles = blocks_per_row;
|
||||
const size_t matrix_size = (size_t) n_col_tiles * n_weight_tiles * HTP_TENSOR_TILE_SIZE_Q2_0;
|
||||
|
||||
for (int64_t slice = 0; slice < t->ne[2] * t->ne[3]; ++slice) {
|
||||
const block_q2_0 * src_slice = src + slice * t->ne[1] * blocks_per_row;
|
||||
uint8_t * dst_slice = (uint8_t *) t->data + slice * matrix_size;
|
||||
memset(dst_slice, 0, matrix_size);
|
||||
|
||||
for (int64_t row_index = 0; row_index < t->ne[1]; ++row_index) {
|
||||
const int64_t ct = row_index / 32;
|
||||
const int row = (int) (row_index % 32);
|
||||
for (int64_t kt = 0; kt < n_weight_tiles; ++kt) {
|
||||
const block_q2_0 * block = src_slice + row_index * blocks_per_row + kt;
|
||||
uint8_t * tile = dst_slice + (ct * n_weight_tiles + kt) * HTP_TENSOR_TILE_SIZE_Q2_0;
|
||||
for (int group = 0; group < 16; ++group) {
|
||||
for (int byte = 0; byte < 4; ++byte) {
|
||||
const int k = 4 * group + byte;
|
||||
const uint8_t code = (block->qs[k / 4] >> (2 * (k % 4))) & 3;
|
||||
tile[(group >> 2) * 128 + 4 * row + byte] |= code << (2 * (group & 3));
|
||||
}
|
||||
}
|
||||
memcpy(tile + 512 + 2 * row, &block->d, sizeof(block->d));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void repack_tiled_q2_0(void * data, const ggml_tensor * t, size_t offset, size_t size) {
|
||||
GGML_ASSERT(t->ne[0] % 64 == 0);
|
||||
GGML_ASSERT(offset + size <= ggml_nbytes(t));
|
||||
|
||||
const size_t first_block = offset / sizeof(block_q2_0);
|
||||
const size_t end_block = (offset + size + sizeof(block_q2_0) - 1) / sizeof(block_q2_0);
|
||||
const size_t blocks_per_row = t->ne[0] / 64;
|
||||
const size_t blocks_per_slice = t->ne[1] * blocks_per_row;
|
||||
const size_t n_weight_tiles = blocks_per_row;
|
||||
const size_t n_col_tiles = hex_round_up(t->ne[1], 32) / 32;
|
||||
const size_t matrix_size = n_col_tiles * n_weight_tiles * HTP_TENSOR_TILE_SIZE_Q2_0;
|
||||
|
||||
for (size_t block_index = first_block; block_index < end_block; ++block_index) {
|
||||
const size_t slice = block_index / blocks_per_slice;
|
||||
const size_t in_slice = block_index % blocks_per_slice;
|
||||
const size_t row_index = in_slice / blocks_per_row;
|
||||
const size_t kt = in_slice % blocks_per_row;
|
||||
const size_t ct = row_index / 32;
|
||||
const size_t row = row_index % 32;
|
||||
const uint8_t * tile = (const uint8_t *) t->data + slice * matrix_size +
|
||||
(ct * n_weight_tiles + kt) * HTP_TENSOR_TILE_SIZE_Q2_0;
|
||||
|
||||
block_q2_0 block = {};
|
||||
memcpy(&block.d, tile + 512 + 2 * row, sizeof(block.d));
|
||||
for (int group = 0; group < 16; ++group) {
|
||||
for (int byte = 0; byte < 4; ++byte) {
|
||||
const uint8_t packed = tile[(group >> 2) * 128 + 4 * row + byte];
|
||||
const uint8_t code = (packed >> (2 * (group & 3))) & 3;
|
||||
block.qs[group] |= code << (2 * byte);
|
||||
}
|
||||
}
|
||||
|
||||
const size_t block_begin = block_index * sizeof(block_q2_0);
|
||||
const size_t copy_begin = (std::max)(block_begin, offset);
|
||||
const size_t copy_end = (std::min)(block_begin + sizeof(block_q2_0), offset + size);
|
||||
memcpy((uint8_t *) data + copy_begin - offset, (const uint8_t *) &block + copy_begin - block_begin, copy_end - copy_begin);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename SrcT, typename ToFp16Fn>
|
||||
static void repack_float_to_f16_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size, ToFp16Fn && to_fp16) {
|
||||
repack_to_tiled_grid_32<SrcT, 32>(t, data, offset, size, HTP_TENSOR_TILE_SIZE_F16,
|
||||
@@ -900,10 +974,122 @@ static void repack_tiled_q5_K(void * data, const ggml_tensor * t, size_t offset,
|
||||
});
|
||||
}
|
||||
|
||||
static inline void q1_0_tiled_bit_location(int k, int row, int & byte_offset, int & bit) {
|
||||
const int quarter = k / 32;
|
||||
const int kq = k % 32;
|
||||
|
||||
byte_offset = quarter * 128 + 4 * row + (kq % 4);
|
||||
bit = kq / 4;
|
||||
}
|
||||
|
||||
static void repack_q1_0_tiled(ggml_tensor * t, const void * data, size_t offset, size_t size) {
|
||||
const block_q1_0 * src_matrix = (const block_q1_0 *) data;
|
||||
const int64_t ne0 = t->ne[0];
|
||||
const int64_t ne1 = t->ne[1];
|
||||
const int64_t ne2 = t->ne[2];
|
||||
const int64_t ne3 = t->ne[3];
|
||||
const int n_col_tiles = hex_round_up(ne1, 32) / 32;
|
||||
const int n_k_tiles = ne0 / QK1_0;
|
||||
const size_t tile_size = HTP_TENSOR_TILE_SIZE_Q1_0;
|
||||
const size_t matrix_size = (size_t) n_col_tiles * n_k_tiles * tile_size;
|
||||
const size_t slice_size = ne1 * ggml_row_size(t->type, ne0);
|
||||
const int64_t start_slice = offset / slice_size;
|
||||
const int64_t end_slice = (std::min)((int64_t) ((offset + size + slice_size - 1) / slice_size), ne2 * ne3);
|
||||
|
||||
GGML_ASSERT(ne0 % QK1_0 == 0);
|
||||
|
||||
for (int64_t slice_idx = start_slice; slice_idx < end_slice; slice_idx++) {
|
||||
const block_q1_0 * src_slice = src_matrix + (slice_idx - start_slice) * ne1 * n_k_tiles;
|
||||
uint8_t * matrix_dst = (uint8_t *) t->data + slice_idx * matrix_size;
|
||||
|
||||
for (int ct = 0; ct < n_col_tiles; ct++) {
|
||||
for (int kt = 0; kt < n_k_tiles; kt++) {
|
||||
uint8_t * tile_dst = matrix_dst + ((size_t) ct * n_k_tiles + kt) * tile_size;
|
||||
ggml_half * scale_dst = (ggml_half *) (tile_dst + 512);
|
||||
memset(tile_dst, 0, tile_size);
|
||||
|
||||
for (int row = 0; row < 32; row++) {
|
||||
const int64_t r = ct * 32 + row;
|
||||
if (r >= ne1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const block_q1_0 & block = src_slice[r * n_k_tiles + kt];
|
||||
scale_dst[row] = block.d;
|
||||
for (int k = 0; k < QK1_0; k++) {
|
||||
int byte_offset;
|
||||
int bit;
|
||||
q1_0_tiled_bit_location(k, row, byte_offset, bit);
|
||||
if (block.qs[k / 8] & (1u << (k % 8))) {
|
||||
tile_dst[byte_offset] |= (uint8_t) (1u << bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void repack_tiled_q1_0(void * data, const ggml_tensor * t, size_t offset, size_t size) {
|
||||
block_q1_0 * dst_matrix = (block_q1_0 *) data;
|
||||
const int64_t ne0 = t->ne[0];
|
||||
const int64_t ne1 = t->ne[1];
|
||||
const int64_t ne2 = t->ne[2];
|
||||
const int64_t ne3 = t->ne[3];
|
||||
const int n_col_tiles = hex_round_up(ne1, 32) / 32;
|
||||
const int n_k_tiles = ne0 / QK1_0;
|
||||
const size_t tile_size = HTP_TENSOR_TILE_SIZE_Q1_0;
|
||||
const size_t matrix_size = (size_t) n_col_tiles * n_k_tiles * tile_size;
|
||||
const size_t row_size_bytes = ggml_row_size(t->type, ne0);
|
||||
const size_t slice_size = ne1 * row_size_bytes;
|
||||
const int64_t start_slice = offset / slice_size;
|
||||
const int64_t end_slice = (std::min)((int64_t) ((offset + size + slice_size - 1) / slice_size), ne2 * ne3);
|
||||
|
||||
GGML_ASSERT(ne0 % QK1_0 == 0);
|
||||
|
||||
for (int64_t slice_idx = start_slice; slice_idx < end_slice; slice_idx++) {
|
||||
const size_t cur_start_byte = (std::max)(offset, (size_t) slice_idx * slice_size);
|
||||
const size_t cur_end_byte = (std::min)(offset + size, (size_t) (slice_idx + 1) * slice_size);
|
||||
const int64_t start_row = (cur_start_byte - (size_t) slice_idx * slice_size) / row_size_bytes;
|
||||
const int64_t end_row = (std::min)((int64_t) ((cur_end_byte - (size_t) slice_idx * slice_size + row_size_bytes - 1) / row_size_bytes), ne1);
|
||||
block_q1_0 * dst_slice = dst_matrix + (cur_start_byte - offset) / sizeof(block_q1_0);
|
||||
const uint8_t * matrix_src = (const uint8_t *) t->data + slice_idx * matrix_size;
|
||||
|
||||
for (int ct = start_row / 32; ct < (end_row + 31) / 32; ct++) {
|
||||
for (int kt = 0; kt < n_k_tiles; kt++) {
|
||||
const uint8_t * tile_src = matrix_src + ((size_t) ct * n_k_tiles + kt) * tile_size;
|
||||
const ggml_half * scale_src = (const ggml_half *) (tile_src + 512);
|
||||
|
||||
for (int row = 0; row < 32; row++) {
|
||||
const int64_t r = ct * 32 + row;
|
||||
if (r < start_row || r >= end_row) {
|
||||
continue;
|
||||
}
|
||||
|
||||
block_q1_0 & block = dst_slice[(r - start_row) * n_k_tiles + kt];
|
||||
memset(block.qs, 0, sizeof(block.qs));
|
||||
block.d = scale_src[row];
|
||||
for (int k = 0; k < QK1_0; k++) {
|
||||
int byte_offset;
|
||||
int bit;
|
||||
q1_0_tiled_bit_location(k, row, byte_offset, bit);
|
||||
if (tile_src[byte_offset] & (1u << bit)) {
|
||||
block.qs[k / 8] |= (uint8_t) (1u << (k % 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Top-level Repack / Unpack Dispatchers ---
|
||||
|
||||
void htp_repack_tensor_tiled(ggml_tensor * tensor, const void * data, size_t size) {
|
||||
switch (tensor->type) {
|
||||
case GGML_TYPE_Q1_0:
|
||||
repack_q1_0_tiled(tensor, data, 0, size);
|
||||
break;
|
||||
case GGML_TYPE_Q4_0:
|
||||
case GGML_TYPE_IQ4_NL:
|
||||
repack_q4_0_tiled(tensor, data, 0, size);
|
||||
@@ -917,6 +1103,9 @@ void htp_repack_tensor_tiled(ggml_tensor * tensor, const void * data, size_t siz
|
||||
case GGML_TYPE_MXFP4:
|
||||
repack_mxfp4_tiled(tensor, data, 0, size);
|
||||
break;
|
||||
case GGML_TYPE_Q2_0:
|
||||
repack_q2_0_tiled(tensor, data, 0, size);
|
||||
break;
|
||||
case GGML_TYPE_Q2_K:
|
||||
repack_q2_K_tiled(tensor, data, 0, size);
|
||||
break;
|
||||
@@ -953,6 +1142,9 @@ void htp_unpack_tensor_tiled(void * data, const ggml_tensor * tensor, size_t off
|
||||
GGML_ASSERT(offset + size <= ggml_nbytes(tensor));
|
||||
|
||||
switch (tensor->type) {
|
||||
case GGML_TYPE_Q1_0:
|
||||
repack_tiled_q1_0(data, tensor, offset, size);
|
||||
break;
|
||||
case GGML_TYPE_Q4_0:
|
||||
case GGML_TYPE_IQ4_NL:
|
||||
repack_tiled_q4_0(data, tensor, offset, size);
|
||||
@@ -966,6 +1158,9 @@ void htp_unpack_tensor_tiled(void * data, const ggml_tensor * tensor, size_t off
|
||||
case GGML_TYPE_MXFP4:
|
||||
repack_tiled_mxfp4(data, tensor, offset, size);
|
||||
break;
|
||||
case GGML_TYPE_Q2_0:
|
||||
repack_tiled_q2_0(data, tensor, offset, size);
|
||||
break;
|
||||
case GGML_TYPE_Q2_K:
|
||||
repack_tiled_q2_K(data, tensor, offset, size);
|
||||
break;
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
static inline bool htp_is_tiled_weight_type(enum ggml_type type) {
|
||||
return type == GGML_TYPE_Q4_0 || type == GGML_TYPE_Q4_1 ||
|
||||
type == GGML_TYPE_Q8_0 || type == GGML_TYPE_IQ4_NL ||
|
||||
type == GGML_TYPE_MXFP4 || type == GGML_TYPE_Q6_K ||
|
||||
type == GGML_TYPE_MXFP4 || type == GGML_TYPE_Q1_0 ||
|
||||
type == GGML_TYPE_Q6_K ||
|
||||
type == GGML_TYPE_Q4_K || type == GGML_TYPE_Q5_K ||
|
||||
type == GGML_TYPE_Q3_K || type == GGML_TYPE_Q2_K ||
|
||||
type == GGML_TYPE_Q2_0 ||
|
||||
type == GGML_TYPE_F16 || type == GGML_TYPE_F32 ||
|
||||
type == GGML_TYPE_BF16;
|
||||
}
|
||||
@@ -42,6 +44,9 @@ static inline size_t htp_tiled_row_size(enum ggml_type type, int64_t ne0) {
|
||||
if (type == GGML_TYPE_Q6_K) {
|
||||
return (size_t) (ne0 / 32) * (HTP_TENSOR_TILE_SIZE_Q6_K / 32);
|
||||
}
|
||||
if (type == GGML_TYPE_Q2_0) {
|
||||
return (size_t) (ne0 / 64) * 18;
|
||||
}
|
||||
if (type == GGML_TYPE_Q4_K) {
|
||||
return (size_t) (ne0 / 32) * (HTP_TENSOR_TILE_SIZE_Q4_K / 32);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ struct htp_fa_context {
|
||||
__fp16 slopes[512];
|
||||
|
||||
uint32_t n_blocks;
|
||||
uint32_t n_active_blocks;
|
||||
bool shared_active_blocks;
|
||||
|
||||
size_t size_q_row_padded;
|
||||
size_t size_k_row_padded;
|
||||
@@ -82,12 +84,13 @@ struct htp_fa_context {
|
||||
size_t size_q_block;
|
||||
size_t size_vkq_acc;
|
||||
|
||||
uint8_t * spad_q;
|
||||
uint8_t * spad_k;
|
||||
uint8_t * spad_v;
|
||||
uint8_t * spad_m;
|
||||
uint8_t * spad_a;
|
||||
float * spad_sinks;
|
||||
uint8_t * spad_q;
|
||||
uint8_t * spad_k;
|
||||
uint8_t * spad_v;
|
||||
uint8_t * spad_m;
|
||||
uint8_t * spad_a;
|
||||
float * spad_sinks;
|
||||
uint16_t active_blocks[FA_MAX_KV_BLOCKS];
|
||||
|
||||
const struct htp_tensor * k;
|
||||
const struct htp_tensor * v;
|
||||
@@ -156,6 +159,7 @@ struct hmx_fa_context {
|
||||
size_t row_buf_stride; // HVX vectors per row buffer (Bc/64)
|
||||
size_t mask_buf_row_stride; // elements (__fp16) per row in mask buffer
|
||||
size_t q_tile_bytes;
|
||||
size_t k_tile_bytes;
|
||||
size_t o_tile_bytes;
|
||||
size_t col_vec_bytes;
|
||||
size_t d_tile_bytes;
|
||||
@@ -163,6 +167,77 @@ struct hmx_fa_context {
|
||||
dma_cache_fa m_cache;
|
||||
};
|
||||
|
||||
// Scan mask rows [q_start, q_start + n_rows_q) and build compact list of KV blocks with any entry != -inf.
|
||||
static uint32_t fa_scan_active_blocks(
|
||||
dma_queue * dma_q,
|
||||
const struct htp_tensor * mask,
|
||||
uint32_t q_start,
|
||||
uint32_t n_rows_q,
|
||||
uint32_t im2,
|
||||
uint32_t im3,
|
||||
uint32_t nek1,
|
||||
uint32_t Bc,
|
||||
uint32_t n_kv_blocks,
|
||||
void * vtcm_scratch,
|
||||
size_t scratch_bytes,
|
||||
uint16_t * active_blocks) {
|
||||
const size_t row_bytes = (size_t) nek1 * sizeof(__fp16);
|
||||
const size_t row_stride = hex_round_up(row_bytes, 128);
|
||||
|
||||
if (!mask || n_kv_blocks <= 1 || n_kv_blocks > FA_MAX_KV_BLOCKS ||
|
||||
(size_t) n_rows_q * row_stride > scratch_bytes) {
|
||||
#pragma clang loop vectorize(disable)
|
||||
for (uint32_t b = 0; b < n_kv_blocks; ++b) {
|
||||
active_blocks[b] = (uint16_t) b;
|
||||
}
|
||||
return n_kv_blocks;
|
||||
}
|
||||
|
||||
const dma_addr_t m_src = mask->data + q_start * mask->nb[1] + im2 * mask->nb[2] + im3 * mask->nb[3];
|
||||
dma_queue_push(dma_q, dma_make_data(vtcm_scratch, m_src), row_stride, mask->nb[1], row_bytes, n_rows_q);
|
||||
const uint8_t * m_buf = (const uint8_t *) dma_queue_pop(dma_q).dst;
|
||||
|
||||
const HVX_Vector v_ninf = Q6_Vh_vsplat_R(0xFC00);
|
||||
uint32_t n_active = 0;
|
||||
|
||||
for (uint32_t b = 0; b < n_kv_blocks; ++b) {
|
||||
const uint32_t col0 = b * Bc;
|
||||
const uint32_t cols = MIN(Bc, nek1 - col0);
|
||||
|
||||
for (uint32_t r = 0; r < n_rows_q; ++r) {
|
||||
const __fp16 * row_ptr = (const __fp16 *) (m_buf + r * row_stride) + col0;
|
||||
HVX_Vector v_any = Q6_V_vzero();
|
||||
uint32_t c = 0;
|
||||
for (; c + 64 <= cols; c += 64) {
|
||||
const HVX_Vector v_m = *(const HVX_Vector *) (row_ptr + c);
|
||||
v_any = Q6_V_vor_VV(v_any, Q6_V_vxor_VV(v_m, v_ninf));
|
||||
}
|
||||
if (c < cols) {
|
||||
const uint32_t rem = cols - c;
|
||||
const HVX_VectorPred q_rem = Q6_Q_vsetq2_R(rem * sizeof(__fp16));
|
||||
const HVX_Vector v_m = *(const HVX_Vector *) (row_ptr + c);
|
||||
const HVX_Vector v_diff = Q6_V_vand_QV(q_rem, Q6_V_vxor_VV(v_m, v_ninf));
|
||||
v_any = Q6_V_vor_VV(v_any, v_diff);
|
||||
}
|
||||
v_any = Q6_V_vor_VV(v_any, Q6_V_vror_VR(v_any, 64));
|
||||
v_any = Q6_V_vor_VV(v_any, Q6_V_vror_VR(v_any, 32));
|
||||
v_any = Q6_V_vor_VV(v_any, Q6_V_vror_VR(v_any, 16));
|
||||
v_any = Q6_V_vor_VV(v_any, Q6_V_vror_VR(v_any, 8));
|
||||
v_any = Q6_V_vor_VV(v_any, Q6_V_vror_VR(v_any, 4));
|
||||
if (Q6_R_vextract_VR(v_any, 0) != 0) {
|
||||
active_blocks[n_active++] = (uint16_t) b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (n_active == 0 && n_kv_blocks > 0) {
|
||||
active_blocks[0] = 0;
|
||||
n_active = 1;
|
||||
}
|
||||
return n_active;
|
||||
}
|
||||
|
||||
static void flash_attn_ext_f16_thread(unsigned int nth, unsigned int ith, void * data) {
|
||||
struct htp_fa_context * factx = (struct htp_fa_context *) data;
|
||||
const struct htp_ops_context * octx = factx->octx;
|
||||
@@ -236,6 +311,18 @@ static void flash_attn_ext_f16_thread(unsigned int nth, unsigned int ith, void *
|
||||
dma_cache_dm m_cache;
|
||||
dma_cache_dm_init(&m_cache, spad_m, factx->size_m_block, HVX_FA_DMA_CACHE_SIZE);
|
||||
|
||||
uint16_t thread_active_blocks[FA_MAX_KV_BLOCKS];
|
||||
const uint16_t * active_blocks = factx->shared_active_blocks ? factx->active_blocks : thread_active_blocks;
|
||||
uint32_t n_active_blocks = factx->n_active_blocks;
|
||||
dma_addr_t prev_mp_base = 0;
|
||||
|
||||
if (!factx->shared_active_blocks && !mask) {
|
||||
#pragma clang loop vectorize(disable)
|
||||
for (uint32_t b = 0; b < factx->n_blocks; ++b) {
|
||||
thread_active_blocks[b] = (uint16_t) b;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t size_vkq_acc_single = hex_round_up(DV * sizeof(float), 128);
|
||||
|
||||
uint32_t ir = ir0;
|
||||
@@ -271,6 +358,19 @@ static void flash_attn_ext_f16_thread(unsigned int nth, unsigned int ith, void *
|
||||
}
|
||||
}
|
||||
|
||||
dma_addr_t mp_base = 0;
|
||||
if (mask) {
|
||||
const uint32_t im2 = fastmodulo(iq2, mask->ne[2], &factx->src3_div2);
|
||||
const uint32_t im3 = fastmodulo(iq3, mask->ne[3], &factx->src3_div3);
|
||||
mp_base = mask->data + iq1*mask->nb[1] + im2*mask->nb[2] + im3*mask->nb[3];
|
||||
if (!factx->shared_active_blocks && mp_base != prev_mp_base) {
|
||||
n_active_blocks = fa_scan_active_blocks(
|
||||
dma_q, mask, iq1, 1, im2, im3, nek1, FLASH_ATTN_BLOCK_SIZE,
|
||||
factx->n_blocks, spad_k, factx->size_k_block * 2, thread_active_blocks);
|
||||
prev_mp_base = mp_base;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t heads[FA_HVX_G_MAX];
|
||||
HVX_Vector slope_vecs[FA_HVX_G_MAX] __attribute__((aligned(128)));
|
||||
HVX_Vector S_vec[FA_HVX_G_MAX] __attribute__((aligned(128)));
|
||||
@@ -303,26 +403,20 @@ static void flash_attn_ext_f16_thread(unsigned int nth, unsigned int ith, void *
|
||||
dma_queue_push(dma_q, dma_make_data(q_dst, q_row_ptr), factx->size_q_row_padded, nbq1, size_q_row, 1);
|
||||
}
|
||||
|
||||
dma_addr_t mp_base = 0;
|
||||
if (mask) {
|
||||
const uint32_t im2 = fastmodulo(iq2, mask->ne[2], &factx->src3_div2);
|
||||
const uint32_t im3 = fastmodulo(iq3, mask->ne[3], &factx->src3_div3);
|
||||
mp_base = mask->data + iq1*mask->nb[1] + im2*mask->nb[2] + im3*mask->nb[3];
|
||||
}
|
||||
|
||||
// Prefetch first two blocks
|
||||
for (uint32_t ib = 0; ib < MIN(factx->n_blocks, 2); ++ib) {
|
||||
// Prefetch first two active blocks
|
||||
for (uint32_t step = 0; step < MIN(n_active_blocks, 2); ++step) {
|
||||
const uint32_t ib = active_blocks[step];
|
||||
const uint32_t ic_start = ib * FLASH_ATTN_BLOCK_SIZE;
|
||||
const uint32_t current_block_size = MIN(FLASH_ATTN_BLOCK_SIZE, nek1 - ic_start);
|
||||
|
||||
// K
|
||||
const dma_addr_t k_src = k->data + ic_start*nbk1 + ik2*nbk2 + ik3*nbk3;
|
||||
uint8_t * k_dst = spad_k + (ib % 2) * factx->size_k_block;
|
||||
uint8_t * k_dst = spad_k + (step % 2) * factx->size_k_block;
|
||||
dma_queue_push(dma_q, dma_make_data(k_dst, k_src), factx->size_k_row_padded, nbk1, size_k_row, current_block_size);
|
||||
|
||||
// V
|
||||
const dma_addr_t v_src = v->data + ic_start*nbv1 + iv2*nbv2 + iv3*nbv3;
|
||||
uint8_t * v_dst = spad_v + (ib % 2) * factx->size_v_block;
|
||||
uint8_t * v_dst = spad_v + (step % 2) * factx->size_v_block;
|
||||
dma_queue_push(dma_q, dma_make_data(v_dst, v_src), factx->size_v_row_padded, nbv1, size_v_row, current_block_size);
|
||||
|
||||
// Mask
|
||||
@@ -348,7 +442,8 @@ static void flash_attn_ext_f16_thread(unsigned int nth, unsigned int ith, void *
|
||||
const HVX_Vector v_log2e = hvx_vec_splat_f16(EXP_LOG2E_F);
|
||||
const uint32_t stride_v2 = factx->size_v_row_padded * 2;
|
||||
|
||||
for (uint32_t ib = 0; ib < factx->n_blocks; ++ib) {
|
||||
for (uint32_t step = 0; step < n_active_blocks; ++step) {
|
||||
const uint32_t ib = active_blocks[step];
|
||||
const uint32_t ic_start = ib * FLASH_ATTN_BLOCK_SIZE;
|
||||
const uint32_t current_block_size = MIN(FLASH_ATTN_BLOCK_SIZE, nek1 - ic_start);
|
||||
|
||||
@@ -411,9 +506,9 @@ static void flash_attn_ext_f16_thread(unsigned int nth, unsigned int ith, void *
|
||||
HVX_Vector v_max = Q6_V_lo_W(hvx_vec_f16_to_f32(v_max_f16));
|
||||
htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_QK, head_ir);
|
||||
|
||||
// prefetch K for block ib + 2 after last head finished QK
|
||||
if (g + 1 == G_local && ib + 2 < factx->n_blocks) {
|
||||
const uint32_t next_ib = ib + 2;
|
||||
// prefetch K for step + 2 after last head finished QK
|
||||
if (g + 1 == G_local && step + 2 < n_active_blocks) {
|
||||
const uint32_t next_ib = active_blocks[step + 2];
|
||||
const uint32_t next_ic_start = next_ib * FLASH_ATTN_BLOCK_SIZE;
|
||||
const uint32_t next_block_size = MIN(FLASH_ATTN_BLOCK_SIZE, nek1 - next_ic_start);
|
||||
|
||||
@@ -504,6 +599,56 @@ static void flash_attn_ext_f16_thread(unsigned int nth, unsigned int ith, void *
|
||||
|
||||
((HVX_VectorPair *) VKQ32)[0] = vkq0;
|
||||
((HVX_VectorPair *) VKQ32)[1] = vkq1;
|
||||
} else if (DV == 256) {
|
||||
HVX_VectorPair vkq0 = ((const HVX_VectorPair *) VKQ32)[0];
|
||||
HVX_VectorPair vkq1 = ((const HVX_VectorPair *) VKQ32)[1];
|
||||
HVX_VectorPair vkq2 = ((const HVX_VectorPair *) VKQ32)[2];
|
||||
HVX_VectorPair vkq3 = ((const HVX_VectorPair *) VKQ32)[3];
|
||||
vkq0 = Q6_W_vcombine_VV(
|
||||
HVX_OP_MUL_F32(Q6_V_hi_W(vkq0), ms_vec),
|
||||
HVX_OP_MUL_F32(Q6_V_lo_W(vkq0), ms_vec)
|
||||
);
|
||||
vkq1 = Q6_W_vcombine_VV(
|
||||
HVX_OP_MUL_F32(Q6_V_hi_W(vkq1), ms_vec),
|
||||
HVX_OP_MUL_F32(Q6_V_lo_W(vkq1), ms_vec)
|
||||
);
|
||||
vkq2 = Q6_W_vcombine_VV(
|
||||
HVX_OP_MUL_F32(Q6_V_hi_W(vkq2), ms_vec),
|
||||
HVX_OP_MUL_F32(Q6_V_lo_W(vkq2), ms_vec)
|
||||
);
|
||||
vkq3 = Q6_W_vcombine_VV(
|
||||
HVX_OP_MUL_F32(Q6_V_hi_W(vkq3), ms_vec),
|
||||
HVX_OP_MUL_F32(Q6_V_lo_W(vkq3), ms_vec)
|
||||
);
|
||||
|
||||
for (uint32_t j = 0; j < current_block_size; j += 2) {
|
||||
HVX_Vector S0 = hvx_vec_repl_f16(Q6_V_vror_VR(P, j * 2));
|
||||
const HVX_Vector * vx0 = (const HVX_Vector *) v_ptr;
|
||||
if (j + 1 == current_block_size) {
|
||||
vkq0 = hvx_vec_mpyacc_f32_f16(vkq0, Q6_Vh_vshuff_Vh(vx0[0]), S0);
|
||||
vkq1 = hvx_vec_mpyacc_f32_f16(vkq1, Q6_Vh_vshuff_Vh(vx0[1]), S0);
|
||||
vkq2 = hvx_vec_mpyacc_f32_f16(vkq2, Q6_Vh_vshuff_Vh(vx0[2]), S0);
|
||||
vkq3 = hvx_vec_mpyacc_f32_f16(vkq3, Q6_Vh_vshuff_Vh(vx0[3]), S0);
|
||||
break;
|
||||
}
|
||||
|
||||
HVX_Vector S1 = hvx_vec_repl_f16(Q6_V_vror_VR(P, (j + 1) * 2));
|
||||
const HVX_Vector * vx1 = (const HVX_Vector *) (v_ptr + factx->size_v_row_padded);
|
||||
vkq0 = hvx_vec_mpyacc_f32_f16(vkq0, Q6_Vh_vshuff_Vh(vx0[0]), S0);
|
||||
vkq0 = hvx_vec_mpyacc_f32_f16(vkq0, Q6_Vh_vshuff_Vh(vx1[0]), S1);
|
||||
vkq1 = hvx_vec_mpyacc_f32_f16(vkq1, Q6_Vh_vshuff_Vh(vx0[1]), S0);
|
||||
vkq1 = hvx_vec_mpyacc_f32_f16(vkq1, Q6_Vh_vshuff_Vh(vx1[1]), S1);
|
||||
vkq2 = hvx_vec_mpyacc_f32_f16(vkq2, Q6_Vh_vshuff_Vh(vx0[2]), S0);
|
||||
vkq2 = hvx_vec_mpyacc_f32_f16(vkq2, Q6_Vh_vshuff_Vh(vx1[2]), S1);
|
||||
vkq3 = hvx_vec_mpyacc_f32_f16(vkq3, Q6_Vh_vshuff_Vh(vx0[3]), S0);
|
||||
vkq3 = hvx_vec_mpyacc_f32_f16(vkq3, Q6_Vh_vshuff_Vh(vx1[3]), S1);
|
||||
v_ptr += stride_v2;
|
||||
}
|
||||
|
||||
((HVX_VectorPair *) VKQ32)[0] = vkq0;
|
||||
((HVX_VectorPair *) VKQ32)[1] = vkq1;
|
||||
((HVX_VectorPair *) VKQ32)[2] = vkq2;
|
||||
((HVX_VectorPair *) VKQ32)[3] = vkq3;
|
||||
} else {
|
||||
hvx_scale_vec_f32_aa((uint8_t *) VKQ32, (const uint8_t *) VKQ32, DV, ms_vec);
|
||||
|
||||
@@ -524,9 +669,9 @@ static void flash_attn_ext_f16_thread(unsigned int nth, unsigned int ith, void *
|
||||
}
|
||||
htp_trace_event_stop(tr, HTP_TRACE_EVT_HVX_FA_SFM, head_ir);
|
||||
|
||||
// prefetch V and mask for block ib + 2 after last head finished V accumulation
|
||||
if (g + 1 == G_local && ib + 2 < factx->n_blocks) {
|
||||
const uint32_t next_ib = ib + 2;
|
||||
// prefetch V and mask for step + 2 after last head finished V accumulation
|
||||
if (g + 1 == G_local && step + 2 < n_active_blocks) {
|
||||
const uint32_t next_ib = active_blocks[step + 2];
|
||||
const uint32_t next_ic_start = next_ib * FLASH_ATTN_BLOCK_SIZE;
|
||||
const uint32_t next_block_size = MIN(FLASH_ATTN_BLOCK_SIZE, nek1 - next_ic_start);
|
||||
|
||||
@@ -541,7 +686,7 @@ static void flash_attn_ext_f16_thread(unsigned int nth, unsigned int ith, void *
|
||||
}
|
||||
}
|
||||
} // end for g
|
||||
} // end for ib
|
||||
} // end for step
|
||||
|
||||
for (uint32_t g = 0; g < G_local; ++g) {
|
||||
const uint32_t head_ir = ir + g;
|
||||
@@ -755,40 +900,11 @@ static void fa_q_load_thread(unsigned int n, unsigned int i, void * data) {
|
||||
const size_t m_start = i * m_bytes_per_t;
|
||||
const size_t m_end = hex_smin(m_start + m_bytes_per_t, col_vec_bytes);
|
||||
|
||||
if (factx->sinks) {
|
||||
const float * sinks_data = factx->vtcm_sinks;
|
||||
float * m_vec = (float *) factx->vtcm_m_vec;
|
||||
const size_t r_start = l_start / sizeof(float);
|
||||
const size_t r_end = l_end / sizeof(float);
|
||||
const float scale_factor = EXP_LOG2E_F;
|
||||
|
||||
const HVX_Vector v_scale = hvx_vec_splat_f32(scale_factor);
|
||||
|
||||
for (size_t r = r_start; r < r_end; r += 32) {
|
||||
HVX_VectorAlias local_m;
|
||||
for (size_t j = 0; j < 32; ++j) {
|
||||
size_t curr_r = r + j;
|
||||
if (curr_r < n_rows_g) {
|
||||
const size_t h_idx = fastmodulo(curr_r, G, &factx->div_G);
|
||||
const size_t head = args->kv_head * G + h_idx;
|
||||
local_m.fp32[j] = sinks_data[head];
|
||||
} else {
|
||||
local_m.fp32[j] = HTP_FA_M_INITIAL_VAL;
|
||||
}
|
||||
}
|
||||
HVX_Vector v_scaled = HVX_OP_MUL_F32(local_m.v, v_scale);
|
||||
*(HVX_Vector *) (m_vec + r) = v_scaled;
|
||||
}
|
||||
if (l_start < col_vec_bytes) {
|
||||
hvx_splat_u8_a((char *) factx->vtcm_l_vec + l_start, 0, l_end - l_start);
|
||||
}
|
||||
} else {
|
||||
if (l_start < col_vec_bytes) {
|
||||
hvx_splat_u8_a((char *) factx->vtcm_l_vec + l_start, 0, l_end - l_start);
|
||||
}
|
||||
if (m_start < col_vec_bytes) {
|
||||
hvx_splat_f32_a((char *) factx->vtcm_m_vec + m_start, HTP_FA_M_INITIAL_VAL, (m_end - m_start) / sizeof(float));
|
||||
}
|
||||
if (l_start < col_vec_bytes) {
|
||||
hvx_splat_u8_a((char *) factx->vtcm_l_vec + l_start, 0, l_end - l_start);
|
||||
}
|
||||
if (m_start < col_vec_bytes) {
|
||||
hvx_splat_f32_a((char *) factx->vtcm_m_vec + m_start, HTP_FA_M_INITIAL_VAL, (m_end - m_start) / sizeof(float));
|
||||
}
|
||||
|
||||
// Zero the whole rescale region: vtcm_d_tiles[0], the optional vtcm_d_tiles[1]
|
||||
@@ -1447,21 +1563,10 @@ static inline void fa_softmax_impl(
|
||||
HVX_Vector v_rowsum_acc_f32_0 = Q6_V_lo_W(rowsum_acc_pair);
|
||||
HVX_Vector v_rowsum_acc_f32_1 = Q6_V_hi_W(rowsum_acc_pair);
|
||||
|
||||
HVX_Vector v_l_curr0;
|
||||
HVX_Vector v_l_curr1;
|
||||
if (args->kv_start == 0 && factx->sinks != NULL) {
|
||||
// First KV block with sinks: m_prev holds the seeded sink value (not -inf),
|
||||
// so exp_m_diff = exp2(sink - m_curr) is the sink's contribution to the
|
||||
// denominator. l_prev is 0 here, so add exp_m_diff directly instead of
|
||||
// multiplying the (uninitialized) l_prev term.
|
||||
v_l_curr0 = HVX_OP_ADD_F32(exp_m_diff0, v_rowsum_acc_f32_0);
|
||||
v_l_curr1 = HVX_OP_ADD_F32(exp_m_diff1, v_rowsum_acc_f32_1);
|
||||
} else {
|
||||
HVX_Vector l_prev_v0 = factx->vtcm_l_vec[r_vec_idx * 2 + 0];
|
||||
HVX_Vector l_prev_v1 = factx->vtcm_l_vec[r_vec_idx * 2 + 1];
|
||||
v_l_curr0 = HVX_OP_ADD_F32(HVX_OP_MUL_F32(l_prev_v0, exp_m_diff0), v_rowsum_acc_f32_0);
|
||||
v_l_curr1 = HVX_OP_ADD_F32(HVX_OP_MUL_F32(l_prev_v1, exp_m_diff1), v_rowsum_acc_f32_1);
|
||||
}
|
||||
HVX_Vector l_prev_v0 = factx->vtcm_l_vec[r_vec_idx * 2 + 0];
|
||||
HVX_Vector l_prev_v1 = factx->vtcm_l_vec[r_vec_idx * 2 + 1];
|
||||
HVX_Vector v_l_curr0 = HVX_OP_ADD_F32(HVX_OP_MUL_F32(l_prev_v0, exp_m_diff0), v_rowsum_acc_f32_0);
|
||||
HVX_Vector v_l_curr1 = HVX_OP_ADD_F32(HVX_OP_MUL_F32(l_prev_v1, exp_m_diff1), v_rowsum_acc_f32_1);
|
||||
|
||||
factx->vtcm_m_vec[r_vec_idx * 2 + 0] = v_m_curr0;
|
||||
factx->vtcm_m_vec[r_vec_idx * 2 + 1] = v_m_curr1;
|
||||
@@ -1532,9 +1637,66 @@ static void fa_softmax_thread(unsigned int n, unsigned int i, void * data) {
|
||||
fa_softmax_impl(n, i, data, has_mask, mask_broadcast, is_g1, has_alibi, has_softcap);
|
||||
}
|
||||
|
||||
static inline HVX_Vector hvx_fa_load_sinks(const float * sinks_data, size_t idx, size_t count) {
|
||||
const HVX_Vector * v_src = (const HVX_Vector *) sinks_data + (idx >> 5);
|
||||
const size_t lane_off = idx & 31;
|
||||
const size_t byte_off = lane_off * sizeof(float);
|
||||
return (lane_off + count > 32)
|
||||
? Q6_V_valign_VVR(v_src[1], v_src[0], byte_off)
|
||||
: Q6_V_vror_VR(v_src[0], byte_off);
|
||||
}
|
||||
|
||||
static __attribute__((noinline)) void fa_build_d_diag_inv_l(struct hmx_fa_context * factx,
|
||||
size_t n_row_tiles,
|
||||
size_t n_row_tiles_g_br) {
|
||||
size_t n_row_tiles_g_br,
|
||||
uint32_t kv_head,
|
||||
size_t n_rows_g) {
|
||||
(void) n_row_tiles_g_br;
|
||||
|
||||
// sinks: l += exp(sink - m * ln2) in f32, m and l are in the log2 domain
|
||||
if (factx->sinks) {
|
||||
const float * sinks_data = factx->vtcm_sinks;
|
||||
const size_t G = factx->G;
|
||||
const size_t head_base = (size_t) kv_head * G;
|
||||
const HVX_Vector v_ln2 = Q6_V_vsplat_R(EXP_LOGN2);
|
||||
const HVX_Vector v_head_sinks = (G <= 32) ? hvx_fa_load_sinks(sinks_data, head_base, G) : Q6_V_vzero();
|
||||
HVX_Vector v_sink = Q6_V_vzero();
|
||||
size_t g_prev = SIZE_MAX;
|
||||
|
||||
for (size_t i = 0; i < n_row_tiles; ++i) {
|
||||
// the sink pattern of a tile only depends on (i * 32) % G, rebuild it when that changes
|
||||
const size_t r0 = i * 32;
|
||||
const size_t g0 = fastmodulo(r0, G, &factx->div_G);
|
||||
if (g0 != g_prev) {
|
||||
const size_t rem_g = G - g0;
|
||||
if (G <= 32) {
|
||||
const HVX_Vector v_lo = Q6_V_vror_VR(v_head_sinks, g0 * sizeof(float));
|
||||
const HVX_Vector v_hi = Q6_V_vror_VR(v_head_sinks, 128 - rem_g * sizeof(float));
|
||||
v_sink = Q6_V_vmux_QVV(Q6_Q_vsetq2_R(rem_g * sizeof(float)), v_lo, v_hi);
|
||||
for (size_t len = G; len < 32; len <<= 1) {
|
||||
const HVX_VectorPred q_keep = Q6_Q_vsetq_R(len * sizeof(float));
|
||||
const HVX_Vector v_shifted = Q6_V_vror_VR(v_sink, 128 - len * sizeof(float));
|
||||
v_sink = Q6_V_vmux_QVV(q_keep, v_sink, v_shifted);
|
||||
}
|
||||
} else if (rem_g >= 32) {
|
||||
v_sink = hvx_fa_load_sinks(sinks_data, head_base + g0, 32);
|
||||
} else {
|
||||
const HVX_Vector v_lo = hvx_fa_load_sinks(sinks_data, head_base + g0, rem_g);
|
||||
const HVX_Vector v_hi = hvx_fa_load_sinks(sinks_data, head_base, 32 - rem_g);
|
||||
v_sink = Q6_V_vmux_QVV(Q6_Q_vsetq_R(rem_g * sizeof(float)), v_lo, Q6_V_vror_VR(v_hi, 128 - rem_g * sizeof(float)));
|
||||
}
|
||||
g_prev = g0;
|
||||
}
|
||||
|
||||
HVX_Vector v_d = HVX_OP_SUB_F32(v_sink, HVX_OP_MUL_F32(factx->vtcm_m_vec[i], v_ln2));
|
||||
if (r0 + 32 > n_rows_g) {
|
||||
const HVX_VectorPred q_valid = Q6_Q_vsetq_R((n_rows_g - r0) * sizeof(float));
|
||||
v_d = Q6_V_vmux_QVV(q_valid, v_d, Q6_V_vzero());
|
||||
}
|
||||
factx->vtcm_l_vec[i] = HVX_OP_ADD_F32(factx->vtcm_l_vec[i], hvx_vec_exp_f32(v_d));
|
||||
}
|
||||
}
|
||||
|
||||
const HVX_Vector v_offsets = *(const HVX_Vector *) d_tile_scatter_offsets;
|
||||
const HVX_VectorPred q_32_mask = Q6_Q_vsetq_R(32 * sizeof(__fp16));
|
||||
const HVX_Vector one = hvx_vec_splat_f32(1.0f);
|
||||
@@ -1980,6 +2142,7 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
factx.vtcm_mask_buf = VTCM_LAYOUT_PTR(__fp16, base, L.off_mask_buf);
|
||||
factx.mask_buf_row_stride = L.mask_buf_row_stride;
|
||||
factx.q_tile_bytes = L.q_tile_bytes;
|
||||
factx.k_tile_bytes = L.k_tile_bytes;
|
||||
factx.o_tile_bytes = L.o_tile_bytes;
|
||||
factx.col_vec_bytes = L.col_vec_bytes;
|
||||
factx.d_tile_bytes = L.d_tile_bytes;
|
||||
@@ -2010,7 +2173,10 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
|
||||
// ======== Initialize HMX output scales ========
|
||||
hmx_init_column_scales(factx.vtcm_hmx_scales_id, Q6_V_vsplat_R(0x3c00)); // 1.0
|
||||
hmx_init_column_scales(factx.vtcm_hmx_scales_qk, hvx_vec_splat_f16(factx.scale));
|
||||
{
|
||||
union { __fp16 f; uint16_t u; } sc = { .f = factx.scale };
|
||||
hmx_init_column_scales(factx.vtcm_hmx_scales_qk, Q6_V_vsplat_R(sc.u));
|
||||
}
|
||||
|
||||
// ======== DMA setup ========
|
||||
dma_queue * const dma_q = ctx->dma[0];
|
||||
@@ -2029,7 +2195,6 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
const size_t q_src_stride = q_transposed ? q->nb[2] : q->nb[1];
|
||||
const size_t q_row_bytes_untransposed = factx.G * factx.DK * qo_element_size;
|
||||
const size_t q_row_bytes_trans_factor = factx.DK * qo_element_size;
|
||||
const uint32_t kv_rows0 = hex_smin(Bc, nek1);
|
||||
|
||||
// ======== Reusable job descriptors for pipeline ========
|
||||
hmx_fa_qk_job_t qk_job;
|
||||
@@ -2045,34 +2210,51 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
const size_t g_br_actual = hex_align_up(n_rows_g, HMX_FP16_TILE_N_ROWS);
|
||||
const size_t n_row_tiles = g_br_actual / HMX_FP16_TILE_N_ROWS;
|
||||
|
||||
uint16_t active_blocks[FA_MAX_KV_BLOCKS];
|
||||
uint32_t n_active_blocks = factx.n_kv_blocks;
|
||||
if (mask && factx.mask_broadcast && factx.n_kv_blocks > 1) {
|
||||
const size_t scratch_bytes = factx.k_tile_bytes * (factx.pipeline ? 2 : 1);
|
||||
n_active_blocks = fa_scan_active_blocks(
|
||||
dma_q, mask, q_start, n_rows_q, 0, im3, nek1, Bc,
|
||||
factx.n_kv_blocks, factx.vtcm_k_tiles[0], scratch_bytes, active_blocks);
|
||||
} else {
|
||||
#pragma clang loop vectorize(disable)
|
||||
for (uint32_t b = 0; b < factx.n_kv_blocks; ++b) {
|
||||
active_blocks[b] = (uint16_t) b;
|
||||
}
|
||||
}
|
||||
const uint32_t b0 = (n_active_blocks > 0) ? active_blocks[0] : 0;
|
||||
const uint32_t b0_start = b0 * Bc;
|
||||
const uint32_t b0_rows = hex_smin(Bc, nek1 - b0_start);
|
||||
|
||||
for (uint32_t kv_head = kv_head_min; kv_head < kv_head_max; ++kv_head) {
|
||||
const uint32_t ik2 = kv_head;
|
||||
const uint32_t ik3 = fastdiv(ib3, &kparams->broadcast_rk3);
|
||||
const uint32_t iv2 = kv_head;
|
||||
const uint32_t iv3 = fastdiv(ib3, &kparams->broadcast_rv3);
|
||||
|
||||
// 1. Push Q and KV DMAs for the very first iteration.
|
||||
// Subsequent iterations are enqueued early at the end of the previous iteration.
|
||||
if (ib3 == 0 && q_start == q_start_min && kv_head == kv_head_min) {
|
||||
// 1. Push Q and KV DMAs for the first kv_head of this q_start block.
|
||||
// Subsequent kv_head iterations are enqueued early at the end of the previous iteration.
|
||||
if (kv_head == kv_head_min) {
|
||||
const dma_addr_t q_ptr = q->data + q_start * q->nb[1] +
|
||||
(kv_head * factx.G) * q->nb[2] + ib3 * q->nb[3];
|
||||
const size_t q_row_bytes = q_transposed ? n_rows_q * q_row_bytes_trans_factor : q_row_bytes_untransposed;
|
||||
const size_t n_rows = q_transposed ? factx.G : n_rows_q;
|
||||
dma_queue_push(dma_q, dma_make_data(factx.vtcm_q_dma, q_ptr), q_row_bytes, hex_smax(q_src_stride, q_row_bytes), q_row_bytes, n_rows);
|
||||
|
||||
if (factx.n_kv_blocks > 0) {
|
||||
const dma_addr_t k_src = k->data + ik2 * k->nb[2] + ik3 * k->nb[3];
|
||||
dma_queue_push(dma_q, dma_make_data(factx.vtcm_k_fp16[0], k_src), size_k_row_padded, k->nb[1], size_k_row, kv_rows0);
|
||||
if (n_active_blocks > 0) {
|
||||
const dma_addr_t k_src = k->data + b0_start * k->nb[1] + ik2 * k->nb[2] + ik3 * k->nb[3];
|
||||
dma_queue_push(dma_q, dma_make_data(factx.vtcm_k_fp16[0], k_src), size_k_row_padded, k->nb[1], size_k_row, b0_rows);
|
||||
|
||||
const dma_addr_t v_src = v->data + iv2 * v->nb[2] + iv3 * v->nb[3];
|
||||
dma_queue_push(dma_q, dma_make_data(factx.vtcm_v_fp16[0], v_src), size_v_row_padded, v->nb[1], size_v_row, kv_rows0);
|
||||
const dma_addr_t v_src = v->data + b0_start * v->nb[1] + iv2 * v->nb[2] + iv3 * v->nb[3];
|
||||
dma_queue_push(dma_q, dma_make_data(factx.vtcm_v_fp16[0], v_src), size_v_row_padded, v->nb[1], size_v_row, b0_rows);
|
||||
|
||||
if (factx.pipeline && mask) {
|
||||
if (__builtin_expect(factx.mask_broadcast, true)) {
|
||||
const dma_addr_t ms_src = mask->data + q_start * mask->nb[1] + im3 * mask->nb[3] + 0;
|
||||
dma_cache_fa_push(dma_q, &factx.m_cache, ms_src, m_line_bytes, mask->nb[1], kv_rows0 * sizeof(__fp16), n_rows_q);
|
||||
const dma_addr_t ms_src = mask->data + q_start * mask->nb[1] + im3 * mask->nb[3] + b0_start * sizeof(__fp16);
|
||||
dma_cache_fa_push(dma_q, &factx.m_cache, ms_src, m_line_bytes, mask->nb[1], b0_rows * sizeof(__fp16), n_rows_q);
|
||||
} else {
|
||||
fa_push_mask_dma_gqa(dma_q, mask, q_start, im3, 0, kv_head, G, m_line_bytes, kv_rows0, n_rows_q, &factx);
|
||||
fa_push_mask_dma_gqa(dma_q, mask, q_start, im3, b0_start, kv_head, G, m_line_bytes, b0_rows, n_rows_q, &factx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2104,27 +2286,28 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
hmx_fa_qk_job_t qk_job[2];
|
||||
hmx_fa_o_update_job_t ou_job[2];
|
||||
|
||||
// Prefetch block 1 early if there are multiple blocks
|
||||
if (factx.n_kv_blocks > 1) {
|
||||
fa_prefetch_block(dma_q, k, v, mask, 1, Bc, size_k_row_padded, size_k_row, size_v_row_padded, size_v_row,
|
||||
// Prefetch second active block early if there are multiple active blocks
|
||||
if (n_active_blocks > 1) {
|
||||
fa_prefetch_block(dma_q, k, v, mask, active_blocks[1], Bc, size_k_row_padded, size_k_row, size_v_row_padded, size_v_row,
|
||||
ik2, ik3, iv2, iv3, q_start, im3, kv_head, G, m_line_bytes, n_rows_q, nek1, 1, &factx);
|
||||
}
|
||||
|
||||
// Prep and start QK-dot(0)
|
||||
void * curr_k0 = (void *) dma_queue_pop(dma_q).dst;
|
||||
fa_phase_k_interleave(&factx, kv_rows0, k_src_stride, curr_k0, 0, 0);
|
||||
fa_phase_k_interleave(&factx, b0_rows, k_src_stride, curr_k0, b0_start, 0);
|
||||
|
||||
qk_job[0].q_tiles = factx.vtcm_q_tiles;
|
||||
qk_job[0].k_tiles = factx.vtcm_k_tiles[0];
|
||||
qk_job[0].s_tiles = factx.vtcm_s_tiles[0];
|
||||
qk_job[0].n_row_tiles = n_row_tiles;
|
||||
qk_job[0].n_col_tiles = hmx_ceil_div(kv_rows0, HMX_FP16_TILE_N_COLS);
|
||||
qk_job[0].n_col_tiles = hmx_ceil_div(b0_rows, HMX_FP16_TILE_N_COLS);
|
||||
qk_job[0].n_dot_tiles = DK_pad / 32;
|
||||
qk_job[0].n_tiles_per_bc = n_tiles_per_bc;
|
||||
qk_job[0].hmx_scales = factx.vtcm_hmx_scales_qk;
|
||||
hmx_queue_push(hmx_q, hmx_queue_make_desc(hmx_fa_qk_dot_worker, &qk_job[0]));
|
||||
|
||||
for (uint32_t kv_blk = 0; kv_blk < factx.n_kv_blocks; ++kv_blk) {
|
||||
for (uint32_t step = 0; step < n_active_blocks; ++step) {
|
||||
const uint32_t kv_blk = active_blocks[step];
|
||||
const uint32_t kv_start = kv_blk * Bc;
|
||||
const uint32_t kv_rows = hex_smin(Bc, nek1 - kv_start);
|
||||
const size_t n_col_tiles = hmx_ceil_div(kv_rows, HMX_FP16_TILE_N_COLS);
|
||||
@@ -2144,13 +2327,14 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 3. Start HMX O update for block kv_blk - 1 (reads P[1 - buf_idx], V[1 - buf_idx], D) ----
|
||||
// O update relys on the previous block's P and V tiles.
|
||||
// ---- 3. Start HMX O update for step - 1 (reads P[1 - buf_idx], V[1 - buf_idx], D) ----
|
||||
// O update relies on the previous block's P and V tiles.
|
||||
// O update MUST be pushed before the next block's QK-dot: hmx_queue_pop() retires the
|
||||
// oldest descriptor, so push order alone decides which pop waits for which job.
|
||||
// If OU went in after QK(i+1), the pop below would retire QK(i+1) and leave
|
||||
// OU(i-1) in flight into the next iteration, where V-prep overwrites V[prev_buf].
|
||||
if (kv_blk > 0) {
|
||||
if (step > 0) {
|
||||
const uint32_t prev_blk = active_blocks[step - 1];
|
||||
const size_t prev_buf = 1 - buf_idx;
|
||||
ou_job[prev_buf].o_curr = o_tile_curr;
|
||||
ou_job[prev_buf].o_prev = o_tile_prev;
|
||||
@@ -2160,16 +2344,17 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
ou_job[prev_buf].hmx_scales = factx.vtcm_hmx_scales_id;
|
||||
ou_job[prev_buf].n_row_tiles = n_row_tiles;
|
||||
ou_job[prev_buf].n_col_tiles =
|
||||
hmx_ceil_div(hex_smin(Bc, nek1 - (kv_blk - 1) * Bc), HMX_FP16_TILE_N_COLS);
|
||||
hmx_ceil_div(hex_smin(Bc, nek1 - prev_blk * Bc), HMX_FP16_TILE_N_COLS);
|
||||
ou_job[prev_buf].n_row_tiles_g_br = n_row_tiles_g_br;
|
||||
ou_job[prev_buf].n_tiles_per_bc = n_tiles_per_bc;
|
||||
ou_job[prev_buf].DV = DV_pad;
|
||||
hmx_queue_push(hmx_q, hmx_queue_make_desc(hmx_fa_o_update_worker, &ou_job[prev_buf]));
|
||||
}
|
||||
|
||||
// ---- 4. Pop and run K-prep for next block & push next QK-dot ----
|
||||
if (kv_blk + 1 < factx.n_kv_blocks) {
|
||||
const uint32_t next_start = (kv_blk + 1) * Bc;
|
||||
// ---- 4. Pop and run K-prep for next active block & push next QK-dot ----
|
||||
if (step + 1 < n_active_blocks) {
|
||||
const uint32_t next_blk = active_blocks[step + 1];
|
||||
const uint32_t next_start = next_blk * Bc;
|
||||
const uint32_t next_rows = hex_smin(Bc, nek1 - next_start);
|
||||
const size_t next_buf = 1 - buf_idx;
|
||||
|
||||
@@ -2216,15 +2401,15 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
// Run Softmax on HVX (blocking call)
|
||||
fa_phase_softmax_and_build_d(&factx, &sargs, n_row_tiles, n_row_tiles_g_br);
|
||||
|
||||
// Wait for HMX O update for block kv_blk - 1 to finish
|
||||
if (kv_blk > 0) {
|
||||
// Wait for HMX O update for step - 1 to finish
|
||||
if (step > 0) {
|
||||
hmx_queue_pop(hmx_q);
|
||||
hex_swap_ptr((void **) &o_tile_curr, (void **) &o_tile_prev);
|
||||
}
|
||||
|
||||
// Prefetch block kv_blk + 2
|
||||
if (kv_blk + 2 < factx.n_kv_blocks) {
|
||||
fa_prefetch_block(dma_q, k, v, mask, kv_blk + 2, Bc, size_k_row_padded, size_k_row, size_v_row_padded, size_v_row,
|
||||
// Prefetch block step + 2
|
||||
if (step + 2 < n_active_blocks) {
|
||||
fa_prefetch_block(dma_q, k, v, mask, active_blocks[step + 2], Bc, size_k_row_padded, size_k_row, size_v_row_padded, size_v_row,
|
||||
ik2, ik3, iv2, iv3, q_start, im3, kv_head, G, m_line_bytes, n_rows_q, nek1, buf_idx, &factx);
|
||||
}
|
||||
|
||||
@@ -2232,8 +2417,8 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
}
|
||||
|
||||
// Epilogue
|
||||
if (factx.n_kv_blocks > 0) {
|
||||
const uint32_t last_blk = factx.n_kv_blocks - 1;
|
||||
if (n_active_blocks > 0) {
|
||||
const uint32_t last_blk = active_blocks[n_active_blocks - 1];
|
||||
const size_t last_cols = hmx_ceil_div(hex_smin(Bc, nek1 - last_blk * Bc), HMX_FP16_TILE_N_COLS);
|
||||
ou_job[0].o_curr = o_tile_curr;
|
||||
ou_job[0].o_prev = o_tile_prev;
|
||||
@@ -2250,7 +2435,7 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
|
||||
// Overlapped: run HVX build diag inv L while HMX is busy executing the update
|
||||
htp_trace_event_start(tr_hvx, HTP_TRACE_EVT_HVX_O_PROC, (uint16_t) q_start);
|
||||
fa_build_d_diag_inv_l(&factx, n_row_tiles, n_row_tiles_g_br);
|
||||
fa_build_d_diag_inv_l(&factx, n_row_tiles, n_row_tiles_g_br, kv_head, n_rows_g);
|
||||
htp_trace_event_stop(tr_hvx, HTP_TRACE_EVT_HVX_O_PROC, (uint16_t) q_start);
|
||||
hmx_queue_pop(hmx_q);
|
||||
|
||||
@@ -2259,7 +2444,8 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
|
||||
} else {
|
||||
// Fallback path
|
||||
for (uint32_t kv_blk = 0; kv_blk < factx.n_kv_blocks; ++kv_blk) {
|
||||
for (uint32_t step = 0; step < n_active_blocks; ++step) {
|
||||
const uint32_t kv_blk = active_blocks[step];
|
||||
const uint32_t kv_start = kv_blk * Bc;
|
||||
const uint32_t kv_rows = hex_smin(Bc, nek1 - kv_start);
|
||||
const size_t n_col_tiles = hmx_ceil_div(kv_rows, HMX_FP16_TILE_N_COLS);
|
||||
@@ -2273,8 +2459,9 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
}
|
||||
}
|
||||
|
||||
if (kv_blk + 1 < factx.n_kv_blocks) {
|
||||
const uint32_t prefetch_start = (kv_blk + 1) * Bc;
|
||||
if (step + 1 < n_active_blocks) {
|
||||
const uint32_t prefetch_blk = active_blocks[step + 1];
|
||||
const uint32_t prefetch_start = prefetch_blk * Bc;
|
||||
const uint32_t prefetch_rows = hex_smin(Bc, nek1 - prefetch_start);
|
||||
const size_t prefetch_buf = 1 - buf_idx;
|
||||
const dma_addr_t k_prefetch_src = k->data + prefetch_start * k->nb[1] + ik2 * k->nb[2] + ik3 * k->nb[3];
|
||||
@@ -2352,10 +2539,10 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
ou_job.DV = DV_pad;
|
||||
|
||||
hmx_queue_push(ctx->hmx_queue, hmx_queue_make_desc(hmx_fa_o_update_worker, &ou_job));
|
||||
if (kv_blk + 1 == factx.n_kv_blocks) {
|
||||
if (step + 1 == n_active_blocks) {
|
||||
// Overlapped: run HVX build diag inv L while HMX is busy executing the update
|
||||
htp_trace_event_start(tr_hvx, HTP_TRACE_EVT_HVX_O_PROC, (uint16_t) q_start);
|
||||
fa_build_d_diag_inv_l(&factx, n_row_tiles, n_row_tiles_g_br);
|
||||
fa_build_d_diag_inv_l(&factx, n_row_tiles, n_row_tiles_g_br, kv_head, n_rows_g);
|
||||
htp_trace_event_stop(tr_hvx, HTP_TRACE_EVT_HVX_O_PROC, (uint16_t) q_start);
|
||||
}
|
||||
hmx_queue_pop(ctx->hmx_queue);
|
||||
@@ -2367,53 +2554,32 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue DMAs for the next iteration early so they overlap with O-PROC
|
||||
uint32_t next_kv_head = kv_head + 1;
|
||||
uint32_t next_q_start = q_start;
|
||||
uint32_t next_ib3 = ib3;
|
||||
if (next_kv_head >= kv_head_max) {
|
||||
next_kv_head = kv_head_min;
|
||||
next_q_start = q_start + Br;
|
||||
if (next_q_start >= q_start_max) {
|
||||
next_q_start = q_start_min;
|
||||
next_ib3 = ib3 + 1;
|
||||
}
|
||||
}
|
||||
bool has_next = (next_ib3 < neq3);
|
||||
// Enqueue DMAs for the next kv_head early so they overlap with O-PROC
|
||||
const uint32_t next_kv_head = kv_head + 1;
|
||||
const bool has_next = (next_kv_head < kv_head_max);
|
||||
|
||||
if (has_next) {
|
||||
const uint32_t next_n_rows_q = hex_smin(Br, neq1 - next_q_start);
|
||||
const dma_addr_t next_q_ptr = q->data + next_q_start * q->nb[1] + (next_kv_head * factx.G) * q->nb[2] + next_ib3 * q->nb[3];
|
||||
const size_t next_q_row_bytes = q_transposed ? next_n_rows_q * q_row_bytes_trans_factor : q_row_bytes_untransposed;
|
||||
const size_t next_n_rows = q_transposed ? factx.G : next_n_rows_q;
|
||||
const dma_addr_t next_q_ptr = q->data + q_start * q->nb[1] + (next_kv_head * factx.G) * q->nb[2] + ib3 * q->nb[3];
|
||||
const size_t next_q_row_bytes = q_transposed ? n_rows_q * q_row_bytes_trans_factor : q_row_bytes_untransposed;
|
||||
const size_t next_n_rows = q_transposed ? factx.G : n_rows_q;
|
||||
dma_queue_push(dma_q, dma_make_data(factx.vtcm_q_dma, next_q_ptr), next_q_row_bytes, hex_smax(q_src_stride, next_q_row_bytes), next_q_row_bytes, next_n_rows);
|
||||
|
||||
if (factx.n_kv_blocks > 0) {
|
||||
if (n_active_blocks > 0) {
|
||||
const uint32_t next_ik2 = next_kv_head;
|
||||
const uint32_t next_iv2 = next_kv_head;
|
||||
uint32_t next_ik3 = ik3;
|
||||
uint32_t next_iv3 = iv3;
|
||||
if (next_ib3 != ib3) {
|
||||
next_ik3 = fastdiv(next_ib3, &kparams->broadcast_rk3);
|
||||
next_iv3 = fastdiv(next_ib3, &kparams->broadcast_rv3);
|
||||
}
|
||||
|
||||
const dma_addr_t next_k_src = k->data + next_ik2 * k->nb[2] + next_ik3 * k->nb[3];
|
||||
dma_queue_push(dma_q, dma_make_data(factx.vtcm_k_fp16[0], next_k_src), size_k_row_padded, k->nb[1], size_k_row, kv_rows0);
|
||||
const dma_addr_t next_k_src = k->data + b0_start * k->nb[1] + next_ik2 * k->nb[2] + ik3 * k->nb[3];
|
||||
dma_queue_push(dma_q, dma_make_data(factx.vtcm_k_fp16[0], next_k_src), size_k_row_padded, k->nb[1], size_k_row, b0_rows);
|
||||
|
||||
const dma_addr_t next_v_src = v->data + next_iv2 * v->nb[2] + next_iv3 * v->nb[3];
|
||||
dma_queue_push(dma_q, dma_make_data(factx.vtcm_v_fp16[0], next_v_src), size_v_row_padded, v->nb[1], size_v_row, kv_rows0);
|
||||
const dma_addr_t next_v_src = v->data + b0_start * v->nb[1] + next_iv2 * v->nb[2] + iv3 * v->nb[3];
|
||||
dma_queue_push(dma_q, dma_make_data(factx.vtcm_v_fp16[0], next_v_src), size_v_row_padded, v->nb[1], size_v_row, b0_rows);
|
||||
|
||||
if (factx.pipeline && mask) {
|
||||
uint32_t next_im3 = im3;
|
||||
if (next_ib3 != ib3) {
|
||||
next_im3 = fastmodulo(next_ib3, mask->ne[3], &factx.src3_div3);
|
||||
}
|
||||
if (__builtin_expect(factx.mask_broadcast, true)) {
|
||||
const dma_addr_t ms_src = mask->data + next_q_start * mask->nb[1] + next_im3 * mask->nb[3] + 0;
|
||||
dma_cache_fa_push(dma_q, &factx.m_cache, ms_src, m_line_bytes, mask->nb[1], kv_rows0 * sizeof(__fp16), next_n_rows_q);
|
||||
const dma_addr_t ms_src = mask->data + q_start * mask->nb[1] + im3 * mask->nb[3] + b0_start * sizeof(__fp16);
|
||||
dma_cache_fa_push(dma_q, &factx.m_cache, ms_src, m_line_bytes, mask->nb[1], b0_rows * sizeof(__fp16), n_rows_q);
|
||||
} else {
|
||||
fa_push_mask_dma_gqa(dma_q, mask, next_q_start, next_im3, 0, next_kv_head, G, m_line_bytes, kv_rows0, next_n_rows_q, &factx);
|
||||
fa_push_mask_dma_gqa(dma_q, mask, q_start, im3, b0_start, next_kv_head, G, m_line_bytes, b0_rows, n_rows_q, &factx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2461,7 +2627,7 @@ int op_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
|
||||
const struct htp_fa_kernel_params * kparams = (const struct htp_fa_kernel_params *) octx->kernel_params;
|
||||
|
||||
if (kparams->kernel_type == HTP_FA_KERNEL_UNSUPPORTED) {
|
||||
if (kparams->kernel_type == HTP_FA_KERNEL_UNSUPPORTED || kparams->n_kv_blocks > FA_MAX_KV_BLOCKS) {
|
||||
return HTP_STATUS_NO_SUPPORT;
|
||||
}
|
||||
|
||||
@@ -2576,12 +2742,21 @@ int op_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
|
||||
uint8_t * const base = octx->ctx->vtcm_base;
|
||||
|
||||
factx.spad_q = VTCM_LAYOUT_PTR(uint8_t, base, L.off_q);
|
||||
factx.spad_k = VTCM_LAYOUT_PTR(uint8_t, base, L.off_k);
|
||||
factx.spad_v = VTCM_LAYOUT_PTR(uint8_t, base, L.off_v);
|
||||
factx.spad_m = VTCM_LAYOUT_PTR_OPTIONAL(uint8_t, base, L.off_m, has_mask);
|
||||
factx.spad_a = VTCM_LAYOUT_PTR(uint8_t, base, L.off_a);
|
||||
factx.spad_sinks = VTCM_LAYOUT_PTR_OPTIONAL(float, base, L.off_sinks, has_sinks);
|
||||
factx.spad_q = VTCM_LAYOUT_PTR(uint8_t, base, L.off_q);
|
||||
factx.spad_k = VTCM_LAYOUT_PTR(uint8_t, base, L.off_k);
|
||||
factx.spad_v = VTCM_LAYOUT_PTR(uint8_t, base, L.off_v);
|
||||
factx.spad_m = VTCM_LAYOUT_PTR_OPTIONAL(uint8_t, base, L.off_m, has_mask);
|
||||
factx.spad_a = VTCM_LAYOUT_PTR(uint8_t, base, L.off_a);
|
||||
factx.spad_sinks = VTCM_LAYOUT_PTR_OPTIONAL(float, base, L.off_sinks, has_sinks);
|
||||
factx.n_active_blocks = factx.n_blocks;
|
||||
factx.shared_active_blocks = false;
|
||||
|
||||
if (has_mask && neq1 == 1 && neq3 == 1 && mask->ne[2] == 1 && factx.n_blocks > 1 && factx.n_blocks <= FA_MAX_KV_BLOCKS) {
|
||||
factx.n_active_blocks = fa_scan_active_blocks(
|
||||
octx->ctx->dma[0], mask, 0, 1, 0, 0, k->ne[1], FLASH_ATTN_BLOCK_SIZE,
|
||||
factx.n_blocks, factx.spad_k, factx.size_k_block * 2, factx.active_blocks);
|
||||
factx.shared_active_blocks = true;
|
||||
}
|
||||
|
||||
if (has_sinks) {
|
||||
const struct htp_tensor * sinks = octx->src[4];
|
||||
|
||||
@@ -126,6 +126,7 @@ struct hmx_fa_vtcm_layout {
|
||||
// Region byte sizes reused by the device at runtime (not just for allocation).
|
||||
size_t q_tile_bytes;
|
||||
size_t o_tile_bytes;
|
||||
size_t k_tile_bytes;
|
||||
size_t s_tile_bytes; // S and P tiles (same size)
|
||||
size_t d_tile_bytes; // d_tiles[0..1] + d_inv_l, allocated back to back
|
||||
size_t m_line_bytes; // one mask row
|
||||
@@ -141,6 +142,8 @@ struct hmx_fa_vtcm_layout {
|
||||
size_t total_bytes;
|
||||
};
|
||||
|
||||
#define FA_MAX_KV_BLOCKS 1024
|
||||
|
||||
// Build the VTCM layout.
|
||||
|
||||
static inline void hmx_fa_vtcm_layout_build(struct hmx_fa_vtcm_layout * L,
|
||||
@@ -224,6 +227,7 @@ static inline void hmx_fa_vtcm_layout_build(struct hmx_fa_vtcm_layout * L,
|
||||
|
||||
L->q_tile_bytes = q_tile_size;
|
||||
L->o_tile_bytes = o_tile_size;
|
||||
L->k_tile_bytes = k_tile_size;
|
||||
L->col_vec_bytes = col_vec_size;
|
||||
L->s_tile_bytes = s_tile_size;
|
||||
// Measured from the actual offsets rather than assumed to be N * d_tile_size, so
|
||||
@@ -332,8 +336,12 @@ static inline int hmx_fa_find_chunk_size(size_t * Br_out,
|
||||
|
||||
// Pipeline constraint: cap Bc so n_kv_blocks >= FA_MIN_KV_BLOCKS.
|
||||
// Only relax when kv_len is too short to form enough blocks.
|
||||
const size_t Bc_limit = can_pipeline ? hex_align_down(kv_len / FA_MIN_KV_BLOCKS, bc_unit) :
|
||||
(kv_len >= bc_unit ? hex_align_down(kv_len, bc_unit) : bc_unit);
|
||||
size_t Bc_limit = can_pipeline ? hex_align_down(kv_len / FA_MIN_KV_BLOCKS, bc_unit) :
|
||||
(kv_len >= bc_unit ? hex_align_down(kv_len, bc_unit) : bc_unit);
|
||||
// For small-Q SWA-sized KV caches (qo_len <= 16, kv_len <= 2048), cap Bc at 256 for block-skipping granularity
|
||||
if (qo_len <= 16 && kv_len <= 2048 && Bc_limit > 256) {
|
||||
Bc_limit = 256;
|
||||
}
|
||||
// Cost coefficients calibrated from profiling
|
||||
const size_t c_q_fixed = 800; // per-Q-block: q_load + epilogue o_update + o_norm + o_store
|
||||
const size_t c_iter_base = 200; // per-KV-iter base (HMX dot/update + DMA)
|
||||
|
||||
@@ -288,7 +288,7 @@ static void get_rows_thread_tiled(unsigned int nth, unsigned int ith, void * dat
|
||||
}
|
||||
|
||||
const uint32_t ir1 = MIN(ir0 + dr, grctx->task_start + grctx->tasks);
|
||||
const uint32_t n_k_tiles = ne00 / HTP_TENSOR_TILE_N_COLS;
|
||||
const uint32_t n_k_tiles = ne00 / htp_tensor_get_tile_k(octx->src[0]->type);
|
||||
const struct htp_get_rows_vtcm_layout * vtcm_layout = &grctx->vtcm_layout;
|
||||
uint8_t * src_spad_base = VTCM_LAYOUT_PTR(uint8_t, grctx->vtcm_base, vtcm_layout->off_src0) + ith * vtcm_layout->src0_bytes_per_thread;
|
||||
uint8_t * dst_spad_base = VTCM_LAYOUT_PTR(uint8_t, grctx->vtcm_base, vtcm_layout->off_dst) + ith * vtcm_layout->dst_bytes_per_thread;
|
||||
@@ -450,7 +450,9 @@ int op_get_rows(struct htp_ops_context * octx) {
|
||||
break;
|
||||
case HTP_GET_ROWS_KERNEL_TILED:
|
||||
switch (octx->src[0]->type) {
|
||||
case HTP_TYPE_F16: grctx.compute_tiled = hvx_dequantize_row_tiled_f16_f32; break;
|
||||
case HTP_TYPE_F16: grctx.compute_tiled = hvx_dequantize_row_tiled_f16_f32; break;
|
||||
case HTP_TYPE_Q1_0: grctx.compute_tiled = hvx_dequantize_row_tiled_q1_0_f32; break;
|
||||
case HTP_TYPE_Q2_0: grctx.compute_tiled = hvx_dequantize_row_tiled_q2_0_f32; break;
|
||||
case HTP_TYPE_Q8_0: grctx.compute_tiled = hvx_dequantize_row_tiled_q8_0_f32; break;
|
||||
case HTP_TYPE_Q4_0: grctx.compute_tiled = hvx_dequantize_row_tiled_q4_0_f32; break;
|
||||
case HTP_TYPE_Q4_K: grctx.compute_tiled = hvx_dequantize_row_tiled_q4_k_f32; break;
|
||||
@@ -466,6 +468,8 @@ int op_get_rows(struct htp_ops_context * octx) {
|
||||
switch (octx->src[0]->type) {
|
||||
case HTP_TYPE_F16: grctx.compute_flat = hvx_dequantize_row_flat_f16_f32; break;
|
||||
case HTP_TYPE_BF16: grctx.compute_flat = hvx_dequantize_row_flat_bf16_f32; break;
|
||||
case HTP_TYPE_Q1_0: grctx.compute_flat = hvx_dequantize_row_flat_q1_0_f32; break;
|
||||
case HTP_TYPE_Q2_0: grctx.compute_flat = hvx_dequantize_row_flat_q2_0_f32; break;
|
||||
case HTP_TYPE_Q8_0: grctx.compute_flat = hvx_dequantize_row_flat_q8_0_f32; break;
|
||||
case HTP_TYPE_Q4_0: grctx.compute_flat = hvx_dequantize_row_flat_q4_0_f32; break;
|
||||
case HTP_TYPE_Q4_K: grctx.compute_flat = hvx_dequantize_row_flat_q4_k_f32; break;
|
||||
|
||||
@@ -64,7 +64,7 @@ static inline void htp_get_rows_vtcm_layout_build(
|
||||
size_t src0_row_size = 0;
|
||||
if (kernel_type == HTP_GET_ROWS_KERNEL_TILED) {
|
||||
const size_t tile_stride = htp_tensor_get_tile_padded_size(type);
|
||||
const uint32_t n_k_tiles = ne00 / HTP_TENSOR_TILE_N_COLS;
|
||||
const uint32_t n_k_tiles = ne00 / htp_tensor_get_tile_k(type);
|
||||
src0_row_size = n_k_tiles > 0 ? (n_k_tiles * tile_stride) : tile_stride;
|
||||
} else {
|
||||
src0_row_size = htp_tensor_get_row_size(type, ne00);
|
||||
|
||||
@@ -26,6 +26,7 @@ struct tiled_dequantize_state {
|
||||
|
||||
struct fastdiv_values n_k_tiles_div;
|
||||
uint32_t n_k_tiles;
|
||||
uint32_t n_weight_tiles;
|
||||
uint32_t n_tot_tiles;
|
||||
uint32_t n_tiles_per_task;
|
||||
uint32_t tile_size;
|
||||
@@ -37,6 +38,39 @@ struct tiled_dequantize_state {
|
||||
uint32_t weight_type;
|
||||
};
|
||||
|
||||
static void dequantize_tiled_weight_to_fp16_task_q1_0(
|
||||
const tiled_dequantize_state_t *state,
|
||||
uint32_t start_tile, uint32_t end_tile) {
|
||||
|
||||
const HVX_Vector v_neg_one = Q6_Vb_vsplat_R(-1);
|
||||
const HVX_Vector v_pos_one = Q6_Vb_vsplat_R(1);
|
||||
|
||||
for (uint32_t t = start_tile; t < end_tile; t++) {
|
||||
const uint8_t * tile_src = state->src + t * state->aligned_tile_size;
|
||||
__fp16 * dst_ptr = state->dst + 4 * t * HTP_MM_HMX_TILE_N_ELMS;
|
||||
|
||||
const HVX_Vector v_sc = hvx_vmem(tile_src + 512);
|
||||
const HVX_Vector v_scale = Q6_V_lo_W(Q6_W_vshuff_VVR(v_sc, v_sc, -2));
|
||||
|
||||
for (uint32_t q = 0; q < 4; q++) {
|
||||
const HVX_Vector v_plane = hvx_vmem(tile_src + q * 128);
|
||||
__fp16 * quarter_dst = dst_ptr + q * HTP_MM_HMX_TILE_N_ELMS;
|
||||
|
||||
#pragma unroll
|
||||
for (int g = 0; g < 8; g++) {
|
||||
const HVX_Vector v_q = unpack_q1_0_group(v_plane, g, v_neg_one, v_pos_one);
|
||||
const HVX_VectorPair vp16 = Q6_Wh_vunpack_Vb(v_q);
|
||||
const HVX_VectorPair vp_k = Q6_W_vdeal_VVR(Q6_V_hi_W(vp16), Q6_V_lo_W(vp16), -4);
|
||||
|
||||
hvx_vmem(quarter_dst + (2 * g + 0) * 64) =
|
||||
Q6_Vhf_equals_Vqf16(Q6_Vqf16_vmpy_VhfVhf(Q6_Vhf_equals_Vh(Q6_V_lo_W(vp_k)), v_scale));
|
||||
hvx_vmem(quarter_dst + (2 * g + 1) * 64) =
|
||||
Q6_Vhf_equals_Vqf16(Q6_Vqf16_vmpy_VhfVhf(Q6_Vhf_equals_Vh(Q6_V_hi_W(vp_k)), v_scale));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dequantize a single tile from tiled weight data (already in VTCM) to tile-major FP16.
|
||||
static void dequantize_tiled_weight_to_fp16_task_q4_0(
|
||||
const tiled_dequantize_state_t *state,
|
||||
@@ -751,6 +785,40 @@ static void dequantize_tiled_weight_to_fp16_task_q2_k(
|
||||
}
|
||||
}
|
||||
|
||||
// Q2_0 stores codes 0..3 (value code - 1) and one fp16 scale per row, see HTP_MM_WEIGHT_TILE_SIZE_Q2_0.
|
||||
// One packed 64-k tile expands into two consecutive 32-k fp16 tiles.
|
||||
static void dequantize_tiled_weight_to_fp16_task_q2_0(
|
||||
const tiled_dequantize_state_t *state,
|
||||
uint32_t start_tile, uint32_t end_tile) {
|
||||
|
||||
const HVX_Vector mask_03 = Q6_Vb_vsplat_R(0x03);
|
||||
const HVX_Vector i1 = Q6_Vb_vsplat_R(1);
|
||||
|
||||
for (uint32_t t = start_tile; t < end_tile; t++) {
|
||||
const HVX_Vector * vptr = (const HVX_Vector *) (state->src + t * state->aligned_tile_size);
|
||||
__fp16 * dst_base = state->dst + 2 * t * HTP_MM_HMX_TILE_N_ELMS;
|
||||
|
||||
HVX_Vector v_sc = vptr[4];
|
||||
HVX_Vector v_scale = Q6_V_lo_W(Q6_W_vshuff_VVR(v_sc, v_sc, -2));
|
||||
|
||||
for (int half = 0; half < 2; half++) {
|
||||
__fp16 * dst_ptr = dst_base + half * HTP_MM_HMX_TILE_N_ELMS;
|
||||
|
||||
#pragma unroll
|
||||
for (int g = 0; g < 8; g++) {
|
||||
HVX_Vector v_q = Q6_Vb_vsub_VbVb(unpack_q3_k_low2(vptr + 2 * half, g, mask_03), i1);
|
||||
HVX_VectorPair vp16 = Q6_Wh_vunpack_Vb(v_q);
|
||||
HVX_VectorPair vp_k = Q6_W_vdeal_VVR(Q6_V_hi_W(vp16), Q6_V_lo_W(vp16), -4);
|
||||
|
||||
hvx_vmem(dst_ptr + (2 * g + 0) * 64) =
|
||||
Q6_Vhf_equals_Vqf16(Q6_Vqf16_vmpy_VhfVhf(Q6_Vhf_equals_Vh(Q6_V_lo_W(vp_k)), v_scale));
|
||||
hvx_vmem(dst_ptr + (2 * g + 1) * 64) =
|
||||
Q6_Vhf_equals_Vqf16(Q6_Vqf16_vmpy_VhfVhf(Q6_Vhf_equals_Vh(Q6_V_hi_W(vp_k)), v_scale));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static __attribute__((noinline))
|
||||
void convert_f16_weight_to_fp16_tiles_task(
|
||||
const tiled_dequantize_state_t *state,
|
||||
|
||||
@@ -32,6 +32,8 @@ enum htp_data_type {
|
||||
HTP_TYPE_I64 = 27,
|
||||
HTP_TYPE_BF16 = 30,
|
||||
HTP_TYPE_MXFP4 = 39,
|
||||
HTP_TYPE_Q1_0 = 41,
|
||||
HTP_TYPE_Q2_0 = 42,
|
||||
|
||||
HTP_TYPE_INVALID
|
||||
};
|
||||
@@ -112,6 +114,7 @@ enum htp_op_code {
|
||||
HTP_OP_POOL_1D,
|
||||
HTP_OP_UNARY_GELU_QUICK,
|
||||
HTP_OP_IM2COL_3D,
|
||||
HTP_OP_HADAMARD,
|
||||
|
||||
HTP_OP_INVALID
|
||||
};
|
||||
|
||||
@@ -25,32 +25,13 @@
|
||||
#define HTP_TENSOR_TILE_SIZE_Q6_K 896
|
||||
#define HTP_TENSOR_TILE_SIZE_Q3_K 512
|
||||
#define HTP_TENSOR_TILE_SIZE_Q2_K 336
|
||||
#define HTP_TENSOR_TILE_K_Q1_0 128
|
||||
#define HTP_TENSOR_TILE_SIZE_Q1_0 576
|
||||
#define HTP_TENSOR_TILE_K_Q2_0 64
|
||||
#define HTP_TENSOR_TILE_SIZE_Q2_0 576
|
||||
|
||||
// Q2_K, Q3_K, Q6_K use vrmpy byte order within each 128B HVX vector:
|
||||
// k = 0..31 is split into 8 groups g = k/4 of 4 k each; byte 4*r + (k%4) holds row r
|
||||
//
|
||||
// Q2_K (336B, lossless tiling with same size as block_q2_K):
|
||||
// bytes 0..255 (vec 0..1): unsigned 2-bit quants, vec m holds groups 4m..4m+3 at bit offsets 0,2,4,6
|
||||
// bytes 256..319: raw scales[] bytes (min << 4 | sc): [0..31] for k 0..15, [32..63] for k 16..31
|
||||
// bytes 320..335: 8 fp16 in tile j (0..7) of super-block: d (j < 4) or -dmin (j >= 4) for rows 8*(j%4)..8*(j%4)+7
|
||||
//
|
||||
// Q3_K (512B):
|
||||
// bytes 0..255 (vec 0..1): low 2 bits, vec m holds groups 4m..4m+3 at bit offsets 0,2,4,6
|
||||
// bytes 256..383 (vec 2): sign mask, bit g set when hmask is 0 (quant = low2 - 4)
|
||||
// bytes 384..511 (vec 3): fp16 scales d * (scales[] - 32): fp16[0..31] for k 0..15, fp16[32..63] for k 16..31
|
||||
//
|
||||
// Q4_K (640B, same layout as Q4_1 tile; each 256-k super-block splits into 8 tiles of 32 k):
|
||||
// bytes 0..511 (vec 0..3): 4-bit quants, byte 32*cp + r holds k = 2*cp (low nibble) and 2*cp + 1 (high nibble)
|
||||
// bytes 512..639 (vec 4): 32x2 fp16 per row: (d * sc, -dmin * m)
|
||||
//
|
||||
// Q5_K (768B):
|
||||
// bytes 0..639 (vec 0..4): Q4_K tile (low 4 bits + fp16 scale/min pairs)
|
||||
// bytes 640..767 (vec 5): 5th-bit plane; byte l holds the 8 high bits for lane l across vec i = 0..3 (bit 2i = lo, 2i+1 = hi)
|
||||
//
|
||||
// Q6_K (896B):
|
||||
// bytes 0..511 (vec 0..3): low 4 bits, vec i holds group 2i (low nibble) and 2i+1 (high nibble)
|
||||
// bytes 512..767 (vec 4..5): high 2 bits, vec 4+m holds groups 4m..4m+3 at bit offsets 0,2,4,6
|
||||
// bytes 768..895 (vec 6): fp16 scales d * scales[]: fp16[0..31] for k 0..15, fp16[32..63] for k 16..31
|
||||
// Tiled weight tensor layouts (Q4_0, Q8_0, IQ4_NL, MXFP4, Q1_0, Q2_0, K-quants):
|
||||
// See docs/backend/snapdragon/developer.md ("Tiled Weight Formats").
|
||||
|
||||
struct htp_tensor_mdev_range {
|
||||
uint32_t start;
|
||||
@@ -168,12 +149,22 @@ static inline uint32_t htp_tensor_get_tile_size(int type) {
|
||||
case HTP_TYPE_Q6_K: return HTP_TENSOR_TILE_SIZE_Q6_K;
|
||||
case HTP_TYPE_Q3_K: return HTP_TENSOR_TILE_SIZE_Q3_K;
|
||||
case HTP_TYPE_Q2_K: return HTP_TENSOR_TILE_SIZE_Q2_K;
|
||||
case HTP_TYPE_Q1_0: return HTP_TENSOR_TILE_SIZE_Q1_0;
|
||||
case HTP_TYPE_Q2_0: return HTP_TENSOR_TILE_SIZE_Q2_0;
|
||||
case HTP_TYPE_MXFP4: return HTP_TENSOR_TILE_SIZE_MXFP4;
|
||||
case HTP_TYPE_F16: return HTP_TENSOR_TILE_SIZE_F16;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t htp_tensor_get_tile_k(int type) {
|
||||
switch (type) {
|
||||
case HTP_TYPE_Q1_0: return HTP_TENSOR_TILE_K_Q1_0;
|
||||
case HTP_TYPE_Q2_0: return HTP_TENSOR_TILE_K_Q2_0;
|
||||
default: return 32;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t htp_tensor_get_tile_padded_size(int type) {
|
||||
return (htp_tensor_get_tile_size(type) + 127u) & ~127u;
|
||||
}
|
||||
@@ -193,10 +184,12 @@ static inline uint32_t htp_tensor_get_row_size(int type, uint32_t ne00) {
|
||||
case HTP_TYPE_Q4_1: return (ne00 / 32) * 20;
|
||||
case HTP_TYPE_MXFP4: return (ne00 / 32) * 17;
|
||||
case HTP_TYPE_Q2_K: return (ne00 / 256) * 84;
|
||||
case HTP_TYPE_Q2_0: return (ne00 / 64) * 18;
|
||||
case HTP_TYPE_Q3_K: return (ne00 / 256) * 110;
|
||||
case HTP_TYPE_Q4_K: return (ne00 / 256) * 144;
|
||||
case HTP_TYPE_Q5_K: return (ne00 / 256) * 176;
|
||||
case HTP_TYPE_Q6_K: return (ne00 / 256) * 210;
|
||||
case HTP_TYPE_Q1_0: return (ne00 / HTP_TENSOR_TILE_K_Q1_0) * 18;
|
||||
case HTP_TYPE_I32: return ne00 * 4;
|
||||
case HTP_TYPE_I64: return ne00 * 8;
|
||||
default: return 0;
|
||||
|
||||
@@ -29,29 +29,27 @@ static inline void quantize_block_f32_q8_1_tiled_impl(float * restrict x, uint8_
|
||||
|
||||
HVX_Vector vx0_qf = Q6_Vqf32_vsub_VsfVsf(vx[0], zero);
|
||||
HVX_Vector vx1_qf = Q6_Vqf32_vsub_VsfVsf(vx[1], zero);
|
||||
HVX_Vector vx2_qf = Q6_Vqf32_vsub_VsfVsf(vx[2], zero);
|
||||
HVX_Vector vx3_qf = Q6_Vqf32_vsub_VsfVsf(vx[3], zero);
|
||||
|
||||
HVX_Vector vmax0_qf = Q6_Vqf32_vsub_VsfVsf(vmax0_sf, zero);
|
||||
HVX_Vector vmax1_qf = Q6_Vqf32_vsub_VsfVsf(vmax1_sf, zero);
|
||||
|
||||
HVX_Vector vmax01_hf = Q6_Vh_vdeal_Vh(Q6_Vhf_equals_Wqf32(Q6_W_vcombine_VV(vmax1_qf, vmax0_qf)));
|
||||
HVX_Vector vx01_hf = Q6_Vh_vdeal_Vh(Q6_Vhf_equals_Wqf32(Q6_W_vcombine_VV(vx1_qf, vx0_qf)));
|
||||
HVX_Vector vd01_qf16 = Q6_Vqf16_vmpy_VhfVhf(vmax01_hf, Q6_Vh_vsplat_R(0x2008)); // 1.0 / 127.0
|
||||
HVX_Vector vd01_hf = Q6_Vhf_equals_Vqf16(vd01_qf16);
|
||||
HVX_Vector vd01_inv_hf = hvx_vec_inverse_f16(vd01_hf);
|
||||
vx01_hf = Q6_Vhf_equals_Vqf16(Q6_Vqf16_vmpy_VhfVhf(vx01_hf, vd01_inv_hf));
|
||||
|
||||
HVX_Vector vx2_qf = Q6_Vqf32_vsub_VsfVsf(vx[2], zero);
|
||||
HVX_Vector vx3_qf = Q6_Vqf32_vsub_VsfVsf(vx[3], zero);
|
||||
HVX_Vector vmax2_qf = Q6_Vqf32_vsub_VsfVsf(vmax2_sf, zero);
|
||||
HVX_Vector vmax3_qf = Q6_Vqf32_vsub_VsfVsf(vmax3_sf, zero);
|
||||
|
||||
HVX_Vector vmax01_hf = Q6_Vh_vdeal_Vh(Q6_Vhf_equals_Wqf32(Q6_W_vcombine_VV(vmax1_qf, vmax0_qf)));
|
||||
HVX_Vector vmax23_hf = Q6_Vh_vdeal_Vh(Q6_Vhf_equals_Wqf32(Q6_W_vcombine_VV(vmax3_qf, vmax2_qf)));
|
||||
|
||||
HVX_Vector vx01_hf = Q6_Vh_vdeal_Vh(Q6_Vhf_equals_Wqf32(Q6_W_vcombine_VV(vx1_qf, vx0_qf)));
|
||||
HVX_Vector vx23_hf = Q6_Vh_vdeal_Vh(Q6_Vhf_equals_Wqf32(Q6_W_vcombine_VV(vx3_qf, vx2_qf)));
|
||||
|
||||
HVX_Vector vd01_qf16 = Q6_Vqf16_vmpy_VhfVhf(vmax01_hf, Q6_Vh_vsplat_R(0x2008)); // 1.0 / 127.0
|
||||
HVX_Vector vd23_qf16 = Q6_Vqf16_vmpy_VhfVhf(vmax23_hf, Q6_Vh_vsplat_R(0x2008)); // 1.0 / 127.0
|
||||
HVX_Vector vd01_hf = Q6_Vhf_equals_Vqf16(vd01_qf16);
|
||||
HVX_Vector vd23_hf = Q6_Vhf_equals_Vqf16(vd23_qf16);
|
||||
|
||||
HVX_Vector vd01_inv_hf = hvx_vec_inverse_f16(vd01_hf);
|
||||
HVX_Vector vmax23_hf = Q6_Vh_vdeal_Vh(Q6_Vhf_equals_Wqf32(Q6_W_vcombine_VV(vmax3_qf, vmax2_qf)));
|
||||
HVX_Vector vx23_hf = Q6_Vh_vdeal_Vh(Q6_Vhf_equals_Wqf32(Q6_W_vcombine_VV(vx3_qf, vx2_qf)));
|
||||
HVX_Vector vd23_qf16 = Q6_Vqf16_vmpy_VhfVhf(vmax23_hf, Q6_Vh_vsplat_R(0x2008)); // 1.0 / 127.0
|
||||
HVX_Vector vd23_hf = Q6_Vhf_equals_Vqf16(vd23_qf16);
|
||||
HVX_Vector vd23_inv_hf = hvx_vec_inverse_f16(vd23_hf);
|
||||
vx01_hf = Q6_Vhf_equals_Vqf16(Q6_Vqf16_vmpy_VhfVhf(vx01_hf, vd01_inv_hf));
|
||||
vx23_hf = Q6_Vhf_equals_Vqf16(Q6_Vqf16_vmpy_VhfVhf(vx23_hf, vd23_inv_hf));
|
||||
vx23_hf = Q6_Vhf_equals_Vqf16(Q6_Vqf16_vmpy_VhfVhf(vx23_hf, vd23_inv_hf));
|
||||
|
||||
HVX_Vector vx01_i16 = hvx_vec_i16_from_hf_rnd_sat(vx01_hf);
|
||||
HVX_Vector vx23_i16 = hvx_vec_i16_from_hf_rnd_sat(vx23_hf);
|
||||
@@ -81,25 +79,10 @@ static inline void quantize_block_f32_q8_1_tiled_impl(float * restrict x, uint8_
|
||||
HVX_Vector voff01_hf = hvx_vec_f32_to_f16(voff0_sf, voff1_sf);
|
||||
HVX_Vector voff23_hf = hvx_vec_f32_to_f16(voff2_sf, voff3_sf);
|
||||
|
||||
HVX_Vector r_scale[4] = {
|
||||
hvx_vec_repl_f16(vd01_hf),
|
||||
hvx_vec_repl_f16(Q6_V_vror_VR(vd01_hf, 64)),
|
||||
hvx_vec_repl_f16(vd23_hf),
|
||||
hvx_vec_repl_f16(Q6_V_vror_VR(vd23_hf, 64)),
|
||||
};
|
||||
HVX_Vector r_offset[4] = {
|
||||
hvx_vec_repl_f16(voff01_hf),
|
||||
hvx_vec_repl_f16(Q6_V_vror_VR(voff01_hf, 64)),
|
||||
hvx_vec_repl_f16(voff23_hf),
|
||||
hvx_vec_repl_f16(Q6_V_vror_VR(voff23_hf, 64)),
|
||||
};
|
||||
int32_t __attribute__((aligned(128))) ws[32];
|
||||
if (sums16) {
|
||||
// |sum of 16 int8| <= 2032 fits a halfword
|
||||
int32_t __attribute__((aligned(128))) ws[32];
|
||||
*(HVX_Vector *) ws = v_sums;
|
||||
for (int b = 0; b < 4; b++) {
|
||||
r_offset[b] = Q6_V_vsplat_R(((uint32_t) ws[8 * b + 4] << 16) | ((uint32_t) ws[8 * b] & 0xFFFF));
|
||||
}
|
||||
}
|
||||
|
||||
static const uint8_t __attribute__((aligned(128))) repl[128] = {
|
||||
@@ -114,8 +97,15 @@ static inline void quantize_block_f32_q8_1_tiled_impl(float * restrict x, uint8_
|
||||
};
|
||||
HVX_Vector v_repl_ctrl = * (const HVX_Vector *) repl;
|
||||
|
||||
#pragma unroll
|
||||
for (int b = 0; b < 4; b++) {
|
||||
HVX_Vector v_act = Q6_V_vror_VR(vx_i8, b * 32);
|
||||
HVX_Vector vd_b = (b < 2) ? vd01_hf : vd23_hf;
|
||||
HVX_Vector voff_b = (b < 2) ? voff01_hf : voff23_hf;
|
||||
HVX_Vector r_scale = hvx_vec_repl_f16((b & 1) ? Q6_V_vror_VR(vd_b, 64) : vd_b);
|
||||
HVX_Vector r_offset = sums16
|
||||
? Q6_V_vsplat_R(((uint32_t) ws[8 * b + 4] << 16) | ((uint32_t) ws[8 * b] & 0xFFFF))
|
||||
: hvx_vec_repl_f16((b & 1) ? Q6_V_vror_VR(voff_b, 64) : voff_b);
|
||||
|
||||
HVX_Vector r0 = Q6_V_vdelta_VV(v_act, v_repl_ctrl);
|
||||
HVX_Vector r1 = Q6_V_vdelta_VV(Q6_V_vror_VR(v_act, 4), v_repl_ctrl);
|
||||
@@ -135,8 +125,8 @@ static inline void quantize_block_f32_q8_1_tiled_impl(float * restrict x, uint8_
|
||||
dst[5] = r5;
|
||||
dst[6] = r6;
|
||||
dst[7] = r7;
|
||||
dst[8] = r_scale[b];
|
||||
dst[9] = r_offset[b];
|
||||
dst[8] = r_scale;
|
||||
dst[9] = r_offset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,6 +385,36 @@ static inline HVX_VectorPair unpack_and_interleave_4bit_x2(HVX_Vector v_src, HVX
|
||||
return Q6_W_vcombine_VV(v_W1, v_W0);
|
||||
}
|
||||
|
||||
static inline HVX_Vector unpack_q1_0_group(
|
||||
HVX_Vector v_plane,
|
||||
int g,
|
||||
HVX_Vector v_neg_one,
|
||||
HVX_Vector v_pos_one
|
||||
) {
|
||||
HVX_VectorPred q = Q6_Q_vand_VR(v_plane, 0x01010101u << g);
|
||||
return Q6_V_vmux_QVV(q, v_pos_one, v_neg_one);
|
||||
}
|
||||
|
||||
static inline HVX_Vector accum_q1_0_32x1(
|
||||
HVX_Vector v_plane,
|
||||
const HVX_Vector * restrict v_act,
|
||||
HVX_Vector v_neg_one,
|
||||
HVX_Vector v_pos_one
|
||||
) {
|
||||
HVX_Vector v_sum_lo = Q6_V_vzero();
|
||||
HVX_Vector v_sum_hi = Q6_V_vzero();
|
||||
|
||||
#pragma unroll
|
||||
for (int g = 0; g < 4; g++) {
|
||||
HVX_Vector v_W_lo = unpack_q1_0_group(v_plane, g, v_neg_one, v_pos_one);
|
||||
HVX_Vector v_W_hi = unpack_q1_0_group(v_plane, g + 4, v_neg_one, v_pos_one);
|
||||
v_sum_lo = Q6_Vw_vrmpyacc_VwVbVb(v_sum_lo, v_W_lo, v_act[g]);
|
||||
v_sum_hi = Q6_Vw_vrmpyacc_VwVbVb(v_sum_hi, v_W_hi, v_act[g + 4]);
|
||||
}
|
||||
|
||||
return Q6_Vw_vadd_VwVw(v_sum_lo, v_sum_hi);
|
||||
}
|
||||
|
||||
static inline HVX_Vector accum_4bit_32x1(
|
||||
const HVX_Vector * restrict vptr,
|
||||
const HVX_Vector * restrict v_act,
|
||||
@@ -437,6 +457,33 @@ static inline HVX_Vector accum_4bit_32x1_lut(
|
||||
return Q6_Vw_vadd_VwVw(v_sum0, v_sum1);
|
||||
}
|
||||
|
||||
static inline HVX_VectorPair accum_q1_0_32x2(
|
||||
HVX_Vector v_plane,
|
||||
const HVX_Vector * restrict v_act0,
|
||||
const HVX_Vector * restrict v_act1,
|
||||
HVX_Vector v_neg_one,
|
||||
HVX_Vector v_pos_one
|
||||
) {
|
||||
HVX_Vector v_sum0_lo = Q6_V_vzero();
|
||||
HVX_Vector v_sum0_hi = Q6_V_vzero();
|
||||
HVX_Vector v_sum1_lo = Q6_V_vzero();
|
||||
HVX_Vector v_sum1_hi = Q6_V_vzero();
|
||||
|
||||
#pragma unroll
|
||||
for (int g = 0; g < 4; g++) {
|
||||
HVX_Vector v_W_lo = unpack_q1_0_group(v_plane, g, v_neg_one, v_pos_one);
|
||||
HVX_Vector v_W_hi = unpack_q1_0_group(v_plane, g + 4, v_neg_one, v_pos_one);
|
||||
v_sum0_lo = Q6_Vw_vrmpyacc_VwVbVb(v_sum0_lo, v_W_lo, v_act0[g]);
|
||||
v_sum0_hi = Q6_Vw_vrmpyacc_VwVbVb(v_sum0_hi, v_W_hi, v_act0[g + 4]);
|
||||
v_sum1_lo = Q6_Vw_vrmpyacc_VwVbVb(v_sum1_lo, v_W_lo, v_act1[g]);
|
||||
v_sum1_hi = Q6_Vw_vrmpyacc_VwVbVb(v_sum1_hi, v_W_hi, v_act1[g + 4]);
|
||||
}
|
||||
|
||||
return Q6_W_vcombine_VV(
|
||||
Q6_Vw_vadd_VwVw(v_sum1_lo, v_sum1_hi),
|
||||
Q6_Vw_vadd_VwVw(v_sum0_lo, v_sum0_hi));
|
||||
}
|
||||
|
||||
static inline HVX_VectorPair accum_4bit_32x2(
|
||||
const HVX_Vector * restrict vptr,
|
||||
const HVX_Vector * restrict v_act0,
|
||||
@@ -771,6 +818,53 @@ static inline void accum_q2_k_32x2(
|
||||
*v_sums1 = Q6_W_vcombine_VV(v_sum1_hi, v_sum1_lo);
|
||||
}
|
||||
|
||||
// Q2_0: unsigned codes 0..3; the -1 offset is applied via the Q8_1 block sum (v_act[9])
|
||||
static inline HVX_Vector accum_q2_0_32x1(
|
||||
const HVX_Vector * restrict vptr,
|
||||
const HVX_Vector * restrict v_act
|
||||
) {
|
||||
HVX_Vector v_sum_lo = Q6_V_vzero();
|
||||
HVX_Vector v_sum_hi = Q6_V_vzero();
|
||||
HVX_Vector mask_03 = Q6_Vb_vsplat_R(0x03);
|
||||
|
||||
#pragma unroll
|
||||
for (int g = 0; g < 4; g++) {
|
||||
HVX_Vector v_W_lo = unpack_q3_k_low2(vptr, g, mask_03);
|
||||
HVX_Vector v_W_hi = unpack_q3_k_low2(vptr, g + 4, mask_03);
|
||||
v_sum_lo = Q6_Vw_vrmpyacc_VwVubVb(v_sum_lo, v_W_lo, v_act[g]);
|
||||
v_sum_hi = Q6_Vw_vrmpyacc_VwVubVb(v_sum_hi, v_W_hi, v_act[g + 4]);
|
||||
}
|
||||
|
||||
return Q6_Vw_vadd_VwVw(v_sum_lo, v_sum_hi);
|
||||
}
|
||||
|
||||
static inline void accum_q2_0_32x2(
|
||||
const HVX_Vector * restrict vptr,
|
||||
const HVX_Vector * restrict v_act0,
|
||||
const HVX_Vector * restrict v_act1,
|
||||
HVX_Vector * v_sum0,
|
||||
HVX_Vector * v_sum1
|
||||
) {
|
||||
HVX_Vector v_sum0_lo = Q6_V_vzero();
|
||||
HVX_Vector v_sum0_hi = Q6_V_vzero();
|
||||
HVX_Vector v_sum1_lo = Q6_V_vzero();
|
||||
HVX_Vector v_sum1_hi = Q6_V_vzero();
|
||||
HVX_Vector mask_03 = Q6_Vb_vsplat_R(0x03);
|
||||
|
||||
#pragma unroll
|
||||
for (int g = 0; g < 4; g++) {
|
||||
HVX_Vector v_W_lo = unpack_q3_k_low2(vptr, g, mask_03);
|
||||
HVX_Vector v_W_hi = unpack_q3_k_low2(vptr, g + 4, mask_03);
|
||||
v_sum0_lo = Q6_Vw_vrmpyacc_VwVubVb(v_sum0_lo, v_W_lo, v_act0[g]);
|
||||
v_sum0_hi = Q6_Vw_vrmpyacc_VwVubVb(v_sum0_hi, v_W_hi, v_act0[g + 4]);
|
||||
v_sum1_lo = Q6_Vw_vrmpyacc_VwVubVb(v_sum1_lo, v_W_lo, v_act1[g]);
|
||||
v_sum1_hi = Q6_Vw_vrmpyacc_VwVubVb(v_sum1_hi, v_W_hi, v_act1[g + 4]);
|
||||
}
|
||||
|
||||
*v_sum0 = Q6_Vw_vadd_VwVw(v_sum0_lo, v_sum0_hi);
|
||||
*v_sum1 = Q6_Vw_vadd_VwVw(v_sum1_lo, v_sum1_hi);
|
||||
}
|
||||
|
||||
// Q2_K: fp16 d (*v_d) and -dmin (*v_m) of the 32 rows of a super-block (8 tiles, VTCM stride 384), each in both vector halves
|
||||
static inline void q2_k_load_dmin(const uint8_t * restrict sb, HVX_Vector * restrict v_d, HVX_Vector * restrict v_m) {
|
||||
HVX_Vector v_dd = Q6_V_vzero(); // lanes 0..31: d, lanes 32..63: -dmin
|
||||
@@ -824,6 +918,89 @@ static inline HVX_Vector q2_k_int_scale(HVX_Vector v_isum, HVX_Vector v_msum, HV
|
||||
return hvx_vec_mul_f32_f32(v_t, v_da32);
|
||||
}
|
||||
|
||||
static void tiled_vec_dot_q1_0_32x1(const uint32_t n, float * restrict s, const void * restrict vx, const void * restrict vy, uint32_t valid_rows, const float * restrict sz) {
|
||||
const uint8_t * restrict tile_ptr = vx;
|
||||
const uint8_t * restrict y_q = vy;
|
||||
|
||||
HVX_Vector v_sum_qf = Q6_V_vzero();
|
||||
HVX_Vector v_neg_one = Q6_Vb_vsplat_R(-1);
|
||||
HVX_Vector v_pos_one = Q6_Vb_vsplat_R(1);
|
||||
|
||||
const uint32_t n_k_tiles = n / HTP_TENSOR_TILE_K_Q1_0;
|
||||
for (uint32_t kt = 0; kt < n_k_tiles; kt++) {
|
||||
const HVX_Vector * restrict vptr = (const HVX_Vector *) (tile_ptr + kt * htp_tensor_get_tile_padded_size(HTP_TYPE_Q1_0));
|
||||
HVX_Vector v_tile_qf = Q6_V_vzero();
|
||||
|
||||
#pragma unroll
|
||||
for (int quarter = 0; quarter < 4; quarter++) {
|
||||
const HVX_Vector * restrict v_act = (const HVX_Vector *) (y_q + (4 * kt + quarter) * HTP_MM_ACT_TILE_SIZE_Q8_0);
|
||||
HVX_Vector v_sum_sf = Q6_Vsf_equals_Vw(accum_q1_0_32x1(vptr[quarter], v_act, v_neg_one, v_pos_one));
|
||||
HVX_Vector v_da_sf = Q6_V_lo_W(hvx_vec_f16_to_f32_shuff(v_act[8]));
|
||||
HVX_Vector v_q_qf = Q6_Vqf32_vmpy_VsfVsf(v_sum_sf, v_da_sf);
|
||||
v_tile_qf = (quarter == 0) ? v_q_qf : Q6_Vqf32_vadd_Vqf32Vqf32(v_tile_qf, v_q_qf);
|
||||
}
|
||||
|
||||
HVX_Vector v_dw_sf = Q6_V_lo_W(hvx_vec_f16_to_f32(vptr[4]));
|
||||
v_sum_qf = Q6_Vqf32_vadd_Vqf32Vqf32(v_sum_qf, Q6_Vqf32_vmpy_VsfVsf(Q6_Vsf_equals_Vqf32(v_tile_qf), v_dw_sf));
|
||||
}
|
||||
|
||||
HVX_Vector v_sum_float = Q6_Vsf_equals_Vqf32(v_sum_qf);
|
||||
if (sz) {
|
||||
hvx_vec_store_u(s, valid_rows * sizeof(float), hvx_vec_add_f32_f32(v_sum_float, hvx_vmemu(sz)));
|
||||
} else {
|
||||
hvx_vec_store_u(s, valid_rows * sizeof(float), v_sum_float);
|
||||
}
|
||||
}
|
||||
|
||||
static void tiled_vec_dot_q1_0_32x2(const uint32_t n, float * restrict s0, float * restrict s1, const void * restrict vx, const void * restrict vy0, const void * restrict vy1, uint32_t valid_rows, const float * restrict sz0, const float * restrict sz1) {
|
||||
const uint8_t * restrict tile_ptr = vx;
|
||||
const uint8_t * restrict y0_q = vy0;
|
||||
const uint8_t * restrict y1_q = vy1;
|
||||
|
||||
HVX_Vector v_sum_qf_c0 = Q6_V_vzero();
|
||||
HVX_Vector v_sum_qf_c1 = Q6_V_vzero();
|
||||
HVX_Vector v_neg_one = Q6_Vb_vsplat_R(-1);
|
||||
HVX_Vector v_pos_one = Q6_Vb_vsplat_R(1);
|
||||
|
||||
const uint32_t n_k_tiles = n / HTP_TENSOR_TILE_K_Q1_0;
|
||||
for (uint32_t kt = 0; kt < n_k_tiles; kt++) {
|
||||
const HVX_Vector * restrict vptr = (const HVX_Vector *) (tile_ptr + kt * htp_tensor_get_tile_padded_size(HTP_TYPE_Q1_0));
|
||||
HVX_Vector v_tile_qf_c0 = Q6_V_vzero();
|
||||
HVX_Vector v_tile_qf_c1 = Q6_V_vzero();
|
||||
|
||||
for (int quarter = 0; quarter < 4; quarter++) {
|
||||
const uint32_t act_tile = 4 * kt + quarter;
|
||||
const HVX_Vector * restrict v_act0 = (const HVX_Vector *) (y0_q + act_tile * HTP_MM_ACT_TILE_SIZE_Q8_0);
|
||||
const HVX_Vector * restrict v_act1 = (const HVX_Vector *) (y1_q + act_tile * HTP_MM_ACT_TILE_SIZE_Q8_0);
|
||||
HVX_VectorPair v_sums = accum_q1_0_32x2(vptr[quarter], v_act0, v_act1, v_neg_one, v_pos_one);
|
||||
HVX_Vector v_sum_sf_c0 = Q6_Vsf_equals_Vw(Q6_V_lo_W(v_sums));
|
||||
HVX_Vector v_sum_sf_c1 = Q6_Vsf_equals_Vw(Q6_V_hi_W(v_sums));
|
||||
HVX_Vector v_da_sf_c0 = Q6_V_lo_W(hvx_vec_f16_to_f32_shuff(v_act0[8]));
|
||||
HVX_Vector v_da_sf_c1 = Q6_V_lo_W(hvx_vec_f16_to_f32_shuff(v_act1[8]));
|
||||
|
||||
v_tile_qf_c0 = Q6_Vqf32_vadd_Vqf32Vqf32(v_tile_qf_c0, Q6_Vqf32_vmpy_VsfVsf(v_sum_sf_c0, v_da_sf_c0));
|
||||
v_tile_qf_c1 = Q6_Vqf32_vadd_Vqf32Vqf32(v_tile_qf_c1, Q6_Vqf32_vmpy_VsfVsf(v_sum_sf_c1, v_da_sf_c1));
|
||||
}
|
||||
|
||||
HVX_Vector v_dw_sf = Q6_V_lo_W(hvx_vec_f16_to_f32(vptr[4]));
|
||||
v_sum_qf_c0 = Q6_Vqf32_vadd_Vqf32Vqf32(v_sum_qf_c0, Q6_Vqf32_vmpy_VsfVsf(Q6_Vsf_equals_Vqf32(v_tile_qf_c0), v_dw_sf));
|
||||
v_sum_qf_c1 = Q6_Vqf32_vadd_Vqf32Vqf32(v_sum_qf_c1, Q6_Vqf32_vmpy_VsfVsf(Q6_Vsf_equals_Vqf32(v_tile_qf_c1), v_dw_sf));
|
||||
}
|
||||
|
||||
HVX_Vector v_sum_float_c0 = Q6_Vsf_equals_Vqf32(v_sum_qf_c0);
|
||||
HVX_Vector v_sum_float_c1 = Q6_Vsf_equals_Vqf32(v_sum_qf_c1);
|
||||
if (sz0) {
|
||||
hvx_vec_store_u(s0, valid_rows * sizeof(float), hvx_vec_add_f32_f32(v_sum_float_c0, hvx_vmemu(sz0)));
|
||||
} else {
|
||||
hvx_vec_store_u(s0, valid_rows * sizeof(float), v_sum_float_c0);
|
||||
}
|
||||
if (sz1) {
|
||||
hvx_vec_store_u(s1, valid_rows * sizeof(float), hvx_vec_add_f32_f32(v_sum_float_c1, hvx_vmemu(sz1)));
|
||||
} else {
|
||||
hvx_vec_store_u(s1, valid_rows * sizeof(float), v_sum_float_c1);
|
||||
}
|
||||
}
|
||||
|
||||
static void tiled_vec_dot_q4_0_32x1(const uint32_t n, float * restrict s, const void * restrict vx, const void * restrict vy, uint32_t valid_rows, const float * restrict sz) {
|
||||
const uint8_t * restrict tile_ptr = vx;
|
||||
const uint8_t * restrict y_q = vy;
|
||||
@@ -1362,6 +1539,93 @@ static void tiled_vec_dot_q2_k_32x2(const uint32_t n, float * restrict s0, float
|
||||
}
|
||||
}
|
||||
|
||||
// One Q2_0 weight tile covers 64 k: act tiles 2 * kt and 2 * kt + 1, each 32 k wide (Q8_1)
|
||||
static void tiled_vec_dot_q2_0_32x1(const uint32_t n, float * restrict s, const void * restrict vx, const void * restrict vy, uint32_t valid_rows, const float * restrict sz) {
|
||||
const uint8_t * restrict tile_ptr = vx;
|
||||
const uint8_t * restrict y_q = vy;
|
||||
|
||||
HVX_Vector v_sum_float = Q6_V_vzero();
|
||||
HVX_Vector v_one_f16 = hvx_vec_splat_f16(1.0);
|
||||
|
||||
uint32_t n_weight_tiles = n / HTP_MM_WEIGHT_TILE_K_Q2_0;
|
||||
for (uint32_t kt = 0; kt < n_weight_tiles; kt++) {
|
||||
const HVX_Vector * restrict vptr = (const HVX_Vector *) (tile_ptr + kt * HTP_MM_WEIGHT_ALIGNED_TILE_SIZE_Q2_0);
|
||||
HVX_Vector v_tile_qf = Q6_V_vzero();
|
||||
|
||||
#pragma unroll
|
||||
for (int half = 0; half < 2; half++) {
|
||||
const HVX_Vector * restrict v_act = (const HVX_Vector *) (y_q + (2 * kt + half) * HTP_MM_ACT_TILE_SIZE_Q8_1);
|
||||
|
||||
HVX_Vector v_sum_sf = Q6_Vsf_equals_Vw(accum_q2_0_32x1(vptr + 2 * half, v_act));
|
||||
HVX_Vector v_da_sf = Q6_V_lo_W(hvx_vec_f16_to_f32_shuff(v_act[8]));
|
||||
HVX_Vector v_sa_qf = Q6_V_lo_W(Q6_Wqf32_vmpy_VhfVhf(v_act[9], v_one_f16));
|
||||
HVX_Vector v_h_qf = Q6_Vqf32_vsub_Vqf32Vqf32(Q6_Vqf32_vmpy_VsfVsf(v_sum_sf, v_da_sf), v_sa_qf);
|
||||
v_tile_qf = (half == 0) ? v_h_qf : Q6_Vqf32_vadd_Vqf32Vqf32(v_tile_qf, v_h_qf);
|
||||
}
|
||||
|
||||
HVX_Vector v_dw_sf = Q6_V_lo_W(hvx_vec_f16_to_f32(vptr[4]));
|
||||
v_sum_float = hvx_vec_add_f32_f32(v_sum_float, hvx_vec_mul_f32_f32(Q6_Vsf_equals_Vqf32(v_tile_qf), v_dw_sf));
|
||||
}
|
||||
|
||||
if (sz) {
|
||||
hvx_vec_store_u(s, valid_rows * sizeof(float), hvx_vec_add_f32_f32(v_sum_float, hvx_vmemu(sz)));
|
||||
} else {
|
||||
hvx_vec_store_u(s, valid_rows * sizeof(float), v_sum_float);
|
||||
}
|
||||
}
|
||||
|
||||
static void tiled_vec_dot_q2_0_32x2(const uint32_t n, float * restrict s0, float * restrict s1, const void * restrict vx, const void * restrict vy0, const void * restrict vy1, uint32_t valid_rows, const float * restrict sz0, const float * restrict sz1) {
|
||||
const uint8_t * restrict tile_ptr = vx;
|
||||
const uint8_t * restrict y0_q = vy0;
|
||||
const uint8_t * restrict y1_q = vy1;
|
||||
|
||||
HVX_Vector v_sum_float_c0 = Q6_V_vzero();
|
||||
HVX_Vector v_sum_float_c1 = Q6_V_vzero();
|
||||
HVX_Vector v_one_f16 = hvx_vec_splat_f16(1.0);
|
||||
|
||||
uint32_t n_weight_tiles = n / HTP_MM_WEIGHT_TILE_K_Q2_0;
|
||||
for (uint32_t kt = 0; kt < n_weight_tiles; kt++) {
|
||||
const HVX_Vector * restrict vptr = (const HVX_Vector *) (tile_ptr + kt * HTP_MM_WEIGHT_ALIGNED_TILE_SIZE_Q2_0);
|
||||
HVX_Vector v_tile_qf_c0 = Q6_V_vzero();
|
||||
HVX_Vector v_tile_qf_c1 = Q6_V_vzero();
|
||||
|
||||
#pragma unroll
|
||||
for (int half = 0; half < 2; half++) {
|
||||
const HVX_Vector * restrict v_act0 = (const HVX_Vector *) (y0_q + (2 * kt + half) * HTP_MM_ACT_TILE_SIZE_Q8_1);
|
||||
const HVX_Vector * restrict v_act1 = (const HVX_Vector *) (y1_q + (2 * kt + half) * HTP_MM_ACT_TILE_SIZE_Q8_1);
|
||||
|
||||
HVX_Vector v_sum_c0, v_sum_c1;
|
||||
accum_q2_0_32x2(vptr + 2 * half, v_act0, v_act1, &v_sum_c0, &v_sum_c1);
|
||||
|
||||
HVX_Vector v_da_sf_c0 = Q6_V_lo_W(hvx_vec_f16_to_f32_shuff(v_act0[8]));
|
||||
HVX_Vector v_da_sf_c1 = Q6_V_lo_W(hvx_vec_f16_to_f32_shuff(v_act1[8]));
|
||||
HVX_Vector v_sa_qf_c0 = Q6_V_lo_W(Q6_Wqf32_vmpy_VhfVhf(v_act0[9], v_one_f16));
|
||||
HVX_Vector v_sa_qf_c1 = Q6_V_lo_W(Q6_Wqf32_vmpy_VhfVhf(v_act1[9], v_one_f16));
|
||||
|
||||
HVX_Vector v_h_qf_c0 = Q6_Vqf32_vsub_Vqf32Vqf32(Q6_Vqf32_vmpy_VsfVsf(Q6_Vsf_equals_Vw(v_sum_c0), v_da_sf_c0), v_sa_qf_c0);
|
||||
HVX_Vector v_h_qf_c1 = Q6_Vqf32_vsub_Vqf32Vqf32(Q6_Vqf32_vmpy_VsfVsf(Q6_Vsf_equals_Vw(v_sum_c1), v_da_sf_c1), v_sa_qf_c1);
|
||||
|
||||
v_tile_qf_c0 = (half == 0) ? v_h_qf_c0 : Q6_Vqf32_vadd_Vqf32Vqf32(v_tile_qf_c0, v_h_qf_c0);
|
||||
v_tile_qf_c1 = (half == 0) ? v_h_qf_c1 : Q6_Vqf32_vadd_Vqf32Vqf32(v_tile_qf_c1, v_h_qf_c1);
|
||||
}
|
||||
|
||||
HVX_Vector v_dw_sf = Q6_V_lo_W(hvx_vec_f16_to_f32(vptr[4]));
|
||||
v_sum_float_c0 = hvx_vec_add_f32_f32(v_sum_float_c0, hvx_vec_mul_f32_f32(Q6_Vsf_equals_Vqf32(v_tile_qf_c0), v_dw_sf));
|
||||
v_sum_float_c1 = hvx_vec_add_f32_f32(v_sum_float_c1, hvx_vec_mul_f32_f32(Q6_Vsf_equals_Vqf32(v_tile_qf_c1), v_dw_sf));
|
||||
}
|
||||
|
||||
if (sz0) {
|
||||
hvx_vec_store_u(s0, valid_rows * sizeof(float), hvx_vec_add_f32_f32(v_sum_float_c0, hvx_vmemu(sz0)));
|
||||
} else {
|
||||
hvx_vec_store_u(s0, valid_rows * sizeof(float), v_sum_float_c0);
|
||||
}
|
||||
if (sz1) {
|
||||
hvx_vec_store_u(s1, valid_rows * sizeof(float), hvx_vec_add_f32_f32(v_sum_float_c1, hvx_vmemu(sz1)));
|
||||
} else {
|
||||
hvx_vec_store_u(s1, valid_rows * sizeof(float), v_sum_float_c1);
|
||||
}
|
||||
}
|
||||
|
||||
static void tiled_vec_dot_iq4nl_32x1(const uint32_t n, float * restrict s, const void * restrict vx, const void * restrict vy, uint32_t valid_rows, const float * restrict sz) {
|
||||
const uint8_t * restrict tile_ptr = vx;
|
||||
const uint8_t * restrict y_q = vy;
|
||||
|
||||
@@ -191,6 +191,64 @@ static inline void hvx_dequantize_row_flat_bf16_f32(float * restrict dst_ptr, co
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hvx_dequantize_row_flat_q1_0_f32(float * restrict dst_ptr, const void * restrict src_ptr, uint32_t n) {
|
||||
static const uint32_t __attribute__((aligned(128))) bit_mask[32] = {
|
||||
1u << 0, 1u << 1, 1u << 2, 1u << 3, 1u << 4, 1u << 5, 1u << 6, 1u << 7,
|
||||
1u << 8, 1u << 9, 1u << 10, 1u << 11, 1u << 12, 1u << 13, 1u << 14, 1u << 15,
|
||||
1u << 16, 1u << 17, 1u << 18, 1u << 19, 1u << 20, 1u << 21, 1u << 22, 1u << 23,
|
||||
1u << 24, 1u << 25, 1u << 26, 1u << 27, 1u << 28, 1u << 29, 1u << 30, 1u << 31,
|
||||
};
|
||||
const int nb = n / QK1_0;
|
||||
const block_q1_0 * src = (const block_q1_0 *) src_ptr;
|
||||
const HVX_Vector v_bit_mask = *(const HVX_Vector *) bit_mask;
|
||||
const HVX_Vector v_zero = Q6_V_vzero();
|
||||
const HVX_Vector v_sign_bit = Q6_V_vsplat_R(0x80000000);
|
||||
|
||||
for (int i = 0; i < nb; i++) {
|
||||
HVX_Vector vd_f16 = Q6_Vh_vsplat_R(*(const int16_t *) &src[i].d);
|
||||
HVX_Vector vd = Q6_V_lo_W(hvx_vec_f16_to_f32(vd_f16));
|
||||
HVX_Vector neg_vd = Q6_V_vxor_VV(vd, v_sign_bit);
|
||||
HVX_Vector v_qs = *(const HVX_UVector *) src[i].qs;
|
||||
|
||||
float * block_dst = dst_ptr + i * QK1_0;
|
||||
#pragma unroll
|
||||
for (int q = 0; q < 4; q++) {
|
||||
HVX_Vector v_bits = hvx_vec_repl_u32(q == 0 ? v_qs : Q6_V_vror_VR(v_qs, 4 * q));
|
||||
HVX_VectorPred q_zero = Q6_Q_vcmp_eq_VwVw(Q6_V_vand_VV(v_bits, v_bit_mask), v_zero);
|
||||
hvx_vmem(block_dst + q * 32) = Q6_V_vmux_QVV(q_zero, neg_vd, vd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hvx_dequantize_row_flat_q2_0_f32(float * restrict dst_ptr, const void * restrict src_ptr, uint32_t n) {
|
||||
const int nb = n / QK2_0;
|
||||
const block_q2_0 * src = (const block_q2_0 *) src_ptr;
|
||||
const HVX_Vector mask_03 = Q6_Vb_vsplat_R(0x03);
|
||||
const HVX_Vector v_one = Q6_Vb_vsplat_R(1);
|
||||
|
||||
for (int i = 0; i < nb; i++) {
|
||||
HVX_Vector vd_f16 = Q6_Vh_vsplat_R(*(const int16_t *) &src[i].d);
|
||||
HVX_Vector vd = Q6_V_lo_W(hvx_vec_f16_to_f32(vd_f16));
|
||||
|
||||
HVX_Vector vq = *(const HVX_UVector *) src[i].qs;
|
||||
HVX_Vector v0 = Q6_V_vand_VV(vq, mask_03);
|
||||
HVX_Vector v1 = Q6_V_vand_VV(Q6_Vub_vlsr_VubR(vq, 2), mask_03);
|
||||
HVX_Vector v2 = Q6_V_vand_VV(Q6_Vub_vlsr_VubR(vq, 4), mask_03);
|
||||
HVX_Vector v3 = Q6_Vub_vlsr_VubR(vq, 6);
|
||||
|
||||
HVX_Vector v01 = Q6_V_lo_W(Q6_W_vshuff_VVR(v1, v0, -1));
|
||||
HVX_Vector v23 = Q6_V_lo_W(Q6_W_vshuff_VVR(v3, v2, -1));
|
||||
HVX_Vector v64 = Q6_Vb_vsub_VbVb(Q6_V_lo_W(Q6_W_vshuff_VVR(v23, v01, -2)), v_one);
|
||||
|
||||
HVX_VectorPair p16 = Q6_Wh_vunpack_Vb(v64);
|
||||
HVX_VectorPair p32 = Q6_Ww_vunpack_Vh(Q6_V_lo_W(p16));
|
||||
|
||||
float * block_dst = dst_ptr + i * QK2_0;
|
||||
hvx_vmem(block_dst + 0) = hvx_vec_mul_f32_f32(Q6_Vsf_equals_Vw(Q6_V_lo_W(p32)), vd);
|
||||
hvx_vmem(block_dst + 32) = hvx_vec_mul_f32_f32(Q6_Vsf_equals_Vw(Q6_V_hi_W(p32)), vd);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hvx_dequantize_row_flat_q4_0_f32(float * restrict dst_ptr, const void * restrict src_ptr, uint32_t n) {
|
||||
const int nb = n / QK4_0;
|
||||
const block_q4_0 * src = (const block_q4_0 *) src_ptr;
|
||||
@@ -603,4 +661,66 @@ static inline void hvx_dequantize_row_tiled_f16_f32(float * dst_spad, const uint
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hvx_dequantize_row_tiled_q1_0_f32(float * dst_spad, const uint8_t * src_spad, uint32_t n_k_tiles, uint32_t tile_stride, uint32_t row) {
|
||||
static const uint32_t __attribute__((aligned(128))) bit_mask[32] = {
|
||||
1u << 0, 1u << 8, 1u << 16, 1u << 24,
|
||||
1u << 1, 1u << 9, 1u << 17, 1u << 25,
|
||||
1u << 2, 1u << 10, 1u << 18, 1u << 26,
|
||||
1u << 3, 1u << 11, 1u << 19, 1u << 27,
|
||||
1u << 4, 1u << 12, 1u << 20, 1u << 28,
|
||||
1u << 5, 1u << 13, 1u << 21, 1u << 29,
|
||||
1u << 6, 1u << 14, 1u << 22, 1u << 30,
|
||||
1u << 7, 1u << 15, 1u << 23, 1u << 31,
|
||||
};
|
||||
const HVX_Vector v_bit_mask = *(const HVX_Vector *) bit_mask;
|
||||
const HVX_Vector v_zero = Q6_V_vzero();
|
||||
const HVX_Vector v_sign_bit = Q6_V_vsplat_R(0x80000000);
|
||||
|
||||
for (uint32_t k_tile = 0; k_tile < n_k_tiles; ++k_tile) {
|
||||
const uint8_t * tile = src_spad + k_tile * tile_stride;
|
||||
float * dst = dst_spad + k_tile * HTP_TENSOR_TILE_K_Q1_0;
|
||||
|
||||
const HVX_Vector scales = hvx_vmem(tile + 512);
|
||||
const HVX_Vector scale_hf = hvx_vec_repl_f16(Q6_V_vror_VR(scales, 2 * row));
|
||||
const HVX_Vector vd = Q6_V_lo_W(hvx_vec_f16_to_f32(scale_hf));
|
||||
const HVX_Vector neg_vd = Q6_V_vxor_VV(vd, v_sign_bit);
|
||||
|
||||
#pragma unroll
|
||||
for (int q = 0; q < 4; ++q) {
|
||||
const HVX_Vector v_bits = hvx_vec_repl_u32(Q6_V_vror_VR(hvx_vmem(tile + q * VLEN), 4 * row));
|
||||
const HVX_VectorPred q_zero = Q6_Q_vcmp_eq_VwVw(Q6_V_vand_VV(v_bits, v_bit_mask), v_zero);
|
||||
hvx_vmem(dst + q * 32) = Q6_V_vmux_QVV(q_zero, neg_vd, vd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hvx_dequantize_row_tiled_q2_0_f32(float * dst_spad, const uint8_t * src_spad, uint32_t n_k_tiles, uint32_t tile_stride, uint32_t row) {
|
||||
const HVX_Vector mask_03 = Q6_Vb_vsplat_R(0x03);
|
||||
const HVX_Vector v_one = Q6_Vb_vsplat_R(1);
|
||||
|
||||
for (uint32_t k_tile = 0; k_tile < n_k_tiles; ++k_tile) {
|
||||
const uint8_t * tile = src_spad + k_tile * tile_stride;
|
||||
float * dst = dst_spad + k_tile * HTP_TENSOR_TILE_K_Q2_0;
|
||||
|
||||
const HVX_Vector v_packed = hvx_dequantize_tiled_f16_gather4(tile, 4 * row);
|
||||
const HVX_Vector p0 = Q6_V_vand_VV(v_packed, mask_03);
|
||||
const HVX_Vector p1 = Q6_V_vand_VV(Q6_Vub_vlsr_VubR(v_packed, 2), mask_03);
|
||||
const HVX_Vector p2 = Q6_V_vand_VV(Q6_Vub_vlsr_VubR(v_packed, 4), mask_03);
|
||||
const HVX_Vector p3 = Q6_Vub_vlsr_VubR(v_packed, 6);
|
||||
|
||||
const HVX_Vector p01 = Q6_V_lo_W(Q6_W_vshuff_VVR(p1, p0, -4));
|
||||
const HVX_Vector p23 = Q6_V_lo_W(Q6_W_vshuff_VVR(p3, p2, -4));
|
||||
const HVX_Vector vq = Q6_Vb_vsub_VbVb(Q6_V_lo_W(Q6_W_vshuff_VVR(p23, p01, -8)), v_one);
|
||||
|
||||
const HVX_Vector scales = hvx_vmem(tile + 512);
|
||||
const HVX_Vector scale_hf = hvx_vec_repl_f16(Q6_V_vror_VR(scales, 2 * row));
|
||||
const HVX_Vector scale = Q6_V_lo_W(hvx_vec_f16_to_f32(scale_hf));
|
||||
|
||||
const HVX_VectorPair p16 = Q6_Wh_vunpack_Vb(vq);
|
||||
const HVX_VectorPair p32 = Q6_Ww_vunpack_Vh(Q6_V_lo_W(p16));
|
||||
hvx_vmem(dst + 0) = hvx_vec_mul_f32_f32(Q6_Vsf_equals_Vw(Q6_V_lo_W(p32)), scale);
|
||||
hvx_vmem(dst + 32) = hvx_vec_mul_f32_f32(Q6_Vsf_equals_Vw(Q6_V_hi_W(p32)), scale);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HVX_QUANT_H
|
||||
|
||||
@@ -940,6 +940,7 @@ static int execute_op(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_RELU:
|
||||
case HTP_OP_UNARY_STEP:
|
||||
case HTP_OP_L2_NORM:
|
||||
case HTP_OP_HADAMARD:
|
||||
return op_unary(octx);
|
||||
|
||||
case HTP_OP_GLU_SWIGLU:
|
||||
|
||||
@@ -277,10 +277,9 @@ static void hvx_mm_2d_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
const uint32_t tile_size = htp_tensor_get_tile_size(src0->type);
|
||||
const uint32_t aligned_tile_size = htp_tensor_get_tile_padded_size(src0->type);
|
||||
|
||||
uint32_t n_k_tiles_w = ne00 / 32;
|
||||
uint32_t n_k_tiles_a = ne10 / 32;
|
||||
uint32_t n_k_tiles_w = ne00 / htp_tensor_get_tile_k(src0->type);
|
||||
uint32_t tile_row_stride = n_k_tiles_w * tile_size;
|
||||
uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;
|
||||
uint32_t tile_row_transfer_size_aligned = n_k_tiles_w * aligned_tile_size;
|
||||
|
||||
uint32_t ct_start = src0_start_row / 32;
|
||||
uint32_t ct_end = (src0_end_row + 31) / 32;
|
||||
@@ -289,7 +288,7 @@ static void hvx_mm_2d_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
if (src0_start_row < src0_end_row) {
|
||||
for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {
|
||||
dma_queue_push(dma_q, dma_make_data(vtcm_src0_ptr + d * tile_row_transfer_size_aligned,
|
||||
src0_row + push_ct * tile_row_stride), aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
src0_row + push_ct * tile_row_stride), aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +340,7 @@ static void hvx_mm_2d_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
|
||||
if (push_ct < ct_end) {
|
||||
dma_queue_push(dma_q, dma_make_data(w_tile, src0_row + push_ct * tile_row_stride),
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
push_ct++;
|
||||
}
|
||||
}
|
||||
@@ -379,10 +378,9 @@ static void hvx_mv_2d_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
const uint32_t tile_size = htp_tensor_get_tile_size(src0->type);
|
||||
const uint32_t aligned_tile_size = htp_tensor_get_tile_padded_size(src0->type);
|
||||
|
||||
uint32_t n_k_tiles_w = ne00 / 32;
|
||||
uint32_t n_k_tiles_a = ne10 / 32;
|
||||
uint32_t n_k_tiles_w = ne00 / htp_tensor_get_tile_k(src0->type);
|
||||
uint32_t tile_row_stride = n_k_tiles_w * tile_size;
|
||||
uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;
|
||||
uint32_t tile_row_transfer_size_aligned = n_k_tiles_w * aligned_tile_size;
|
||||
|
||||
uint32_t ct_start = src0_start_row / 32;
|
||||
uint32_t ct_end = (src0_end_row + 31) / 32;
|
||||
@@ -401,7 +399,7 @@ static void hvx_mv_2d_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
}
|
||||
for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {
|
||||
dma_queue_push(dma_q, dma_make_data(vtcm_src0_ptr + d * tile_row_transfer_size_aligned,
|
||||
src0_row + push_ct * tile_row_stride), aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
src0_row + push_ct * tile_row_stride), aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +420,7 @@ static void hvx_mv_2d_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
|
||||
if (push_ct < ct_end) {
|
||||
dma_queue_push(dma_q, dma_make_data(w_tile, src0_row + push_ct * tile_row_stride),
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
push_ct++;
|
||||
}
|
||||
}
|
||||
@@ -460,8 +458,6 @@ static void hvx_mm_nx_2d_tiled(unsigned int nth, unsigned int ith, void * data)
|
||||
const uint32_t n_prefetch = kparams->n_prefetch;
|
||||
assert(n_prefetch >= 2 && n_prefetch <= HTP_MM_MAX_PREFETCH && (n_prefetch & (n_prefetch - 1)) == 0);
|
||||
|
||||
uint32_t n_k_tiles_a = ne10 / 32;
|
||||
|
||||
for (uint32_t widx = 0; widx < n_weights; widx++) {
|
||||
const struct htp_tensor * restrict src_w = octx->src[widx];
|
||||
const struct htp_tensor * restrict dst = octx->dsts[widx];
|
||||
@@ -470,15 +466,14 @@ static void hvx_mm_nx_2d_tiled(unsigned int nth, unsigned int ith, void * data)
|
||||
|
||||
const uint32_t tile_size = htp_tensor_get_tile_size(src_w->type);
|
||||
const uint32_t aligned_tile_size = htp_tensor_get_tile_padded_size(src_w->type);
|
||||
const uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;
|
||||
|
||||
const uint32_t ne00 = src_w->ne[0];
|
||||
const uint32_t ne01 = src_w->ne[1];
|
||||
const size_t dst_row_size = dst->nb[1];
|
||||
const dma_addr_t src_w_row = src_w->data;
|
||||
|
||||
uint32_t n_k_tiles_w = ne00 / 32;
|
||||
uint32_t tile_row_stride = n_k_tiles_w * tile_size;
|
||||
const uint32_t n_k_tiles_w = ne00 / htp_tensor_get_tile_k(src_w->type);
|
||||
const uint32_t tile_row_stride = n_k_tiles_w * tile_size;
|
||||
const uint32_t tile_row_transfer_size_aligned = n_k_tiles_w * aligned_tile_size;
|
||||
|
||||
uint32_t src0_start_row = 0;
|
||||
uint32_t src0_end_row = ne01;
|
||||
@@ -504,7 +499,7 @@ static void hvx_mm_nx_2d_tiled(unsigned int nth, unsigned int ith, void * data)
|
||||
uint32_t push_ct = ct_start;
|
||||
for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {
|
||||
dma_queue_push(dma_q, dma_make_data(vtcm_weight_ptr + d * tile_row_transfer_size_aligned,
|
||||
src_w_row + push_ct * tile_row_stride), aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
src_w_row + push_ct * tile_row_stride), aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
}
|
||||
|
||||
for (uint32_t ct = ct_start; ct < ct_end; ct++) {
|
||||
@@ -536,7 +531,7 @@ static void hvx_mm_nx_2d_tiled(unsigned int nth, unsigned int ith, void * data)
|
||||
|
||||
if (push_ct < ct_end) {
|
||||
dma_queue_push(dma_q, dma_make_data(w_tile, src_w_row + push_ct * tile_row_stride),
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
push_ct++;
|
||||
}
|
||||
}
|
||||
@@ -840,10 +835,9 @@ static void hvx_mm_4d_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
const uint32_t tile_size = htp_tensor_get_tile_size(src0->type);
|
||||
const uint32_t aligned_tile_size = htp_tensor_get_tile_padded_size(src0->type);
|
||||
|
||||
const uint32_t n_k_tiles_w = ne00 / 32;
|
||||
const uint32_t n_k_tiles_a = ne10 / 32;
|
||||
const uint32_t n_k_tiles_w = ne00 / htp_tensor_get_tile_k(src0->type);
|
||||
const uint32_t tile_row_stride = n_k_tiles_w * tile_size;
|
||||
const uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;
|
||||
const uint32_t tile_row_transfer_size_aligned = n_k_tiles_w * aligned_tile_size;
|
||||
const uint32_t src0_slice_stride = ((ne01 + 31) / 32) * tile_row_stride;
|
||||
|
||||
const uint32_t ct_start = src0_start_row / 32;
|
||||
@@ -884,7 +878,7 @@ static void hvx_mm_4d_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {
|
||||
dma_queue_push(dma_q, dma_make_data(vtcm_src0_ptr + d * tile_row_transfer_size_aligned,
|
||||
src0_slice + (size_t) push_ct * tile_row_stride),
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
}
|
||||
|
||||
for (uint32_t ct = ct_start; ct < ct_end; ct++) {
|
||||
@@ -929,7 +923,7 @@ static void hvx_mm_4d_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
|
||||
if (push_ct < ct_end) {
|
||||
dma_queue_push(dma_q, dma_make_data(w_tile, src0_slice + (size_t) push_ct * tile_row_stride),
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
push_ct++;
|
||||
}
|
||||
}
|
||||
@@ -1343,10 +1337,9 @@ static void hvx_mm_id_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
|
||||
const uint32_t tile_size = htp_tensor_get_tile_size(src0->type);
|
||||
const uint32_t aligned_tile_size = htp_tensor_get_tile_padded_size(src0->type);
|
||||
const uint32_t n_k_tiles_w = ne00 / 32;
|
||||
const uint32_t n_k_tiles_a = ne10 / 32;
|
||||
const uint32_t n_k_tiles_w = ne00 / htp_tensor_get_tile_k(src0->type);
|
||||
const uint32_t tile_row_stride = n_k_tiles_w * tile_size;
|
||||
const uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;
|
||||
const uint32_t tile_row_transfer_size_aligned = n_k_tiles_w * aligned_tile_size;
|
||||
|
||||
const uint32_t ct_start = src0_start_row / 32;
|
||||
const uint32_t ct_end = (src0_end_row + 31) / 32;
|
||||
@@ -1354,7 +1347,7 @@ static void hvx_mm_id_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
uint32_t push_ct = ct_start;
|
||||
for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {
|
||||
dma_queue_push(dma_q, dma_make_data(vtcm_src0_ptr + d * tile_row_transfer_size_aligned, src0_row + push_ct * tile_row_stride),
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
}
|
||||
|
||||
for (uint32_t ct = ct_start; ct < ct_end; ct++) {
|
||||
@@ -1379,7 +1372,7 @@ static void hvx_mm_id_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
|
||||
if (push_ct < ct_end) {
|
||||
dma_queue_push(dma_q, dma_make_data(w_tile, src0_row + push_ct * tile_row_stride),
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
push_ct++;
|
||||
}
|
||||
}
|
||||
@@ -1431,10 +1424,9 @@ static void hvx_mv_id_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
|
||||
const uint32_t tile_size = htp_tensor_get_tile_size(src0->type);
|
||||
const uint32_t aligned_tile_size = htp_tensor_get_tile_padded_size(src0->type);
|
||||
const uint32_t n_k_tiles_w = ne00 / 32;
|
||||
const uint32_t n_k_tiles_a = ne10 / 32;
|
||||
const uint32_t n_k_tiles_w = ne00 / htp_tensor_get_tile_k(src0->type);
|
||||
const uint32_t tile_row_stride = n_k_tiles_w * tile_size;
|
||||
const uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;
|
||||
const uint32_t tile_row_transfer_size_aligned = n_k_tiles_w * aligned_tile_size;
|
||||
|
||||
const uint32_t ct_start = src0_start_row / 32;
|
||||
const uint32_t ct_end = (src0_end_row + 31) / 32;
|
||||
@@ -1442,7 +1434,7 @@ static void hvx_mv_id_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
uint32_t push_ct = ct_start;
|
||||
for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {
|
||||
dma_queue_push(dma_q, dma_make_data(vtcm_src0_ptr + d * tile_row_transfer_size_aligned, src0_row + push_ct * tile_row_stride),
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
}
|
||||
|
||||
for (uint32_t ct = ct_start; ct < ct_end; ct++) {
|
||||
@@ -1457,7 +1449,7 @@ static void hvx_mv_id_tiled(unsigned int nth, unsigned int ith, void * data) {
|
||||
|
||||
if (push_ct < ct_end) {
|
||||
dma_queue_push(dma_q, dma_make_data(w_tile, src0_row + push_ct * tile_row_stride),
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
push_ct++;
|
||||
}
|
||||
}
|
||||
@@ -1520,10 +1512,9 @@ static void hvx_mv_id_nx_tiled(unsigned int nth, unsigned int ith, void * data)
|
||||
|
||||
const uint32_t tile_size = htp_tensor_get_tile_size(src_w->type);
|
||||
const uint32_t aligned_tile_size = htp_tensor_get_tile_padded_size(src_w->type);
|
||||
const uint32_t n_k_tiles_w = src_w->ne[0] / 32;
|
||||
const uint32_t n_k_tiles_a = act->ne[0] / 32;
|
||||
const uint32_t n_k_tiles_w = src_w->ne[0] / htp_tensor_get_tile_k(src_w->type);
|
||||
const uint32_t tile_row_stride = n_k_tiles_w * tile_size;
|
||||
const uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;
|
||||
const uint32_t tile_row_transfer_size_aligned = n_k_tiles_w * aligned_tile_size;
|
||||
|
||||
const uint32_t ct_start = src0_start_row / 32;
|
||||
const uint32_t ct_end = (src0_end_row + 31) / 32;
|
||||
@@ -1531,7 +1522,7 @@ static void hvx_mv_id_nx_tiled(unsigned int nth, unsigned int ith, void * data)
|
||||
uint32_t push_ct = ct_start;
|
||||
for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {
|
||||
dma_queue_push(dma_q, dma_make_data(vtcm_src0_ptr + d * tile_row_transfer_size_aligned, src0_row + push_ct * tile_row_stride),
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
}
|
||||
|
||||
for (uint32_t ct = ct_start; ct < ct_end; ct++) {
|
||||
@@ -1546,7 +1537,7 @@ static void hvx_mv_id_nx_tiled(unsigned int nth, unsigned int ith, void * data)
|
||||
|
||||
if (push_ct < ct_end) {
|
||||
dma_queue_push(dma_q, dma_make_data(w_tile, src0_row + push_ct * tile_row_stride),
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
push_ct++;
|
||||
}
|
||||
}
|
||||
@@ -1611,10 +1602,9 @@ static void hvx_mm_id_nx_tiled(unsigned int nth, unsigned int ith, void * data)
|
||||
|
||||
const uint32_t tile_size = htp_tensor_get_tile_size(src_w->type);
|
||||
const uint32_t aligned_tile_size = htp_tensor_get_tile_padded_size(src_w->type);
|
||||
const uint32_t n_k_tiles_w = src_w->ne[0] / 32;
|
||||
const uint32_t n_k_tiles_a = act->ne[0] / 32;
|
||||
const uint32_t n_k_tiles_w = src_w->ne[0] / htp_tensor_get_tile_k(src_w->type);
|
||||
const uint32_t tile_row_stride = n_k_tiles_w * tile_size;
|
||||
const uint32_t tile_row_transfer_size_aligned = n_k_tiles_a * aligned_tile_size;
|
||||
const uint32_t tile_row_transfer_size_aligned = n_k_tiles_w * aligned_tile_size;
|
||||
|
||||
const uint32_t ct_start = src0_start_row / 32;
|
||||
const uint32_t ct_end = (src0_end_row + 31) / 32;
|
||||
@@ -1622,7 +1612,7 @@ static void hvx_mm_id_nx_tiled(unsigned int nth, unsigned int ith, void * data)
|
||||
uint32_t push_ct = ct_start;
|
||||
for (uint32_t d = 0; d < n_prefetch && push_ct < ct_end; d++, push_ct++) {
|
||||
dma_queue_push(dma_q, dma_make_data(vtcm_src0_ptr + d * tile_row_transfer_size_aligned, src0_row + push_ct * tile_row_stride),
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
}
|
||||
|
||||
for (uint32_t ct = ct_start; ct < ct_end; ct++) {
|
||||
@@ -1647,7 +1637,7 @@ static void hvx_mm_id_nx_tiled(unsigned int nth, unsigned int ith, void * data)
|
||||
|
||||
if (push_ct < ct_end) {
|
||||
dma_queue_push(dma_q, dma_make_data(w_tile, src0_row + push_ct * tile_row_stride),
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_a);
|
||||
aligned_tile_size, tile_size, tile_size, n_k_tiles_w);
|
||||
push_ct++;
|
||||
}
|
||||
}
|
||||
@@ -1693,6 +1683,11 @@ static int hvx_mm_init_tiled_vec_dot(struct htp_mm_context * mmctx, enum htp_dat
|
||||
mmctx->vec_dot_32x1 = tiled_vec_dot_q2_k_32x1;
|
||||
mmctx->vec_dot_32x2 = tiled_vec_dot_q2_k_32x2;
|
||||
return 0;
|
||||
case HTP_TYPE_Q2_0:
|
||||
mmctx->type = "q2_0_tiled-f32";
|
||||
mmctx->vec_dot_32x1 = tiled_vec_dot_q2_0_32x1;
|
||||
mmctx->vec_dot_32x2 = tiled_vec_dot_q2_0_32x2;
|
||||
return 0;
|
||||
case HTP_TYPE_IQ4_NL:
|
||||
mmctx->type = "iq4nl_tiled-f32";
|
||||
mmctx->vec_dot_32x1 = tiled_vec_dot_iq4nl_32x1;
|
||||
@@ -1703,6 +1698,11 @@ static int hvx_mm_init_tiled_vec_dot(struct htp_mm_context * mmctx, enum htp_dat
|
||||
mmctx->vec_dot_32x1 = tiled_vec_dot_mxfp4_32x1;
|
||||
mmctx->vec_dot_32x2 = tiled_vec_dot_mxfp4_32x2;
|
||||
return 0;
|
||||
case HTP_TYPE_Q1_0:
|
||||
mmctx->type = "q1_0_tiled-f32";
|
||||
mmctx->vec_dot_32x1 = tiled_vec_dot_q1_0_32x1;
|
||||
mmctx->vec_dot_32x2 = tiled_vec_dot_q1_0_32x2;
|
||||
return 0;
|
||||
case HTP_TYPE_F16:
|
||||
mmctx->type = "f16_tiled-f16";
|
||||
mmctx->vec_dot_32x1 = tiled_vec_dot_f16_32x1;
|
||||
@@ -2325,6 +2325,8 @@ static inline tiled_dequant_task_fn_t hmx_get_dequant_worker_fn(int weight_type,
|
||||
case HTP_TYPE_Q6_K: return dequantize_tiled_weight_to_fp16_task_q6_k;
|
||||
case HTP_TYPE_Q3_K: return dequantize_tiled_weight_to_fp16_task_q3_k;
|
||||
case HTP_TYPE_Q2_K: return dequantize_tiled_weight_to_fp16_task_q2_k;
|
||||
case HTP_TYPE_Q1_0: return dequantize_tiled_weight_to_fp16_task_q1_0;
|
||||
case HTP_TYPE_Q2_0: return dequantize_tiled_weight_to_fp16_task_q2_0;
|
||||
case HTP_TYPE_F16: return is_tiled ? NULL : convert_f16_weight_to_fp16_tiles_task;
|
||||
case HTP_TYPE_BF16: return convert_bf16_weight_to_fp16_tiles_task;
|
||||
case HTP_TYPE_F32: return quantize_f32_weight_to_fp16_tiles_task;
|
||||
@@ -2346,8 +2348,9 @@ static void dequantize_tiled_weight_chunk_to_fp16_tiles(
|
||||
|
||||
assert(k_block % HTP_MM_HMX_TILE_N_COLS == 0);
|
||||
|
||||
size_t n_col_tiles = hmx_ceil_div(n_cols, HTP_MM_HMX_TILE_N_COLS);
|
||||
size_t n_tot_tiles = n_col_tiles * n_k_tiles;
|
||||
const size_t n_col_tiles = hmx_ceil_div(n_cols, HTP_MM_HMX_TILE_N_COLS);
|
||||
const uint32_t n_weight_tiles = k_block / htp_tensor_get_tile_k(weight_type);
|
||||
const size_t n_tot_tiles = n_col_tiles * n_weight_tiles;
|
||||
|
||||
size_t n_tiles_per_task = (n_threads == 1) ? n_tot_tiles : hmx_ceil_div(n_tot_tiles, n_threads);
|
||||
|
||||
@@ -2363,6 +2366,7 @@ static void dequantize_tiled_weight_chunk_to_fp16_tiles(
|
||||
state.row_stride = row_stride;
|
||||
state.weight_type = weight_type;
|
||||
state.n_k_tiles = n_k_tiles;
|
||||
state.n_weight_tiles = n_weight_tiles;
|
||||
state.n_k_tiles_div = n_k_tiles_div;
|
||||
state.traces = ctx->trace;
|
||||
state.ctx = ctx;
|
||||
@@ -2666,6 +2670,7 @@ static int hmx_mm_2d_f32(struct htp_context *ctx,
|
||||
tiled_dequant_task_fn_t dequant_worker_fn = hmx_get_dequant_worker_fn(weight_type, is_tiled);
|
||||
|
||||
const int n_k_tiles = k / HTP_MM_HMX_TILE_N_COLS;
|
||||
const int n_weight_tiles = k / htp_tensor_get_tile_k(weight_type);
|
||||
const struct fastdiv_values n_k_tiles_div = init_fastdiv_values(n_k_tiles);
|
||||
|
||||
const size_t vec_dot_size = k * sizeof(__fp16);
|
||||
@@ -2679,8 +2684,6 @@ static int hmx_mm_2d_f32(struct htp_context *ctx,
|
||||
size_t n_chunk_n_cols = n_chunk;
|
||||
size_t vtcm_used = vtcm_size;
|
||||
|
||||
const size_t qweight_row_stride = is_tiled ? (size_t)(n_k_tiles * aligned_tile_size) / 32 : 0;
|
||||
|
||||
struct htp_mm_hmx_vtcm_layout L;
|
||||
htp_mm_hmx_vtcm_layout_build(&L, HTP_MM_KERNEL_HMX_2D, weight_type, k, m_chunk_n_rows, n_chunk_n_cols, 1, pipeline, act_threads, aligned_tile_size, src2_bytes);
|
||||
|
||||
@@ -2759,13 +2762,13 @@ static int hmx_mm_2d_f32(struct htp_context *ctx,
|
||||
|
||||
// Prologue: push A0 and optionally A1 (if n_chunk_cnt > 1)
|
||||
const size_t n_cols_A0 = hex_smin(n - 0 * n_chunk_n_cols, n_chunk_n_cols);
|
||||
const uint32_t height_A0 = is_tiled ? hmx_ceil_div(n_cols_A0, 32) * n_k_tiles : n_cols_A0;
|
||||
const uint32_t height_A0 = is_tiled ? hmx_ceil_div(n_cols_A0, 32) * n_weight_tiles : n_cols_A0;
|
||||
dma_queue_push(weight_dma, dma_make_data(vtcm_raw_bufs[0], weight),
|
||||
dma_dst_stride, dma_src_stride, dma_width_bytes, height_A0);
|
||||
|
||||
if (1 < n_chunk_cnt) {
|
||||
const size_t n_cols_A1 = hex_smin(n - 1 * n_chunk_n_cols, n_chunk_n_cols);
|
||||
const uint32_t height_A1 = is_tiled ? hmx_ceil_div(n_cols_A1, 32) * n_k_tiles : n_cols_A1;
|
||||
const uint32_t height_A1 = is_tiled ? hmx_ceil_div(n_cols_A1, 32) * n_weight_tiles : n_cols_A1;
|
||||
dma_queue_push(weight_dma, dma_make_data(vtcm_raw_bufs[1], weight + n_chunk_n_cols * weight_stride),
|
||||
dma_dst_stride, dma_src_stride, dma_width_bytes, height_A1);
|
||||
}
|
||||
@@ -2789,7 +2792,7 @@ static int hmx_mm_2d_f32(struct htp_context *ctx,
|
||||
|
||||
// 3. push A_{i+2} (if i+2 < n_chunk_cnt)
|
||||
if (i + 2 < n_chunk_cnt) {
|
||||
const uint32_t height_p2 = is_tiled ? hmx_ceil_div(n_cols_p2, 32) * n_k_tiles : n_cols_p2;
|
||||
const uint32_t height_p2 = is_tiled ? hmx_ceil_div(n_cols_p2, 32) * n_weight_tiles : n_cols_p2;
|
||||
dma_queue_push(weight_dma, dma_make_data(vtcm_raw_bufs[(i + 2) % 4], weight + nc_p2 * weight_stride),
|
||||
dma_dst_stride, dma_src_stride, dma_width_bytes, height_p2);
|
||||
}
|
||||
@@ -2855,7 +2858,7 @@ static int hmx_mm_2d_f32(struct htp_context *ctx,
|
||||
// A0: Pre-fetch the first weight chunk (nc = 0)
|
||||
if (n > 0) {
|
||||
const size_t n_cols = hex_smin(n, n_chunk_n_cols);
|
||||
const uint32_t height = is_tiled ? hmx_ceil_div(n_cols, 32) * n_k_tiles : n_cols;
|
||||
const uint32_t height = is_tiled ? hmx_ceil_div(n_cols, 32) * n_weight_tiles : n_cols;
|
||||
dma_queue_push(weight_dma, dma_make_data(vtcm_raw_bufs[0], weight), dma_dst_stride, dma_src_stride, dma_width_bytes, height);
|
||||
}
|
||||
|
||||
@@ -2878,7 +2881,7 @@ static int hmx_mm_2d_f32(struct htp_context *ctx,
|
||||
const size_t nc_next = nc + n_chunk_n_cols;
|
||||
if (nc_next < n) {
|
||||
const size_t n_cols_next = hex_smin(n - nc_next, n_chunk_n_cols);
|
||||
const uint32_t height_next = is_tiled ? hmx_ceil_div(n_cols_next, 32) * n_k_tiles : n_cols_next;
|
||||
const uint32_t height_next = is_tiled ? hmx_ceil_div(n_cols_next, 32) * n_weight_tiles : n_cols_next;
|
||||
dma_queue_push(weight_dma, dma_make_data(vtcm_raw_bufs[(i + 1) % 2], weight + nc_next * weight_stride), dma_dst_stride, dma_src_stride, dma_width_bytes, height_next);
|
||||
}
|
||||
|
||||
@@ -2930,6 +2933,7 @@ static int hmx_mm_nx_2d_f32(struct htp_ops_context * octx, const struct htp_mm_k
|
||||
}
|
||||
|
||||
const int n_k_tiles = k / HTP_MM_HMX_TILE_N_COLS;
|
||||
const int n_weight_tiles = k / htp_tensor_get_tile_k(weight_type);
|
||||
const struct fastdiv_values n_k_tiles_div = init_fastdiv_values(n_k_tiles);
|
||||
|
||||
const int tile_size = kparams->tile_size;
|
||||
@@ -3057,13 +3061,13 @@ static int hmx_mm_nx_2d_f32(struct htp_ops_context * octx, const struct htp_mm_k
|
||||
const uint32_t dma_src_stride = is_tiled ? tile_size : weight_stride;
|
||||
|
||||
const size_t n_cols_A0 = hex_smin(n - 0 * n_chunk_n_cols, n_chunk_n_cols);
|
||||
const uint32_t height_A0 = is_tiled ? hmx_ceil_div(n_cols_A0, 32) * n_k_tiles : n_cols_A0;
|
||||
const uint32_t height_A0 = is_tiled ? hmx_ceil_div(n_cols_A0, 32) * n_weight_tiles : n_cols_A0;
|
||||
dma_queue_push(weight_dma, dma_make_data(vtcm_raw_bufs[0], weight),
|
||||
dma_dst_stride, dma_src_stride, dma_width_bytes, height_A0);
|
||||
|
||||
if (1 < n_chunk_cnt) {
|
||||
const size_t n_cols_A1 = hex_smin(n - 1 * n_chunk_n_cols, n_chunk_n_cols);
|
||||
const uint32_t height_A1 = is_tiled ? hmx_ceil_div(n_cols_A1, 32) * n_k_tiles : n_cols_A1;
|
||||
const uint32_t height_A1 = is_tiled ? hmx_ceil_div(n_cols_A1, 32) * n_weight_tiles : n_cols_A1;
|
||||
dma_queue_push(weight_dma, dma_make_data(vtcm_raw_bufs[1], weight + n_chunk_n_cols * weight_stride),
|
||||
dma_dst_stride, dma_src_stride, dma_width_bytes, height_A1);
|
||||
}
|
||||
@@ -3083,7 +3087,7 @@ static int hmx_mm_nx_2d_f32(struct htp_ops_context * octx, const struct htp_mm_k
|
||||
n_k_tiles, n_k_tiles_div, dequant_worker_fn, n_threads);
|
||||
|
||||
if (i + 2 < n_chunk_cnt) {
|
||||
const uint32_t height_p2 = is_tiled ? hmx_ceil_div(n_cols_p2, 32) * n_k_tiles : n_cols_p2;
|
||||
const uint32_t height_p2 = is_tiled ? hmx_ceil_div(n_cols_p2, 32) * n_weight_tiles : n_cols_p2;
|
||||
dma_queue_push(weight_dma, dma_make_data(vtcm_raw_bufs[(i + 2) % 4], weight + nc_p2 * weight_stride),
|
||||
dma_dst_stride, dma_src_stride, dma_width_bytes, height_p2);
|
||||
}
|
||||
@@ -3160,7 +3164,7 @@ static int hmx_mm_nx_2d_f32(struct htp_ops_context * octx, const struct htp_mm_k
|
||||
|
||||
if (n > 0) {
|
||||
const size_t n_cols = hex_smin(n, n_chunk_n_cols);
|
||||
const uint32_t height = is_tiled ? hmx_ceil_div(n_cols, 32) * n_k_tiles : n_cols;
|
||||
const uint32_t height = is_tiled ? hmx_ceil_div(n_cols, 32) * n_weight_tiles : n_cols;
|
||||
dma_queue_push(weight_dma, dma_make_data(vtcm_raw_bufs[0], weight), dma_dst_stride, dma_src_stride, dma_width_bytes, height);
|
||||
}
|
||||
|
||||
@@ -3180,7 +3184,7 @@ static int hmx_mm_nx_2d_f32(struct htp_ops_context * octx, const struct htp_mm_k
|
||||
const size_t nc_next = nc + n_chunk_n_cols;
|
||||
if (nc_next < n) {
|
||||
const size_t n_cols_next = hex_smin(n - nc_next, n_chunk_n_cols);
|
||||
const uint32_t height_next = is_tiled ? hmx_ceil_div(n_cols_next, 32) * n_k_tiles : n_cols_next;
|
||||
const uint32_t height_next = is_tiled ? hmx_ceil_div(n_cols_next, 32) * n_weight_tiles : n_cols_next;
|
||||
dma_queue_push(weight_dma, dma_make_data(vtcm_raw_bufs[(i + 1) % 2], weight + nc_next * weight_stride), dma_dst_stride, dma_src_stride, dma_width_bytes, height_next);
|
||||
}
|
||||
|
||||
@@ -3544,6 +3548,7 @@ static int hmx_mm_id_2d_f32(struct htp_context *ctx,
|
||||
}
|
||||
|
||||
const int n_k_tiles = k / HTP_MM_HMX_TILE_N_COLS;
|
||||
const uint32_t n_weight_tiles = k / htp_tensor_get_tile_k(weight_type);
|
||||
const struct fastdiv_values n_k_tiles_div = init_fastdiv_values(n_k_tiles);
|
||||
|
||||
const size_t vec_dot_size = k * sizeof(__fp16);
|
||||
@@ -3557,7 +3562,7 @@ static int hmx_mm_id_2d_f32(struct htp_context *ctx,
|
||||
const uint32_t dma_src_stride = is_tiled ? tile_size : weight_stride;
|
||||
const uint32_t dma_width_bytes = is_tiled ? tile_size : row_stride;
|
||||
|
||||
const size_t qweight_row_stride = is_tiled ? (size_t)(n_k_tiles * aligned_tile_size) / 32 : 0;
|
||||
const size_t qweight_row_stride = is_tiled ? (size_t)(n_weight_tiles * aligned_tile_size) / 32 : 0;
|
||||
const size_t weight_row_stride = is_tiled ? qweight_row_stride : row_stride;
|
||||
|
||||
size_t size_per_n = 0, size_per_m = 0, size_per_mn = 0;
|
||||
@@ -3613,7 +3618,7 @@ static int hmx_mm_id_2d_f32(struct htp_context *ctx,
|
||||
// A0: Pre-fetch the first weight chunk (nc = 0)
|
||||
if (n > 0) {
|
||||
const size_t n_cols = hex_smin((size_t) n, n_chunk_n_cols);
|
||||
const uint32_t height = is_tiled ? hmx_ceil_div(n_cols, 32) * n_k_tiles : n_cols;
|
||||
const uint32_t height = is_tiled ? hmx_ceil_div(n_cols, 32) * n_weight_tiles : n_cols;
|
||||
dma_queue_push(weight_dma, dma_make_data(vtcm_raw_bufs[0], weight),
|
||||
dma_dst_stride, dma_src_stride, dma_width_bytes, height);
|
||||
}
|
||||
@@ -3637,7 +3642,7 @@ static int hmx_mm_id_2d_f32(struct htp_context *ctx,
|
||||
const size_t nc_next = nc + n_chunk_n_cols;
|
||||
if (nc_next < (size_t) n) {
|
||||
const size_t n_cols_next = hex_smin((size_t) n - nc_next, n_chunk_n_cols);
|
||||
const uint32_t height_next = is_tiled ? hmx_ceil_div(n_cols_next, 32) * n_k_tiles : n_cols_next;
|
||||
const uint32_t height_next = is_tiled ? hmx_ceil_div(n_cols_next, 32) * n_weight_tiles : n_cols_next;
|
||||
dma_queue_push(weight_dma, dma_make_data(vtcm_raw_bufs[(i + 1) % 2], weight + nc_next * weight_stride),
|
||||
dma_dst_stride, dma_src_stride, dma_width_bytes, height_next);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ extern "C" {
|
||||
#define HTP_MM_HMX_TILE_N_ELMS 1024
|
||||
#define HTP_MM_HMX_MIN_NROWS 4
|
||||
|
||||
#define HTP_MM_WEIGHT_TILE_K_Q2_0 64
|
||||
#define HTP_MM_WEIGHT_TILE_SIZE_Q2_0 HTP_TENSOR_TILE_SIZE_Q2_0
|
||||
#define HTP_MM_WEIGHT_ALIGNED_TILE_SIZE_Q2_0 640
|
||||
|
||||
// --- Activation Tiled Block Sizes (including padding) ---
|
||||
#define HTP_MM_ACT_TILE_SIZE_Q8_0 1152
|
||||
#define HTP_MM_ACT_TILE_SIZE_Q8_1 1280
|
||||
@@ -183,7 +187,7 @@ next_nc:
|
||||
// (Q2_K: per-16 k sums, q8_1_s16)
|
||||
static inline bool htp_mm_weight_has_offset(int weight_type) {
|
||||
return weight_type == HTP_TYPE_Q4_1 || weight_type == HTP_TYPE_Q4_K || weight_type == HTP_TYPE_Q5_K ||
|
||||
weight_type == HTP_TYPE_Q2_K;
|
||||
weight_type == HTP_TYPE_Q2_K || weight_type == HTP_TYPE_Q2_0;
|
||||
}
|
||||
|
||||
// --- Activation/Row Size Helpers ---
|
||||
@@ -227,8 +231,8 @@ static inline void htp_mm_hmx_get_2d_chunk_costs(
|
||||
const bool is_quant = (wtype != HTP_TYPE_F16 && wtype != HTP_TYPE_F32 && wtype != HTP_TYPE_BF16);
|
||||
const size_t row_stride = htp_tensor_get_row_size(wtype, k);
|
||||
const size_t vec_dot_size = k * sizeof(uint16_t);
|
||||
const uint32_t n_k_tiles = k / HTP_MM_HMX_TILE_N_COLS;
|
||||
const size_t qweight_row_stride = is_quant ? (size_t)(n_k_tiles * aligned_tile_size) / 32 : 0;
|
||||
const uint32_t n_weight_tiles = k / htp_tensor_get_tile_k(wtype);
|
||||
const size_t qweight_row_stride = is_quant ? (size_t)(n_weight_tiles * aligned_tile_size) / 32 : 0;
|
||||
|
||||
*size_per_n_out = (pipeline ? 2 : 1) * (is_quant ? qweight_row_stride : row_stride) +
|
||||
(pipeline ? 2 * vec_dot_size : vec_dot_size);
|
||||
@@ -359,11 +363,11 @@ static inline void htp_mm_hmx_vtcm_layout_build(
|
||||
const bool is_quant = (wtype != HTP_TYPE_F16 && wtype != HTP_TYPE_F32 && wtype != HTP_TYPE_BF16);
|
||||
const size_t row_stride = htp_tensor_get_row_size(wtype, k);
|
||||
const size_t vec_dot_size = k * sizeof(uint16_t);
|
||||
const uint32_t n_k_tiles = k / HTP_MM_HMX_TILE_N_COLS;
|
||||
const uint32_t n_weight_tiles = k / htp_tensor_get_tile_k(wtype);
|
||||
|
||||
const size_t min_f32_size = hex_align_up(act_threads * HTP_MM_DMA_ACT_MULTIPLIER * k * sizeof(float), 128);
|
||||
const size_t weight_area_size = is_quant
|
||||
? hex_align_up((nc / 32) * n_k_tiles * aligned_tile_size, HTP_MM_HMX_TILE_SIZE)
|
||||
? hex_align_up((nc / 32) * n_weight_tiles * aligned_tile_size, HTP_MM_HMX_TILE_SIZE)
|
||||
: hex_align_up(nc * row_stride, HTP_MM_HMX_TILE_SIZE);
|
||||
const size_t act_area_size = hex_align_up(mc * vec_dot_size, HTP_MM_HMX_TILE_SIZE);
|
||||
const size_t output_area_size = hex_align_up(mc * nc * sizeof(__fp16), HTP_MM_HMX_TILE_SIZE);
|
||||
@@ -435,7 +439,7 @@ static inline void htp_mm_hvx_vtcm_layout_build(
|
||||
|
||||
if (is_fused_nx) {
|
||||
uint32_t aligned_tile_size = htp_tensor_get_tile_padded_size(wtype);
|
||||
uint32_t n_k_tiles = hex_round_up(ne10, 32) / 32;
|
||||
uint32_t n_k_tiles = (ne10 + htp_tensor_get_tile_k(wtype) - 1) / htp_tensor_get_tile_k(wtype);
|
||||
uint32_t tile_row_size = n_k_tiles * aligned_tile_size;
|
||||
size_t weight_sz_per_thread = hex_round_up(n_prefetch * tile_row_size, 128);
|
||||
|
||||
@@ -451,7 +455,7 @@ static inline void htp_mm_hvx_vtcm_layout_build(
|
||||
} else if (is_matmul_id) {
|
||||
const size_t act_row_size_tiled = htp_mm_tiled_act_row_size(wtype, ne10);
|
||||
const uint32_t aligned_tile_size = htp_tensor_get_tile_padded_size(wtype);
|
||||
const uint32_t n_k_tiles = ne10 / 32;
|
||||
const uint32_t n_k_tiles = (ne10 + htp_tensor_get_tile_k(wtype) - 1) / htp_tensor_get_tile_k(wtype);
|
||||
const uint32_t tile_row_size = n_k_tiles * aligned_tile_size;
|
||||
size_t src0_sz_per_thread = htp_mm_round_up(n_prefetch * tile_row_size, 256);
|
||||
|
||||
@@ -489,8 +493,8 @@ static inline void htp_mm_hvx_vtcm_layout_build(
|
||||
case HTP_MM_KERNEL_HVX_QUANT_ROW: {
|
||||
size_t q_act_row_size = htp_mm_tiled_act_row_size(wtype, ne10);
|
||||
uint32_t aligned_tile_size = htp_tensor_get_tile_padded_size(wtype);
|
||||
uint32_t n_k_tiles = (ne10 + 31) / 32;
|
||||
uint32_t tile_row_size = n_k_tiles * aligned_tile_size;
|
||||
uint32_t n_weight_tiles = (ne10 + htp_tensor_get_tile_k(wtype) - 1) / htp_tensor_get_tile_k(wtype);
|
||||
uint32_t tile_row_size = n_weight_tiles * aligned_tile_size;
|
||||
size_t tiled_vtcm_size = htp_mm_round_up(n_prefetch * tile_row_size, 256);
|
||||
|
||||
src0_sz = tiled_vtcm_size * n_threads;
|
||||
|
||||
@@ -690,22 +690,48 @@ static void tri_f32(const void * restrict src,
|
||||
}
|
||||
}
|
||||
|
||||
// softplus(x) = log(1 + exp(x))
|
||||
// Match CPU reference: return x for x > 20 to avoid overflow.
|
||||
static inline void hvx_softplus_f32_aa(uint8_t * restrict dst, const uint8_t * restrict src, uint32_t n) {
|
||||
const HVX_Vector * restrict v_src = (const HVX_Vector *) src;
|
||||
HVX_Vector * restrict v_dst = (HVX_Vector *) dst;
|
||||
|
||||
const HVX_Vector twenty = hvx_vec_splat_f32(20.0f);
|
||||
const HVX_Vector one = hvx_vec_splat_f32(1.0f);
|
||||
|
||||
const uint32_t nvec = n / VLEN_FP32;
|
||||
const uint32_t nloe = n % VLEN_FP32;
|
||||
|
||||
#pragma unroll(4)
|
||||
for (uint32_t i = 0; i < nvec; i++) {
|
||||
HVX_Vector x = v_src[i];
|
||||
HVX_VectorPred pred_gt20 = Q6_Q_vcmp_gt_VsfVsf(x, twenty);
|
||||
HVX_Vector expx = hvx_vec_exp_f32(x);
|
||||
HVX_Vector logv = hvx_vec_log_f32(hvx_vec_add_f32_f32(one, expx));
|
||||
v_dst[i] = Q6_V_vmux_QVV(pred_gt20, x, logv);
|
||||
}
|
||||
|
||||
if (nloe > 0) {
|
||||
HVX_Vector x = v_src[nvec];
|
||||
HVX_VectorPred pred_gt20 = Q6_Q_vcmp_gt_VsfVsf(x, twenty);
|
||||
HVX_Vector expx = hvx_vec_exp_f32(x);
|
||||
HVX_Vector logv = hvx_vec_log_f32(hvx_vec_add_f32_f32(one, expx));
|
||||
HVX_Vector sp = Q6_V_vmux_QVV(pred_gt20, x, logv);
|
||||
hvx_vec_store_a((void *) &v_dst[nvec], nloe * sizeof(float), sp);
|
||||
}
|
||||
}
|
||||
|
||||
static void softplus_f32(const void * restrict src,
|
||||
void * restrict dst,
|
||||
const uint32_t num_rows,
|
||||
const struct htp_unary_context * uctx) {
|
||||
htp_unary_op_preamble;
|
||||
// softplus(x) = log(1 + exp(x))
|
||||
// Match CPU reference: ggml_compute_softplus_f32() in ggml-impl.h
|
||||
for (uint32_t ir = 0; ir < num_rows; ir++) {
|
||||
const float * restrict src_f = (const float *)((const uint8_t *)src + (ir * src0_row_size_aligned));
|
||||
float * restrict dst_f = (float *)((uint8_t *)dst + (ir * dst_row_size_aligned));
|
||||
|
||||
for (uint32_t i = 0; i < ne0; i++) {
|
||||
float x = src_f[i];
|
||||
// For x > 20: softplus(x) ~ x (avoids exp overflow)
|
||||
dst_f[i] = (x > 20.0f) ? x : logf(1.0f + expf(x));
|
||||
}
|
||||
for (uint32_t ir = 0; ir < num_rows; ir++) {
|
||||
const uint8_t * restrict src_local = (const uint8_t *)src + (ir * src0_row_size_aligned);
|
||||
uint8_t * restrict dst_local = (uint8_t *)dst + (ir * dst_row_size_aligned);
|
||||
|
||||
hvx_softplus_f32_aa(dst_local, src_local, ne0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -725,6 +751,143 @@ static void l2_norm_f32(const void * restrict src,
|
||||
}
|
||||
}
|
||||
|
||||
static inline HVX_VectorPair hvx_fwht_stage_pair(HVX_Vector v0, HVX_Vector v1, int32_t b) {
|
||||
HVX_VectorPair p = Q6_W_vshuff_VVR(v1, v0, b);
|
||||
HVX_Vector s = Q6_Vqf32_vadd_Vqf32Vqf32(Q6_V_lo_W(p), Q6_V_hi_W(p));
|
||||
HVX_Vector d = Q6_Vqf32_vsub_Vqf32Vqf32(Q6_V_lo_W(p), Q6_V_hi_W(p));
|
||||
return Q6_W_vshuff_VVR(d, s, b);
|
||||
}
|
||||
|
||||
static inline HVX_VectorPair hvx_fwht32_pair(HVX_Vector v0_sf, HVX_Vector v1_sf, HVX_Vector v_scale) {
|
||||
HVX_Vector v0 = Q6_Vqf32_vmpy_VsfVsf(v0_sf, v_scale);
|
||||
HVX_Vector v1 = Q6_Vqf32_vmpy_VsfVsf(v1_sf, v_scale);
|
||||
HVX_VectorPair p;
|
||||
p = hvx_fwht_stage_pair(v0, v1, 4);
|
||||
p = hvx_fwht_stage_pair(Q6_V_lo_W(p), Q6_V_hi_W(p), 8);
|
||||
p = hvx_fwht_stage_pair(Q6_V_lo_W(p), Q6_V_hi_W(p), 16);
|
||||
p = hvx_fwht_stage_pair(Q6_V_lo_W(p), Q6_V_hi_W(p), 32);
|
||||
p = hvx_fwht_stage_pair(Q6_V_lo_W(p), Q6_V_hi_W(p), 64);
|
||||
return p;
|
||||
}
|
||||
|
||||
static inline void hvx_fwht_store32(HVX_Vector * restrict v_dst, HVX_VectorPair p) {
|
||||
v_dst[0] = Q6_Vsf_equals_Vqf32(Q6_V_lo_W(p));
|
||||
}
|
||||
|
||||
static inline void hvx_fwht_store64(HVX_Vector * restrict v_dst, HVX_VectorPair p) {
|
||||
HVX_Vector u = Q6_V_lo_W(p);
|
||||
HVX_Vector v = Q6_V_hi_W(p);
|
||||
v_dst[0] = Q6_Vsf_equals_Vqf32(Q6_Vqf32_vadd_Vqf32Vqf32(u, v));
|
||||
v_dst[1] = Q6_Vsf_equals_Vqf32(Q6_Vqf32_vsub_Vqf32Vqf32(u, v));
|
||||
}
|
||||
|
||||
static inline void hvx_fwht_tail_f32(HVX_Vector * restrict v_dst, uint32_t nvec) {
|
||||
uint32_t len_vec = 2;
|
||||
for (; len_vec * 2 < nvec; len_vec <<= 2) {
|
||||
const uint32_t step = len_vec * 4;
|
||||
for (uint32_t i = 0; i < nvec; i += step) {
|
||||
for (uint32_t j = 0; j < len_vec; j++) {
|
||||
HVX_Vector a0 = v_dst[i + j];
|
||||
HVX_Vector a1 = v_dst[i + j + len_vec];
|
||||
HVX_Vector a2 = v_dst[i + j + 2 * len_vec];
|
||||
HVX_Vector a3 = v_dst[i + j + 3 * len_vec];
|
||||
|
||||
HVX_Vector t0 = Q6_Vqf32_vadd_VsfVsf(a0, a1);
|
||||
HVX_Vector t1 = Q6_Vqf32_vsub_VsfVsf(a0, a1);
|
||||
HVX_Vector t2 = Q6_Vqf32_vadd_VsfVsf(a2, a3);
|
||||
HVX_Vector t3 = Q6_Vqf32_vsub_VsfVsf(a2, a3);
|
||||
|
||||
v_dst[i + j] = Q6_Vsf_equals_Vqf32(Q6_Vqf32_vadd_Vqf32Vqf32(t0, t2));
|
||||
v_dst[i + j + len_vec] = Q6_Vsf_equals_Vqf32(Q6_Vqf32_vadd_Vqf32Vqf32(t1, t3));
|
||||
v_dst[i + j + 2 * len_vec] = Q6_Vsf_equals_Vqf32(Q6_Vqf32_vsub_Vqf32Vqf32(t0, t2));
|
||||
v_dst[i + j + 3 * len_vec] = Q6_Vsf_equals_Vqf32(Q6_Vqf32_vsub_Vqf32Vqf32(t1, t3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (len_vec < nvec) {
|
||||
for (uint32_t i = 0; i < nvec; i += 2 * len_vec) {
|
||||
for (uint32_t j = 0; j < len_vec; j++) {
|
||||
HVX_Vector u = v_dst[i + j];
|
||||
HVX_Vector v = v_dst[i + len_vec + j];
|
||||
v_dst[i + j] = hvx_vec_add_f32_f32(u, v);
|
||||
v_dst[i + len_vec + j] = hvx_vec_sub_f32_f32(u, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hvx_fwht_row_f32(const uint8_t * restrict src, uint8_t * restrict dst, uint32_t n, HVX_Vector v_scale) {
|
||||
const HVX_Vector * restrict v_src = (const HVX_Vector *) src;
|
||||
HVX_Vector * restrict v_dst = (HVX_Vector *) dst;
|
||||
const uint32_t nvec = n / VLEN_FP32;
|
||||
|
||||
if (nvec == 1) {
|
||||
HVX_VectorPair p = hvx_fwht32_pair(v_src[0], v_src[0], v_scale);
|
||||
hvx_fwht_store32(v_dst, p);
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < nvec; i += 2) {
|
||||
HVX_VectorPair p = hvx_fwht32_pair(v_src[i], v_src[i + 1], v_scale);
|
||||
hvx_fwht_store64(&v_dst[i], p);
|
||||
}
|
||||
|
||||
hvx_fwht_tail_f32(v_dst, nvec);
|
||||
}
|
||||
|
||||
static inline void hvx_fwht_row_f16(const uint8_t * restrict src, uint8_t * restrict dst, uint32_t n, HVX_Vector v_scale) {
|
||||
const HVX_Vector * restrict v_src = (const HVX_Vector *) src;
|
||||
HVX_Vector * restrict v_dst = (HVX_Vector *) dst;
|
||||
const uint32_t nvec = n / VLEN_FP32;
|
||||
|
||||
if (nvec == 1) {
|
||||
HVX_Vector v0 = Q6_V_lo_W(hvx_vec_f16_to_f32(v_src[0]));
|
||||
HVX_VectorPair p = hvx_fwht32_pair(v0, v0, v_scale);
|
||||
hvx_fwht_store32(v_dst, p);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t nvec_f16 = n / VLEN_FP16;
|
||||
for (uint32_t i = 0; i < nvec_f16; i++) {
|
||||
HVX_VectorPair f32p = hvx_vec_f16_to_f32(v_src[i]);
|
||||
HVX_VectorPair p = hvx_fwht32_pair(Q6_V_lo_W(f32p), Q6_V_hi_W(f32p), v_scale);
|
||||
hvx_fwht_store64(&v_dst[2 * i], p);
|
||||
}
|
||||
|
||||
hvx_fwht_tail_f32(v_dst, nvec);
|
||||
}
|
||||
|
||||
static void hadamard_f32(const void * restrict src,
|
||||
void * restrict dst,
|
||||
const uint32_t num_rows,
|
||||
const struct htp_unary_context * uctx) {
|
||||
htp_unary_op_preamble;
|
||||
const HVX_Vector v_scale = hvx_vec_splat_f32(1.0f / sqrtf((float) ne0));
|
||||
|
||||
for (uint32_t ir = 0; ir < num_rows; ir++) {
|
||||
const uint8_t * restrict src_row = (const uint8_t *) src + (ir * src0_row_size_aligned);
|
||||
uint8_t * restrict dst_row = (uint8_t *) dst + (ir * dst_row_size_aligned);
|
||||
|
||||
hvx_fwht_row_f32(src_row, dst_row, ne0, v_scale);
|
||||
}
|
||||
}
|
||||
|
||||
static void hadamard_f16(const void * restrict src,
|
||||
void * restrict dst,
|
||||
const uint32_t num_rows,
|
||||
const struct htp_unary_context * uctx) {
|
||||
htp_unary_op_preamble;
|
||||
const HVX_Vector v_scale = hvx_vec_splat_f32(1.0f / sqrtf((float) ne0));
|
||||
|
||||
for (uint32_t ir = 0; ir < num_rows; ir++) {
|
||||
const uint8_t * restrict src_row = (const uint8_t *) src + (ir * src0_row_size_aligned);
|
||||
uint8_t * restrict dst_row = (uint8_t *) dst + (ir * dst_row_size_aligned);
|
||||
|
||||
hvx_fwht_row_f16(src_row, dst_row, ne0, v_scale);
|
||||
}
|
||||
}
|
||||
|
||||
static void tanh_f32(const void * restrict src,
|
||||
void * restrict dst,
|
||||
const uint32_t num_rows,
|
||||
@@ -866,12 +1029,7 @@ static void tile_gelu_erf_f32(void * restrict dst, const void * restrict src, ui
|
||||
|
||||
static void tile_softplus_f32(void * restrict dst, const void * restrict src, uint32_t tw, const struct htp_unary_context * uctx) {
|
||||
(void) uctx;
|
||||
const float * restrict sf = (const float *) src;
|
||||
float * restrict df = (float *) dst;
|
||||
for (uint32_t i = 0; i < tw; i++) {
|
||||
float x = sf[i];
|
||||
df[i] = (x > 20.0f) ? x : logf(1.0f + expf(x));
|
||||
}
|
||||
hvx_softplus_f32_aa((uint8_t *) dst, (const uint8_t *) src, tw);
|
||||
}
|
||||
|
||||
static void tile_tanh_f32(void * restrict dst, const void * restrict src, uint32_t tw, const struct htp_unary_context * uctx) {
|
||||
@@ -1623,6 +1781,7 @@ static int execute_op_unary(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_STEP: op_type = is_f16 ? "step-f16" : "step-f32"; break;
|
||||
case HTP_OP_L2_NORM: op_type = is_f16 ? "l2norm-f16" : "l2norm-f32"; break;
|
||||
case HTP_OP_TRI: op_type = "tri-f32"; break;
|
||||
case HTP_OP_HADAMARD: op_type = is_f16 ? "hadamard-f16" : "hadamard-f32"; break;
|
||||
default:
|
||||
FARF(ERROR, "Unsupported unary Op %u\n", octx->op);
|
||||
return HTP_STATUS_NO_SUPPORT;
|
||||
@@ -1642,6 +1801,7 @@ static int execute_op_unary(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_ABS:
|
||||
case HTP_OP_UNARY_LOG:
|
||||
case HTP_OP_UNARY_STEP:
|
||||
case HTP_OP_HADAMARD:
|
||||
break;
|
||||
default:
|
||||
FARF(ERROR, "unary-%s: not supported for F16\n", op_type);
|
||||
@@ -1656,16 +1816,17 @@ static int execute_op_unary(struct htp_ops_context * octx) {
|
||||
}
|
||||
|
||||
const uint32_t src0_nrows = src0->ne[1] * src0->ne[2] * src0->ne[3];
|
||||
const size_t elem_size = is_f16 ? sizeof(_Float16) : sizeof(float);
|
||||
const size_t src0_data_row_size = src0->ne[0] * elem_size;
|
||||
const size_t dst_data_row_size = dst->ne[0] * elem_size;
|
||||
const size_t src0_elem_size = is_f16 ? sizeof(_Float16) : sizeof(float);
|
||||
const size_t dst_elem_size = (dst->type == HTP_TYPE_F16) ? sizeof(_Float16) : sizeof(float);
|
||||
const size_t src0_data_row_size = src0->ne[0] * src0_elem_size;
|
||||
const size_t dst_data_row_size = dst->ne[0] * dst_elem_size;
|
||||
|
||||
uint32_t row_start = 0;
|
||||
uint32_t nrows = src0_nrows;
|
||||
|
||||
if (octx->ctx->mdev.count > 1) {
|
||||
uint32_t rows_per_chunk = 0;
|
||||
htp_tensor_mdev_rows_per_chunk(dst, (uint32_t) elem_size, (uint32_t) dst_data_row_size, &rows_per_chunk);
|
||||
htp_tensor_mdev_rows_per_chunk(dst, (uint32_t) dst_elem_size, (uint32_t) dst_data_row_size, &rows_per_chunk);
|
||||
const struct htp_tensor_mdev_range range = htp_tensor_mdev_partition(src0_nrows, rows_per_chunk, octx->ctx->mdev.idx, octx->ctx->mdev.count, &octx->ctx->mdev.count_div);
|
||||
row_start = range.start;
|
||||
nrows = range.count;
|
||||
@@ -1792,6 +1953,7 @@ static int execute_op_unary(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_ABS: compute_func = (void *) abs_f16; break;
|
||||
case HTP_OP_UNARY_LOG: compute_func = (void *) log_f16; break;
|
||||
case HTP_OP_UNARY_STEP: compute_func = (void *) step_f16; break;
|
||||
case HTP_OP_HADAMARD: compute_func = (void *) hadamard_f16; break;
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
@@ -1822,6 +1984,7 @@ static int execute_op_unary(struct htp_ops_context * octx) {
|
||||
case HTP_OP_UNARY_RELU: compute_func = (void *) relu_f32; break;
|
||||
case HTP_OP_UNARY_STEP: compute_func = (void *) step_f32; break;
|
||||
case HTP_OP_L2_NORM: compute_func = (void *) l2_norm_f32; break;
|
||||
case HTP_OP_HADAMARD: compute_func = (void *) hadamard_f32; break;
|
||||
case HTP_OP_TRI:
|
||||
task_func = unary_thread_tri_f32;
|
||||
compute_func = (void *) tri_f32;
|
||||
|
||||
@@ -64,6 +64,7 @@ static inline bool htp_op_is_unary(uint32_t opcode) {
|
||||
case HTP_OP_UNARY_STEP:
|
||||
case HTP_OP_L2_NORM:
|
||||
case HTP_OP_TRI:
|
||||
case HTP_OP_HADAMARD:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -94,8 +95,9 @@ static inline void htp_unary_vtcm_layout_build(
|
||||
uint32_t * out_col_tile,
|
||||
uint32_t * out_vtcm_row_per_thread
|
||||
) {
|
||||
const size_t dst_elem_size = (op == HTP_OP_HADAMARD) ? sizeof(float) : elem_size;
|
||||
const size_t src0_data_row_size = ne00 * elem_size;
|
||||
const size_t dst_data_row_size = ne10 * elem_size;
|
||||
const size_t dst_data_row_size = ne10 * dst_elem_size;
|
||||
|
||||
const size_t src0_row_size_aligned = hex_round_up(src0_data_row_size, 128);
|
||||
const size_t dst_row_size_aligned = hex_round_up(dst_data_row_size, 128);
|
||||
@@ -131,7 +133,8 @@ static inline void htp_unary_vtcm_layout_build(
|
||||
}
|
||||
|
||||
const bool is_reduction = (op == HTP_OP_NORM || op == HTP_OP_RMS_NORM ||
|
||||
op == HTP_OP_RMS_NORM_MUL || op == HTP_OP_L2_NORM);
|
||||
op == HTP_OP_RMS_NORM_MUL || op == HTP_OP_L2_NORM ||
|
||||
op == HTP_OP_HADAMARD);
|
||||
// The tiled fallback path below only has F32 task functions (unary_task_f32_tiled_*);
|
||||
// F16 has no tiled kernels, so it must stay on the row-block path like reduction ops.
|
||||
// NOTE: if F16 ends up with vtcm_row_per_thread == 0 here (row too large for the VTCM
|
||||
|
||||
@@ -132,6 +132,7 @@ set(GGML_OPENCL_KERNELS
|
||||
gemm_moe_q6_k_q8_1_dp4a
|
||||
gemm_moe_q8_1_dp4a
|
||||
moe_reorder_quant_a_q8_1
|
||||
transpose_activation_q8
|
||||
gemm_noshuffle_q4_k_q8_1_dp4a
|
||||
gemm_noshuffle_q5_k_q8_1_dp4a
|
||||
gemm_noshuffle_q6_k_q8_1_dp4a
|
||||
|
||||
@@ -720,6 +720,10 @@ struct ggml_backend_opencl_context {
|
||||
ggml_cl_buffer prealloc_moe_qa; // int8 quants [tok_slots * ne00]
|
||||
ggml_cl_buffer prealloc_moe_da; // per-block d [tok_slots * ne00/32] (half)
|
||||
ggml_cl_buffer prealloc_moe_sa; // per-block s [tok_slots * ne00/32] (half)
|
||||
// per-tile transposed qa/da and on-chip scratch for the ldck dp4a MoE GEMM
|
||||
ggml_cl_buffer prealloc_moe_qa_trans; // int8 quants [tok_slots * ne00]
|
||||
ggml_cl_buffer prealloc_moe_da_trans; // per-block d [tok_slots * ne00/32] (float)
|
||||
ggml_cl_buffer prealloc_moe_qda_on_chip;
|
||||
// scratch copy of the router weights to avoid dst aliasing
|
||||
ggml_cl_buffer prealloc_moe_combine_w;
|
||||
ggml_cl_buffer prealloc_splitk_partial; // [ksplit * M] partials for split-K GEMV
|
||||
@@ -1016,6 +1020,7 @@ struct ggml_backend_opencl_context {
|
||||
cl_kernel kernel_gemv_moe_q4_k_f32_ns_wimg = nullptr; // weight-as-texture MoE decode GEMV (opt-in)
|
||||
cl_kernel kernel_gemm_moe_q4_k_q8_1_dp4a = nullptr; // dp4a (int8) prefill GEMM variant
|
||||
cl_kernel kernel_moe_reorder_quant_a_q8_1; // fused reorder + q8_1 quant for the dp4a GEMM
|
||||
cl_kernel kernel_transpose_activation_q8; // per-tile transpose of q8_1 activations for the ldck dp4a GEMM
|
||||
cl_kernel kernel_gemm_moe_q8_1_dp4a_q80 = nullptr; // generic dp4a MoE GEMM (MOE_QT=80), opt-in
|
||||
cl_kernel kernel_moe_expand_scale_q8_0 = nullptr; // q8_0 per-block d -> uniform scale[16]
|
||||
cl_kernel kernel_gemm_moe_q8_1_dp4a_q50 = nullptr; // generic dp4a MoE GEMM (MOE_QT=50, q5_0), opt-in
|
||||
@@ -1031,6 +1036,7 @@ struct ggml_backend_opencl_context {
|
||||
cl_kernel kernel_gemm_moe_mxfp4_q8_1_dp4a = nullptr; // dp4a (int8) mxfp4 MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_q4_0_q8_1_dp4a = nullptr; // dp4a (int8) q4_0 MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_mxfp4_q8_1_dp4a_bin = nullptr; // binary dp4a (int8) mxfp4 MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_mxfp4_q8_1_dp4a_ldck_bin = nullptr; // binary dp4a (int8) mxfp4 MoE prefill GEMM using ldck
|
||||
cl_kernel kernel_gemm_moe_q4_0_q8_1_dp4a_bin = nullptr; // binary dp4a (int8) q4_0 MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_q4_k_q8_1_dp4a_bin = nullptr; // binary dp4a (int8) q4_k MoE prefill GEMM
|
||||
cl_kernel kernel_gemm_moe_q6_k_q8_1_dp4a_bin = nullptr; // binary dp4a (int8) q6_k MoE prefill GEMM
|
||||
@@ -4903,6 +4909,24 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// gemm_moe_mxfp4_q8_1_dp4a_ldck_bin (dp4a prefill GEMM)
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
size_t bin_size = 0;
|
||||
backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_ldck_bin = nullptr;
|
||||
|
||||
if (use_adreno_bin_kernels(backend_ctx)) {
|
||||
const char * kernel_bin = (const char *)backend_ctx->get_adreno_bin_kernel("gemm_moe_mxfp4_q8_1_dp4a_ila_ldck", &bin_size);
|
||||
if (kernel_bin && bin_size > 0) {
|
||||
cl_program prog =
|
||||
build_program_from_binary(backend_ctx->context, backend_ctx->device, kernel_bin, CL_moe_compile_opts, bin_size);
|
||||
|
||||
CL_CHECK((backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_ldck_bin = clCreateKernel(prog, "kernel_gemm_moe_mxfp4_q8_1_dp4a_ila_ldck", &err), err));
|
||||
CL_CHECK(clReleaseProgram(prog));
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gemm_moe_q4_0_q8_1_dp4a (dp4a prefill GEMM)
|
||||
if (backend_ctx->has_integer_dot) {
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
@@ -4984,6 +5008,23 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
|
||||
// transpose_activation_q8 (per-tile transpose of q8_1 activations for the ldck dp4a GEMM)
|
||||
{
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "transpose_activation_q8.cl.h"
|
||||
};
|
||||
#else
|
||||
const std::string kernel_src = read_file("transpose_activation_q8.cl");
|
||||
#endif
|
||||
cl_program prog =
|
||||
build_program_from_source(backend_ctx, kernel_src.c_str(), CL_moe_compile_opts);
|
||||
|
||||
CL_CHECK((backend_ctx->kernel_transpose_activation_q8 = clCreateKernel(prog, "kernel_transpose_activation_q8", &err), err));
|
||||
CL_CHECK(clReleaseProgram(prog));
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
|
||||
// gemv_moe_q5_k_f32_ns
|
||||
{
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
@@ -28258,11 +28299,19 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
static const char * mxfp4_moe_dp4a_env = getenv("GGML_OPENCL_MXFP4_MOE_DP4A");
|
||||
bool use_moe_dp4a = mxfp4_moe_dp4a_env
|
||||
? (atoi(mxfp4_moe_dp4a_env) != 0)
|
||||
: (backend_ctx->adreno_gen == ADRENO_GPU_GEN::X2E);
|
||||
: (backend_ctx->adreno_gen == ADRENO_GPU_GEN::X2E || backend_ctx->adreno_gen == ADRENO_GPU_GEN::X1E);
|
||||
// dot prod has to be available
|
||||
use_moe_dp4a = backend_ctx->has_integer_dot && use_moe_dp4a;
|
||||
// bin kernel takes precedence, dp4a bin kernel has higher priority than normal bin kernel
|
||||
if (backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_bin == nullptr) {
|
||||
// ldck dp4a bin kernel (X1) has the same priority as normal bin kernel (X2/A8x), they are not expected to coexist.
|
||||
const bool use_moe_dp4a_ldck = backend_ctx->has_integer_dot &&
|
||||
(!mxfp4_moe_dp4a_env || atoi(mxfp4_moe_dp4a_env) != 0) &&
|
||||
backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_bin == nullptr &&
|
||||
backend_ctx->kernel_gemm_moe_mxfp4_f32_ns_bin == nullptr &&
|
||||
backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_ldck_bin != nullptr;
|
||||
if (use_moe_dp4a_ldck) {
|
||||
use_moe_dp4a = true;
|
||||
} else if (backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_bin == nullptr) {
|
||||
use_moe_dp4a = use_moe_dp4a && backend_ctx->kernel_gemm_moe_mxfp4_f32_ns_bin == nullptr;
|
||||
}
|
||||
|
||||
@@ -28351,6 +28400,12 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
backend_ctx->prealloc_moe_qa.allocate(backend_ctx->context, tok_slots * ne00 * sizeof(cl_char));
|
||||
backend_ctx->prealloc_moe_da.allocate(backend_ctx->context, n_blocks * sizeof(cl_half));
|
||||
backend_ctx->prealloc_moe_sa.allocate(backend_ctx->context, n_blocks * sizeof(cl_half));
|
||||
if (use_moe_dp4a_ldck) {
|
||||
backend_ctx->prealloc_moe_qa_trans.allocate(backend_ctx->context, tok_slots * ne00 * sizeof(cl_char));
|
||||
backend_ctx->prealloc_moe_da_trans.allocate(backend_ctx->context, n_blocks * sizeof(cl_float));
|
||||
// matches max_constant_size of src1_a_cm in the ldck kernel
|
||||
backend_ctx->prealloc_moe_qda_on_chip.allocate(backend_ctx->context, 2304);
|
||||
}
|
||||
|
||||
// fused reorder + q8_1 quant straight from the original
|
||||
// activations (no intermediate f32 reorder buffer). mxfp4 has no
|
||||
@@ -28375,13 +28430,33 @@ static void ggml_cl_mul_mat_id(ggml_backend_t backend, const ggml_tensor * src0,
|
||||
cl_kernel dk = backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a;
|
||||
if (backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_bin) {
|
||||
dk = backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_bin;
|
||||
} else if (use_moe_dp4a_ldck) {
|
||||
dk = backend_ctx->kernel_gemm_moe_mxfp4_q8_1_dp4a_ldck_bin;
|
||||
|
||||
// per-tile transpose of qa/da for the ldck kernel
|
||||
cl_kernel tk = backend_ctx->kernel_transpose_activation_q8;
|
||||
CL_CHECK(clSetKernelArg(tk, 0, sizeof(cl_mem), &backend_ctx->prealloc_moe_qa.buffer));
|
||||
CL_CHECK(clSetKernelArg(tk, 1, sizeof(cl_mem), &backend_ctx->prealloc_moe_qa_trans.buffer));
|
||||
CL_CHECK(clSetKernelArg(tk, 2, sizeof(cl_mem), &backend_ctx->prealloc_moe_da.buffer));
|
||||
CL_CHECK(clSetKernelArg(tk, 3, sizeof(cl_mem), &backend_ctx->prealloc_moe_da_trans.buffer));
|
||||
CL_CHECK(clSetKernelArg(tk, 4, sizeof(cl_mem), &(backend_ctx->prealloc_total_tiles.buffer)));
|
||||
CL_CHECK(clSetKernelArg(tk, 5, sizeof(cl_uint), &ne00));
|
||||
size_t tk_local[3] = { 32, 8, 1 };
|
||||
size_t tk_global[3] = { 32, (size_t)(ne00 / 4), (size_t)max_post_router_tile };
|
||||
backend_ctx->enqueue_ndrange_kernel(tk, 3, tk_global, tk_local, dst);
|
||||
}
|
||||
|
||||
int aidx = 0;
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &extra0_mxfp4->q_img));
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &extra0_mxfp4->e));
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &backend_ctx->prealloc_moe_qa.buffer));
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &backend_ctx->prealloc_moe_da.buffer));
|
||||
if (use_moe_dp4a_ldck) {
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &backend_ctx->prealloc_moe_qa_trans.buffer));
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &backend_ctx->prealloc_moe_da_trans.buffer));
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &backend_ctx->prealloc_moe_qda_on_chip.buffer));
|
||||
} else {
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &backend_ctx->prealloc_moe_qa.buffer));
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &backend_ctx->prealloc_moe_da.buffer));
|
||||
}
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &buf_src2));
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &buf_src2_emap));
|
||||
CL_CHECK(clSetKernelArg(dk, aidx++, sizeof(cl_mem), &buf_dst_image));
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma OPENCL EXTENSION cl_khr_fp16 : enable
|
||||
#pragma OPENCL EXTENSION cl_khr_subgroups : enable
|
||||
|
||||
#define tile_size 32
|
||||
|
||||
kernel void kernel_transpose_activation_q8(
|
||||
__global uint * src1_qa,
|
||||
__global uint * src1_qa_trans,
|
||||
__global half * src1_sa,
|
||||
__global float * src1_sa_trans,
|
||||
__global const int * total_tiles,
|
||||
uint K
|
||||
) {
|
||||
uint row_in_tile = get_global_id(0);
|
||||
uint blk_k = get_group_id(1);
|
||||
uint col_in_tile = get_local_id(1);
|
||||
uint tile_n = get_global_id(2);
|
||||
|
||||
if (blk_k >= (K / tile_size) || tile_n >= (uint)total_tiles[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint src_idx = (tile_n * tile_size + row_in_tile) * K / 4 + blk_k * 8 + col_in_tile;
|
||||
uint dst_idx = tile_n * tile_size * K / 4 + (blk_k * 8 + col_in_tile) * tile_size + row_in_tile;
|
||||
|
||||
src1_qa_trans[dst_idx] = src1_qa[src_idx];
|
||||
|
||||
if (col_in_tile == 0) {
|
||||
src_idx = (tile_n * tile_size + row_in_tile) * K / 32 + blk_k;
|
||||
dst_idx = tile_n * tile_size * K / 32 + blk_k * tile_size + row_in_tile;
|
||||
|
||||
src1_sa_trans[dst_idx] = (float)(src1_sa[src_idx]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -115,7 +115,8 @@ void * ggml_sycl_malloc_device(size_t size, sycl::queue &q, ggml_sycl_mem_type t
|
||||
#else
|
||||
ze_device_mem_alloc_desc_t alloc_desc = {ZE_STRUCTURE_TYPE_DEVICE_MEM_ALLOC_DESC, nullptr, 0, 0};
|
||||
#endif
|
||||
ze_result_t r = zeMemAllocDevice(ze_ctx, &alloc_desc, size, 64, ze_dev, &ptr);
|
||||
// request SYCL_BUFFER_ALIGNMENT so tensor offsets aligned to it stay aligned in absolute terms
|
||||
ze_result_t r = zeMemAllocDevice(ze_ctx, &alloc_desc, size, SYCL_BUFFER_ALIGNMENT, ze_dev, &ptr);
|
||||
if (r == ZE_RESULT_SUCCESS && ptr) {
|
||||
ggml_sycl_memtrace_add(type, ptr, size);
|
||||
return ptr;
|
||||
|
||||
@@ -37,6 +37,10 @@ namespace syclexp = sycl::ext::oneapi::experimental;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__INTEL_LLVM_COMPILER)
|
||||
#define GGML_SYCL_DMMV_HAS_ESIMD
|
||||
#endif
|
||||
|
||||
#if GGML_SYCL_DNNL
|
||||
#include "dnnl.hpp"
|
||||
#include "dnnl_sycl.hpp"
|
||||
|
||||
@@ -158,16 +158,16 @@ static void dequantize_row_q4_0_sycl_reorder(const void *vx, dst_t *y, const int
|
||||
dpct::has_capability_or_fail(stream->get_device(),
|
||||
{sycl::aspect::fp16});
|
||||
|
||||
int constexpr WARP_K = WARP_SIZE * QK4_0;
|
||||
const int n_warp = (k + WARP_K - 1) / WARP_K;
|
||||
GGML_ASSERT(k % 2 == 0);
|
||||
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, n_warp) *
|
||||
sycl::range<3>(1, 1, WARP_SIZE),
|
||||
sycl::range<3>(1, 1, WARP_SIZE)),
|
||||
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
|
||||
GGML_ASSERT(k % QK4_0 == 0);
|
||||
const int64_t n_groups = ceil_div(k / 2, SYCL_DEQUANTIZE_BLOCK_SIZE);
|
||||
// decrease global range when it exceeds the max int; the kernel loops to cover the rest
|
||||
const int64_t wg_size = downsample_sycl_global_range(n_groups, SYCL_DEQUANTIZE_BLOCK_SIZE);
|
||||
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, n_groups) *
|
||||
sycl::range<3>(1, 1, wg_size),
|
||||
sycl::range<3>(1, 1, wg_size)),
|
||||
[=](sycl::nd_item<3> item_ct1) {
|
||||
dequantize_block_q4_0_reorder(vx, y, k, item_ct1);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
template <typename dst_t>
|
||||
@@ -177,16 +177,16 @@ static void dequantize_row_q8_0_sycl_reorder(const void *vx, dst_t *y, const int
|
||||
dpct::has_capability_or_fail(stream->get_device(),
|
||||
{sycl::aspect::fp16});
|
||||
|
||||
int constexpr WARP_K = WARP_SIZE * QK8_0;
|
||||
const int n_warp = (k + WARP_K - 1) / WARP_K;
|
||||
GGML_ASSERT(k % QK8_0 == 0);
|
||||
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, n_warp) *
|
||||
sycl::range<3>(1, 1, WARP_SIZE),
|
||||
sycl::range<3>(1, 1, WARP_SIZE)),
|
||||
[=](sycl::nd_item<3> item_ct1) [[sycl::reqd_sub_group_size(WARP_SIZE)]]{
|
||||
const int64_t n_groups = ceil_div(k / 2, SYCL_DEQUANTIZE_BLOCK_SIZE);
|
||||
// decrease global range when it exceeds the max int; the kernel loops to cover the rest
|
||||
const int64_t wg_size = downsample_sycl_global_range(n_groups, SYCL_DEQUANTIZE_BLOCK_SIZE);
|
||||
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, n_groups) *
|
||||
sycl::range<3>(1, 1, wg_size),
|
||||
sycl::range<3>(1, 1, wg_size)),
|
||||
[=](sycl::nd_item<3> item_ct1) {
|
||||
dequantize_block_q8_0_reorder(vx, y, k, item_ct1);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
template <typename dst_t>
|
||||
|
||||
@@ -1255,6 +1255,8 @@ void ggml_sycl_cpy(ggml_backend_sycl_context & ctx, const ggml_tensor * src0, co
|
||||
scope_op_debug_print scope_dbg_print(__func__, src1, /*num_src=*/0, debug_get_tensor_str("\tsrc0", src0));
|
||||
const int64_t ne = ggml_nelements(src0);
|
||||
GGML_ASSERT(ne == ggml_nelements(src1));
|
||||
// the cpy kernels index in 32-bit
|
||||
GGML_ASSERT(ne <= INT32_MAX);
|
||||
|
||||
GGML_TENSOR_BINARY_OP_LOCALS01;
|
||||
|
||||
|
||||
@@ -818,61 +818,43 @@ static void dequantize_block_q4_0(const void * __restrict__ vx, dst_t * __restri
|
||||
}
|
||||
|
||||
template<typename dst_t>
|
||||
static void dequantize_block_q4_0_reorder(const void * __restrict__ vx, dst_t * __restrict__ yy, int64_t nb32,
|
||||
static void dequantize_block_q4_0_reorder(const void * __restrict__ vx, dst_t * __restrict__ yy, int64_t k,
|
||||
const sycl::nd_item<3> &item_ct1) {
|
||||
// one thread per qs byte, like dequantize_block<dequantize_q4_0>; the reorder layout makes the byte reads contiguous
|
||||
// grid-stride loop because the launcher may shrink the work-group to keep the global range below INT_MAX
|
||||
// float math, not dequantize_q4_0_reorder: that helper computes in half with GGML_SYCL_F16, which changes f32 output
|
||||
const sycl::half * d_ptr = (const sycl::half *) ((const uint8_t *) vx + k/2);
|
||||
const int64_t stride = item_ct1.get_local_range(2) * item_ct1.get_group_range(2);
|
||||
|
||||
const int64_t i = item_ct1.get_group(2);
|
||||
auto k=nb32;
|
||||
// assume 32 threads
|
||||
const int64_t tid = item_ct1.get_local_id(2);
|
||||
const int lane_ib = i * WARP_SIZE + tid;
|
||||
for (int64_t iq = item_ct1.get_global_id(2); iq < k/2; iq += stride) {
|
||||
const int64_t ib = iq / (QK4_0/2);
|
||||
const int64_t iqs = iq % (QK4_0/2);
|
||||
const int vq = ((const uint8_t *) vx)[iq];
|
||||
const float d = float(d_ptr[ib]);
|
||||
|
||||
if (lane_ib >= k / QK4_0) {
|
||||
return;
|
||||
dst_t * y = yy + ib*QK4_0 + iqs;
|
||||
y[0] = d * ((vq & 0xF) - 8);
|
||||
y[QK4_0/2] = d * ((vq >> 4) - 8);
|
||||
}
|
||||
|
||||
dst_t * y_ptr = yy + lane_ib * QK4_0;
|
||||
|
||||
auto qs = (const uint8_t*)vx + lane_ib * QK4_0 / 2;
|
||||
auto s_ptr = (const sycl::half*)((const uint8_t*)vx + k / 2) + lane_ib;
|
||||
|
||||
const float d = float(*s_ptr);
|
||||
|
||||
#pragma unroll
|
||||
for (int l = 0; l < QK4_0 / 2; ++l) {
|
||||
int vq = qs[l];
|
||||
y_ptr[l + 0] = d * ((vq & 0xF) - 8);
|
||||
y_ptr[l + 16] = d * ((vq >> 4) - 8);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Dequantize Q8_0 from reorder layout: [all qs (k bytes)][all d values]
|
||||
// Each thread handles one block of QK8_0 elements.
|
||||
template<typename dst_t>
|
||||
static void dequantize_block_q8_0_reorder(const void * __restrict__ vx, dst_t * __restrict__ yy, int64_t k,
|
||||
const sycl::nd_item<3> &item_ct1) {
|
||||
// two values per thread, like dequantize_block<dequantize_q8_0>; the reorder layout keeps all qs contiguous
|
||||
// grid-stride loop because the launcher may shrink the work-group to keep the global range below INT_MAX
|
||||
// float math, not dequantize_q8_0_reorder: that helper computes in half with GGML_SYCL_F16, which changes f32 output
|
||||
const int8_t * qs = (const int8_t *) vx;
|
||||
const sycl::half * d_ptr = (const sycl::half *) ((const uint8_t *) vx + k);
|
||||
const int64_t stride = 2 * item_ct1.get_local_range(2) * item_ct1.get_group_range(2);
|
||||
|
||||
const int64_t i = item_ct1.get_group(2);
|
||||
const int64_t tid = item_ct1.get_local_id(2);
|
||||
const int lane_ib = i * WARP_SIZE + tid;
|
||||
for (int64_t i = 2 * item_ct1.get_global_id(2); i < k; i += stride) {
|
||||
const float d = float(d_ptr[i / QK8_0]);
|
||||
|
||||
if (lane_ib >= k / QK8_0) {
|
||||
return;
|
||||
yy[i + 0] = d * qs[i + 0];
|
||||
yy[i + 1] = d * qs[i + 1];
|
||||
}
|
||||
|
||||
dst_t * y_ptr = yy + lane_ib * QK8_0;
|
||||
|
||||
auto qs = (const int8_t*)vx + lane_ib * QK8_0;
|
||||
auto s_ptr = (const sycl::half*)((const uint8_t*)vx + k) + lane_ib;
|
||||
|
||||
const float d = float(*s_ptr);
|
||||
|
||||
#pragma unroll
|
||||
for (int l = 0; l < QK8_0; ++l) {
|
||||
y_ptr[l] = d * qs[l];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<typename dst_t>
|
||||
|
||||
+181
-73
@@ -10,7 +10,6 @@
|
||||
#endif
|
||||
#include <sycl/ext/intel/esimd.hpp>
|
||||
#include "esimd.hpp"
|
||||
#define GGML_SYCL_DMMV_HAS_ESIMD
|
||||
#endif
|
||||
|
||||
static void convert_f16(const void * vx, const int64_t ib, const int iqs, dfloat2 & v){
|
||||
@@ -1873,11 +1872,67 @@ static void dequantize_mul_mat_vec_q6_K_sycl(const void *vx, const float *y,
|
||||
#ifdef GGML_SYCL_DMMV_HAS_ESIMD
|
||||
using ggml_sycl_esimd::GGML_SYCL_DMMV_ESIMD_WG_SIZE;
|
||||
|
||||
// generic reordered dequantize-matvec: each work-group owns a pair of
|
||||
// consecutive output rows and updates one 32-wide accumulator per row
|
||||
template <ggml_type T>
|
||||
// policies for dequantize_mul_mat_vec_reorder_esimd: row mapping and what to do with the two sums
|
||||
// plain mat-vec: a work-group owns two consecutive rows of one matrix
|
||||
struct esimd_dmmv_row_pair_policy {
|
||||
const void * vx;
|
||||
float * dst;
|
||||
|
||||
ESIMD_INLINE const void * w_a() const { return vx; }
|
||||
ESIMD_INLINE const void * w_b() const { return vx; }
|
||||
|
||||
ESIMD_INLINE int row_a(int g) const { return g * 2; }
|
||||
ESIMD_INLINE int row_b(int g) const { return g * 2 + 1; }
|
||||
ESIMD_INLINE bool has_b(int g, int nrows) const { return row_b(g) < nrows; }
|
||||
|
||||
ESIMD_INLINE void epilogue(int g, int nrows, float sum_a, float sum_b) const {
|
||||
dst[row_a(g)] = sum_a;
|
||||
if (has_b(g, nrows)) {
|
||||
dst[row_b(g)] = sum_b;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// fused gate+up+GLU: a work-group owns one row of both matrices; a is the gate, b the up
|
||||
struct esimd_dmmv_glu_policy {
|
||||
const void * vgate;
|
||||
const void * vup;
|
||||
float * dst;
|
||||
ggml_glu_op glu_op;
|
||||
|
||||
ESIMD_INLINE const void * w_a() const { return vgate; }
|
||||
ESIMD_INLINE const void * w_b() const { return vup; }
|
||||
|
||||
ESIMD_INLINE int row_a(int g) const { return g; }
|
||||
ESIMD_INLINE int row_b(int g) const { return g; }
|
||||
ESIMD_INLINE bool has_b(int, int) const { return true; } // up always contributes
|
||||
|
||||
ESIMD_INLINE void epilogue(int g, int, float gate, float up) const {
|
||||
using namespace sycl::ext::intel::esimd;
|
||||
|
||||
// SwiGLU: silu(gate)*up = gate*sigmoid(gate)*up. GEGLU's tanh form reduces
|
||||
// to the same shape via tanh(y) = 2*sigmoid(2y) - 1, so only the sigmoid
|
||||
// argument differs. sigmoid via exp2 (width-1 simd is a degenerate case for
|
||||
// the EM pipe, so broadcast to 16 lanes); lane 0 holds the result.
|
||||
const float GELU_COEF_A = 0.044715f;
|
||||
const float SQRT_2_OVER_PI = 0.79788456080286535587989211986876f;
|
||||
const float NEG_LOG2E = -1.44269504088896341f;
|
||||
|
||||
const float arg = (glu_op == GGML_GLU_OP_GEGLU) ?
|
||||
2.0f * SQRT_2_OVER_PI * gate * (1.0f + GELU_COEF_A * gate * gate) :
|
||||
gate;
|
||||
|
||||
simd<float, 16> t = arg * NEG_LOG2E;
|
||||
simd<float, 16> e = exp2(t);
|
||||
dst[g] = gate / (1.0f + e[0]) * up;
|
||||
}
|
||||
};
|
||||
|
||||
// generic reordered dequantize-matvec: each work-group owns a pair of rows chosen by the policy
|
||||
// and updates one 32-wide accumulator per row
|
||||
template <ggml_type T, typename Policy>
|
||||
ESIMD_INLINE void dequantize_mul_mat_vec_reorder_esimd(
|
||||
const void * vx, const float * y, float * dst,
|
||||
const Policy & policy, const float * y,
|
||||
const int ncols, const int nrows,
|
||||
sycl::local_accessor<float, 1> lmem,
|
||||
const sycl::nd_item<1> & it) {
|
||||
@@ -1886,41 +1941,41 @@ ESIMD_INLINE void dequantize_mul_mat_vec_reorder_esimd(
|
||||
|
||||
const int num_blocks_per_row = ncols / QK_K;
|
||||
const size_t nb = (size_t) nrows * num_blocks_per_row;
|
||||
const auto ps = traits::make_ptrs(vx, nb);
|
||||
const auto pa = traits::make_ptrs(policy.w_a(), nb);
|
||||
const auto pb = traits::make_ptrs(policy.w_b(), nb);
|
||||
|
||||
const int tid = it.get_local_id(0);
|
||||
const int row_pair = it.get_group(0);
|
||||
const int row0 = row_pair * 2; // two consecutive output rows
|
||||
const bool has_row1 = row0 + 1 < nrows;
|
||||
const int tid = it.get_local_id(0);
|
||||
const int g = it.get_group(0);
|
||||
const int row_a = policy.row_a(g);
|
||||
const int row_b = policy.row_b(g);
|
||||
const bool has_b = policy.has_b(g, nrows);
|
||||
|
||||
// one 32-wide accumulator per output row (small footprint, no spill)
|
||||
simd<float, 32> acc0 = 0.0f;
|
||||
simd<float, 32> acc1 = 0.0f;
|
||||
// one 32-wide accumulator per row (small footprint, no spill)
|
||||
simd<float, 32> acc_a = 0.0f;
|
||||
simd<float, 32> acc_b = 0.0f;
|
||||
|
||||
for (int ib = tid; ib < num_blocks_per_row; ib += GGML_SYCL_DMMV_ESIMD_WG_SIZE) {
|
||||
simd<float, 256> y_vec = block_load<float, 256>(y + (size_t) ib * QK_K);
|
||||
|
||||
const size_t bi0 = (size_t) (row0 + 0) * num_blocks_per_row + ib;
|
||||
const size_t bi1 = (size_t) (row0 + 1) * num_blocks_per_row + ib;
|
||||
const size_t bi_a = (size_t) row_a * num_blocks_per_row + ib;
|
||||
const size_t bi_b = (size_t) row_b * num_blocks_per_row + ib;
|
||||
|
||||
traits::mac_pair(ps, bi0, ps, bi1, has_row1, y_vec, acc0, acc1);
|
||||
traits::mac_pair(pa, bi_a, pb, bi_b, has_b, y_vec, acc_a, acc_b);
|
||||
}
|
||||
|
||||
lmem[tid * 2 + 0] = reduce<float>(acc0, std::plus<>{});
|
||||
lmem[tid * 2 + 1] = reduce<float>(acc1, std::plus<>{});
|
||||
lmem[tid * 2 + 0] = reduce<float>(acc_a, std::plus<>{});
|
||||
lmem[tid * 2 + 1] = reduce<float>(acc_b, std::plus<>{});
|
||||
it.barrier(sycl::access::fence_space::local_space);
|
||||
|
||||
if (tid == 0) {
|
||||
float sum0 = 0.0f;
|
||||
float sum1 = 0.0f;
|
||||
float sum_a = 0.0f;
|
||||
float sum_b = 0.0f;
|
||||
for (int p = 0; p < GGML_SYCL_DMMV_ESIMD_WG_SIZE; ++p) {
|
||||
sum0 += lmem[p * 2 + 0];
|
||||
sum1 += lmem[p * 2 + 1];
|
||||
}
|
||||
dst[row0 + 0] = sum0;
|
||||
if (has_row1) {
|
||||
dst[row0 + 1] = sum1;
|
||||
sum_a += lmem[p * 2 + 0];
|
||||
sum_b += lmem[p * 2 + 1];
|
||||
}
|
||||
|
||||
policy.epilogue(g, nrows, sum_a, sum_b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1936,7 +1991,7 @@ static void dequantize_mul_mat_vec_q2_K_sycl_reorder_esimd(const void *vx, const
|
||||
sycl::nd_range<1>(sycl::range<1>((size_t)workgroups * GGML_SYCL_DMMV_ESIMD_WG_SIZE), sycl::range<1>(GGML_SYCL_DMMV_ESIMD_WG_SIZE)),
|
||||
[=](sycl::nd_item<1> it) [[intel::sycl_explicit_simd]] {
|
||||
dequantize_mul_mat_vec_reorder_esimd<GGML_TYPE_Q2_K>(
|
||||
vx, y, dst, ncols, nrows, lmem, it);
|
||||
esimd_dmmv_row_pair_policy{ vx, dst }, y, ncols, nrows, lmem, it);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1953,7 +2008,7 @@ static void dequantize_mul_mat_vec_q3_K_sycl_reorder_esimd(const void *vx, const
|
||||
sycl::nd_range<1>(sycl::range<1>((size_t)workgroups * GGML_SYCL_DMMV_ESIMD_WG_SIZE), sycl::range<1>(GGML_SYCL_DMMV_ESIMD_WG_SIZE)),
|
||||
[=](sycl::nd_item<1> it) [[intel::sycl_explicit_simd]] {
|
||||
dequantize_mul_mat_vec_reorder_esimd<GGML_TYPE_Q3_K>(
|
||||
vx, y, dst, ncols, nrows, lmem, it);
|
||||
esimd_dmmv_row_pair_policy{ vx, dst }, y, ncols, nrows, lmem, it);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1970,7 +2025,7 @@ static void dequantize_mul_mat_vec_q4_K_sycl_reorder_esimd(const void *vx, const
|
||||
sycl::nd_range<1>(sycl::range<1>((size_t)workgroups * GGML_SYCL_DMMV_ESIMD_WG_SIZE), sycl::range<1>(GGML_SYCL_DMMV_ESIMD_WG_SIZE)),
|
||||
[=](sycl::nd_item<1> it) [[intel::sycl_explicit_simd]] {
|
||||
dequantize_mul_mat_vec_reorder_esimd<GGML_TYPE_Q4_K>(
|
||||
vx, y, dst, ncols, nrows, lmem, it);
|
||||
esimd_dmmv_row_pair_policy{ vx, dst }, y, ncols, nrows, lmem, it);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1987,7 +2042,7 @@ static void dequantize_mul_mat_vec_q5_K_sycl_reorder_esimd(const void *vx, const
|
||||
sycl::nd_range<1>(sycl::range<1>((size_t)workgroups * GGML_SYCL_DMMV_ESIMD_WG_SIZE), sycl::range<1>(GGML_SYCL_DMMV_ESIMD_WG_SIZE)),
|
||||
[=](sycl::nd_item<1> it) [[intel::sycl_explicit_simd]] {
|
||||
dequantize_mul_mat_vec_reorder_esimd<GGML_TYPE_Q5_K>(
|
||||
vx, y, dst, ncols, nrows, lmem, it);
|
||||
esimd_dmmv_row_pair_policy{ vx, dst }, y, ncols, nrows, lmem, it);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -2004,7 +2059,7 @@ static void dequantize_mul_mat_vec_q6_K_sycl_reorder_esimd(const void *vx, const
|
||||
sycl::nd_range<1>(sycl::range<1>((size_t)workgroups * GGML_SYCL_DMMV_ESIMD_WG_SIZE), sycl::range<1>(GGML_SYCL_DMMV_ESIMD_WG_SIZE)),
|
||||
[=](sycl::nd_item<1> it) [[intel::sycl_explicit_simd]] {
|
||||
dequantize_mul_mat_vec_reorder_esimd<GGML_TYPE_Q6_K>(
|
||||
vx, y, dst, ncols, nrows, lmem, it);
|
||||
esimd_dmmv_row_pair_policy{ vx, dst }, y, ncols, nrows, lmem, it);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -2045,9 +2100,9 @@ ESIMD_INLINE void q8_0_mac_stripe(
|
||||
}
|
||||
}
|
||||
|
||||
template <int WG>
|
||||
template <int WG, typename Policy>
|
||||
ESIMD_INLINE void dequantize_mul_mat_vec_q8_0_reorder_esimd(
|
||||
const void * vx, const float * y, float * dst,
|
||||
const Policy & policy, const float * y,
|
||||
const int ncols, const int nrows,
|
||||
sycl::local_accessor<float, 1> lmem,
|
||||
const sycl::nd_item<1> & it) {
|
||||
@@ -2057,84 +2112,137 @@ ESIMD_INLINE void dequantize_mul_mat_vec_q8_0_reorder_esimd(
|
||||
|
||||
const int nblk_row = ncols / QK8_0;
|
||||
const size_t nb = (size_t) nrows * nblk_row;
|
||||
const int8_t * qs = (const int8_t *) vx;
|
||||
const sycl::half * d = (const sycl::half *) (qs + nb * QK8_0);
|
||||
const int8_t * qs_a = (const int8_t *) policy.w_a();
|
||||
const int8_t * qs_b = (const int8_t *) policy.w_b();
|
||||
const sycl::half * d_a = (const sycl::half *) (qs_a + nb * QK8_0);
|
||||
const sycl::half * d_b = (const sycl::half *) (qs_b + nb * QK8_0);
|
||||
|
||||
const int tid = it.get_local_id(0);
|
||||
const int row_pair = it.get_group(0);
|
||||
const int row0 = row_pair * 2;
|
||||
const bool has_row1 = row0 + 1 < nrows;
|
||||
const int tid = it.get_local_id(0);
|
||||
const int g = it.get_group(0);
|
||||
const bool has_b = policy.has_b(g, nrows);
|
||||
|
||||
const size_t base0 = (size_t) row0 * nblk_row;
|
||||
const size_t base1 = has_row1 ? (size_t) (row0 + 1) * nblk_row : base0;
|
||||
const size_t base_a = (size_t) policy.row_a(g) * nblk_row;
|
||||
const size_t base_b = (size_t) policy.row_b(g) * nblk_row;
|
||||
|
||||
simd<float, 32> acc0 = 0.0f;
|
||||
simd<float, 32> acc1 = 0.0f;
|
||||
simd<float, 32> acc_a = 0.0f;
|
||||
simd<float, 32> acc_b = 0.0f;
|
||||
|
||||
// Each thread processes one contiguous stripe.
|
||||
int ib = 0;
|
||||
for (; ib + WG * STRIPE <= nblk_row; ib += WG * STRIPE) {
|
||||
const int b = ib + tid * STRIPE;
|
||||
simd<float, 256> y_vec = block_load<float, 256>(y + (size_t) b * QK8_0);
|
||||
q8_0_mac_stripe<STRIPE>(qs + (base0 + b) * QK8_0, qs + (base1 + b) * QK8_0,
|
||||
d + base0 + b, d + base1 + b, has_row1, y_vec, acc0, acc1);
|
||||
q8_0_mac_stripe<STRIPE>(qs_a + (base_a + b) * QK8_0, qs_b + (base_b + b) * QK8_0,
|
||||
d_a + base_a + b, d_b + base_b + b, has_b, y_vec, acc_a, acc_b);
|
||||
}
|
||||
|
||||
// Distribute remaining blocks across the work-group.
|
||||
for (int b = ib + tid; b < nblk_row; b += WG) {
|
||||
simd<float, 32> y_vec = block_load<float, 32>(y + (size_t) b * QK8_0);
|
||||
q8_0_mac_stripe<1>(qs + (base0 + b) * QK8_0, qs + (base1 + b) * QK8_0,
|
||||
d + base0 + b, d + base1 + b, has_row1, y_vec, acc0, acc1);
|
||||
q8_0_mac_stripe<1>(qs_a + (base_a + b) * QK8_0, qs_b + (base_b + b) * QK8_0,
|
||||
d_a + base_a + b, d_b + base_b + b, has_b, y_vec, acc_a, acc_b);
|
||||
}
|
||||
|
||||
lmem[tid * 2 + 0] = reduce<float>(acc0, std::plus<>{});
|
||||
lmem[tid * 2 + 1] = reduce<float>(acc1, std::plus<>{});
|
||||
lmem[tid * 2 + 0] = reduce<float>(acc_a, std::plus<>{});
|
||||
lmem[tid * 2 + 1] = reduce<float>(acc_b, std::plus<>{});
|
||||
it.barrier(sycl::access::fence_space::local_space);
|
||||
|
||||
if (tid == 0) {
|
||||
float sum0 = 0.0f;
|
||||
float sum1 = 0.0f;
|
||||
float sum_a = 0.0f;
|
||||
float sum_b = 0.0f;
|
||||
for (int p = 0; p < WG; ++p) {
|
||||
sum0 += lmem[p * 2 + 0];
|
||||
sum1 += lmem[p * 2 + 1];
|
||||
}
|
||||
dst[row0 + 0] = sum0;
|
||||
if (has_row1) {
|
||||
dst[row0 + 1] = sum1;
|
||||
sum_a += lmem[p * 2 + 0];
|
||||
sum_b += lmem[p * 2 + 1];
|
||||
}
|
||||
|
||||
policy.epilogue(g, nrows, sum_a, sum_b);
|
||||
}
|
||||
}
|
||||
|
||||
template <int WG>
|
||||
static void q8_0_esimd_launch(const void * vx, const float * y, float * dst, const int ncols,
|
||||
const int nrows, dpct::queue_ptr stream) {
|
||||
const int workgroups = (nrows + 1) / 2;
|
||||
template <int WG, typename Policy>
|
||||
static void q8_0_esimd_launch(const Policy & policy, const float * y, const int ncols, const int nrows,
|
||||
const int workgroups, dpct::queue_ptr stream) {
|
||||
stream->submit([&](sycl::handler & h) {
|
||||
sycl::local_accessor<float, 1> lmem(sycl::range<1>(WG * 2), h);
|
||||
h.parallel_for(
|
||||
sycl::nd_range<1>(sycl::range<1>((size_t) workgroups * WG), sycl::range<1>(WG)),
|
||||
[=](sycl::nd_item<1> it) [[intel::sycl_explicit_simd]] {
|
||||
dequantize_mul_mat_vec_q8_0_reorder_esimd<WG>(vx, y, dst, ncols, nrows, lmem, it);
|
||||
dequantize_mul_mat_vec_q8_0_reorder_esimd<WG>(policy, y, ncols, nrows, lmem, it);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Scale the work-group with the number of blocks per row.
|
||||
template <typename Policy>
|
||||
static void q8_0_esimd_launch_scaled(const Policy & policy, const float * y, const int ncols, const int nrows,
|
||||
const int workgroups, dpct::queue_ptr stream) {
|
||||
GGML_ASSERT(ncols % QK8_0 == 0);
|
||||
|
||||
const int nblk_row = ncols / QK8_0;
|
||||
if (nblk_row >= 64) {
|
||||
q8_0_esimd_launch<8>(policy, y, ncols, nrows, workgroups, stream);
|
||||
} else if (nblk_row >= 32) {
|
||||
q8_0_esimd_launch<4>(policy, y, ncols, nrows, workgroups, stream);
|
||||
} else if (nblk_row >= 16) {
|
||||
q8_0_esimd_launch<2>(policy, y, ncols, nrows, workgroups, stream);
|
||||
} else {
|
||||
q8_0_esimd_launch<1>(policy, y, ncols, nrows, workgroups, stream);
|
||||
}
|
||||
}
|
||||
|
||||
static void dequantize_mul_mat_vec_q8_0_sycl_reorder_esimd(const void *vx, const float *y,
|
||||
float *dst, const int ncols,
|
||||
const int nrows,
|
||||
dpct::queue_ptr stream) {
|
||||
GGML_ASSERT(ncols % QK8_0 == 0);
|
||||
q8_0_esimd_launch_scaled(esimd_dmmv_row_pair_policy{ vx, dst }, y, ncols, nrows, (nrows + 1) / 2, stream);
|
||||
}
|
||||
|
||||
// Scale the work-group with the number of blocks per row.
|
||||
const int nblk_row = ncols / QK8_0;
|
||||
if (nblk_row >= 64) {
|
||||
q8_0_esimd_launch<8>(vx, y, dst, ncols, nrows, stream);
|
||||
} else if (nblk_row >= 32) {
|
||||
q8_0_esimd_launch<4>(vx, y, dst, ncols, nrows, stream);
|
||||
} else if (nblk_row >= 16) {
|
||||
q8_0_esimd_launch<2>(vx, y, dst, ncols, nrows, stream);
|
||||
} else {
|
||||
q8_0_esimd_launch<1>(vx, y, dst, ncols, nrows, stream);
|
||||
// ESIMD fused gate+up+GLU on reordered K-quant SOA weights: one output row (gate
|
||||
// and up) per work-group, two independent 32-wide accumulators per super-block.
|
||||
// Reuses the DMMV kernels' dequant+MAC primitive (esimd_reorder_q_traits<T>::mac_pair).
|
||||
template <ggml_type T>
|
||||
static void dequantize_mul_mat_vec_glu_reorder_esimd(const void * vx, const void * vgate,
|
||||
const float * y, float * dst,
|
||||
const int ncols, const int nrows,
|
||||
const ggml_glu_op glu_op,
|
||||
dpct::queue_ptr stream) {
|
||||
GGML_ASSERT(ncols % QK_K == 0);
|
||||
const int workgroups = nrows; // one output row (gate+up) per work-group
|
||||
stream->submit([&](sycl::handler & h) {
|
||||
sycl::local_accessor<float, 1> lmem(sycl::range<1>(GGML_SYCL_DMMV_ESIMD_WG_SIZE * 2), h);
|
||||
h.parallel_for(
|
||||
sycl::nd_range<1>(sycl::range<1>((size_t) workgroups * GGML_SYCL_DMMV_ESIMD_WG_SIZE), sycl::range<1>(GGML_SYCL_DMMV_ESIMD_WG_SIZE)),
|
||||
[=](sycl::nd_item<1> it) [[intel::sycl_explicit_simd]] {
|
||||
dequantize_mul_mat_vec_reorder_esimd<T>(
|
||||
esimd_dmmv_glu_policy{ vgate, vx, dst, glu_op }, y, ncols, nrows, lmem, it);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool ggml_sycl_dequantize_mul_mat_vec_glu_reorder_esimd(enum ggml_type src0_type, enum ggml_glu_op glu_op,
|
||||
const void * vx, const void * vgate, const float * y,
|
||||
float * dst, int ncols, int nrows, dpct::queue_ptr stream) {
|
||||
switch (src0_type) {
|
||||
case GGML_TYPE_Q2_K:
|
||||
dequantize_mul_mat_vec_glu_reorder_esimd<GGML_TYPE_Q2_K>(vx, vgate, y, dst, ncols, nrows, glu_op, stream);
|
||||
return true;
|
||||
case GGML_TYPE_Q3_K:
|
||||
dequantize_mul_mat_vec_glu_reorder_esimd<GGML_TYPE_Q3_K>(vx, vgate, y, dst, ncols, nrows, glu_op, stream);
|
||||
return true;
|
||||
case GGML_TYPE_Q4_K:
|
||||
dequantize_mul_mat_vec_glu_reorder_esimd<GGML_TYPE_Q4_K>(vx, vgate, y, dst, ncols, nrows, glu_op, stream);
|
||||
return true;
|
||||
case GGML_TYPE_Q5_K:
|
||||
dequantize_mul_mat_vec_glu_reorder_esimd<GGML_TYPE_Q5_K>(vx, vgate, y, dst, ncols, nrows, glu_op, stream);
|
||||
return true;
|
||||
case GGML_TYPE_Q6_K:
|
||||
dequantize_mul_mat_vec_glu_reorder_esimd<GGML_TYPE_Q6_K>(vx, vgate, y, dst, ncols, nrows, glu_op, stream);
|
||||
return true;
|
||||
case GGML_TYPE_Q8_0:
|
||||
q8_0_esimd_launch_scaled(esimd_dmmv_glu_policy{ vgate, vx, dst, glu_op }, y, ncols, nrows, nrows, stream);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,4 +24,13 @@ void ggml_sycl_op_dequantize_mul_mat_vec(
|
||||
const int64_t src1_ncols, const int64_t src1_padded_row_size,
|
||||
const dpct::queue_ptr &stream);
|
||||
|
||||
// Fused dense-FFN mat-vec: writes glu(gate . y, up . y) for same-type reordered
|
||||
// weights (Q2_K..Q6_K, Q8_0) and a single F32 activation column, using the ESIMD DMMV kernels.
|
||||
// vx is the up weight, vgate the gate weight. Only defined when GGML_SYCL_DMMV_HAS_ESIMD;
|
||||
// returns false if the type is unhandled.
|
||||
bool ggml_sycl_dequantize_mul_mat_vec_glu_reorder_esimd(
|
||||
enum ggml_type src0_type, enum ggml_glu_op glu_op,
|
||||
const void * vx, const void * vgate, const float * y,
|
||||
float * dst, int ncols, int nrows, dpct::queue_ptr stream);
|
||||
|
||||
#endif // GGML_SYCL_DMMV_HPP
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
// mul_mat(gate) + mul_mat(up) + GLU: graph shape and tensor properties only. Backend state
|
||||
// (weight layout, split buffers, DMMV) is checked by ggml_sycl_mul_mat_glu_mmvq_fused().
|
||||
// (weight layout, split buffers, DMMV) is checked by ggml_sycl_op_mul_mat_glu_fused().
|
||||
static bool ggml_sycl_should_fuse_mul_mat_glu(const ggml_tensor * gate, const ggml_tensor * up,
|
||||
const ggml_tensor * glu) {
|
||||
// the fused epilogue implements these two; the rest fall back to the standalone GLU kernels
|
||||
@@ -32,12 +32,22 @@ static bool ggml_sycl_should_fuse_mul_mat_glu(const ggml_tensor * gate, const gg
|
||||
return false;
|
||||
}
|
||||
|
||||
// fused GEMVs walk whole QK_K super-blocks: the reorder kernel covers same-type
|
||||
// fused K-quant GEMVs walk whole QK_K super-blocks: the reorder kernel covers same-type
|
||||
// q4_K, the plain-layout kernel covers q5_K / iq4_xs pairs incl. mixed gate/up types
|
||||
const bool reorder_pair = wu->type == GGML_TYPE_Q4_K && wg->type == GGML_TYPE_Q4_K;
|
||||
const bool plain_pair = (wu->type == GGML_TYPE_Q5_K || wu->type == GGML_TYPE_IQ4_XS) &&
|
||||
(wg->type == GGML_TYPE_Q5_K || wg->type == GGML_TYPE_IQ4_XS);
|
||||
if ((!reorder_pair && !plain_pair) || wu->ne[0] % QK_K != 0) {
|
||||
// the ESIMD kernel covers same-type q2_K..q6_K and q8_0, single column only (as unfused DMMV)
|
||||
#ifdef GGML_SYCL_DMMV_HAS_ESIMD
|
||||
const bool esimd_pair = g_ggml_sycl_enable_esimd && wu->type == wg->type && act->ne[1] == 1 &&
|
||||
(wu->type == GGML_TYPE_Q2_K || wu->type == GGML_TYPE_Q3_K || wu->type == GGML_TYPE_Q4_K ||
|
||||
wu->type == GGML_TYPE_Q5_K || wu->type == GGML_TYPE_Q6_K || wu->type == GGML_TYPE_Q8_0);
|
||||
#else
|
||||
const bool esimd_pair = false;
|
||||
#endif
|
||||
// q8_0 rows are walked in 32-wide blocks, so they only need to be a multiple of QK8_0
|
||||
const int64_t row_align = esimd_pair && wu->type == GGML_TYPE_Q8_0 ? QK8_0 : QK_K;
|
||||
if ((!reorder_pair && !plain_pair && !esimd_pair) || wu->ne[0] % row_align != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,9 +46,6 @@
|
||||
# include <sycl/ext/oneapi/virtual_mem/virtual_mem.hpp>
|
||||
# define GGML_SYCL_SUPPORT_VMM
|
||||
#endif
|
||||
#if defined(__INTEL_LLVM_COMPILER)
|
||||
#define GGML_SYCL_DMMV_HAS_ESIMD
|
||||
#endif
|
||||
#include <sycl/half_type.hpp>
|
||||
|
||||
#include "ggml.h"
|
||||
@@ -899,6 +896,10 @@ static bool ggml_sycl_is_l0_discrete_gpu(int device) {
|
||||
static void memcpy_host_forward(sycl::queue &q_dst, sycl::queue &q_src, void *ptr_dst,
|
||||
const void *ptr_src, size_t size) {
|
||||
char *host_buf = (char *)malloc(size);
|
||||
if (host_buf == nullptr) {
|
||||
GGML_LOG_ERROR("%s: can't allocate %zu Bytes of memory for host staging\n", __func__, size);
|
||||
GGML_ABORT("fatal error");
|
||||
}
|
||||
q_src.memcpy(host_buf, (const char *)ptr_src, size).wait();
|
||||
q_dst.memcpy((char *)ptr_dst, host_buf, size).wait();
|
||||
free(host_buf);
|
||||
@@ -1177,12 +1178,13 @@ bool is_bmg_g31_arch(int device) {
|
||||
}
|
||||
|
||||
static size_t ggml_backend_sycl_buffer_type_get_max_size(ggml_backend_buffer_type_t buft) {
|
||||
size_t max_alloc_size = dpct::get_current_device().get_max_mem_alloc_size();
|
||||
ggml_backend_sycl_buffer_type_context * ctx = (ggml_backend_sycl_buffer_type_context *)buft->context;
|
||||
int device = ctx->device;
|
||||
// use the buft device, the current device may differ on multi-GPU systems
|
||||
size_t max_alloc_size = dpct::get_device(device).get_max_mem_alloc_size();
|
||||
if (g_ggml_sycl_host_pinned_mem_2g) {
|
||||
return std::min(max_alloc_size, (size_t) 2LL*1024*1024*1024);
|
||||
} else {
|
||||
ggml_backend_sycl_buffer_type_context * ctx = (ggml_backend_sycl_buffer_type_context *)buft->context;
|
||||
int device = ctx->device;
|
||||
if(is_bmg_g31_arch(device)) {
|
||||
//Todo, it's workaround for BMG-G31, which has a known issue with large allocations.
|
||||
//The max alloc size is reduced to 60% of the reported max alloc size.
|
||||
@@ -1398,7 +1400,12 @@ ggml_backend_sycl_split_buffer_init_tensor(ggml_backend_buffer_t buffer,
|
||||
ggml_tensor_extra_gpu * extra = new ggml_tensor_extra_gpu{};
|
||||
|
||||
ctx->tensor_extras.push_back(extra);
|
||||
ctx->streams.push_back(&(dpct::get_current_device().default_queue()));
|
||||
// consumers index the queues by device id, so size the vector once with one queue per device
|
||||
if (ctx->streams.empty()) {
|
||||
for (int i = 0; i < ggml_sycl_info().device_count; ++i) {
|
||||
ctx->streams.push_back(&(dpct::get_device(i).default_queue()));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < ggml_sycl_info().device_count; ++i) {
|
||||
int64_t row_low, row_high;
|
||||
@@ -2089,8 +2096,6 @@ struct ggml_sycl_pool_host : public ggml_sycl_pool {
|
||||
queue_ptr qptr;
|
||||
int device;
|
||||
|
||||
inline static int counter{ 0 };
|
||||
|
||||
struct ggml_sycl_buffer {
|
||||
void * ptr = nullptr;
|
||||
size_t size = 0;
|
||||
@@ -2100,6 +2105,7 @@ struct ggml_sycl_pool_host : public ggml_sycl_pool {
|
||||
static constexpr int MAX_POOL_SIZE{ 64 };
|
||||
std::vector<ggml_sycl_buffer> buffer_pool = std::vector<ggml_sycl_buffer>(MAX_POOL_SIZE);
|
||||
size_t pool_size = 0;
|
||||
int counter = 0;
|
||||
|
||||
explicit ggml_sycl_pool_host(queue_ptr qptr_, int device_) : qptr(qptr_), device(device_) {}
|
||||
|
||||
@@ -2113,41 +2119,55 @@ struct ggml_sycl_pool_host : public ggml_sycl_pool {
|
||||
b.size = 0;
|
||||
}
|
||||
}
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
void * alloc(size_t size, size_t * actual_size) override {
|
||||
if (counter == MAX_POOL_SIZE) {
|
||||
ggml_sycl_buffer b = buffer_pool[0];
|
||||
void * ptr = b.ptr;
|
||||
*actual_size = b.size;
|
||||
counter = 1;
|
||||
return ptr;
|
||||
}
|
||||
ggml_sycl_buffer & b = buffer_pool[counter];
|
||||
|
||||
if (b.ptr == nullptr) {
|
||||
void * ptr;
|
||||
|
||||
// the pool wrapped around, no slot is reserved for this allocation
|
||||
void * ptr = nullptr;
|
||||
SYCL_CHECK(CHECK_TRY_ERROR(ptr = (void *) sycl::malloc_host(size, *qptr)));
|
||||
if (!ptr) {
|
||||
GGML_LOG_ERROR("%s: can't allocate %zu Bytes of memory on host\n", __func__, size);
|
||||
return nullptr;
|
||||
}
|
||||
pool_size += size;
|
||||
pool_size += size;
|
||||
*actual_size = size;
|
||||
counter = counter + 1;
|
||||
counter = 0;
|
||||
return ptr;
|
||||
} else {
|
||||
++counter;
|
||||
b.size = size;
|
||||
return b.ptr;
|
||||
}
|
||||
ggml_sycl_buffer & b = buffer_pool[counter];
|
||||
++counter;
|
||||
|
||||
if (b.ptr != nullptr) {
|
||||
if (b.size >= size) {
|
||||
// hand out the cached buffer, free() puts it back into the pool
|
||||
void * ptr = b.ptr;
|
||||
*actual_size = b.size;
|
||||
b.ptr = nullptr;
|
||||
b.size = 0;
|
||||
return ptr;
|
||||
}
|
||||
// the cached buffer is too small, replace it
|
||||
SYCL_CHECK(CHECK_TRY_ERROR(sycl::free(b.ptr, *qptr)));
|
||||
pool_size -= b.size;
|
||||
b.ptr = nullptr;
|
||||
b.size = 0;
|
||||
}
|
||||
|
||||
void * ptr;
|
||||
SYCL_CHECK(CHECK_TRY_ERROR(ptr = (void *) sycl::malloc_host(size, *qptr)));
|
||||
if (!ptr) {
|
||||
GGML_LOG_ERROR("%s: can't allocate %zu Bytes of memory on host\n", __func__, size);
|
||||
return nullptr;
|
||||
}
|
||||
pool_size += size;
|
||||
*actual_size = size;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void free(void * ptr, size_t size) override {
|
||||
// if the pool is not completed add the pointer to it in place of the first nullptr found.
|
||||
// Otherwise do nothing, pointers will be freed once the pool is deallocated.
|
||||
// Otherwise free the buffer, all slots are cached.
|
||||
for (int i = 0; i < MAX_POOL_SIZE; ++i) {
|
||||
ggml_sycl_buffer & b = buffer_pool[i];
|
||||
if (b.ptr == nullptr) {
|
||||
@@ -2156,6 +2176,8 @@ struct ggml_sycl_pool_host : public ggml_sycl_pool {
|
||||
return;
|
||||
}
|
||||
}
|
||||
SYCL_CHECK(CHECK_TRY_ERROR(sycl::free(ptr, *qptr)));
|
||||
pool_size -= size;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3154,8 +3176,8 @@ inline void ggml_sycl_op_mul_mat_sycl(
|
||||
to_fp16_sycl(src1_ddf_i, src1_as_f16.get(), ne, stream);
|
||||
}
|
||||
const sycl::half *src1_ptr = src1->type == GGML_TYPE_F16
|
||||
? (const sycl::half *)src1->data + src1_padded_row_size
|
||||
: src1_as_f16.get();
|
||||
? (const sycl::half *)src1_ddf_i
|
||||
: src1_as_f16.get();
|
||||
|
||||
ggml_sycl_pool_alloc<sycl::half> src0_as_f16(ctx.pool());
|
||||
if (src0->type != GGML_TYPE_F16) {
|
||||
@@ -3252,6 +3274,8 @@ inline void ggml_sycl_op_sum(ggml_backend_sycl_context & ctx, ggml_tensor *dst)
|
||||
float * dst_dd = static_cast<float *>(dst->data);
|
||||
|
||||
const int64_t ne = ggml_nelements(dst->src[0]);
|
||||
// the kernel indexes in 32-bit
|
||||
GGML_ASSERT(ne <= INT32_MAX);
|
||||
|
||||
sum_rows_f32_sycl(src0_dd, dst_dd, ne, 1, main_stream);
|
||||
}
|
||||
@@ -3266,6 +3290,8 @@ inline void ggml_sycl_op_sum_rows(ggml_backend_sycl_context & ctx, ggml_tensor *
|
||||
|
||||
const int64_t ncols = dst->src[0]->ne[0];
|
||||
const int64_t nrows = ggml_nrows(dst->src[0]);
|
||||
// the kernel indexes in 32-bit
|
||||
GGML_ASSERT(ncols * nrows <= INT32_MAX);
|
||||
|
||||
sum_rows_f32_sycl(src0_dd, dst_dd, ncols, nrows, main_stream);
|
||||
}
|
||||
@@ -3282,6 +3308,8 @@ inline void ggml_sycl_op_mean(ggml_backend_sycl_context & ctx, ggml_tensor * dst
|
||||
|
||||
const int64_t ncols = dst->src[0]->ne[0];
|
||||
const int64_t nrows = ggml_nrows(dst->src[0]);
|
||||
// the kernel indexes in 32-bit
|
||||
GGML_ASSERT(ncols * nrows <= INT32_MAX);
|
||||
|
||||
sum_rows_f32_sycl(src0_dd, dst_dd, ncols, nrows, main_stream);
|
||||
|
||||
@@ -3351,6 +3379,8 @@ inline void ggml_sycl_op_argmax(ggml_backend_sycl_context & ctx, ggml_tensor * d
|
||||
|
||||
const int64_t ncols = dst->src[0]->ne[0];
|
||||
const int64_t nrows = ggml_nrows(dst->src[0]);
|
||||
// the kernel indexes in 32-bit
|
||||
GGML_ASSERT(ncols * nrows <= INT32_MAX);
|
||||
|
||||
argmax_f32_i32_sycl(src0_dd, dst_dd, ncols, nrows, main_stream);
|
||||
}
|
||||
@@ -3366,6 +3396,8 @@ inline void ggml_sycl_op_diag_mask_inf(ggml_backend_sycl_context & ctx, ggml_ten
|
||||
const int64_t ne00 = dst->src[0]->ne[0];
|
||||
const int64_t ne01 = dst->src[0]->ne[1];
|
||||
const int nrows0 = ggml_nrows(dst->src[0]);
|
||||
// the kernel indexes in 32-bit
|
||||
GGML_ASSERT(ne00 * nrows0 <= INT32_MAX);
|
||||
|
||||
const int n_past = ((int32_t *) dst->op_params)[0];
|
||||
|
||||
@@ -3444,6 +3476,9 @@ inline void ggml_sycl_op_scale(ggml_backend_sycl_context & ctx, ggml_tensor * ds
|
||||
memcpy(&scale, (float *) dst->op_params + 0, sizeof(float));
|
||||
memcpy(&bias, (float *) dst->op_params + 1, sizeof(float));
|
||||
|
||||
// the kernel indexes in 32-bit
|
||||
GGML_ASSERT(ggml_nelements(dst->src[0]) <= INT32_MAX);
|
||||
|
||||
scale_f32_sycl(src0_dd, dst_dd, scale, bias, ggml_nelements(dst->src[0]), main_stream);
|
||||
/*
|
||||
DPCT1010:87: SYCL uses exceptions to report errors and does not use the
|
||||
@@ -3524,7 +3559,6 @@ static void ggml_sycl_op_mul_mat(ggml_backend_sycl_context & ctx, const ggml_ten
|
||||
const size_t q8_1_bs = QK8_1;
|
||||
|
||||
ggml_tensor_extra_gpu * src0_extra = (ggml_tensor_extra_gpu *) src0->extra;
|
||||
ggml_tensor_extra_gpu * src1_extra = (ggml_tensor_extra_gpu *) src1->extra;
|
||||
|
||||
const bool src0_is_contiguous = ggml_is_contiguous(src0);
|
||||
const bool src1_is_contiguous = ggml_is_contiguous(src1);
|
||||
@@ -3606,7 +3640,8 @@ static void ggml_sycl_op_mul_mat(ggml_backend_sycl_context & ctx, const ggml_ten
|
||||
queue_ptr stream = ctx.stream(i, 0);
|
||||
|
||||
if (src0_is_contiguous) {
|
||||
dev[i].src0_dd = (char *) src0->data;
|
||||
// a split tensor keeps its rows in the per-device slices of the tensor extra
|
||||
dev[i].src0_dd = split ? (char *) src0_extra->data_device[i] : (char *) src0->data;
|
||||
} else {
|
||||
dev[i].src0_dd = dev[i].src0_dd_alloc.alloc(ctx.pool(i), ggml_nbytes(src0));
|
||||
}
|
||||
@@ -3702,7 +3737,8 @@ static void ggml_sycl_op_mul_mat(ggml_backend_sycl_context & ctx, const ggml_ten
|
||||
src1_ncols * src1_padded_col_size * q8_1_ts / q8_1_bs)
|
||||
.wait()));
|
||||
} else {
|
||||
const char * src1_ddf_i_source = (const char *) src1_extra->data_device[ctx.device] +
|
||||
// src1 is not split, on the main device it lives at dev[ctx.device].src1_ddf
|
||||
const char * src1_ddf_i_source = (const char *) dev[ctx.device].src1_ddf +
|
||||
(i0 * ne11 + src1_col_0) * ne10 * ggml_type_size(src1->type);
|
||||
|
||||
SYCL_CHECK(
|
||||
@@ -5248,6 +5284,12 @@ static bool ggml_sycl_mul_mat_glu_mmvq_plain(ggml_backend_sycl_context & ctx, gg
|
||||
return false;
|
||||
}
|
||||
|
||||
// the plain kernel only covers q5_K / iq4_xs pairs: bail before quantizing the activation
|
||||
const auto plain_type = [](ggml_type t) { return t == GGML_TYPE_Q5_K || t == GGML_TYPE_IQ4_XS; };
|
||||
if (!plain_type(wu->type) || !plain_type(wg->type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// log the up mat-mul: glu's own srcs are the two intermediates the fusion never materialises
|
||||
scope_op_debug_print scope_dbg_print(__func__, up, /*num_src=*/2, " : fused with gate + GLU (plain layout)");
|
||||
|
||||
@@ -5269,11 +5311,45 @@ static bool ggml_sycl_mul_mat_glu_mmvq_plain(ggml_backend_sycl_context & ctx, gg
|
||||
(int) ne11,
|
||||
/*stride_col_y=*/src1_padded_cols / QK8_1,
|
||||
/*stride_col_dst=*/(int) glu->ne[0], stream);
|
||||
GGML_UNUSED(gate);
|
||||
}
|
||||
|
||||
#ifdef GGML_SYCL_DMMV_HAS_ESIMD
|
||||
// {mul_mat(gate), mul_mat(up), GLU} on the reorder layout via the ESIMD DMMV kernel, for
|
||||
// same-type K-quant or Q8_0 pairs and a single activation column. It reads the F32 activation
|
||||
// directly, so one launch replaces three and no q8_1 quantization is needed.
|
||||
static bool ggml_sycl_mul_mat_glu_dmmv_esimd(ggml_backend_sycl_context & ctx, ggml_tensor * glu,
|
||||
ggml_tensor * gate, ggml_tensor * up, const ggml_tensor * wu,
|
||||
const ggml_tensor * wg, const ggml_tensor * act) {
|
||||
// the kernel reads the activation while writing the output; glu may reuse the dead activation's memory
|
||||
const char * act_begin = (const char *) act->data;
|
||||
const char * glu_begin = (const char *) glu->data;
|
||||
if (act_begin < glu_begin + ggml_nbytes(glu) && glu_begin < act_begin + ggml_nbytes(act)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// install the reorder (SoA) layout the kernel needs, as the unfused DMMV path would
|
||||
opt_for_reorder(&ctx, wu, act, up, mul_mat_algo::DMMV);
|
||||
opt_for_reorder(&ctx, wg, act, gate, mul_mat_algo::DMMV);
|
||||
|
||||
const auto * extra_u = static_cast<const ggml_tensor_extra_gpu *>(wu->extra);
|
||||
const auto * extra_g = static_cast<const ggml_tensor_extra_gpu *>(wg->extra);
|
||||
if (!extra_u || !extra_g || !extra_u->optimized_feature.reorder || !extra_g->optimized_feature.reorder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// log the up mat-mul: glu's own srcs are the two intermediates the fusion never materialises
|
||||
scope_op_debug_print scope_dbg_print(__func__, up, /*num_src=*/2, " : fused with gate + GLU (ESIMD)");
|
||||
|
||||
return ggml_sycl_dequantize_mul_mat_vec_glu_reorder_esimd(wu->type, ggml_get_glu_op(glu), wu->data, wg->data,
|
||||
(const float *) act->data, (float *) glu->data,
|
||||
(int) wu->ne[0], (int) wu->ne[1], ctx.stream());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Fused dense-FFN mat-vec for the {mul_mat(gate), mul_mat(up), GLU} subgraph at node_idx.
|
||||
// Returns false if it declined, in which case the caller runs the three nodes normally.
|
||||
static bool ggml_sycl_mul_mat_glu_mmvq_fused(ggml_backend_sycl_context & ctx, ggml_cgraph * cgraph, int node_idx) {
|
||||
static bool ggml_sycl_mul_mat_glu_fused(ggml_backend_sycl_context & ctx, ggml_cgraph * cgraph, int node_idx) {
|
||||
if (!ggml_sycl_can_fuse(cgraph, node_idx, { GGML_OP_MUL_MAT, GGML_OP_MUL_MAT, GGML_OP_GLU }, {})) {
|
||||
return false;
|
||||
}
|
||||
@@ -5291,6 +5367,16 @@ static bool ggml_sycl_mul_mat_glu_mmvq_fused(ggml_backend_sycl_context & ctx, gg
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef GGML_SYCL_DMMV_HAS_ESIMD
|
||||
// single-column same-type K-quant or Q8_0 pairs take the ESIMD kernel, as the unfused mul-mats
|
||||
// would via DMMV; if the reorder layout could not be installed, fall through to mmvq
|
||||
if (g_ggml_sycl_enable_esimd && wu->type == wg->type && act->ne[1] == 1 &&
|
||||
ggml_sycl_supports_reorder_esimd(wu->type) &&
|
||||
ggml_sycl_mul_mat_glu_dmmv_esimd(ctx, glu, gate, up, wu, wg, act)) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// with DMMV prioritised the unfused path would not have gone through mmvq at all
|
||||
if (g_ggml_sycl_prioritize_dmmv) {
|
||||
return false;
|
||||
@@ -6554,7 +6640,7 @@ static void ggml_backend_sycl_graph_compute_impl(ggml_backend_sycl_context * syc
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node->op == GGML_OP_MUL_MAT && ggml_sycl_mul_mat_glu_mmvq_fused(*sycl_ctx, cgraph, i)) {
|
||||
if (node->op == GGML_OP_MUL_MAT && ggml_sycl_mul_mat_glu_fused(*sycl_ctx, cgraph, i)) {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
@@ -6860,7 +6946,9 @@ static bool do_ggml_backend_sycl_device_supports_op(ggml_backend_dev_t dev, cons
|
||||
case GGML_GLU_OP_GEGLU_ERF:
|
||||
case GGML_GLU_OP_GEGLU_QUICK:
|
||||
case GGML_GLU_OP_SWIGLU_CLAMP:
|
||||
return ggml_is_contiguous_1(op->src[0]);
|
||||
// the fused GLU kernels only have f32/f16 instantiations
|
||||
return ggml_is_contiguous_1(op->src[0]) &&
|
||||
(op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -7123,7 +7211,8 @@ static bool do_ggml_backend_sycl_device_supports_op(ggml_backend_dev_t dev, cons
|
||||
case GGML_OP_RMS_NORM_BACK:
|
||||
return ggml_is_contiguous(op->src[0]);
|
||||
case GGML_OP_SCALE:
|
||||
return true;
|
||||
// the scale kernel is f32 only
|
||||
return op->type == GGML_TYPE_F32 && op->src[0]->type == GGML_TYPE_F32;
|
||||
case GGML_OP_CONT:
|
||||
return true;
|
||||
case GGML_OP_TRI:
|
||||
@@ -7156,12 +7245,19 @@ static bool do_ggml_backend_sycl_device_supports_op(ggml_backend_dev_t dev, cons
|
||||
#endif
|
||||
) &&
|
||||
op->src[0]->type == op->type;
|
||||
case GGML_OP_CONV_3D:
|
||||
case GGML_OP_CONV_3D: {
|
||||
const int32_t * opts = (const int32_t *) op->op_params;
|
||||
// zero kernel volume, channels or output makes the im2col GEMM degenerate (k == 0 or m == 0)
|
||||
// and MKL BLAS rejects it; let those fall back to the CPU backend
|
||||
const int64_t knl_n_total = op->src[0]->ne[0] * op->src[0]->ne[1] * op->src[0]->ne[2] * opts[9];
|
||||
const int64_t patch_total = op->ne[0] * op->ne[1] * op->ne[2] * opts[10];
|
||||
return op->type == GGML_TYPE_F32 &&
|
||||
(op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16) &&
|
||||
op->src[1]->type == GGML_TYPE_F32 &&
|
||||
ggml_is_contiguous(op->src[0]) &&
|
||||
ggml_is_contiguous(op->src[1]);
|
||||
ggml_is_contiguous(op->src[1]) &&
|
||||
knl_n_total > 0 && patch_total > 0;
|
||||
}
|
||||
case GGML_OP_SUM:
|
||||
case GGML_OP_SUM_ROWS:
|
||||
case GGML_OP_MEAN:
|
||||
|
||||
@@ -73,6 +73,8 @@ void ggml_sycl_roll(ggml_backend_sycl_context & ctx, ggml_tensor *dst) {
|
||||
const ggml_tensor *src = dst->src[0];
|
||||
GGML_ASSERT(src && src->type == GGML_TYPE_F32);
|
||||
|
||||
// the kernel indexes in 32-bit
|
||||
GGML_ASSERT(dst->ne[0] * dst->ne[1] * dst->ne[2] * dst->ne[3] <= INT32_MAX);
|
||||
const int ne0 = (int) dst->ne[0];
|
||||
const int ne1 = (int) dst->ne[1];
|
||||
const int ne2 = (int) dst->ne[2];
|
||||
|
||||
@@ -15872,6 +15872,7 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
|
||||
return ggml_is_contiguous(op->src[0]) && op->src[0]->type == GGML_TYPE_F32
|
||||
&& ggml_is_contiguous(op->src[1]) && op->src[1]->type == GGML_TYPE_F32;
|
||||
case GGML_OP_SUM:
|
||||
return op->src[0]->type == GGML_TYPE_F32 && ggml_is_contiguously_allocated(op->src[0]);
|
||||
case GGML_OP_SUM_ROWS:
|
||||
case GGML_OP_MEAN:
|
||||
return op->src[0]->type == GGML_TYPE_F32 && ggml_is_contiguous_rows(op->src[0]);
|
||||
|
||||
@@ -4781,6 +4781,8 @@ static bool ggml_backend_webgpu_device_supports_op(ggml_backend_dev_t dev, const
|
||||
supports_op = op->type == GGML_TYPE_F32 && src0->type == op->type;
|
||||
break;
|
||||
case GGML_OP_SUM:
|
||||
supports_op = op->type == GGML_TYPE_F32 && src0->type == op->type && ggml_is_contiguously_allocated(src0);
|
||||
break;
|
||||
case GGML_OP_SUM_ROWS:
|
||||
supports_op = op->type == GGML_TYPE_F32 && src0->type == op->type && ggml_is_contiguous_rows(src0);
|
||||
break;
|
||||
|
||||
@@ -573,6 +573,7 @@ class MODEL_ARCH(IntEnum):
|
||||
QWEN3MOE = auto()
|
||||
QWEN3NEXT = auto()
|
||||
HRM_TEXT = auto()
|
||||
BEAM = auto()
|
||||
QWEN3VL = auto()
|
||||
QWEN3VLMOE = auto()
|
||||
QWEN35 = auto()
|
||||
@@ -700,6 +701,7 @@ class MODEL_ARCH(IntEnum):
|
||||
QWEN3TTS = auto()
|
||||
POCKETTTS = auto()
|
||||
K2HORIZON = auto()
|
||||
KOLIBRI1 = auto()
|
||||
|
||||
|
||||
class VISION_PROJECTOR_TYPE(IntEnum):
|
||||
@@ -1388,6 +1390,7 @@ MODEL_ARCH_NAMES: dict[MODEL_ARCH, str] = {
|
||||
MODEL_ARCH.QWEN3MOE: "qwen3moe",
|
||||
MODEL_ARCH.QWEN3NEXT: "qwen3next",
|
||||
MODEL_ARCH.HRM_TEXT: "hrm_text",
|
||||
MODEL_ARCH.BEAM: "beam",
|
||||
MODEL_ARCH.QWEN3VL: "qwen3vl",
|
||||
MODEL_ARCH.QWEN3VLMOE: "qwen3vlmoe",
|
||||
MODEL_ARCH.QWEN35: "qwen35",
|
||||
@@ -1516,6 +1519,7 @@ MODEL_ARCH_NAMES: dict[MODEL_ARCH, str] = {
|
||||
MODEL_ARCH.QWEN3TTS: "qwen3tts",
|
||||
MODEL_ARCH.POCKETTTS: "pockettts",
|
||||
MODEL_ARCH.K2HORIZON: "k2-horizon",
|
||||
MODEL_ARCH.KOLIBRI1: "kolibri1",
|
||||
}
|
||||
|
||||
VISION_PROJECTOR_TYPE_NAMES: dict[VISION_PROJECTOR_TYPE, str] = {
|
||||
@@ -2925,6 +2929,7 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
|
||||
MODEL_TENSOR.FFN_GATE,
|
||||
MODEL_TENSOR.FFN_DOWN,
|
||||
MODEL_TENSOR.FFN_UP,
|
||||
MODEL_TENSOR.DENSE_2_OUT, # voyage-4-nano
|
||||
],
|
||||
MODEL_ARCH.QWEN3MOE: [
|
||||
MODEL_TENSOR.TOKEN_EMBD,
|
||||
@@ -2981,6 +2986,30 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
|
||||
MODEL_TENSOR.NEXTN_SHARED_HEAD_HEAD,
|
||||
MODEL_TENSOR.NEXTN_SHARED_HEAD_NORM,
|
||||
],
|
||||
MODEL_ARCH.BEAM: [
|
||||
MODEL_TENSOR.TOKEN_EMBD,
|
||||
MODEL_TENSOR.OUTPUT_NORM,
|
||||
MODEL_TENSOR.OUTPUT,
|
||||
MODEL_TENSOR.ATTN_NORM,
|
||||
MODEL_TENSOR.ATTN_POST_NORM,
|
||||
MODEL_TENSOR.ATTN_Q,
|
||||
MODEL_TENSOR.ATTN_K,
|
||||
MODEL_TENSOR.ATTN_V,
|
||||
MODEL_TENSOR.ATTN_OUT,
|
||||
MODEL_TENSOR.ATTN_Q_NORM,
|
||||
MODEL_TENSOR.ATTN_K_NORM,
|
||||
MODEL_TENSOR.ATTN_GATE,
|
||||
MODEL_TENSOR.FFN_PRE_NORM,
|
||||
MODEL_TENSOR.FFN_POST_NORM,
|
||||
MODEL_TENSOR.FFN_GATE,
|
||||
MODEL_TENSOR.FFN_DOWN,
|
||||
MODEL_TENSOR.FFN_UP,
|
||||
MODEL_TENSOR.FFN_GATE_INP,
|
||||
MODEL_TENSOR.FFN_EXP_PROBS_B,
|
||||
MODEL_TENSOR.FFN_GATE_EXP,
|
||||
MODEL_TENSOR.FFN_DOWN_EXP,
|
||||
MODEL_TENSOR.FFN_UP_EXP,
|
||||
],
|
||||
MODEL_ARCH.HRM_TEXT: [
|
||||
MODEL_TENSOR.TOKEN_EMBD,
|
||||
MODEL_TENSOR.OUTPUT,
|
||||
@@ -5950,6 +5979,11 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
|
||||
MODEL_TENSOR.FFN_GATE_EXP,
|
||||
MODEL_TENSOR.FFN_DOWN_EXP,
|
||||
MODEL_TENSOR.FFN_UP_EXP,
|
||||
# NextN/MTP tensors
|
||||
MODEL_TENSOR.NEXTN_EH_PROJ,
|
||||
MODEL_TENSOR.NEXTN_ENORM,
|
||||
MODEL_TENSOR.NEXTN_HNORM,
|
||||
MODEL_TENSOR.NEXTN_SHARED_HEAD_NORM,
|
||||
],
|
||||
MODEL_ARCH.NANBEIGE: [
|
||||
MODEL_TENSOR.TOKEN_EMBD,
|
||||
@@ -6022,6 +6056,29 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
|
||||
MODEL_TENSOR.FFN_UP_SHEXP,
|
||||
MODEL_TENSOR.FFN_DOWN_SHEXP,
|
||||
],
|
||||
MODEL_ARCH.KOLIBRI1: [
|
||||
MODEL_TENSOR.TOKEN_EMBD,
|
||||
MODEL_TENSOR.OUTPUT_NORM,
|
||||
MODEL_TENSOR.OUTPUT,
|
||||
MODEL_TENSOR.ATTN_NORM,
|
||||
MODEL_TENSOR.ATTN_POST_NORM,
|
||||
MODEL_TENSOR.ATTN_Q,
|
||||
MODEL_TENSOR.ATTN_K,
|
||||
MODEL_TENSOR.ATTN_V,
|
||||
MODEL_TENSOR.ATTN_OUT,
|
||||
MODEL_TENSOR.ATTN_Q_NORM,
|
||||
MODEL_TENSOR.ATTN_K_NORM,
|
||||
MODEL_TENSOR.FFN_NORM,
|
||||
MODEL_TENSOR.FFN_POST_NORM,
|
||||
MODEL_TENSOR.FFN_GATE_INP,
|
||||
MODEL_TENSOR.FFN_EXP_PROBS_B,
|
||||
MODEL_TENSOR.FFN_GATE_EXP,
|
||||
MODEL_TENSOR.FFN_UP_EXP,
|
||||
MODEL_TENSOR.FFN_DOWN_EXP,
|
||||
MODEL_TENSOR.FFN_GATE_SHEXP,
|
||||
MODEL_TENSOR.FFN_UP_SHEXP,
|
||||
MODEL_TENSOR.FFN_DOWN_SHEXP,
|
||||
],
|
||||
}
|
||||
|
||||
# tensors that will not be serialized
|
||||
@@ -6278,6 +6335,7 @@ class DecisionType:
|
||||
VLLM_D3 = "vllm-d3" # same as pplx-decider, non-causal attention
|
||||
LFM2_D1 = "lfm2-d1" # same as openjev, the labels depend on the question type
|
||||
LFM2_D1_OMNI = "lfm2-d1-omni" # same head as laya on a bidirectional LFM2 trunk, other prompt layout
|
||||
GRANITE_SWITCH = "granite-switch" # same as openjev, run by the decision aLoRA of granite switch
|
||||
|
||||
|
||||
class VisionProjectorType:
|
||||
|
||||
@@ -654,6 +654,59 @@ class TQ2_0(__Quant, qtype=GGMLQuantizationType.TQ2_0):
|
||||
return (d * qs.astype(np.float32))
|
||||
|
||||
|
||||
class Q1_0(__Quant, qtype=GGMLQuantizationType.Q1_0):
|
||||
@classmethod
|
||||
def quantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
||||
# sequential sum like the C reference, np.sum uses pairwise summation and can round differently
|
||||
d = np.cumsum(abs(blocks), axis=-1, dtype=np.float32)[..., -1:] / np.float32(cls.block_size)
|
||||
qs = np.packbits(blocks >= 0, axis=-1, bitorder="little")
|
||||
|
||||
d = d.astype(np.float16).view(np.uint8)
|
||||
|
||||
return np.concatenate([d, qs], axis=-1)
|
||||
|
||||
@classmethod
|
||||
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
||||
d, qs = np.hsplit(blocks, [2])
|
||||
|
||||
d = d.view(np.float16).astype(np.float32)
|
||||
|
||||
qs = np.unpackbits(qs, axis=-1, bitorder="little")
|
||||
|
||||
return np.where(qs != 0, d, -d)
|
||||
|
||||
|
||||
class Q2_0(__Quant, qtype=GGMLQuantizationType.Q2_0):
|
||||
@classmethod
|
||||
def quantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
||||
n_blocks = blocks.shape[0]
|
||||
|
||||
d = abs(blocks).max(axis=-1, keepdims=True)
|
||||
with np.errstate(divide="ignore"):
|
||||
id = np.where(d == 0, 0, 1 / d)
|
||||
qs = (np_roundf(blocks * id) + 1).clip(0, 3).astype(np.uint8)
|
||||
|
||||
qs = qs.reshape((n_blocks, -1, 4)) << np.array([0, 2, 4, 6], dtype=np.uint8)
|
||||
qs = qs[..., 0] | qs[..., 1] | qs[..., 2] | qs[..., 3]
|
||||
|
||||
d = d.astype(np.float16).view(np.uint8)
|
||||
|
||||
return np.concatenate([d, qs], axis=-1)
|
||||
|
||||
@classmethod
|
||||
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
||||
n_blocks = blocks.shape[0]
|
||||
|
||||
d, qs = np.hsplit(blocks, [2])
|
||||
|
||||
d = d.view(np.float16).astype(np.float32)
|
||||
|
||||
qs = qs.reshape((n_blocks, -1, 1)) >> np.array([0, 2, 4, 6], dtype=np.uint8)
|
||||
qs = (qs & 0x03).reshape((n_blocks, -1)).astype(np.int8) - np.int8(1)
|
||||
|
||||
return (d * qs.astype(np.float32))
|
||||
|
||||
|
||||
class MXFP4(__Quant, qtype=GGMLQuantizationType.MXFP4):
|
||||
# e2m1 values (doubled)
|
||||
# ref: https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf
|
||||
|
||||
@@ -19,71 +19,27 @@ import gguf
|
||||
logger = logging.getLogger("gguf-convert-endian")
|
||||
|
||||
|
||||
def byteswap_noop(tensor, block_offs):
|
||||
# this function is used when byteswapping is not needed
|
||||
pass
|
||||
|
||||
|
||||
def byteswap_q4_0(tensor, block_offs):
|
||||
# Each block_q4_0 consists of an f16 delta (scaling factor) followed by 16 int8 quantizations.
|
||||
|
||||
# Byte-Swap f16 sized delta field
|
||||
delta = tensor.data[block_offs:block_offs + 2].view(dtype=np.uint16)
|
||||
delta.byteswap(inplace=True)
|
||||
|
||||
|
||||
def byteswap_q8_0(tensor, block_offs):
|
||||
# Each block_q8_0 consists of an f16 delta (scaling factor) followed by 32 int8 quantizations.
|
||||
|
||||
# Byte-Swap f16 sized delta field
|
||||
delta = tensor.data[block_offs:block_offs + 2].view(dtype=np.uint16)
|
||||
delta.byteswap(inplace=True)
|
||||
|
||||
|
||||
def byteswap_q4_k(tensor, block_offs):
|
||||
# Each block_q4_k consists of 2 f16 values followed by 140 int8 values.
|
||||
|
||||
# Byte-Swap f16 sized fields
|
||||
delta = tensor.data[block_offs:block_offs + 2].view(dtype=np.uint16)
|
||||
delta.byteswap(inplace=True)
|
||||
|
||||
delta = tensor.data[block_offs + 2:block_offs + 4].view(dtype=np.uint16)
|
||||
delta.byteswap(inplace=True)
|
||||
|
||||
|
||||
def byteswap_q6_k(tensor, block_offs):
|
||||
# Each block_q6_k consists of 208 int8 values followed by 1 f16 value.
|
||||
|
||||
# Byte-Swap f16 sized field
|
||||
delta = tensor.data[block_offs + 208:block_offs + 210].view(dtype=np.uint16)
|
||||
delta.byteswap(inplace=True)
|
||||
|
||||
|
||||
def byteswap_q1_0(tensor, block_offs):
|
||||
# Each block_q1_0 consists of an f16 delta followed by 16 int8 quantizations.
|
||||
|
||||
# Byte-Swap f16 sized delta field
|
||||
delta = tensor.data[block_offs:block_offs + 2].view(dtype=np.uint16)
|
||||
delta.byteswap(inplace=True)
|
||||
|
||||
|
||||
def byteswap_tq2_0(tensor, block_offs):
|
||||
# Each block_tq2_0 consists of 64 int8 values followed by 1 f16 value.
|
||||
|
||||
# Byte-Swap f16 sized field
|
||||
delta = tensor.data[block_offs + 64:block_offs + 66].view(dtype=np.uint16)
|
||||
delta.byteswap(inplace=True)
|
||||
|
||||
|
||||
byteswap_tensors = {
|
||||
gguf.GGMLQuantizationType.Q1_0: byteswap_q1_0,
|
||||
gguf.GGMLQuantizationType.Q4_0: byteswap_q4_0,
|
||||
gguf.GGMLQuantizationType.Q8_0: byteswap_q8_0,
|
||||
gguf.GGMLQuantizationType.Q4_K: byteswap_q4_k,
|
||||
gguf.GGMLQuantizationType.Q6_K: byteswap_q6_k,
|
||||
gguf.GGMLQuantizationType.TQ2_0: byteswap_tq2_0,
|
||||
gguf.GGMLQuantizationType.MXFP4: byteswap_noop,
|
||||
gguf.GGMLQuantizationType.NVFP4: byteswap_noop,
|
||||
# byte offset and type of the multi-byte fields in each block, all other fields are single bytes
|
||||
# use the width ggml reads the field with, e.g. Q5_0 qh is uint8_t[4] but read as uint32_t
|
||||
byteswap_tensors: dict[gguf.GGMLQuantizationType, tuple[tuple[int, type[np.unsignedinteger]], ...]] = {
|
||||
gguf.GGMLQuantizationType.Q1_0: ((0, np.uint16),), # d
|
||||
gguf.GGMLQuantizationType.Q2_0: ((0, np.uint16),), # d
|
||||
gguf.GGMLQuantizationType.Q4_0: ((0, np.uint16),), # d
|
||||
gguf.GGMLQuantizationType.Q4_1: ((0, np.uint16), (2, np.uint16)), # d, m
|
||||
gguf.GGMLQuantizationType.Q5_0: ((0, np.uint16), (2, np.uint32)), # d, qh
|
||||
gguf.GGMLQuantizationType.Q5_1: ((0, np.uint16), (2, np.uint16), (4, np.uint32)), # d, m, qh
|
||||
gguf.GGMLQuantizationType.Q8_0: ((0, np.uint16),), # d
|
||||
gguf.GGMLQuantizationType.Q2_K: ((80, np.uint16), (82, np.uint16)), # d, dmin
|
||||
gguf.GGMLQuantizationType.Q3_K: ((108, np.uint16),), # d
|
||||
gguf.GGMLQuantizationType.Q4_K: ((0, np.uint16), (2, np.uint16)), # d, dmin
|
||||
gguf.GGMLQuantizationType.Q5_K: ((0, np.uint16), (2, np.uint16)), # d, dmin
|
||||
gguf.GGMLQuantizationType.Q6_K: ((208, np.uint16),), # d
|
||||
gguf.GGMLQuantizationType.IQ4_NL: ((0, np.uint16),), # d
|
||||
gguf.GGMLQuantizationType.IQ4_XS: ((0, np.uint16), (2, np.uint16)), # d, scales_h
|
||||
gguf.GGMLQuantizationType.TQ1_0: ((52, np.uint16),), # d
|
||||
gguf.GGMLQuantizationType.TQ2_0: ((64, np.uint16),), # d
|
||||
gguf.GGMLQuantizationType.MXFP4: (), # noop
|
||||
gguf.GGMLQuantizationType.NVFP4: (), # noop
|
||||
}
|
||||
|
||||
|
||||
@@ -140,28 +96,12 @@ def convert_byteorder(reader: gguf.GGUFReader, args: argparse.Namespace) -> None
|
||||
|
||||
# Byte-swap tensor data if necessary
|
||||
if tensor.tensor_type in byteswap_tensors:
|
||||
# first flatten structure
|
||||
oldshape = tensor.data.shape
|
||||
newshape = 1
|
||||
for i in tensor.data.shape:
|
||||
newshape *= i
|
||||
|
||||
tensor.data.resize(newshape)
|
||||
|
||||
block_size = gguf.constants.GGML_QUANT_SIZES[tensor.tensor_type][1]
|
||||
byteswap_func = byteswap_tensors[tensor.tensor_type]
|
||||
|
||||
n_blocks = len(tensor.data) // block_size
|
||||
for block_num in (inner_pbar := tqdm(range(n_blocks), desc="Byte-swapping Blocks", leave=False)):
|
||||
block_offs = block_num * block_size
|
||||
|
||||
byteswap_func(tensor, block_offs)
|
||||
|
||||
if block_num % 100000 == 0:
|
||||
inner_pbar.set_description(f"Byte-swapping Blocks [{(n_blocks - block_num) // n_blocks}]")
|
||||
|
||||
# restore old shape in case it's ever used
|
||||
tensor.data.resize(oldshape)
|
||||
# one row per block, then swap each field of all blocks at once
|
||||
type_size = gguf.constants.GGML_QUANT_SIZES[tensor.tensor_type][1]
|
||||
blocks = tensor.data.reshape(-1, type_size, copy=False)
|
||||
for offs, dtype in byteswap_tensors[tensor.tensor_type]:
|
||||
size = np.dtype(dtype).itemsize
|
||||
blocks[:, offs:offs + size].view(dtype=dtype)[:, 0].byteswap(inplace=True)
|
||||
elif tensor.tensor_type == gguf.GGMLQuantizationType.BF16:
|
||||
# Special case for BF16
|
||||
# It is 2-bytes data, but by default view loads it as 1-byte data.
|
||||
@@ -173,6 +113,7 @@ def convert_byteorder(reader: gguf.GGUFReader, args: argparse.Namespace) -> None
|
||||
|
||||
pbar.set_description(log_message)
|
||||
|
||||
reader.data.flush()
|
||||
logger.info("* Completion")
|
||||
|
||||
|
||||
|
||||
@@ -416,6 +416,7 @@ class TensorNameMap:
|
||||
"model.layers.{bid}.post_attention_layernorm", # gemma2 olmo2 # ge
|
||||
"layers.{bid}.post_attention_layernorm", # embeddinggemma
|
||||
"model.layers.{bid}.post_self_attn_layernorm", # glm-4-0414
|
||||
"model.layers.{bid}.post_attn_norm", # kolibri1
|
||||
"model.layers.layers.{bid}.post_mixer_norm.weight", # plamo2
|
||||
),
|
||||
|
||||
@@ -498,6 +499,7 @@ class TensorNameMap:
|
||||
"model.layers.{bid}.post_feedforward_layernorm", # gemma2 olmo2
|
||||
"layers.{bid}.post_feedforward_layernorm", # embeddinggemma
|
||||
"model.layers.{bid}.post_mlp_layernorm", # glm-4-0414
|
||||
"model.layers.{bid}.post_ffn_norm", # kolibri1
|
||||
"model.layers.layers.{bid}.post_mlp_norm.weight", # plamo2
|
||||
"model.layers.{bid}.feed_forward.up_proj",
|
||||
"model.layers.{bid}.post_moe_norm", # grok-2
|
||||
@@ -542,6 +544,7 @@ class TensorNameMap:
|
||||
"model.layers.{bid}.mlp.moe_statics.e_score_correction", # ernie4.5-moe
|
||||
"model.layers.{bid}.mlp.gate.expert_bias", # bailingmoe2
|
||||
"model.layers.{bid}.mlp.expert_bias", # afmoe
|
||||
"model.layers.{bid}.moe.router.expert_bias", # kolibri1
|
||||
"model.layers.{bid}.feed_forward.expert_bias", # lfm2moe
|
||||
"model.layers.{bid}.block_sparse_moe.e_score_correction", # minimax-m2
|
||||
"backbone.layers.{bid}.mixer.gate.e_score_correction", # nemotron-h-moe
|
||||
|
||||
@@ -69,6 +69,7 @@ class GGMLQuants:
|
||||
"tq1_0", "tq2_0",
|
||||
"mxfp4",
|
||||
"nvfp4",
|
||||
"q1_0", "q2_0",
|
||||
"iq2_xxs", "iq2_xs", "iq2_s", "iq3_xxs", "iq3_s", "iq1_s", "iq1_m",
|
||||
"iq4_nl", "iq4_xs",
|
||||
):
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# Environment:
|
||||
# SKIP_APIABI_CHECK: set to 1 or true to skip
|
||||
# APIABI_COMPARE_TAG: equivalent to --tag (used by CI)
|
||||
# APIABI_REPORT_DIR: preserve reports and dumps in this directory, even if the comparison fails
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -113,4 +114,15 @@ cmake --build "$BUILD_NEW" --parallel "$(nproc)"
|
||||
|
||||
(cd "$WORKTREE_DIR" && "$SCRIPT_DIR/check-apiabi-compat.sh" --include-path ggml/include --generate "$BUILD_OLD" "${LIBS[@]}")
|
||||
(cd "$REPO_ROOT" && "$SCRIPT_DIR/check-apiabi-compat.sh" --include-path ggml/include --generate "$BUILD_NEW" "${LIBS[@]}")
|
||||
(cd "$REPO_ROOT" && "$SCRIPT_DIR/check-apiabi-compat.sh" "${CHECK_FLAGS[@]}" --check "$BUILD_OLD" "$BUILD_NEW")
|
||||
|
||||
CHECK_RESULT=0
|
||||
(cd "$REPO_ROOT" && "$SCRIPT_DIR/check-apiabi-compat.sh" "${CHECK_FLAGS[@]}" --check "$BUILD_OLD" "$BUILD_NEW") || CHECK_RESULT=$?
|
||||
|
||||
if [[ -n "${APIABI_REPORT_DIR:-}" ]]; then
|
||||
mkdir -p "$APIABI_REPORT_DIR/old" "$APIABI_REPORT_DIR/new"
|
||||
cp -R "$BUILD_OLD/apiabi/." "$APIABI_REPORT_DIR/old/"
|
||||
cp -R "$BUILD_NEW/apiabi/." "$APIABI_REPORT_DIR/new/"
|
||||
echo "API/ABI reports saved to $APIABI_REPORT_DIR"
|
||||
fi
|
||||
|
||||
exit "$CHECK_RESULT"
|
||||
|
||||
@@ -72,19 +72,12 @@ dir=$(basename $(pwd))
|
||||
git branch -D pr/$PR 2> /dev/null
|
||||
git worktree add -b pr/$PR ../$dir-pr-$PR pr/$PR/$head_ref 2> /dev/null
|
||||
|
||||
og_path=$(pwd)
|
||||
wt_path=$(cd ../$dir-pr-$PR && pwd)
|
||||
|
||||
echo "git worktree created in $wt_path"
|
||||
|
||||
cd $wt_path
|
||||
|
||||
# pi agent setup in the worktree
|
||||
if [[ -f "$og_path/.pi/SYSTEM.md" && ! -f ".pi/SYSTEM.md" ]]; then
|
||||
mkdir -p .pi
|
||||
ln -sfn "$og_path/.pi/SYSTEM.md" .pi/SYSTEM.md
|
||||
fi
|
||||
|
||||
git branch --set-upstream-to=pr/$PR/$head_ref
|
||||
git pull --ff-only || {
|
||||
echo "error: failed to pull pr/$PR"
|
||||
|
||||
@@ -39,19 +39,12 @@ dir_suffix=$(echo "$BRANCH" | tr '/' '-')
|
||||
|
||||
git worktree add "../$dir-$dir_suffix" "$BRANCH" || git worktree add -b "$BRANCH" "../$dir-$dir_suffix" HEAD
|
||||
|
||||
og_path=$(pwd)
|
||||
wt_path=$(cd "../$dir-$dir_suffix" && pwd)
|
||||
|
||||
echo "git worktree created in $wt_path"
|
||||
|
||||
cd "$wt_path"
|
||||
|
||||
# pi agent setup in the worktree
|
||||
if [[ -f "$og_path/.pi/SYSTEM.md" && ! -f ".pi/SYSTEM.md" ]]; then
|
||||
mkdir -p .pi
|
||||
ln -sfn "$og_path/.pi/SYSTEM.md" .pi/SYSTEM.md
|
||||
fi
|
||||
|
||||
if [[ $# -eq 2 ]]; then
|
||||
echo "executing: $2"
|
||||
eval "$2"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: slice-model
|
||||
description: Build a small test "slice" of a HF model (fewer layers/experts) by downloading only the needed tensors via HTTP range requests. Use when the user needs a small version of a big model to test conversion or inference.
|
||||
---
|
||||
|
||||
# Slice a HF model for testing
|
||||
|
||||
[slice_model.py](slice_model.py) is a **reference** implementation. Read it first: its docstring and `--help` explain how it works and what it does. Its heuristics only cover common layouts, and new models often have tensor names, shapes or config keys it does not handle. Do not run it blindly. If the model needs special handling, copy it to `tmp/slice_<model>.py` and adapt the copy, or write a new script with the same approach.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Ask the user for the repo, what the slice must exercise (e.g. a MoE layer, each layer type of a hybrid model, MTP, the vision encoder), the size budget and the output dir.
|
||||
2. Inspect the model: run the script with `--inspect`, read `config.json`, and read the HF modeling code if anything is unclear. Check the plan against the pitfalls below.
|
||||
3. Build the slice, then read the config patches in the log and check each one.
|
||||
4. Verify: load the slice in transformers (no missing/unexpected weight warnings, no NaN logits), then convert it with `convert_hf_to_gguf.py` and run it. For numerical checks, see `examples/model-conversion/`.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Layer counts in nested or unknown config keys, or several towers with the same layer count.
|
||||
- Hybrid layer patterns (`layer_types`, `full_attention_interval`, ...): keep at least one layer of each type, and check the patched config still matches the kept layers.
|
||||
- Dense leading layers (`first_k_dense_replace`) and MTP/nextn layers numbered after the regular layers.
|
||||
- Every per-expert tensor must be sliced to the same expert count: experts, router weight and biases. Tensors with the expert dim not at dim 0 are not contiguous.
|
||||
- Routing constraints: top-k, expert groups, shared experts, zero-compute experts.
|
||||
- Quantized weights (FP8, MXFP4): slice the scale tensors in the same way as their weight.
|
||||
- Sizes derived in modeling code instead of stored in the config: follow the exact formula if you shrink them.
|
||||
- Slicing the vocab without the tokenizer breaks conversion.
|
||||
@@ -0,0 +1,558 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
REFERENCE ONLY - build a small test "slice" of a HF safetensors model without downloading the full weights.
|
||||
|
||||
The heuristics below (layer/expert detection, config patching) cover common layouts only. New models often use tensor names, shapes or config keys that this script does not know about. Always run --inspect first, check the plan, and adapt the script to the model.
|
||||
|
||||
How it works: each safetensors shard starts with an 8-byte header length + JSON header, which gives the exact byte span of every tensor. We fetch only the headers, then use HTTP range requests to fetch the tensors we keep:
|
||||
1. keep only some layers (per numeric "family" in tensor names whose count matches a layer count in config.json), renumbered to 0..n-1
|
||||
2. keep only the first E experts (per-expert tensors are dropped, stacked expert tensors and router weights are row-sliced along dim 0)
|
||||
3. optionally keep only the first V rows of vocab-sized tensors (off by default, see --vocab-size)
|
||||
config.json is patched to match. Other small repo files are copied as-is.
|
||||
|
||||
Usage:
|
||||
python slice_model.py Qwen/Qwen3-0.6B --inspect
|
||||
python slice_model.py Qwen/Qwen3-30B-A3B --num-layers 2 --num-experts 8 --dry-run
|
||||
python slice_model.py Qwen/Qwen3-30B-A3B --keep-layers 0,3 --output ../models/qwen3moe-slice
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import concurrent.futures as cf
|
||||
import fnmatch
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from huggingface_hub import HfApi, get_token, hf_hub_url
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||
logger = logging.getLogger("slice_model")
|
||||
|
||||
INDEX_FILE = "model.safetensors.index.json"
|
||||
CONFIG_FILE = "config.json"
|
||||
|
||||
# files not worth copying into a slice
|
||||
AUX_BLACKLIST = [
|
||||
# weights in other formats (the slice replaces them)
|
||||
"*.bin", "*.pth", "*.pt", "*.ckpt", "*.h5", "*.msgpack",
|
||||
"*.onnx", "*.tflite", "*.mlmodel", "*.mlpackage/*", "*.gguf", "*.nemo",
|
||||
"onnx/*", "openvino/*", "coreml/*", "original/*", "metal/*",
|
||||
# media / demo assets
|
||||
"*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.avif", "*.svg",
|
||||
"*.mp4", "*.webm", "*.mov", "*.wav", "*.mp3", "*.flac", "*.ogg", "*.pdf",
|
||||
".gitattributes",
|
||||
]
|
||||
AUX_MAX_SIZE = 50 * 1024 * 1024
|
||||
|
||||
LAYER_CONTAINERS = {"layers", "layer", "blocks", "block", "h"}
|
||||
EXPERT_ROW_KEYWORDS = ("expert", "router", "gate", "score")
|
||||
|
||||
LAYER_KEYS = {
|
||||
"num_hidden_layers", "num_layers", "n_layer", "n_layers", "num_layer",
|
||||
"num_decoder_layers", "encoder_layers", "decoder_layers", "depth",
|
||||
"num_encoder_layers", "num_attention_layers", "layer_count",
|
||||
}
|
||||
VOCAB_KEYS = {"vocab_size", "padded_vocab_size"}
|
||||
NEXTN_KEYS = {"num_nextn_predict_layers", "num_mtp_layers"}
|
||||
TOPK_SUBSTR = ("per_tok", "topk", "top_k", "moe_k")
|
||||
EXPERT_KEY_EXCLUDE = ("per_tok", "topk", "top_k", "shared", "group", "intermediate", "size", "dim", "dtype")
|
||||
|
||||
|
||||
def parse_size(text: str) -> int:
|
||||
m = re.fullmatch(r"(\d+(?:\.\d+)?)\s*([kKmMgGtT]?)[bB]?", text.strip())
|
||||
if not m:
|
||||
raise argparse.ArgumentTypeError(f"invalid size: {text!r}")
|
||||
mult = {"": 1, "k": 1024, "m": 1024**2, "g": 1024**3, "t": 1024**4}[m.group(2).lower()]
|
||||
return int(float(m.group(1)) * mult)
|
||||
|
||||
|
||||
def human(n: float) -> str:
|
||||
for unit in ("B", "KB", "MB", "GB"):
|
||||
if n < 1024 or unit == "GB":
|
||||
return f"{n:.1f}{unit}" if unit != "B" else f"{int(n)}B"
|
||||
n /= 1024
|
||||
return f"{n}GB"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
# HTTP
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
_tls = threading.local()
|
||||
|
||||
|
||||
def _session() -> requests.Session:
|
||||
s = getattr(_tls, "s", None)
|
||||
if s is None:
|
||||
s = requests.Session()
|
||||
_tls.s = s
|
||||
return s
|
||||
|
||||
|
||||
class HubClient:
|
||||
def __init__(self, repo_id: str, revision: str, token: str | None):
|
||||
self.repo_id = repo_id
|
||||
self.revision = revision
|
||||
self.headers = {"Authorization": f"Bearer {token}"} if token else {}
|
||||
|
||||
def url(self, filename: str) -> str:
|
||||
return hf_hub_url(self.repo_id, filename, revision=self.revision)
|
||||
|
||||
def _request(self, filename: str, headers: dict, stream: bool = False, retries: int = 5):
|
||||
last: Exception | None = None
|
||||
for attempt in range(1, retries + 1):
|
||||
try:
|
||||
r = _session().get(self.url(filename), headers=headers, stream=stream, timeout=90, allow_redirects=True)
|
||||
if r.status_code in (429, 500, 502, 503, 504):
|
||||
raise requests.HTTPError(f"HTTP {r.status_code}", response=r)
|
||||
r.raise_for_status()
|
||||
return r
|
||||
except Exception as e: # noqa: BLE001
|
||||
last = e
|
||||
if attempt == retries or "404" in str(e) or "401" in str(e) or "403" in str(e):
|
||||
break
|
||||
time.sleep(2.0 * attempt)
|
||||
raise RuntimeError(f"{self.repo_id}/{filename}: {last}") from last
|
||||
|
||||
def get_range(self, filename: str, start: int, end_inclusive: int) -> bytes:
|
||||
h = dict(self.headers)
|
||||
h["Range"] = f"bytes={start}-{end_inclusive}"
|
||||
return self._request(filename, h).content
|
||||
|
||||
def get_json(self, filename: str) -> Any:
|
||||
return json.loads(self._request(filename, dict(self.headers)).content)
|
||||
|
||||
def download_file(self, filename: str, dest: Path):
|
||||
r = self._request(filename, dict(self.headers), stream=True)
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(dest, "wb") as f:
|
||||
for chunk in r.iter_content(chunk_size=4 * 1024 * 1024):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
def stream_range_into_fd(self, filename: str, start: int, end_inclusive: int, fd: int, write_offset: int):
|
||||
h = dict(self.headers)
|
||||
h["Range"] = f"bytes={start}-{end_inclusive}"
|
||||
r = self._request(filename, h, stream=True)
|
||||
pos = write_offset
|
||||
for chunk in r.iter_content(chunk_size=8 * 1024 * 1024):
|
||||
if chunk:
|
||||
os.pwrite(fd, chunk, pos)
|
||||
pos += len(chunk)
|
||||
if pos - write_offset != end_inclusive - start + 1:
|
||||
raise RuntimeError(f"short read: {filename} bytes={start}-{end_inclusive}")
|
||||
|
||||
|
||||
def fetch_shard_header(client: HubClient, filename: str) -> tuple[dict, int]:
|
||||
(header_len,) = struct.unpack("<Q", client.get_range(filename, 0, 7))
|
||||
header = json.loads(client.get_range(filename, 8, 8 + header_len - 1))
|
||||
header.pop("__metadata__", None)
|
||||
return header, 8 + header_len
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
# config.json analysis / patching
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
def walk_config(cfg: Any):
|
||||
"""Yield (container_dict, key, value) for every leaf in nested dicts."""
|
||||
if isinstance(cfg, dict):
|
||||
for k, v in cfg.items():
|
||||
if isinstance(v, dict):
|
||||
yield from walk_config(v)
|
||||
else:
|
||||
yield cfg, k, v
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConfigInfo:
|
||||
cfg: dict | None
|
||||
layer_counts: set[int] = field(default_factory=set)
|
||||
nextn_counts: set[int] = field(default_factory=set)
|
||||
expert_counts: set[int] = field(default_factory=set)
|
||||
vocab_sizes: set[int] = field(default_factory=set)
|
||||
first_k_dense: int = 0
|
||||
|
||||
|
||||
def analyze_config(cfg: dict | None) -> ConfigInfo:
|
||||
info = ConfigInfo(cfg=cfg)
|
||||
if cfg is None:
|
||||
return info
|
||||
for _d, key, val in walk_config(cfg):
|
||||
if not isinstance(val, int) or isinstance(val, bool) or val <= 0:
|
||||
continue
|
||||
if key in LAYER_KEYS:
|
||||
info.layer_counts.add(val)
|
||||
elif key in NEXTN_KEYS:
|
||||
info.nextn_counts.add(val)
|
||||
elif key in VOCAB_KEYS:
|
||||
info.vocab_sizes.add(val)
|
||||
elif key == "first_k_dense_replace":
|
||||
info.first_k_dense = val
|
||||
elif "expert" in key and val > 1 and not any(x in key for x in EXPERT_KEY_EXCLUDE):
|
||||
info.expert_counts.add(val)
|
||||
return info
|
||||
|
||||
|
||||
def patch_config(cfg: dict, families: dict[str, Family], new_vocab: int | None, orig_vocabs: set[int]) -> list[str]:
|
||||
"""Patch counts in-place to match the slice. Returns log lines."""
|
||||
log = []
|
||||
by_card: dict[int, Family] = {}
|
||||
for fam in families.values():
|
||||
if fam.kept is not None:
|
||||
by_card.setdefault(fam.card, fam)
|
||||
by_card.setdefault(fam.regular_total, fam)
|
||||
max_new_experts = max((f.new_count for f in families.values() if f.kind == "expert" and f.kept is not None), default=None)
|
||||
|
||||
for d, key, val in list(walk_config(cfg)):
|
||||
if isinstance(val, int) and not isinstance(val, bool):
|
||||
fam = by_card.get(val)
|
||||
if key in LAYER_KEYS and fam is not None and fam.kind == "layer":
|
||||
d[key] = fam.new_regular
|
||||
elif key == "first_k_dense_replace":
|
||||
lfam = next((f for f in families.values() if f.kind == "layer"), None)
|
||||
if lfam is None:
|
||||
continue
|
||||
d[key] = sum(1 for i in lfam.kept if i < val)
|
||||
elif "expert" in key and fam is not None and fam.kind == "expert" and not any(x in key for x in EXPERT_KEY_EXCLUDE):
|
||||
d[key] = fam.new_count
|
||||
elif "expert" in key and any(x in key for x in TOPK_SUBSTR) and max_new_experts is not None and val > max_new_experts:
|
||||
d[key] = max_new_experts
|
||||
elif key in VOCAB_KEYS and new_vocab is not None and val in orig_vocabs:
|
||||
d[key] = new_vocab
|
||||
if d[key] != val:
|
||||
log.append(f"{key}: {val} -> {d[key]}")
|
||||
elif isinstance(val, list) and val and all(not isinstance(x, (dict, list)) for x in val):
|
||||
# per-layer lists, e.g. layer_types
|
||||
fam = by_card.get(len(val))
|
||||
if fam is not None and fam.kind == "layer" and len(val) in (fam.card, fam.regular_total):
|
||||
d[key] = [val[i] for i in fam.kept if i < len(val)]
|
||||
log.append(f"{key}: list[{len(val)}] -> list[{len(d[key])}]")
|
||||
return log
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
# tensor name analysis
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
@dataclass
|
||||
class Family:
|
||||
"""One numeric slot in tensor names, e.g. 'model.layers.#'."""
|
||||
prefix: str
|
||||
kind: str # "layer" | "expert" | "other"
|
||||
indices: set[int] = field(default_factory=set)
|
||||
kept: list[int] | None = None # None = keep all, no renumbering
|
||||
renumber: dict[int, int] = field(default_factory=dict)
|
||||
regular_total: int = 0 # layer count excluding MTP tail
|
||||
new_regular: int = 0
|
||||
|
||||
@property
|
||||
def card(self) -> int:
|
||||
return len(self.indices)
|
||||
|
||||
@property
|
||||
def new_count(self) -> int:
|
||||
return len(self.kept) if self.kept is not None else self.card
|
||||
|
||||
|
||||
def name_slots(name: str):
|
||||
"""Yield (slot_pos, family_prefix, index) for each numeric path component."""
|
||||
tokens = name.split(".")
|
||||
norm: list[str] = []
|
||||
for i, tok in enumerate(tokens):
|
||||
if tok.isdigit():
|
||||
yield i, ".".join(norm), int(tok)
|
||||
norm.append("#")
|
||||
else:
|
||||
norm.append(tok)
|
||||
|
||||
|
||||
def build_families(names: list[str], cfg_info: ConfigInfo) -> dict[str, Family]:
|
||||
families: dict[str, Family] = {}
|
||||
for name in names:
|
||||
tokens = name.split(".")
|
||||
for i, prefix, idx in name_slots(name):
|
||||
container = tokens[i - 1] if i > 0 else ""
|
||||
fam = families.get(prefix)
|
||||
if fam is None:
|
||||
fam = Family(prefix=prefix, kind="expert" if "expert" in container else "other")
|
||||
families[prefix] = fam
|
||||
fam.indices.add(idx)
|
||||
|
||||
acceptable_layer_cards: dict[int, int] = {} # card -> regular layer count
|
||||
for c in cfg_info.layer_counts:
|
||||
acceptable_layer_cards[c] = c
|
||||
for m in cfg_info.nextn_counts:
|
||||
acceptable_layer_cards[c + m] = c
|
||||
|
||||
for fam in families.values():
|
||||
if fam.kind == "expert":
|
||||
continue
|
||||
container = fam.prefix.rsplit(".", 1)[-1]
|
||||
if cfg_info.cfg is not None:
|
||||
if fam.card in acceptable_layer_cards:
|
||||
fam.kind = "layer"
|
||||
fam.regular_total = acceptable_layer_cards[fam.card]
|
||||
elif fam.card >= 4 and container in LAYER_CONTAINERS:
|
||||
fam.kind = "layer"
|
||||
fam.regular_total = fam.card
|
||||
return families
|
||||
|
||||
|
||||
def plan_families(families: dict[str, Family], num_layers: int, keep_layers: list[int] | None, num_experts: int, first_k_dense: int):
|
||||
for fam in families.values():
|
||||
if fam.kind == "layer":
|
||||
if keep_layers is not None:
|
||||
kept = [i for i in keep_layers if i < fam.regular_total]
|
||||
else:
|
||||
base = min(num_layers, fam.regular_total)
|
||||
kept = list(range(base))
|
||||
# keep one MoE layer for models whose first k layers are dense
|
||||
if 0 < first_k_dense < fam.regular_total and first_k_dense >= base >= 1:
|
||||
kept = list(range(base - 1)) + [first_k_dense]
|
||||
fam.new_regular = len(kept)
|
||||
# keep the MTP tail (layer indices beyond the regular count)
|
||||
kept += [i for i in sorted(fam.indices) if i >= fam.regular_total]
|
||||
fam.kept = sorted(set(kept))
|
||||
elif fam.kind == "expert":
|
||||
fam.kept = sorted(fam.indices)[:num_experts]
|
||||
fam.new_regular = len(fam.kept)
|
||||
else:
|
||||
fam.kept = None
|
||||
continue
|
||||
fam.renumber = {old: new for new, old in enumerate(fam.kept)}
|
||||
|
||||
|
||||
def slice_tensor_name(name: str, families: dict[str, Family]) -> str | None:
|
||||
"""Return the renumbered output name, or None if the tensor is dropped."""
|
||||
tokens = name.split(".")
|
||||
for i, prefix, idx in name_slots(name):
|
||||
fam = families[prefix]
|
||||
if fam.kept is None:
|
||||
continue
|
||||
if idx not in fam.renumber:
|
||||
return None
|
||||
tokens[i] = str(fam.renumber[idx])
|
||||
return ".".join(tokens)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
# slicing
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
@dataclass
|
||||
class TensorPlan:
|
||||
src_name: str
|
||||
out_name: str
|
||||
dtype: str
|
||||
shape: list[int]
|
||||
orig_shape: list[int]
|
||||
size: int
|
||||
src_file: str
|
||||
seg_start: int # absolute byte range in src_file
|
||||
seg_end: int # inclusive
|
||||
|
||||
|
||||
def pick_weight_files(filenames: list[str]) -> tuple[list[str], bool]:
|
||||
"""Return (weight files, has_index). Preference: index -> model.safetensors -> any root safetensors."""
|
||||
if INDEX_FILE in filenames:
|
||||
return [], True
|
||||
if "model.safetensors" in filenames:
|
||||
return ["model.safetensors"], False
|
||||
root_st = [f for f in filenames if f.endswith(".safetensors") and "/" not in f]
|
||||
if "consolidated.safetensors" in root_st:
|
||||
return ["consolidated.safetensors"], False
|
||||
return root_st[:1], False
|
||||
|
||||
|
||||
def row_slice(shape: list[int], size: int, keep_rows: int) -> tuple[list[int], int] | None:
|
||||
# only a prefix of dim 0 is a single contiguous byte range
|
||||
if len(shape) < 1 or shape[0] <= keep_rows or size % shape[0] != 0:
|
||||
return None
|
||||
row_bytes = size // shape[0]
|
||||
return [keep_rows] + shape[1:], keep_rows * row_bytes
|
||||
|
||||
|
||||
def build_plan(args, families, headers, weight_files, src_file_of, cfg_info) -> list[TensorPlan]:
|
||||
plan_families(families, args.num_layers, args.keep_layers, args.num_experts, cfg_info.first_k_dense)
|
||||
plans = []
|
||||
for fname in weight_files:
|
||||
header, data_start = headers[fname]
|
||||
for name, meta in header.items():
|
||||
out_name = slice_tensor_name(name, families)
|
||||
if out_name is None:
|
||||
continue
|
||||
start, end = meta["data_offsets"]
|
||||
shape, size = list(meta["shape"]), end - start
|
||||
new_rows = None
|
||||
if args.vocab_size and shape and shape[0] > args.vocab_size \
|
||||
and any(shape[0] >= vs and shape[0] - vs <= 1024 for vs in cfg_info.vocab_sizes):
|
||||
new_rows = args.vocab_size
|
||||
elif shape and shape[0] in cfg_info.expert_counts and shape[0] > args.num_experts \
|
||||
and any(k in name for k in EXPERT_ROW_KEYWORDS):
|
||||
new_rows = args.num_experts
|
||||
if new_rows is not None:
|
||||
sliced = row_slice(shape, size, new_rows)
|
||||
if sliced is not None:
|
||||
shape, size = sliced
|
||||
plans.append(TensorPlan(name, out_name, meta["dtype"], shape, list(meta["shape"]), size, src_file_of[name],
|
||||
data_start + start, data_start + start + size - 1))
|
||||
return plans
|
||||
|
||||
|
||||
def print_inspect(cfg_info: ConfigInfo, families: dict[str, Family], plans: list[TensorPlan], all_names: list[str]):
|
||||
logger.info("=== config analysis ===")
|
||||
logger.info(f"layer counts: {sorted(cfg_info.layer_counts)} nextn: {sorted(cfg_info.nextn_counts)} "
|
||||
f"experts: {sorted(cfg_info.expert_counts)} vocab: {sorted(cfg_info.vocab_sizes)} first_k_dense: {cfg_info.first_k_dense}")
|
||||
logger.info("\n=== numeric families in tensor names ===")
|
||||
for fam in sorted(families.values(), key=lambda f: f.prefix):
|
||||
kept = "all" if fam.kept is None else f"{fam.kept[:16]}{'...' if len(fam.kept) > 16 else ''}"
|
||||
logger.info(f"{fam.prefix:60s} kind={fam.kind:6s} count={fam.card:4d} kept={kept}")
|
||||
logger.info("\n=== kept tensors (first index of each family only) ===")
|
||||
seen = set()
|
||||
for t in plans:
|
||||
key = re.sub(r"\.\d+\.", ".#.", t.out_name)
|
||||
if key in seen and t.shape == t.orig_shape:
|
||||
continue
|
||||
seen.add(key)
|
||||
mark = f" (sliced from {t.orig_shape})" if t.shape != t.orig_shape else ""
|
||||
logger.info(f"{t.out_name:70s} {t.dtype:5s} {t.shape}{mark}")
|
||||
kept_src = {t.src_name for t in plans}
|
||||
dropped = [n for n in all_names if n not in kept_src]
|
||||
logger.info(f"\n{len(plans)}/{len(all_names)} tensors kept, {len(dropped)} dropped")
|
||||
|
||||
|
||||
def write_slice(out_dir: Path, client: HubClient, plans: list[TensorPlan], jobs: int):
|
||||
header_out: dict[str, Any] = {}
|
||||
offset = 0
|
||||
for t in plans:
|
||||
header_out[t.out_name] = {"dtype": t.dtype, "shape": t.shape, "data_offsets": [offset, offset + t.size]}
|
||||
offset += t.size
|
||||
header_out["__metadata__"] = {"format": "pt"}
|
||||
hbytes = json.dumps(header_out, separators=(",", ":")).encode()
|
||||
hbytes += b" " * ((-(len(hbytes) + 8)) % 8)
|
||||
data_start = 8 + len(hbytes)
|
||||
|
||||
shard_path = out_dir / "model.safetensors"
|
||||
with open(shard_path, "wb") as f:
|
||||
f.write(struct.pack("<Q", len(hbytes)))
|
||||
f.write(hbytes)
|
||||
f.truncate(data_start + offset)
|
||||
fd = os.open(shard_path, os.O_WRONLY)
|
||||
try:
|
||||
def fetch(t: TensorPlan):
|
||||
dst = data_start + header_out[t.out_name]["data_offsets"][0]
|
||||
client.stream_range_into_fd(t.src_file, t.seg_start, t.seg_end, fd, dst)
|
||||
|
||||
with cf.ThreadPoolExecutor(max_workers=jobs) as ex:
|
||||
list(ex.map(fetch, plans))
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument("repo", help="HF repo id, e.g. Qwen/Qwen3-0.6B")
|
||||
ap.add_argument("--revision", default=None)
|
||||
ap.add_argument("--output", type=Path, default=None, help="output dir (default: ./tmp/hf_slices/{user}--{model})")
|
||||
ap.add_argument("--inspect", action="store_true", help="print config analysis, tensor families and the plan, write nothing")
|
||||
ap.add_argument("--dry-run", action="store_true", help="plan and estimate size only, write nothing")
|
||||
ap.add_argument("--num-layers", type=int, default=2, help="keep the first N layers (default: 2)")
|
||||
ap.add_argument("--keep-layers", default=None, help="explicit comma-separated layer indices to keep, overrides --num-layers")
|
||||
ap.add_argument("--num-experts", type=int, default=8)
|
||||
ap.add_argument("--vocab-size", type=int, default=0,
|
||||
help="row-slice vocab-sized tensors to this size (default: 0 = off; tokenizer is NOT sliced, so this breaks conversion for most tokenizers)")
|
||||
ap.add_argument("--max-size", type=parse_size, default="1G", help="abort if the sliced weights exceed this (default: 1G)")
|
||||
ap.add_argument("--hf-token", default=None, help="HF token (default: HF_TOKEN env var or 'hf auth login' token)")
|
||||
ap.add_argument("--jobs", type=int, default=8, help="concurrent HTTP requests")
|
||||
args = ap.parse_args()
|
||||
args.keep_layers = sorted({int(x) for x in args.keep_layers.split(",")}) if args.keep_layers else None
|
||||
|
||||
token = args.hf_token or get_token()
|
||||
api = HfApi(token=token)
|
||||
info = api.model_info(args.repo, revision=args.revision, files_metadata=True)
|
||||
client = HubClient(args.repo, info.sha or "main", token)
|
||||
siblings = {s.rfilename: (s.size or 0) for s in info.siblings or []}
|
||||
filenames = sorted(siblings)
|
||||
|
||||
weight_files, has_index = pick_weight_files(filenames)
|
||||
weight_map: dict[str, str] = {}
|
||||
if has_index:
|
||||
weight_map = client.get_json(INDEX_FILE)["weight_map"]
|
||||
weight_files = sorted(set(weight_map.values()))
|
||||
elif not weight_files:
|
||||
logger.error("no .safetensors found")
|
||||
return 1
|
||||
|
||||
cfg = client.get_json(CONFIG_FILE) if CONFIG_FILE in filenames else None
|
||||
cfg_info = analyze_config(cfg)
|
||||
|
||||
with cf.ThreadPoolExecutor(max_workers=args.jobs) as ex:
|
||||
headers = dict(zip(weight_files, ex.map(lambda f: fetch_shard_header(client, f), weight_files)))
|
||||
all_names = [n for fname in weight_files for n in headers[fname][0]]
|
||||
src_file_of = weight_map if has_index else {n: weight_files[0] for n in all_names}
|
||||
|
||||
families = build_families(all_names, cfg_info)
|
||||
plans = build_plan(args, families, headers, weight_files, src_file_of, cfg_info)
|
||||
est = sum(t.size for t in plans)
|
||||
|
||||
if args.inspect:
|
||||
print_inspect(cfg_info, families, plans, all_names)
|
||||
logger.info(f"{args.repo}@{info.sha}: {len(plans)}/{len(all_names)} tensors, {human(est)}")
|
||||
if not any(f.kind == "layer" for f in families.values()):
|
||||
logger.warning("no layer family detected - check --inspect output and adapt the script")
|
||||
if est > args.max_size:
|
||||
logger.error(f"slice is {human(est)} > --max-size {human(args.max_size)}")
|
||||
return 1
|
||||
if args.inspect or args.dry_run:
|
||||
return 0
|
||||
|
||||
out_dir = args.output or Path("tmp") / "hf_slices" / args.repo.replace("/", "--")
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
write_slice(out_dir, client, plans, args.jobs)
|
||||
|
||||
skipped_aux = []
|
||||
for fname in filenames:
|
||||
if fname in weight_files or fname in (INDEX_FILE, CONFIG_FILE) or fname.endswith(".safetensors"):
|
||||
continue
|
||||
if any(fnmatch.fnmatch(fname, p) or fnmatch.fnmatch(Path(fname).name, p) for p in AUX_BLACKLIST):
|
||||
continue
|
||||
if siblings[fname] > AUX_MAX_SIZE:
|
||||
skipped_aux.append(f"{fname} ({human(siblings[fname])})")
|
||||
continue
|
||||
client.download_file(fname, out_dir / fname)
|
||||
for f in skipped_aux:
|
||||
logger.warning(f"skipped large aux file {f}")
|
||||
|
||||
patch_log = []
|
||||
if cfg is not None:
|
||||
new_vocab = args.vocab_size if args.vocab_size and any(vs > args.vocab_size for vs in cfg_info.vocab_sizes) else None
|
||||
patch_log = patch_config(cfg, families, new_vocab, cfg_info.vocab_sizes)
|
||||
with open(out_dir / CONFIG_FILE, "w") as f:
|
||||
json.dump(cfg, f, indent=2)
|
||||
f.write("\n")
|
||||
for line in patch_log:
|
||||
logger.info(f"config: {line}")
|
||||
|
||||
with open(out_dir / ".slice_meta.json", "w") as f:
|
||||
json.dump({
|
||||
"repo": args.repo, "revision": info.sha,
|
||||
"tensors": len(plans), "total_tensors": len(all_names), "size": est,
|
||||
"config_patches": patch_log, "skipped_aux": skipped_aux,
|
||||
}, f, indent=2)
|
||||
logger.info(f"wrote {out_dir}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: update-fusion-table
|
||||
description: Regenerate a per-device test-fusion baseline (tests/fusion/MTL.csv, ...) after fusion patterns or the dummy-model set change. Use when test-fusion --check fails.
|
||||
---
|
||||
|
||||
# Update a fusion baseline
|
||||
|
||||
`tests/fusion/<BACKEND>.csv` is the `test-fusion` golden baseline: `arch,moe,mode,label,count`,
|
||||
where `count` is how many times a fusion label matched per graph run. See `tests/fusion/README.md`.
|
||||
|
||||
Re-record only when the counts moved for the reason the contributor intended. A row failing on
|
||||
`nmse_fus` (> 1e-4) is a fused-vs-unfused correctness bug, not baseline drift - fix it, never
|
||||
record it away.
|
||||
|
||||
## Steps
|
||||
|
||||
```sh
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -DGGML_METAL=ON # enable the target backend
|
||||
cmake --build build --config Release --target test-llama-archs --target test-fusion -j
|
||||
|
||||
rm -rf build-ci-models && mkdir -p build-ci-models # always regenerate
|
||||
./build/bin/test-llama-archs -o build-ci-models
|
||||
|
||||
D=MTL0; B=tests/fusion/MTL.csv; T=/tmp/$(basename $B) # keep the target backend/device in sync
|
||||
./build/bin/test-fusion --models build-ci-models --device $D --check $B # read the FAIL rows
|
||||
./build/bin/test-fusion --models build-ci-models --device $D --record $T
|
||||
diff $B $T
|
||||
cp $T $B
|
||||
./build/bin/test-fusion --models build-ci-models --device $D --check $B # 0 failed, exit 0
|
||||
```
|
||||
|
||||
- Read the diff before applying it. Every added, removed or changed row must be explainable by the
|
||||
change under review; an unexplained row means the run picked up something unintended.
|
||||
- `expected 0` in a FAIL row = no baseline row for that label (new arch, or a fusion newly
|
||||
firing); non-zero `expected` = count changed; `fusion ... is no longer matched` = row stopped
|
||||
firing.
|
||||
- `--record` rewrites the whole file, so never record a partial run (`--model FILE`, a filtered
|
||||
models dir) - it drops the rows of every architecture that was not run.
|
||||
- `--record` marks every row `ok`; only the `--check` run validates.
|
||||
@@ -165,6 +165,8 @@ static const std::map<llm_arch, const char *> LLM_ARCH_NAMES = {
|
||||
{ LLM_ARCH_QWEN3TTS, "qwen3tts" },
|
||||
{ LLM_ARCH_POCKETTTS, "pockettts" },
|
||||
{ LLM_ARCH_K2_HORIZON, "k2-horizon" },
|
||||
{ LLM_ARCH_BEAM, "beam" },
|
||||
{ LLM_ARCH_KOLIBRI1, "kolibri1" },
|
||||
{ LLM_ARCH_UNKNOWN, "(unknown)" },
|
||||
};
|
||||
|
||||
|
||||
@@ -170,6 +170,8 @@ enum llm_arch {
|
||||
LLM_ARCH_MINIMAX_01,
|
||||
LLM_ARCH_HRM_TEXT,
|
||||
LLM_ARCH_K2_HORIZON,
|
||||
LLM_ARCH_BEAM,
|
||||
LLM_ARCH_KOLIBRI1,
|
||||
LLM_ARCH_UNKNOWN,
|
||||
};
|
||||
|
||||
|
||||
+20
-6
@@ -218,17 +218,31 @@ void llm_graph_input_attn_temp::set_input(const llama_ubatch * ubatch) {
|
||||
GGML_ASSERT(n_attn_temp_floor_scale != 0);
|
||||
|
||||
std::vector<float> attn_scale_data(n_tokens, 0.0f);
|
||||
for (int i = 0; i < n_tokens; ++i) {
|
||||
const float pos = ubatch->pos[i];
|
||||
attn_scale_data[i] = std::log(
|
||||
std::floor((pos + f_attn_temp_offset) / n_attn_temp_floor_scale) + 1.0
|
||||
) * f_attn_temp_scale + 1.0;
|
||||
if (attn_temp_pow) {
|
||||
// (1 + (pos + offset) / length) ^ power
|
||||
for (int i = 0; i < n_tokens; ++i) {
|
||||
const double pos = ubatch->pos[i];
|
||||
attn_scale_data[i] = (float) std::exp((double) f_attn_temp_scale *
|
||||
std::log1p((pos + f_attn_temp_offset) / (double) n_attn_temp_floor_scale));
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < n_tokens; ++i) {
|
||||
const float pos = ubatch->pos[i];
|
||||
attn_scale_data[i] = std::log(
|
||||
std::floor((pos + f_attn_temp_offset) / n_attn_temp_floor_scale) + 1.0
|
||||
) * f_attn_temp_scale + 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
ggml_backend_tensor_set(attn_scale, attn_scale_data.data(), 0, n_tokens*ggml_element_size(attn_scale));
|
||||
}
|
||||
}
|
||||
|
||||
bool llm_graph_input_attn_temp::can_reuse(const llm_graph_params & params) {
|
||||
// only the power form opts in; the log form keeps no reuse
|
||||
return attn_temp_pow && attn_scale != nullptr && attn_scale->ne[2] == params.ubatch.n_tokens;
|
||||
}
|
||||
|
||||
void llm_graph_input_pos_bucket::set_input(const llama_ubatch * ubatch) {
|
||||
if (pos_bucket) {
|
||||
const int64_t n_tokens = ubatch->n_tokens;
|
||||
@@ -2743,7 +2757,7 @@ ggml_tensor * llm_graph_context::build_inp_pos() const {
|
||||
}
|
||||
|
||||
ggml_tensor * llm_graph_context::build_inp_attn_scale() const {
|
||||
auto inp = std::make_unique<llm_graph_input_attn_temp>(hparams.n_attn_temp_floor_scale, hparams.f_attn_temp_scale, hparams.f_attn_temp_offset);
|
||||
auto inp = std::make_unique<llm_graph_input_attn_temp>(hparams.n_attn_temp_floor_scale, hparams.f_attn_temp_scale, hparams.f_attn_temp_offset, hparams.attn_temp_pow);
|
||||
|
||||
auto & cur = inp->attn_scale;
|
||||
|
||||
|
||||
+5
-2
@@ -188,17 +188,20 @@ public:
|
||||
// temperature tuning, used by llama4
|
||||
class llm_graph_input_attn_temp : public llm_graph_input_i {
|
||||
public:
|
||||
llm_graph_input_attn_temp(uint32_t n_attn_temp_floor_scale, float f_attn_temp_scale, float f_attn_temp_offset)
|
||||
: n_attn_temp_floor_scale(n_attn_temp_floor_scale), f_attn_temp_scale(f_attn_temp_scale), f_attn_temp_offset(f_attn_temp_offset) {}
|
||||
llm_graph_input_attn_temp(uint32_t n_attn_temp_floor_scale, float f_attn_temp_scale, float f_attn_temp_offset, bool attn_temp_pow = false)
|
||||
: n_attn_temp_floor_scale(n_attn_temp_floor_scale), f_attn_temp_scale(f_attn_temp_scale), f_attn_temp_offset(f_attn_temp_offset), attn_temp_pow(attn_temp_pow) {}
|
||||
virtual ~llm_graph_input_attn_temp() = default;
|
||||
|
||||
void set_input(const llama_ubatch * ubatch) override;
|
||||
|
||||
bool can_reuse(const llm_graph_params & params) override;
|
||||
|
||||
ggml_tensor * attn_scale = nullptr; // F32 [n_batch]
|
||||
|
||||
const uint32_t n_attn_temp_floor_scale;
|
||||
const float f_attn_temp_scale;
|
||||
const float f_attn_temp_offset;
|
||||
const bool attn_temp_pow;
|
||||
};
|
||||
|
||||
class llm_graph_input_pos_bucket : public llm_graph_input_i {
|
||||
|
||||
@@ -280,6 +280,8 @@ struct llama_hparams {
|
||||
uint32_t n_attn_temp_floor_scale = 0;
|
||||
float f_attn_temp_scale = 0.0f;
|
||||
float f_attn_temp_offset = 0.0f; // offset position index
|
||||
// use (1 + (pos + offset) / floor_scale) ^ scale instead of the llama4 log form
|
||||
bool attn_temp_pow = false;
|
||||
|
||||
// realtime models: number of tokens the output lags behind the audio input
|
||||
uint32_t n_tokens_delay = 0;
|
||||
|
||||
+14
-1
@@ -322,6 +322,8 @@ static llama_model * llama_model_mapping(llm_arch arch, const llama_model_params
|
||||
return new llama_model_minimax_m3(params);
|
||||
case LLM_ARCH_HRM_TEXT:
|
||||
return new llama_model_hrm_text(params);
|
||||
case LLM_ARCH_BEAM:
|
||||
return new llama_model_beam(params);
|
||||
case LLM_ARCH_COGVLM:
|
||||
return new llama_model_cogvlm(params);
|
||||
case LLM_ARCH_PANGU_EMBED:
|
||||
@@ -358,6 +360,8 @@ static llama_model * llama_model_mapping(llm_arch arch, const llama_model_params
|
||||
return new llama_model_spark2_5(params);
|
||||
case LLM_ARCH_K2_HORIZON:
|
||||
return new llama_model_k2_horizon(params);
|
||||
case LLM_ARCH_KOLIBRI1:
|
||||
return new llama_model_kolibri1(params);
|
||||
default:
|
||||
throw std::runtime_error(std::string("unsupported model architecture: '") + llm_arch_name(arch) + "'");
|
||||
}
|
||||
@@ -405,6 +409,7 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
|
||||
static const std::regex pattern_idx_cache ("cache_idx_(k|v)_l\\d*");
|
||||
static const std::regex pattern_dsv4_state ("dsv4_(csa|hca|lid)_state_(kv|score)_l\\d*");
|
||||
static const std::regex pattern_attn_sinks ("blk\\.\\d*\\.attn_sinks.weight");
|
||||
static const std::regex pattern_attn_gate_bias ("blk\\.\\d*\\.attn_gate\\.bias");
|
||||
static const std::regex pattern_attn_out_weight ("blk\\.\\d*\\.attn_output.weight");
|
||||
static const std::regex pattern_attn_out_bias ("blk\\.\\d*\\.attn_output.bias");
|
||||
static const std::regex pattern_attn_out_a_weight("blk\\.\\d*\\.attn_output_a\\.weight");
|
||||
@@ -550,7 +555,8 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
|
||||
if (std::regex_match(tensor_name, pattern_qk_norm)) {
|
||||
return get_tensor_config_impl(tensor->ne[1] == 1 ? GGML_BACKEND_SPLIT_AXIS_MIRRORED : GGML_BACKEND_SPLIT_AXIS_1, "attn_output.weight");
|
||||
}
|
||||
if (std::regex_match(tensor_name, pattern_kv_cache) || std::regex_match(tensor_name, pattern_attn_sinks)) {
|
||||
if (std::regex_match(tensor_name, pattern_kv_cache) || std::regex_match(tensor_name, pattern_attn_sinks) ||
|
||||
std::regex_match(tensor_name, pattern_attn_gate_bias)) {
|
||||
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0, "attn_output.weight");
|
||||
}
|
||||
if (std::regex_match(tensor_name, pattern_attn_out_weight)) {
|
||||
@@ -749,6 +755,10 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
|
||||
|
||||
const int64_t granularity_q = std::lcm(n_embd_q, blck_size_perf);
|
||||
const int64_t granularity_head = granularity_q / hparams.n_embd_head_k(il); // for tensors with one value per head
|
||||
if (std::regex_match(tensor_name, pattern_attn_gate_bias)) {
|
||||
GGML_ASSERT(segments.size() == 1);
|
||||
return {granularity_head};
|
||||
}
|
||||
if (std::regex_match(tensor_name, pattern_attn_sinks)) {
|
||||
GGML_ASSERT(segments.size() == 1);
|
||||
if (is_dsv4) {
|
||||
@@ -994,6 +1004,7 @@ const char * llm_type_name(llm_type type) {
|
||||
case LLM_TYPE_10B_128x3_66B: return "10B+128x3.66B";
|
||||
case LLM_TYPE_57B_A14B: return "57B.A14B";
|
||||
case LLM_TYPE_816B_A42B: return "816B.A42B";
|
||||
case LLM_TYPE_78B_A3_5B: return "78B.A3.5B";
|
||||
case LLM_TYPE_17B_16E: return "17Bx16E (Scout)";
|
||||
case LLM_TYPE_17B_128E: return "17Bx128E (Maverick)";
|
||||
case LLM_TYPE_A13B: return "A13B";
|
||||
@@ -3606,6 +3617,8 @@ llama_rope_type llama_model_rope_type(const llama_model * model) {
|
||||
case LLM_ARCH_MELLUM:
|
||||
case LLM_ARCH_MAPLE:
|
||||
case LLM_ARCH_HRM_TEXT:
|
||||
case LLM_ARCH_BEAM:
|
||||
case LLM_ARCH_KOLIBRI1:
|
||||
return LLAMA_ROPE_TYPE_NEOX;
|
||||
|
||||
case LLM_ARCH_DFLASH:
|
||||
|
||||
@@ -115,6 +115,7 @@ enum llm_type {
|
||||
LLM_TYPE_10B_128x3_66B,
|
||||
LLM_TYPE_57B_A14B,
|
||||
LLM_TYPE_816B_A42B,
|
||||
LLM_TYPE_78B_A3_5B, // Kolibri1
|
||||
LLM_TYPE_17B_16E, // llama4 Scout
|
||||
LLM_TYPE_17B_128E, // llama4 Maverick
|
||||
LLM_TYPE_A13B,
|
||||
@@ -310,6 +311,7 @@ struct llama_layer {
|
||||
struct ggml_tensor * wv_enc = nullptr;
|
||||
struct ggml_tensor * wo_enc = nullptr;
|
||||
struct ggml_tensor * wqkv_gate = nullptr;
|
||||
struct ggml_tensor * wqkv_gate_b = nullptr;
|
||||
// K2 Horizon MoVA
|
||||
struct ggml_tensor * attn_v_gate = nullptr;
|
||||
struct ggml_tensor * attn_v_gate_b = nullptr;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user