mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-10-19 11:17:30 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f174553e16 | ||
|
|
7951f601e8 | ||
|
|
33d6135cbe | ||
|
|
098ce526b5 | ||
|
|
f3fb2fcdbc | ||
|
|
fda00f18cd | ||
|
|
4866db350d | ||
|
|
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 | ||
|
|
3dbcb7fca5 | ||
|
|
c804754529 | ||
|
|
c0083e3052 | ||
|
|
13de11e78f | ||
|
|
88aaf325a7 | ||
|
|
a8d9a7e1e5 |
@@ -50,7 +50,7 @@ RUN if [ "${GGML_SYCL_F16}" = "ON" ]; then \
|
||||
fi && \
|
||||
echo "Building with dynamic libs" && \
|
||||
cmake -B build -DGGML_NATIVE=OFF -DGGML_SYCL=ON -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON -DLLAMA_BUILD_TESTS=OFF ${OPT_SYCL_F16} && \
|
||||
cmake --build build --config Release -j$(nproc)
|
||||
cmake --build build --config Release -j$((($(nproc)+1)/2))
|
||||
|
||||
RUN mkdir -p /app/lib && \
|
||||
find build -name "*.so*" -exec cp -P {} /app/lib \;
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
|
||||
@@ -20,7 +20,11 @@ on:
|
||||
paths: [
|
||||
'.github/workflows/build-ibm.yml',
|
||||
'ggml/src/ggml-cpu/**',
|
||||
'ggml/src/ggml-zdnn/**'
|
||||
'ggml/src/ggml-zdnn/**',
|
||||
'ggml/src/ggml-quants.c',
|
||||
'src/llama-quant.cpp',
|
||||
'gguf-py/gguf/scripts/gguf_convert_endian.py',
|
||||
'tests/test-quantize-be.sh'
|
||||
]
|
||||
|
||||
cache-mode: none
|
||||
@@ -105,6 +109,11 @@ jobs:
|
||||
wget https://huggingface.co/ggml-org/models/resolve/main/tinyllamas/stories260K-be.gguf
|
||||
./bin/llama-completion -m stories260K-be.gguf -p "One day, Lily met a Shoggoth" -n 500 -c 256
|
||||
|
||||
- name: Test quantize (s390x)
|
||||
id: quantize_test_s390x
|
||||
run: |
|
||||
./tests/test-quantize-be.sh ./build/bin/llama-quantize ./build/bin/llama-perplexity 2>&1
|
||||
|
||||
ubuntu-26-zdnn-s390x:
|
||||
name: ubuntu-26-zdnn-s390x
|
||||
runs-on: ubuntu-24.04-s390x
|
||||
|
||||
@@ -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,41 +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:
|
||||
- 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:
|
||||
- Model: `<model>`, linked to its Hugging Face page - the link text is the plain model name
|
||||
- Harness: `pi:llama.cpp`
|
||||
- Hardware: `<hardware>`
|
||||
- Stats: `<stats>`
|
||||
- Run `~/.pi/stats.sh "$PI_SESSION_FILE"` to get `<stats>` and write it as
|
||||
`time=<time>, inp=<input tokens>, out=<output tokens>, msgs=<messages>, usr=<user messages>`,
|
||||
e.g. `time=12.3m, inp=120k, out=45k, msgs=87, usr=6`
|
||||
- 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)
|
||||
|
||||
@@ -2455,9 +2455,10 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
|
||||
#ifdef ASCEND_310P
|
||||
// Q4 && Q8 per group is not support on 310p device
|
||||
return false;
|
||||
#endif
|
||||
#else
|
||||
// only support contiguous for quantized types.
|
||||
return ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]);
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2472,9 +2473,10 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
|
||||
#ifdef ASCEND_310P
|
||||
// Q4 && Q8 per group is not support on 310p device
|
||||
return false;
|
||||
#endif
|
||||
#else
|
||||
// only support contiguous for quantized types.
|
||||
return ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]);
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2641,7 +2643,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
|
||||
#ifdef ASCEND_310P
|
||||
// Ger is not supported on 310p device
|
||||
return false;
|
||||
#endif
|
||||
#else
|
||||
switch (op->src[0]->type) {
|
||||
case GGML_TYPE_F16:
|
||||
case GGML_TYPE_F32:
|
||||
@@ -2649,6 +2651,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
case GGML_OP_CONV_TRANSPOSE_1D:
|
||||
return true;
|
||||
@@ -2667,7 +2670,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
|
||||
#ifdef ASCEND_310P
|
||||
// FA not support on 310p device
|
||||
return false;
|
||||
#endif
|
||||
#else
|
||||
// derived from [ggml-cuda.cu]
|
||||
if (op->src[1]->type != GGML_TYPE_F16 || op->src[2]->type != GGML_TYPE_F16) {
|
||||
return false;
|
||||
@@ -2693,6 +2696,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
case GGML_OP_SSM_CONV:
|
||||
return true;
|
||||
|
||||
@@ -310,7 +310,7 @@ void ggml_vec_dot_q4_1_q8_1(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
const int8x16_t v_yl = vec_xl(0 , y[ib].qs);
|
||||
const int8x16_t v_yh = vec_xl(QK8_1/2, y[ib].qs);
|
||||
|
||||
const int32x4_t v_xy_ = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const int32x4_t v_xy_ = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const float32x4_t v_xy = vec_float(v_xy_);
|
||||
|
||||
const float32x4_t v_d = vec_splats(GGML_CPU_FP16_TO_FP32(x[ib].d) * GGML_CPU_FP16_TO_FP32(y[ib].d));
|
||||
@@ -379,8 +379,8 @@ void ggml_vec_dot_mxfp4_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const vo
|
||||
const int8x16_t v_y1l = vec_xl(0, y1->qs);
|
||||
const int8x16_t v_y1h = vec_xl(QK8_0/2, y1->qs);
|
||||
|
||||
const int32x4_t v_xy0 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x0l, v_y0l), v_x0h, v_y0h);
|
||||
const int32x4_t v_xy1 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x1l, v_y1l), v_x1h, v_y1h);
|
||||
const int32x4_t v_xy0 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x0l, v_y0l), v_x0h, v_y0h);
|
||||
const int32x4_t v_xy1 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x1l, v_y1l), v_x1h, v_y1h);
|
||||
|
||||
const float32x4_t v_xy0f = vec_float(v_xy0);
|
||||
const float32x4_t v_xy1f = vec_float(v_xy1);
|
||||
@@ -407,7 +407,7 @@ void ggml_vec_dot_mxfp4_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const vo
|
||||
const int8x16_t v_yl = vec_xl(0, y0->qs);
|
||||
const int8x16_t v_yh = vec_xl(QK8_0/2, y0->qs);
|
||||
|
||||
const int32x4_t v_xy = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const int32x4_t v_xy = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const float32x4_t v_xyf = vec_float(v_xy);
|
||||
|
||||
const float32x4_t v_d = vec_splats(GGML_E8M0_TO_FP32_HALF(x0->e) * GGML_CPU_FP16_TO_FP32(y0->d));
|
||||
@@ -502,8 +502,8 @@ void ggml_vec_dot_q5_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
const int8x16_t v_y1l = vec_xl(0, (const int8_t *)y1->qs);
|
||||
const int8x16_t v_y1h = vec_xl(QK8_0/2, (const int8_t *)y1->qs);
|
||||
|
||||
const int32x4_t v_xy0 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x0lf, v_y0l), v_x0hf, v_y0h);
|
||||
const int32x4_t v_xy1 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x1lf, v_y1l), v_x1hf, v_y1h);
|
||||
const int32x4_t v_xy0 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x0lf, v_y0l), v_x0hf, v_y0h);
|
||||
const int32x4_t v_xy1 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x1lf, v_y1l), v_x1hf, v_y1h);
|
||||
|
||||
const float32x4_t v_xy0f = vec_float(v_xy0);
|
||||
const float32x4_t v_xy1f = vec_float(v_xy1);
|
||||
@@ -548,7 +548,7 @@ void ggml_vec_dot_q5_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
const int8x16_t v_yl = vec_xl(0, (const int8_t *)y0->qs);
|
||||
const int8x16_t v_yh = vec_xl(QK8_0/2, (const int8_t *)y0->qs);
|
||||
|
||||
const int32x4_t v_xy = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_xlf, v_yl), v_xhf, v_yh);
|
||||
const int32x4_t v_xy = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_xlf, v_yl), v_xhf, v_yh);
|
||||
const float32x4_t v_xyf = vec_float(v_xy);
|
||||
|
||||
const float32x4_t v_d = vec_splats(GGML_CPU_FP16_TO_FP32(x0->d) * GGML_CPU_FP16_TO_FP32(y0->d));
|
||||
@@ -653,8 +653,8 @@ void ggml_vec_dot_q5_1_q8_1(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
const int8x16_t v_y1l = vec_xl(0 , y1->qs);
|
||||
const int8x16_t v_y1h = vec_xl(QK8_1/2, y1->qs);
|
||||
|
||||
const int32x4_t v_xy0 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x0lf, v_y0l), v_x0hf, v_y0h);
|
||||
const int32x4_t v_xy1 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x1lf, v_y1l), v_x1hf, v_y1h);
|
||||
const int32x4_t v_xy0 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x0lf, v_y0l), v_x0hf, v_y0h);
|
||||
const int32x4_t v_xy1 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x1lf, v_y1l), v_x1hf, v_y1h);
|
||||
|
||||
const float32x4_t v_xy0f = vec_float(v_xy0);
|
||||
const float32x4_t v_xy1f = vec_float(v_xy1);
|
||||
@@ -701,7 +701,7 @@ void ggml_vec_dot_q5_1_q8_1(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
const int8x16_t v_yl = vec_xl(0 , y0->qs);
|
||||
const int8x16_t v_yh = vec_xl(QK8_1/2, y0->qs);
|
||||
|
||||
const int32x4_t v_xy = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_xlf, v_yl), v_xhf, v_yh);
|
||||
const int32x4_t v_xy = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_xlf, v_yl), v_xhf, v_yh);
|
||||
const float32x4_t v_xyf = vec_float(v_xy);
|
||||
|
||||
const float32x4_t v_d = vec_splats(GGML_CPU_FP16_TO_FP32(x0->d) * GGML_CPU_FP16_TO_FP32(y0->d));
|
||||
@@ -751,7 +751,7 @@ void ggml_vec_dot_q8_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
const int8x16_t v_yl = vec_xl(0 , y[ib].qs);
|
||||
const int8x16_t v_yh = vec_xl(QK8_0/2, y[ib].qs);
|
||||
|
||||
const int32x4_t v_xy_ = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const int32x4_t v_xy_ = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const float32x4_t v_xy = vec_float(v_xy_);
|
||||
const float32x4_t v_d = vec_splats(GGML_CPU_FP16_TO_FP32(x[ib].d) * GGML_CPU_FP16_TO_FP32(y[ib].d));
|
||||
|
||||
@@ -855,10 +855,10 @@ void ggml_vec_dot_q3_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
q3bytes[2] = vec_sub((int8x16_t)vec_and(vec_sr(q3b[0], 2), v_3m), (int8x16_t)q3h[2]);
|
||||
q3bytes[3] = vec_sub((int8x16_t)vec_and(vec_sr(q3b[1], 2), v_3m), (int8x16_t)q3h[3]);
|
||||
|
||||
isum0 = ggml_vec_dot(v_z, q3bytes[0], q8bytes[0]);
|
||||
isum1 = ggml_vec_dot(v_z, q3bytes[1], q8bytes[1]);
|
||||
isum2 = ggml_vec_dot(v_z, q3bytes[2], q8bytes[2]);
|
||||
isum3 = ggml_vec_dot(v_z, q3bytes[3], q8bytes[3]);
|
||||
isum0 = ggml_vec_dot_s32(v_z, q3bytes[0], q8bytes[0]);
|
||||
isum1 = ggml_vec_dot_s32(v_z, q3bytes[1], q8bytes[1]);
|
||||
isum2 = ggml_vec_dot_s32(v_z, q3bytes[2], q8bytes[2]);
|
||||
isum3 = ggml_vec_dot_s32(v_z, q3bytes[3], q8bytes[3]);
|
||||
|
||||
isum += (isum0[0] + isum0[1] + isum0[2] + isum0[3]) * scale[0];
|
||||
isum += (isum1[0] + isum1[1] + isum1[2] + isum1[3]) * scale[1];
|
||||
@@ -877,10 +877,10 @@ void ggml_vec_dot_q3_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
q3bytes[2] = vec_sub((int8x16_t)vec_and(vec_sr(q3b[0], 6), v_3m), (int8x16_t)q3h[2]);
|
||||
q3bytes[3] = vec_sub((int8x16_t)vec_and(vec_sr(q3b[1], 6), v_3m), (int8x16_t)q3h[3]);
|
||||
|
||||
isum0 = ggml_vec_dot(v_z, q3bytes[0], q8bytes[4]);
|
||||
isum1 = ggml_vec_dot(v_z, q3bytes[1], q8bytes[5]);
|
||||
isum2 = ggml_vec_dot(v_z, q3bytes[2], q8bytes[6]);
|
||||
isum3 = ggml_vec_dot(v_z, q3bytes[3], q8bytes[7]);
|
||||
isum0 = ggml_vec_dot_s32(v_z, q3bytes[0], q8bytes[4]);
|
||||
isum1 = ggml_vec_dot_s32(v_z, q3bytes[1], q8bytes[5]);
|
||||
isum2 = ggml_vec_dot_s32(v_z, q3bytes[2], q8bytes[6]);
|
||||
isum3 = ggml_vec_dot_s32(v_z, q3bytes[3], q8bytes[7]);
|
||||
|
||||
isum += vec_hsum_i32x4(isum0) * scale[0];
|
||||
isum += vec_hsum_i32x4(isum1) * scale[1];
|
||||
@@ -981,7 +981,7 @@ void ggml_vec_dot_q4_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
v_xl[0] = (int8x16_t)vec_and(v_x[0], v_lm);
|
||||
v_xl[1] = (int8x16_t)vec_and(v_x[1], v_lm);
|
||||
|
||||
const int32x4_t p1 = ggml_vec_dot(ggml_vec_dot(v_z, v_xl[0], v_y[0]), v_xl[1], v_y[1]);
|
||||
const int32x4_t p1 = ggml_vec_dot_s32(ggml_vec_dot_s32(v_z, v_xl[0], v_y[0]), v_xl[1], v_y[1]);
|
||||
sumi1 += vec_hsum_i32x4(p1) * scales[2*j+0];
|
||||
|
||||
v_y[0] = vec_xl(0 , y0);
|
||||
@@ -991,7 +991,7 @@ void ggml_vec_dot_q4_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
v_xl[0] = (int8x16_t)vec_sr(v_x[0], 4);
|
||||
v_xl[1] = (int8x16_t)vec_sr(v_x[1], 4);
|
||||
|
||||
const int32x4_t p2 = ggml_vec_dot(ggml_vec_dot(v_z, v_xl[0], v_y[0]), v_xl[1], v_y[1]);
|
||||
const int32x4_t p2 = ggml_vec_dot_s32(ggml_vec_dot_s32(v_z, v_xl[0], v_y[0]), v_xl[1], v_y[1]);
|
||||
sumi2 += vec_hsum_i32x4(p2) * scales[2*j+1];
|
||||
}
|
||||
|
||||
@@ -1107,8 +1107,8 @@ void ggml_vec_dot_q5_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
q5b[2] = (int8x16_t)vec_or(vec_sr(v_xl[0], 4), q5h[2]);
|
||||
q5b[3] = (int8x16_t)vec_or(vec_sr(v_xl[1], 4), q5h[3]);
|
||||
|
||||
int32x4_t sumi0 = ggml_vec_dot(ggml_vec_dot(v_z, q5b[0], v_y[0]), q5b[1], v_y[1]);
|
||||
int32x4_t sumi1 = ggml_vec_dot(ggml_vec_dot(v_z, q5b[2], v_y[2]), q5b[3], v_y[3]);
|
||||
int32x4_t sumi0 = ggml_vec_dot_s32(ggml_vec_dot_s32(v_z, q5b[0], v_y[0]), q5b[1], v_y[1]);
|
||||
int32x4_t sumi1 = ggml_vec_dot_s32(ggml_vec_dot_s32(v_z, q5b[2], v_y[2]), q5b[3], v_y[3]);
|
||||
|
||||
sumi += vec_hsum_i32x4(sumi0) * *scales++;
|
||||
sumi += vec_hsum_i32x4(sumi1) * *scales++;
|
||||
@@ -1217,10 +1217,10 @@ void ggml_vec_dot_q6_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
q6b[2] = (int8x16_t)(vec_or(vec_and(v_xl[2], v_lm), q6h[2]));
|
||||
q6b[3] = (int8x16_t)(vec_or(vec_and(v_xl[3], v_lm), q6h[3]));
|
||||
|
||||
int32x4_t summs0 = ggml_vec_dot(v_z, q6b[0], v_y[0]);
|
||||
int32x4_t summs1 = ggml_vec_dot(v_z, q6b[1], v_y[1]);
|
||||
int32x4_t summs2 = ggml_vec_dot(v_z, q6b[2], v_y[2]);
|
||||
int32x4_t summs3 = ggml_vec_dot(v_z, q6b[3], v_y[3]);
|
||||
int32x4_t summs0 = ggml_vec_dot_s32(v_z, q6b[0], v_y[0]);
|
||||
int32x4_t summs1 = ggml_vec_dot_s32(v_z, q6b[1], v_y[1]);
|
||||
int32x4_t summs2 = ggml_vec_dot_s32(v_z, q6b[2], v_y[2]);
|
||||
int32x4_t summs3 = ggml_vec_dot_s32(v_z, q6b[3], v_y[3]);
|
||||
|
||||
isum += vec_hsum_i32x4(summs0) * scale[0] +
|
||||
vec_hsum_i32x4(summs1) * scale[1] +
|
||||
@@ -1251,10 +1251,10 @@ void ggml_vec_dot_q6_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
q6b[2] = (int8x16_t)(vec_or(vec_sr(v_xl[2], 4), q6h[2]));
|
||||
q6b[3] = (int8x16_t)(vec_or(vec_sr(v_xl[3], 4), q6h[3]));
|
||||
|
||||
summs0 = ggml_vec_dot(v_z, q6b[0], v_y[0]);
|
||||
summs1 = ggml_vec_dot(v_z, q6b[1], v_y[1]);
|
||||
summs2 = ggml_vec_dot(v_z, q6b[2], v_y[2]);
|
||||
summs3 = ggml_vec_dot(v_z, q6b[3], v_y[3]);
|
||||
summs0 = ggml_vec_dot_s32(v_z, q6b[0], v_y[0]);
|
||||
summs1 = ggml_vec_dot_s32(v_z, q6b[1], v_y[1]);
|
||||
summs2 = ggml_vec_dot_s32(v_z, q6b[2], v_y[2]);
|
||||
summs3 = ggml_vec_dot_s32(v_z, q6b[3], v_y[3]);
|
||||
|
||||
isum += vec_hsum_i32x4(summs0) * scale[0] +
|
||||
vec_hsum_i32x4(summs1) * scale[1] +
|
||||
@@ -1367,8 +1367,8 @@ void ggml_vec_dot_q6_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
||||
// q2u2 = vec_mul(q2u2, q2s2);
|
||||
// q2u3 = vec_mul(q2u3, q2s3);
|
||||
|
||||
// const int32x4_t p1 = ggml_vec_dot(ggml_vec_dot(vec_splat_s32(0), q2u0, q8b0), q2u1, q8b1);
|
||||
// const int32x4_t p2 = ggml_vec_dot(ggml_vec_dot(vec_splat_s32(0), q2u2, q8b2), q2u3, q8b3);
|
||||
// const int32x4_t p1 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splat_s32(0), q2u0, q8b0), q2u1, q8b1);
|
||||
// const int32x4_t p2 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splat_s32(0), q2u2, q8b2), q2u3, q8b3);
|
||||
|
||||
// sumf1 += (p1[0] + p1[1] + p1[2] + p1[3]) * (0.5f + (aux32[1] >> 28));
|
||||
// sumf2 += (p2[0] + p2[1] + p2[2] + p2[3]) * (0.5f + (aux32[3] >> 28));
|
||||
@@ -1445,7 +1445,7 @@ void ggml_vec_dot_iq4_nl_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const v
|
||||
|
||||
const int8x16_t v_yl = vec_xl(0 , y0->qs);
|
||||
const int8x16_t v_yh = vec_xl(QK8_0/2, y0->qs);
|
||||
const int32x4_t v_xy = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
const int32x4_t v_xy = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_xl, v_yl), v_xh, v_yh);
|
||||
|
||||
sumf += GGML_CPU_FP16_TO_FP32(x0->d) * GGML_CPU_FP16_TO_FP32(y0->d) * vec_hsum_i32x4(v_xy);
|
||||
}
|
||||
@@ -1508,8 +1508,8 @@ void ggml_vec_dot_iq4_xs_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const v
|
||||
const int8x16_t v_y3 = vec_xl(48, q8);
|
||||
q8 += 64;
|
||||
|
||||
int32x4_t vsumi0 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x0l, v_y0), v_x0h, v_y1);
|
||||
int32x4_t vsumi1 = ggml_vec_dot(ggml_vec_dot(vec_splats(0), v_x1l, v_y2), v_x1h, v_y3);
|
||||
int32x4_t vsumi0 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x0l, v_y0), v_x0h, v_y1);
|
||||
int32x4_t vsumi1 = ggml_vec_dot_s32(ggml_vec_dot_s32(vec_splats(0), v_x1l, v_y2), v_x1h, v_y3);
|
||||
|
||||
int ls1 = ((x[ibl].scales_l[ib] & 0xF) | ((h << 4) & 0x30)) - 32;
|
||||
int ls2 = ((x[ibl].scales_l[ib] >> 4) | ((h << 2) & 0x30)) - 32;
|
||||
|
||||
@@ -70,23 +70,6 @@ void ggml_quantize_mat_q8_0_4x4(const float * GGML_RESTRICT x, void * GGML_RESTR
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__VXE__) || defined(__VXE2__)
|
||||
static inline int16x8_t vxe_dot_acc(const int8x16_t v_x, const int8x16_t v_y, const int16x8_t v_acc) {
|
||||
return vec_meadd(v_x, v_y, vec_moadd(v_x, v_y, v_acc));
|
||||
}
|
||||
|
||||
static inline int8x16_t vxe_splat_granule(const int8_t * qs) {
|
||||
uint32_t g;
|
||||
memcpy(&g, qs, sizeof(g));
|
||||
return (int8x16_t)vec_splats(g);
|
||||
}
|
||||
|
||||
static inline int32x4_t vxe_fold(const int16x8_t v_sumi) {
|
||||
const int16x8_t v_ones = vec_splats((int16_t)1);
|
||||
return vec_add(vec_mule(v_sumi, v_ones), vec_mulo(v_sumi, v_ones));
|
||||
}
|
||||
#endif
|
||||
|
||||
void ggml_gemv_q4_0_4x4_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, const void * GGML_RESTRICT vy, int nr, int nc) {
|
||||
const int qk = QK8_0;
|
||||
const int nb = n / qk;
|
||||
@@ -131,21 +114,21 @@ void ggml_gemv_q4_0_4x4_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const vo
|
||||
|
||||
int16x8_t v_sumi = vec_splats((int16_t)0);
|
||||
|
||||
v_sumi = vxe_dot_acc(v_x0l, vxe_splat_granule(y_lo + 0), v_sumi);
|
||||
v_sumi = vxe_dot_acc(v_x1l, vxe_splat_granule(y_lo + 4), v_sumi);
|
||||
v_sumi = vxe_dot_acc(v_x2l, vxe_splat_granule(y_lo + 8), v_sumi);
|
||||
v_sumi = vxe_dot_acc(v_x3l, vxe_splat_granule(y_lo + 12), v_sumi);
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x0l, ggml_vec_xl_s8x4_splat(y_lo + 0));
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x1l, ggml_vec_xl_s8x4_splat(y_lo + 4));
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x2l, ggml_vec_xl_s8x4_splat(y_lo + 8));
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x3l, ggml_vec_xl_s8x4_splat(y_lo + 12));
|
||||
|
||||
v_sumi = vxe_dot_acc(v_x0h, vxe_splat_granule(y_hi + 0), v_sumi);
|
||||
v_sumi = vxe_dot_acc(v_x1h, vxe_splat_granule(y_hi + 4), v_sumi);
|
||||
v_sumi = vxe_dot_acc(v_x2h, vxe_splat_granule(y_hi + 8), v_sumi);
|
||||
v_sumi = vxe_dot_acc(v_x3h, vxe_splat_granule(y_hi + 12), v_sumi);
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x0h, ggml_vec_xl_s8x4_splat(y_hi + 0));
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x1h, ggml_vec_xl_s8x4_splat(y_hi + 4));
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x2h, ggml_vec_xl_s8x4_splat(y_hi + 8));
|
||||
v_sumi = ggml_vec_dot_s16(v_sumi, v_x3h, ggml_vec_xl_s8x4_splat(y_hi + 12));
|
||||
|
||||
const float32x4_t v_yd = vec_splats(GGML_CPU_FP16_TO_FP32(a_ptr[l].d));
|
||||
const float32x4_t v_xd = __lzs_f16cx4_load(b_ptr[l].d);
|
||||
const float32x4_t v_xd = ggml_vec_xl_f16x4_finite(b_ptr[l].d);
|
||||
const float32x4_t v_d = vec_mul(v_yd, v_xd);
|
||||
|
||||
v_sumf = vec_madd(vec_float(vxe_fold(v_sumi)), v_d, v_sumf);
|
||||
v_sumf = vec_madd(vec_float(vec_paddl_s16(v_sumi)), v_d, v_sumf);
|
||||
}
|
||||
|
||||
vec_xst(v_sumf, 0, res_ptr + x * ncols_interleaved);
|
||||
@@ -192,24 +175,24 @@ void ggml_gemm_q4_0_4x4_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const vo
|
||||
const int8_t * y_lo = a_ptr[l].qs + 16 * k;
|
||||
const int8_t * y_hi = y_lo + qk / 2 * 4;
|
||||
|
||||
v_sumi0 = vxe_dot_acc(v_xl, vxe_splat_granule(y_lo + 0), v_sumi0);
|
||||
v_sumi1 = vxe_dot_acc(v_xl, vxe_splat_granule(y_lo + 4), v_sumi1);
|
||||
v_sumi2 = vxe_dot_acc(v_xl, vxe_splat_granule(y_lo + 8), v_sumi2);
|
||||
v_sumi3 = vxe_dot_acc(v_xl, vxe_splat_granule(y_lo + 12), v_sumi3);
|
||||
v_sumi0 = ggml_vec_dot_s16(v_sumi0, v_xl, ggml_vec_xl_s8x4_splat(y_lo + 0));
|
||||
v_sumi1 = ggml_vec_dot_s16(v_sumi1, v_xl, ggml_vec_xl_s8x4_splat(y_lo + 4));
|
||||
v_sumi2 = ggml_vec_dot_s16(v_sumi2, v_xl, ggml_vec_xl_s8x4_splat(y_lo + 8));
|
||||
v_sumi3 = ggml_vec_dot_s16(v_sumi3, v_xl, ggml_vec_xl_s8x4_splat(y_lo + 12));
|
||||
|
||||
v_sumi0 = vxe_dot_acc(v_xh, vxe_splat_granule(y_hi + 0), v_sumi0);
|
||||
v_sumi1 = vxe_dot_acc(v_xh, vxe_splat_granule(y_hi + 4), v_sumi1);
|
||||
v_sumi2 = vxe_dot_acc(v_xh, vxe_splat_granule(y_hi + 8), v_sumi2);
|
||||
v_sumi3 = vxe_dot_acc(v_xh, vxe_splat_granule(y_hi + 12), v_sumi3);
|
||||
v_sumi0 = ggml_vec_dot_s16(v_sumi0, v_xh, ggml_vec_xl_s8x4_splat(y_hi + 0));
|
||||
v_sumi1 = ggml_vec_dot_s16(v_sumi1, v_xh, ggml_vec_xl_s8x4_splat(y_hi + 4));
|
||||
v_sumi2 = ggml_vec_dot_s16(v_sumi2, v_xh, ggml_vec_xl_s8x4_splat(y_hi + 8));
|
||||
v_sumi3 = ggml_vec_dot_s16(v_sumi3, v_xh, ggml_vec_xl_s8x4_splat(y_hi + 12));
|
||||
}
|
||||
|
||||
const float32x4_t v_yd = __lzs_f16cx4_load(a_ptr[l].d);
|
||||
const float32x4_t v_xd = __lzs_f16cx4_load(b_ptr[l].d);
|
||||
const float32x4_t v_xd = ggml_vec_xl_f16x4_finite(b_ptr[l].d);
|
||||
|
||||
v_sumf[0] = vec_madd(vec_float(vxe_fold(v_sumi0)), vec_mul(v_xd, vec_splat(v_yd, 0)), v_sumf[0]);
|
||||
v_sumf[1] = vec_madd(vec_float(vxe_fold(v_sumi1)), vec_mul(v_xd, vec_splat(v_yd, 1)), v_sumf[1]);
|
||||
v_sumf[2] = vec_madd(vec_float(vxe_fold(v_sumi2)), vec_mul(v_xd, vec_splat(v_yd, 2)), v_sumf[2]);
|
||||
v_sumf[3] = vec_madd(vec_float(vxe_fold(v_sumi3)), vec_mul(v_xd, vec_splat(v_yd, 3)), v_sumf[3]);
|
||||
v_sumf[0] = vec_madd(vec_float(vec_paddl_s16(v_sumi0)), vec_mul(v_xd, vec_splat(v_yd, 0)), v_sumf[0]);
|
||||
v_sumf[1] = vec_madd(vec_float(vec_paddl_s16(v_sumi1)), vec_mul(v_xd, vec_splat(v_yd, 1)), v_sumf[1]);
|
||||
v_sumf[2] = vec_madd(vec_float(vec_paddl_s16(v_sumi2)), vec_mul(v_xd, vec_splat(v_yd, 2)), v_sumf[2]);
|
||||
v_sumf[3] = vec_madd(vec_float(vec_paddl_s16(v_sumi3)), vec_mul(v_xd, vec_splat(v_yd, 3)), v_sumf[3]);
|
||||
}
|
||||
|
||||
for (int m = 0; m < 4; m++) {
|
||||
|
||||
@@ -443,6 +443,13 @@ inline static ggml_int8x16x4_t ggml_vec_xl_s8x4(const int8_t * ptr) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// load 4 bytes and replicate them into all 4 word lanes
|
||||
inline static int8x16_t ggml_vec_xl_s8x4_splat(const int8_t * ptr) {
|
||||
uint32_t tmp;
|
||||
memcpy(&tmp, ptr, sizeof(tmp));
|
||||
return (int8x16_t)vec_splats(tmp);
|
||||
}
|
||||
|
||||
typedef struct ggml_int16x8x2_t {
|
||||
int16x8_t val[2];
|
||||
} ggml_int16x8x2_t;
|
||||
@@ -456,31 +463,17 @@ inline static ggml_int16x8x2_t ggml_vec_xl_s16x2(const int16_t * ptr) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
! WARNING: Very slow. Use vec_perm if possible. Refer to iq4_xs
|
||||
! or iq4_nl for example implementation.
|
||||
*/
|
||||
inline static int8x16_t ggml_vec_tbl(int8x16_t a, uint8x16_t b) {
|
||||
int8x16_t res;
|
||||
// faster fp16 -> fp32 conversion using vector intrinsics
|
||||
// WARNING: fp16 inf converts into 65536.0f. use this only for finite values.
|
||||
inline static float32x4_t ggml_vec_xl_f16x4_finite(const ggml_fp16_t * x) {
|
||||
uint64_t tmp;
|
||||
memcpy(&tmp, x, sizeof(tmp));
|
||||
|
||||
res[ 0] = a[b[ 0]];
|
||||
res[ 1] = a[b[ 1]];
|
||||
res[ 2] = a[b[ 2]];
|
||||
res[ 3] = a[b[ 3]];
|
||||
res[ 4] = a[b[ 4]];
|
||||
res[ 5] = a[b[ 5]];
|
||||
res[ 6] = a[b[ 6]];
|
||||
res[ 7] = a[b[ 7]];
|
||||
res[ 8] = a[b[ 8]];
|
||||
res[ 9] = a[b[ 9]];
|
||||
res[10] = a[b[10]];
|
||||
res[11] = a[b[11]];
|
||||
res[12] = a[b[12]];
|
||||
res[13] = a[b[13]];
|
||||
res[14] = a[b[14]];
|
||||
res[15] = a[b[15]];
|
||||
|
||||
return res;
|
||||
const uint64x2_t v_x = { tmp, tmp };
|
||||
const uint32x4_t v_xh = vec_unpackh((uint16x8_t)v_x);
|
||||
const uint32x4_t v_bits = vec_sl(vec_and(v_xh, vec_splats((uint32_t)0x7FFF)), 13);
|
||||
const float32x4_t v_abs = vec_mul((float32x4_t)v_bits, vec_splats(0x1.0p+112f));
|
||||
return (float32x4_t)vec_or((uint32x4_t)v_abs, vec_sl(vec_and(v_xh, vec_splats((uint32_t)0x8000)), 16));
|
||||
}
|
||||
|
||||
inline static int16x8_t vec_padd_s16(int16x8_t a, int16x8_t b) {
|
||||
@@ -492,6 +485,11 @@ inline static int16x8_t vec_padd_s16(int16x8_t a, int16x8_t b) {
|
||||
return v_abo + v_abe;
|
||||
}
|
||||
|
||||
inline static int32x4_t vec_paddl_s16(int16x8_t a) {
|
||||
const int16x8_t v_ones = vec_splats((int16_t)1);
|
||||
return vec_mule(a, v_ones) + vec_mulo(a, v_ones);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/ggml-org/llama.cpp/pull/14037
|
||||
*/
|
||||
@@ -505,7 +503,11 @@ inline static int32_t vec_hsum_i32x4(int32x4_t v) {
|
||||
return v_temp[0] + v_temp[1];
|
||||
}
|
||||
|
||||
inline static int32x4_t ggml_vec_dot(int32x4_t acc, int8x16_t a, int8x16_t b) {
|
||||
inline static int16x8_t ggml_vec_dot_s16(int16x8_t acc, int8x16_t a, int8x16_t b) {
|
||||
return vec_meadd(a, b, vec_moadd(a, b, acc));
|
||||
}
|
||||
|
||||
inline static int32x4_t ggml_vec_dot_s32(int32x4_t acc, int8x16_t a, int8x16_t b) {
|
||||
const int16x8_t p = vec_mule(a, b) + vec_mulo(a, b);
|
||||
return acc + (vec_unpackh(p) + vec_unpackl(p));
|
||||
}
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
|
||||
@@ -244,16 +244,6 @@ static void tiled_run_microtile_avx2(const tiled_tile_src0 & src0, const tiled_t
|
||||
const int bs_stride = (nkr == 1) ? TILED_TILE_ROWS : TILED_MICRO;
|
||||
const int bs_off = seff * TILED_MICRO;
|
||||
|
||||
// Horizontal sum helper: converts 8x32-bit int lane inside a YMM register to scalar int32
|
||||
auto hsum256_epi32 = [](const __m256i v) -> int32_t {
|
||||
__m128i low = _mm256_castsi256_si128(v);
|
||||
__m128i high = _mm256_extracti128_si256(v, 1);
|
||||
__m128i sum128 = _mm_add_epi32(low, high);
|
||||
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_SHUFFLE(2, 3, 0, 1)));
|
||||
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_SHUFFLE(1, 0, 3, 2)));
|
||||
return _mm_cvtsi128_si32(sum128);
|
||||
};
|
||||
|
||||
for (int i = 0; i < TILED_MICRO; i++) {
|
||||
const int ar = i0 + i;
|
||||
const int d_off = seff * TILED_MICRO + ar;
|
||||
@@ -362,15 +352,20 @@ static void tiled_run_microtile_avx2(const tiled_tile_src0 & src0, const tiled_t
|
||||
}
|
||||
}
|
||||
|
||||
// OPTIMIZATION 2: Vectorized Epilogue with FMA3 gating
|
||||
// Reduce accumulators into a 256-bit vector across 8 columns
|
||||
alignas(32) int32_t s1_vals[GROUP];
|
||||
#pragma GCC unroll 8
|
||||
for (int t = 0; t < GROUP; t++) {
|
||||
s1_vals[t] = hsum256_epi32(acc[t]);
|
||||
}
|
||||
__m256i s1_vec = _mm256_load_si256((const __m256i *) s1_vals);
|
||||
// Reduce the eight accumulators to column sums in acc[0]..acc[7] order, with additions modulo 2^32.
|
||||
const __m256i ab = _mm256_hadd_epi32(acc[0], acc[1]);
|
||||
const __m256i cd = _mm256_hadd_epi32(acc[2], acc[3]);
|
||||
const __m256i ef = _mm256_hadd_epi32(acc[4], acc[5]);
|
||||
const __m256i gh = _mm256_hadd_epi32(acc[6], acc[7]);
|
||||
|
||||
// Each 128-bit half now holds four column sums over input lanes 0..3 or 4..7.
|
||||
const __m256i abcd = _mm256_hadd_epi32(ab, cd);
|
||||
const __m256i efgh = _mm256_hadd_epi32(ef, gh);
|
||||
const __m256i lo = _mm256_permute2x128_si256(abcd, efgh, 0x20);
|
||||
const __m256i hi = _mm256_permute2x128_si256(abcd, efgh, 0x31);
|
||||
__m256i s1_vec = _mm256_add_epi32(lo, hi);
|
||||
|
||||
// OPTIMIZATION 2: Vectorized Epilogue with FMA3 gating
|
||||
if constexpr (BIAS != 0 && ACTBIAS) {
|
||||
// act-bias correction: raw = desired + 128*sum_s scales[s]*w_bsum[s]; corr precomputed in repack_src0
|
||||
s1_vec = _mm256_sub_epi32(s1_vec, _mm256_set1_epi32(mins_row[0]));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <climits>
|
||||
#include <mutex>
|
||||
|
||||
namespace {
|
||||
namespace ggml_cuda_gated_delta_net_mma {
|
||||
constexpr int D = 128;
|
||||
using bf16 = nv_bfloat16;
|
||||
|
||||
@@ -417,6 +417,7 @@ static gdn_path select_gdn_mma_path(int device, const ggml_cuda_gdn_mma_args & a
|
||||
#endif // GGML_USE_HIP
|
||||
}
|
||||
} // namespace
|
||||
using namespace ggml_cuda_gated_delta_net_mma;
|
||||
|
||||
bool ggml_cuda_gdn_mma_launch(int device, const ggml_cuda_gdn_mma_args & a, cudaStream_t stream) {
|
||||
if (!a.state_out) {
|
||||
|
||||
+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
|
||||
|
||||
@@ -636,6 +636,11 @@ static bool ggml_metal_fusion_check_moe_reduce(
|
||||
if (!match.experts->data || !match.weights->data || !match.dst->data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// the fused kernel reads experts and weights while writing dst, so dst must not overlap either input
|
||||
if (ggml_metal_fusion_overlap(match.dst, match.experts) || ggml_metal_fusion_overlap(match.dst, match.weights)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -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>
|
||||
@@ -212,20 +212,10 @@ template <typename dst_t>
|
||||
static void dequantize_row_q4_K_sycl(const void *vx, dst_t *y, const int64_t k,
|
||||
dpct::queue_ptr stream) {
|
||||
const int64_t nb = k / QK_K;
|
||||
{
|
||||
dpct::has_capability_or_fail(stream->get_device(),
|
||||
{sycl::aspect::fp16});
|
||||
|
||||
stream->submit([&](sycl::handler &cgh) {
|
||||
sycl::local_accessor<uint8_t, 1> scale_local_acc(sycl::range<1>(12), cgh);
|
||||
cgh.parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, nb) *
|
||||
sycl::range<3>(1, 1, 32),
|
||||
sycl::range<3>(1, 1, 32)),
|
||||
[=](sycl::nd_item<3> item_ct1) {
|
||||
dequantize_block_q4_K(vx, y, get_pointer(scale_local_acc), item_ct1);
|
||||
});
|
||||
});
|
||||
}
|
||||
dpct::has_capability_or_fail(stream->get_device(), { sycl::aspect::fp16 });
|
||||
stream->parallel_for(sycl::nd_range<1>(sycl::range<1>((size_t) nb * SYCL_DEQK_WI_PER_BLOCK),
|
||||
sycl::range<1>((size_t) SYCL_DEQK_WI_PER_BLOCK)),
|
||||
[=](sycl::nd_item<1> it) { dequantize_block_q4_K_wide(vx, y, nb, it); });
|
||||
}
|
||||
|
||||
template <typename dst_t>
|
||||
@@ -250,32 +240,10 @@ template <typename dst_t>
|
||||
static void dequantize_row_q5_K_sycl(const void *vx, dst_t *y, const int64_t k,
|
||||
dpct::queue_ptr stream) {
|
||||
const int64_t nb = k / QK_K;
|
||||
#if QK_K == 256
|
||||
{
|
||||
dpct::has_capability_or_fail(stream->get_device(),
|
||||
{sycl::aspect::fp16});
|
||||
|
||||
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, nb) *
|
||||
sycl::range<3>(1, 1, 64),
|
||||
sycl::range<3>(1, 1, 64)),
|
||||
[=](sycl::nd_item<3> item_ct1) {
|
||||
dequantize_block_q5_K(vx, y, item_ct1);
|
||||
});
|
||||
}
|
||||
#else
|
||||
{
|
||||
dpct::has_capability_or_fail(stream->get_device(),
|
||||
{sycl::aspect::fp16});
|
||||
|
||||
stream->parallel_for(sycl::nd_range<3>(sycl::range<3>(1, 1, nb) *
|
||||
sycl::range<3>(1, 1, 32),
|
||||
sycl::range<3>(1, 1, 32)),
|
||||
[=](sycl::nd_item<3> item_ct1) {
|
||||
dequantize_block_q5_K(vx, y, item_ct1);
|
||||
});
|
||||
}
|
||||
|
||||
#endif
|
||||
dpct::has_capability_or_fail(stream->get_device(), { sycl::aspect::fp16 });
|
||||
stream->parallel_for(sycl::nd_range<1>(sycl::range<1>((size_t) nb * SYCL_DEQK_WI_PER_BLOCK),
|
||||
sycl::range<1>((size_t) SYCL_DEQK_WI_PER_BLOCK)),
|
||||
[=](sycl::nd_item<1> it) { dequantize_block_q5_K_wide(vx, y, nb, it); });
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
+113
-120
@@ -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>
|
||||
@@ -1108,6 +1090,53 @@ static inline void get_scale_min_k4(int j, const uint8_t * q, uint8_t & d, uint8
|
||||
}
|
||||
#endif
|
||||
|
||||
// Work-items that cover one 256-weight K-quant block: four 64-element chunks, 8 work-items
|
||||
// per chunk, 4 qs bytes per work-item.
|
||||
static constexpr int SYCL_DEQK_WI_PER_BLOCK = 32;
|
||||
|
||||
template <typename dst_t>
|
||||
static inline void dequantize_q4_K_wide_one(dst_t * __restrict__ y, const uint8_t * __restrict__ qs,
|
||||
const uint8_t * __restrict__ scales, const float dall,
|
||||
const float dmin, const int tid) {
|
||||
const int il = tid >> 3; // 64-element chunk, 0..3
|
||||
const int ir = (tid & 7) * 4; // 4 qs bytes within the chunk
|
||||
|
||||
uint8_t sc, m;
|
||||
get_scale_min_k4(2 * il + 0, scales, sc, m);
|
||||
const float d1 = dall * sc;
|
||||
const float m1 = dmin * m;
|
||||
get_scale_min_k4(2 * il + 1, scales, sc, m);
|
||||
const float d2 = dall * sc;
|
||||
const float m2 = dmin * m;
|
||||
|
||||
const sycl::vec<uint8_t, 4> q = vec_aligned_load<uint8_t, 4>(qs + 32 * il + ir);
|
||||
|
||||
sycl::vec<dst_t, 4> lo, hi;
|
||||
#pragma unroll
|
||||
for (int l = 0; l < 4; ++l) {
|
||||
lo[l] = d1 * (q[l] & 0xF) - m1;
|
||||
hi[l] = d2 * (q[l] >> 4) - m2;
|
||||
}
|
||||
dst_t * yb = y + 64 * il + ir;
|
||||
*reinterpret_cast<sycl::vec<dst_t, 4> *>(yb + 0) = lo;
|
||||
*reinterpret_cast<sycl::vec<dst_t, 4> *>(yb + 32) = hi;
|
||||
}
|
||||
|
||||
template <typename dst_t>
|
||||
static void dequantize_block_q4_K_wide(const void * __restrict__ vx, dst_t * __restrict__ yy, int64_t nb,
|
||||
const sycl::nd_item<1> & it) {
|
||||
static_assert(QK_K == 256, "wide K-quant dequant assumes QK_K == 256");
|
||||
const int64_t g = it.get_global_id(0);
|
||||
const int64_t b = g / SYCL_DEQK_WI_PER_BLOCK;
|
||||
const int tid = (int) (g % SYCL_DEQK_WI_PER_BLOCK);
|
||||
if (b >= nb) {
|
||||
return;
|
||||
}
|
||||
const block_q4_K * x = (const block_q4_K *) vx + b;
|
||||
dequantize_q4_K_wide_one(yy + b * QK_K, x->qs, x->scales, (float) x->dm[0], (float) x->dm[1], tid);
|
||||
}
|
||||
|
||||
|
||||
template <typename dst_t>
|
||||
inline void dequantize_q4_K_common(dst_t * __restrict__ y, const uint8_t * __restrict__ qs_ptr, const float dall,
|
||||
const float dmin, uint8_t * __restrict__ scales_local, int il, int ir) {
|
||||
@@ -1130,41 +1159,6 @@ inline void dequantize_q4_K_common(dst_t * __restrict__ y, const uint8_t * __res
|
||||
}
|
||||
}
|
||||
|
||||
template<typename dst_t>
|
||||
static void dequantize_block_q4_K(const void * __restrict__ vx, dst_t * __restrict__ yy,
|
||||
uint8_t* scales_local, const sycl::nd_item<3> &item_ct1) {
|
||||
const block_q4_K * x = (const block_q4_K *) vx;
|
||||
|
||||
const int64_t i = item_ct1.get_group(2);
|
||||
|
||||
#if QK_K == 256
|
||||
const int64_t tid = item_ct1.get_local_id(2);
|
||||
const int64_t il = tid / 8;
|
||||
const int64_t ir = tid % 8;
|
||||
|
||||
dst_t * y = yy + i * QK_K + 64 * il + 4 * ir;
|
||||
|
||||
const sycl::half2 dm = x[i].dm;
|
||||
const float dall = dm[0];
|
||||
const float dmin = dm[1];
|
||||
|
||||
if (tid < 12) {
|
||||
scales_local[tid] = x[i].scales[tid];
|
||||
}
|
||||
|
||||
item_ct1.barrier(sycl::access::fence_space::local_space);
|
||||
dequantize_q4_K_common(y, x[i].qs, dall, dmin, scales_local, il, ir);
|
||||
#else
|
||||
const int64_t tid = item_ct1.get_local_id(2);
|
||||
const uint8_t * q = x[i].qs;
|
||||
dst_t * y = yy + i*QK_K;
|
||||
const float d = (float)x[i].dm[0];
|
||||
const float m = (float)x[i].dm[1];
|
||||
y[tid+ 0] = d * (x[i].scales[0] & 0xF) * (q[tid] & 0xF) - m * (x[i].scales[0] >> 4);
|
||||
y[tid+32] = d * (x[i].scales[1] & 0xF) * (q[tid] >> 4) - m * (x[i].scales[1] >> 4);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename dst_t>
|
||||
static void dequantize_block_q4_K_reorder(const void * __restrict__ vx, dst_t * __restrict__ yy, uint8_t * scales_local,
|
||||
const sycl::nd_item<1> & item_ct1, int64_t nb) {
|
||||
@@ -1195,52 +1189,51 @@ static void dequantize_block_q4_K_reorder(const void * __restrict__ vx, dst_t *
|
||||
dequantize_q4_K_common(y, qs_ptr, dall, dmin, scales_local, il, ir);
|
||||
}
|
||||
|
||||
template<typename dst_t>
|
||||
static void dequantize_block_q5_K(const void * __restrict__ vx, dst_t * __restrict__ yy,
|
||||
const sycl::nd_item<3> &item_ct1) {
|
||||
const block_q5_K * x = (const block_q5_K *) vx;
|
||||
|
||||
const int64_t i = item_ct1.get_group(2);
|
||||
|
||||
#if QK_K == 256
|
||||
// assume 64 threads - this is very slightly better than the one below
|
||||
const int64_t tid = item_ct1.get_local_id(2);
|
||||
const int64_t il = tid/16; // il is in 0...3
|
||||
const int64_t ir = tid%16; // ir is in 0...15
|
||||
const int64_t is = 2*il; // is is in 0...6
|
||||
|
||||
dst_t * y = yy + i*QK_K + 64*il + 2*ir;
|
||||
|
||||
const float dall = x[i].dm[0];
|
||||
const float dmin = x[i].dm[1];
|
||||
|
||||
const uint8_t * ql = x[i].qs + 32*il + 2*ir;
|
||||
const uint8_t * qh = x[i].qh + 2*ir;
|
||||
template <typename dst_t>
|
||||
static inline void dequantize_q5_K_wide_one(dst_t * __restrict__ y, const uint8_t * __restrict__ qs,
|
||||
const uint8_t * __restrict__ qh_ptr,
|
||||
const uint8_t * __restrict__ scales, const float dall,
|
||||
const float dmin, const int tid) {
|
||||
const int il = tid >> 3; // 64-element chunk, 0..3
|
||||
const int ir = (tid & 7) * 4; // 4 qs bytes within the chunk; qh holds the 5th bit
|
||||
|
||||
uint8_t sc, m;
|
||||
get_scale_min_k4(is + 0, x[i].scales, sc, m);
|
||||
const float d1 = dall * sc; const float m1 = dmin * m;
|
||||
get_scale_min_k4(is + 1, x[i].scales, sc, m);
|
||||
const float d2 = dall * sc; const float m2 = dmin * m;
|
||||
get_scale_min_k4(2 * il + 0, scales, sc, m);
|
||||
const float d1 = dall * sc;
|
||||
const float m1 = dmin * m;
|
||||
get_scale_min_k4(2 * il + 1, scales, sc, m);
|
||||
const float d2 = dall * sc;
|
||||
const float m2 = dmin * m;
|
||||
|
||||
uint8_t hm = 1 << (2*il);
|
||||
y[ 0] = d1 * ((ql[ 0] & 0xF) + (qh[ 0] & hm ? 16 : 0)) - m1;
|
||||
y[ 1] = d1 * ((ql[ 1] & 0xF) + (qh[ 1] & hm ? 16 : 0)) - m1;
|
||||
hm <<= 1;
|
||||
y[32] = d2 * ((ql[ 0] >> 4) + (qh[ 0] & hm ? 16 : 0)) - m2;
|
||||
y[33] = d2 * ((ql[ 1] >> 4) + (qh[ 1] & hm ? 16 : 0)) - m2;
|
||||
#else
|
||||
const int64_t tid = item_ct1.get_local_id(2);
|
||||
const uint8_t q = x[i].qs[tid];
|
||||
const int64_t im = tid/8; // 0...3
|
||||
const int64_t in = tid%8; // 0...7
|
||||
const int64_t is = tid/16; // 0 or 1
|
||||
const uint8_t h = x[i].qh[in] >> im;
|
||||
const float d = x[i].d;
|
||||
dst_t * y = yy + i*QK_K + tid;
|
||||
y[ 0] = d * x[i].scales[is+0] * ((q & 0xF) - ((h >> 0) & 1 ? 0 : 16));
|
||||
y[32] = d * x[i].scales[is+2] * ((q >> 4) - ((h >> 4) & 1 ? 0 : 16));
|
||||
#endif
|
||||
const sycl::vec<uint8_t, 4> q = vec_aligned_load<uint8_t, 4>(qs + 32 * il + ir);
|
||||
const sycl::vec<uint8_t, 4> h = vec_aligned_load<uint8_t, 4>(qh_ptr + ir);
|
||||
|
||||
const uint8_t hm1 = 1 << (2 * il);
|
||||
const uint8_t hm2 = hm1 << 1;
|
||||
|
||||
sycl::vec<dst_t, 4> lo, hi;
|
||||
#pragma unroll
|
||||
for (int l = 0; l < 4; ++l) {
|
||||
lo[l] = d1 * ((q[l] & 0xF) + ((h[l] & hm1) ? 16 : 0)) - m1;
|
||||
hi[l] = d2 * ((q[l] >> 4) + ((h[l] & hm2) ? 16 : 0)) - m2;
|
||||
}
|
||||
dst_t * yb = y + 64 * il + ir;
|
||||
*reinterpret_cast<sycl::vec<dst_t, 4> *>(yb + 0) = lo;
|
||||
*reinterpret_cast<sycl::vec<dst_t, 4> *>(yb + 32) = hi;
|
||||
}
|
||||
|
||||
template <typename dst_t>
|
||||
static void dequantize_block_q5_K_wide(const void * __restrict__ vx, dst_t * __restrict__ yy, int64_t nb,
|
||||
const sycl::nd_item<1> & it) {
|
||||
static_assert(QK_K == 256, "wide K-quant dequant assumes QK_K == 256");
|
||||
const int64_t g = it.get_global_id(0);
|
||||
const int64_t b = g / SYCL_DEQK_WI_PER_BLOCK;
|
||||
const int tid = (int) (g % SYCL_DEQK_WI_PER_BLOCK);
|
||||
if (b >= nb) {
|
||||
return;
|
||||
}
|
||||
const block_q5_K * x = (const block_q5_K *) vx + b;
|
||||
dequantize_q5_K_wide_one(yy + b * QK_K, x->qs, x->qh, x->scales, (float) x->dm[0], (float) x->dm[1], tid);
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "ggml-vulkan-common.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
@@ -363,21 +362,16 @@ void ggml_vk_host_get(const vk_device& device, const void * ptr, vk_buffer& buf,
|
||||
vk_host_allocation & entry = vk_instance.host_allocations[index];
|
||||
|
||||
if (entry.views[device->idx] == nullptr) {
|
||||
if (!vk_instance.multi_device) {
|
||||
return;
|
||||
}
|
||||
if (!device->external_memory_host) {
|
||||
static std::atomic<uint32_t> warned_mask{0};
|
||||
const uint32_t bit = 1u << device->idx;
|
||||
if (!(warned_mask.fetch_or(bit) & bit)) {
|
||||
GGML_LOG_WARN("ggml_vulkan: %s does not support VK_EXT_external_memory_host; host memory "
|
||||
"will be staged, not pinned, on this device\n", device->name.c_str());
|
||||
}
|
||||
const uint32_t bit = 1u << device->idx;
|
||||
if (!vk_instance.multi_device || !vk_instance.device_host_buffer_import[device->idx] || (vk_instance.host_import_failed & bit)) {
|
||||
return;
|
||||
}
|
||||
GGML_ASSERT(device->external_memory_host);
|
||||
// const only propagates from the const context; the pointee is mutable
|
||||
entry.views[device->idx] = ggml_vk_buffer_from_host_ptr(const_cast<vk_device&>(device), entry.base, entry.size);
|
||||
if (entry.views[device->idx] == nullptr) {
|
||||
vk_instance.host_import_failed |= bit;
|
||||
GGML_LOG_WARN("ggml_vulkan: %s: host memory import failed, using staging\n", device->name.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1329,12 +1329,14 @@ struct vk_instance_t {
|
||||
|
||||
std::vector<size_t> device_indices;
|
||||
std::vector<bool> device_supports_membudget;
|
||||
std::vector<bool> device_host_buffer_import;
|
||||
vk_device devices[GGML_VK_MAX_DEVICES];
|
||||
|
||||
std::vector<vk_host_allocation> host_allocations;
|
||||
std::shared_mutex host_allocations_mutex;
|
||||
|
||||
size_t host_import_alignment = 0;
|
||||
uint32_t host_import_failed = 0; // bitmask by device->idx, guarded by host_allocations_mutex
|
||||
|
||||
bool multi_device = false;
|
||||
};
|
||||
|
||||
@@ -3987,6 +3987,17 @@ void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool ggml_vk_external_memory_host_usable(vk::DriverId driver_id) {
|
||||
// Disable external_memory_host until https://github.com/KhronosGroup/MoltenVK/pull/2622
|
||||
// is available in the Vulkan SDK.
|
||||
return driver_id != vk::DriverId::eMoltenvk;
|
||||
}
|
||||
|
||||
static bool ggml_vk_host_buffer_import_usable(uint32_t vendor_id) {
|
||||
// amdgpu re-faults all pages of every imported host allocation on each submission
|
||||
return vendor_id != VK_VENDOR_ID_AMD;
|
||||
}
|
||||
|
||||
vk_device ggml_vk_get_device(size_t idx) {
|
||||
VK_LOG_DEBUG("ggml_vk_get_device(" << idx << ")");
|
||||
|
||||
@@ -4174,11 +4185,7 @@ vk_device ggml_vk_get_device(size_t idx) {
|
||||
device->vendor_id = device->properties.vendorID;
|
||||
device->driver_id = driver_props.driverID;
|
||||
|
||||
if (device->driver_id == vk::DriverId::eMoltenvk) {
|
||||
// Disable external_memory_host until https://github.com/KhronosGroup/MoltenVK/pull/2622
|
||||
// is available in the Vulkan SDK.
|
||||
device->external_memory_host = false;
|
||||
}
|
||||
device->external_memory_host = device->external_memory_host && ggml_vk_external_memory_host_usable(device->driver_id);
|
||||
|
||||
// Implementing the async backend interfaces seems broken on older Intel HW,
|
||||
// see https://github.com/ggml-org/llama.cpp/issues/17302.
|
||||
@@ -5421,7 +5428,7 @@ void ggml_vk_instance_init() {
|
||||
}
|
||||
GGML_LOG_DEBUG("ggml_vulkan: Found %zu Vulkan devices:\n", vk_instance.device_indices.size());
|
||||
|
||||
bool all_external_memory_host = vk_instance.device_indices.size() > 1;
|
||||
size_t host_buffer_import_count = 0;
|
||||
for (size_t i = 0; i < vk_instance.device_indices.size(); i++) {
|
||||
vk::PhysicalDevice vkdev = devices[vk_instance.device_indices[i]];
|
||||
std::vector<vk::ExtensionProperties> extensionprops = vkdev.enumerateDeviceExtensionProperties();
|
||||
@@ -5438,20 +5445,27 @@ void ggml_vk_instance_init() {
|
||||
|
||||
vk_instance.device_supports_membudget.push_back(membudget_supported);
|
||||
|
||||
bool host_buffer_import = false;
|
||||
if (external_memory_host_supported) {
|
||||
vk::PhysicalDeviceProperties2 props2;
|
||||
vk::PhysicalDeviceDriverProperties driver_props;
|
||||
vk::PhysicalDeviceExternalMemoryHostPropertiesEXT ext_props;
|
||||
props2.pNext = &ext_props;
|
||||
props2.pNext = &driver_props;
|
||||
driver_props.pNext = &ext_props;
|
||||
vkdev.getProperties2(&props2);
|
||||
vk_instance.host_import_alignment = std::max<size_t>(vk_instance.host_import_alignment, ext_props.minImportedHostPointerAlignment);
|
||||
} else {
|
||||
all_external_memory_host = false;
|
||||
host_buffer_import = ggml_vk_external_memory_host_usable(driver_props.driverID) &&
|
||||
ggml_vk_host_buffer_import_usable(props2.properties.vendorID);
|
||||
if (host_buffer_import) {
|
||||
vk_instance.host_import_alignment = std::max<size_t>(vk_instance.host_import_alignment, ext_props.minImportedHostPointerAlignment);
|
||||
host_buffer_import_count++;
|
||||
}
|
||||
}
|
||||
vk_instance.device_host_buffer_import.push_back(host_buffer_import);
|
||||
|
||||
ggml_vk_print_gpu_info(i);
|
||||
}
|
||||
|
||||
vk_instance.multi_device = all_external_memory_host;
|
||||
vk_instance.multi_device = host_buffer_import_count > 1 && vk_instance.device_host_buffer_import[0];
|
||||
}
|
||||
|
||||
void ggml_vk_init(ggml_backend_vk_context * ctx, size_t idx) {
|
||||
@@ -15858,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"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user