mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-08-20 19:52:43 +02:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b4fa88a6c | |||
| 521a64cd01 | |||
| 681c29d36a | |||
| 07822bddf8 | |||
| 78ec4c3780 | |||
| 63b64a50a3 | |||
| bf0040e15f | |||
| 9855ad69d3 | |||
| 8a832e4bf3 | |||
| 2b5621094e | |||
| dc64a1620e | |||
| 70aff25250 | |||
| f20395dae5 | |||
| 8497981321 | |||
| a3b1effcda | |||
| d9b6be07d0 | |||
| 929d47a391 | |||
| f466cfa38f | |||
| 2cfdb5fc08 | |||
| 9ee9fc04c1 | |||
| d59d455fd8 | |||
| 990e3bfee3 | |||
| b062ba735e | |||
| cd644c3954 | |||
| 947fd9bb2b | |||
| ee0ea03adf | |||
| dc72703fc6 | |||
| b95502ba9a | |||
| 3e7344670a | |||
| 7221e24f57 | |||
| 6cc504a2e9 | |||
| 01ac3ad761 | |||
| 2e92ecd024 | |||
| 645ca2834b | |||
| fe8156f789 | |||
| 77acca437f | |||
| 95c409c136 | |||
| 8ef78e644f |
@@ -119,6 +119,7 @@ jobs:
|
||||
./bin/llama-completion -m stories260K.gguf -p "One day, Lily met a Shoggoth" -n 500 -c 256
|
||||
|
||||
windows:
|
||||
name: windows / ${{ matrix.build }}
|
||||
runs-on: windows-2025
|
||||
|
||||
env:
|
||||
@@ -130,13 +131,13 @@ jobs:
|
||||
include:
|
||||
- build: 'x64-cpu-static'
|
||||
arch: 'x64'
|
||||
defines: '-G "Ninja Multi-Config" -D CMAKE_TOOLCHAIN_FILE=cmake/x64-windows-llvm.cmake -DGGML_NATIVE=OFF -DLLAMA_BUILD_SERVER=ON -DGGML_RPC=ON -DBUILD_SHARED_LIBS=OFF'
|
||||
defines: '-G "Ninja Multi-Config" -D CMAKE_TOOLCHAIN_FILE=cmake/x64-windows-llvm.cmake -DGGML_NATIVE=OFF -DGGML_OPENMP_FETCH=ON -DLLAMA_BUILD_SERVER=ON -DGGML_RPC=ON -DBUILD_SHARED_LIBS=OFF'
|
||||
- build: 'x64-openblas'
|
||||
arch: 'x64'
|
||||
defines: '-G "Ninja Multi-Config" -D CMAKE_TOOLCHAIN_FILE=cmake/x64-windows-llvm.cmake -DGGML_NATIVE=OFF -DLLAMA_BUILD_SERVER=ON -DGGML_RPC=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON -DGGML_OPENMP=OFF -DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS -DBLAS_INCLUDE_DIRS="$env:RUNNER_TEMP/openblas/include" -DBLAS_LIBRARIES="$env:RUNNER_TEMP/openblas/lib/openblas.lib"'
|
||||
- build: 'arm64'
|
||||
arch: 'arm64'
|
||||
defines: '-G "Ninja Multi-Config" -D CMAKE_TOOLCHAIN_FILE=cmake/arm64-windows-llvm.cmake -DGGML_NATIVE=OFF -DLLAMA_BUILD_SERVER=ON'
|
||||
defines: '-G "Ninja Multi-Config" -D CMAKE_TOOLCHAIN_FILE=cmake/arm64-windows-llvm.cmake -DGGML_NATIVE=OFF -DGGML_OPENMP_FETCH=ON -DLLAMA_BUILD_SERVER=ON'
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
|
||||
@@ -681,6 +681,7 @@ jobs:
|
||||
name: llama-bin-win-openvino-${{ env.OPENVINO_VERSION_MAJOR }}-x64.zip
|
||||
|
||||
windows-cpu:
|
||||
name: windows-cpu / ${{ matrix.arch }}
|
||||
needs: [check-release]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
@@ -728,6 +729,7 @@ jobs:
|
||||
-DGGML_BACKEND_DL=ON ^
|
||||
-DGGML_CPU_ALL_VARIANTS=${{ matrix.arch == 'x64' && 'ON' || 'OFF' }} ^
|
||||
-DGGML_OPENMP=ON ^
|
||||
-DGGML_OPENMP_FETCH=ON ^
|
||||
${{ env.CMAKE_ARGS }}
|
||||
cmake --build build --config Release
|
||||
|
||||
@@ -739,7 +741,6 @@ jobs:
|
||||
- name: Pack artifacts
|
||||
id: pack_artifacts
|
||||
run: |
|
||||
Copy-Item "C:\Program Files\Microsoft Visual Studio\18\Enterprise\VC\Redist\MSVC\14.51.36231\debug_nonredist\${{ matrix.arch }}\Microsoft.VC145.OpenMP.LLVM\libomp140.${{ matrix.arch == 'x64' && 'x86_64' || 'aarch64' }}.dll" .\build\bin\Release\
|
||||
7z a -snl llama-bin-win-cpu-${{ matrix.arch }}.zip .\build\bin\Release\*
|
||||
|
||||
- name: Upload artifacts
|
||||
@@ -1579,14 +1580,14 @@ jobs:
|
||||
- windows
|
||||
- windows-cpu
|
||||
- windows-cuda
|
||||
#- windows-sycl
|
||||
- windows-sycl
|
||||
- windows-rocm
|
||||
- windows-openvino
|
||||
#- ubuntu-22-rocm
|
||||
- ubuntu-cpu
|
||||
- ubuntu-vulkan
|
||||
- ubuntu-24-openvino
|
||||
#- ubuntu-24-sycl
|
||||
- ubuntu-24-sycl
|
||||
- android-arm64
|
||||
- macos-cpu
|
||||
- ios-xcode
|
||||
@@ -1665,6 +1666,7 @@ jobs:
|
||||
tar -czvf release/llama-${{ steps.tag.outputs.name }}-ui.tar.gz --transform "s,^\.,llama-${{ steps.tag.outputs.name }}," -C ./ui-dist .
|
||||
|
||||
- name: Attest release artifacts
|
||||
id: attest
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: 'release/*'
|
||||
@@ -1696,6 +1698,9 @@ jobs:
|
||||
**Website:**
|
||||
- <https://llama.app>
|
||||
|
||||
**Attestations:**
|
||||
- <${{ steps.attest.outputs.attestation-url }}>
|
||||
|
||||
**macOS/iOS:**
|
||||
- [macOS Apple Silicon (arm64)](https://github.com/ggml-org/llama.cpp/releases/download/${{ steps.tag.outputs.name }}/llama-${{ steps.tag.outputs.name }}-bin-macos-arm64.tar.gz)
|
||||
- macOS Apple Silicon (arm64, KleidiAI enabled) [DISABLED](https://github.com/ggml-org/llama.cpp/pull/23780)
|
||||
|
||||
@@ -9,6 +9,7 @@ General:
|
||||
|
||||
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>`
|
||||
|
||||
@@ -8,6 +8,7 @@ set( CMAKE_CXX_COMPILER clang++ )
|
||||
|
||||
set( CMAKE_C_COMPILER_TARGET ${target} )
|
||||
set( CMAKE_CXX_COMPILER_TARGET ${target} )
|
||||
set( CMAKE_ASM_COMPILER_TARGET ${target} )
|
||||
|
||||
set( arch_c_flags "-march=armv8.7-a -fvectorize -ffp-model=fast -fno-finite-math-only" )
|
||||
set( warn_c_flags "-Wno-format -Wno-unused-variable -Wno-unused-function -Wno-gnu-zero-variadic-macro-arguments" )
|
||||
|
||||
@@ -2595,6 +2595,26 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
||||
params.mmproj_use_gpu = value;
|
||||
}
|
||||
).set_examples(mmproj_examples).set_env("LLAMA_ARG_MMPROJ_OFFLOAD"));
|
||||
add_opt(common_arg(
|
||||
// note: "-mmdev" must sort after "--rpc" in the preset map, else RPC devices are not registered yet
|
||||
{"-mmdev", "--mmproj-device"}, "DEVICE",
|
||||
"device to use for multimodal projector (none = don't offload, default: auto)\n"
|
||||
"use --list-devices to see a list of available devices",
|
||||
[](common_params & params, const std::string & value) {
|
||||
if (value == "none") {
|
||||
params.mmproj_use_gpu = false;
|
||||
params.mmproj_device = nullptr;
|
||||
return;
|
||||
}
|
||||
auto devices = parse_device_list(value);
|
||||
// parse_device_list pushes nullptr at back so devices is length 2 for single device.
|
||||
if (devices.size() > 2) {
|
||||
throw std::invalid_argument("only one device may be specified for mmproj");
|
||||
}
|
||||
params.mmproj_use_gpu = true;
|
||||
params.mmproj_device = devices.front();
|
||||
}
|
||||
).set_examples(mmproj_examples).set_env("MTMD_BACKEND_DEVICE")); // no LLAMA_ARG_ prefix for backward compatibility reason
|
||||
add_opt(common_arg(
|
||||
{"--image", "--audio", "--video"}, "FILE",
|
||||
"path to an image, audio, or video file. use with multimodal models, use comma-separated values for multiple files\n",
|
||||
|
||||
+3
-15
@@ -1750,18 +1750,6 @@ struct ggml_threadpool_params ggml_threadpool_params_from_cpu_params(const commo
|
||||
return tpp;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool can_share_threadpool(const ggml_threadpool_params & tpp1, const ggml_threadpool_params & tpp2) {
|
||||
// n_threads does not matter -> we'll use what's larger
|
||||
ggml_threadpool_params tpp_comparison = tpp1;
|
||||
tpp_comparison.n_threads = tpp2.n_threads;
|
||||
|
||||
return ggml_threadpool_params_match(&tpp_comparison, &tpp2);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
common_threadpools::~common_threadpools() {
|
||||
if (!free_fn) {
|
||||
return;
|
||||
@@ -1790,9 +1778,9 @@ void common_threadpools::init(llama_context * ctx, const common_params & params)
|
||||
struct ggml_threadpool_params tpp =
|
||||
ggml_threadpool_params_from_cpu_params(params.cpuparams);
|
||||
|
||||
if (can_share_threadpool(tpp, tpp_batch)) {
|
||||
tpp.n_threads = std::max(tpp.n_threads, tpp_batch.n_threads);
|
||||
} else {
|
||||
// each pool needs to match the respective n_threads exactly
|
||||
// see: https://github.com/ggml-org/llama.cpp/pull/27138#issuecomment-5332307332
|
||||
if (!ggml_threadpool_params_match(&tpp, &tpp_batch)) {
|
||||
threadpool_batch = ggml_threadpool_new_fn(&tpp_batch);
|
||||
if (!threadpool_batch) {
|
||||
COM_WRN("batch threadpool create failed : n_threads %d\n", tpp_batch.n_threads);
|
||||
|
||||
+4
-3
@@ -581,9 +581,10 @@ struct common_params {
|
||||
|
||||
// multimodal models (see tools/mtmd)
|
||||
struct common_params_model mmproj;
|
||||
bool mmproj_use_gpu = true; // use GPU for multimodal model
|
||||
bool no_mmproj = false; // explicitly disable multimodal model
|
||||
std::vector<std::string> image; // path to image file(s) ; TODO: change the name to "media"
|
||||
bool mmproj_use_gpu = true; // use GPU for multimodal model
|
||||
ggml_backend_dev_t mmproj_device = nullptr; // GPU device to use for multimodal model
|
||||
bool no_mmproj = false; // explicitly disable multimodal model
|
||||
std::vector<std::string> image; // path to image file(s) ; TODO: change the name to "media"
|
||||
int image_min_tokens = -1;
|
||||
int image_max_tokens = -1;
|
||||
int mtmd_batch_max_tokens = 1024;
|
||||
|
||||
@@ -278,7 +278,9 @@ static std::unordered_map<char, std::string> GRAMMAR_LITERAL_ESCAPES = {
|
||||
{'\r', "\\r"}, {'\n', "\\n"}, {'"', "\\\""}, {'-', "\\-"}, {']', "\\]"}, {'\\', "\\\\"}
|
||||
};
|
||||
|
||||
static std::unordered_set<char> NON_LITERAL_SET = {'|', '.', '(', ')', '[', ']', '{', '}', '*', '+', '?'};
|
||||
static const int MAX_PATTERN_DEPTH = 100;
|
||||
|
||||
static std::unordered_set<char> NON_LITERAL_SET = {'|', '.', '(', ')', '[', ']', '{', '}', '*', '+', '?', '^', '$'};
|
||||
static std::unordered_set<char> ESCAPED_IN_REGEXPS_BUT_NOT_IN_LITERALS = {'^', '$', '.', '[', ']', '(', ')', '|', '{', '}', '*', '+', '?'};
|
||||
|
||||
static std::string replacePattern(const std::string & input, const std::regex & regex, const std::function<std::string(const std::smatch &)> & replacement) {
|
||||
@@ -309,6 +311,32 @@ static std::string format_literal(const std::string & literal) {
|
||||
|
||||
std::string gbnf_format_literal(const std::string & literal) { return format_literal(literal); }
|
||||
|
||||
static size_t gbnf_escape_length(const std::string & pattern, size_t pos) {
|
||||
if (pos + 1 >= pattern.length() || pattern[pos] != '\\') {
|
||||
return 0;
|
||||
}
|
||||
size_t n_hex = 0;
|
||||
switch (pattern[pos + 1]) {
|
||||
case 'x': n_hex = 2; break;
|
||||
case 'u': n_hex = 4; break;
|
||||
case 'U': n_hex = 8; break;
|
||||
case 't': case 'r': case 'n': case '\\': case '"': case '[': case ']':
|
||||
return 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
if (pos + 2 + n_hex > pattern.length()) {
|
||||
return 0;
|
||||
}
|
||||
for (size_t i = pos + 2; i < pos + 2 + n_hex; i++) {
|
||||
char h = pattern[i];
|
||||
if (!((h >= '0' && h <= '9') || (h >= 'a' && h <= 'f') || (h >= 'A' && h <= 'F'))) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 2 + n_hex;
|
||||
}
|
||||
|
||||
class common_schema_converter {
|
||||
private:
|
||||
friend class common_schema_info;
|
||||
@@ -345,16 +373,42 @@ private:
|
||||
return string_join(rules, " | ");
|
||||
}
|
||||
|
||||
// thrown when the pattern is a valid regex with no grammar equivalent
|
||||
struct unsupported_pattern : public std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
// thrown when the pattern is not a valid regex
|
||||
struct invalid_pattern : public std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
std::string _visit_pattern(const std::string & pattern, const std::string & name) {
|
||||
if (!(pattern.front() == '^' && pattern.back() == '$')) {
|
||||
_errors.push_back("Pattern must start with '^' and end with '$'");
|
||||
auto rules_snapshot = _rules;
|
||||
try {
|
||||
return _pattern_to_rule(pattern, name);
|
||||
} catch (const unsupported_pattern & err) {
|
||||
// revert rules
|
||||
_rules = std::move(rules_snapshot);
|
||||
_warnings.push_back("pattern " + pattern + " is not supported (" + err.what() + "), accepting any string");
|
||||
return _add_rule(name, _add_primitive("string", PRIMITIVE_RULES.at("string")));
|
||||
} catch (const invalid_pattern & err) {
|
||||
_rules = std::move(rules_snapshot);
|
||||
_errors.push_back("Invalid pattern " + pattern + ": " + err.what());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string _pattern_to_rule(const std::string & pattern, const std::string & name) {
|
||||
if (pattern.length() < 2 || pattern.front() != '^' || pattern.back() != '$') {
|
||||
throw unsupported_pattern("not anchored with '^' and '$'");
|
||||
}
|
||||
std::string sub_pattern = pattern.substr(1, pattern.length() - 2);
|
||||
std::unordered_map<std::string, std::string> sub_rule_ids;
|
||||
|
||||
size_t i = 0;
|
||||
size_t length = sub_pattern.length();
|
||||
int paren_depth = 0;
|
||||
|
||||
using literal_or_rule = std::pair<std::string, bool>;
|
||||
auto to_rule = [&](const literal_or_rule & ls) {
|
||||
@@ -363,7 +417,6 @@ private:
|
||||
return is_literal ? "\"" + s + "\"" : s;
|
||||
};
|
||||
std::function<literal_or_rule()> transform = [&]() -> literal_or_rule {
|
||||
size_t start = i;
|
||||
std::vector<literal_or_rule> seq;
|
||||
|
||||
auto get_dot = [&]() {
|
||||
@@ -420,43 +473,42 @@ private:
|
||||
if (i + 1 < length && sub_pattern[i + 1] == ':') {
|
||||
i += 2; // skip "?:" for non-capturing group, treat as regular group
|
||||
} else {
|
||||
// lookahead/lookbehind (?=, ?!, ?<=, ?<!) - not supported
|
||||
_warnings.push_back("Unsupported pattern syntax");
|
||||
// skip to matching ')' to avoid UB on empty seq
|
||||
int depth = 1;
|
||||
while (i < length && depth > 0) {
|
||||
if (sub_pattern[i] == '\\' && i + 1 < length) {
|
||||
i += 2; // skip escaped character
|
||||
} else {
|
||||
if (sub_pattern[i] == '(') depth++;
|
||||
else if (sub_pattern[i] == ')') depth--;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
// lookaround, named group, inline flags, ...
|
||||
throw unsupported_pattern("unsupported group syntax");
|
||||
}
|
||||
}
|
||||
paren_depth++;
|
||||
if (paren_depth > MAX_PATTERN_DEPTH) {
|
||||
throw unsupported_pattern("pattern nesting too deep");
|
||||
}
|
||||
seq.emplace_back("(" + to_rule(transform()) + ")", false);
|
||||
} else if (c == ')') {
|
||||
i++;
|
||||
if (start > 0 && sub_pattern[start - 1] != '(' && (start < 2 || sub_pattern[start - 2] != '?' || sub_pattern[start - 1] != ':')) {
|
||||
_errors.push_back("Unbalanced parentheses");
|
||||
if (paren_depth == 0) {
|
||||
throw invalid_pattern("unbalanced parentheses");
|
||||
}
|
||||
paren_depth--;
|
||||
return join_seq();
|
||||
} else if (c == '^' || c == '$') {
|
||||
throw unsupported_pattern("anchor inside the pattern");
|
||||
} else if (c == '[') {
|
||||
std::string square_brackets = std::string(1, c);
|
||||
i++;
|
||||
while (i < length && sub_pattern[i] != ']') {
|
||||
if (sub_pattern[i] == '\\') {
|
||||
square_brackets += sub_pattern.substr(i, 2);
|
||||
i += 2;
|
||||
auto escape_length = gbnf_escape_length(sub_pattern, i);
|
||||
if (escape_length == 0) {
|
||||
throw unsupported_pattern("unsupported escape in character class: " + sub_pattern.substr(i, 2));
|
||||
}
|
||||
square_brackets += sub_pattern.substr(i, escape_length);
|
||||
i += escape_length;
|
||||
} else {
|
||||
square_brackets += sub_pattern[i];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (i >= length) {
|
||||
_errors.push_back("Unbalanced square brackets");
|
||||
throw invalid_pattern("unterminated character class");
|
||||
}
|
||||
square_brackets += ']';
|
||||
i++;
|
||||
@@ -465,6 +517,9 @@ private:
|
||||
seq.emplace_back("|", false);
|
||||
i++;
|
||||
} else if (c == '*' || c == '+' || c == '?') {
|
||||
if (seq.empty()) {
|
||||
throw invalid_pattern("nothing to repeat");
|
||||
}
|
||||
seq.back() = std::make_pair(to_rule(seq.back()) + c, false);
|
||||
i++;
|
||||
} else if (c == '{') {
|
||||
@@ -475,18 +530,19 @@ private:
|
||||
i++;
|
||||
}
|
||||
if (i >= length) {
|
||||
_errors.push_back("Unbalanced curly brackets");
|
||||
throw unsupported_pattern("unterminated curly brackets");
|
||||
}
|
||||
curly_brackets += '}';
|
||||
i++;
|
||||
auto nums = string_split(curly_brackets.substr(1, curly_brackets.length() - 2), ",");
|
||||
int min_times = 0;
|
||||
int max_times = std::numeric_limits<int>::max();
|
||||
if (nums.size() != 1 && nums.size() != 2) {
|
||||
throw unsupported_pattern("wrong number of values in curly brackets");
|
||||
}
|
||||
try {
|
||||
if (nums.size() == 1) {
|
||||
min_times = max_times = std::stoi(nums[0]);
|
||||
} else if (nums.size() != 2) {
|
||||
_errors.push_back("Wrong number of values in curly brackets");
|
||||
} else {
|
||||
if (!nums[0].empty()) {
|
||||
min_times = std::stoi(nums[0]);
|
||||
@@ -495,9 +551,11 @@ private:
|
||||
max_times = std::stoi(nums[1]);
|
||||
}
|
||||
}
|
||||
} catch (const std::invalid_argument & e) {
|
||||
_errors.push_back("Invalid number in curly brackets");
|
||||
return std::make_pair("", false);
|
||||
} catch (const std::logic_error &) {
|
||||
throw unsupported_pattern("invalid number in curly brackets");
|
||||
}
|
||||
if (seq.empty()) {
|
||||
throw invalid_pattern("nothing to repeat");
|
||||
}
|
||||
auto &last = seq.back();
|
||||
auto &sub = last.first;
|
||||
@@ -523,15 +581,22 @@ private:
|
||||
return NON_LITERAL_SET.find(c) != NON_LITERAL_SET.end();
|
||||
};
|
||||
while (i < length) {
|
||||
if (sub_pattern[i] == '\\' && i < length - 1) {
|
||||
if (sub_pattern[i] == '\\') {
|
||||
if (i == length - 1) {
|
||||
throw invalid_pattern("trailing backslash");
|
||||
}
|
||||
char next = sub_pattern[i + 1];
|
||||
if (ESCAPED_IN_REGEXPS_BUT_NOT_IN_LITERALS.find(next) != ESCAPED_IN_REGEXPS_BUT_NOT_IN_LITERALS.end()) {
|
||||
i++;
|
||||
literal += sub_pattern[i];
|
||||
i++;
|
||||
} else {
|
||||
literal += sub_pattern.substr(i, 2);
|
||||
i += 2;
|
||||
auto escape_length = gbnf_escape_length(sub_pattern, i);
|
||||
if (escape_length == 0) {
|
||||
throw unsupported_pattern("unsupported escape: " + sub_pattern.substr(i, 2));
|
||||
}
|
||||
literal += sub_pattern.substr(i, escape_length);
|
||||
i += escape_length;
|
||||
}
|
||||
} else if (sub_pattern[i] == '"') {
|
||||
literal += "\\\"";
|
||||
@@ -544,14 +609,21 @@ private:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!literal.empty()) {
|
||||
seq.emplace_back(literal, true);
|
||||
if (literal.empty()) { // nothing was consumed, ex. a stray ']' or '}'
|
||||
throw unsupported_pattern(std::string("unsupported character: ") + c);
|
||||
}
|
||||
seq.emplace_back(literal, true);
|
||||
}
|
||||
}
|
||||
return join_seq();
|
||||
};
|
||||
return _add_rule(name, "\"\\\"\" (" + to_rule(transform()) + ") \"\\\"\"");
|
||||
|
||||
auto rule = to_rule(transform());
|
||||
if (paren_depth != 0) {
|
||||
throw invalid_pattern("unbalanced parentheses");
|
||||
}
|
||||
|
||||
return _add_rule(name, "\"\\\"\" (" + rule + ") \"\\\"\"");
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -2649,6 +2649,10 @@ void common_speculative_draft(common_speculative * spec) {
|
||||
for (llama_seq_id seq_id = 0; seq_id < (llama_seq_id) dparams.size(); ++seq_id) {
|
||||
auto & dp = dparams[seq_id];
|
||||
|
||||
if (!dp.drafting) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto & result = *dp.result;
|
||||
|
||||
// a new draft has been sampled
|
||||
|
||||
@@ -57,6 +57,7 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"Qwen3DSparkModel": "qwen",
|
||||
"DSparkDraftModel": "qwen",
|
||||
"DSparkSpeculator": "qwen",
|
||||
"Lfm2DSparkDraftModel": "qwen",
|
||||
"DeepseekV4ForCausalLM": "deepseek",
|
||||
"DeepseekV4DSparkModel": "deepseek",
|
||||
"DistilBertForMaskedLM": "bert",
|
||||
@@ -109,6 +110,8 @@ TEXT_MODEL_MAP: dict[str, str] = {
|
||||
"GraniteSwitchForCausalLM": "granite",
|
||||
"GraniteSpeechForConditionalGeneration": "granite",
|
||||
"GraniteSpeechPlusForConditionalGeneration": "granite",
|
||||
"GraniteSWAForCausalLM": "granite",
|
||||
"GraniteMoeSWAForCausalLM": "granite",
|
||||
"Grok1ForCausalLM": "grok",
|
||||
"GrokForCausalLM": "grok",
|
||||
"GroveMoeForCausalLM": "grovemoe",
|
||||
|
||||
@@ -74,6 +74,108 @@ class GraniteModel(LlamaModel):
|
||||
return super().filter_tensors(item)
|
||||
|
||||
|
||||
@ModelBase.register("GraniteSWAForCausalLM")
|
||||
class GraniteSWAModel(GraniteModel):
|
||||
"""Conversion for IBM's GraniteSWAForCausalLM (interleaved sliding window attention)"""
|
||||
model_arch = gguf.MODEL_ARCH.GRANITE_SWA
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
|
||||
if name.endswith("sinks"):
|
||||
name += ".weight"
|
||||
|
||||
return super().filter_tensors((name, gen))
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
"""GraniteSWA uses Granite parameters plus sliding window configuration."""
|
||||
super().set_gguf_parameters()
|
||||
|
||||
# Add sliding_window from config
|
||||
sliding_window = self.hparams.get("sliding_window", 128)
|
||||
self.gguf_writer.add_sliding_window(sliding_window)
|
||||
logger.info("gguf: (granite_swa) sliding_window = %s", sliding_window)
|
||||
|
||||
# Derive sliding_window_pattern from layer_types
|
||||
if layer_types := self.hparams.get("layer_types"):
|
||||
is_swa = [t == "sliding_attention" for t in layer_types]
|
||||
self.gguf_writer.add_sliding_window_pattern(is_swa)
|
||||
logger.info("gguf: (granite_swa) sliding_window_pattern = %d SWA layers / %d total",
|
||||
sum(is_swa), len(is_swa))
|
||||
else:
|
||||
# Fall back to period-based pattern: i % 4 != 0
|
||||
# This matches the transformers default pattern
|
||||
n_layers = self.block_count
|
||||
is_swa = [i % 4 != 0 for i in range(n_layers)]
|
||||
self.gguf_writer.add_sliding_window_pattern(is_swa)
|
||||
logger.info("gguf: (granite_swa) sliding_window_pattern (inferred) = %d SWA layers / %d total",
|
||||
sum(is_swa), n_layers)
|
||||
|
||||
# Add rope_pattern from no_rope_layers
|
||||
if no_rope_layers := self.hparams.get("no_rope_layers"):
|
||||
# Convert 1/0 to bool (1 = use RoPE, 0 = NoPE)
|
||||
rope_pattern = [bool(x) for x in no_rope_layers]
|
||||
self.gguf_writer.add_rope_pattern(rope_pattern)
|
||||
logger.info("gguf: (granite_swa) rope_pattern = %d RoPE layers / %d total",
|
||||
sum(rope_pattern), len(rope_pattern))
|
||||
|
||||
|
||||
@ModelBase.register("GraniteMoeSWAForCausalLM")
|
||||
class GraniteMoeSWAModel(GraniteSWAModel):
|
||||
"""Conversion for IBM's GraniteMoeSWAForCausalLM (unified dense + MoE with iSWA)"""
|
||||
model_arch = gguf.MODEL_ARCH.GRANITE_SWA
|
||||
|
||||
def set_gguf_parameters(self):
|
||||
super().set_gguf_parameters()
|
||||
if shared_intermediate_size := self.hparams.get("shared_intermediate_size"):
|
||||
self.gguf_writer.add_expert_shared_feed_forward_length(shared_intermediate_size)
|
||||
logger.info("gguf: (granitemoewa) shared_intermediate_size = %s", shared_intermediate_size)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
"""Split merged MoE tensors (gate+up) following standard MoE pattern."""
|
||||
|
||||
# Handle expert FFN tensors (merged gate+up) - swash format: experts.gate_up_proj
|
||||
# Kept fused since inference (build_moe_ffn) supports a single gate_up_exps
|
||||
# tensor for the routed experts.
|
||||
if name.endswith("block_sparse_moe.experts.gate_up_proj"):
|
||||
ffn_dim = self.hparams["intermediate_size"]
|
||||
assert data_torch.shape[-2] == 2 * ffn_dim, f"Merged FFN tensor size must be 2 * intermediate_size, got {data_torch.shape[-2]}"
|
||||
yield from ModelBase.modify_tensors(self, data_torch, self.format_tensor_name(gguf.MODEL_TENSOR.FFN_GATE_UP_EXP, bid), bid)
|
||||
return
|
||||
|
||||
# Handle expert FFN down projection - swash format: experts.down_proj
|
||||
if name.endswith("block_sparse_moe.experts.down_proj"):
|
||||
yield from ModelBase.modify_tensors(self, data_torch, self.format_tensor_name(gguf.MODEL_TENSOR.FFN_DOWN_EXP, bid), bid)
|
||||
return
|
||||
|
||||
# Handle expert FFN tensors (merged gate+up) - standard granite format: input_linear.weight
|
||||
# Kept fused since inference (build_moe_ffn) supports a single gate_up_exps
|
||||
# tensor for the routed experts.
|
||||
if name.endswith("block_sparse_moe.input_linear.weight"):
|
||||
ffn_dim = self.hparams["intermediate_size"]
|
||||
assert data_torch.shape[-2] == 2 * ffn_dim, "Merged FFN tensor size must be 2 * intermediate_size"
|
||||
yield from ModelBase.modify_tensors(self, data_torch, self.format_tensor_name(gguf.MODEL_TENSOR.FFN_GATE_UP_EXP, bid), bid)
|
||||
return
|
||||
|
||||
# Handle shared expert FFN tensors (if present) - kept fused since
|
||||
# inference (build_ffn) supports a single ffn_up_shexp tensor with
|
||||
# LLM_FFN_SWIGLU for the shared expert.
|
||||
if name.endswith("shared_mlp.input_linear.weight"):
|
||||
ffn_dim = self.hparams.get("shared_intermediate_size", self.hparams["intermediate_size"])
|
||||
assert data_torch.shape[-2] == 2 * ffn_dim, "Merged FFN tensor size must be 2 * shared_intermediate_size"
|
||||
yield from ModelBase.modify_tensors(self, data_torch, self.format_tensor_name(gguf.MODEL_TENSOR.FFN_UP_SHEXP, bid), bid)
|
||||
return
|
||||
|
||||
# Handle shared expert output (if present)
|
||||
if name.endswith("shared_mlp.output_linear.weight"):
|
||||
yield from ModelBase.modify_tensors(self, data_torch, self.format_tensor_name(gguf.MODEL_TENSOR.FFN_DOWN_SHEXP, bid), bid)
|
||||
return
|
||||
|
||||
# Pass through to parent for all other tensors (including sinks)
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
|
||||
@ModelBase.register("GraniteMoeForCausalLM", "GraniteMoeSharedForCausalLM")
|
||||
@ModelBase.example("ibm-granite/granite-3.1-3b-a800m-instruct")
|
||||
class GraniteMoeModel(GraniteModel):
|
||||
|
||||
@@ -207,7 +207,9 @@ class NemotronHModel(GraniteHybridModel):
|
||||
# calling the parent __init__. This is because the parent constructor
|
||||
# uses self.model_arch to build the tensor name map, and all MoE-specific
|
||||
# mappings would be missed if it were called with the default non-MoE arch.
|
||||
hparams = ModelBase.load_hparams(args[0], self.is_mistral_format)
|
||||
hparams = kwargs.pop("hparams", None)
|
||||
if hparams is None:
|
||||
hparams = ModelBase.load_hparams(args[0], self.is_mistral_format)
|
||||
has_moe_params = (
|
||||
"num_experts_per_tok" in hparams
|
||||
or (isinstance(hparams.get("llm_config"), dict) and "num_experts_per_tok" in hparams["llm_config"])
|
||||
@@ -215,8 +217,11 @@ class NemotronHModel(GraniteHybridModel):
|
||||
if has_moe_params:
|
||||
self.model_arch = gguf.MODEL_ARCH.NEMOTRON_H_MOE
|
||||
self.is_moe = True
|
||||
layers_block_type = hparams.get("layers_block_type")
|
||||
if layers_block_type is not None:
|
||||
hparams["num_hidden_layers"] = len(layers_block_type)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
super().__init__(*args, hparams=hparams, **kwargs)
|
||||
|
||||
# Save the top-level head_dim for later
|
||||
self.head_dim = self.hparams.get("head_dim", self.hparams.get("attention_head_dim"))
|
||||
|
||||
+14
-1
@@ -709,7 +709,7 @@ class DFlashModel(Qwen3Model):
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
|
||||
@ModelBase.register("Qwen3DSparkModel", "DSparkDraftModel", "DSparkSpeculator")
|
||||
@ModelBase.register("Qwen3DSparkModel", "DSparkDraftModel", "DSparkSpeculator", "Lfm2DSparkDraftModel")
|
||||
@ModelBase.example("satgeze/Qwen3.6-27B-DSpark")
|
||||
class DSparkModel(DFlashModel):
|
||||
# DSpark = DFlash + a semi-autoregressive Markov head.
|
||||
@@ -759,6 +759,13 @@ class DSparkModel(DFlashModel):
|
||||
return None
|
||||
return super().filter_tensors(item)
|
||||
|
||||
_ROPE_PERMUTE_SUFFIXES = (
|
||||
"self_attn.q_proj.weight",
|
||||
"self_attn.k_proj.weight",
|
||||
"self_attn.q_norm.weight",
|
||||
"self_attn.k_norm.weight",
|
||||
)
|
||||
|
||||
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
|
||||
if name == "model.d2t":
|
||||
self._d2t = data_torch
|
||||
@@ -767,6 +774,12 @@ class DSparkModel(DFlashModel):
|
||||
if self._n_vocab_draft == self.hparams["vocab_size"] and name.endswith(("embed_tokens.weight", "lm_head.weight")):
|
||||
return
|
||||
|
||||
# interleaved-rope checkpoints (rope_is_neox_style = false) -> NeoX layout: per head, even dims first then odd
|
||||
if not self.hparams.get("rope_is_neox_style", True) and name.endswith(self._ROPE_PERMUTE_SUFFIXES):
|
||||
head_dim = self.hparams["head_dim"]
|
||||
shape = data_torch.shape
|
||||
data_torch = data_torch.reshape(-1, head_dim // 2, 2, *shape[1:]).transpose(1, 2).reshape(shape)
|
||||
|
||||
yield from super().modify_tensors(data_torch, name, bid)
|
||||
|
||||
def prepare_tensors(self):
|
||||
|
||||
+2
-1
@@ -72,9 +72,10 @@ cmake --build build --config Release
|
||||
- Please remember to always use a Developer Command Prompt / PowerShell for VS2022 for git, build, test
|
||||
- For Windows on ARM (arm64, WoA) build with:
|
||||
```bash
|
||||
cmake --preset arm64-windows-llvm-release -D GGML_OPENMP=OFF
|
||||
cmake --preset arm64-windows-llvm-release -D GGML_OPENMP_FETCH=ON
|
||||
cmake --build build-arm64-windows-llvm-release
|
||||
```
|
||||
`GGML_OPENMP_FETCH` downloads the official LLVM OpenMP runtime and requires Clang, 7-Zip and network access during configuration. CMake selects the runtime from the target architecture, so this also works when cross-compiling for WoA from x64. The extracted header, import library, DLL and OpenMP license are placed under `build/_deps`. The build copies `libomp.dll` and `LICENSE-LLVM-OpenMP` to the runtime output directory and installs them together. Omit the option to use CMake's normal OpenMP detection, or pass `-D GGML_OPENMP=OFF` to disable OpenMP.
|
||||
For building with ninja generator and clang compiler as default:
|
||||
-set path:set LIB=C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\um\x64;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\lib\x64\uwp;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\ucrt\x64
|
||||
```bash
|
||||
|
||||
@@ -243,6 +243,7 @@ set (GGML_METAL_MACOSX_VERSION_MIN "" CACHE STRING
|
||||
"ggml: metal minimum macOS version")
|
||||
set (GGML_METAL_STD "" CACHE STRING "ggml: metal standard version (-std flag)")
|
||||
option(GGML_OPENMP "ggml: use OpenMP" ON)
|
||||
option(GGML_OPENMP_FETCH "ggml: fetch LLVM OpenMP" OFF)
|
||||
option(GGML_RPC "ggml: use RPC" OFF)
|
||||
option(GGML_SYCL "ggml: use SYCL" OFF)
|
||||
option(GGML_SYCL_F16 "ggml: use 16 bit floats for sycl calculations" OFF)
|
||||
|
||||
@@ -1981,6 +1981,14 @@ extern "C" {
|
||||
float beta_fast,
|
||||
float beta_slow);
|
||||
|
||||
// set the offset dims for RoPE
|
||||
// a must be GGML_OP_ROPE or GGML_OP_ROPE_BACK
|
||||
// vision RoPE is not supported
|
||||
// example: (marking: x = rotated, 0 = unrotated)
|
||||
// n_embd = 10, n_dims = 4, offset = 2 --> [00xxxx0000]
|
||||
GGML_API struct ggml_tensor * ggml_rope_set_offset(
|
||||
struct ggml_tensor * a,
|
||||
int n_offs);
|
||||
|
||||
// clamp
|
||||
// in-place, returns view(a)
|
||||
|
||||
+116
-2
@@ -222,9 +222,123 @@ if (GGML_SCHED_NO_REALLOC)
|
||||
target_compile_definitions(ggml-base PUBLIC GGML_SCHED_NO_REALLOC)
|
||||
endif()
|
||||
|
||||
if (GGML_OPENMP)
|
||||
if (GGML_OPENMP_FETCH)
|
||||
if (NOT GGML_OPENMP)
|
||||
message(FATAL_ERROR "GGML_OPENMP_FETCH requires GGML_OPENMP")
|
||||
elseif (NOT WIN32 OR NOT (CMAKE_C_COMPILER_ID MATCHES "Clang"))
|
||||
message(FATAL_ERROR "GGML_OPENMP_FETCH currently requires Clang on Windows")
|
||||
endif()
|
||||
|
||||
set(GGML_OPENMP_LLVM_VERSION "20.1.8")
|
||||
string(REGEX MATCH "^[0-9]+" GGML_OPENMP_LLVM_VERSION_MAJOR "${GGML_OPENMP_LLVM_VERSION}")
|
||||
string(REGEX MATCH "^[0-9]+" GGML_OPENMP_COMPILER_VERSION_MAJOR "${CMAKE_C_COMPILER_VERSION}")
|
||||
if (NOT GGML_OPENMP_COMPILER_VERSION_MAJOR STREQUAL GGML_OPENMP_LLVM_VERSION_MAJOR)
|
||||
message(FATAL_ERROR "LLVM OpenMP ${GGML_OPENMP_LLVM_VERSION} requires Clang ${GGML_OPENMP_LLVM_VERSION_MAJOR}.x")
|
||||
endif()
|
||||
|
||||
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" GGML_OPENMP_SYSTEM_PROCESSOR)
|
||||
if (GGML_OPENMP_SYSTEM_PROCESSOR MATCHES "^(amd64|x86_64)$")
|
||||
set(GGML_OPENMP_ARCH "x64")
|
||||
set(GGML_OPENMP_INSTALLER_SUFFIX "win64")
|
||||
set(GGML_OPENMP_INSTALLER_SHA256 "3197846a2b19063687dd56e93e34cd941e3548d907f23a6131571321bdf9fe7b")
|
||||
elseif (GGML_OPENMP_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)$")
|
||||
set(GGML_OPENMP_ARCH "arm64")
|
||||
set(GGML_OPENMP_INSTALLER_SUFFIX "woa64")
|
||||
set(GGML_OPENMP_INSTALLER_SHA256 "7c4ac97eb2ae6b960ca5f9caf3ff6124c8d2a18cc07a7840a4d2ea15537bad8e")
|
||||
else()
|
||||
message(FATAL_ERROR "GGML_OPENMP_FETCH does not support ${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif()
|
||||
|
||||
set(GGML_OPENMP_CACHE_DIR "${CMAKE_BINARY_DIR}/_deps")
|
||||
set(GGML_OPENMP_ROOT "${GGML_OPENMP_CACHE_DIR}/llvm-openmp-${GGML_OPENMP_LLVM_VERSION}-${GGML_OPENMP_ARCH}")
|
||||
set(GGML_OPENMP_LIBRARY "${GGML_OPENMP_ROOT}/lib/libomp.lib")
|
||||
set(GGML_OPENMP_RUNTIME "${GGML_OPENMP_ROOT}/bin/libomp.dll")
|
||||
set(GGML_OPENMP_HEADER "${GGML_OPENMP_ROOT}/include/omp.h")
|
||||
set(GGML_OPENMP_LICENSE "${GGML_OPENMP_ROOT}/LICENSE.TXT")
|
||||
set(GGML_OPENMP_LICENSE_SHA256 "fdad1758a9e1f9d5a81e18879b3406772115edc92c24bfa36b70c654f325e8e4")
|
||||
|
||||
if (NOT EXISTS "${GGML_OPENMP_LIBRARY}" OR NOT EXISTS "${GGML_OPENMP_RUNTIME}" OR NOT EXISTS "${GGML_OPENMP_HEADER}")
|
||||
find_program(GGML_OPENMP_7Z NAMES 7z 7zz 7za)
|
||||
if (NOT GGML_OPENMP_7Z)
|
||||
message(FATAL_ERROR "GGML_OPENMP_FETCH requires 7-Zip to extract the LLVM installer")
|
||||
endif()
|
||||
|
||||
set(GGML_OPENMP_INSTALLER "${GGML_OPENMP_ROOT}/LLVM-${GGML_OPENMP_LLVM_VERSION}-${GGML_OPENMP_INSTALLER_SUFFIX}.exe")
|
||||
set(GGML_OPENMP_EXTRACT_DIR "${GGML_OPENMP_ROOT}/extract")
|
||||
set(GGML_OPENMP_INSTALLER_URL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${GGML_OPENMP_LLVM_VERSION}/LLVM-${GGML_OPENMP_LLVM_VERSION}-${GGML_OPENMP_INSTALLER_SUFFIX}.exe")
|
||||
|
||||
file(MAKE_DIRECTORY "${GGML_OPENMP_EXTRACT_DIR}")
|
||||
file(DOWNLOAD "${GGML_OPENMP_INSTALLER_URL}" "${GGML_OPENMP_INSTALLER}"
|
||||
EXPECTED_HASH "SHA256=${GGML_OPENMP_INSTALLER_SHA256}"
|
||||
SHOW_PROGRESS
|
||||
STATUS GGML_OPENMP_DOWNLOAD_STATUS)
|
||||
list(GET GGML_OPENMP_DOWNLOAD_STATUS 0 GGML_OPENMP_DOWNLOAD_RESULT)
|
||||
if (NOT GGML_OPENMP_DOWNLOAD_RESULT EQUAL 0)
|
||||
list(GET GGML_OPENMP_DOWNLOAD_STATUS 1 GGML_OPENMP_DOWNLOAD_ERROR)
|
||||
message(FATAL_ERROR "Failed to download LLVM OpenMP: ${GGML_OPENMP_DOWNLOAD_ERROR}")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND "${GGML_OPENMP_7Z}" e -y "-o${GGML_OPENMP_EXTRACT_DIR}" "${GGML_OPENMP_INSTALLER}" -r libomp.lib libomp.dll omp.h
|
||||
RESULT_VARIABLE GGML_OPENMP_EXTRACT_RESULT
|
||||
OUTPUT_QUIET)
|
||||
if (NOT GGML_OPENMP_EXTRACT_RESULT EQUAL 0 OR
|
||||
NOT EXISTS "${GGML_OPENMP_EXTRACT_DIR}/libomp.lib" OR
|
||||
NOT EXISTS "${GGML_OPENMP_EXTRACT_DIR}/libomp.dll" OR
|
||||
NOT EXISTS "${GGML_OPENMP_EXTRACT_DIR}/omp.h")
|
||||
message(FATAL_ERROR "Failed to extract libomp from ${GGML_OPENMP_INSTALLER}")
|
||||
endif()
|
||||
|
||||
file(MAKE_DIRECTORY "${GGML_OPENMP_ROOT}/lib" "${GGML_OPENMP_ROOT}/bin" "${GGML_OPENMP_ROOT}/include")
|
||||
file(COPY "${GGML_OPENMP_EXTRACT_DIR}/libomp.lib" DESTINATION "${GGML_OPENMP_ROOT}/lib")
|
||||
file(COPY "${GGML_OPENMP_EXTRACT_DIR}/libomp.dll" DESTINATION "${GGML_OPENMP_ROOT}/bin")
|
||||
file(COPY "${GGML_OPENMP_EXTRACT_DIR}/omp.h" DESTINATION "${GGML_OPENMP_ROOT}/include")
|
||||
file(REMOVE_RECURSE "${GGML_OPENMP_INSTALLER}" "${GGML_OPENMP_EXTRACT_DIR}")
|
||||
endif()
|
||||
|
||||
# The NSIS installer embeds LLVM's general license in its UI but does not install it as a file; use OpenMP's license to include its additional notices.
|
||||
if (EXISTS "${GGML_OPENMP_LICENSE}")
|
||||
file(SHA256 "${GGML_OPENMP_LICENSE}" GGML_OPENMP_LICENSE_ACTUAL_SHA256)
|
||||
endif()
|
||||
if (NOT GGML_OPENMP_LICENSE_ACTUAL_SHA256 STREQUAL GGML_OPENMP_LICENSE_SHA256)
|
||||
file(DOWNLOAD "https://raw.githubusercontent.com/llvm/llvm-project/llvmorg-${GGML_OPENMP_LLVM_VERSION}/openmp/LICENSE.TXT" "${GGML_OPENMP_LICENSE}"
|
||||
EXPECTED_HASH "SHA256=${GGML_OPENMP_LICENSE_SHA256}")
|
||||
endif()
|
||||
|
||||
if (COMMAND license_add_file)
|
||||
license_add_file("LLVM OpenMP" "${GGML_OPENMP_LICENSE}")
|
||||
endif()
|
||||
|
||||
add_library(ggml-openmp-c INTERFACE)
|
||||
target_compile_options(ggml-openmp-c INTERFACE "$<$<COMPILE_LANGUAGE:C>:-fopenmp=libomp>")
|
||||
target_include_directories(ggml-openmp-c SYSTEM INTERFACE "${GGML_OPENMP_ROOT}/include")
|
||||
target_link_libraries(ggml-openmp-c INTERFACE "${GGML_OPENMP_LIBRARY}")
|
||||
|
||||
add_library(ggml-openmp-cxx INTERFACE)
|
||||
target_compile_options(ggml-openmp-cxx INTERFACE "$<$<COMPILE_LANGUAGE:CXX>:-fopenmp=libomp>")
|
||||
target_include_directories(ggml-openmp-cxx SYSTEM INTERFACE "${GGML_OPENMP_ROOT}/include")
|
||||
target_link_libraries(ggml-openmp-cxx INTERFACE "${GGML_OPENMP_LIBRARY}")
|
||||
|
||||
set(GGML_OPENMP_RUNTIME_OUTPUT_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
|
||||
if (CMAKE_CONFIGURATION_TYPES)
|
||||
string(APPEND GGML_OPENMP_RUNTIME_OUTPUT_DIR "/$<CONFIG>")
|
||||
endif()
|
||||
add_custom_target(ggml-openmp-runtime ALL
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${GGML_OPENMP_RUNTIME_OUTPUT_DIR}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${GGML_OPENMP_RUNTIME}" "${GGML_OPENMP_RUNTIME_OUTPUT_DIR}/libomp.dll"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${GGML_OPENMP_LICENSE}" "${GGML_OPENMP_RUNTIME_OUTPUT_DIR}/LICENSE-LLVM-OpenMP")
|
||||
add_dependencies(ggml-base ggml-openmp-runtime)
|
||||
install(FILES "${GGML_OPENMP_RUNTIME}" DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES "${GGML_OPENMP_LICENSE}" DESTINATION ${CMAKE_INSTALL_BINDIR} RENAME LICENSE-LLVM-OpenMP)
|
||||
|
||||
set(GGML_OPENMP_TARGET_C ggml-openmp-c)
|
||||
set(GGML_OPENMP_TARGET_CXX ggml-openmp-cxx)
|
||||
set(GGML_OPENMP_ENABLED "ON" CACHE INTERNAL "")
|
||||
elseif (GGML_OPENMP)
|
||||
find_package(OpenMP)
|
||||
if (OpenMP_FOUND)
|
||||
set(GGML_OPENMP_TARGET_C OpenMP::OpenMP_C)
|
||||
set(GGML_OPENMP_TARGET_CXX OpenMP::OpenMP_CXX)
|
||||
set(GGML_OPENMP_ENABLED "ON" CACHE INTERNAL "")
|
||||
else()
|
||||
set(GGML_OPENMP_ENABLED "OFF" CACHE INTERNAL "")
|
||||
@@ -236,7 +350,7 @@ endif()
|
||||
|
||||
if (GGML_OPENMP_ENABLED)
|
||||
target_compile_definitions(ggml-base PRIVATE GGML_USE_OPENMP)
|
||||
target_link_libraries(ggml-base PRIVATE OpenMP::OpenMP_C OpenMP::OpenMP_CXX)
|
||||
target_link_libraries(ggml-base PRIVATE ${GGML_OPENMP_TARGET_C} ${GGML_OPENMP_TARGET_CXX})
|
||||
endif()
|
||||
|
||||
add_library(ggml
|
||||
|
||||
@@ -1599,11 +1599,23 @@ static enum ggml_status ggml_backend_sched_compute_splits(ggml_backend_sched_t s
|
||||
std::vector<int32_t> ids;
|
||||
std::vector<ggml_bitset_t> used_ids;
|
||||
|
||||
int prev_backend_id = -1;
|
||||
|
||||
for (int split_id = 0; split_id < sched->n_splits; split_id++) {
|
||||
struct ggml_backend_sched_split * split = &splits[split_id];
|
||||
int split_backend_id = split->backend_id;
|
||||
ggml_backend_t split_backend = sched->backends[split_backend_id];
|
||||
|
||||
// ensure the previous split's async work has completed before we start
|
||||
// this split, the allocator may have reused buffer regions across splits
|
||||
if (split->n_inputs == 0 && prev_backend_id >= 0 && prev_backend_id != split_backend_id) {
|
||||
if (sched->events[prev_backend_id][sched->cur_copy] != NULL) {
|
||||
ggml_backend_event_synchronize(sched->events[prev_backend_id][sched->cur_copy]);
|
||||
} else {
|
||||
ggml_backend_synchronize(sched->backends[prev_backend_id]);
|
||||
}
|
||||
}
|
||||
|
||||
// copy the input tensors to the split backend
|
||||
for (int input_id = 0; input_id < split->n_inputs; input_id++) {
|
||||
ggml_backend_t input_backend = ggml_backend_sched_get_tensor_backend(sched, split->inputs[input_id]);
|
||||
@@ -1766,12 +1778,12 @@ static enum ggml_status ggml_backend_sched_compute_splits(ggml_backend_sched_t s
|
||||
}
|
||||
}
|
||||
|
||||
// record the event of this copy
|
||||
if (split->n_inputs > 0) {
|
||||
if (sched->events[split_backend_id][sched->cur_copy] != NULL) {
|
||||
ggml_backend_event_record(sched->events[split_backend_id][sched->cur_copy], split_backend);
|
||||
}
|
||||
// record the event of this split
|
||||
if (sched->events[split_backend_id][sched->cur_copy] != NULL) {
|
||||
ggml_backend_event_record(sched->events[split_backend_id][sched->cur_copy], split_backend);
|
||||
}
|
||||
|
||||
prev_backend_id = split_backend_id;
|
||||
}
|
||||
|
||||
return GGML_STATUS_SUCCESS;
|
||||
|
||||
@@ -2534,6 +2534,9 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
|
||||
}
|
||||
case GGML_OP_ROPE:
|
||||
{
|
||||
if (((const int32_t *) op->op_params)[15] != 0) {
|
||||
return false; // FIXME: support ggml_rope_set_offset
|
||||
}
|
||||
if (op->src[0]->ne[0] > 896) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ function(ggml_add_cpu_backend_variant_impl tag_name)
|
||||
|
||||
if (GGML_OPENMP_ENABLED)
|
||||
target_compile_definitions(${GGML_CPU_NAME} PRIVATE GGML_USE_OPENMP)
|
||||
target_link_libraries(${GGML_CPU_NAME} PRIVATE OpenMP::OpenMP_C OpenMP::OpenMP_CXX)
|
||||
target_link_libraries(${GGML_CPU_NAME} PRIVATE ${GGML_OPENMP_TARGET_C} ${GGML_OPENMP_TARGET_CXX})
|
||||
endif()
|
||||
|
||||
if (GGML_LLAMAFILE)
|
||||
|
||||
@@ -5979,6 +5979,8 @@ static void ggml_compute_forward_rope_flt(
|
||||
memcpy(&beta_slow, (int32_t *) dst->op_params + 10, sizeof(float));
|
||||
memcpy(§ions, (int32_t *) dst->op_params + 11, sizeof(int)*4);
|
||||
|
||||
const int n_offs = ((int32_t *) dst->op_params)[15];
|
||||
|
||||
GGML_TENSOR_UNARY_OP_LOCALS
|
||||
|
||||
//printf("ne0: %d, ne1: %d, ne2: %d, ne3: %d\n", ne0, ne1, ne2, ne3);
|
||||
@@ -5995,6 +5997,10 @@ static void ggml_compute_forward_rope_flt(
|
||||
GGML_ASSERT(n_dims <= ne0);
|
||||
GGML_ASSERT(n_dims % 2 == 0);
|
||||
|
||||
GGML_ASSERT(n_offs >= 0);
|
||||
GGML_ASSERT(n_offs % 2 == 0);
|
||||
GGML_ASSERT(n_offs + n_dims <= ne0);
|
||||
|
||||
// rows per thread
|
||||
const int dr = (nr + nth - 1)/nth;
|
||||
|
||||
@@ -6020,6 +6026,7 @@ static void ggml_compute_forward_rope_flt(
|
||||
|
||||
if (is_vision) {
|
||||
GGML_ASSERT(n_dims == ne0/2);
|
||||
GGML_ASSERT(n_offs == 0);
|
||||
}
|
||||
|
||||
const float * freq_factors = NULL;
|
||||
@@ -6068,12 +6075,12 @@ static void ggml_compute_forward_rope_flt(
|
||||
|
||||
switch (mode) {
|
||||
case GGML_ROPE_TYPE_NORMAL:
|
||||
rotate_pairs<T>(n_dims, 1, cache, src, dst_data, 1);
|
||||
rotate_pairs<T>(n_dims, 1, cache, src + n_offs, dst_data + n_offs, 1);
|
||||
break;
|
||||
case GGML_ROPE_TYPE_NEOX:
|
||||
case GGML_ROPE_TYPE_MROPE:
|
||||
case GGML_ROPE_TYPE_IMROPE:
|
||||
rotate_pairs<T>(n_dims, n_dims/2, cache, src, dst_data);
|
||||
rotate_pairs<T>(n_dims, n_dims/2, cache, src + n_offs, dst_data + n_offs);
|
||||
break;
|
||||
case GGML_ROPE_TYPE_VISION:
|
||||
rotate_pairs<T>(ne0, n_dims, cache, src, dst_data);
|
||||
@@ -6084,7 +6091,11 @@ static void ggml_compute_forward_rope_flt(
|
||||
|
||||
if (!is_vision) {
|
||||
// fill the remain channels with data from src tensor
|
||||
for (int64_t i0 = n_dims; i0 < ne0; i0 += 2) {
|
||||
for (int64_t i0 = 0; i0 < ne0; i0 += 2) {
|
||||
if (i0 == n_offs) {
|
||||
i0 += n_dims - 2; // skip the rotated channels
|
||||
continue;
|
||||
}
|
||||
const T * const src = (T *)((char *) src0->data + i3*nb03 + i2*nb02 + i1*nb01 + i0*nb00);
|
||||
T * dst_data = (T *)((char *) dst->data + i3*nb3 + i2*nb2 + i1*nb1 + i0*nb0);
|
||||
|
||||
|
||||
@@ -29,13 +29,15 @@ extern "C" {
|
||||
// FP16 to FP32 conversion
|
||||
|
||||
// 16-bit float
|
||||
// on Arm, we use __fp16
|
||||
// on Arm, we use __fp16, which requires the IEEE fp16 format: implied on
|
||||
// AArch64, selected by -mfp16-format=ieee on 32 bit Arm, where the compiler
|
||||
// may otherwise reject the type
|
||||
// on x86, we use uint16_t
|
||||
//
|
||||
// for old CUDA compilers (<= 11), we use uint16_t: ref https://github.com/ggml-org/llama.cpp/pull/10616
|
||||
// for MUSA compilers , we use uint16_t: ref https://github.com/ggml-org/llama.cpp/pull/11843
|
||||
//
|
||||
#if defined(__ARM_NEON) && !(defined(__CUDACC__) && __CUDACC_VER_MAJOR__ <= 11) && !defined(__MUSACC__)
|
||||
#if defined(__ARM_NEON) && defined(__ARM_FP16_FORMAT_IEEE) && !(defined(__CUDACC__) && __CUDACC_VER_MAJOR__ <= 11) && !defined(__MUSACC__)
|
||||
#define GGML_CPU_COMPUTE_FP16_TO_FP32(x) neon_compute_fp16_to_fp32(x)
|
||||
#define GGML_CPU_COMPUTE_FP32_TO_FP16(x) neon_compute_fp32_to_fp16(x)
|
||||
|
||||
@@ -326,7 +328,7 @@ inline static float ggml_lookup_fp16_to_fp32(ggml_fp16_t f) {
|
||||
#define GGML_F16_VEC_REDUCE GGML_F32Cx4_REDUCE
|
||||
#endif
|
||||
|
||||
#elif defined(__ARM_NEON) && defined(__ARM_FEATURE_FMA)
|
||||
#elif defined(__ARM_NEON) && defined(__ARM_FEATURE_FMA) && defined(__ARM_FP16_FORMAT_IEEE)
|
||||
|
||||
#define GGML_SIMD
|
||||
|
||||
|
||||
@@ -1418,7 +1418,9 @@ struct ggml_backend_cuda_context {
|
||||
cudaEvent_t copy_event = nullptr;
|
||||
|
||||
cudaStream_t streams[GGML_CUDA_MAX_DEVICES][GGML_CUDA_MAX_STREAMS] = { { nullptr } };
|
||||
cublasHandle_t cublas_handles[GGML_CUDA_MAX_DEVICES] = {nullptr};
|
||||
cublasHandle_t cublas_handles[GGML_CUDA_MAX_DEVICES][GGML_CUDA_MAX_STREAMS] = {nullptr};
|
||||
void * cublas_workspaces[GGML_CUDA_MAX_DEVICES][GGML_CUDA_MAX_STREAMS] = {nullptr};
|
||||
size_t cublas_workspace_sizes[GGML_CUDA_MAX_DEVICES] = {0};
|
||||
|
||||
int curr_stream_no = 0;
|
||||
|
||||
@@ -1495,17 +1497,22 @@ struct ggml_backend_cuda_context {
|
||||
|
||||
ggml_cuda_stream_context & stream_context() { return concurrent_stream_context; }
|
||||
|
||||
cublasHandle_t cublas_handle(int device) {
|
||||
if (cublas_handles[device] == nullptr) {
|
||||
ggml_cuda_set_device(device);
|
||||
CUBLAS_CHECK(cublasCreate(&cublas_handles[device]));
|
||||
CUBLAS_CHECK(cublasSetMathMode(cublas_handles[device], CUBLAS_TF32_TENSOR_OP_MATH));
|
||||
}
|
||||
return cublas_handles[device];
|
||||
}
|
||||
|
||||
cublasHandle_t cublas_handle() {
|
||||
return cublas_handle(device);
|
||||
if (cublas_handles[device][curr_stream_no] == nullptr) {
|
||||
ggml_cuda_set_device(device);
|
||||
CUBLAS_CHECK(cublasCreate(&cublas_handles[device][curr_stream_no]));
|
||||
CUBLAS_CHECK(cublasSetMathMode(cublas_handles[device][curr_stream_no], CUBLAS_TF32_TENSOR_OP_MATH));
|
||||
CUBLAS_CHECK(cublasSetStream(cublas_handles[device][curr_stream_no], stream()));
|
||||
#if !defined(GGML_USE_HIP) && !defined(GGML_USE_MUSA) && (CUBLAS_VER_MAJOR > 11 || (CUBLAS_VER_MAJOR == 11 && CUBLAS_VER_MINOR >= 2))
|
||||
if (cublas_workspace_sizes[device] == 0) {
|
||||
const int cc = ggml_cuda_info().devices[device].cc;
|
||||
cublas_workspace_sizes[device] = (cc >= GGML_CUDA_CC_HOPPER) ? 32 * 1024 * 1024 : 4 * 1024 * 1024;
|
||||
}
|
||||
CUDA_CHECK(cudaMalloc(&cublas_workspaces[device][curr_stream_no], cublas_workspace_sizes[device]));
|
||||
CUBLAS_CHECK(cublasSetWorkspace(cublas_handles[device][curr_stream_no], cublas_workspaces[device][curr_stream_no], cublas_workspace_sizes[device]));
|
||||
#endif
|
||||
}
|
||||
return cublas_handles[device][curr_stream_no];
|
||||
}
|
||||
|
||||
// pool
|
||||
|
||||
@@ -711,9 +711,12 @@ ggml_backend_cuda_context::~ggml_backend_cuda_context() {
|
||||
if (streams[i][j] != nullptr) {
|
||||
CUDA_CHECK(cudaStreamDestroy(streams[i][j]));
|
||||
}
|
||||
}
|
||||
if (cublas_handles[i] != nullptr) {
|
||||
CUBLAS_CHECK(cublasDestroy(cublas_handles[i]));
|
||||
if (cublas_handles[i][j] != nullptr) {
|
||||
CUBLAS_CHECK(cublasDestroy(cublas_handles[i][j]));
|
||||
}
|
||||
if (cublas_workspaces[i][j] != nullptr) {
|
||||
CUDA_CHECK(cudaFree(cublas_workspaces[i][j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1416,7 +1419,7 @@ static void ggml_cuda_mul_mat_cublas_impl(ggml_backend_cuda_context & ctx, const
|
||||
|
||||
const int64_t ne_dst = ggml_nelements(dst);
|
||||
cudaStream_t main_stream = ctx.stream();
|
||||
CUBLAS_CHECK(cublasSetStream(ctx.cublas_handle(), main_stream));
|
||||
cublasHandle_t cublas_h = ctx.cublas_handle();
|
||||
|
||||
const size_t src0_ts = ggml_type_size(src0->type);
|
||||
GGML_ASSERT(nb00 == src0_ts);
|
||||
@@ -1539,14 +1542,14 @@ static void ggml_cuda_mul_mat_cublas_impl(ggml_backend_cuda_context & ctx, const
|
||||
// probably because the internal kernel selection logic is suboptimal.
|
||||
if (compute_type == GGML_TYPE_F32 && ne12 == 1 && ne13 == 1) {
|
||||
CUBLAS_CHECK(
|
||||
cublasSgemm(ctx.cublas_handle(), CUBLAS_OP_T, CUBLAS_OP_N,
|
||||
cublasSgemm(cublas_h, CUBLAS_OP_T, CUBLAS_OP_N,
|
||||
ne01, ne11, ne10,
|
||||
(const float *) alpha, (const float *) src0_ptr, s01,
|
||||
(const float *) src1_ptr, s11,
|
||||
(const float *) beta, (float *) dst_ptr, ne0));
|
||||
} else if (ne12 == 1 && ne13 == 1) {
|
||||
CUBLAS_CHECK(
|
||||
cublasGemmEx(ctx.cublas_handle(), CUBLAS_OP_T, CUBLAS_OP_N,
|
||||
cublasGemmEx(cublas_h, CUBLAS_OP_T, CUBLAS_OP_N,
|
||||
ne01, ne11, ne10,
|
||||
alpha, src0_ptr, cu_data_type_a, s01,
|
||||
src1_ptr, cu_data_type_b, s11,
|
||||
@@ -1561,7 +1564,7 @@ static void ggml_cuda_mul_mat_cublas_impl(ggml_backend_cuda_context & ctx, const
|
||||
// there is no broadcast and src0, src1 are contiguous across dims 2, 3
|
||||
// use cublasGemmStridedBatchedEx
|
||||
CUBLAS_CHECK(
|
||||
cublasGemmStridedBatchedEx(ctx.cublas_handle(), CUBLAS_OP_T, CUBLAS_OP_N,
|
||||
cublasGemmStridedBatchedEx(cublas_h, CUBLAS_OP_T, CUBLAS_OP_N,
|
||||
ne01, ne11, ne10,
|
||||
alpha, src0_ptr, cu_data_type_a, s01, sma, // strideA
|
||||
src1_ptr, cu_data_type_b, s11, smb, // strideB
|
||||
@@ -1599,7 +1602,7 @@ static void ggml_cuda_mul_mat_cublas_impl(ggml_backend_cuda_context & ctx, const
|
||||
CUDA_CHECK(cudaGetLastError());
|
||||
|
||||
CUBLAS_CHECK(
|
||||
cublasGemmBatchedEx(ctx.cublas_handle(), CUBLAS_OP_T, CUBLAS_OP_N,
|
||||
cublasGemmBatchedEx(cublas_h, CUBLAS_OP_T, CUBLAS_OP_N,
|
||||
ne01, ne11, ne10,
|
||||
alpha, (const void **) (ptrs_src.get() + 0*ne23), cu_data_type_a, s01,
|
||||
(const void **) (ptrs_src.get() + 1*ne23), cu_data_type_b, s11,
|
||||
@@ -2723,6 +2726,12 @@ static bool ggml_cuda_should_fuse_rms_norm_mul_rope(const ggml_tensor * rms_norm
|
||||
return false;
|
||||
}
|
||||
|
||||
// ggml_rope_set_offset is not yet supported in the fused kernel
|
||||
const int n_offs = ((const int32_t *) rope->op_params)[15];
|
||||
if (n_offs != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -290,6 +290,42 @@ bool ggml_cuda_should_use_mmvq(enum ggml_type type, int cc, int64_t ne11) {
|
||||
if (!ggml_is_quantized(type)) {
|
||||
return false;
|
||||
}
|
||||
// k-quants cost more to decode and mvq redoes that per column, so MMQ wins sooner.
|
||||
// Only list quant-types MMQ supports, others would fall back to cuBLAS.
|
||||
if (GGML_CUDA_CC_IS_NVIDIA(cc) && cc == GGML_CUDA_CC_ADA_LOVELACE) {
|
||||
switch (type) { // tuned on RTX 4090
|
||||
case GGML_TYPE_Q2_K:
|
||||
return ne11 <= 4;
|
||||
case GGML_TYPE_Q3_K:
|
||||
return ne11 <= 6;
|
||||
case GGML_TYPE_Q4_K:
|
||||
case GGML_TYPE_Q5_K:
|
||||
return ne11 <= 7;
|
||||
default:
|
||||
return ne11 <= MMVQ_MAX_BATCH_SIZE;
|
||||
}
|
||||
}
|
||||
if (GGML_CUDA_CC_IS_NVIDIA(cc) && cc == GGML_CUDA_CC_BLACKWELL) {
|
||||
switch (type) { // tuned on RTX 5090
|
||||
case GGML_TYPE_Q2_K:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_Q4_K:
|
||||
case GGML_TYPE_Q5_K:
|
||||
return ne11 <= 5;
|
||||
case GGML_TYPE_Q6_K:
|
||||
return ne11 <= 7;
|
||||
default:
|
||||
return ne11 <= MMVQ_MAX_BATCH_SIZE;
|
||||
}
|
||||
}
|
||||
if (GGML_CUDA_CC_IS_NVIDIA(cc) && cc == GGML_CUDA_CC_DGX_SPARK) {
|
||||
switch (type) { // tuned on DGX Spark GB10
|
||||
case GGML_TYPE_Q2_K:
|
||||
return ne11 <= 6;
|
||||
default:
|
||||
return ne11 <= MMVQ_MAX_BATCH_SIZE;
|
||||
}
|
||||
}
|
||||
if (GGML_CUDA_CC_IS_CDNA(cc)) {
|
||||
if (GGML_CUDA_CC_IS_CDNA1(cc)) {
|
||||
switch (type) {
|
||||
|
||||
@@ -54,8 +54,6 @@ void ggml_cuda_out_prod(ggml_backend_cuda_context & ctx, ggml_tensor * dst) {
|
||||
const float alpha = 1.0f;
|
||||
const float beta = 0.0f;
|
||||
|
||||
CUBLAS_CHECK(cublasSetStream(handle, stream));
|
||||
|
||||
const int64_t lda = nb01 / sizeof(float);
|
||||
const int64_t ldc = nb1 / sizeof(float);
|
||||
|
||||
|
||||
+93
-59
@@ -53,6 +53,7 @@ static __global__ void rope_norm(const T * x,
|
||||
const int s2,
|
||||
const int s3,
|
||||
const int n_dims,
|
||||
const int n_offs,
|
||||
const int32_t * pos,
|
||||
const float freq_scale,
|
||||
const float ext_factor,
|
||||
@@ -61,7 +62,8 @@ static __global__ void rope_norm(const T * x,
|
||||
const float theta_scale,
|
||||
const float * freq_factors,
|
||||
const int64_t * row_indices,
|
||||
const int set_rows_stride) {
|
||||
const int set_rows_stride,
|
||||
const bool inplace) {
|
||||
const int i0 = 2*(blockDim.y*blockIdx.y + threadIdx.y);
|
||||
|
||||
if (i0 >= ne00) {
|
||||
@@ -92,19 +94,24 @@ static __global__ void rope_norm(const T * x,
|
||||
ggml_cuda_memcpy_1<4>(dst + idst, &v);
|
||||
}
|
||||
};
|
||||
if (i0 >= n_dims) {
|
||||
if (i0 < n_offs || i0 >= n_offs + n_dims) {
|
||||
if (inplace) {
|
||||
return;
|
||||
}
|
||||
store_coaelsced(x[ix + 0], x[ix + 1]);
|
||||
return;
|
||||
}
|
||||
|
||||
const float theta_base = pos[i2]*powf(theta_scale, i0/2.0f);
|
||||
const int iw = i0 - n_offs; // relative idx
|
||||
|
||||
const float freq_factor = has_ff ? freq_factors[i0/2] : 1.0f;
|
||||
const float theta_base = pos[i2]*powf(theta_scale, iw/2.0f);
|
||||
|
||||
const float freq_factor = has_ff ? freq_factors[iw/2] : 1.0f;
|
||||
|
||||
float cos_theta;
|
||||
float sin_theta;
|
||||
|
||||
rope_yarn<forward>(theta_base/freq_factor, freq_scale, corr_dims, i0, ext_factor, attn_factor, cos_theta, sin_theta);
|
||||
rope_yarn<forward>(theta_base/freq_factor, freq_scale, corr_dims, iw, ext_factor, attn_factor, cos_theta, sin_theta);
|
||||
|
||||
const float x0 = x[ix + 0];
|
||||
const float x1 = x[ix + 1];
|
||||
@@ -125,6 +132,7 @@ static __global__ void rope_neox(const T * x,
|
||||
const int s2,
|
||||
const int s3,
|
||||
const int n_dims,
|
||||
const int n_offs,
|
||||
const int32_t * pos,
|
||||
const float freq_scale,
|
||||
const float ext_factor,
|
||||
@@ -133,7 +141,8 @@ static __global__ void rope_neox(const T * x,
|
||||
const float theta_scale,
|
||||
const float * freq_factors,
|
||||
const int64_t * row_indices,
|
||||
const int set_rows_stride) {
|
||||
const int set_rows_stride,
|
||||
const bool inplace) {
|
||||
ggml_cuda_pdl_lc();
|
||||
const int i0 = 2*(blockDim.y*blockIdx.y + threadIdx.y);
|
||||
|
||||
@@ -158,27 +167,33 @@ static __global__ void rope_neox(const T * x,
|
||||
idst += row_indices[i2] * set_rows_stride;
|
||||
}
|
||||
|
||||
if (i0 >= n_dims) {
|
||||
if (i0 < n_offs || i0 >= n_offs + n_dims) {
|
||||
if (inplace) {
|
||||
return;
|
||||
}
|
||||
dst[idst + i0 / 2 + 0] = ggml_cuda_cast<D>(x[ix + i0 / 2 + 0]);
|
||||
dst[idst + i0 / 2 + 1] = ggml_cuda_cast<D>(x[ix + i0 / 2 + 1]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const float theta_base = pos[i2]*powf(theta_scale, i0/2.0f);
|
||||
const int iw = i0 - n_offs; // relative idx
|
||||
|
||||
const float freq_factor = has_ff ? freq_factors[i0/2] : 1.0f;
|
||||
const float theta_base = pos[i2]*powf(theta_scale, iw/2.0f);
|
||||
|
||||
const float freq_factor = has_ff ? freq_factors[iw/2] : 1.0f;
|
||||
|
||||
float cos_theta;
|
||||
float sin_theta;
|
||||
|
||||
rope_yarn<forward>(theta_base/freq_factor, freq_scale, corr_dims, i0, ext_factor, attn_factor, cos_theta, sin_theta);
|
||||
rope_yarn<forward>(theta_base/freq_factor, freq_scale, corr_dims, iw, ext_factor, attn_factor, cos_theta, sin_theta);
|
||||
|
||||
const float x0 = x[ix + 0];
|
||||
const float x1 = x[ix + n_dims/2];
|
||||
// idst/ix point at channel i0/2; the first channel of the rotated pair is n_offs + iw/2 = i0/2 + n_offs/2
|
||||
const float x0 = x[ix + n_offs/2 + 0];
|
||||
const float x1 = x[ix + n_offs/2 + n_dims/2];
|
||||
|
||||
dst[idst + 0] = ggml_cuda_cast<D>(x0 * cos_theta - x1 * sin_theta);
|
||||
dst[idst + n_dims / 2] = ggml_cuda_cast<D>(x0 * sin_theta + x1 * cos_theta);
|
||||
dst[idst + n_offs/2 + 0] = ggml_cuda_cast<D>(x0 * cos_theta - x1 * sin_theta);
|
||||
dst[idst + n_offs/2 + n_dims / 2] = ggml_cuda_cast<D>(x0 * sin_theta + x1 * cos_theta);
|
||||
}
|
||||
|
||||
template <bool forward, bool has_ff, typename T>
|
||||
@@ -194,6 +209,7 @@ static __global__ void rope_multi(const T * x,
|
||||
const int s2,
|
||||
const int s3,
|
||||
const int n_dims,
|
||||
const int n_offs,
|
||||
const int32_t * pos,
|
||||
const float freq_scale,
|
||||
const float ext_factor,
|
||||
@@ -202,7 +218,8 @@ static __global__ void rope_multi(const T * x,
|
||||
const float theta_scale,
|
||||
const float * freq_factors,
|
||||
const mrope_sections sections,
|
||||
const bool is_imrope) {
|
||||
const bool is_imrope,
|
||||
const bool inplace) {
|
||||
const int i0 = 2 * (blockDim.y * blockIdx.y + threadIdx.y);
|
||||
|
||||
if (i0 >= ne00) {
|
||||
@@ -219,52 +236,58 @@ static __global__ void rope_multi(const T * x,
|
||||
const int ix = i0 / 2 + i1 * s01 + i2 * s02 + i3 * s03;
|
||||
|
||||
ggml_cuda_pdl_sync();
|
||||
if (i0 >= n_dims) {
|
||||
if (i0 < n_offs || i0 >= n_offs + n_dims) {
|
||||
if (inplace) {
|
||||
return;
|
||||
}
|
||||
dst[idst + i0/2 + 0] = x[ix + i0/2 + 0];
|
||||
dst[idst + i0/2 + 1] = x[ix + i0/2 + 1];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const int iw = i0 - n_offs; // relative idx
|
||||
|
||||
const int sect_dims = sections.v[0] + sections.v[1] + sections.v[2] + sections.v[3];
|
||||
const int sec_w = sections.v[1] + sections.v[0];
|
||||
const int sector = (i0 / 2) % sect_dims;
|
||||
const int sector = (iw / 2) % sect_dims;
|
||||
|
||||
float theta_base = 0.0;
|
||||
if (is_imrope) {
|
||||
if (sector % 3 == 1 && sector < 3 * sections.v[1]) { // h
|
||||
theta_base = pos[i2 + ne02 * 1] * powf(theta_scale, i0 / 2.0f);
|
||||
theta_base = pos[i2 + ne02 * 1] * powf(theta_scale, iw / 2.0f);
|
||||
} else if (sector % 3 == 2 && sector < 3 * sections.v[2]) { // w
|
||||
theta_base = pos[i2 + ne02 * 2] * powf(theta_scale, i0 / 2.0f);
|
||||
theta_base = pos[i2 + ne02 * 2] * powf(theta_scale, iw / 2.0f);
|
||||
} else if (sector % 3 == 0 && sector < 3 * sections.v[0]) { // t
|
||||
theta_base = pos[i2] * powf(theta_scale, i0 / 2.0f);
|
||||
theta_base = pos[i2] * powf(theta_scale, iw / 2.0f);
|
||||
} else {
|
||||
theta_base = pos[i2 + ne02 * 3] * powf(theta_scale, i0 / 2.0f);
|
||||
theta_base = pos[i2 + ne02 * 3] * powf(theta_scale, iw / 2.0f);
|
||||
}
|
||||
} else {
|
||||
if (sector < sections.v[0]) {
|
||||
theta_base = pos[i2] * powf(theta_scale, i0 / 2.0f);
|
||||
theta_base = pos[i2] * powf(theta_scale, iw / 2.0f);
|
||||
} else if (sector >= sections.v[0] && sector < sec_w) {
|
||||
theta_base = pos[i2 + ne02 * 1] * powf(theta_scale, i0 / 2.0f);
|
||||
theta_base = pos[i2 + ne02 * 1] * powf(theta_scale, iw / 2.0f);
|
||||
} else if (sector >= sec_w && sector < sec_w + sections.v[2]) {
|
||||
theta_base = pos[i2 + ne02 * 2] * powf(theta_scale, i0 / 2.0f);
|
||||
theta_base = pos[i2 + ne02 * 2] * powf(theta_scale, iw / 2.0f);
|
||||
} else if (sector >= sec_w + sections.v[2]) {
|
||||
theta_base = pos[i2 + ne02 * 3] * powf(theta_scale, i0 / 2.0f);
|
||||
theta_base = pos[i2 + ne02 * 3] * powf(theta_scale, iw / 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
const float freq_factor = has_ff ? freq_factors[i0/2] : 1.0f;
|
||||
const float freq_factor = has_ff ? freq_factors[iw/2] : 1.0f;
|
||||
|
||||
float cos_theta;
|
||||
float sin_theta;
|
||||
|
||||
rope_yarn<forward>(theta_base/freq_factor, freq_scale, corr_dims, i0, ext_factor, attn_factor, cos_theta, sin_theta);
|
||||
rope_yarn<forward>(theta_base/freq_factor, freq_scale, corr_dims, iw, ext_factor, attn_factor, cos_theta, sin_theta);
|
||||
|
||||
const float x0 = x[ix + 0];
|
||||
const float x1 = x[ix + n_dims/2];
|
||||
// idst/ix point at channel i0/2; the first channel of the rotated pair is n_offs + iw/2 = i0/2 + n_offs/2
|
||||
const float x0 = x[ix + n_offs/2 + 0];
|
||||
const float x1 = x[ix + n_offs/2 + n_dims/2];
|
||||
|
||||
dst[idst + 0] = x0*cos_theta - x1*sin_theta;
|
||||
dst[idst + n_dims/2] = x0*sin_theta + x1*cos_theta;
|
||||
dst[idst + n_offs/2 + 0] = x0*cos_theta - x1*sin_theta;
|
||||
dst[idst + n_offs/2 + n_dims/2] = x0*sin_theta + x1*cos_theta;
|
||||
}
|
||||
|
||||
template <bool forward, bool has_ff, typename T>
|
||||
@@ -344,6 +367,7 @@ static void rope_norm_cuda(const T * x,
|
||||
const int s2,
|
||||
const int s3,
|
||||
const int n_dims,
|
||||
const int n_offs,
|
||||
const int nr,
|
||||
const int32_t * pos,
|
||||
const float freq_scale,
|
||||
@@ -354,6 +378,7 @@ static void rope_norm_cuda(const T * x,
|
||||
const float * freq_factors,
|
||||
const int64_t * row_indices,
|
||||
const int set_rows_stride,
|
||||
const bool inplace,
|
||||
cudaStream_t stream) {
|
||||
GGML_ASSERT(ne00 % 2 == 0);
|
||||
const dim3 block_dims(1, CUDA_ROPE_BLOCK_SIZE, 1);
|
||||
@@ -364,12 +389,12 @@ static void rope_norm_cuda(const T * x,
|
||||
|
||||
if (freq_factors == nullptr) {
|
||||
rope_norm<forward, false><<<block_nums, block_dims, 0, stream>>>(
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, row_indices, set_rows_stride);
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, n_offs, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, row_indices, set_rows_stride, inplace);
|
||||
} else {
|
||||
rope_norm<forward, true><<<block_nums, block_dims, 0, stream>>>(
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, row_indices, set_rows_stride);
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, n_offs, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, row_indices, set_rows_stride, inplace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,6 +411,7 @@ static void rope_neox_cuda(const T * x,
|
||||
const int s2,
|
||||
const int s3,
|
||||
const int n_dims,
|
||||
const int n_offs,
|
||||
const int nr,
|
||||
const int32_t * pos,
|
||||
const float freq_scale,
|
||||
@@ -396,6 +422,7 @@ static void rope_neox_cuda(const T * x,
|
||||
const float * freq_factors,
|
||||
const int64_t * row_indices,
|
||||
const int set_rows_stride,
|
||||
const bool inplace,
|
||||
cudaStream_t stream) {
|
||||
GGML_ASSERT(ne00 % 2 == 0);
|
||||
const dim3 block_dims(1, CUDA_ROPE_BLOCK_SIZE, 1);
|
||||
@@ -407,12 +434,12 @@ static void rope_neox_cuda(const T * x,
|
||||
|
||||
if (freq_factors == nullptr) {
|
||||
ggml_cuda_kernel_launch(rope_neox<forward, false, T, D>, launch_params,
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, row_indices, set_rows_stride);
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, n_offs, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, row_indices, set_rows_stride, inplace);
|
||||
} else {
|
||||
ggml_cuda_kernel_launch(rope_neox<forward, true, T, D>, launch_params,
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, row_indices, set_rows_stride);
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, n_offs, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, row_indices, set_rows_stride, inplace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,6 +456,7 @@ static void rope_multi_cuda(const T * x,
|
||||
const int s2,
|
||||
const int s3,
|
||||
const int n_dims,
|
||||
const int n_offs,
|
||||
const int nr,
|
||||
const int32_t * pos,
|
||||
const float freq_scale,
|
||||
@@ -439,6 +467,7 @@ static void rope_multi_cuda(const T * x,
|
||||
const float * freq_factors,
|
||||
const mrope_sections sections,
|
||||
const bool is_imrope,
|
||||
const bool inplace,
|
||||
cudaStream_t stream) {
|
||||
GGML_ASSERT(ne00 % 2 == 0);
|
||||
const dim3 block_dims(1, CUDA_ROPE_BLOCK_SIZE, 1);
|
||||
@@ -450,13 +479,13 @@ static void rope_multi_cuda(const T * x,
|
||||
if (freq_factors == nullptr) {
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(block_nums, block_dims, 0, stream);
|
||||
ggml_cuda_kernel_launch(rope_multi<forward, false, T>, launch_params,
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, sections, is_imrope);
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, n_offs, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, sections, is_imrope, inplace);
|
||||
} else {
|
||||
const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(block_nums, block_dims, 0, stream);
|
||||
ggml_cuda_kernel_launch(rope_multi<forward, true, T>, launch_params,
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, sections, is_imrope);
|
||||
x, dst, ne00, ne01, ne02, s01, s02, s03, s1, s2, s3, n_dims, n_offs, pos, freq_scale, ext_factor,
|
||||
attn_factor, corr_dims, theta_scale, freq_factors, sections, is_imrope, inplace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,8 +581,12 @@ void ggml_cuda_op_rope_impl(ggml_backend_cuda_context & ctx,
|
||||
const int mode = ((int32_t *) dst->op_params)[2];
|
||||
//const int n_ctx = ((int32_t *) dst->op_params)[3];
|
||||
const int n_ctx_orig = ((int32_t *) dst->op_params)[4];
|
||||
const int n_offs = ((int32_t *) dst->op_params)[15];
|
||||
mrope_sections sections;
|
||||
|
||||
// when dst aliases src0, the channels outside the rotated window already hold the correct data
|
||||
const bool inplace = dst_d == src0->data;
|
||||
|
||||
// RoPE alteration for extended context
|
||||
float freq_base;
|
||||
float freq_scale;
|
||||
@@ -581,6 +614,7 @@ void ggml_cuda_op_rope_impl(ggml_backend_cuda_context & ctx,
|
||||
|
||||
if (is_vision) {
|
||||
GGML_ASSERT(n_dims == ne00/2);
|
||||
GGML_ASSERT(n_offs == 0); // offset not supported for vision, as the rotated pairs span the whole row
|
||||
}
|
||||
|
||||
const int32_t * pos = (const int32_t *) src1_d;
|
||||
@@ -597,31 +631,31 @@ void ggml_cuda_op_rope_impl(ggml_backend_cuda_context & ctx,
|
||||
if (is_neox) {
|
||||
if (src0->type == GGML_TYPE_F32 && dst_type == GGML_TYPE_F32) {
|
||||
rope_neox_cuda<forward, float, float>((const float *) src0_d, (float *) dst_d, ne00, ne01, ne02, s01, s02,
|
||||
s03, s1, s2, s3, n_dims, nr, pos, freq_scale, freq_base,
|
||||
s03, s1, s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base,
|
||||
ext_factor, attn_factor, corr_dims, freq_factors, row_indices,
|
||||
set_rows_stride, stream);
|
||||
set_rows_stride, inplace, stream);
|
||||
} else if (src0->type == GGML_TYPE_F32 && dst_type == GGML_TYPE_F16) {
|
||||
rope_neox_cuda<forward, float, half>((const float *) src0_d, (half *) dst_d, ne00, ne01, ne02, s01, s02,
|
||||
s03, s1, s2, s3, n_dims, nr, pos, freq_scale, freq_base,
|
||||
s03, s1, s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base,
|
||||
ext_factor, attn_factor, corr_dims, freq_factors, row_indices,
|
||||
set_rows_stride, stream);
|
||||
set_rows_stride, inplace, stream);
|
||||
} else if (src0->type == GGML_TYPE_F16 && dst_type == GGML_TYPE_F16) {
|
||||
rope_neox_cuda<forward, half, half>((const half *) src0_d, (half *) dst_d, ne00, ne01, ne02, s01, s02,
|
||||
s03, s1, s2, s3, n_dims, nr, pos, freq_scale, freq_base,
|
||||
s03, s1, s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base,
|
||||
ext_factor, attn_factor, corr_dims, freq_factors, row_indices,
|
||||
set_rows_stride, stream);
|
||||
set_rows_stride, inplace, stream);
|
||||
} else {
|
||||
GGML_ABORT("fatal error");
|
||||
}
|
||||
} else if (is_mrope && !is_vision) {
|
||||
if (src0->type == GGML_TYPE_F32) {
|
||||
rope_multi_cuda<forward>((const float *) src0_d, (float *) dst_d, ne00, ne01, ne02, s01, s02, s03, s1,
|
||||
s2, s3, n_dims, nr, pos, freq_scale, freq_base, ext_factor, attn_factor,
|
||||
corr_dims, freq_factors, sections, is_imrope, stream);
|
||||
s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base, ext_factor, attn_factor,
|
||||
corr_dims, freq_factors, sections, is_imrope, inplace, stream);
|
||||
} else if (src0->type == GGML_TYPE_F16) {
|
||||
rope_multi_cuda<forward>((const half *) src0_d, (half *) dst_d, ne00, ne01, ne02, s01, s02, s03, s1,
|
||||
s2, s3, n_dims, nr, pos, freq_scale, freq_base, ext_factor, attn_factor,
|
||||
corr_dims, freq_factors, sections, is_imrope, stream);
|
||||
s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base, ext_factor, attn_factor,
|
||||
corr_dims, freq_factors, sections, is_imrope, inplace, stream);
|
||||
} else {
|
||||
GGML_ABORT("fatal error");
|
||||
}
|
||||
@@ -640,19 +674,19 @@ void ggml_cuda_op_rope_impl(ggml_backend_cuda_context & ctx,
|
||||
} else {
|
||||
if (src0->type == GGML_TYPE_F32 && dst_type == GGML_TYPE_F32) {
|
||||
rope_norm_cuda<forward, float, float>((const float *) src0_d, (float *) dst_d, ne00, ne01, ne02, s01, s02,
|
||||
s03, s1, s2, s3, n_dims, nr, pos, freq_scale, freq_base,
|
||||
s03, s1, s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base,
|
||||
ext_factor, attn_factor, corr_dims, freq_factors, row_indices,
|
||||
set_rows_stride, stream);
|
||||
set_rows_stride, inplace, stream);
|
||||
} else if (src0->type == GGML_TYPE_F32 && dst_type == GGML_TYPE_F16) {
|
||||
rope_norm_cuda<forward, float, half>((const float *) src0_d, (half *) dst_d, ne00, ne01, ne02, s01, s02,
|
||||
s03, s1, s2, s3, n_dims, nr, pos, freq_scale, freq_base,
|
||||
s03, s1, s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base,
|
||||
ext_factor, attn_factor, corr_dims, freq_factors, row_indices,
|
||||
set_rows_stride, stream);
|
||||
set_rows_stride, inplace, stream);
|
||||
} else if (src0->type == GGML_TYPE_F16 && dst_type == GGML_TYPE_F16) {
|
||||
rope_norm_cuda<forward, half, half>((const half *) src0_d, (half *) dst_d, ne00, ne01, ne02, s01, s02,
|
||||
s03, s1, s2, s3, n_dims, nr, pos, freq_scale, freq_base,
|
||||
s03, s1, s2, s3, n_dims, n_offs, nr, pos, freq_scale, freq_base,
|
||||
ext_factor, attn_factor, corr_dims, freq_factors, row_indices,
|
||||
set_rows_stride, stream);
|
||||
set_rows_stride, inplace, stream);
|
||||
} else {
|
||||
GGML_ABORT("fatal error");
|
||||
}
|
||||
|
||||
@@ -65,15 +65,13 @@ static void solve_tri_f32_cublas(ggml_backend_cuda_context & ctx,
|
||||
get_batch_pointers<<<(total_batches + 255) / 256, 256, 0, stream>>>(A, X, A_ptrs_dev, X_ptrs_dev, ne02,
|
||||
total_batches, s02, s03, s2, s3);
|
||||
|
||||
CUBLAS_CHECK(cublasSetStream(ctx.cublas_handle(id), stream));
|
||||
|
||||
// Yes, this is necessary, without this we get RMSE errors
|
||||
CUBLAS_CHECK(cublasSetMathMode(ctx.cublas_handle(id), CUBLAS_DEFAULT_MATH));
|
||||
CUBLAS_CHECK(cublasStrsmBatched(ctx.cublas_handle(id), CUBLAS_SIDE_RIGHT, CUBLAS_FILL_MODE_UPPER, CUBLAS_OP_N,
|
||||
CUBLAS_CHECK(cublasSetMathMode(ctx.cublas_handle(), CUBLAS_DEFAULT_MATH));
|
||||
CUBLAS_CHECK(cublasStrsmBatched(ctx.cublas_handle(), CUBLAS_SIDE_RIGHT, CUBLAS_FILL_MODE_UPPER, CUBLAS_OP_N,
|
||||
CUBLAS_DIAG_NON_UNIT, k, n, &alpha, A_ptrs_dev, n, X_ptrs_dev, k, total_batches));
|
||||
|
||||
// revert to standard mode from common.cuh
|
||||
CUBLAS_CHECK(cublasSetMathMode(ctx.cublas_handle(id), CUBLAS_TF32_TENSOR_OP_MATH));
|
||||
CUBLAS_CHECK(cublasSetMathMode(ctx.cublas_handle(), CUBLAS_TF32_TENSOR_OP_MATH));
|
||||
|
||||
GGML_UNUSED_VARS(s12, s13);
|
||||
}
|
||||
|
||||
@@ -632,7 +632,6 @@ static void ssm_scan_ssd_f32_cuda(
|
||||
// Step 3: chunked SSD loop
|
||||
// Per chunk: pre_matmul (incl. M) + 4 cuBLAS (CB, Y, S@C, state update) + scale_state
|
||||
cublasHandle_t handle = ctx.cublas_handle();
|
||||
CUBLAS_CHECK(cublasSetStream(handle, stream));
|
||||
const float alpha_one = 1.0f;
|
||||
const float beta_zero = 0.0f;
|
||||
const float beta_one = 1.0f;
|
||||
|
||||
@@ -1061,9 +1061,11 @@ static bool ggml_backend_et_device_supports_op(ggml_backend_dev_t dev, const ggm
|
||||
const bool zero_view_offset = op->src[0]->view_src == nullptr || op->src[0]->view_offs == 0;
|
||||
const bool has_sections = ggml_get_op_params_i32(op, 11) > 0 || ggml_get_op_params_i32(op, 12) > 0 ||
|
||||
ggml_get_op_params_i32(op, 13) > 0;
|
||||
// FIXME: support ggml_rope_set_offset
|
||||
const bool zero_rot_offset = ggml_get_op_params_i32(op, 15) == 0;
|
||||
|
||||
supported =
|
||||
zero_view_offset && ndims <= 512 &&
|
||||
zero_view_offset && zero_rot_offset && ndims <= 512 &&
|
||||
(is_normal || (is_neox && ndims % 16 == 0) || (is_imrope && ndims % 16 == 0 && has_sections));
|
||||
} else {
|
||||
supported = false;
|
||||
|
||||
@@ -3180,6 +3180,10 @@ static bool ggml_hexagon_supported_argsort(const struct ggml_hexagon_session * s
|
||||
static bool ggml_hexagon_supported_rope(const struct ggml_hexagon_session * sess, const struct ggml_tensor * op) {
|
||||
const int32_t * op_params = &op->op_params[0];
|
||||
|
||||
if (op_params[15] != 0) {
|
||||
return false; // FIXME: support ggml_rope_set_offset
|
||||
}
|
||||
|
||||
int mode = op_params[2];
|
||||
|
||||
// n_dims == ne0/2, so the rotation spans the full row
|
||||
|
||||
@@ -132,8 +132,8 @@ struct hmx_fa_context {
|
||||
__fp16 * vtcm_v_tiles[2]; // V tiles (column-major, double-buffered)
|
||||
__fp16 * vtcm_s_tiles[2]; // S = QK^T [g_br, Bc] (double-buffered)
|
||||
__fp16 * vtcm_p_tiles[2]; // P = softmax(S) [g_br, Bc]
|
||||
__fp16 * vtcm_d_tiles; // Diagonal rescale [g_br, g_br]
|
||||
__fp16 * vtcm_d_inv_l; // Diagonal rescale (1/l) [g_br, g_br]
|
||||
__fp16 * vtcm_d_tiles[2]; // Diagonal rescale, g_br/32 packed diagonal tiles (double-buffered)
|
||||
__fp16 * vtcm_d_inv_l; // Diagonal rescale (1/l), same packed layout
|
||||
HVX_Vector * vtcm_m_vec; // Row max [g_br]
|
||||
HVX_Vector * vtcm_l_vec; // Row sum [g_br]
|
||||
HVX_Vector * vtcm_s_rowmax; // Softmax intermediate [g_br]
|
||||
@@ -782,13 +782,14 @@ static void fa_q_load_thread(unsigned int n, unsigned int i, void * data) {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize vtcm_d_tiles and vtcm_d_inv_l to 0
|
||||
// Zero the whole rescale region: vtcm_d_tiles[0], the optional vtcm_d_tiles[1]
|
||||
// and vtcm_d_inv_l are equal-sized and allocated back to back, so one run covers
|
||||
// them all. The scatter only ever writes the diagonal, ignore the rest.
|
||||
const size_t d_bytes_per_t = hex_align_up(d_tile_bytes / n, 128);
|
||||
const size_t d_start = i * d_bytes_per_t;
|
||||
const size_t d_end = hex_smin(d_start + d_bytes_per_t, d_tile_bytes);
|
||||
if (d_start < d_tile_bytes) {
|
||||
hvx_splat_u8_a((char *) factx->vtcm_d_tiles + d_start, 0, d_end - d_start);
|
||||
hvx_splat_u8_a((char *) factx->vtcm_d_inv_l + d_start, 0, d_end - d_start);
|
||||
hvx_splat_u8_a((char *) factx->vtcm_d_tiles[0] + d_start, 0, d_end - d_start);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1432,17 +1433,19 @@ static inline void fa_softmax_impl(
|
||||
const HVX_VectorPred q_32_mask = Q6_Q_vsetq_R(32 * sizeof(__fp16));
|
||||
HVX_Vector v_exp_m_diff = exp_m_diff_f16;
|
||||
|
||||
__fp16 * const d_tiles_out = factx->vtcm_d_tiles[args->buf_idx];
|
||||
|
||||
size_t t0 = r_vec_idx * 2;
|
||||
if (t0 < args->n_row_tiles) {
|
||||
const HVX_Vector v_content = v_exp_m_diff;
|
||||
__fp16 * out_base = factx->vtcm_d_tiles + t0 * (args->n_row_tiles_g_br + 1) * HMX_FP16_TILE_N_ELMS;
|
||||
__fp16 * out_base = d_tiles_out + t0 * HMX_FP16_TILE_N_ELMS;
|
||||
Q6_vscatter_QRMVhV(q_32_mask, (size_t) out_base, HMX_FP16_TILE_SIZE - 1, v_offsets, v_content);
|
||||
}
|
||||
|
||||
size_t t1 = r_vec_idx * 2 + 1;
|
||||
if (t1 < args->n_row_tiles) {
|
||||
const HVX_Vector v_content = Q6_V_vror_VR(v_exp_m_diff, 64);
|
||||
__fp16 * out_base = factx->vtcm_d_tiles + t1 * (args->n_row_tiles_g_br + 1) * HMX_FP16_TILE_N_ELMS;
|
||||
__fp16 * out_base = d_tiles_out + t1 * HMX_FP16_TILE_N_ELMS;
|
||||
Q6_vscatter_QRMVhV(q_32_mask, (size_t) out_base, HMX_FP16_TILE_SIZE - 1, v_offsets, v_content);
|
||||
}
|
||||
}
|
||||
@@ -1506,7 +1509,7 @@ static __attribute__((noinline)) void fa_build_d_diag_inv_l(struct hmx_fa_contex
|
||||
v_content = Q6_V_vror_VR(v_content, 64);
|
||||
}
|
||||
|
||||
__fp16 * out_base = factx->vtcm_d_inv_l + i * (n_row_tiles_g_br + 1) * HMX_FP16_TILE_N_ELMS;
|
||||
__fp16 * out_base = factx->vtcm_d_inv_l + i * HMX_FP16_TILE_N_ELMS;
|
||||
Q6_vscatter_QRMVhV(q_32_mask, (size_t) out_base, HMX_FP16_TILE_SIZE - 1, v_offsets, v_content);
|
||||
}
|
||||
}
|
||||
@@ -1615,7 +1618,7 @@ static void hmx_fa_o_update_worker(void * data) {
|
||||
const size_t o_stride = n_row_tiles_g_br * HMX_FP16_TILE_N_ELMS;
|
||||
const size_t v_stride = n_tiles_per_bc * HMX_FP16_TILE_N_ELMS;
|
||||
for (size_t r = 0; r < n_row_tiles; ++r) {
|
||||
const __fp16 * d_diag = d_tiles + r * (n_row_tiles_g_br + 1) * HMX_FP16_TILE_N_ELMS;
|
||||
const __fp16 * d_diag = d_tiles + r * HMX_FP16_TILE_N_ELMS;
|
||||
const __fp16 * p_tile_in = p_tiles + (r * n_tiles_per_bc) * HMX_FP16_TILE_N_ELMS;
|
||||
const __fp16 * o_rc = o_prev + r * HMX_FP16_TILE_N_ELMS;
|
||||
const __fp16 * v_tile_in = v_tiles;
|
||||
@@ -1654,7 +1657,7 @@ static void hmx_fa_o_norm_worker(void * data) {
|
||||
asm volatile(HMX_SET_BIAS("%0") :: "r"((unsigned int)job->hmx_scales));
|
||||
const size_t o_stride = n_row_tiles_g_br * HMX_FP16_TILE_N_ELMS;
|
||||
for (size_t r = 0; r < n_row_tiles; ++r) {
|
||||
const __fp16 * d_diag = d_tiles + r * (n_row_tiles_g_br + 1) * HMX_FP16_TILE_N_ELMS;
|
||||
const __fp16 * d_diag = d_tiles + r * HMX_FP16_TILE_N_ELMS;
|
||||
const __fp16 * o_rc = o_prev + r * HMX_FP16_TILE_N_ELMS;
|
||||
__fp16 * o_out = o_curr + r * DV_tiles * HMX_FP16_TILE_N_ELMS;
|
||||
|
||||
@@ -1882,7 +1885,8 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
factx.vtcm_s_tiles[1] = VTCM_LAYOUT_PTR_OPTIONAL(__fp16, base, L.off_s_tiles[1], pipeline);
|
||||
factx.vtcm_p_tiles[0] = VTCM_LAYOUT_PTR(__fp16, base, L.off_p_tiles[0]);
|
||||
factx.vtcm_p_tiles[1] = VTCM_LAYOUT_PTR_OPTIONAL(__fp16, base, L.off_p_tiles[1], pipeline);
|
||||
factx.vtcm_d_tiles = VTCM_LAYOUT_PTR(__fp16, base, L.off_d_tiles);
|
||||
factx.vtcm_d_tiles[0] = VTCM_LAYOUT_PTR(__fp16, base, L.off_d_tiles[0]);
|
||||
factx.vtcm_d_tiles[1] = VTCM_LAYOUT_PTR_OPTIONAL(__fp16, base, L.off_d_tiles[1], pipeline);
|
||||
factx.vtcm_d_inv_l = VTCM_LAYOUT_PTR(__fp16, base, L.off_d_inv_l);
|
||||
factx.vtcm_m_vec = VTCM_LAYOUT_PTR(HVX_Vector, base, L.off_m_vec);
|
||||
factx.vtcm_l_vec = VTCM_LAYOUT_PTR(HVX_Vector, base, L.off_l_vec);
|
||||
@@ -2039,7 +2043,30 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 3. Pop and run K-prep for next block & push next QK-dot ----
|
||||
// ---- 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.
|
||||
// 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) {
|
||||
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;
|
||||
ou_job[prev_buf].p_tiles = factx.vtcm_p_tiles[prev_buf];
|
||||
ou_job[prev_buf].v_tiles = factx.vtcm_v_tiles[prev_buf];
|
||||
ou_job[prev_buf].d_tiles = factx.vtcm_d_tiles[prev_buf];
|
||||
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);
|
||||
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;
|
||||
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;
|
||||
const uint32_t next_rows = hex_smin(Bc, nek1 - next_start);
|
||||
@@ -2059,10 +2086,10 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
hmx_queue_push(hmx_q, hmx_queue_make_desc(hmx_fa_qk_dot_worker, &qk_job[next_buf]));
|
||||
}
|
||||
|
||||
// ---- 4. Wait for current block's QK-dot to finish ----
|
||||
// ---- 5. Wait for current block's QK-dot to finish ----
|
||||
hmx_queue_pop(hmx_q);
|
||||
|
||||
// ---- 5. Phase 2: softmax + build_D ----
|
||||
// ---- 6. Phase 2: softmax + build_D ----
|
||||
fa_softmax_args_t sargs;
|
||||
memset(&sargs, 0, sizeof(sargs));
|
||||
sargs.factx = &factx;
|
||||
@@ -2085,23 +2112,6 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
sargs.mask_vtcm_row_stride = factx.mask_buf_row_stride;
|
||||
sargs.slopes = factx.vtcm_slopes;
|
||||
|
||||
// Start HMX O update for block kv_blk - 1 (reads P[1 - buf_idx], V[1 - buf_idx])
|
||||
if (kv_blk > 0) {
|
||||
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;
|
||||
ou_job[prev_buf].p_tiles = factx.vtcm_p_tiles[prev_buf];
|
||||
ou_job[prev_buf].v_tiles = factx.vtcm_v_tiles[prev_buf];
|
||||
ou_job[prev_buf].d_tiles = factx.vtcm_d_tiles;
|
||||
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);
|
||||
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;
|
||||
hmx_queue_push(hmx_q, hmx_queue_make_desc(hmx_fa_o_update_worker, &ou_job[prev_buf]));
|
||||
}
|
||||
|
||||
// Run Softmax on HVX (blocking call)
|
||||
fa_phase_softmax_and_build_d(&factx, &sargs, n_row_tiles, n_row_tiles_g_br);
|
||||
|
||||
@@ -2128,7 +2138,7 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
ou_job[0].o_prev = o_tile_prev;
|
||||
ou_job[0].p_tiles = factx.vtcm_p_tiles[1 - buf_idx];
|
||||
ou_job[0].v_tiles = factx.vtcm_v_tiles[1 - buf_idx];
|
||||
ou_job[0].d_tiles = factx.vtcm_d_tiles;
|
||||
ou_job[0].d_tiles = factx.vtcm_d_tiles[1 - buf_idx];
|
||||
ou_job[0].hmx_scales = factx.vtcm_hmx_scales_id;
|
||||
ou_job[0].n_row_tiles = n_row_tiles;
|
||||
ou_job[0].n_col_tiles = last_cols;
|
||||
@@ -2232,7 +2242,7 @@ int hmx_flash_attn_ext(struct htp_ops_context * octx) {
|
||||
ou_job.o_prev = o_tile_prev;
|
||||
ou_job.p_tiles = factx.vtcm_p_tiles[0];
|
||||
ou_job.v_tiles = factx.vtcm_v_tiles[0];
|
||||
ou_job.d_tiles = factx.vtcm_d_tiles;
|
||||
ou_job.d_tiles = factx.vtcm_d_tiles[0];
|
||||
ou_job.hmx_scales = factx.vtcm_hmx_scales_id;
|
||||
ou_job.n_row_tiles = n_row_tiles;
|
||||
ou_job.n_col_tiles = n_col_tiles;
|
||||
|
||||
@@ -109,7 +109,7 @@ struct hmx_fa_vtcm_layout {
|
||||
size_t off_v_tiles[2];
|
||||
size_t off_s_tiles[2];
|
||||
size_t off_p_tiles[2];
|
||||
size_t off_d_tiles;
|
||||
size_t off_d_tiles[2];
|
||||
size_t off_d_inv_l;
|
||||
size_t off_m_vec;
|
||||
size_t off_l_vec;
|
||||
@@ -125,7 +125,7 @@ struct hmx_fa_vtcm_layout {
|
||||
size_t q_tile_bytes;
|
||||
size_t o_tile_bytes;
|
||||
size_t s_tile_bytes; // S and P tiles (same size)
|
||||
size_t d_tile_bytes;
|
||||
size_t d_tile_bytes; // d_tiles[0..1] + d_inv_l, allocated back to back
|
||||
size_t m_line_bytes; // one mask row
|
||||
size_t m_buf_slot_bytes; // one dma_cache slot = align_up(Br * m_line_bytes, 4096)
|
||||
size_t col_vec_bytes;
|
||||
@@ -149,7 +149,12 @@ static inline void hmx_fa_vtcm_layout_build(struct hmx_fa_vtcm_layout * L,
|
||||
const size_t k_tile_size = hex_align_up(Bc * DK * sizeof(__fp16), HTP_FA_HMX_TILE_SIZE);
|
||||
const size_t v_tile_size = hex_align_up(Bc * DV * sizeof(__fp16), HTP_FA_HMX_TILE_SIZE);
|
||||
const size_t s_tile_size = hex_align_up(g_br * Bc * sizeof(__fp16), HTP_FA_HMX_TILE_SIZE);
|
||||
const size_t d_tile_size = hex_align_up(g_br * g_br * sizeof(__fp16), HTP_FA_HMX_TILE_SIZE);
|
||||
|
||||
// The rescale matrices are diagonal: the HMX kernels only ever load the g_br/32
|
||||
// tiles that sit on the diagonal, so store just those, packed back to back with
|
||||
// a stride of one tile. The old [g_br, g_br] square layout allocated g_br/32
|
||||
// times more than it used, which is also why a second D buffer was unaffordable.
|
||||
const size_t d_tile_size = (g_br / HMX_FP16_TILE_N_ROWS) * HTP_FA_HMX_TILE_SIZE;
|
||||
|
||||
const size_t q_dma_size = hex_align_up(g_br * DK * (is_q_fp32 ? sizeof(float) : sizeof(__fp16)), 128);
|
||||
const size_t k_dma_size = hex_align_up(Bc * hex_round_up(DK * sizeof(__fp16), 128), 128);
|
||||
@@ -167,7 +172,8 @@ static inline void hmx_fa_vtcm_layout_build(struct hmx_fa_vtcm_layout * L,
|
||||
VTCM_LAYOUT_ALLOC(off, off_q_tiles, q_tile_size);
|
||||
VTCM_LAYOUT_ALLOC(off, off_o_tiles[0], o_tile_size);
|
||||
VTCM_LAYOUT_ALLOC(off, off_o_tiles[1], o_tile_size);
|
||||
VTCM_LAYOUT_ALLOC(off, off_d_tiles, d_tile_size);
|
||||
VTCM_LAYOUT_ALLOC(off, off_d_tiles[0], d_tile_size);
|
||||
VTCM_LAYOUT_ALLOC_OPTIONAL(off, off_d_tiles[1], d_tile_size, pipeline);
|
||||
VTCM_LAYOUT_ALLOC(off, off_d_inv_l, d_tile_size);
|
||||
|
||||
// Group B & C share start offset (Group B tiles must be 2KB aligned)
|
||||
@@ -213,7 +219,10 @@ static inline void hmx_fa_vtcm_layout_build(struct hmx_fa_vtcm_layout * L,
|
||||
L->o_tile_bytes = o_tile_size;
|
||||
L->col_vec_bytes = col_vec_size;
|
||||
L->s_tile_bytes = s_tile_size;
|
||||
L->d_tile_bytes = d_tile_size;
|
||||
// Measured from the actual offsets rather than assumed to be N * d_tile_size, so
|
||||
// that inserting a region between them (or adding padding to VTCM_LAYOUT_ALLOC)
|
||||
// cannot silently leave the tail of the run unzeroed.
|
||||
L->d_tile_bytes = (L->off_d_inv_l + d_tile_size) - L->off_d_tiles[0];
|
||||
L->m_line_bytes = m_line_size;
|
||||
L->m_buf_slot_bytes = m_buf_slot;
|
||||
L->row_buf_stride = row_vec_size / 128;
|
||||
|
||||
@@ -1409,6 +1409,23 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_attn_ext_p
|
||||
return res;
|
||||
}
|
||||
|
||||
ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_attn_ext_kv_f16(
|
||||
ggml_metal_library_t lib,
|
||||
const ggml_tensor * op) {
|
||||
assert(op->op == GGML_OP_FLASH_ATTN_EXT);
|
||||
|
||||
char base[256];
|
||||
|
||||
snprintf(base, 256, "kernel_flash_attn_ext_kv_%s_f16", ggml_type_name(op->src[1]->type));
|
||||
|
||||
ggml_metal_pipeline_with_params res = ggml_metal_library_get_pipeline(lib, base);
|
||||
if (!res.pipeline) {
|
||||
res = ggml_metal_library_compile_pipeline(lib, base, base, nullptr);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_attn_ext_blk(
|
||||
ggml_metal_library_t lib,
|
||||
const struct ggml_tensor * op,
|
||||
@@ -1460,7 +1477,10 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_attn_ext(
|
||||
bool has_bias,
|
||||
bool has_scap,
|
||||
bool has_kvpad,
|
||||
int32_t nsg) {
|
||||
int32_t nsg,
|
||||
bool use_kv_f16,
|
||||
int32_t ns10,
|
||||
int32_t ns20) {
|
||||
assert(op->op == GGML_OP_FLASH_ATTN_EXT);
|
||||
|
||||
char base[256];
|
||||
@@ -1469,15 +1489,14 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_attn_ext(
|
||||
const int32_t dk = (int32_t) op->src[1]->ne[0];
|
||||
const int32_t dv = (int32_t) op->src[2]->ne[0];
|
||||
|
||||
const int32_t ns10 = op->src[1]->nb[1]/op->src[1]->nb[0];
|
||||
const int32_t ns20 = op->src[2]->nb[1]/op->src[2]->nb[0];
|
||||
const char * type = use_kv_f16 ? "f16" : ggml_type_name(op->src[1]->type);
|
||||
|
||||
// do bounds checks for the mask?
|
||||
const bool bc_mask = op->src[3] && (op->src[3]->ne[1] % 8 != 0);
|
||||
|
||||
snprintf(base, 256, "kernel_%s_%s_dk%d_dv%d",
|
||||
"flash_attn_ext",
|
||||
ggml_type_name(op->src[1]->type),
|
||||
type,
|
||||
dk,
|
||||
dv);
|
||||
|
||||
@@ -1526,7 +1545,10 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_attn_ext_v
|
||||
bool has_scap,
|
||||
bool has_kvpad,
|
||||
int32_t nsg,
|
||||
int32_t nwg) {
|
||||
int32_t nwg,
|
||||
bool use_kv_f16,
|
||||
int32_t ns10,
|
||||
int32_t ns20) {
|
||||
assert(op->op == GGML_OP_FLASH_ATTN_EXT);
|
||||
|
||||
char base[256];
|
||||
@@ -1535,12 +1557,11 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_attn_ext_v
|
||||
const int32_t dk = (int32_t) op->src[1]->ne[0];
|
||||
const int32_t dv = (int32_t) op->src[2]->ne[0];
|
||||
|
||||
const int32_t ns10 = op->src[1]->nb[1]/op->src[1]->nb[0];
|
||||
const int32_t ns20 = op->src[2]->nb[1]/op->src[2]->nb[0];
|
||||
const char * type = use_kv_f16 ? "f16" : ggml_type_name(op->src[1]->type);
|
||||
|
||||
snprintf(base, 256, "kernel_%s_%s_dk%d_dv%d",
|
||||
"flash_attn_ext_vec",
|
||||
ggml_type_name(op->src[1]->type),
|
||||
type,
|
||||
dk,
|
||||
dv);
|
||||
|
||||
|
||||
@@ -176,6 +176,10 @@ struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_att
|
||||
bool has_mask,
|
||||
int32_t ncpsg);
|
||||
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_attn_ext_kv_f16(
|
||||
ggml_metal_library_t lib,
|
||||
const struct ggml_tensor * op);
|
||||
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_attn_ext_blk(
|
||||
ggml_metal_library_t lib,
|
||||
const struct ggml_tensor * op,
|
||||
@@ -190,7 +194,10 @@ struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_att
|
||||
bool has_bias,
|
||||
bool has_scap,
|
||||
bool has_kvpad,
|
||||
int32_t nsg);
|
||||
int32_t nsg,
|
||||
bool use_kv_f16,
|
||||
int32_t ns10,
|
||||
int32_t ns20);
|
||||
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_attn_ext_vec(
|
||||
ggml_metal_library_t lib,
|
||||
@@ -201,7 +208,10 @@ struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_att
|
||||
bool has_scap,
|
||||
bool has_kvpad,
|
||||
int32_t nsg,
|
||||
int32_t nwg);
|
||||
int32_t nwg,
|
||||
bool use_kv_f16,
|
||||
int32_t ns10,
|
||||
int32_t ns20);
|
||||
|
||||
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_flash_attn_ext_vec_reduce(
|
||||
ggml_metal_library_t lib,
|
||||
|
||||
@@ -329,6 +329,7 @@ typedef struct {
|
||||
uint64_t nb3;
|
||||
int32_t n_past;
|
||||
int32_t n_dims;
|
||||
int32_t n_offs;
|
||||
int32_t n_ctx_orig;
|
||||
float freq_base;
|
||||
float freq_scale;
|
||||
@@ -341,8 +342,21 @@ typedef struct {
|
||||
int32_t sect_2;
|
||||
int32_t sect_3;
|
||||
bool src2;
|
||||
bool inplace;
|
||||
} ggml_metal_kargs_rope;
|
||||
|
||||
typedef struct {
|
||||
int32_t ne0;
|
||||
int32_t ne1;
|
||||
int32_t ne2;
|
||||
int32_t ne3;
|
||||
uint64_t nb0;
|
||||
uint64_t nb1;
|
||||
uint64_t nb2;
|
||||
uint64_t nb3;
|
||||
int32_t nblocks;
|
||||
} ggml_metal_kargs_flash_attn_ext_kv_f16;
|
||||
|
||||
typedef struct {
|
||||
int32_t ne11;
|
||||
int32_t ne_12_2; // assume K and V are same shape
|
||||
|
||||
@@ -2801,6 +2801,51 @@ bool ggml_metal_op_flash_attn_ext_use_vec(const ggml_tensor * op) {
|
||||
return (ne01 < 20) && (ne00 % 32 == 0);
|
||||
}
|
||||
|
||||
// ref: https://github.com/ggml-org/llama.cpp/pull/27390
|
||||
// dequantize the quantized KV cache to F16 before running the F16 flash attention kernels
|
||||
static bool ggml_metal_op_flash_attn_ext_use_kv_f16(const ggml_tensor * op) {
|
||||
assert(op->op == GGML_OP_FLASH_ATTN_EXT);
|
||||
|
||||
// depending on compute/bandwidth ratio, dequant to f16 kv is not always beneficial
|
||||
// ref: https://github.com/ggml-org/llama.cpp/pull/27390#issuecomment-5355152767
|
||||
// TODO: tune per device
|
||||
if (op->src[0]->ne[1] < 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (op->src[1]->type) {
|
||||
case GGML_TYPE_Q4_0:
|
||||
case GGML_TYPE_Q4_1:
|
||||
case GGML_TYPE_Q5_0:
|
||||
case GGML_TYPE_Q5_1:
|
||||
case GGML_TYPE_Q8_0:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// in some models (e.g. MLA-based), V is a view of K (the first ne20 elements of each K row);
|
||||
// the dequantized V is then a view of the dequantized K and does not need its own dequant or scratch
|
||||
// - ref: https://github.com/ggml-org/llama.cpp/pull/13435
|
||||
static bool ggml_metal_op_flash_attn_ext_v_is_view_of_k(const ggml_tensor * op) {
|
||||
assert(op->op == GGML_OP_FLASH_ATTN_EXT);
|
||||
|
||||
const ggml_tensor * K = op->src[1];
|
||||
const ggml_tensor * V = op->src[2];
|
||||
|
||||
return V->view_src && (V->view_src == K || (V->view_src == K->view_src && V->view_offs == K->view_offs));
|
||||
}
|
||||
|
||||
// size of the F16 dequantized K tensor; the dequantized V tensor follows it in the same scratch buffer
|
||||
static size_t ggml_metal_op_flash_attn_ext_kv_f16_k_size(const ggml_tensor * op) {
|
||||
assert(op->op == GGML_OP_FLASH_ATTN_EXT);
|
||||
|
||||
GGML_TENSOR_LOCALS( int32_t, ne1, op->src[1], ne);
|
||||
|
||||
return GGML_PAD(sizeof(ggml_fp16_t)*(size_t) ne10*ne11*ne12*ne13, 16);
|
||||
}
|
||||
|
||||
size_t ggml_metal_op_flash_attn_ext_extra_pad(const ggml_tensor * op) {
|
||||
assert(op->op == GGML_OP_FLASH_ATTN_EXT);
|
||||
|
||||
@@ -2816,6 +2861,18 @@ size_t ggml_metal_op_flash_attn_ext_extra_pad(const ggml_tensor * op) {
|
||||
size_t res = 0;
|
||||
|
||||
const bool has_mask = op->src[3] != nullptr;
|
||||
const bool use_kv_f16 = ggml_metal_op_flash_attn_ext_use_kv_f16(op);
|
||||
|
||||
// when the KV is dequantized to F16, the pad kernel copies the tail chunk from the F16 scratch buffer
|
||||
// note: when V is a view of K, the dequantized V is read from the dequantized K with K's row stride
|
||||
const bool v_is_view_of_k = use_kv_f16 && ggml_metal_op_flash_attn_ext_v_is_view_of_k(op);
|
||||
uint64_t nb11_pad = nb11;
|
||||
uint64_t nb21_pad = nb21;
|
||||
|
||||
if (use_kv_f16) {
|
||||
nb11_pad = sizeof(ggml_fp16_t)*ne10;
|
||||
nb21_pad = sizeof(ggml_fp16_t)*(v_is_view_of_k ? ne10 : ne20);
|
||||
}
|
||||
|
||||
// note: the non-vec kernel requires more extra memory, so always reserve for it
|
||||
GGML_ASSERT(OP_FLASH_ATTN_EXT_NCPSG >= OP_FLASH_ATTN_EXT_VEC_NCPSG);
|
||||
@@ -2828,8 +2885,8 @@ size_t ggml_metal_op_flash_attn_ext_extra_pad(const ggml_tensor * op) {
|
||||
|
||||
if (has_kvpad) {
|
||||
res += OP_FLASH_ATTN_EXT_VEC_NCPSG*(
|
||||
nb11*ne12*ne13 +
|
||||
nb21*ne22*ne23 +
|
||||
nb11_pad*ne12*ne13 +
|
||||
nb21_pad*ne22*ne23 +
|
||||
(has_mask ? ggml_type_size(GGML_TYPE_F16)*ne31*ne32*ne33 : 0));
|
||||
}
|
||||
} else {
|
||||
@@ -2838,8 +2895,8 @@ size_t ggml_metal_op_flash_attn_ext_extra_pad(const ggml_tensor * op) {
|
||||
|
||||
if (has_kvpad) {
|
||||
res += OP_FLASH_ATTN_EXT_NCPSG*(
|
||||
nb11*ne12*ne13 +
|
||||
nb21*ne22*ne23 +
|
||||
nb11_pad*ne12*ne13 +
|
||||
nb21_pad*ne22*ne23 +
|
||||
(has_mask ? ggml_type_size(GGML_TYPE_F16)*ne31*ne32*ne33 : 0));
|
||||
}
|
||||
}
|
||||
@@ -2915,6 +2972,29 @@ size_t ggml_metal_op_flash_attn_ext_extra_tmp(const ggml_tensor * op) {
|
||||
return res;
|
||||
}
|
||||
|
||||
size_t ggml_metal_op_flash_attn_ext_extra_kv_f16(const ggml_tensor * op) {
|
||||
assert(op->op == GGML_OP_FLASH_ATTN_EXT);
|
||||
|
||||
// note: always reserve the temp buffer to avoid graph reallocations
|
||||
//if (!ggml_metal_op_flash_attn_ext_use_kv_f16(op)) {
|
||||
// return 0;
|
||||
//}
|
||||
|
||||
GGML_TENSOR_LOCALS( int32_t, ne2, op->src[2], ne);
|
||||
|
||||
const size_t k_size = ggml_metal_op_flash_attn_ext_kv_f16_k_size(op);
|
||||
|
||||
// when V is a view of K, the dequantized V is a view of the dequantized K
|
||||
const bool v_is_view_of_k = ggml_metal_op_flash_attn_ext_v_is_view_of_k(op);
|
||||
if (v_is_view_of_k) {
|
||||
return k_size;
|
||||
}
|
||||
|
||||
const size_t v_size = GGML_PAD(sizeof(ggml_fp16_t)*(size_t) ne20*ne21*ne22*ne23, 16);
|
||||
|
||||
return k_size + v_size;
|
||||
}
|
||||
|
||||
int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
ggml_tensor * op = ctx->node(idx);
|
||||
|
||||
@@ -2989,6 +3069,111 @@ int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
ggml_metal_buffer_id bid_tmp = bid_blk;
|
||||
bid_tmp.offs += ggml_metal_op_flash_attn_ext_extra_blk(op);
|
||||
|
||||
ggml_metal_buffer_id bid_kv_f16 = bid_tmp;
|
||||
bid_kv_f16.offs += ggml_metal_op_flash_attn_ext_extra_tmp(op);
|
||||
|
||||
const bool use_kv_f16 = ggml_metal_op_flash_attn_ext_use_kv_f16(op);
|
||||
|
||||
ggml_metal_buffer_id bid_k = bid_src1;
|
||||
ggml_metal_buffer_id bid_v = bid_src2;
|
||||
|
||||
uint64_t nb10_attn = nb10;
|
||||
uint64_t nb11_attn = nb11;
|
||||
uint64_t nb12_attn = nb12;
|
||||
uint64_t nb13_attn = nb13;
|
||||
uint64_t nb20_attn = nb20;
|
||||
uint64_t nb21_attn = nb21;
|
||||
uint64_t nb22_attn = nb22;
|
||||
uint64_t nb23_attn = nb23;
|
||||
|
||||
if (use_kv_f16) {
|
||||
assert(ggml_metal_op_flash_attn_ext_extra_kv_f16(op) != 0);
|
||||
|
||||
const bool v_is_view_of_k = ggml_metal_op_flash_attn_ext_v_is_view_of_k(op);
|
||||
|
||||
const int64_t nblocks1_64 = (ne10/ggml_blck_size(op->src[1]->type))*(int64_t) ne11*ne12*ne13;
|
||||
GGML_ASSERT(nblocks1_64 <= INT32_MAX);
|
||||
const int32_t nblocks1 = nblocks1_64;
|
||||
|
||||
ggml_metal_buffer_id bid_v_f16 = bid_kv_f16;
|
||||
bid_v_f16.offs += ggml_metal_op_flash_attn_ext_kv_f16_k_size(op);
|
||||
|
||||
auto pipeline0 = ggml_metal_library_get_pipeline_flash_attn_ext_kv_f16(lib, op);
|
||||
const int nth = std::min(ggml_metal_pipeline_max_theads_per_threadgroup(pipeline0), 256);
|
||||
|
||||
// K
|
||||
ggml_metal_kargs_flash_attn_ext_kv_f16 args_k = {
|
||||
/*.ne0 =*/ ne10,
|
||||
/*.ne1 =*/ ne11,
|
||||
/*.ne2 =*/ ne12,
|
||||
/*.ne3 =*/ ne13,
|
||||
/*.nb0 =*/ nb10,
|
||||
/*.nb1 =*/ nb11,
|
||||
/*.nb2 =*/ nb12,
|
||||
/*.nb3 =*/ nb13,
|
||||
/*.nblocks =*/ nblocks1,
|
||||
};
|
||||
|
||||
ggml_metal_encoder_set_pipeline(enc, pipeline0);
|
||||
ggml_metal_encoder_set_bytes (enc, &args_k, sizeof(args_k), 0);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src1, 1);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_kv_f16, 2);
|
||||
|
||||
ggml_metal_encoder_dispatch_threadgroups(enc, (nblocks1 + nth - 1)/nth, 1, 1, nth, 1, 1);
|
||||
|
||||
// V (skip when V is a view of K: the dequantized V is a view of the dequantized K)
|
||||
if (!v_is_view_of_k) {
|
||||
const int64_t nblocks2_64 = (ne20/ggml_blck_size(op->src[2]->type))*(int64_t) ne21*ne22*ne23;
|
||||
GGML_ASSERT(nblocks2_64 <= INT32_MAX);
|
||||
const int32_t nblocks2 = nblocks2_64;
|
||||
|
||||
ggml_metal_kargs_flash_attn_ext_kv_f16 args_v = {
|
||||
/*.ne0 =*/ ne20,
|
||||
/*.ne1 =*/ ne21,
|
||||
/*.ne2 =*/ ne22,
|
||||
/*.ne3 =*/ ne23,
|
||||
/*.nb0 =*/ nb20,
|
||||
/*.nb1 =*/ nb21,
|
||||
/*.nb2 =*/ nb22,
|
||||
/*.nb3 =*/ nb23,
|
||||
/*.nblocks =*/ nblocks2,
|
||||
};
|
||||
|
||||
ggml_metal_encoder_set_pipeline(enc, pipeline0);
|
||||
ggml_metal_encoder_set_bytes (enc, &args_v, sizeof(args_v), 0);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src2, 1);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_v_f16, 2);
|
||||
|
||||
ggml_metal_encoder_dispatch_threadgroups(enc, (nblocks2 + nth - 1)/nth, 1, 1, nth, 1, 1);
|
||||
}
|
||||
|
||||
// the pad and attention kernels read the dequantized KV
|
||||
ggml_metal_op_concurrency_reset(ctx);
|
||||
|
||||
bid_k = bid_kv_f16;
|
||||
bid_v = v_is_view_of_k ? bid_k : bid_v_f16;
|
||||
|
||||
// contiguous F16 layout of the dequantized K
|
||||
nb10_attn = sizeof(ggml_fp16_t);
|
||||
nb11_attn = nb10_attn*ne10;
|
||||
nb12_attn = nb11_attn*ne11;
|
||||
nb13_attn = nb12_attn*ne12;
|
||||
|
||||
// if V is a view of K, the dequantized V is read from the dequantized K with K's strides
|
||||
if (v_is_view_of_k) {
|
||||
nb20_attn = nb10_attn;
|
||||
nb21_attn = nb11_attn;
|
||||
nb22_attn = nb12_attn;
|
||||
nb23_attn = nb13_attn;
|
||||
} else {
|
||||
// contiguous F16 layout of the dequantized V
|
||||
nb20_attn = sizeof(ggml_fp16_t);
|
||||
nb21_attn = nb20_attn*ne20;
|
||||
nb22_attn = nb21_attn*ne21;
|
||||
nb23_attn = nb22_attn*ne22;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ggml_metal_op_flash_attn_ext_use_vec(op)) {
|
||||
// half8x8 kernel
|
||||
const int nqptg = OP_FLASH_ATTN_EXT_NQPSG; // queries per threadgroup
|
||||
@@ -3009,12 +3194,12 @@ int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
/*.ne11 =*/ne11,
|
||||
/*.ne_12_2 =*/ne12,
|
||||
/*.ne_12_3 =*/ne13,
|
||||
/*.nb11 =*/nb11,
|
||||
/*.nb12 =*/nb12,
|
||||
/*.nb13 =*/nb13,
|
||||
/*.nb21 =*/nb21,
|
||||
/*.nb22 =*/nb22,
|
||||
/*.nb23 =*/nb23,
|
||||
/*.nb11 =*/nb11_attn,
|
||||
/*.nb12 =*/nb12_attn,
|
||||
/*.nb13 =*/nb13_attn,
|
||||
/*.nb21 =*/nb21_attn,
|
||||
/*.nb22 =*/nb22_attn,
|
||||
/*.nb23 =*/nb23_attn,
|
||||
/*.ne31 =*/ne31,
|
||||
/*.ne32 =*/ne32,
|
||||
/*.ne33 =*/ne33,
|
||||
@@ -3027,8 +3212,8 @@ int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
|
||||
ggml_metal_encoder_set_pipeline(enc, pipeline0);
|
||||
ggml_metal_encoder_set_bytes (enc, &args0, sizeof(args0), 0);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src1, 1);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src2, 2);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_k, 1);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_v, 2);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src3, 3);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_pad, 4);
|
||||
|
||||
@@ -3073,7 +3258,7 @@ int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
ggml_metal_op_concurrency_reset(ctx);
|
||||
}
|
||||
|
||||
const int is_q = ggml_is_quantized(op->src[1]->type) ? 1 : 0;
|
||||
const int is_q = !use_kv_f16 && ggml_is_quantized(op->src[1]->type) ? 1 : 0;
|
||||
|
||||
// 2*(2*ncpsg)
|
||||
// ncpsg soft_max values + ncpsg mask values
|
||||
@@ -3104,6 +3289,9 @@ int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
|
||||
const size_t smem = FATTN_SMEM(nsg);
|
||||
|
||||
const int32_t ns10 = nb11_attn/nb10_attn;
|
||||
const int32_t ns20 = nb21_attn/nb20_attn;
|
||||
|
||||
ggml_metal_kargs_flash_attn_ext args = {
|
||||
/*.ne01 =*/ ne01,
|
||||
/*.ne02 =*/ ne02,
|
||||
@@ -3114,14 +3302,14 @@ int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
/*.ne11 =*/ ne11,
|
||||
/*.ne_12_2 =*/ ne12,
|
||||
/*.ne_12_3 =*/ ne13,
|
||||
/*.ns10 =*/ int32_t(nb11/nb10),
|
||||
/*.nb11 =*/ nb11,
|
||||
/*.nb12 =*/ nb12,
|
||||
/*.nb13 =*/ nb13,
|
||||
/*.ns20 =*/ int32_t(nb21/nb20),
|
||||
/*.nb21 =*/ nb21,
|
||||
/*.nb22 =*/ nb22,
|
||||
/*.nb23 =*/ nb23,
|
||||
/*.ns10 =*/ ns10,
|
||||
/*.nb11 =*/ nb11_attn,
|
||||
/*.nb12 =*/ nb12_attn,
|
||||
/*.nb13 =*/ nb13_attn,
|
||||
/*.ns20 =*/ ns20,
|
||||
/*.nb21 =*/ nb21_attn,
|
||||
/*.nb22 =*/ nb22_attn,
|
||||
/*.nb23 =*/ nb23_attn,
|
||||
/*.ne31 =*/ ne31,
|
||||
/*.ne32 =*/ ne32,
|
||||
/*.ne33 =*/ ne33,
|
||||
@@ -3139,13 +3327,13 @@ int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
/*.logit_softcap =*/ logit_softcap,
|
||||
};
|
||||
|
||||
auto pipeline = ggml_metal_library_get_pipeline_flash_attn_ext(lib, op, has_mask, has_sinks, has_bias, has_scap, has_kvpad, nsg);
|
||||
auto pipeline = ggml_metal_library_get_pipeline_flash_attn_ext(lib, op, has_mask, has_sinks, has_bias, has_scap, has_kvpad, nsg, use_kv_f16, ns10, ns20);
|
||||
|
||||
ggml_metal_encoder_set_pipeline(enc, pipeline);
|
||||
ggml_metal_encoder_set_bytes (enc, &args, sizeof(args), 0);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src0, 1);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src1, 2);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src2, 3);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_k, 2);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_v, 3);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src3, 4);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src4, 5);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_pad, 6);
|
||||
@@ -3177,12 +3365,12 @@ int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
/*.ne11 =*/ne11,
|
||||
/*.ne_12_2 =*/ne12,
|
||||
/*.ne_12_3 =*/ne13,
|
||||
/*.nb11 =*/nb11,
|
||||
/*.nb12 =*/nb12,
|
||||
/*.nb13 =*/nb13,
|
||||
/*.nb21 =*/nb21,
|
||||
/*.nb22 =*/nb22,
|
||||
/*.nb23 =*/nb23,
|
||||
/*.nb11 =*/nb11_attn,
|
||||
/*.nb12 =*/nb12_attn,
|
||||
/*.nb13 =*/nb13_attn,
|
||||
/*.nb21 =*/nb21_attn,
|
||||
/*.nb22 =*/nb22_attn,
|
||||
/*.nb23 =*/nb23_attn,
|
||||
/*.ne31 =*/ne31,
|
||||
/*.ne32 =*/ne32,
|
||||
/*.ne33 =*/ne33,
|
||||
@@ -3195,8 +3383,8 @@ int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
|
||||
ggml_metal_encoder_set_pipeline(enc, pipeline0);
|
||||
ggml_metal_encoder_set_bytes (enc, &args0, sizeof(args0), 0);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src1, 1);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src2, 2);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_k, 1);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_v, 2);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src3, 3);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_pad, 4);
|
||||
|
||||
@@ -3242,6 +3430,9 @@ int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
}
|
||||
}
|
||||
|
||||
const int32_t ns10 = nb11_attn/nb10_attn;
|
||||
const int32_t ns20 = nb21_attn/nb20_attn;
|
||||
|
||||
ggml_metal_kargs_flash_attn_ext_vec args = {
|
||||
/*.ne01 =*/ ne01,
|
||||
/*.ne02 =*/ ne02,
|
||||
@@ -3252,14 +3443,14 @@ int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
/*.ne11 =*/ ne11,
|
||||
/*.ne_12_2 =*/ ne12,
|
||||
/*.ne_12_3 =*/ ne13,
|
||||
/*.ns10 =*/ int32_t(nb11/nb10),
|
||||
/*.nb11 =*/ nb11,
|
||||
/*.nb12 =*/ nb12,
|
||||
/*.nb13 =*/ nb13,
|
||||
/*.ns20 =*/ int32_t(nb21/nb20),
|
||||
/*.nb21 =*/ nb21,
|
||||
/*.nb22 =*/ nb22,
|
||||
/*.nb23 =*/ nb23,
|
||||
/*.ns10 =*/ ns10,
|
||||
/*.nb11 =*/ nb11_attn,
|
||||
/*.nb12 =*/ nb12_attn,
|
||||
/*.nb13 =*/ nb13_attn,
|
||||
/*.ns20 =*/ ns20,
|
||||
/*.nb21 =*/ nb21_attn,
|
||||
/*.nb22 =*/ nb22_attn,
|
||||
/*.nb23 =*/ nb23_attn,
|
||||
/*.ne31 =*/ ne31,
|
||||
/*.ne32 =*/ ne32,
|
||||
/*.ne33 =*/ ne33,
|
||||
@@ -3277,15 +3468,15 @@ int ggml_metal_op_flash_attn_ext(ggml_metal_op_t ctx, int idx) {
|
||||
/*.logit_softcap =*/ logit_softcap,
|
||||
};
|
||||
|
||||
auto pipeline = ggml_metal_library_get_pipeline_flash_attn_ext_vec(lib, op, has_mask, has_sinks, has_bias, has_scap, has_kvpad, nsg, nwg);
|
||||
auto pipeline = ggml_metal_library_get_pipeline_flash_attn_ext_vec(lib, op, has_mask, has_sinks, has_bias, has_scap, has_kvpad, nsg, nwg, use_kv_f16, ns10, ns20);
|
||||
|
||||
GGML_ASSERT(nsg*32 <= ggml_metal_pipeline_max_theads_per_threadgroup(pipeline));
|
||||
|
||||
ggml_metal_encoder_set_pipeline(enc, pipeline);
|
||||
ggml_metal_encoder_set_bytes (enc, &args, sizeof(args), 0);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src0, 1);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src1, 2);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src2, 3);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_k, 2);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_v, 3);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src3, 4);
|
||||
ggml_metal_encoder_set_buffer (enc, bid_src4, 5);
|
||||
|
||||
@@ -3884,6 +4075,11 @@ int ggml_metal_op_rope(ggml_metal_op_t ctx, int idx) {
|
||||
const int sect_2 = ((const int32_t *) op->op_params)[13];
|
||||
const int sect_3 = ((const int32_t *) op->op_params)[14];
|
||||
|
||||
const int n_offs = ((const int32_t *) op->op_params)[15];
|
||||
|
||||
// when dst aliases src0, the channels outside the rotated window already hold the correct data
|
||||
const bool inplace = op->data == op->src[0]->data;
|
||||
|
||||
ggml_metal_kargs_rope args = {
|
||||
/*.ne00 =*/ ne00,
|
||||
/*.ne01 =*/ ne01,
|
||||
@@ -3903,6 +4099,7 @@ int ggml_metal_op_rope(ggml_metal_op_t ctx, int idx) {
|
||||
/*.nb3 =*/ nb3,
|
||||
/*.n_past =*/ n_past,
|
||||
/*.n_dims =*/ n_dims,
|
||||
/*.n_offs =*/ n_offs,
|
||||
/*.n_ctx_orig =*/ n_ctx_orig,
|
||||
/*.freq_base =*/ freq_base,
|
||||
/*.freq_scale =*/ freq_scale,
|
||||
@@ -3915,6 +4112,7 @@ int ggml_metal_op_rope(ggml_metal_op_t ctx, int idx) {
|
||||
/* sect_2 =*/ sect_2,
|
||||
/* sect_3 =*/ sect_3,
|
||||
/* src2 =*/ op->src[2] != nullptr,
|
||||
/* inplace =*/ inplace,
|
||||
};
|
||||
|
||||
auto pipeline = ggml_metal_library_get_pipeline_rope(lib, op);
|
||||
|
||||
@@ -42,6 +42,7 @@ bool ggml_metal_op_flash_attn_ext_use_vec(const struct ggml_tensor * op);
|
||||
size_t ggml_metal_op_flash_attn_ext_extra_pad(const struct ggml_tensor * op);
|
||||
size_t ggml_metal_op_flash_attn_ext_extra_blk(const struct ggml_tensor * op);
|
||||
size_t ggml_metal_op_flash_attn_ext_extra_tmp(const struct ggml_tensor * op);
|
||||
size_t ggml_metal_op_flash_attn_ext_extra_kv_f16(const struct ggml_tensor * op);
|
||||
|
||||
int ggml_metal_op_concat (ggml_metal_op_t ctx, int idx);
|
||||
int ggml_metal_op_repeat (ggml_metal_op_t ctx, int idx);
|
||||
|
||||
@@ -225,6 +225,7 @@ static size_t ggml_backend_metal_buffer_type_get_alloc_size(ggml_backend_buffer_
|
||||
res += ggml_metal_op_flash_attn_ext_extra_pad(tensor);
|
||||
res += ggml_metal_op_flash_attn_ext_extra_blk(tensor);
|
||||
res += ggml_metal_op_flash_attn_ext_extra_tmp(tensor);
|
||||
res += ggml_metal_op_flash_attn_ext_extra_kv_f16(tensor);
|
||||
} break;
|
||||
case GGML_OP_CUMSUM:
|
||||
case GGML_OP_ARGSORT:
|
||||
|
||||
@@ -656,13 +656,13 @@ void dequantize_q5_1_t4(device const block_q5_1 * xb, short il, thread type4 & r
|
||||
|
||||
template <typename type4x4>
|
||||
void dequantize_q8_0(device const block_q8_0 *xb, short il, thread type4x4 & reg) {
|
||||
device const int8_t * qs = ((device const int8_t *)xb->qs);
|
||||
device const packed_char4 * qs = (device const packed_char4 *) xb->qs;
|
||||
const float d = xb->d;
|
||||
|
||||
float4x4 reg_f;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
reg_f[i/4][i%4] = (qs[i + 16*il] * d);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
reg_f[i] = float4(qs[4*il + i]) * d;
|
||||
}
|
||||
|
||||
reg = (type4x4) reg_f;
|
||||
@@ -670,12 +670,10 @@ void dequantize_q8_0(device const block_q8_0 *xb, short il, thread type4x4 & reg
|
||||
|
||||
template <typename type4>
|
||||
void dequantize_q8_0_t4(device const block_q8_0 *xb, short il, thread type4 & reg) {
|
||||
device const int8_t * qs = ((device const int8_t *)xb->qs);
|
||||
device const packed_char4 * qs = (device const packed_char4 *) xb->qs;
|
||||
const float d = xb->d;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
reg[i] = (qs[4*(il%4) + i + 16*(il/4)] * d);
|
||||
}
|
||||
reg = (type4) (float4(qs[il]) * d);
|
||||
}
|
||||
|
||||
template <typename type4x4>
|
||||
@@ -4688,14 +4686,15 @@ kernel void kernel_rope_norm(
|
||||
float sin_theta;
|
||||
|
||||
for (int i0 = 2*tiitg; i0 < args.ne0; i0 += 2*tptg.x) {
|
||||
if (i0 < args.n_dims) {
|
||||
const int ic = i0/2;
|
||||
if (i0 >= args.n_offs && i0 < args.n_offs + args.n_dims) {
|
||||
const int iw = i0 - args.n_offs; // relative idx
|
||||
const int ic = iw/2;
|
||||
|
||||
const float theta = theta_base * pow(args.freq_base, inv_ndims*i0);
|
||||
const float theta = theta_base * pow(args.freq_base, inv_ndims*iw);
|
||||
|
||||
const float freq_factor = args.src2 ? ((device const float *) src2)[ic] : 1.0f;
|
||||
|
||||
rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, i0, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta);
|
||||
rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, iw, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta);
|
||||
|
||||
device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00);
|
||||
device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0);
|
||||
@@ -4706,6 +4705,10 @@ kernel void kernel_rope_norm(
|
||||
dst_data[0] = x0*cos_theta - x1*sin_theta;
|
||||
dst_data[1] = x0*sin_theta + x1*cos_theta;
|
||||
} else {
|
||||
if (args.inplace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00);
|
||||
device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0);
|
||||
|
||||
@@ -4741,17 +4744,18 @@ kernel void kernel_rope_neox(
|
||||
float sin_theta;
|
||||
|
||||
for (int i0 = 2*tiitg; i0 < args.ne0; i0 += 2*tptg.x) {
|
||||
if (i0 < args.n_dims) {
|
||||
const int ic = i0/2;
|
||||
if (i0 >= args.n_offs && i0 < args.n_offs + args.n_dims) {
|
||||
const int iw = i0 - args.n_offs; // relative idx
|
||||
const int ic = iw/2;
|
||||
|
||||
const float theta = theta_base * pow(args.freq_base, inv_ndims*i0);
|
||||
const float theta = theta_base * pow(args.freq_base, inv_ndims*iw);
|
||||
|
||||
const float freq_factor = args.src2 ? ((device const float *) src2)[ic] : 1.0f;
|
||||
|
||||
rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, i0, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta);
|
||||
rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, iw, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta);
|
||||
|
||||
device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + ic*args.nb00);
|
||||
device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + ic*args.nb0);
|
||||
device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + (args.n_offs + ic)*args.nb00);
|
||||
device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + (args.n_offs + ic)*args.nb0);
|
||||
|
||||
const float x0 = src[0];
|
||||
const float x1 = src[args.n_dims/2];
|
||||
@@ -4759,6 +4763,10 @@ kernel void kernel_rope_neox(
|
||||
dst_data[0] = x0*cos_theta - x1*sin_theta;
|
||||
dst_data[args.n_dims/2] = x0*sin_theta + x1*cos_theta;
|
||||
} else {
|
||||
if (args.inplace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00);
|
||||
device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0);
|
||||
|
||||
@@ -4793,8 +4801,9 @@ kernel void kernel_rope_multi(
|
||||
float sin_theta;
|
||||
|
||||
for (int i0 = 2*tiitg; i0 < args.ne0; i0 += 2*tptg.x) {
|
||||
if (i0 < args.n_dims) {
|
||||
const int ic = i0/2;
|
||||
if (i0 >= args.n_offs && i0 < args.n_offs + args.n_dims) {
|
||||
const int iw = i0 - args.n_offs; // relative idx
|
||||
const int ic = iw/2;
|
||||
|
||||
// mrope theta calculations
|
||||
// note: the rest is the same as kernel_rope_neox
|
||||
@@ -4827,14 +4836,14 @@ kernel void kernel_rope_multi(
|
||||
}
|
||||
// end of mrope
|
||||
|
||||
const float theta = theta_base * pow(args.freq_base, inv_ndims*i0);
|
||||
const float theta = theta_base * pow(args.freq_base, inv_ndims*iw);
|
||||
|
||||
const float freq_factor = args.src2 ? ((device const float *) src2)[ic] : 1.0f;
|
||||
|
||||
rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, i0, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta);
|
||||
rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, iw, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta);
|
||||
|
||||
device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + ic*args.nb00);
|
||||
device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + ic*args.nb0);
|
||||
device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + (args.n_offs + ic)*args.nb00);
|
||||
device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + (args.n_offs + ic)*args.nb0);
|
||||
|
||||
const float x0 = src[0];
|
||||
const float x1 = src[args.n_dims/2];
|
||||
@@ -4842,6 +4851,10 @@ kernel void kernel_rope_multi(
|
||||
dst_data[0] = x0*cos_theta - x1*sin_theta;
|
||||
dst_data[args.n_dims/2] = x0*sin_theta + x1*cos_theta;
|
||||
} else {
|
||||
if (args.inplace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00);
|
||||
device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0);
|
||||
|
||||
@@ -6305,6 +6318,53 @@ template [[host_name("kernel_fwht_f32_128")]] kernel kernel_fwht_t kernel_fwht_f
|
||||
template [[host_name("kernel_fwht_f32_256")]] kernel kernel_fwht_t kernel_fwht_f32<256>;
|
||||
template [[host_name("kernel_fwht_f32_512")]] kernel kernel_fwht_t kernel_fwht_f32<512>;
|
||||
|
||||
// dequantize a quantized KV cache tensor to contiguous F16 before running the F16 flash attention kernels
|
||||
// - one thread per block; dispatched separately for K and V
|
||||
// - ref: https://github.com/ggml-org/llama.cpp/pull/27390
|
||||
template <
|
||||
typename block_t,
|
||||
short QK,
|
||||
void (*deq_t4x4)(device const block_t *, short, thread float4x4 &)>
|
||||
kernel void kernel_flash_attn_ext_kv_f16(
|
||||
constant ggml_metal_kargs_flash_attn_ext_kv_f16 & args,
|
||||
device const char * x,
|
||||
device half * x_dst,
|
||||
uint gid [[thread_position_in_grid]]) {
|
||||
if (gid >= (uint) args.nblocks) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint nb = args.ne0/QK;
|
||||
const uint i0 = gid%nb;
|
||||
uint ib = gid/nb;
|
||||
const uint i1 = ib%args.ne1;
|
||||
ib /= args.ne1;
|
||||
const uint i2 = ib%args.ne2;
|
||||
const uint i3 = ib/args.ne2;
|
||||
|
||||
const uint64_t offs = i0*args.nb0 + i1*args.nb1 + i2*args.nb2 + i3*args.nb3;
|
||||
|
||||
device const block_t * src = (device const block_t *) (x + offs);
|
||||
device half4 * dst = (device half4 *) x_dst + (QK/4)*gid;
|
||||
|
||||
for (short i = 0; i < QK/16; ++i) {
|
||||
float4x4 reg;
|
||||
deq_t4x4(src, i, reg);
|
||||
dst[4*i + 0] = (half4) reg[0];
|
||||
dst[4*i + 1] = (half4) reg[1];
|
||||
dst[4*i + 2] = (half4) reg[2];
|
||||
dst[4*i + 3] = (half4) reg[3];
|
||||
}
|
||||
}
|
||||
|
||||
typedef decltype(kernel_flash_attn_ext_kv_f16<block_q8_0, 32, dequantize_q8_0>) kernel_flash_attn_ext_kv_f16_t;
|
||||
|
||||
template [[host_name("kernel_flash_attn_ext_kv_q4_0_f16")]] kernel kernel_flash_attn_ext_kv_f16_t kernel_flash_attn_ext_kv_f16<block_q4_0, 32, dequantize_q4_0>;
|
||||
template [[host_name("kernel_flash_attn_ext_kv_q4_1_f16")]] kernel kernel_flash_attn_ext_kv_f16_t kernel_flash_attn_ext_kv_f16<block_q4_1, 32, dequantize_q4_1>;
|
||||
template [[host_name("kernel_flash_attn_ext_kv_q5_0_f16")]] kernel kernel_flash_attn_ext_kv_f16_t kernel_flash_attn_ext_kv_f16<block_q5_0, 32, dequantize_q5_0>;
|
||||
template [[host_name("kernel_flash_attn_ext_kv_q5_1_f16")]] kernel kernel_flash_attn_ext_kv_f16_t kernel_flash_attn_ext_kv_f16<block_q5_1, 32, dequantize_q5_1>;
|
||||
template [[host_name("kernel_flash_attn_ext_kv_q8_0_f16")]] kernel kernel_flash_attn_ext_kv_f16_t kernel_flash_attn_ext_kv_f16<block_q8_0, 32, dequantize_q8_0>;
|
||||
|
||||
constant bool FC_flash_attn_ext_pad_has_mask [[function_constant(FC_FLASH_ATTN_EXT_PAD + 0)]];
|
||||
|
||||
constant int32_t FC_flash_attn_ext_pad_ncpsg [[function_constant(FC_FLASH_ATTN_EXT_PAD + 25)]];
|
||||
|
||||
@@ -202,6 +202,7 @@ set(GGML_OPENCL_KERNELS
|
||||
sqr
|
||||
sqrt
|
||||
ssm_conv
|
||||
ssm_scan
|
||||
gated_delta_net
|
||||
sub
|
||||
sum_rows
|
||||
|
||||
@@ -866,6 +866,9 @@ struct ggml_backend_opencl_context {
|
||||
// [size_idx][kda][tgpp] where size_idx: 0=S_V=16, 1=32, 2=64, 3=128; kda: 0 or 1.
|
||||
// tgpp 0 = TG variant (COLS_PER_LANE_GROUP=1), tgpp 1 = prefill variant (COLS_PER_LANE_GROUP=4).
|
||||
cl_kernel kernel_gated_delta_net_f32[4][2][2] = {};
|
||||
cl_kernel kernel_ssm_scan_f32_mamba2_d128 = nullptr;
|
||||
cl_kernel kernel_ssm_scan_f32_mamba2_d256 = nullptr;
|
||||
|
||||
cl_kernel kernel_timestep_embedding;
|
||||
cl_kernel kernel_gemv_moe_q4_0_f32_ns, kernel_gemm_moe_q4_0_f32_ns, kernel_gemm_moe_q4_0_f32_ns_bin;
|
||||
cl_kernel kernel_gemm_moe_q8_0_f32_ns;
|
||||
@@ -892,6 +895,7 @@ struct ggml_backend_opencl_context {
|
||||
cl_kernel kernel_gemm_moe_q4_0_q8_1_dp4a = nullptr; // dp4a (int8) q4_0 MoE prefill GEMM
|
||||
cl_kernel kernel_moe_reorder_b;
|
||||
cl_kernel kernel_moe_histogram, kernel_moe_scan, kernel_moe_fill, kernel_moe_scatter;
|
||||
cl_kernel kernel_moe_scatter_stable = nullptr; // deterministic slot assignment
|
||||
cl_kernel kernel_moe_combine_f32 = nullptr; // fused router-weight mul + cross-expert sum
|
||||
cl_kernel kernel_mul_mv_id_q4_0_f32_8x_flat;
|
||||
cl_kernel kernel_mul_mv_id_q8_0_f32, kernel_mul_mv_id_q8_0_f32_flat;
|
||||
@@ -3154,6 +3158,24 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
|
||||
// ssm_scan (Mamba-2 fused per-token recurrent step; d_state in {128, 256})
|
||||
{
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
const std::string kernel_src {
|
||||
#include "ssm_scan.cl.h"
|
||||
};
|
||||
#else
|
||||
const std::string kernel_src = read_file("ssm_scan.cl");
|
||||
#endif
|
||||
cl_program prog =
|
||||
build_program_from_source(backend_ctx, kernel_src.c_str(), compile_opts);
|
||||
|
||||
CL_CHECK((backend_ctx->kernel_ssm_scan_f32_mamba2_d128 = clCreateKernel(prog, "kernel_ssm_scan_f32_mamba2_d128", &err), err));
|
||||
CL_CHECK((backend_ctx->kernel_ssm_scan_f32_mamba2_d256 = clCreateKernel(prog, "kernel_ssm_scan_f32_mamba2_d256", &err), err));
|
||||
CL_CHECK(clReleaseProgram(prog));
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
|
||||
// gated_delta_net: one kernel per (S_V, KDA, tgpp) triple.
|
||||
{
|
||||
#ifdef GGML_OPENCL_EMBED_KERNELS
|
||||
@@ -4442,6 +4464,7 @@ static void load_cl_kernels(ggml_backend_opencl_context *backend_ctx) {
|
||||
CL_CHECK((backend_ctx->kernel_moe_scan = clCreateKernel(prog, "kernel_moe_scan", &err), err));
|
||||
CL_CHECK((backend_ctx->kernel_moe_fill = clCreateKernel(prog, "kernel_moe_fill", &err), err));
|
||||
CL_CHECK((backend_ctx->kernel_moe_scatter = clCreateKernel(prog, "kernel_moe_scatter", &err), err));
|
||||
CL_CHECK((backend_ctx->kernel_moe_scatter_stable = clCreateKernel(prog, "kernel_moe_scatter_stable", &err), err));
|
||||
CL_CHECK(clReleaseProgram(prog));
|
||||
GGML_LOG_CONT(".");
|
||||
}
|
||||
@@ -7301,6 +7324,23 @@ static bool ggml_opencl_supports_op(ggml_backend_dev_t dev, const struct ggml_te
|
||||
(op->src[0]->type == GGML_TYPE_F16 && op->src[1]->type == GGML_TYPE_F32 && op->type == GGML_TYPE_F32);
|
||||
case GGML_OP_SSM_CONV:
|
||||
return (op->src[0]->type == GGML_TYPE_F32 && op->src[1]->type == GGML_TYPE_F32 && op->type == GGML_TYPE_F32);
|
||||
case GGML_OP_SSM_SCAN: {
|
||||
// Mamba-2 fused per-token scan. Requires src3->ne[0] == 1 (scalar
|
||||
// A per head); d_state in {128, 256}; all sources f32. Falls back
|
||||
// to CPU otherwise (incl. Mamba-1 element-wise A).
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
if (op->src[i]->type != GGML_TYPE_F32) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (op->type != GGML_TYPE_F32) {
|
||||
return false;
|
||||
}
|
||||
const int K = ggml_get_op_params_i32(op, 0);
|
||||
const int d_state = (int) op->src[0]->ne[0];
|
||||
const bool is_mamba2 = (op->src[3]->ne[0] == 1);
|
||||
return is_mamba2 && (d_state == 128 || d_state == 256) && (K == 1);
|
||||
}
|
||||
case GGML_OP_GATED_DELTA_NET:
|
||||
{
|
||||
// Match the Vulkan backend: only F32 -> F32, S_v in {16, 32, 64, 128}.
|
||||
@@ -7376,6 +7416,9 @@ static bool ggml_opencl_supports_op(ggml_backend_dev_t dev, const struct ggml_te
|
||||
case GGML_OP_DIAG_MASK_INF:
|
||||
return op->ne[3] == 1;
|
||||
case GGML_OP_ROPE: {
|
||||
if (((const int32_t *) op->op_params)[15] != 0) {
|
||||
return false; // FIXME: support ggml_rope_set_offset
|
||||
}
|
||||
const int mode = ((const int32_t *) op->op_params)[2];
|
||||
const bool is_mrope = mode & GGML_ROPE_TYPE_MROPE;
|
||||
const bool is_vision = mode == GGML_ROPE_TYPE_VISION;
|
||||
@@ -12257,6 +12300,103 @@ static void ggml_cl_mean(ggml_backend_t backend, const ggml_tensor * src0, const
|
||||
backend_ctx->enqueue_ndrange_kernel(kernel, 3, global_work_size, local_work_size, dst);
|
||||
}
|
||||
|
||||
static void ggml_cl_ssm_scan(ggml_backend_t backend, ggml_tensor * dst) {
|
||||
const ggml_tensor * src0 = dst->src[0]; // s
|
||||
const ggml_tensor * src1 = dst->src[1]; // x
|
||||
const ggml_tensor * src2 = dst->src[2]; // dt
|
||||
const ggml_tensor * src3 = dst->src[3]; // A
|
||||
const ggml_tensor * src4 = dst->src[4]; // B
|
||||
const ggml_tensor * src5 = dst->src[5]; // C
|
||||
const ggml_tensor * src6 = dst->src[6]; // ids
|
||||
|
||||
GGML_ASSERT(src0 && src1 && src2 && src3 && src4 && src5 && src6 && dst);
|
||||
|
||||
ggml_backend_opencl_context * backend_ctx = (ggml_backend_opencl_context *) backend->context;
|
||||
|
||||
ggml_tensor_extra_cl * e0 = (ggml_tensor_extra_cl *) src0->extra;
|
||||
ggml_tensor_extra_cl * e1 = (ggml_tensor_extra_cl *) src1->extra;
|
||||
ggml_tensor_extra_cl * e2 = (ggml_tensor_extra_cl *) src2->extra;
|
||||
ggml_tensor_extra_cl * e3 = (ggml_tensor_extra_cl *) src3->extra;
|
||||
ggml_tensor_extra_cl * e4 = (ggml_tensor_extra_cl *) src4->extra;
|
||||
ggml_tensor_extra_cl * e5 = (ggml_tensor_extra_cl *) src5->extra;
|
||||
ggml_tensor_extra_cl * e6 = (ggml_tensor_extra_cl *) src6->extra;
|
||||
ggml_tensor_extra_cl * ed = (ggml_tensor_extra_cl *) dst->extra;
|
||||
|
||||
cl_ulong o0 = e0->offset + src0->view_offs;
|
||||
cl_ulong o1 = e1->offset + src1->view_offs;
|
||||
cl_ulong o2 = e2->offset + src2->view_offs;
|
||||
cl_ulong o3 = e3->offset + src3->view_offs;
|
||||
cl_ulong o4 = e4->offset + src4->view_offs;
|
||||
cl_ulong o5 = e5->offset + src5->view_offs;
|
||||
cl_ulong o6 = e6->offset + src6->view_offs;
|
||||
cl_ulong od = ed->offset + dst->view_offs;
|
||||
|
||||
const int d_state = (int) src0->ne[0];
|
||||
const int head_dim = (int) src0->ne[1];
|
||||
const int n_head = (int) src1->ne[1];
|
||||
const int n_group = (int) src4->ne[1];
|
||||
const int n_tokens = (int) src1->ne[2];
|
||||
const int n_seqs = (int) src1->ne[3];
|
||||
|
||||
// Mirror CPU ref: s_off = ggml_nelements(src1) * sizeof(float)
|
||||
const cl_ulong s_off_bytes = (cl_ulong) ggml_nelements(src1) * sizeof(float);
|
||||
|
||||
cl_kernel kernel = (d_state == 128)
|
||||
? backend_ctx->kernel_ssm_scan_f32_mamba2_d128
|
||||
: backend_ctx->kernel_ssm_scan_f32_mamba2_d256;
|
||||
GGML_ASSERT(kernel != nullptr);
|
||||
|
||||
cl_ulong s0_nb2 = src0->nb[2];
|
||||
cl_ulong s0_nb3 = src0->nb[3];
|
||||
cl_ulong x_nb2 = src1->nb[2];
|
||||
cl_ulong x_nb3 = src1->nb[3];
|
||||
cl_ulong dt_nb1 = src2->nb[1];
|
||||
cl_ulong dt_nb2 = src2->nb[2];
|
||||
cl_ulong A_nb1 = src3->nb[1];
|
||||
cl_ulong B_nb2 = src4->nb[2];
|
||||
cl_ulong B_nb3 = src4->nb[3];
|
||||
cl_ulong C_nb2 = src5->nb[2];
|
||||
cl_ulong C_nb3 = src5->nb[3];
|
||||
|
||||
CL_CHECK(clSetKernelArg(kernel, 0, sizeof(cl_mem), &e0->data_device));
|
||||
CL_CHECK(clSetKernelArg(kernel, 1, sizeof(cl_ulong), &o0));
|
||||
CL_CHECK(clSetKernelArg(kernel, 2, sizeof(cl_mem), &e1->data_device));
|
||||
CL_CHECK(clSetKernelArg(kernel, 3, sizeof(cl_ulong), &o1));
|
||||
CL_CHECK(clSetKernelArg(kernel, 4, sizeof(cl_mem), &e2->data_device));
|
||||
CL_CHECK(clSetKernelArg(kernel, 5, sizeof(cl_ulong), &o2));
|
||||
CL_CHECK(clSetKernelArg(kernel, 6, sizeof(cl_mem), &e3->data_device));
|
||||
CL_CHECK(clSetKernelArg(kernel, 7, sizeof(cl_ulong), &o3));
|
||||
CL_CHECK(clSetKernelArg(kernel, 8, sizeof(cl_mem), &e4->data_device));
|
||||
CL_CHECK(clSetKernelArg(kernel, 9, sizeof(cl_ulong), &o4));
|
||||
CL_CHECK(clSetKernelArg(kernel, 10, sizeof(cl_mem), &e5->data_device));
|
||||
CL_CHECK(clSetKernelArg(kernel, 11, sizeof(cl_ulong), &o5));
|
||||
CL_CHECK(clSetKernelArg(kernel, 12, sizeof(cl_mem), &e6->data_device));
|
||||
CL_CHECK(clSetKernelArg(kernel, 13, sizeof(cl_ulong), &o6));
|
||||
CL_CHECK(clSetKernelArg(kernel, 14, sizeof(cl_mem), &ed->data_device));
|
||||
CL_CHECK(clSetKernelArg(kernel, 15, sizeof(cl_ulong), &od));
|
||||
CL_CHECK(clSetKernelArg(kernel, 16, sizeof(cl_ulong), &s0_nb2));
|
||||
CL_CHECK(clSetKernelArg(kernel, 17, sizeof(cl_ulong), &s0_nb3));
|
||||
CL_CHECK(clSetKernelArg(kernel, 18, sizeof(cl_ulong), &x_nb2));
|
||||
CL_CHECK(clSetKernelArg(kernel, 19, sizeof(cl_ulong), &x_nb3));
|
||||
CL_CHECK(clSetKernelArg(kernel, 20, sizeof(cl_ulong), &dt_nb1));
|
||||
CL_CHECK(clSetKernelArg(kernel, 21, sizeof(cl_ulong), &dt_nb2));
|
||||
CL_CHECK(clSetKernelArg(kernel, 22, sizeof(cl_ulong), &A_nb1));
|
||||
CL_CHECK(clSetKernelArg(kernel, 23, sizeof(cl_ulong), &B_nb2));
|
||||
CL_CHECK(clSetKernelArg(kernel, 24, sizeof(cl_ulong), &B_nb3));
|
||||
CL_CHECK(clSetKernelArg(kernel, 25, sizeof(cl_ulong), &C_nb2));
|
||||
CL_CHECK(clSetKernelArg(kernel, 26, sizeof(cl_ulong), &C_nb3));
|
||||
CL_CHECK(clSetKernelArg(kernel, 27, sizeof(cl_ulong), &s_off_bytes));
|
||||
CL_CHECK(clSetKernelArg(kernel, 28, sizeof(int), &head_dim));
|
||||
CL_CHECK(clSetKernelArg(kernel, 29, sizeof(int), &n_head));
|
||||
CL_CHECK(clSetKernelArg(kernel, 30, sizeof(int), &n_group));
|
||||
CL_CHECK(clSetKernelArg(kernel, 31, sizeof(int), &n_tokens));
|
||||
|
||||
size_t global_work_size[] = { (size_t)n_head * head_dim * 64, (size_t)n_seqs, 1 };
|
||||
size_t local_work_size[] = { 64, 1, 1 };
|
||||
|
||||
backend_ctx->enqueue_ndrange_kernel(kernel, 3, global_work_size, local_work_size, dst);
|
||||
}
|
||||
|
||||
static void ggml_cl_ssm_conv(ggml_backend_t backend, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) {
|
||||
GGML_ASSERT(src0);
|
||||
GGML_ASSERT(src0->extra);
|
||||
@@ -12690,7 +12830,10 @@ static void ggml_cl_norm(ggml_backend_t backend, const ggml_tensor * src0, const
|
||||
GGML_TENSOR_LOCALS(int, ne0, src0, ne);
|
||||
GGML_TENSOR_LOCALS(cl_ulong, nb0, src0, nb);
|
||||
|
||||
const int nth = MIN(64, ne00);
|
||||
int nth = 1;
|
||||
while (nth < ne00 && nth < 64) {
|
||||
nth *= 2;
|
||||
}
|
||||
|
||||
cl_kernel kernel = backend_ctx->kernel_norm;
|
||||
|
||||
@@ -20725,18 +20868,42 @@ static void moe_router_reoerder(ggml_backend_t backend, const ggml_tensor * src,
|
||||
size_t fill_local_size[] = {64, 1, 1};
|
||||
backend_ctx->enqueue_ndrange_kernel(kernel, 3, fill_global_size, fill_local_size, src);
|
||||
|
||||
// Scatter
|
||||
kernel = backend_ctx->kernel_moe_scatter;
|
||||
CL_CHECK(clSetKernelArg(kernel, 0, sizeof(cl_mem), &original_router_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 1, sizeof(cl_mem), &post_router_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 2, sizeof(cl_mem), &emap_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 3, sizeof(cl_mem), &tile_offset_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 4, sizeof(cl_mem), &slot_counter_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 5, sizeof(int), &ne21));
|
||||
CL_CHECK(clSetKernelArg(kernel, 6, sizeof(int), &ne20));
|
||||
CL_CHECK(clSetKernelArg(kernel, 7, sizeof(int), &ne02));
|
||||
// Scatter. The deterministic variant is the default: kernel_moe_scatter derives
|
||||
// each token's slot from an atomic counter, so the packing inside an expert - and
|
||||
// with it the output of the ragged prefill GEMM - changes from run to run. Set
|
||||
// GGML_OPENCL_MOE_STABLE_SCATTER=0 to restore the atomic version.
|
||||
static const bool stable_scatter = []{
|
||||
const char * e = getenv("GGML_OPENCL_MOE_STABLE_SCATTER");
|
||||
return !e || e[0] == '\0' || e[0] != '0';
|
||||
}();
|
||||
|
||||
backend_ctx->enqueue_ndrange_kernel(kernel, 3, histogram_global_size, histogram_local_size, src);
|
||||
if (stable_scatter) {
|
||||
kernel = backend_ctx->kernel_moe_scatter_stable;
|
||||
CL_CHECK(clSetKernelArg(kernel, 0, sizeof(cl_mem), &original_router_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 1, sizeof(cl_mem), &post_router_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 2, sizeof(cl_mem), &emap_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 3, sizeof(cl_mem), &tile_offset_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 4, sizeof(int), &ne21));
|
||||
CL_CHECK(clSetKernelArg(kernel, 5, sizeof(int), &ne20));
|
||||
CL_CHECK(clSetKernelArg(kernel, 6, sizeof(int), &ne02));
|
||||
|
||||
// one workgroup (one wave) per expert; each ranks its own tokens
|
||||
size_t scatter_global_size[] = {64, (size_t)ne02};
|
||||
size_t scatter_local_size[] = {64, 1};
|
||||
backend_ctx->enqueue_ndrange_kernel(kernel, 2, scatter_global_size, scatter_local_size, src);
|
||||
} else {
|
||||
kernel = backend_ctx->kernel_moe_scatter;
|
||||
CL_CHECK(clSetKernelArg(kernel, 0, sizeof(cl_mem), &original_router_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 1, sizeof(cl_mem), &post_router_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 2, sizeof(cl_mem), &emap_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 3, sizeof(cl_mem), &tile_offset_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 4, sizeof(cl_mem), &slot_counter_buf));
|
||||
CL_CHECK(clSetKernelArg(kernel, 5, sizeof(int), &ne21));
|
||||
CL_CHECK(clSetKernelArg(kernel, 6, sizeof(int), &ne20));
|
||||
CL_CHECK(clSetKernelArg(kernel, 7, sizeof(int), &ne02));
|
||||
|
||||
backend_ctx->enqueue_ndrange_kernel(kernel, 3, histogram_global_size, histogram_local_size, src);
|
||||
}
|
||||
|
||||
// [MOE_TILES] env-gated padding probe: read back total_tiles (= Sum_e
|
||||
// ceil(k_e/n_tile_size)) and compare to the ideal tile count for the real
|
||||
@@ -24743,6 +24910,14 @@ bool ggml_cl_compute_forward(ggml_backend_t backend, struct ggml_tensor * tensor
|
||||
}
|
||||
func = ggml_cl_ssm_conv;
|
||||
break;
|
||||
case GGML_OP_SSM_SCAN:
|
||||
if (!any_on_device) {
|
||||
return false;
|
||||
}
|
||||
// SSM_SCAN has 7 source tensors, so it cannot use the standard
|
||||
// (src0, src1, dst) func signature. Dispatch directly and return.
|
||||
ggml_cl_ssm_scan(backend, tensor);
|
||||
return true;
|
||||
case GGML_OP_GATED_DELTA_NET:
|
||||
if (!any_on_device) {
|
||||
return false;
|
||||
|
||||
@@ -68,6 +68,79 @@ __kernel void kernel_moe_scatter(
|
||||
emap[tile_idx] = val;
|
||||
}
|
||||
|
||||
// Deterministic replacement for kernel_moe_scatter.
|
||||
//
|
||||
// kernel_moe_scatter takes each token's slot from atomic_inc(slot_counter[expert]),
|
||||
// so the token -> slot packing inside an expert depends on which work-item wins the
|
||||
// atomic and changes from run to run. The ragged prefill GEMM path is sensitive to
|
||||
// that packing (the non-ragged path is not, since its padded slots alias slot 0 and
|
||||
// are overwritten last), which makes MoE prompt processing non-reproducible: the same
|
||||
// binary on the same prompt returns one of several outputs.
|
||||
//
|
||||
// Here the slot is the token's rank in flat (n, k) order among the tokens routed to
|
||||
// the same expert - a fixed function of the routing input. One workgroup per expert
|
||||
// walks the flat routing list in blocks of 64 and ranks its own tokens with a
|
||||
// workgroup scan, carrying a running count between blocks. Cost is one pass over the
|
||||
// routing list per expert; the list is a few KiB and stays in cache.
|
||||
__kernel void kernel_moe_scatter_stable(
|
||||
__global const int * input,
|
||||
__global int * post_router,
|
||||
__global ushort * emap,
|
||||
__global const int * tile_offset,
|
||||
int N,
|
||||
int topK,
|
||||
uint n_experts
|
||||
) {
|
||||
const int e = get_group_id(1);
|
||||
const int lid = get_local_id(0);
|
||||
const int M = N * topK;
|
||||
|
||||
__local int scan[64];
|
||||
__local int running;
|
||||
|
||||
if (lid == 0) {
|
||||
running = 0;
|
||||
}
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
for (int base = 0; base < M; base += 64) {
|
||||
const int j = base + lid;
|
||||
|
||||
int pred = 0;
|
||||
if (j < M) {
|
||||
const int n = j / topK;
|
||||
const int k = j - n * topK;
|
||||
pred = (input[n * (int)n_experts + k] == e) ? 1 : 0;
|
||||
}
|
||||
|
||||
scan[lid] = pred;
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
|
||||
// Hillis-Steele inclusive scan over the 64 lanes
|
||||
for (int off = 1; off < 64; off <<= 1) {
|
||||
int add = (lid >= off) ? scan[lid - off] : 0;
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
scan[lid] += add;
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
}
|
||||
|
||||
if (pred) {
|
||||
const int local_slot = running + (scan[lid] - 1); // exclusive rank
|
||||
const int tile_idx = tile_offset[e] + (local_slot >> 5);
|
||||
const int lane = local_slot & 31;
|
||||
|
||||
post_router[tile_idx * 32 + lane] = j;
|
||||
emap[tile_idx] = (ushort)e;
|
||||
}
|
||||
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
if (lid == 63) {
|
||||
running += scan[63];
|
||||
}
|
||||
barrier(CLK_LOCAL_MEM_FENCE);
|
||||
}
|
||||
}
|
||||
|
||||
__kernel void kernel_moe_fill(
|
||||
__global int * post_router,
|
||||
__global int * total_tiles,
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
// Mamba2 fused SSM scan kernel. One workgroup per (head, dim, seq); WG size =
|
||||
// 64 threads. Each thread owns c_factor = d_state/64 state elements in
|
||||
// private registers; the state stays resident across the n_tokens t-loop
|
||||
//
|
||||
// References:
|
||||
// ggml/src/ggml-cuda/ssm-scan.cu:117 ssm_scan_f32_group
|
||||
// ggml/src/ggml-cpu/ops.cpp:9368 ggml_compute_forward_ssm_scan_f32
|
||||
|
||||
#pragma OPENCL EXTENSION cl_khr_fp16 : enable
|
||||
|
||||
#ifdef cl_khr_subgroups
|
||||
#pragma OPENCL EXTENSION cl_khr_subgroups : enable
|
||||
#endif
|
||||
|
||||
#if defined(cl_qcom_reqd_sub_group_size)
|
||||
#pragma OPENCL EXTENSION cl_qcom_reqd_sub_group_size : enable
|
||||
#define REQD_SUBGROUP_SIZE_64 __attribute__((qcom_reqd_sub_group_size("half")))
|
||||
#else
|
||||
#define REQD_SUBGROUP_SIZE_64
|
||||
#endif
|
||||
|
||||
inline float softplus_f32(float x) {
|
||||
return (x <= 20.0f) ? log(1.0f + exp(x)) : x;
|
||||
}
|
||||
|
||||
// d_state = 128 (most Mamba-2 models, e.g. mamba2-2.7B, Codestral-Mamba).
|
||||
// WG = 64 threads, each holds 2 state elements (tid and tid+64).
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
kernel void kernel_ssm_scan_f32_mamba2_d128(
|
||||
global const char * src0_base, ulong src0_off,
|
||||
global const char * src1_base, ulong src1_off,
|
||||
global const char * src2_base, ulong src2_off,
|
||||
global const char * src3_base, ulong src3_off,
|
||||
global const char * src4_base, ulong src4_off,
|
||||
global const char * src5_base, ulong src5_off,
|
||||
global const char * src6_base, ulong src6_off,
|
||||
global char * dst_base, ulong dst_off,
|
||||
ulong s0_nb2, ulong s0_nb3,
|
||||
ulong x_nb2, ulong x_nb3,
|
||||
ulong dt_nb1, ulong dt_nb2,
|
||||
ulong A_nb1,
|
||||
ulong B_nb2, ulong B_nb3,
|
||||
ulong C_nb2, ulong C_nb3,
|
||||
ulong s_off_bytes,
|
||||
int head_dim, int n_head, int n_group, int n_tokens
|
||||
) {
|
||||
const int d_state = 128;
|
||||
|
||||
const int tid = (int) get_local_id(0);
|
||||
const int wg_x = (int) get_group_id(0);
|
||||
const int seq_id = (int) get_group_id(1);
|
||||
|
||||
const int head_id = wg_x / head_dim;
|
||||
const int dim_id = wg_x - head_id * head_dim;
|
||||
const int g = head_id / (n_head / n_group);
|
||||
|
||||
src0_base += src0_off;
|
||||
src1_base += src1_off;
|
||||
src2_base += src2_off;
|
||||
src3_base += src3_off;
|
||||
src4_base += src4_off;
|
||||
src5_base += src5_off;
|
||||
src6_base += src6_off;
|
||||
dst_base += dst_off;
|
||||
|
||||
const int seq_slot = ((global const int *) src6_base)[seq_id];
|
||||
|
||||
const ulong state_base_off = (ulong)seq_slot * s0_nb3 + (ulong)head_id * s0_nb2
|
||||
+ (ulong)dim_id * d_state * sizeof(float);
|
||||
global const float * s0_warp = (global const float *)(src0_base + state_base_off);
|
||||
const ulong state_out_off = (ulong)seq_id * s0_nb3 + (ulong)head_id * s0_nb2
|
||||
+ (ulong)dim_id * d_state * sizeof(float);
|
||||
global float * s_warp = (global float *)(dst_base + s_off_bytes + state_out_off);
|
||||
|
||||
global const char * x_seq = src1_base + (ulong)seq_id * x_nb3;
|
||||
global const char * dt_seq = src2_base + (ulong)seq_id * dt_nb2;
|
||||
global const char * B_seq = src4_base + (ulong)seq_id * B_nb3 + (ulong)g * d_state * sizeof(float);
|
||||
global const char * C_seq = src5_base + (ulong)seq_id * C_nb3 + (ulong)g * d_state * sizeof(float);
|
||||
|
||||
const ulong y_dim_total = (ulong)n_head * head_dim;
|
||||
global float * y_seq = (global float *)dst_base
|
||||
+ (ulong)seq_id * (ulong)n_tokens * y_dim_total;
|
||||
|
||||
const float A_val = ((global const float *)src3_base)[(ulong)head_id * A_nb1 / sizeof(float)];
|
||||
|
||||
// c_factor = 2: each thread owns 2 state elements (tid and tid+64).
|
||||
float state0 = s0_warp[tid];
|
||||
float state1 = s0_warp[tid + 64];
|
||||
|
||||
for (int t = 0; t < n_tokens; ++t) {
|
||||
const float dt_h = ((global const float *)(dt_seq + (ulong)t * dt_nb1))[head_id];
|
||||
const float dt_softplus = softplus_f32(dt_h);
|
||||
const float dA = exp(dt_softplus * A_val);
|
||||
const float x_val = ((global const float *)(x_seq + (ulong)t * x_nb2))[(ulong)head_id * head_dim + dim_id];
|
||||
const float x_dt = x_val * dt_softplus;
|
||||
|
||||
const float B0 = ((global const float *)(B_seq + (ulong)t * B_nb2))[tid];
|
||||
const float B1 = ((global const float *)(B_seq + (ulong)t * B_nb2))[tid + 64];
|
||||
const float C0 = ((global const float *)(C_seq + (ulong)t * C_nb2))[tid];
|
||||
const float C1 = ((global const float *)(C_seq + (ulong)t * C_nb2))[tid + 64];
|
||||
|
||||
state0 = state0 * dA + B0 * x_dt;
|
||||
state1 = state1 * dA + B1 * x_dt;
|
||||
const float partial = state0 * C0 + state1 * C1;
|
||||
|
||||
const float sum = sub_group_reduce_add(partial);
|
||||
if (tid == 0) {
|
||||
y_seq[(ulong)t * y_dim_total + (ulong)head_id * head_dim + dim_id] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
s_warp[tid] = state0;
|
||||
s_warp[tid + 64] = state1;
|
||||
}
|
||||
|
||||
// d_state = 256 (Falcon-H1). WG = 64 threads, each holds 4 state elements.
|
||||
REQD_SUBGROUP_SIZE_64
|
||||
kernel void kernel_ssm_scan_f32_mamba2_d256(
|
||||
global const char * src0_base, ulong src0_off,
|
||||
global const char * src1_base, ulong src1_off,
|
||||
global const char * src2_base, ulong src2_off,
|
||||
global const char * src3_base, ulong src3_off,
|
||||
global const char * src4_base, ulong src4_off,
|
||||
global const char * src5_base, ulong src5_off,
|
||||
global const char * src6_base, ulong src6_off,
|
||||
global char * dst_base, ulong dst_off,
|
||||
ulong s0_nb2, ulong s0_nb3,
|
||||
ulong x_nb2, ulong x_nb3,
|
||||
ulong dt_nb1, ulong dt_nb2,
|
||||
ulong A_nb1,
|
||||
ulong B_nb2, ulong B_nb3,
|
||||
ulong C_nb2, ulong C_nb3,
|
||||
ulong s_off_bytes,
|
||||
int head_dim, int n_head, int n_group, int n_tokens
|
||||
) {
|
||||
const int d_state = 256;
|
||||
|
||||
const int tid = (int) get_local_id(0);
|
||||
const int wg_x = (int) get_group_id(0);
|
||||
const int seq_id = (int) get_group_id(1);
|
||||
|
||||
const int head_id = wg_x / head_dim;
|
||||
const int dim_id = wg_x - head_id * head_dim;
|
||||
const int g = head_id / (n_head / n_group);
|
||||
|
||||
src0_base += src0_off;
|
||||
src1_base += src1_off;
|
||||
src2_base += src2_off;
|
||||
src3_base += src3_off;
|
||||
src4_base += src4_off;
|
||||
src5_base += src5_off;
|
||||
src6_base += src6_off;
|
||||
dst_base += dst_off;
|
||||
|
||||
const int seq_slot = ((global const int *) src6_base)[seq_id];
|
||||
|
||||
const ulong state_base_off = (ulong)seq_slot * s0_nb3 + (ulong)head_id * s0_nb2
|
||||
+ (ulong)dim_id * d_state * sizeof(float);
|
||||
global const float * s0_warp = (global const float *)(src0_base + state_base_off);
|
||||
const ulong state_out_off = (ulong)seq_id * s0_nb3 + (ulong)head_id * s0_nb2
|
||||
+ (ulong)dim_id * d_state * sizeof(float);
|
||||
global float * s_warp = (global float *)(dst_base + s_off_bytes + state_out_off);
|
||||
|
||||
global const char * x_seq = src1_base + (ulong)seq_id * x_nb3;
|
||||
global const char * dt_seq = src2_base + (ulong)seq_id * dt_nb2;
|
||||
global const char * B_seq = src4_base + (ulong)seq_id * B_nb3 + (ulong)g * d_state * sizeof(float);
|
||||
global const char * C_seq = src5_base + (ulong)seq_id * C_nb3 + (ulong)g * d_state * sizeof(float);
|
||||
|
||||
const ulong y_dim_total = (ulong)n_head * head_dim;
|
||||
global float * y_seq = (global float *)dst_base
|
||||
+ (ulong)seq_id * (ulong)n_tokens * y_dim_total;
|
||||
|
||||
const float A_val = ((global const float *)src3_base)[(ulong)head_id * A_nb1 / sizeof(float)];
|
||||
|
||||
// c_factor = 4: each thread owns 4 state elements.
|
||||
float state0 = s0_warp[tid];
|
||||
float state1 = s0_warp[tid + 64];
|
||||
float state2 = s0_warp[tid + 128];
|
||||
float state3 = s0_warp[tid + 192];
|
||||
|
||||
for (int t = 0; t < n_tokens; ++t) {
|
||||
const float dt_h = ((global const float *)(dt_seq + (ulong)t * dt_nb1))[head_id];
|
||||
const float dt_softplus = softplus_f32(dt_h);
|
||||
const float dA = exp(dt_softplus * A_val);
|
||||
const float x_val = ((global const float *)(x_seq + (ulong)t * x_nb2))[(ulong)head_id * head_dim + dim_id];
|
||||
const float x_dt = x_val * dt_softplus;
|
||||
|
||||
global const float * B_t = (global const float *)(B_seq + (ulong)t * B_nb2);
|
||||
global const float * C_t = (global const float *)(C_seq + (ulong)t * C_nb2);
|
||||
|
||||
const float B0 = B_t[tid];
|
||||
const float B1 = B_t[tid + 64];
|
||||
const float B2 = B_t[tid + 128];
|
||||
const float B3 = B_t[tid + 192];
|
||||
const float C0 = C_t[tid];
|
||||
const float C1 = C_t[tid + 64];
|
||||
const float C2 = C_t[tid + 128];
|
||||
const float C3 = C_t[tid + 192];
|
||||
|
||||
state0 = state0 * dA + B0 * x_dt;
|
||||
state1 = state1 * dA + B1 * x_dt;
|
||||
state2 = state2 * dA + B2 * x_dt;
|
||||
state3 = state3 * dA + B3 * x_dt;
|
||||
const float partial = state0 * C0 + state1 * C1 + state2 * C2 + state3 * C3;
|
||||
|
||||
const float sum = sub_group_reduce_add(partial);
|
||||
if (tid == 0) {
|
||||
y_seq[(ulong)t * y_dim_total + (ulong)head_id * head_dim + dim_id] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
s_warp[tid] = state0;
|
||||
s_warp[tid + 64] = state1;
|
||||
s_warp[tid + 128] = state2;
|
||||
s_warp[tid + 192] = state3;
|
||||
}
|
||||
@@ -1227,6 +1227,10 @@ static bool is_op_unsupported_case(const ggml_tensor * op) {
|
||||
const int32_t * op_params = op->op_params;
|
||||
const int n_dims = op_params[1];
|
||||
const int mode = op_params[2];
|
||||
if (op_params[15] != 0) {
|
||||
// FIXME: support ggml_rope_set_offset
|
||||
return true;
|
||||
}
|
||||
if (mode != GGML_ROPE_TYPE_NORMAL && mode != GGML_ROPE_TYPE_NEOX && mode != GGML_ROPE_TYPE_IMROPE) {
|
||||
// GGML_LOG_WARN("OpenVINO backend does not support ROPE with mode %d\n", mode);
|
||||
return true;
|
||||
|
||||
@@ -109,7 +109,14 @@ int g_ggml_sycl_enable_host_pinned_mem = 1;
|
||||
static ggml_sycl_device_info ggml_sycl_init() {
|
||||
ggml_sycl_device_info info = {};
|
||||
|
||||
info.device_count = dpct::dev_mgr::instance().device_count();
|
||||
// Do not hard crash when there exists no SYCL devices.
|
||||
// We want to allow the user to use non-SYCL tools when SYCL is compiled (such as llama-quantize)
|
||||
try {
|
||||
info.device_count = dpct::dev_mgr::instance().device_count();
|
||||
} catch (sycl::exception const &exc) {
|
||||
GGML_LOG_INFO("%s: no SYCL device available: %s\n", __func__, exc.what());
|
||||
info.device_count = 0;
|
||||
}
|
||||
if (info.device_count == 0) {
|
||||
GGML_LOG_ERROR("%s: failed to initialize: %s\n", GGML_SYCL_NAME, __func__);
|
||||
return info;
|
||||
@@ -6235,6 +6242,8 @@ static bool do_ggml_backend_sycl_device_supports_op(ggml_backend_dev_t dev, cons
|
||||
}
|
||||
case GGML_OP_ROPE:
|
||||
case GGML_OP_ROPE_BACK:
|
||||
// FIXME: support ggml_rope_set_offset
|
||||
return ((const int32_t *) op->op_params)[15] == 0;
|
||||
case GGML_OP_IM2COL:
|
||||
case GGML_OP_IM2COL_3D:
|
||||
case GGML_OP_UPSCALE:
|
||||
|
||||
@@ -200,8 +200,11 @@ if (Vulkan_FOUND)
|
||||
set (_ggml_vk_header "${CMAKE_CURRENT_BINARY_DIR}/ggml-vulkan-shaders.hpp")
|
||||
set (_ggml_vk_input_dir "${CMAKE_CURRENT_SOURCE_DIR}/vulkan-shaders")
|
||||
set (_ggml_vk_output_dir "${CMAKE_CURRENT_BINARY_DIR}/vulkan-shaders.spv")
|
||||
set (_ggml_vk_generated_shader_files ${_ggml_vk_header})
|
||||
|
||||
file(GLOB _ggml_vk_shader_files CONFIGURE_DEPENDS "${_ggml_vk_input_dir}/*.comp")
|
||||
set_source_files_properties(${_ggml_vk_shader_files} PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
target_sources(ggml-vulkan PRIVATE ${_ggml_vk_shader_files})
|
||||
|
||||
# Because external projects do not provide source-level tracking,
|
||||
# the vulkan-shaders-gen sources need to be explicitly added to
|
||||
@@ -241,8 +244,11 @@ if (Vulkan_FOUND)
|
||||
COMMENT "Generate vulkan shaders for ${file}"
|
||||
)
|
||||
target_sources(ggml-vulkan PRIVATE ${_ggml_vk_target_cpp})
|
||||
list(APPEND _ggml_vk_generated_shader_files ${_ggml_vk_target_cpp})
|
||||
endforeach()
|
||||
|
||||
source_group("Vulkan shaders" FILES ${_ggml_vk_shader_files})
|
||||
source_group("Generated Vulkan shaders" FILES ${_ggml_vk_generated_shader_files})
|
||||
else()
|
||||
message(WARNING "Vulkan not found")
|
||||
endif()
|
||||
|
||||
@@ -913,6 +913,7 @@ struct vk_device_struct {
|
||||
vk_pipeline pipeline_quantize_q8_1_x4;
|
||||
|
||||
vk_pipeline pipeline_dequant[GGML_TYPE_COUNT];
|
||||
vk_pipeline pipeline_dequant_transpose[GGML_TYPE_COUNT]; // fused dequant+transpose for FA quant-KV
|
||||
vk_pipeline pipeline_dequant_mul_mat_vec_f32_f32[DMMV_WG_SIZE_COUNT][GGML_TYPE_COUNT][mul_mat_vec_max_cols];
|
||||
vk_pipeline pipeline_dequant_mul_mat_vec_f16_f32[DMMV_WG_SIZE_COUNT][GGML_TYPE_COUNT][mul_mat_vec_max_cols];
|
||||
vk_pipeline pipeline_dequant_mul_mat_vec_id_f32[DMMV_WG_SIZE_COUNT][GGML_TYPE_COUNT];
|
||||
@@ -1645,6 +1646,7 @@ struct vk_op_rope_push_constants {
|
||||
uint32_t rope_mode;
|
||||
uint32_t nrows;
|
||||
uint32_t n_dims;
|
||||
uint32_t n_offs;
|
||||
float freq_scale;
|
||||
float freq_base;
|
||||
float ext_factor;
|
||||
@@ -3383,10 +3385,10 @@ static void ggml_vk_queue_command_pools_cleanup(vk_device& device) {
|
||||
// Arbitrary frequency to cleanup/reuse command buffers
|
||||
static constexpr uint32_t cleanup_frequency = 10;
|
||||
|
||||
if (device->compute_queue->cmd_pool.buffers_in_use() >= cleanup_frequency) {
|
||||
if (device->compute_queue && device->compute_queue->cmd_pool.buffers_in_use() >= cleanup_frequency) {
|
||||
ggml_vk_command_pool_cleanup(device, device->compute_queue->cmd_pool);
|
||||
}
|
||||
if (device->transfer_queue->cmd_pool.buffers_in_use() >= cleanup_frequency) {
|
||||
if (device->transfer_queue && device->transfer_queue->cmd_pool.buffers_in_use() >= cleanup_frequency) {
|
||||
ggml_vk_command_pool_cleanup(device, device->transfer_queue->cmd_pool);
|
||||
}
|
||||
}
|
||||
@@ -5390,6 +5392,7 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
|
||||
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_Q5_0], "dequant_q5_0", dequant_q5_0_len, dequant_q5_0_data, "main", 2, 5 * sizeof(uint32_t), {256 * 16, 1, 1}, {}, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_Q5_1], "dequant_q5_1", dequant_q5_1_len, dequant_q5_1_data, "main", 2, 5 * sizeof(uint32_t), {256 * 16, 1, 1}, {}, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_Q8_0], "dequant_q8_0", dequant_q8_0_len, dequant_q8_0_data, "main", 2, 5 * sizeof(uint32_t), {256 * 16, 1, 1}, {}, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_dequant_transpose[GGML_TYPE_Q8_0], "dequant_q8_0_transpose", dequant_q8_0_transpose_len, dequant_q8_0_transpose_data, "main", 2, 5 * sizeof(uint32_t), {256 * 16, 1, 1}, {}, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_Q2_K], "dequant_q2_k", dequant_q2_k_len, dequant_q2_k_data, "main", 2, 5 * sizeof(uint32_t), {256 * 64, 1, 1}, {}, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_TQ2_0], "dequant_tq2_0", dequant_tq2_0_len, dequant_tq2_0_data, "main", 2, 5 * sizeof(uint32_t), {256 * 64, 1, 1}, {}, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_Q3_K], "dequant_q3_k", dequant_q3_k_len, dequant_q3_k_data, "main", 2, 5 * sizeof(uint32_t), {256 * 64, 1, 1}, {}, 1);
|
||||
@@ -10822,9 +10825,32 @@ static void ggml_vk_flash_attn(ggml_backend_vk_context * ctx, vk_context& subctx
|
||||
|
||||
const bool f32acc = !ctx->device->fp16 || dst->op_params[3] == GGML_PREC_F32 || k->type == GGML_TYPE_BF16;
|
||||
|
||||
// dequant K/V once into an f16 scratch, reordered KV layout so FA can read without a stride
|
||||
auto is_dense_kv_cache = [](const ggml_tensor * t) {
|
||||
return t->nb[0] == ggml_type_size(t->type) &&
|
||||
t->nb[2] == ggml_row_size(t->type, t->ne[0]) &&
|
||||
t->nb[1] == t->nb[2] * t->ne[2] &&
|
||||
t->nb[3] == t->nb[1] * t->ne[1];
|
||||
};
|
||||
const bool k_quant = k->type != GGML_TYPE_F16 && k->type != GGML_TYPE_BF16 && k->type != GGML_TYPE_F32;
|
||||
const bool v_quant = v->type != GGML_TYPE_F16 && v->type != GGML_TYPE_BF16 && v->type != GGML_TYPE_F32;
|
||||
const bool use_dequant_kv = k_quant && v_quant && neq1 >= 64 &&
|
||||
is_dense_kv_cache(k) && is_dense_kv_cache(v) &&
|
||||
(uint64_t)ggml_nelements(k) * sizeof(ggml_fp16_t) <= ctx->device->properties.limits.maxStorageBufferRange &&
|
||||
(uint64_t)ggml_nelements(v) * sizeof(ggml_fp16_t) <= ctx->device->properties.limits.maxStorageBufferRange &&
|
||||
ctx->device->pipeline_dequant_transpose[k->type] != nullptr &&
|
||||
ctx->device->pipeline_dequant_transpose[v->type] != nullptr &&
|
||||
// coopmat2 path does not benefit from the f16 scratch
|
||||
!ctx->device->coopmat2 &&
|
||||
// Intel Xe1 regresses, see PR 25494
|
||||
(ctx->device->vendor_id != VK_VENDOR_ID_INTEL ||
|
||||
(ctx->device->coopmat_support && ctx->device->architecture != vk_device_architecture::INTEL_XE1));
|
||||
const ggml_type k_type_eff = use_dequant_kv ? GGML_TYPE_F16 : k->type;
|
||||
const ggml_type v_type_eff = use_dequant_kv ? GGML_TYPE_F16 : v->type;
|
||||
|
||||
// For scalar/coopmat1 FA, we can use the "large" size to accommodate qga.
|
||||
// For coopmat2 FA, we always use the small size (which is still pretty large for gqa).
|
||||
vk_fa_tuning_params tuning_params = get_fa_tuning_params(ctx->device, HSK, HSV, 512, KV, k->type, v->type, f32acc);
|
||||
vk_fa_tuning_params tuning_params = get_fa_tuning_params(ctx->device, HSK, HSV, 512, KV, k_type_eff, v_type_eff, f32acc);
|
||||
const uint32_t max_gqa = std::min(tuning_params.block_rows, 32u);
|
||||
|
||||
if (N <= 8 && qk_ratio > 1 && qk_ratio <= max_gqa &&
|
||||
@@ -10837,7 +10863,7 @@ static void ggml_vk_flash_attn(ggml_backend_vk_context * ctx, vk_context& subctx
|
||||
workgroups_y /= gqa_ratio;
|
||||
}
|
||||
|
||||
tuning_params = get_fa_tuning_params(ctx->device, HSK, HSV, N, KV, k->type, v->type, f32acc);
|
||||
tuning_params = get_fa_tuning_params(ctx->device, HSK, HSV, N, KV, k_type_eff, v_type_eff, f32acc);
|
||||
|
||||
const uint32_t q_stride = (uint32_t)(nbq1 / ggml_type_size(q->type));
|
||||
uint32_t k_stride = (uint32_t)(nbk1 / ggml_type_size(k->type));
|
||||
@@ -10851,6 +10877,17 @@ static void ggml_vk_flash_attn(ggml_backend_vk_context * ctx, vk_context& subctx
|
||||
v_stride /= 4;
|
||||
}
|
||||
|
||||
uint32_t nbk2_eff = (uint32_t)nbk2, nbk3_eff = (uint32_t)nbk3;
|
||||
uint32_t nbv2_eff = (uint32_t)nbv2, nbv3_eff = (uint32_t)nbv3;
|
||||
if (use_dequant_kv) {
|
||||
k_stride = HSK;
|
||||
v_stride = HSV;
|
||||
nbk2_eff = (uint32_t)((uint64_t)HSK * KV * sizeof(ggml_fp16_t));
|
||||
nbk3_eff = (uint32_t)((uint64_t)HSK * KV * nek2 * sizeof(ggml_fp16_t));
|
||||
nbv2_eff = (uint32_t)((uint64_t)HSV * KV * sizeof(ggml_fp16_t));
|
||||
nbv3_eff = (uint32_t)((uint64_t)HSV * KV * nev2 * sizeof(ggml_fp16_t));
|
||||
}
|
||||
|
||||
const uint32_t alignment = tuning_params.block_cols;
|
||||
bool aligned = (KV % alignment) == 0 &&
|
||||
// the "aligned" shader variant will forcibly align strides, for performance
|
||||
@@ -10877,7 +10914,7 @@ static void ggml_vk_flash_attn(ggml_backend_vk_context * ctx, vk_context& subctx
|
||||
bool use_mask_opt = mask && nem1 >= 32 && nem0 * nem1 > 32768 && nem0 >= tuning_params.block_cols * 16
|
||||
&& (ctx->device->architecture != vk_device_architecture::AMD_GCN || HSK > 256 || HSV > 256);
|
||||
vk_fa_pipeline_state fa_pipeline_state = get_fa_pipeline_state(ctx->device, tuning_params, HSK, HSV, aligned, f32acc,
|
||||
mask != nullptr, use_mask_opt, logit_softcap != 0, k->type, v->type);
|
||||
mask != nullptr, use_mask_opt, logit_softcap != 0, k_type_eff, v_type_eff);
|
||||
|
||||
vk_pipeline pipeline = nullptr;
|
||||
|
||||
@@ -10981,6 +11018,34 @@ static void ggml_vk_flash_attn(ggml_backend_vk_context * ctx, vk_context& subctx
|
||||
vk_subbuffer sinks_buf = sinks ? ggml_vk_tensor_subbuffer(ctx, sinks) : q_buf;
|
||||
vk_subbuffer mask_opt_buf = use_mask_opt ? ggml_vk_subbuffer(ctx, ctx->prealloc_y, 0) : q_buf;
|
||||
|
||||
if (use_dequant_kv) {
|
||||
const uint64_t fp = sizeof(ggml_fp16_t);
|
||||
const uint64_t k_f16_sz = (uint64_t)ggml_nelements(k) * fp;
|
||||
const uint64_t v_f16_sz = (uint64_t)ggml_nelements(v) * fp;
|
||||
if (ctx->prealloc_size_x < k_f16_sz + v_f16_sz) {
|
||||
ctx->prealloc_size_x = k_f16_sz + v_f16_sz;
|
||||
ggml_vk_preallocate_buffers(ctx, subctx);
|
||||
}
|
||||
vk_pipeline tr_k = ctx->device->pipeline_dequant_transpose[k->type];
|
||||
vk_pipeline tr_v = ctx->device->pipeline_dequant_transpose[v->type];
|
||||
ggml_pipeline_request_descriptor_sets(ctx, tr_k, 1);
|
||||
ggml_pipeline_request_descriptor_sets(ctx, tr_v, 1);
|
||||
if (ctx->prealloc_x_need_sync) {
|
||||
ggml_vk_sync_buffers(ctx, subctx);
|
||||
}
|
||||
vk_subbuffer k_dst = vk_subbuffer{ ctx->prealloc_x, 0, k_f16_sz };
|
||||
vk_subbuffer v_dst = vk_subbuffer{ ctx->prealloc_x, k_f16_sz, v_f16_sz };
|
||||
const uint32_t k_nel = (uint32_t)ggml_nelements(k);
|
||||
const uint32_t v_nel = (uint32_t)ggml_nelements(v);
|
||||
{ const std::vector<uint32_t> pc = { (uint32_t)HSK, (uint32_t)nek2, (uint32_t)KV, 0, k_nel };
|
||||
ggml_vk_dispatch_pipeline(ctx, subctx, tr_k, { k_buf, k_dst }, pc, { k_nel, 1, 1 }); }
|
||||
{ const std::vector<uint32_t> pc = { (uint32_t)HSV, (uint32_t)nev2, (uint32_t)KV, 0, v_nel };
|
||||
ggml_vk_dispatch_pipeline(ctx, subctx, tr_v, { v_buf, v_dst }, pc, { v_nel, 1, 1 }); }
|
||||
ggml_vk_sync_buffers(ctx, subctx);
|
||||
k_buf = k_dst;
|
||||
v_buf = v_dst;
|
||||
}
|
||||
|
||||
uint32_t mask_n_head_log2 = ((sinks != nullptr) << 24) | n_head_log2;
|
||||
|
||||
if (use_mask_opt)
|
||||
@@ -11010,8 +11075,8 @@ static void ggml_vk_flash_attn(ggml_backend_vk_context * ctx, vk_context& subctx
|
||||
(uint32_t)nev2, (uint32_t)nev3,
|
||||
nem1, nem2, nem3,
|
||||
q_stride, (uint32_t)nbq2, (uint32_t)nbq3,
|
||||
k_stride, (uint32_t)nbk2, (uint32_t)nbk3,
|
||||
v_stride, (uint32_t)nbv2, (uint32_t)nbv3,
|
||||
k_stride, nbk2_eff, nbk3_eff,
|
||||
v_stride, nbv2_eff, nbv3_eff,
|
||||
scale, max_bias, logit_softcap,
|
||||
mask_n_head_log2, m0, m1,
|
||||
gqa_ratio, split_kv, split_k };
|
||||
@@ -11053,6 +11118,10 @@ static void ggml_vk_flash_attn(ggml_backend_vk_context * ctx, vk_context& subctx
|
||||
{q_buf, k_buf, v_buf, mask_buf, sinks_buf, dst_buf, mask_opt_buf},
|
||||
pc, { workgroups_x, workgroups_y, workgroups_z });
|
||||
}
|
||||
|
||||
if (use_dequant_kv) {
|
||||
ctx->prealloc_x_need_sync = true;
|
||||
}
|
||||
}
|
||||
|
||||
static vk_conv_shapes ggml_vk_conv_select_shape(ggml_backend_vk_context * ctx, uint32_t K, uint32_t NPQ) {
|
||||
@@ -13144,6 +13213,7 @@ static uint32_t ggml_vk_rms_partials_size(ggml_backend_vk_context * ctx, const g
|
||||
static vk_op_rope_push_constants ggml_vk_make_rope_constants(const ggml_tensor *dst, const ggml_tensor *src0, const bool has_ff, bool backprop, const uint32_t set_rows_stride) {
|
||||
const int n_dims = ((const int32_t *) dst->op_params)[1];
|
||||
const int mode = ((const int32_t *) dst->op_params)[2];
|
||||
const int n_offs = ((const int32_t *) dst->op_params)[15];
|
||||
// const int n_ctx = ((const int32_t *) dst->op_params)[3];
|
||||
const int n_ctx_orig = ((const int32_t *) dst->op_params)[4];
|
||||
const float freq_base = ((const float *) dst->op_params)[5];
|
||||
@@ -13173,7 +13243,7 @@ static vk_op_rope_push_constants ggml_vk_make_rope_constants(const ggml_tensor *
|
||||
uint32_t nb13 = dst->nb[3] / ggml_type_size(dst->type);
|
||||
|
||||
vk_op_rope_push_constants rope {
|
||||
(uint32_t)mode, (uint32_t)ggml_nrows(src0), (uint32_t)n_dims, freq_scale,
|
||||
(uint32_t)mode, (uint32_t)ggml_nrows(src0), (uint32_t)n_dims, (uint32_t)n_offs, freq_scale,
|
||||
freq_base, ext_factor, attn_factor, {corr_dims[0], corr_dims[1]}, theta_scale, has_ff,
|
||||
{ sections[0], sections[1], sections[2], sections[3] }, is_imrope, backprop, set_rows_stride,
|
||||
|
||||
@@ -19219,6 +19289,10 @@ static void ggml_vk_check_results_0(ggml_backend_vk_context * ctx, ggml_cgraph *
|
||||
tensor_clone = ggml_rope_ext_back(ggml_ctx, src_clone[0], src_clone[1], src_clone[2], n_dims, mode, n_ctx_orig_ggml, freq_base, freq_scale, ext_factor, attn_factor, beta_fast, beta_slow);
|
||||
}
|
||||
}
|
||||
const int n_offs = ((int32_t *) tensor->op_params)[15];
|
||||
if (n_offs != 0) {
|
||||
tensor_clone = ggml_rope_set_offset(tensor_clone, n_offs);
|
||||
}
|
||||
} else if (tensor->op == GGML_OP_UNARY) {
|
||||
switch (ggml_get_unary_op(tensor)) {
|
||||
case GGML_UNARY_OP_EXP:
|
||||
|
||||
@@ -18,7 +18,18 @@ void main() {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef DEQUANT_TRANSPOSE
|
||||
// read [HS, NH, KV, NS], write [HS, KV, NH, NS]
|
||||
const uint HS = p.M, NH = p.K, KVn = p.stride_a;
|
||||
const uint e0 = ib * 32;
|
||||
const uint b_idx = (e0 % HS)
|
||||
+ ((e0 / (HS * NH)) % KVn) * HS
|
||||
+ ((e0 / HS) % NH) * (HS * KVn)
|
||||
+ (e0 / (HS * NH * KVn)) * (HS * KVn * NH)
|
||||
+ 16 * il;
|
||||
#else
|
||||
const uint b_idx = 1024*i + 32*ir + 16*il;
|
||||
#endif
|
||||
|
||||
const float d = float(data_a[ib].d);
|
||||
|
||||
|
||||
@@ -121,13 +121,13 @@ void main() {
|
||||
const uint buf_ib = r * qf_stride + d / 8;
|
||||
const uint buf_iqs = d % 8;
|
||||
|
||||
FLOAT_TYPEV4 vals = is_in_bounds ? FLOAT_TYPEV4(data_qv4[q_offset / 4 + (i * Br + r) * q_stride / 4 + d] * p.scale) : FLOAT_TYPEV4(0.0f);
|
||||
const FLOAT_TYPEV4 abs_vals = abs(vals);
|
||||
vec4 vals = is_in_bounds ? data_qv4[q_offset / 4 + (i * Br + r) * q_stride / 4 + d] * p.scale : vec4(0.0f);
|
||||
const vec4 abs_vals = abs(vals);
|
||||
|
||||
const FLOAT_TYPE thread_max = max(max(abs_vals.x, abs_vals.y), max(abs_vals.z, abs_vals.w));
|
||||
const FLOAT_TYPE amax = subgroupClusteredMax(thread_max, 8);
|
||||
const FLOAT_TYPE qd = amax / FLOAT_TYPE(127.0);
|
||||
const FLOAT_TYPE qd_inv = qd != FLOAT_TYPE(0.0) ? FLOAT_TYPE(1.0) / qd : FLOAT_TYPE(0.0);
|
||||
const float thread_max = max(max(abs_vals.x, abs_vals.y), max(abs_vals.z, abs_vals.w));
|
||||
const float amax = subgroupClusteredMax(thread_max, 8);
|
||||
const float qd = amax / 127.0f;
|
||||
const float qd_inv = qd != 0.0f ? 1.0f / qd : 0.0f;
|
||||
vals = round(vals * qd_inv);
|
||||
|
||||
Qf[buf_ib].qs[buf_iqs] = pack32(i8vec4(vals));
|
||||
@@ -136,11 +136,11 @@ void main() {
|
||||
// the row-sum scaled by qd, used in k_dot_correction.
|
||||
if (FaTypeK == FA_TYPE_Q8_0) {
|
||||
if (buf_iqs == 0) {
|
||||
Qf[buf_ib].ds = FLOAT_TYPEV2(qd, 0.0);
|
||||
Qf[buf_ib].ds = FLOAT_TYPEV2(qd, 0.0f);
|
||||
}
|
||||
} else {
|
||||
const FLOAT_TYPE thread_sum = vals.x + vals.y + vals.z + vals.w;
|
||||
const FLOAT_TYPE sum = subgroupClusteredAdd(thread_sum, 8);
|
||||
const float thread_sum = vals.x + vals.y + vals.z + vals.w;
|
||||
const float sum = subgroupClusteredAdd(thread_sum, 8);
|
||||
|
||||
if (buf_iqs == 0) {
|
||||
Qf[buf_ib].ds = FLOAT_TYPEV2(qd, sum * qd);
|
||||
|
||||
@@ -50,19 +50,21 @@ void rope_norm(const uint i0, const uint i1, const uint i2, const uint i3, rope_
|
||||
}
|
||||
idst += p.d_offset;
|
||||
|
||||
if (i0 >= p.n_dims) {
|
||||
if (i0 < p.n_offs || i0 >= p.n_offs + p.n_dims) {
|
||||
rope_data_d[idst + 0] = ROPE_D_TYPE(rope_data_a[ix + 0]);
|
||||
rope_data_d[idst + 1] = ROPE_D_TYPE(rope_data_a[ix + 1]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const float theta_base = rope_data_pos[i2] * pow(p.theta_scale, i0/2.0f);
|
||||
const uint iw = i0 - p.n_offs; // relative idx
|
||||
|
||||
const float freq_factor = p.has_ff != 0 ? rope_data_ff[i0/2] : 1.0f;
|
||||
const float theta_base = rope_data_pos[i2] * pow(p.theta_scale, iw/2.0f);
|
||||
|
||||
const float freq_factor = p.has_ff != 0 ? rope_data_ff[iw/2] : 1.0f;
|
||||
|
||||
float cos_theta, sin_theta;
|
||||
rope_yarn(theta_base / freq_factor, i0, cos_theta, sin_theta, p);
|
||||
rope_yarn(theta_base / freq_factor, iw, cos_theta, sin_theta, p);
|
||||
|
||||
const float x0 = float(rope_data_a[ix + 0]);
|
||||
const float x1 = float(rope_data_a[ix + 1]);
|
||||
@@ -87,25 +89,28 @@ void rope_neox(const uint i0, const uint i1, const uint i2, const uint i3, rope_
|
||||
}
|
||||
idst += p.d_offset;
|
||||
|
||||
if (i0 >= p.n_dims) {
|
||||
if (i0 < p.n_offs || i0 >= p.n_offs + p.n_dims) {
|
||||
rope_data_d[idst + i0/2 + 0] = ROPE_D_TYPE(rope_data_a[ix + i0/2 + 0]);
|
||||
rope_data_d[idst + i0/2 + 1] = ROPE_D_TYPE(rope_data_a[ix + i0/2 + 1]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const float theta_base = rope_data_pos[i2] * pow(p.theta_scale, i0/2.0f);
|
||||
const uint iw = i0 - p.n_offs; // relative idx
|
||||
|
||||
const float freq_factor = p.has_ff != 0 ? rope_data_ff[i0/2] : 1.0f;
|
||||
const float theta_base = rope_data_pos[i2] * pow(p.theta_scale, iw/2.0f);
|
||||
|
||||
const float freq_factor = p.has_ff != 0 ? rope_data_ff[iw/2] : 1.0f;
|
||||
|
||||
float cos_theta, sin_theta;
|
||||
rope_yarn(theta_base / freq_factor, i0, cos_theta, sin_theta, p);
|
||||
rope_yarn(theta_base / freq_factor, iw, cos_theta, sin_theta, p);
|
||||
|
||||
const float x0 = float(rope_data_a[ix + 0]);
|
||||
const float x1 = float(rope_data_a[ix + p.n_dims/2]);
|
||||
// idst/ix point at channel i0/2; the first channel of the rotated pair is p.n_offs + iw/2 = i0/2 + p.n_offs/2
|
||||
const float x0 = float(rope_data_a[ix + p.n_offs/2 + 0]);
|
||||
const float x1 = float(rope_data_a[ix + p.n_offs/2 + p.n_dims/2]);
|
||||
|
||||
rope_data_d[idst + 0] = ROPE_D_TYPE(x0*cos_theta - x1*sin_theta);
|
||||
rope_data_d[idst + p.n_dims/2] = ROPE_D_TYPE(x0*sin_theta + x1*cos_theta);
|
||||
rope_data_d[idst + p.n_offs/2 + 0] = ROPE_D_TYPE(x0*cos_theta - x1*sin_theta);
|
||||
rope_data_d[idst + p.n_offs/2 + p.n_dims/2] = ROPE_D_TYPE(x0*sin_theta + x1*cos_theta);
|
||||
}
|
||||
|
||||
|
||||
@@ -125,53 +130,56 @@ void rope_multi(const uint i0, const uint i1, const uint i2, const uint i3, rope
|
||||
}
|
||||
idst += p.d_offset;
|
||||
|
||||
if (i0 >= p.n_dims) {
|
||||
if (i0 < p.n_offs || i0 >= p.n_offs + p.n_dims) {
|
||||
rope_data_d[idst + i0/2 + 0] = ROPE_D_TYPE(rope_data_a[ix + i0/2 + 0]);
|
||||
rope_data_d[idst + i0/2 + 1] = ROPE_D_TYPE(rope_data_a[ix + i0/2 + 1]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const uint iw = i0 - p.n_offs; // relative idx
|
||||
|
||||
const int sect_dims = p.sections[0] + p.sections[1] + p.sections[2] + p.sections[3];
|
||||
const int sec_w = p.sections[1] + p.sections[0];
|
||||
const uint sector = (i0 / 2) % sect_dims;
|
||||
const uint sector = (iw / 2) % sect_dims;
|
||||
|
||||
float theta_base = 0.0;
|
||||
if (p.is_imrope != 0) {
|
||||
if (sector % 3 == 1 && sector < 3 * p.sections[1]) {
|
||||
theta_base = rope_data_pos[i2 + p.ne02 * 1]*pow(p.theta_scale, i0/2.0f);
|
||||
theta_base = rope_data_pos[i2 + p.ne02 * 1]*pow(p.theta_scale, iw/2.0f);
|
||||
} else if (sector % 3 == 2 && sector < 3 * p.sections[2]) {
|
||||
theta_base = rope_data_pos[i2 + p.ne02 * 2]*pow(p.theta_scale, i0/2.0f);
|
||||
theta_base = rope_data_pos[i2 + p.ne02 * 2]*pow(p.theta_scale, iw/2.0f);
|
||||
} else if (sector % 3 == 0 && sector < 3 * p.sections[0]) {
|
||||
theta_base = rope_data_pos[i2]*pow(p.theta_scale, i0/2.0f);
|
||||
theta_base = rope_data_pos[i2]*pow(p.theta_scale, iw/2.0f);
|
||||
} else {
|
||||
theta_base = rope_data_pos[i2 + p.ne02 * 3]*pow(p.theta_scale, i0/2.0f);
|
||||
theta_base = rope_data_pos[i2 + p.ne02 * 3]*pow(p.theta_scale, iw/2.0f);
|
||||
}
|
||||
} else {
|
||||
if (sector < p.sections[0]) {
|
||||
theta_base = rope_data_pos[i2]*pow(p.theta_scale, i0/2.0f);
|
||||
theta_base = rope_data_pos[i2]*pow(p.theta_scale, iw/2.0f);
|
||||
}
|
||||
else if (sector >= p.sections[0] && sector < sec_w) {
|
||||
theta_base = rope_data_pos[i2 + p.ne02 * 1]*pow(p.theta_scale, i0/2.0f);
|
||||
theta_base = rope_data_pos[i2 + p.ne02 * 1]*pow(p.theta_scale, iw/2.0f);
|
||||
}
|
||||
else if (sector >= sec_w && sector < sec_w + p.sections[2]) {
|
||||
theta_base = rope_data_pos[i2 + p.ne02 * 2]*pow(p.theta_scale, i0/2.0f);
|
||||
theta_base = rope_data_pos[i2 + p.ne02 * 2]*pow(p.theta_scale, iw/2.0f);
|
||||
}
|
||||
else if (sector >= sec_w + p.sections[2]) {
|
||||
theta_base = rope_data_pos[i2 + p.ne02 * 3]*pow(p.theta_scale, i0/2.0f);
|
||||
theta_base = rope_data_pos[i2 + p.ne02 * 3]*pow(p.theta_scale, iw/2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
const float freq_factor = p.has_ff != 0 ? rope_data_ff[i0/2] : 1.0f;
|
||||
const float freq_factor = p.has_ff != 0 ? rope_data_ff[iw/2] : 1.0f;
|
||||
|
||||
float cos_theta, sin_theta;
|
||||
rope_yarn(theta_base / freq_factor, i0, cos_theta, sin_theta, p);
|
||||
rope_yarn(theta_base / freq_factor, iw, cos_theta, sin_theta, p);
|
||||
|
||||
const float x0 = float(rope_data_a[ix + 0]);
|
||||
const float x1 = float(rope_data_a[ix + p.n_dims/2]);
|
||||
// idst/ix point at channel i0/2; the first channel of the rotated pair is p.n_offs + iw/2 = i0/2 + p.n_offs/2
|
||||
const float x0 = float(rope_data_a[ix + p.n_offs/2 + 0]);
|
||||
const float x1 = float(rope_data_a[ix + p.n_offs/2 + p.n_dims/2]);
|
||||
|
||||
rope_data_d[idst + 0] = ROPE_D_TYPE(x0*cos_theta - x1*sin_theta);
|
||||
rope_data_d[idst + p.n_dims/2] = ROPE_D_TYPE(x0*sin_theta + x1*cos_theta);
|
||||
rope_data_d[idst + p.n_offs/2 + 0] = ROPE_D_TYPE(x0*cos_theta - x1*sin_theta);
|
||||
rope_data_d[idst + p.n_offs/2 + p.n_dims/2] = ROPE_D_TYPE(x0*sin_theta + x1*cos_theta);
|
||||
}
|
||||
|
||||
void rope_vision(const uint i0, const uint i1, const uint i2, const uint i3, rope_params p) {
|
||||
|
||||
@@ -5,6 +5,7 @@ struct rope_params {
|
||||
uint rope_mode;
|
||||
uint nrows;
|
||||
uint n_dims;
|
||||
uint n_offs;
|
||||
float freq_scale;
|
||||
float freq_base;
|
||||
float ext_factor;
|
||||
|
||||
@@ -780,6 +780,10 @@ void process_shaders() {
|
||||
if (tname != "f16" && tname != "bf16") {
|
||||
string_to_spv("dequant_" + tname, "dequant_" + tname + ".comp", merge_maps(base_dict, {{data_a_key, "1"}, {"D_TYPE", "float16_t"}}));
|
||||
}
|
||||
// Fused dequant+transpose variant for FA quant-KV (per-head-contiguous f16 scratch).
|
||||
if (tname == "q8_0") {
|
||||
string_to_spv("dequant_" + tname + "_transpose", "dequant_" + tname + ".comp", merge_maps(base_dict, {{data_a_key, "1"}, {"D_TYPE", "float16_t"}, {"DEQUANT_TRANSPOSE", "1"}}));
|
||||
}
|
||||
|
||||
shader = (tname == "f32" || tname == "f16" || tname == "bf16") ? "get_rows.comp" : "get_rows_quant.comp";
|
||||
|
||||
|
||||
@@ -4472,7 +4472,9 @@ static bool ggml_backend_webgpu_device_supports_op(ggml_backend_dev_t dev, const
|
||||
supports_op = (op->type == GGML_TYPE_F32 && src0->type == GGML_TYPE_F32) && ggml_is_contiguous_rows(src0);
|
||||
break;
|
||||
case GGML_OP_ROPE:
|
||||
supports_op = op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16;
|
||||
// FIXME: support ggml_rope_set_offset
|
||||
supports_op =
|
||||
(op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && ((const int32_t *) op->op_params)[15] == 0;
|
||||
break;
|
||||
case GGML_OP_GLU:
|
||||
switch (ggml_get_glu_op(op)) {
|
||||
|
||||
@@ -86,6 +86,6 @@ endif()
|
||||
|
||||
target_link_libraries(ggml-zendnn PRIVATE m pthread)
|
||||
|
||||
if (GGML_OPENMP)
|
||||
target_link_libraries(ggml-zendnn PRIVATE OpenMP::OpenMP_CXX)
|
||||
if (GGML_OPENMP_ENABLED)
|
||||
target_link_libraries(ggml-zendnn PRIVATE ${GGML_OPENMP_TARGET_CXX})
|
||||
endif()
|
||||
|
||||
+17
-1
@@ -4200,7 +4200,7 @@ static struct ggml_tensor * ggml_rope_impl(
|
||||
|
||||
struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a);
|
||||
|
||||
int32_t params[15] = { /*n_past*/ 0, n_dims, mode, /*n_ctx*/ 0, n_ctx_orig };
|
||||
int32_t params[16] = { /*n_past*/ 0, n_dims, mode, /*n_ctx*/ 0, n_ctx_orig };
|
||||
memcpy(params + 5, &freq_base, sizeof(float));
|
||||
memcpy(params + 6, &freq_scale, sizeof(float));
|
||||
memcpy(params + 7, &ext_factor, sizeof(float));
|
||||
@@ -4212,6 +4212,8 @@ static struct ggml_tensor * ggml_rope_impl(
|
||||
} else {
|
||||
memset(params + 11, 0, sizeof(int32_t) * GGML_MROPE_SECTIONS);
|
||||
}
|
||||
params[15] = 0; // n_offs, set via ggml_rope_set_offset()
|
||||
|
||||
ggml_set_op_params(result, params, sizeof(params));
|
||||
|
||||
result->op = GGML_OP_ROPE;
|
||||
@@ -4422,6 +4424,20 @@ struct ggml_tensor * ggml_rope_multi_back(
|
||||
result->op = GGML_OP_ROPE_BACK;
|
||||
return result;
|
||||
}
|
||||
|
||||
struct ggml_tensor * ggml_rope_set_offset(
|
||||
struct ggml_tensor * a,
|
||||
int n_offs) {
|
||||
GGML_ASSERT(a->op == GGML_OP_ROPE || a->op == GGML_OP_ROPE_BACK);
|
||||
GGML_ASSERT(n_offs >= 0);
|
||||
|
||||
const int32_t mode = ggml_get_op_params_i32(a, 2);
|
||||
GGML_ASSERT(mode != GGML_ROPE_TYPE_VISION);
|
||||
|
||||
ggml_set_op_params_i32(a, 15, n_offs);
|
||||
return a;
|
||||
}
|
||||
|
||||
// ggml_clamp
|
||||
|
||||
struct ggml_tensor * ggml_clamp(
|
||||
|
||||
@@ -208,6 +208,7 @@ class Keys:
|
||||
SHARED_KV_LAYERS = "{arch}.attention.shared_kv_layers"
|
||||
SLIDING_WINDOW_PATTERN = "{arch}.attention.sliding_window_pattern"
|
||||
TEMPERATURE_SCALE = "{arch}.attention.temperature_scale"
|
||||
ROPE_PATTERN = "{arch}.attention.rope_pattern"
|
||||
|
||||
class Indexer:
|
||||
HEAD_COUNT = "{arch}.attention.indexer.head_count"
|
||||
@@ -549,6 +550,7 @@ class MODEL_ARCH(IntEnum):
|
||||
GRANITE_MOE = auto()
|
||||
GRANITE_HYBRID = auto()
|
||||
GRANITE_SWITCH = auto()
|
||||
GRANITE_SWA = auto()
|
||||
CHAMELEON = auto()
|
||||
WAVTOKENIZER_DEC = auto()
|
||||
PLM = auto()
|
||||
@@ -1265,6 +1267,7 @@ MODEL_ARCH_NAMES: dict[MODEL_ARCH, str] = {
|
||||
MODEL_ARCH.GRANITE_MOE: "granitemoe",
|
||||
MODEL_ARCH.GRANITE_HYBRID: "granitehybrid",
|
||||
MODEL_ARCH.GRANITE_SWITCH: "graniteswitch",
|
||||
MODEL_ARCH.GRANITE_SWA: "granite_swa",
|
||||
MODEL_ARCH.CHAMELEON: "chameleon",
|
||||
MODEL_ARCH.WAVTOKENIZER_DEC: "wavtokenizer-dec",
|
||||
MODEL_ARCH.PLM: "plm",
|
||||
@@ -4152,6 +4155,31 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
|
||||
MODEL_TENSOR.FFN_DOWN,
|
||||
MODEL_TENSOR.FFN_UP,
|
||||
],
|
||||
MODEL_ARCH.GRANITE_SWA: [
|
||||
MODEL_TENSOR.TOKEN_EMBD,
|
||||
MODEL_TENSOR.OUTPUT_NORM,
|
||||
MODEL_TENSOR.OUTPUT,
|
||||
MODEL_TENSOR.ATTN_NORM,
|
||||
MODEL_TENSOR.ATTN_Q,
|
||||
MODEL_TENSOR.ATTN_K,
|
||||
MODEL_TENSOR.ATTN_V,
|
||||
MODEL_TENSOR.ATTN_OUT,
|
||||
MODEL_TENSOR.ATTN_SINKS,
|
||||
MODEL_TENSOR.ROPE_FREQS,
|
||||
MODEL_TENSOR.FFN_NORM,
|
||||
MODEL_TENSOR.FFN_GATE,
|
||||
MODEL_TENSOR.FFN_DOWN,
|
||||
MODEL_TENSOR.FFN_UP,
|
||||
# MoE (GraniteMoeSWA)
|
||||
MODEL_TENSOR.FFN_GATE_INP,
|
||||
MODEL_TENSOR.FFN_GATE_EXP,
|
||||
MODEL_TENSOR.FFN_GATE_UP_EXP,
|
||||
MODEL_TENSOR.FFN_DOWN_EXP,
|
||||
MODEL_TENSOR.FFN_UP_EXP,
|
||||
# Shared expert - gate+up kept fused in FFN_UP_SHEXP (LLM_FFN_SWIGLU)
|
||||
MODEL_TENSOR.FFN_UP_SHEXP,
|
||||
MODEL_TENSOR.FFN_DOWN_SHEXP,
|
||||
],
|
||||
MODEL_ARCH.CHAMELEON: [
|
||||
MODEL_TENSOR.TOKEN_EMBD,
|
||||
MODEL_TENSOR.OUTPUT_NORM,
|
||||
|
||||
@@ -824,6 +824,9 @@ class GGUFWriter:
|
||||
else:
|
||||
self.add_array(key, value)
|
||||
|
||||
def add_rope_pattern(self, value: Sequence[bool]) -> None:
|
||||
self.add_array(Keys.Attention.ROPE_PATTERN.format(arch=self.arch), value)
|
||||
|
||||
def add_dense_features_dims(self, dense:str, in_f:int, out_f:int) -> None:
|
||||
self.add_uint32(Keys.LLM.DENSE_FEAT_IN_SIZE.format(arch=self.arch, dense=dense), in_f)
|
||||
self.add_uint32(Keys.LLM.DENSE_FEAT_OUT_SIZE.format(arch=self.arch, dense=dense), out_f)
|
||||
|
||||
@@ -458,6 +458,7 @@ class TensorNameMap:
|
||||
"transformer.decoder_layer.{bid}.router", # Grok
|
||||
"transformer.blocks.{bid}.ffn.router.layer", # dbrx
|
||||
"model.layers.{bid}.block_sparse_moe.router.layer", # granitemoe
|
||||
"model.layers.{bid}.block_sparse_moe.router", # granite_swa
|
||||
"model.layers.{bid}.feed_forward.router", # llama4 jamba
|
||||
"encoder.layers.{bid}.mlp.router.layer", # nomic-bert-moe
|
||||
"model.layers.{bid}.mlp.router", # openai-moe
|
||||
|
||||
@@ -102,6 +102,7 @@ static const std::map<llm_arch, const char *> LLM_ARCH_NAMES = {
|
||||
{ LLM_ARCH_GRANITE_MOE, "granitemoe" },
|
||||
{ LLM_ARCH_GRANITE_HYBRID, "granitehybrid" },
|
||||
{ LLM_ARCH_GRANITE_SWITCH, "graniteswitch" },
|
||||
{ LLM_ARCH_GRANITE_SWA, "granite_swa" },
|
||||
{ LLM_ARCH_CHAMELEON, "chameleon" },
|
||||
{ LLM_ARCH_WAVTOKENIZER_DEC, "wavtokenizer-dec" },
|
||||
{ LLM_ARCH_PLM, "plm" },
|
||||
@@ -261,6 +262,8 @@ static const std::map<llm_kv, const char *> LLM_KV_NAMES = {
|
||||
{ LLM_KV_ATTENTION_RELATIVE_BUCKETS_COUNT, "%s.attention.relative_buckets_count" },
|
||||
{ LLM_KV_ATTENTION_SLIDING_WINDOW, "%s.attention.sliding_window" },
|
||||
{ LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, "%s.attention.sliding_window_pattern" },
|
||||
{ LLM_KV_ATTENTION_ROPE_PATTERN, "%s.attention.rope_pattern" },
|
||||
|
||||
{ LLM_KV_ATTENTION_SCALE, "%s.attention.scale" },
|
||||
{ LLM_KV_ATTENTION_OUTPUT_SCALE, "%s.attention.output_scale" },
|
||||
{ LLM_KV_ATTENTION_VALUE_SCALE, "%s.attention.value_scale" },
|
||||
@@ -1029,6 +1032,8 @@ bool llm_arch_supports_rs_rollback(const llm_arch & arch) {
|
||||
case LLM_ARCH_DEEPSEEK4:
|
||||
case LLM_ARCH_NEMOTRON_H:
|
||||
case LLM_ARCH_NEMOTRON_H_MOE:
|
||||
case LLM_ARCH_LFM2:
|
||||
case LLM_ARCH_LFM2MOE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -107,6 +107,7 @@ enum llm_arch {
|
||||
LLM_ARCH_GRANITE_MOE,
|
||||
LLM_ARCH_GRANITE_HYBRID,
|
||||
LLM_ARCH_GRANITE_SWITCH,
|
||||
LLM_ARCH_GRANITE_SWA,
|
||||
LLM_ARCH_CHAMELEON,
|
||||
LLM_ARCH_WAVTOKENIZER_DEC,
|
||||
LLM_ARCH_PLM,
|
||||
@@ -267,6 +268,8 @@ enum llm_kv {
|
||||
LLM_KV_ATTENTION_SLIDING_WINDOW,
|
||||
LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN,
|
||||
LLM_KV_ATTENTION_SCALE,
|
||||
LLM_KV_ATTENTION_ROPE_PATTERN,
|
||||
|
||||
LLM_KV_ATTENTION_OUTPUT_SCALE,
|
||||
LLM_KV_ATTENTION_VALUE_SCALE,
|
||||
LLM_KV_ATTENTION_TEMPERATURE_LENGTH,
|
||||
|
||||
+1
-3
@@ -3099,8 +3099,6 @@ ggml_tensor * llm_graph_context::build_attn(
|
||||
int il) const {
|
||||
const bool is_swa = hparams.is_swa(il);
|
||||
|
||||
GGML_UNUSED(v_cur);
|
||||
|
||||
auto * k_rot = is_swa ? inp->self_k_rot_swa : inp->self_k_rot;
|
||||
|
||||
if (k_rot) {
|
||||
@@ -3133,7 +3131,7 @@ ggml_tensor * llm_graph_context::build_attn(
|
||||
// MLA-style attention: the cached K is used as V
|
||||
ggml_tensor * q = q_cur;
|
||||
ggml_tensor * k = mctx_cur->get_k(ctx0, il);
|
||||
ggml_tensor * v = k;
|
||||
ggml_tensor * v = ggml_view_4d(ctx0, k, v_cur->ne[0], k->ne[1], k->ne[2], k->ne[3], k->nb[1], k->nb[2], k->nb[3], 0);
|
||||
|
||||
ggml_tensor * cur = build_attn_mha(q, k, v, kq_b, kq_mask, sinks, v_mla, kq_scale, il);
|
||||
cb(cur, "kqv_out", il);
|
||||
|
||||
@@ -291,7 +291,11 @@ bool llama_hparams::has_rope(uint32_t il) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (il < n_layer_all) {
|
||||
return rope_pattern[il] != 0;
|
||||
}
|
||||
|
||||
GGML_ABORT("%s: il (%u) out of bounds (n_layer_all: %u)\n", __func__, il, n_layer_all);
|
||||
}
|
||||
|
||||
uint32_t llama_hparams::n_layer() const {
|
||||
|
||||
@@ -144,6 +144,10 @@ struct llama_hparams {
|
||||
|
||||
std::array<int, 4> rope_sections;
|
||||
|
||||
// Per-layer RoPE enable flags (1 = use RoPE, 0 = NoPE)
|
||||
// by default, all layers use RoPE (controlled by rope_finetuned)
|
||||
std::array<uint32_t, LLAMA_MAX_LAYERS> rope_pattern;
|
||||
|
||||
// Sliding Window Attention (SWA)
|
||||
llama_swa_type swa_type = LLAMA_SWA_TYPE_NONE;
|
||||
// the size of the sliding window (0 - no SWA)
|
||||
|
||||
@@ -30,6 +30,7 @@ bool llama_model_saver_supports_arch(llm_arch arch) {
|
||||
case LLM_ARCH_MUSE_GLIMMER:
|
||||
case LLM_ARCH_MELLUM:
|
||||
case LLM_ARCH_LAGUNA:
|
||||
case LLM_ARCH_GRANITE_SWA:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
@@ -272,6 +273,7 @@ void llama_model_saver::add_kv_from_model() {
|
||||
add_kv(LLM_KV_ATTENTION_VALUE_RESIDUAL_MIX_LORA_RANK, hparams.n_lora_value_res_mix);
|
||||
add_kv(LLM_KV_ATTENTION_GATE_LORA_RANK, hparams.n_lora_gate);
|
||||
add_kv(LLM_KV_ATTENTION_RELATIVE_BUCKETS_COUNT, hparams.n_rel_attn_bkts);
|
||||
add_kv(LLM_KV_ATTENTION_ROPE_PATTERN, hparams.rope_pattern, true);
|
||||
add_kv(LLM_KV_ATTENTION_SLIDING_WINDOW, hparams.n_swa);
|
||||
// add_kv(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, ???);
|
||||
add_kv(LLM_KV_ATTENTION_SCALE, hparams.f_attention_scale);
|
||||
|
||||
@@ -246,6 +246,8 @@ static llama_model * llama_model_mapping(llm_arch arch, const llama_model_params
|
||||
return new llama_model_minicpm(params);
|
||||
case LLM_ARCH_GRANITE_HYBRID:
|
||||
return new llama_model_granite_hybrid(params);
|
||||
case LLM_ARCH_GRANITE_SWA:
|
||||
return new llama_model_granite_swa(params);
|
||||
case LLM_ARCH_CHAMELEON:
|
||||
return new llama_model_chameleon(params);
|
||||
case LLM_ARCH_WAVTOKENIZER_DEC:
|
||||
@@ -1157,6 +1159,7 @@ void llama_model_base::load_hparams(llama_model_loader & ml) {
|
||||
std::fill(hparams.n_ff_arr.begin(), hparams.n_ff_arr.end(), 0);
|
||||
|
||||
std::fill(hparams.rope_sections.begin(), hparams.rope_sections.end(), 0);
|
||||
std::fill(hparams.rope_pattern.begin(), hparams.rope_pattern.end(), 1);
|
||||
std::fill(hparams.is_swa_impl.begin(), hparams.is_swa_impl.end(), 0);
|
||||
std::fill(hparams.is_recr_impl.begin(), hparams.is_recr_impl.end(), llm_arch_is_recurrent(ml.get_arch()) ? 1 : 0);
|
||||
std::fill(hparams.is_indexer_full_impl.begin(), hparams.is_indexer_full_impl.end(), 0);
|
||||
@@ -2639,6 +2642,7 @@ llama_rope_type llama_model_rope_type(const llama_model * model) {
|
||||
case LLM_ARCH_GRANITE_MOE:
|
||||
case LLM_ARCH_GRANITE_HYBRID:
|
||||
case LLM_ARCH_GRANITE_SWITCH:
|
||||
case LLM_ARCH_GRANITE_SWA:
|
||||
case LLM_ARCH_CHAMELEON:
|
||||
case LLM_ARCH_BAILINGMOE:
|
||||
case LLM_ARCH_BAILINGMOE3:
|
||||
|
||||
@@ -10,8 +10,6 @@ void llama_model_deepseek32::load_arch_hparams(llama_model_loader & ml) {
|
||||
ml.get_key_or_arr(LLM_KV_ROPE_DIMENSION_SECTIONS, hparams.rope_sections, 4, false);
|
||||
|
||||
// MoE parameters
|
||||
ml.get_key(LLM_KV_EXPERT_COUNT, hparams.n_expert);
|
||||
ml.get_key(LLM_KV_EXPERT_USED_COUNT, hparams.n_expert_used);
|
||||
ml.get_key(LLM_KV_EXPERT_SHARED_COUNT, hparams.n_expert_shared);
|
||||
ml.get_key(LLM_KV_LEADING_DENSE_BLOCK_COUNT, hparams.n_layer_dense_lead, false);
|
||||
ml.get_key(LLM_KV_EXPERT_WEIGHTS_SCALE, hparams.expert_weights_scale, false);
|
||||
|
||||
@@ -1225,7 +1225,7 @@ ggml_tensor * llama_model_deepseek4::graph::build_attention_impl(
|
||||
if (inp_mtp) {
|
||||
out = build_attn(inp_mtp,
|
||||
nullptr, nullptr, nullptr,
|
||||
q, kv, nullptr,
|
||||
q, kv, kv,
|
||||
nullptr, layer.attn_sinks, nullptr,
|
||||
1.0f/sqrtf(float(n_embd_head)), il);
|
||||
cb(out, "attn_raw", il);
|
||||
|
||||
@@ -32,8 +32,6 @@ void llama_model_glm_dsa::load_arch_hparams(llama_model_loader & ml) {
|
||||
ml.get_key_or_arr(LLM_KV_ROPE_DIMENSION_SECTIONS, hparams.rope_sections, 4, false);
|
||||
|
||||
// MoE parameters
|
||||
ml.get_key(LLM_KV_EXPERT_COUNT, hparams.n_expert);
|
||||
ml.get_key(LLM_KV_EXPERT_USED_COUNT, hparams.n_expert_used);
|
||||
ml.get_key(LLM_KV_EXPERT_SHARED_COUNT, hparams.n_expert_shared);
|
||||
ml.get_key(LLM_KV_LEADING_DENSE_BLOCK_COUNT, hparams.n_layer_dense_lead, false);
|
||||
ml.get_key(LLM_KV_EXPERT_WEIGHTS_SCALE, hparams.expert_weights_scale, false);
|
||||
|
||||
@@ -6,8 +6,6 @@ void llama_model_glm4_moe::load_arch_hparams(llama_model_loader & ml) {
|
||||
ml.get_key_or_arr(LLM_KV_ROPE_DIMENSION_SECTIONS, hparams.rope_sections, 4, false);
|
||||
|
||||
// MoE parameters
|
||||
ml.get_key(LLM_KV_EXPERT_COUNT, hparams.n_expert);
|
||||
ml.get_key(LLM_KV_EXPERT_USED_COUNT, hparams.n_expert_used);
|
||||
ml.get_key(LLM_KV_EXPERT_SHARED_COUNT, hparams.n_expert_shared);
|
||||
ml.get_key(LLM_KV_LEADING_DENSE_BLOCK_COUNT, hparams.n_layer_dense_lead, false);
|
||||
ml.get_key(LLM_KV_EXPERT_WEIGHTS_SCALE, hparams.expert_weights_scale, false);
|
||||
|
||||
@@ -16,7 +16,8 @@ void llama_model_granite_hybrid::load_arch_hparams(llama_model_loader & ml) {
|
||||
// Granite uses rope_finetuned as a switch for rope, so default to true
|
||||
bool rope_finetuned = true;
|
||||
ml.get_key(LLM_KV_ROPE_SCALING_FINETUNED, rope_finetuned, false);
|
||||
hparams.rope_finetuned = rope_finetuned;
|
||||
hparams.rope_finetuned = rope_finetuned; // needed for round trip save
|
||||
std::fill(hparams.rope_pattern.begin(), hparams.rope_pattern.end(), rope_finetuned);
|
||||
|
||||
// A layer is recurrent IFF the n_head_kv value is set to 0
|
||||
for (uint32_t i = 0; i < hparams.n_layer(); ++i) {
|
||||
@@ -147,7 +148,7 @@ llama_model_granite_hybrid::graph::graph(const llama_model & model, const llm_gr
|
||||
|
||||
// Positional embeddings populated if rope enabled
|
||||
ggml_tensor * inp_pos = nullptr;
|
||||
if (hparams.rope_finetuned) {
|
||||
if (hparams.has_rope(0)) {
|
||||
inp_pos = build_inp_pos();
|
||||
}
|
||||
|
||||
@@ -206,8 +207,7 @@ ggml_tensor * llama_model_granite_hybrid::graph::build_attention_layer(ggml_tens
|
||||
const int il) {
|
||||
auto [Qcur, Kcur, Vcur] = build_qkv(model.layers[il], cur, n_embd_head, hparams.n_head(il), hparams.n_head_kv(il), il);
|
||||
|
||||
const bool use_rope = hparams.rope_finetuned;
|
||||
if (use_rope) {
|
||||
if (hparams.has_rope(il)) {
|
||||
ggml_tensor * rope_factors = model.get_rope_factors(cparams, il);
|
||||
Qcur = ggml_rope_ext(ctx0, Qcur, inp_pos, rope_factors, n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
|
||||
ext_factor, attn_factor, beta_fast, beta_slow);
|
||||
|
||||
@@ -7,11 +7,6 @@ void llama_model_granite_moe::load_arch_hparams(llama_model_loader & ml) {
|
||||
ml.get_key(LLM_KV_EMBEDDING_SCALE, hparams.f_embedding_scale, false);
|
||||
ml.get_key(LLM_KV_ATTENTION_SCALE, hparams.f_attention_scale, false);
|
||||
|
||||
// Granite uses rope_finetuned as a switch for rope, so default to true
|
||||
bool rope_finetuned = true;
|
||||
ml.get_key(LLM_KV_ROPE_SCALING_FINETUNED, rope_finetuned, false);
|
||||
hparams.rope_finetuned = rope_finetuned;
|
||||
|
||||
switch (hparams.n_layer()) {
|
||||
case 32: type = LLM_TYPE_3B; break;
|
||||
case 40: type = LLM_TYPE_3B; break;
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
#include "models.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
void llama_model_granite_swa::load_arch_hparams(llama_model_loader & ml) {
|
||||
ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps);
|
||||
ml.get_key(LLM_KV_LOGIT_SCALE, hparams.f_logit_scale);
|
||||
ml.get_key(LLM_KV_RESIDUAL_SCALE, hparams.f_residual_scale, false);
|
||||
ml.get_key(LLM_KV_EMBEDDING_SCALE, hparams.f_embedding_scale, false);
|
||||
ml.get_key(LLM_KV_ATTENTION_SCALE, hparams.f_attention_scale, false);
|
||||
|
||||
// MoE expert configuration
|
||||
ml.get_key(LLM_KV_EXPERT_COUNT, hparams.n_expert, false);
|
||||
ml.get_key(LLM_KV_EXPERT_USED_COUNT, hparams.n_expert_used, false);
|
||||
|
||||
// iSWA configuration
|
||||
ml.get_arr(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, hparams.is_swa_impl);
|
||||
ml.get_key(LLM_KV_ATTENTION_SLIDING_WINDOW, hparams.n_swa);
|
||||
hparams.swa_type = LLAMA_SWA_TYPE_STANDARD;
|
||||
|
||||
// Granite4 Vision uses array deepstack_mapping
|
||||
ml.get_arr(LLM_KV_DEEPSTACK_MAPPING, hparams.deepstack_mapping_arr, false);
|
||||
|
||||
// Count the unique deepstack input indices
|
||||
std::unordered_set<uint32_t> unique_deepstack_idxs;
|
||||
for (const auto val : hparams.deepstack_mapping_arr) {
|
||||
if (val >= 0) {
|
||||
unique_deepstack_idxs.insert(val);
|
||||
}
|
||||
}
|
||||
hparams.n_deepstack_layers = unique_deepstack_idxs.size();
|
||||
|
||||
// Ensure all values are valid (avoid overflow attacks)
|
||||
for (const auto val : unique_deepstack_idxs) {
|
||||
if (val > hparams.n_deepstack_layers) {
|
||||
std::stringstream ss;
|
||||
ss << "Invalid deepstack index: " << val << " > " << hparams.n_deepstack_layers;
|
||||
throw std::runtime_error(ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
// Per-layer RoPE pattern (optional)
|
||||
ml.get_arr(LLM_KV_ATTENTION_ROPE_PATTERN, hparams.rope_pattern, false);
|
||||
|
||||
switch (hparams.n_layer()) {
|
||||
case 32: type = LLM_TYPE_3B; break;
|
||||
case 40: type = LLM_TYPE_3B; break;
|
||||
// Add additional layer/vocab/etc checks here for other model sizes
|
||||
default: type = LLM_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
// For Granite MoE Shared
|
||||
ml.get_key(LLM_KV_EXPERT_SHARED_FEED_FORWARD_LENGTH, hparams.n_ff_shexp, /* required */ false);
|
||||
}
|
||||
|
||||
void llama_model_granite_swa::load_arch_tensors(llama_model_loader &) {
|
||||
LLAMA_LOAD_LOCALS;
|
||||
|
||||
tok_embd = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, 0);
|
||||
|
||||
// output
|
||||
output_norm = create_tensor(tn(LLM_TENSOR_OUTPUT_NORM, "weight"), {n_embd}, 0);
|
||||
output = create_tensor(tn(LLM_TENSOR_OUTPUT, "weight"), {n_embd, n_vocab}, TENSOR_NOT_REQUIRED);
|
||||
|
||||
// if output is NULL, init from the input tok embed
|
||||
if (output == NULL) {
|
||||
output = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, TENSOR_DUPLICATED);
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_layer; ++i) {
|
||||
auto & layer = layers[i];
|
||||
|
||||
layer.attn_norm = create_tensor(tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd}, 0);
|
||||
|
||||
create_tensor_qkv(layer, i, n_embd, n_embd_head_k * n_head, n_embd_k_gqa, n_embd_v_gqa, 0);
|
||||
layer.wo = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_embd_head_k * n_head, n_embd}, 0);
|
||||
|
||||
// optional bias tensors
|
||||
layer.wo_b = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "bias", i), {n_embd}, TENSOR_NOT_REQUIRED);
|
||||
|
||||
// Per-layer attention sinks for iSWA
|
||||
layer.attn_sinks = create_tensor(tn(LLM_TENSOR_ATTN_SINKS, "weight", i), {n_head}, 0);
|
||||
|
||||
layer.ffn_norm = create_tensor(tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd}, 0);
|
||||
|
||||
if (hparams.rope_scaling_type_train == LLAMA_ROPE_SCALING_TYPE_LONGROPE) {
|
||||
layer.rope_long = create_tensor(tn(LLM_TENSOR_ROPE_FACTORS_LONG, "weight", i), {n_rot/2}, TENSOR_NOT_REQUIRED | (i != 0 ? TENSOR_DUPLICATED : 0));
|
||||
layer.rope_short = create_tensor(tn(LLM_TENSOR_ROPE_FACTORS_SHORT, "weight", i), {n_rot/2}, TENSOR_NOT_REQUIRED | (i != 0 ? TENSOR_DUPLICATED : 0));
|
||||
}
|
||||
else {
|
||||
layer.rope_freqs = create_tensor(tn(LLM_TENSOR_ROPE_FREQS, "weight", i), {n_rot/2}, TENSOR_NOT_REQUIRED | (i != 0 ? TENSOR_DUPLICATED : 0));
|
||||
}
|
||||
|
||||
if (n_expert == 0) {
|
||||
layer.ffn_gate = create_tensor(tn(LLM_TENSOR_FFN_GATE, "weight", i), {n_embd, n_ff}, 0);
|
||||
layer.ffn_down = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "weight", i), { n_ff, n_embd}, 0);
|
||||
layer.ffn_up = create_tensor(tn(LLM_TENSOR_FFN_UP, "weight", i), {n_embd, n_ff}, 0);
|
||||
|
||||
// optional MLP bias
|
||||
layer.ffn_gate_b = create_tensor(tn(LLM_TENSOR_FFN_GATE, "bias", i), {n_ff}, TENSOR_NOT_REQUIRED);
|
||||
layer.ffn_down_b = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "bias", i), {n_embd}, TENSOR_NOT_REQUIRED);
|
||||
layer.ffn_up_b = create_tensor(tn(LLM_TENSOR_FFN_UP, "bias", i), {n_ff}, TENSOR_NOT_REQUIRED);
|
||||
} else {
|
||||
layer.ffn_gate_inp = create_tensor(tn(LLM_TENSOR_FFN_GATE_INP, "weight", i), {n_embd, n_expert}, 0);
|
||||
layer.ffn_down_exps = create_tensor(tn(LLM_TENSOR_FFN_DOWN_EXPS, "weight", i), { n_ff, n_embd, n_expert}, 0);
|
||||
create_tensor_gate_up_exps(layer, i, n_embd, n_ff, n_expert, 0);
|
||||
|
||||
// For Granite MoE Shared - gate+up kept fused in ffn_up_shexp (see LLM_FFN_SWIGLU below)
|
||||
if (hparams.n_ff_shexp > 0) {
|
||||
layer.ffn_up_shexp = create_tensor(tn(LLM_TENSOR_FFN_UP_SHEXP, "weight", i), {n_embd, 2*hparams.n_ff_shexp}, 0);
|
||||
layer.ffn_down_shexp = create_tensor(tn(LLM_TENSOR_FFN_DOWN_SHEXP, "weight", i), {hparams.n_ff_shexp, n_embd}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<llm_graph_context> llama_model_granite_swa::build_arch_graph(const llm_graph_params & params) const {
|
||||
return std::make_unique<graph>(*this, params);
|
||||
}
|
||||
|
||||
llama_model_granite_swa::graph::graph(
|
||||
const llama_model & model,
|
||||
const llm_graph_params & params)
|
||||
: llm_graph_context(params) {
|
||||
|
||||
const int64_t n_embd_head = hparams.n_embd_head_v();
|
||||
|
||||
GGML_ASSERT(n_embd_head == hparams.n_embd_head_k());
|
||||
GGML_ASSERT(n_embd_head == n_rot);
|
||||
|
||||
ggml_tensor * cur;
|
||||
ggml_tensor * inpL;
|
||||
|
||||
inpL = build_inp_embd(model.tok_embd);
|
||||
|
||||
// inp_pos - built only if rope enabled
|
||||
ggml_tensor * inp_pos = build_inp_pos();
|
||||
auto * inp_attn = build_attn_inp_kv_iswa();
|
||||
|
||||
ggml_tensor * inp_out_ids = build_inp_out_ids();
|
||||
|
||||
for (int il = 0; il < n_layer; ++il) {
|
||||
|
||||
// Granite Vision 4.1 deepstack: inject the projector stream that
|
||||
// targets decoder layer `il` before the decoder runs.
|
||||
// NOTE: skip the first deepstack layer since that's inpL
|
||||
const auto & deepstack_emb_idx = hparams.deepstack_mapping_arr[il];
|
||||
if (il > 0 && deepstack_emb_idx >= 0) {
|
||||
ggml_tensor * ds = ggml_view_2d(ctx0,
|
||||
res->t_inp_embd, n_embd, n_tokens,
|
||||
res->t_inp_embd->nb[1],
|
||||
deepstack_emb_idx * n_embd * sizeof(float));
|
||||
inpL = ggml_add(ctx0, inpL, ds);
|
||||
cb(inpL, "deepstack_in", il);
|
||||
}
|
||||
|
||||
ggml_tensor * inpSA = inpL;
|
||||
|
||||
// norm
|
||||
cur = build_norm(inpL,
|
||||
model.layers[il].attn_norm, NULL,
|
||||
LLM_NORM_RMS, il);
|
||||
cb(cur, "attn_norm", il);
|
||||
|
||||
// self-attention
|
||||
cur = build_attention_layer(
|
||||
cur, inp_pos, inp_attn,
|
||||
model, n_embd_head, il);
|
||||
|
||||
if (il == n_layer - 1 && inp_out_ids) {
|
||||
cur = ggml_get_rows(ctx0, cur, inp_out_ids);
|
||||
inpSA = ggml_get_rows(ctx0, inpSA, inp_out_ids);
|
||||
}
|
||||
// ffn
|
||||
cur = build_layer_ffn(cur, inpSA, model, il);
|
||||
|
||||
// input for next layer
|
||||
inpL = cur;
|
||||
}
|
||||
cur = inpL;
|
||||
|
||||
cur = build_norm(cur,
|
||||
model.output_norm, NULL,
|
||||
LLM_NORM_RMS, -1);
|
||||
|
||||
cb(cur, "result_norm", -1);
|
||||
res->t_embd = cur;
|
||||
|
||||
// lm_head
|
||||
cur = build_lora_mm(model.output, cur, model.output_s);
|
||||
|
||||
// For Granite architectures - scale logits
|
||||
cur = ggml_scale(ctx0, cur, 1.0f / hparams.f_logit_scale);
|
||||
cb(cur, "result_output", -1);
|
||||
res->t_logits = cur;
|
||||
|
||||
ggml_build_forward_expand(gf, cur);
|
||||
}
|
||||
|
||||
ggml_tensor * llama_model_granite_swa::graph::build_attention_layer(
|
||||
ggml_tensor * cur,
|
||||
ggml_tensor * inp_pos,
|
||||
llm_graph_input_attn_kv_iswa * inp_attn,
|
||||
const llama_model & model,
|
||||
const int64_t n_embd_head,
|
||||
const int il) {
|
||||
|
||||
auto [Qcur, Kcur, Vcur] = build_qkv(model.layers[il], cur,
|
||||
n_embd_head, hparams.n_head(il), hparams.n_head_kv(il), il);
|
||||
|
||||
const bool use_rope = hparams.has_rope(il);
|
||||
if (use_rope) {
|
||||
ggml_tensor * rope_factors = model.get_rope_factors(cparams, il);
|
||||
Qcur = ggml_rope_ext(
|
||||
ctx0, Qcur, inp_pos, rope_factors,
|
||||
n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
|
||||
ext_factor, attn_factor, beta_fast, beta_slow
|
||||
);
|
||||
|
||||
Kcur = ggml_rope_ext(
|
||||
ctx0, Kcur, inp_pos, rope_factors,
|
||||
n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
|
||||
ext_factor, attn_factor, beta_fast, beta_slow
|
||||
);
|
||||
}
|
||||
|
||||
cb(Qcur, "Qcur", il);
|
||||
cb(Kcur, "Kcur", il);
|
||||
cb(Vcur, "Vcur", il);
|
||||
|
||||
const float kq_scale = hparams.f_attention_scale == 0.0f ? 1.0f/sqrtf(float(n_embd_head)) : hparams.f_attention_scale;
|
||||
|
||||
// Pass layer.attn_sinks to build_attn for sink-based attention modulation
|
||||
cur = build_attn(inp_attn,
|
||||
model.layers[il].wo, model.layers[il].wo_b, model.layers[il].wo_s,
|
||||
Qcur, Kcur, Vcur, nullptr, model.layers[il].attn_sinks, nullptr, kq_scale, il);
|
||||
cb(cur, "attn_out", il);
|
||||
return cur;
|
||||
}
|
||||
|
||||
ggml_tensor * llama_model_granite_swa::graph::build_layer_ffn(
|
||||
ggml_tensor * cur,
|
||||
ggml_tensor * inpSA,
|
||||
const llama_model & model,
|
||||
const int il) {
|
||||
|
||||
// For Granite architectures - scale residual
|
||||
if (hparams.f_residual_scale) {
|
||||
cur = ggml_scale(ctx0, cur, hparams.f_residual_scale);
|
||||
}
|
||||
ggml_tensor * ffn_inp = ggml_add(ctx0, cur, inpSA);
|
||||
cb(ffn_inp, "ffn_inp", il);
|
||||
|
||||
// feed-forward network (non-MoE)
|
||||
if (model.layers[il].ffn_gate_inp == nullptr) {
|
||||
|
||||
cur = build_norm(ffn_inp,
|
||||
model.layers[il].ffn_norm, NULL,
|
||||
LLM_NORM_RMS, il);
|
||||
cb(cur, "ffn_norm", il);
|
||||
|
||||
cur = build_ffn(cur,
|
||||
model.layers[il].ffn_up, model.layers[il].ffn_up_b, NULL,
|
||||
model.layers[il].ffn_gate, model.layers[il].ffn_gate_b, NULL,
|
||||
model.layers[il].ffn_down, model.layers[il].ffn_down_b, NULL,
|
||||
NULL,
|
||||
LLM_FFN_SILU, LLM_FFN_PAR, il);
|
||||
cb(cur, "ffn_out", il);
|
||||
|
||||
} else {
|
||||
// MoE branch
|
||||
cur = build_norm(ffn_inp,
|
||||
model.layers[il].ffn_norm, NULL,
|
||||
LLM_NORM_RMS, il);
|
||||
cb(cur, "ffn_norm", il);
|
||||
|
||||
ggml_tensor * moe_out = build_moe_ffn(cur,
|
||||
model.layers[il].ffn_gate_inp,
|
||||
model.layers[il].ffn_up_exps,
|
||||
model.layers[il].ffn_gate_exps,
|
||||
model.layers[il].ffn_down_exps,
|
||||
nullptr,
|
||||
n_expert, n_expert_used,
|
||||
LLM_FFN_SILU, true,
|
||||
hparams.expert_weights_scale,
|
||||
LLAMA_EXPERT_GATING_FUNC_TYPE_SOFTMAX,
|
||||
il,
|
||||
nullptr, model.layers[il].ffn_gate_up_exps);
|
||||
cb(moe_out, "ffn_moe_out", il);
|
||||
|
||||
// For Granite MoE Shared - gate+up kept fused in ffn_up_shexp
|
||||
if (hparams.n_ff_shexp > 0) {
|
||||
ggml_tensor * ffn_shexp = build_ffn(cur,
|
||||
model.layers[il].ffn_up_shexp, NULL, NULL,
|
||||
NULL, NULL, NULL,
|
||||
model.layers[il].ffn_down_shexp, NULL, NULL,
|
||||
NULL,
|
||||
LLM_FFN_SWIGLU, LLM_FFN_SEQ, il);
|
||||
cb(ffn_shexp, "ffn_shexp", il);
|
||||
|
||||
cur = ggml_add(ctx0, moe_out, ffn_shexp);
|
||||
cb(cur, "ffn_out", il);
|
||||
} else {
|
||||
cur = moe_out;
|
||||
}
|
||||
}
|
||||
|
||||
// For Granite architectures - scale residual
|
||||
if (hparams.f_residual_scale) {
|
||||
cur = ggml_scale(ctx0, cur, hparams.f_residual_scale);
|
||||
}
|
||||
cur = ggml_add(ctx0, cur, ffn_inp);
|
||||
cb(cur, "ffn_out", il);
|
||||
|
||||
cur = build_cvec(cur, il);
|
||||
cb(cur, "l_out", il);
|
||||
|
||||
return cur;
|
||||
}
|
||||
@@ -11,7 +11,8 @@ void llama_model_granite_switch::load_arch_hparams(llama_model_loader & ml) {
|
||||
|
||||
bool rope_finetuned = true;
|
||||
ml.get_key(LLM_KV_ROPE_SCALING_FINETUNED, rope_finetuned, false);
|
||||
hparams.rope_finetuned = rope_finetuned;
|
||||
hparams.rope_finetuned = rope_finetuned; // needed for round trip save
|
||||
std::fill(hparams.rope_pattern.begin(), hparams.rope_pattern.end(), rope_finetuned);
|
||||
|
||||
switch (hparams.n_layer()) {
|
||||
case 40: type = hparams.n_embd == 4096 ? LLM_TYPE_8B : LLM_TYPE_3B; break;
|
||||
@@ -254,7 +255,7 @@ llama_model_granite_switch::graph::graph(
|
||||
cb(inpL, "inp_embd", -1);
|
||||
|
||||
ggml_tensor * inp_pos = nullptr;
|
||||
if (hparams.rope_finetuned) {
|
||||
if (hparams.has_rope(0)) {
|
||||
inp_pos = build_inp_pos();
|
||||
}
|
||||
auto * inp_attn = build_attn_inp_kv();
|
||||
@@ -361,7 +362,7 @@ ggml_tensor * llama_model_granite_switch::graph::build_attention_layer(
|
||||
Kcur = ggml_reshape_3d(ctx0, Kcur, n_embd_head, n_head_kv, n_tokens);
|
||||
Vcur = ggml_reshape_3d(ctx0, Vcur, n_embd_head, n_head_kv, n_tokens);
|
||||
|
||||
if (hparams.rope_finetuned) {
|
||||
if (hparams.has_rope(il)) {
|
||||
ggml_tensor * rope_factors = model.get_rope_factors(cparams, il);
|
||||
Qcur = ggml_rope_ext(ctx0, Qcur, inp_pos, rope_factors,
|
||||
n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,
|
||||
|
||||
@@ -33,7 +33,8 @@ void llama_model_granite::load_arch_hparams(llama_model_loader & ml) {
|
||||
// Granite uses rope_finetuned as a switch for rope, so default to true
|
||||
bool rope_finetuned = true;
|
||||
ml.get_key(LLM_KV_ROPE_SCALING_FINETUNED, rope_finetuned, false);
|
||||
hparams.rope_finetuned = rope_finetuned;
|
||||
hparams.rope_finetuned = rope_finetuned; // needed for round trip save
|
||||
std::fill(hparams.rope_pattern.begin(), hparams.rope_pattern.end(), rope_finetuned);
|
||||
|
||||
switch (hparams.n_layer()) {
|
||||
case 32: type = LLM_TYPE_3B; break;
|
||||
@@ -127,7 +128,7 @@ llama_model_granite::graph::graph(
|
||||
|
||||
// inp_pos - built only if rope enabled
|
||||
ggml_tensor * inp_pos = nullptr;
|
||||
if (hparams.rope_finetuned) {
|
||||
if (hparams.has_rope(0)) {
|
||||
inp_pos = build_inp_pos();
|
||||
}
|
||||
auto * inp_attn = build_attn_inp_kv();
|
||||
@@ -203,8 +204,7 @@ ggml_tensor * llama_model_granite::graph::build_attention_layer(
|
||||
auto [Qcur, Kcur, Vcur] = build_qkv(model.layers[il], cur,
|
||||
n_embd_head, hparams.n_head(il), hparams.n_head_kv(il), il);
|
||||
|
||||
const bool use_rope = hparams.rope_finetuned;
|
||||
if (use_rope) {
|
||||
if (hparams.has_rope(il)) {
|
||||
ggml_tensor * rope_factors = model.get_rope_factors(cparams, il);
|
||||
Qcur = ggml_rope_ext(
|
||||
ctx0, Qcur, inp_pos, rope_factors,
|
||||
|
||||
+17
-8
@@ -2,6 +2,8 @@
|
||||
#include "../llama-memory-hybrid-iswa.h"
|
||||
#include "../llama-memory-hybrid.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
void llama_model_lfm2::load_arch_hparams(llama_model_loader & ml) {
|
||||
ml.get_key(LLM_KV_SHORTCONV_L_CACHE, hparams.n_shortconv_l_cache);
|
||||
ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps);
|
||||
@@ -202,15 +204,20 @@ llama_model_lfm2::graph<iswa>::graph(const llama_model & model, const llm_graph_
|
||||
}
|
||||
GGML_ASSERT(bx->ne[0] > conv->ne[0]);
|
||||
|
||||
// last d_conv columns is a new conv state
|
||||
auto * new_conv = ggml_view_3d(ctx0, bx, conv->ne[0], bx->ne[1], bx->ne[2], bx->nb[1], bx->nb[2],
|
||||
(bx->ne[0] - conv->ne[0]) * ggml_element_size(bx));
|
||||
GGML_ASSERT(ggml_are_same_shape(conv, new_conv));
|
||||
// write conv states: slot 0 = the final state, slot s = the state s tokens back (partial rollback)
|
||||
const int64_t K = hparams.causal_attn && cparams.n_rs_seq > 0 ? (int64_t) cparams.n_rs_seq + 1 : 1;
|
||||
const int64_t n_written = std::min<int64_t>(n_seq_tokens, K);
|
||||
const auto mem_size = mctx_cur->get_size();
|
||||
const size_t row_size = ggml_row_size(conv_state->type, (int64_t) d_conv * n_embd);
|
||||
|
||||
// write new conv conv state
|
||||
ggml_build_forward_expand(gf, ggml_cpy(ctx0, new_conv,
|
||||
ggml_view_1d(ctx0, conv_state, ggml_nelements(new_conv),
|
||||
kv_head * d_conv * n_embd * ggml_element_size(new_conv))));
|
||||
for (int64_t slot = 0; slot < n_written; ++slot) {
|
||||
auto * conv_snap = ggml_view_3d(ctx0, bx, d_conv, bx->ne[1], bx->ne[2], bx->nb[1], bx->nb[2],
|
||||
(bx->ne[0] - d_conv - slot) * ggml_element_size(bx));
|
||||
ggml_build_forward_expand(gf, ggml_cpy(ctx0, conv_snap,
|
||||
ggml_view_2d(ctx0, conv_state, (int64_t) d_conv * n_embd, n_seqs,
|
||||
conv_state->nb[1],
|
||||
((size_t) slot * mem_size + kv_head) * row_size)));
|
||||
}
|
||||
|
||||
auto * conv_kernel = model.layers[il].shortconv.conv;
|
||||
auto * conv_out = ggml_ssm_conv(ctx0, bx, conv_kernel);
|
||||
@@ -242,6 +249,8 @@ llama_model_lfm2::graph<iswa>::graph(const llama_model & model, const llm_graph_
|
||||
ggml_tensor * inp_out_ids = build_inp_out_ids();
|
||||
|
||||
for (int il = 0; il < n_layer; ++il) {
|
||||
res->t_layer_inp[il] = cur;
|
||||
|
||||
const bool is_moe_layer = il >= static_cast<int>(hparams.n_layer_dense_lead);
|
||||
|
||||
auto * prev_cur = cur;
|
||||
|
||||
@@ -1719,6 +1719,34 @@ struct llama_model_granite_hybrid : public llama_model_base {
|
||||
};
|
||||
|
||||
|
||||
struct llama_model_granite_swa : public llama_model_base {
|
||||
llama_model_granite_swa(const struct llama_model_params & params) : llama_model_base(params) {}
|
||||
void load_arch_hparams(llama_model_loader & ml) override;
|
||||
void load_arch_tensors(llama_model_loader & ml) override;
|
||||
|
||||
struct graph : public llm_graph_context {
|
||||
graph(const llama_model & model, const llm_graph_params & params);
|
||||
|
||||
private:
|
||||
ggml_tensor * build_attention_layer(
|
||||
ggml_tensor * cur,
|
||||
ggml_tensor * inp_pos,
|
||||
llm_graph_input_attn_kv_iswa * inp_attn,
|
||||
const llama_model & model,
|
||||
const int64_t n_embd_head,
|
||||
const int il);
|
||||
|
||||
ggml_tensor * build_layer_ffn(
|
||||
ggml_tensor * cur,
|
||||
ggml_tensor * inpSA,
|
||||
const llama_model & model,
|
||||
const int il);
|
||||
};
|
||||
|
||||
std::unique_ptr<llm_graph_context> build_arch_graph(const llm_graph_params & params) const override;
|
||||
};
|
||||
|
||||
|
||||
struct llama_model_chameleon : public llama_model_base {
|
||||
llama_model_chameleon(const struct llama_model_params & params) : llama_model_base(params) {}
|
||||
void load_arch_hparams(llama_model_loader & ml) override;
|
||||
|
||||
+70
-11
@@ -5352,24 +5352,27 @@ struct test_rope : public test_case {
|
||||
int v; // view (1 : non-contiguous a)
|
||||
bool forward;
|
||||
bool inplace;
|
||||
int n_offs; // offset of the rotated dims window, set via ggml_rope_set_offset()
|
||||
|
||||
std::string vars() override {
|
||||
// forward can be inferred from the op, does not need to be printed
|
||||
return VARS_TO_STR11(type, ne_a, n_dims, mode, n_ctx, fs, ef, af, ff, v, inplace);
|
||||
return VARS_TO_STR12(type, ne_a, n_dims, mode, n_ctx, fs, ef, af, ff, v, inplace, n_offs);
|
||||
}
|
||||
|
||||
test_rope(ggml_type type = GGML_TYPE_F32,
|
||||
std::array<int64_t, 4> ne_a = {10, 5, 3, 1},
|
||||
int n_dims = 10, int mode = GGML_ROPE_TYPE_NORMAL, int n_ctx = 512, float fs = 1.0f,
|
||||
float ef = 0.0f, float af = 0.0f, bool ff = false, int v = 0, bool forward = true, bool inplace = false)
|
||||
: type(type), ne_a(ne_a), n_dims(n_dims), mode(mode), n_ctx(n_ctx), fs(fs), ef(ef), af(af), ff(ff), v(v), forward(forward), inplace(inplace) {}
|
||||
float ef = 0.0f, float af = 0.0f, bool ff = false, int v = 0, bool forward = true, bool inplace = false,
|
||||
int n_offs = 0)
|
||||
: type(type), ne_a(ne_a), n_dims(n_dims), mode(mode), n_ctx(n_ctx), fs(fs), ef(ef), af(af), ff(ff), v(v), forward(forward), inplace(inplace), n_offs(n_offs) {}
|
||||
|
||||
ggml_tensor * build_graph(ggml_context * ctx) override {
|
||||
ggml_tensor * a;
|
||||
if (v & 1) {
|
||||
auto ne = ne_a; ne[0] *= 2; ne[1] *= 4; ne[2] *= 3;
|
||||
a = ggml_new_tensor(ctx, type, 4, ne.data());
|
||||
if (forward) {
|
||||
if (forward && n_offs == 0) {
|
||||
// FIXME: support gradients with n_offs > 0
|
||||
ggml_set_param(a);
|
||||
}
|
||||
ggml_set_name(a, "a");
|
||||
@@ -5382,7 +5385,8 @@ struct test_rope : public test_case {
|
||||
// non-aligned buffer offset, which exercises backends' alignment paths.
|
||||
auto ne = ne_a; ne[0] *= 2;
|
||||
a = ggml_new_tensor(ctx, type, 4, ne.data());
|
||||
if (forward) {
|
||||
if (forward && n_offs == 0) {
|
||||
// FIXME: support gradients with n_offs > 0
|
||||
ggml_set_param(a);
|
||||
}
|
||||
ggml_set_name(a, "a");
|
||||
@@ -5393,7 +5397,8 @@ struct test_rope : public test_case {
|
||||
ggml_set_name(a, "view_of_a");
|
||||
} else {
|
||||
a = ggml_new_tensor(ctx, type, 4, ne_a.data());
|
||||
if (forward) {
|
||||
if (forward && n_offs == 0) {
|
||||
// FIXME: support gradients with n_offs > 0
|
||||
ggml_set_param(a);
|
||||
}
|
||||
ggml_set_name(a, "a");
|
||||
@@ -5454,6 +5459,9 @@ struct test_rope : public test_case {
|
||||
out = ggml_rope_ext_back(ctx, a, pos, freq, n_dims, mode, 0, 10000.0f, fs, ef, af, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
if (n_offs != 0) {
|
||||
out = ggml_rope_set_offset(out, n_offs);
|
||||
}
|
||||
ggml_set_name(out, "out");
|
||||
|
||||
return out;
|
||||
@@ -7076,9 +7084,10 @@ struct test_flash_attn_ext : public test_case {
|
||||
const ggml_type type_K;
|
||||
const ggml_type type_V;
|
||||
std::array<int32_t, 4> permute;
|
||||
const bool kv_view; // create K/V as views of a larger buffer (like a KV cache)
|
||||
|
||||
std::string vars() override {
|
||||
return VARS_TO_STR14(hsk, hsv, nh, nr23, kv, nb, mask, sinks, max_bias, logit_softcap, prec, type_K, type_V, permute);
|
||||
return VARS_TO_STR15(hsk, hsv, nh, nr23, kv, nb, mask, sinks, max_bias, logit_softcap, prec, type_K, type_V, permute, kv_view);
|
||||
}
|
||||
|
||||
double max_nmse_err() override {
|
||||
@@ -7094,9 +7103,10 @@ struct test_flash_attn_ext : public test_case {
|
||||
|
||||
test_flash_attn_ext(int64_t hsk = 128, int64_t hsv = 128, int64_t nh = 32, std::array<int64_t, 2> nr23 = {1, 1}, int64_t kv = 96, int64_t nb = 8,
|
||||
bool mask = true, bool sinks = false, float max_bias = 0.0f, float logit_softcap = 0.0f, ggml_prec prec = GGML_PREC_F32,
|
||||
ggml_type type_K = GGML_TYPE_F16, ggml_type type_V = GGML_TYPE_F16, std::array<int32_t, 4> permute = {0, 1, 2, 3})
|
||||
ggml_type type_K = GGML_TYPE_F16, ggml_type type_V = GGML_TYPE_F16, std::array<int32_t, 4> permute = {0, 1, 2, 3},
|
||||
bool kv_view = true)
|
||||
: hsk(hsk), hsv(hsv), nh(nh), nr23(nr23), kv(kv), nb(nb), mask(mask), sinks(sinks), max_bias(max_bias), logit_softcap(logit_softcap), prec(prec),
|
||||
type_K(type_K), type_V(type_V), permute(permute) {}
|
||||
type_K(type_K), type_V(type_V), permute(permute), kv_view(kv_view) {}
|
||||
|
||||
ggml_tensor * build_graph(ggml_context * ctx) override {
|
||||
const int64_t hsk_padded = GGML_PAD(hsk, ggml_blck_size(type_K));
|
||||
@@ -7124,7 +7134,7 @@ struct test_flash_attn_ext : public test_case {
|
||||
ggml_tensor * q = create_permuted(GGML_TYPE_F32, hsk_padded, nb, nh*nr23[0], nr23[1], false);
|
||||
ggml_set_name(q, "q");
|
||||
|
||||
ggml_tensor * k = create_permuted(type_K, hsk_padded, kv, nh, nr23[1], true); // the K tensor is usually a view of the K cache
|
||||
ggml_tensor * k = create_permuted(type_K, hsk_padded, kv, nh, nr23[1], kv_view); // the K tensor is usually a view of the K cache
|
||||
ggml_set_name(k, "k");
|
||||
|
||||
ggml_tensor * v = nullptr;
|
||||
@@ -7138,7 +7148,7 @@ struct test_flash_attn_ext : public test_case {
|
||||
// - https://github.com/ggml-org/llama.cpp/pull/18986
|
||||
v = ggml_view_4d(ctx, k, hsv_padded, kv, nh, nr23[1], k->nb[1], k->nb[2], k->nb[3], 0);
|
||||
} else {
|
||||
v = create_permuted(type_V, hsv_padded, kv, nh, nr23[1], true); // the V tensor is usually a view of the V cache
|
||||
v = create_permuted(type_V, hsv_padded, kv, nh, nr23[1], kv_view); // the V tensor is usually a view of the V cache
|
||||
}
|
||||
ggml_set_name(v, "v");
|
||||
|
||||
@@ -9621,6 +9631,20 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
}
|
||||
}
|
||||
|
||||
// rotated dims window at an offset (ggml_rope_set_offset), not supported for vision mode
|
||||
for (ggml_type type : {GGML_TYPE_F32, GGML_TYPE_F16}) {
|
||||
for (bool fw : {true, false}) { // fw == forward
|
||||
for (bool ff : {false, true}) {
|
||||
test_cases.emplace_back(new test_rope(type, {128, 32, 2, 1}, 32, GGML_ROPE_TYPE_NORMAL, 512, 1.4245f, 0.7465f, 1.4245f, ff, 0, fw, false, 32));
|
||||
test_cases.emplace_back(new test_rope(type, {128, 32, 2, 1}, 32, GGML_ROPE_TYPE_NEOX, 512, 1.4245f, 0.7465f, 1.4245f, ff, 0, fw, false, 32));
|
||||
test_cases.emplace_back(new test_rope(type, {128, 12, 2, 1}, 24, GGML_ROPE_TYPE_MROPE, 512, 1.4245f, 0.7465f, 1.4245f, ff, 0, fw, false, 32));
|
||||
test_cases.emplace_back(new test_rope(type, {128, 12, 2, 1}, 24, GGML_ROPE_TYPE_IMROPE, 512, 1.4245f, 0.7465f, 1.4245f, ff, 0, fw, false, 32));
|
||||
}
|
||||
}
|
||||
// inplace with an offset
|
||||
test_cases.emplace_back(new test_rope(type, {128, 32, 2, 1}, 32, GGML_ROPE_TYPE_NEOX, 512, 1.4245f, 0.7465f, 1.4245f, false, 0, true, true, 32));
|
||||
}
|
||||
|
||||
for (int v : { 0, 1, 2, 3 }) {
|
||||
for (int dim : { 0, 1, 2, 3, }) {
|
||||
test_cases.emplace_back(new test_concat(GGML_TYPE_F32, {11, 12, 13, 14}, 7, dim, v));
|
||||
@@ -9910,6 +9934,20 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
test_cases.emplace_back(new test_flash_attn_ext(64, 128, 4, {1, 1}, 128, 2, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q4_0, GGML_TYPE_Q2_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(128, 64, 4, {1, 1}, 64, 2, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q2_0, GGML_TYPE_F16));
|
||||
|
||||
// q8_0 KV cases: decode and prompt batches, KV pad, permuted KV, feature flags, and long context
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 113, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 1024, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 1024, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 2, 1, 3}));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 2}, 1025, 1, true, true, 8, 30, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 1025, 64, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 2, 1, 3}));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 16384, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
|
||||
// MLA shape (V is a view of K) with quantized KV
|
||||
// (the test harness builds V as a view of K for this shape; see build_graph)
|
||||
test_cases.emplace_back(new test_flash_attn_ext(576, 512, 1, {20, 1}, 113, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(576, 512, 1, {20, 1}, 1024, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(576, 512, 1, {20, 1}, 1024, 64, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
|
||||
// large-KV F16 cases (Qwen3.6-27B geometry and a llama-class control): the upstream matrix
|
||||
// stops at kv=1024, blind to long-context FA bugs (e.g. the oneDNN SDPA ordering race on BMG).
|
||||
for (int64_t kv : { 4096, 16384 }) {
|
||||
@@ -9919,6 +9957,12 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
GGML_PREC_F32, GGML_TYPE_F16, GGML_TYPE_F16));
|
||||
}
|
||||
|
||||
// dense-allocated (non-view) quant K/V at batch >= 64, in cache and native layouts
|
||||
test_cases.emplace_back(new test_flash_attn_ext(64, 64, 4, {1, 1}, 512, 75, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 2, 1, 3}, false));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(64, 64, 4, {4, 1}, 512, 75, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 2, 1, 3}, false));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(64, 64, 4, {1, 1}, 1024, 75, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 2, 1, 3}, false));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(64, 64, 4, {1, 1}, 512, 75, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0, {0, 1, 2, 3}, false));
|
||||
|
||||
test_cases.emplace_back(new test_cross_entropy_loss (GGML_TYPE_F32, { 10, 5, 4, 3}));
|
||||
test_cases.emplace_back(new test_cross_entropy_loss (GGML_TYPE_F32, {30000, 1, 1, 1}));
|
||||
test_cases.emplace_back(new test_cross_entropy_loss_back(GGML_TYPE_F32, { 10, 5, 4, 3}));
|
||||
@@ -10295,6 +10339,21 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_perf() {
|
||||
test_cases.emplace_back(new test_flash_attn_ext(64, 64, 8, {8, 1}, 7680, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(64, 64, 8, {8, 1}, 7680, 512, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
|
||||
// q8_0 KV cases with long context (decode and prompt)
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 128, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 512, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 1024, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 2048, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 4096, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 10000, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 20000, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 10000, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F16, GGML_TYPE_F16));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 20000, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F16, GGML_TYPE_F16));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 10000, 512, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 20000, 512, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 10000, 512, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F16, GGML_TYPE_F16));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 20000, 512, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F16, GGML_TYPE_F16));
|
||||
|
||||
for (int kv : { 4096, 8192, 16384, }) {
|
||||
for (int hs : { 64, 128, }) {
|
||||
for (int nr : { 1, 4, }) {
|
||||
|
||||
@@ -1564,6 +1564,70 @@ int main() {
|
||||
space ::= | " " | "\n"{1,2} [ \t]{0,20}
|
||||
)""",
|
||||
});
|
||||
|
||||
run({
|
||||
SUCCESS,
|
||||
"unanchored regexp",
|
||||
R"""({
|
||||
"type": "string",
|
||||
"pattern": "[0-9]+"
|
||||
})""",
|
||||
R"""(
|
||||
char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
|
||||
root ::= string
|
||||
space ::= | " " | "\n"{1,2} [ \t]{0,20}
|
||||
string ::= "\"" char* "\""
|
||||
)""",
|
||||
});
|
||||
|
||||
// the rules of the partial conversion (here "root-0") must not leak into the grammar
|
||||
run({
|
||||
SUCCESS,
|
||||
"regexp with unsupported shorthand",
|
||||
R"""({
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]{3}\\w$"
|
||||
})""",
|
||||
R"""(
|
||||
char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
|
||||
root ::= string
|
||||
space ::= | " " | "\n"{1,2} [ \t]{0,20}
|
||||
string ::= "\"" char* "\""
|
||||
)""",
|
||||
});
|
||||
|
||||
// a regexp that is invalid under any flavor is still an error
|
||||
run({
|
||||
FAILURE,
|
||||
"regexp with unbalanced parentheses",
|
||||
R"""({
|
||||
"type": "string",
|
||||
"pattern": "^(a$"
|
||||
})""",
|
||||
""
|
||||
});
|
||||
|
||||
// only the property with the bad pattern degrades
|
||||
run({
|
||||
SUCCESS,
|
||||
"unsupported regexp in a property",
|
||||
R"""({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"a": { "type": "string", "pattern": "^[a-z\\-]+$" }
|
||||
},
|
||||
"required": ["a"],
|
||||
"additionalProperties": false
|
||||
})""",
|
||||
R"""(
|
||||
a ::= string
|
||||
a-kv ::= "\"a\"" space ":" space a
|
||||
char ::= [^"\\\x7F\x00-\x1F] | [\\] (["\\bfnrt] | "u" [0-9a-fA-F]{4})
|
||||
root ::= "{" space a-kv space "}"
|
||||
space ::= | " " | "\n"{1,2} [ \t]{0,20}
|
||||
string ::= "\"" char* "\""
|
||||
)""",
|
||||
});
|
||||
}
|
||||
|
||||
if (getenv("LLAMA_SKIP_TESTS_SLOW_ON_EMULATOR")) {
|
||||
|
||||
@@ -197,7 +197,7 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {
|
||||
ms.add_kv(LLM_KV_ROPE_FREQ_BASE_SWA, 10000.0f);
|
||||
// SWA pattern: every 5th layer is full attention (matches E2B layer_types)
|
||||
ms.add_kv(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, uint32_t(5));
|
||||
} else if (arch == LLM_ARCH_COHERE2MOE || arch == LLM_ARCH_MIMO2 || arch == LLM_ARCH_STEP35 || arch == LLM_ARCH_MUSE_GLIMMER) {
|
||||
} else if (arch == LLM_ARCH_COHERE2MOE || arch == LLM_ARCH_MIMO2 || arch == LLM_ARCH_STEP35 || arch == LLM_ARCH_MUSE_GLIMMER || arch == LLM_ARCH_GRANITE_SWA) {
|
||||
std::vector<uint32_t> pattern;
|
||||
pattern.reserve(n_layer);
|
||||
for (uint32_t il = 0; il < n_layer; il++) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#include "testing.h"
|
||||
|
||||
#include "mtmd-image.h"
|
||||
#include "mtmd-internal.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -67,6 +70,73 @@ MAKE_TEST(test_image_preprocessor_lfm2) {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// mtmd temporal merge
|
||||
//
|
||||
|
||||
MAKE_TEST(test_temporal_merge_grouping) {
|
||||
std::vector<mtmd::bitmap_ptr> pool; // keeps the bitmaps alive until the end of the test
|
||||
|
||||
// spec chars:
|
||||
// v = video frame, w = video frame of another size, a = audio, i = plain image, t = text
|
||||
auto make_parts = [&pool](const std::string & spec) {
|
||||
std::vector<mtmd_input_part> parts;
|
||||
for (char c : spec) {
|
||||
if (c == 't') {
|
||||
parts.push_back({ "hello", nullptr });
|
||||
continue;
|
||||
}
|
||||
mtmd_bitmap * bm = nullptr;
|
||||
switch (c) {
|
||||
case 'v': bm = mtmd_bitmap_init(100, 100, nullptr); break;
|
||||
case 'w': bm = mtmd_bitmap_init(200, 200, nullptr); break;
|
||||
case 'a': bm = mtmd_bitmap_init_from_audio(100, nullptr); break;
|
||||
case 'i': bm = mtmd_bitmap_init(100, 100, nullptr); break;
|
||||
default: throw std::runtime_error(std::string("unknown spec char: ") + c);
|
||||
}
|
||||
mtmd_bitmap_set_mergeable(bm, c != 'i');
|
||||
pool.emplace_back(bm);
|
||||
parts.push_back({ "", bm });
|
||||
}
|
||||
return parts;
|
||||
};
|
||||
|
||||
// { parts, n_merge, expected size of each group }
|
||||
const std::vector<std::tuple<std::string, int, std::string>> cases = {
|
||||
{ "vv", 2, "2" },
|
||||
{ "vvv", 2, "21" },
|
||||
{ "vvvv", 2, "22" },
|
||||
{ "vvi", 2, "21" },
|
||||
{ "tvvt", 2, "2" },
|
||||
{ "vtv", 2, "11" }, // text in between breaks the merge
|
||||
{ "vw", 2, "11" }, // different sizes cannot be merged
|
||||
{ "aa", 2, "11" }, // audio is never merged
|
||||
{ "ii", 2, "11" }, // two unrelated images must stay separated
|
||||
{ "iv", 2, "11" },
|
||||
{ "vi", 2, "11" },
|
||||
{ "vv", 1, "11" }, // model without temporal merge
|
||||
};
|
||||
|
||||
for (const auto & [spec, n_merge, expected] : cases) {
|
||||
auto parts = make_parts(spec);
|
||||
auto groups = mtmd_group_mergeable_bitmaps(parts, n_merge);
|
||||
|
||||
std::string actual;
|
||||
for (const auto & group : groups) {
|
||||
actual += std::to_string(group.size());
|
||||
}
|
||||
|
||||
const std::string name = "\"" + spec + "\" with n_merge=" + std::to_string(n_merge);
|
||||
t.assert_equal("groups for " + name, expected, actual);
|
||||
|
||||
size_t n_bitmap_parts = 0;
|
||||
for (const auto & p : parts) {
|
||||
n_bitmap_parts += p.bitmap != nullptr ? 1 : 0;
|
||||
}
|
||||
t.assert_equal("remaining bitmap parts for " + name, groups.size(), n_bitmap_parts);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// main
|
||||
//
|
||||
|
||||
@@ -162,6 +162,7 @@
|
||||
| `-mmu, --mmproj-url URL` | URL to a multimodal projector file. see tools/mtmd/README.md<br/>(env: LLAMA_ARG_MMPROJ_URL) |
|
||||
| `--mmproj-auto, --no-mmproj, --no-mmproj-auto` | whether to use multimodal projector file (if available), useful when using -hf (default: enabled)<br/>(env: LLAMA_ARG_MMPROJ_AUTO) |
|
||||
| `--mmproj-offload, --no-mmproj-offload` | whether to enable GPU offloading for multimodal projector (default: enabled)<br/>(env: LLAMA_ARG_MMPROJ_OFFLOAD) |
|
||||
| `-mmdev, --mmproj-device DEVICE` | device to use for multimodal projector (none = don't offload, default: auto)<br/>use --list-devices to see a list of available devices<br/>(env: MTMD_BACKEND_DEVICE) |
|
||||
| `--image, --audio, --video FILE` | path to an image, audio, or video file. use with multimodal models, use comma-separated values for multiple files |
|
||||
| `--image-min-tokens N` | minimum number of tokens each image can take, only used by vision models with dynamic resolution (default: read from model)<br/>(env: LLAMA_ARG_IMAGE_MIN_TOKENS) |
|
||||
| `--image-max-tokens N` | maximum number of tokens each image can take, only used by vision models with dynamic resolution (default: read from model)<br/>(env: LLAMA_ARG_IMAGE_MAX_TOKENS) |
|
||||
|
||||
@@ -17,6 +17,7 @@ add_library(mtmd
|
||||
mtmd-audio.cpp
|
||||
mtmd-image.cpp
|
||||
mtmd.h
|
||||
mtmd-internal.h
|
||||
mtmd-helper.cpp
|
||||
mtmd-helper-gen.cpp
|
||||
mtmd-helper-common.h
|
||||
|
||||
@@ -21,7 +21,7 @@ A typical pipeline of the core libmtmd is as follows:
|
||||
- A bitmap (RGB image or PCM audio) is created
|
||||
- Bitmap and the text prompt is provided to `mtmd_tokenize()` that breaks the input into chunks
|
||||
- The tokenizer function first expands a "lazy" bitmap if it finds one. Typically, this is used by video, so that one media token corresponds to one input bitmap
|
||||
- For models that support "fused" temporal frames like Qwen-VL, the tokenizer tries to merge pair of consecutive frames into one batch
|
||||
- For models that support "fused" temporal frames like Qwen-VL, the tokenizer tries to merge pair of consecutive frames into one batch. Only bitmaps marked by `mtmd_bitmap_set_mergeable()` are merged
|
||||
- The preprocessor will then be called, which produces a list of chunks
|
||||
- Depending on the model itself, special tokens will be injected to separate image chunks (i.e. llava-uhd-style models)
|
||||
- Multiple bitmaps may be batched together to form a larger `mtmd_batch()`
|
||||
|
||||
+5
-6
@@ -186,14 +186,13 @@ struct clip_ctx {
|
||||
throw std::runtime_error("failed to initialize CPU backend");
|
||||
}
|
||||
if (ctx_params.use_gpu) {
|
||||
auto * backend_name = std::getenv("MTMD_BACKEND_DEVICE");
|
||||
if (backend_name != nullptr) {
|
||||
backend = ggml_backend_init_by_name(backend_name, nullptr);
|
||||
if (ctx_params.device != nullptr) {
|
||||
backend = ggml_backend_dev_init(ctx_params.device, nullptr);
|
||||
if (!backend) {
|
||||
LOG_WRN("%s: Warning: Failed to initialize \"%s\" backend, falling back to default GPU backend\n", __func__, backend_name);
|
||||
throw std::runtime_error(string_format("%s: failed to initialize \"%s\" backend\n",
|
||||
__func__, ggml_backend_dev_name(ctx_params.device)));
|
||||
}
|
||||
}
|
||||
if (!backend) {
|
||||
} else {
|
||||
backend = ggml_backend_init_by_type(GGML_BACKEND_DEVICE_TYPE_GPU, nullptr);
|
||||
backend = backend ? backend : ggml_backend_init_by_type(GGML_BACKEND_DEVICE_TYPE_IGPU, nullptr);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ enum clip_flash_attn_type {
|
||||
|
||||
struct clip_context_params {
|
||||
bool use_gpu;
|
||||
ggml_backend_dev_t device;
|
||||
enum clip_flash_attn_type flash_attn_type;
|
||||
int image_min_tokens;
|
||||
int image_max_tokens;
|
||||
|
||||
@@ -84,6 +84,7 @@ int main(int argc, char ** argv) {
|
||||
const char * clip_path = params.mmproj.path.c_str();
|
||||
mtmd_context_params mparams = mtmd_context_params_default();
|
||||
mparams.use_gpu = params.mmproj_use_gpu;
|
||||
mparams.device = params.mmproj_device;
|
||||
mparams.print_timings = true;
|
||||
mparams.n_threads = params.cpuparams.n_threads;
|
||||
mparams.flash_attn_type = params.flash_attn_type;
|
||||
|
||||
@@ -154,6 +154,7 @@ struct mtmd_cli_context {
|
||||
const char * clip_path = params.mmproj.path.c_str();
|
||||
mtmd_context_params mparams = mtmd_context_params_default();
|
||||
mparams.use_gpu = params.mmproj_use_gpu;
|
||||
mparams.device = params.mmproj_device;
|
||||
mparams.print_timings = true;
|
||||
mparams.n_threads = params.cpuparams.n_threads;
|
||||
mparams.flash_attn_type = params.flash_attn_type;
|
||||
|
||||
@@ -727,7 +727,9 @@ struct mtmd_helper_video {
|
||||
|
||||
LOG_DBG("%s: frame %d read OK\n", __func__, current_frame);
|
||||
current_frame++;
|
||||
return mtmd_bitmap_init(info.width, info.height, frame_buf.data());
|
||||
mtmd_bitmap * frame = mtmd_bitmap_init(info.width, info.height, frame_buf.data());
|
||||
mtmd_bitmap_set_mergeable(frame, true);
|
||||
return frame;
|
||||
}
|
||||
|
||||
int32_t read_next(mtmd_bitmap ** out_bitmap, char ** out_text) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "mtmd.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// !!! Internal header, to be used by mtmd and its unit tests only !!!
|
||||
|
||||
#define MTMD_INTERNAL_HEADER
|
||||
|
||||
// bitmap is null for text parts
|
||||
struct mtmd_input_part {
|
||||
std::string text;
|
||||
const mtmd_bitmap * bitmap;
|
||||
};
|
||||
|
||||
// [QWEN_VIDEO] merged parts are erased from `parts`, so one group always maps to one part
|
||||
std::vector<std::vector<const mtmd_bitmap *>> mtmd_group_mergeable_bitmaps(std::vector<mtmd_input_part> & parts, int n_merge);
|
||||
+32
-33
@@ -1,6 +1,7 @@
|
||||
#include "clip.h"
|
||||
#include "clip-impl.h"
|
||||
#include "mtmd.h"
|
||||
#include "mtmd-internal.h"
|
||||
#include "mtmd-audio.h"
|
||||
#include "mtmd-image.h"
|
||||
#include "debug/mtmd-debug.h"
|
||||
@@ -149,6 +150,7 @@ struct mtmd_bitmap {
|
||||
uint32_t ny = 0;
|
||||
std::string id; // optional user-defined id, for ex: can be set to image hash, useful for KV cache tracking
|
||||
bool is_audio = false; // true if the bitmap is audio
|
||||
bool mergeable = false; // [QWEN_VIDEO] set only on frames of the same video
|
||||
|
||||
// lazy-loaded bitmap
|
||||
mtmd_bitmap_lazy_callback lazy_callback = nullptr;
|
||||
@@ -186,7 +188,9 @@ struct mtmd_bitmap {
|
||||
|
||||
bool can_merge_with(const mtmd_bitmap & other) const {
|
||||
// [QWEN_VIDEO] can (temporal) merge if both are images with same size
|
||||
return !is_audio && !other.is_audio && nx == other.nx && ny == other.ny;
|
||||
return mergeable && other.mergeable
|
||||
&& !is_audio && !other.is_audio
|
||||
&& nx == other.nx && ny == other.ny;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -452,6 +456,7 @@ static clip_flash_attn_type mtmd_get_clip_flash_attn_type(enum llama_flash_attn_
|
||||
mtmd_context_params mtmd_context_params_default() {
|
||||
mtmd_context_params params {
|
||||
/* use_gpu */ true,
|
||||
/* device */ nullptr,
|
||||
/* print_timings */ true,
|
||||
/* n_threads */ 4,
|
||||
/* image_marker */ nullptr,
|
||||
@@ -560,6 +565,7 @@ struct mtmd_context {
|
||||
|
||||
clip_context_params ctx_clip_params {
|
||||
/* use_gpu */ ctx_params.use_gpu,
|
||||
/* device */ ctx_params.device,
|
||||
/* flash_attn_type */ mtmd_get_clip_flash_attn_type(ctx_params.flash_attn_type),
|
||||
/* image_min_tokens */ ctx_params.image_min_tokens,
|
||||
/* image_max_tokens */ ctx_params.image_max_tokens,
|
||||
@@ -1076,6 +1082,25 @@ void mtmd_free(mtmd_context * ctx) {
|
||||
delete ctx;
|
||||
}
|
||||
|
||||
std::vector<std::vector<const mtmd_bitmap *>> mtmd_group_mergeable_bitmaps(std::vector<mtmd_input_part> & parts, int n_merge) {
|
||||
std::vector<std::vector<const mtmd_bitmap *>> output;
|
||||
for (size_t i = 0; i < parts.size(); i++) {
|
||||
if (parts[i].bitmap == nullptr) {
|
||||
continue; // text part
|
||||
}
|
||||
const bool has_next = n_merge > 1 && i + 1 < parts.size() && parts[i + 1].bitmap != nullptr;
|
||||
if (has_next && parts[i].bitmap->can_merge_with(*parts[i + 1].bitmap)) {
|
||||
LOG_DBG("%s: merging 2 frames at part index %zu and %zu\n", __func__, i, i + 1);
|
||||
output.push_back({parts[i].bitmap, parts[i + 1].bitmap});
|
||||
parts.erase(parts.begin() + i + 1);
|
||||
continue;
|
||||
}
|
||||
LOG_DBG("%s: no merging for part index %zu\n", __func__, i);
|
||||
output.push_back({parts[i].bitmap});
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
struct mtmd_tokenizer {
|
||||
mtmd_context * ctx;
|
||||
|
||||
@@ -1084,10 +1109,7 @@ struct mtmd_tokenizer {
|
||||
bool parse_special;
|
||||
const llama_vocab * vocab;
|
||||
|
||||
struct part {
|
||||
std::string text;
|
||||
const mtmd_bitmap * bitmap;
|
||||
};
|
||||
using part = mtmd_input_part;
|
||||
std::vector<part> parts;
|
||||
// these will be freed when mtmd_tokenizer finishes
|
||||
std::vector<mtmd::bitmap> bm_from_lazy; // TODO @ngxson : refactor, free bm_from_lazy progressively
|
||||
@@ -1192,34 +1214,7 @@ struct mtmd_tokenizer {
|
||||
GGML_ASSERT(n_merge_frames <= 2 && "we only support merging maximum 2 images for now; open an issue if this model supports merging more");
|
||||
}
|
||||
|
||||
// Build merged_bitmaps: each entry is a group of 1 or 2 bitmaps.
|
||||
// For consecutive mergeable bitmap parts, merge them and collapse the second part out of this->parts.
|
||||
std::vector<std::vector<const mtmd_bitmap *>> merged_bitmaps;
|
||||
if (n_merge_frames > 1) {
|
||||
for (size_t i = 0; i < parts.size(); ++i) {
|
||||
if (parts[i].bitmap == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (i + 1 < parts.size() && parts[i + 1].bitmap != nullptr) {
|
||||
const mtmd_bitmap * bm_a = parts[i].bitmap;
|
||||
const mtmd_bitmap * bm_b = parts[i + 1].bitmap;
|
||||
if (bm_a->can_merge_with(*bm_b)) {
|
||||
LOG_DBG("%s: merging 2 frames at part index %zu and %zu\n", __func__, i, i + 1);
|
||||
merged_bitmaps.push_back({bm_a, bm_b});
|
||||
parts.erase(parts.begin() + i + 1); // collapse the second bitmap part
|
||||
continue;
|
||||
}
|
||||
}
|
||||
LOG_DBG("%s: no merging for part index %zu\n", __func__, i);
|
||||
merged_bitmaps.push_back({parts[i].bitmap});
|
||||
}
|
||||
} else {
|
||||
for (const auto & p : parts) {
|
||||
if (p.bitmap != nullptr) {
|
||||
merged_bitmaps.push_back({p.bitmap});
|
||||
}
|
||||
}
|
||||
}
|
||||
auto merged_bitmaps = mtmd_group_mergeable_bitmaps(parts, n_merge_frames);
|
||||
|
||||
size_t i_bm = 0;
|
||||
for (const auto & p : parts) {
|
||||
@@ -2200,6 +2195,10 @@ void mtmd_bitmap_set_id(mtmd_bitmap * bitmap, const char * id) {
|
||||
}
|
||||
}
|
||||
|
||||
void mtmd_bitmap_set_mergeable(mtmd_bitmap * bitmap, bool mergeable) {
|
||||
bitmap->mergeable = mergeable;
|
||||
}
|
||||
|
||||
mtmd_bitmap * mtmd_bitmap_init_lazy(mtmd_context * ctx,
|
||||
const char * id,
|
||||
void * user_data,
|
||||
|
||||
+5
-1
@@ -89,6 +89,7 @@ typedef bool (*mtmd_progress_callback)(float progress, void * user_data);
|
||||
|
||||
struct mtmd_context_params {
|
||||
bool use_gpu;
|
||||
ggml_backend_dev_t device;
|
||||
bool print_timings;
|
||||
int n_threads;
|
||||
const char * image_marker; // deprecated, use media_marker instead
|
||||
@@ -154,7 +155,8 @@ MTMD_API const char * mtmd_get_marker(const mtmd_context * ctx);
|
||||
// length of data must be nx * ny * 3
|
||||
// the data is in RGBRGBRGB... format
|
||||
// note: some video-capable models (i.e. qwen-vl) can merge consecutive bitmaps
|
||||
// into one chunk, mtmd_tokenize() will automatically handle this
|
||||
// into one chunk; mtmd_tokenize() handles this, but remember to set
|
||||
// mtmd_bitmap_set_mergeable(true) for every frame
|
||||
// if bitmap is audio:
|
||||
// length of data must be n_samples * sizeof(float)
|
||||
// the data is in float format (PCM F32)
|
||||
@@ -175,6 +177,8 @@ MTMD_API void mtmd_bitmap_free (mtmd_bitmap * bitmap);
|
||||
// these getters/setters are dedicated functions, so you can for example calculate the hash of the image based on mtmd_bitmap_get_data()
|
||||
MTMD_API const char * mtmd_bitmap_get_id(const mtmd_bitmap * bitmap);
|
||||
MTMD_API void mtmd_bitmap_set_id(mtmd_bitmap * bitmap, const char * id);
|
||||
// if true, this bitmap can be merged (temporal merge) with an adjacent mergeable bitmap by certain video input models
|
||||
MTMD_API void mtmd_bitmap_set_mergeable(mtmd_bitmap * bitmap, bool mergeable);
|
||||
|
||||
// mtmd_bitmap lazy
|
||||
//
|
||||
|
||||
@@ -291,6 +291,36 @@ The flow for downloading a new model:
|
||||
- If a stop request comes in, the router asks the child process to stop (same mechanism as running a model in child process)
|
||||
- Otherwise, upon completion, we call `load_models()` to refresh the list of models
|
||||
|
||||
### Sleep mode
|
||||
|
||||
Sleep mode was initially introduced in PR [#18228](https://github.com/ggml-org/llama.cpp/pull/18228). The main idea is to have:
|
||||
- `server_queue` keeping track of the idle timeout
|
||||
- When the timeout is detected, `server_queue` signals to `server_context_impl` that it should go into sleep
|
||||
- `server_context_impl` frees all `llama_context` and `mtmd_context`
|
||||
|
||||
Compared to simply exiting the whole process, this approach allows accessing some read-only endpoints during sleep, while also handling wakeup-on-request. Any inference request will wake the server up.
|
||||
|
||||
Call stack on entering sleeping:
|
||||
- `server_queue::start_loop` (main thread) sees no task for `idle_sleep_ms` --> `sleeping = true`
|
||||
- `cb0(true)` --> `server_routes::update_cached_responses`
|
||||
- snapshots `/props`, `/models` and metrics; the model is still alive here
|
||||
- `cb1(true)` --> `server_context_impl::handle_sleeping_state`
|
||||
- `callback_state(SERVER_STATE_SLEEPING)` --> reported to router in child mode
|
||||
- `destroy()` --> frees `llama_context` and `mtmd_context`
|
||||
- `condition_tasks.wait` until `req_stop_sleeping`
|
||||
|
||||
Call stack on waking up:
|
||||
- `server_res_generator` constructor (HTTP thread) --> `server_queue::wait_until_no_sleep`
|
||||
- sets `req_stop_sleeping = true`, then waits until `sleeping == false`
|
||||
- `server_queue::start_loop` (main thread) wakes up
|
||||
- `cb1(false)` --> `server_context_impl::handle_sleeping_state`
|
||||
- `load_model()`, which then emits `callback_state(SERVER_STATE_READY)`
|
||||
- `cb0(false)` --> `server_routes::update_cached_responses`
|
||||
- nothing to do, the cache is only read during sleep
|
||||
- `sleeping = false` --> `notify_all` unblocks the HTTP thread, the request is handled as usual
|
||||
|
||||
Endpoints created with `create_response(true)` (`/health`, `/props`, `/models`, `/metrics`) skip `wait_until_no_sleep`, so they answer from the cached responses instead of waking the server.
|
||||
|
||||
### Notable Related PRs
|
||||
|
||||
- Initial server implementation: https://github.com/ggml-org/llama.cpp/pull/1443
|
||||
|
||||
@@ -178,6 +178,7 @@ For the full list of features, please refer to [server's changelog](https://gith
|
||||
| `-mmu, --mmproj-url URL` | URL to a multimodal projector file. see tools/mtmd/README.md<br/>(env: LLAMA_ARG_MMPROJ_URL) |
|
||||
| `--mmproj-auto, --no-mmproj, --no-mmproj-auto` | whether to use multimodal projector file (if available), useful when using -hf (default: enabled)<br/>(env: LLAMA_ARG_MMPROJ_AUTO) |
|
||||
| `--mmproj-offload, --no-mmproj-offload` | whether to enable GPU offloading for multimodal projector (default: enabled)<br/>(env: LLAMA_ARG_MMPROJ_OFFLOAD) |
|
||||
| `-mmdev, --mmproj-device DEVICE` | device to use for multimodal projector (none = don't offload, default: auto)<br/>use --list-devices to see a list of available devices<br/>(env: MTMD_BACKEND_DEVICE) |
|
||||
| `--image-min-tokens N` | minimum number of tokens each image can take, only used by vision models with dynamic resolution (default: read from model)<br/>(env: LLAMA_ARG_IMAGE_MIN_TOKENS) |
|
||||
| `--image-max-tokens N` | maximum number of tokens each image can take, only used by vision models with dynamic resolution (default: read from model)<br/>(env: LLAMA_ARG_IMAGE_MAX_TOKENS) |
|
||||
| `--mtmd-batch-max-tokens N` | maximum number of image tokens per batch when encoding images (default: 1024)<br/>(env: LLAMA_ARG_MTMD_BATCH_MAX_TOKENS) |
|
||||
@@ -196,11 +197,11 @@ For the full list of features, please refer to [server's changelog](https://gith
|
||||
| `--ui-config, --webui-config JSON` | JSON that provides default UI settings (overrides UI defaults)<br/>(env: LLAMA_ARG_UI_CONFIG) |
|
||||
| `--ui-config-file, --webui-config-file PATH` | JSON file that provides default UI settings (overrides UI defaults)<br/>(env: LLAMA_ARG_UI_CONFIG_FILE) |
|
||||
| `--ui-mcp-proxy, --webui-mcp-proxy, --no-ui-mcp-proxy, --no-webui-mcp-proxy` | experimental: whether to enable MCP CORS proxy - do not enable in untrusted environments (default: disabled)<br/>(env: LLAMA_ARG_UI_MCP_PROXY) |
|
||||
| `--tools TOOL1,TOOL2,...` | experimental: whether to enable server tools for AI agents - do not enable in untrusted environments (default: no tools)<br/>specify "all" to enable all tools<br/>available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, get_info<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_TOOLS) |
|
||||
| `--tools TOOL1,TOOL2,...` | experimental: whether to enable built-in tools for AI agents - do not enable in untrusted environments (default: no tools)<br/>specify "all" to enable all tools<br/>available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, get_info<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_TOOLS) |
|
||||
| `--tools-runtime OPTION` | experimental: run tools in a separate runtime environment (default: none, use host environment)<br/>available options:<br/> 'docker:<image>', 'podman:<image>': spin up a new container and reuse it for all invocations, clean up on server exit<br/> 'docker-container:<id>', 'podman-container:<id>': use an existing container by ID, won't stop on server exit<br/> 'ssh:<target>': run tools on a remote POSIX host over SSH, key-based auth and a trusted host key are required<br/><br/>(env: LLAMA_ARG_TOOLS_RUNTIME) |
|
||||
| `--mcp-servers-config PATH` | experimental: path to JSON file with MCP server definitions (Cursor-compatible format) - do not enable in untrusted environments (default: none)<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_MCP_SERVERS_CONFIG) |
|
||||
| `--mcp-servers-json JSON` | experimental: inline JSON with MCP server definitions (Cursor-compatible format) - do not enable in untrusted environments (default: none)<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_MCP_SERVERS_JSON) |
|
||||
| `-ag, --agent, -no-ag, --no-agent` | whether to enable CORS proxy and all server tools - do not enable in untrusted environments (default: disabled)<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_AGENT) |
|
||||
| `-ag, --agent, -no-ag, --no-agent` | whether to enable CORS proxy and all built-in tools - do not enable in untrusted environments (default: disabled)<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_AGENT) |
|
||||
| `--ui, --webui, --no-ui, --no-webui` | whether to enable the Web UI (default: enabled)<br/>(env: LLAMA_ARG_UI) |
|
||||
| `--embedding, --embeddings` | restrict to only support embedding use case; use only with dedicated embedding models (default: disabled)<br/>(env: LLAMA_ARG_EMBEDDINGS) |
|
||||
| `--rerank, --reranking` | enable reranking endpoint on server (default: disabled)<br/>(env: LLAMA_ARG_RERANKING) |
|
||||
@@ -1757,7 +1758,7 @@ The precedence rule for preset options is as follows:
|
||||
3. **Global options** defined in the preset file (`[*]`)
|
||||
|
||||
We also offer additional options that are exclusive to presets (these aren't treated as command-line arguments):
|
||||
- `load-on-startup` (boolean): Controls whether the model loads automatically when the server starts
|
||||
- `load-on-startup` (boolean): Controls whether the model loads automatically when the server starts. Only applies at startup: if the model list is reloaded later (for example after editing the preset file), a newly added model is listed but not loaded
|
||||
- `stop-timeout` (int, seconds): After requested unload, wait for this many seconds before forcing termination (default: 10)
|
||||
- `dedup-cache-models` (boolean): When the preset uses `hf-repo` pointing to a model that is already downloaded, hide the corresponding cached model entry from `GET /models` (the preset entry remains visible). Set it in the `[*]` section to apply to all presets.
|
||||
|
||||
@@ -2071,6 +2072,7 @@ Note that the following endpoints are exempt from being considered as incoming t
|
||||
- `GET /health`
|
||||
- `GET /props`
|
||||
- `GET /models`
|
||||
- `GET /metrics`
|
||||
|
||||
## More examples
|
||||
|
||||
|
||||
+237
-152
@@ -818,6 +818,14 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
server_metrics get_metrics() const {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
void reset_metrics_bucket() {
|
||||
metrics.reset_bucket();
|
||||
}
|
||||
|
||||
private:
|
||||
// note: accessing these fields outside of this class is not thread-safe
|
||||
// use server_context methods instead
|
||||
@@ -898,6 +906,10 @@ private:
|
||||
void handle_sleeping_state(bool new_state) {
|
||||
GGML_ASSERT(sleeping != new_state);
|
||||
if (new_state) {
|
||||
if (callback_state) {
|
||||
callback_state(SERVER_STATE_SLEEPING, {});
|
||||
// note: for sleeping == false, event is emitted by load_model()
|
||||
}
|
||||
SRV_INF("%s", "server is entering sleeping state\n");
|
||||
destroy();
|
||||
} else {
|
||||
@@ -986,6 +998,7 @@ private:
|
||||
mtmd_context_params mparams = mtmd_context_params_default();
|
||||
if (has_mmproj) {
|
||||
mparams.use_gpu = params_base.mmproj_use_gpu;
|
||||
mparams.device = params_base.mmproj_device;
|
||||
mparams.print_timings = false;
|
||||
mparams.n_threads = params_base.cpuparams.n_threads;
|
||||
mparams.flash_attn_type = params_base.flash_attn_type;
|
||||
@@ -2290,8 +2303,8 @@ private:
|
||||
|
||||
// returns false to decline the task, it is offered again after the decode is done
|
||||
bool process_single_task(server_task && task, bool is_yielding) {
|
||||
// while yielding, an encode / decode is running and only accessing metrics is safe
|
||||
if (is_yielding && task.type != SERVER_TASK_TYPE_METRICS) {
|
||||
// while yielding, an encode / decode is running and only reading the server state is safe
|
||||
if (is_yielding && task.type != SERVER_TASK_TYPE_METRICS && task.type != SERVER_TASK_TYPE_SLOT_GET) {
|
||||
SRV_DBG("decoding, decline task, id_task = %d\n", task.id);
|
||||
return false;
|
||||
}
|
||||
@@ -2417,28 +2430,17 @@ private:
|
||||
} break;
|
||||
case SERVER_TASK_TYPE_METRICS:
|
||||
{
|
||||
json slots_data = json::array();
|
||||
|
||||
int n_idle_slots = 0;
|
||||
int n_processing_slots = 0;
|
||||
|
||||
for (server_slot & slot : slots) {
|
||||
json slot_data = slot.to_json(slots_debug == 0);
|
||||
|
||||
if (slot.is_processing()) {
|
||||
n_processing_slots++;
|
||||
} else {
|
||||
n_idle_slots++;
|
||||
}
|
||||
|
||||
slots_data.push_back(slot_data);
|
||||
}
|
||||
SRV_DBG("n_idle_slots = %d, n_processing_slots = %d\n", n_idle_slots, n_processing_slots);
|
||||
SRV_DBG("n_processing_slots = %d\n", n_processing_slots);
|
||||
|
||||
auto res = std::make_unique<server_task_result_metrics>();
|
||||
res->id = task.id;
|
||||
res->slots_data = std::move(slots_data);
|
||||
res->n_idle_slots = n_idle_slots;
|
||||
res->n_processing_slots = n_processing_slots;
|
||||
res->n_tasks_deferred = queue_tasks.queue_tasks_deferred_size();
|
||||
res->metrics = metrics;
|
||||
@@ -2446,6 +2448,28 @@ private:
|
||||
if (task.metrics_reset_bucket) {
|
||||
metrics.reset_bucket();
|
||||
}
|
||||
queue_results.send(std::move(res));
|
||||
} break;
|
||||
case SERVER_TASK_TYPE_SLOT_GET:
|
||||
{
|
||||
json slots_data = json::array();
|
||||
|
||||
int n_idle_slots = 0;
|
||||
|
||||
for (server_slot & slot : slots) {
|
||||
if (!slot.is_processing()) {
|
||||
n_idle_slots++;
|
||||
}
|
||||
|
||||
slots_data.push_back(slot.to_json(slots_debug == 0));
|
||||
}
|
||||
SRV_DBG("n_idle_slots = %d\n", n_idle_slots);
|
||||
|
||||
auto res = std::make_unique<server_task_result_slots>();
|
||||
res->id = task.id;
|
||||
res->slots_data = std::move(slots_data);
|
||||
res->n_idle_slots = n_idle_slots;
|
||||
|
||||
queue_results.send(std::move(res));
|
||||
} break;
|
||||
case SERVER_TASK_TYPE_SLOT_SAVE:
|
||||
@@ -4142,12 +4166,6 @@ struct server_res_generator : server_res_spipe {
|
||||
|
||||
void server_context::set_state_callback(server_state_callback_t callback) {
|
||||
impl->callback_state = std::move(callback);
|
||||
impl->queue_tasks.on_sleeping_state([this](bool sleeping) {
|
||||
if (sleeping) {
|
||||
impl->callback_state(SERVER_STATE_SLEEPING, {});
|
||||
}
|
||||
// for sleeping == false, event is emitted by load_model()
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
@@ -4431,6 +4449,119 @@ server_routes::server_routes(const common_params & params, server_context & ctx_
|
||||
queue_tasks(ctx_server.impl->queue_tasks),
|
||||
queue_results(ctx_server.impl->queue_results) {
|
||||
init_routes();
|
||||
|
||||
// note: this must be registered before load_model()
|
||||
// so that on sleep phase, the callback is called before ctx is destroyed
|
||||
queue_tasks.on_sleeping_state([this](bool is_sleeping) {
|
||||
update_cached_responses(is_sleeping);
|
||||
});
|
||||
}
|
||||
|
||||
static json get_res_model_info(const server_context_meta & meta) {
|
||||
// note: do NOT use ctx_server here, otherwise it's not possible to use this during sleep
|
||||
|
||||
return {
|
||||
{"id", meta.model_name},
|
||||
{"aliases", meta.model_aliases},
|
||||
{"tags", meta.model_tags},
|
||||
{"object", "model"},
|
||||
{"created", std::time(0)},
|
||||
{"owned_by", "llamacpp"},
|
||||
{"meta", {
|
||||
{"vocab_type", meta.model_vocab_type},
|
||||
{"n_vocab", meta.model_vocab_n_tokens},
|
||||
{"n_ctx", meta.slot_n_ctx},
|
||||
{"n_ctx_train", meta.model_n_ctx_train},
|
||||
{"n_embd", meta.model_n_embd_inp},
|
||||
{"n_params", meta.model_n_params},
|
||||
{"size", meta.model_size},
|
||||
{"ftype", meta.model_ftype},
|
||||
}},
|
||||
};
|
||||
}
|
||||
|
||||
static json get_res_models(const server_context_meta & meta) {
|
||||
// note: do NOT use ctx_server here, otherwise it's not possible to use this during sleep
|
||||
|
||||
return {
|
||||
{"models", {
|
||||
{
|
||||
{"name", meta.model_name},
|
||||
{"model", meta.model_name},
|
||||
{"modified_at", ""},
|
||||
{"size", ""},
|
||||
{"digest", ""}, // dummy value, llama.cpp does not support managing model file's hash
|
||||
{"type", "model"},
|
||||
{"description", ""},
|
||||
{"tags", {""}},
|
||||
{"capabilities", meta.has_mtmd ? json({"completion","multimodal"}) : json({"completion"})},
|
||||
{"parameters", ""},
|
||||
{"details", {
|
||||
{"parent_model", ""},
|
||||
{"format", "gguf"},
|
||||
{"family", ""},
|
||||
{"families", {""}},
|
||||
{"parameter_size", ""},
|
||||
{"quantization_level", ""}
|
||||
}}
|
||||
}
|
||||
}},
|
||||
{"object", "list"},
|
||||
{"data", {
|
||||
get_res_model_info(meta),
|
||||
}}
|
||||
};
|
||||
}
|
||||
|
||||
static json get_res_props(const server_context_meta & meta, const common_params & params, bool is_sleeping) {
|
||||
// note: do NOT use ctx_server here, otherwise it's not possible to use this during sleep
|
||||
|
||||
task_params tparams;
|
||||
tparams.sampling = params.sampling;
|
||||
json default_generation_settings_for_props = json {
|
||||
{ "params", tparams.to_json(true) },
|
||||
{ "n_ctx", meta.slot_n_ctx },
|
||||
};
|
||||
|
||||
std::string tmpl_default = common_chat_templates_source(meta.chat_params.tmpls.get(), "");
|
||||
std::string tmpl_tools = common_chat_templates_source(meta.chat_params.tmpls.get(), "tool_use");
|
||||
|
||||
json props = {
|
||||
{ "default_generation_settings", default_generation_settings_for_props },
|
||||
{ "total_slots", params.n_parallel },
|
||||
{ "model_alias", meta.model_name },
|
||||
{ "model_ftype", meta.model_ftype },
|
||||
{ "model_path", meta.model_path },
|
||||
{ "modalities", json {
|
||||
{"vision", meta.has_inp_image},
|
||||
{"video", meta.has_inp_video},
|
||||
{"audio", meta.has_inp_audio},
|
||||
} },
|
||||
{ "media_marker", get_media_marker() },
|
||||
{ "endpoint_slots", params.endpoint_slots },
|
||||
{ "endpoint_props", params.endpoint_props },
|
||||
{ "endpoint_metrics", params.endpoint_metrics },
|
||||
{ "ui", params.ui },
|
||||
{ "ui_settings", meta.json_ui_settings },
|
||||
{ "chat_template", tmpl_default },
|
||||
{ "chat_template_caps", meta.chat_template_caps },
|
||||
{ "bos_token", meta.bos_token_str },
|
||||
{ "eos_token", meta.eos_token_str },
|
||||
{ "build_info", meta.build_info },
|
||||
{ "is_sleeping", is_sleeping },
|
||||
{ "cors_proxy_enabled", params.ui_mcp_proxy },
|
||||
};
|
||||
if (params.use_jinja) {
|
||||
if (!tmpl_tools.empty()) {
|
||||
props["chat_template_tool_use"] = tmpl_tools;
|
||||
}
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
json server_routes::get_model_info() const {
|
||||
return get_res_model_info(*meta);
|
||||
}
|
||||
|
||||
void server_routes::init_routes() {
|
||||
@@ -4451,41 +4582,64 @@ void server_routes::init_routes() {
|
||||
};
|
||||
|
||||
this->get_metrics = [this](const server_http_req & req) {
|
||||
auto res = create_response();
|
||||
auto res = create_response(true);
|
||||
if (!params.endpoint_metrics) {
|
||||
res->error(format_error_response("This server does not support metrics endpoint. Start it with `--metrics`", ERROR_TYPE_NOT_SUPPORTED));
|
||||
return res;
|
||||
}
|
||||
|
||||
// request slots data using task queue
|
||||
{
|
||||
server_task task(SERVER_TASK_TYPE_METRICS);
|
||||
task.id = res->rd.get_new_id();
|
||||
// render response using cached_metrics
|
||||
auto use_cached_metrics = [&]() {
|
||||
std::unique_lock<std::mutex> lock(mutex_cache);
|
||||
res->headers["Process-Start-Time-Unix"] = std::to_string(cached_metrics.t_start);
|
||||
server_task_result_metrics tmp;
|
||||
tmp.metrics = cached_metrics;
|
||||
res->content_type = "text/plain; version=0.0.4";
|
||||
res->status = 200;
|
||||
res->data = tmp.to_metrics();
|
||||
// the gauges are averaged over the window between two scrapes
|
||||
task.metrics_reset_bucket = true;
|
||||
res->rd.post_task(std::move(task), true); // high-priority task
|
||||
cached_metrics.reset_bucket();
|
||||
should_reset_buckets = true;
|
||||
};
|
||||
|
||||
if (queue_tasks.is_sleeping()) {
|
||||
use_cached_metrics();
|
||||
|
||||
} else {
|
||||
// request slots data using task queue
|
||||
{
|
||||
server_task task(SERVER_TASK_TYPE_METRICS);
|
||||
task.id = res->rd.get_new_id();
|
||||
// the gauges are averaged over the window between two scrapes
|
||||
task.metrics_reset_bucket = true;
|
||||
res->rd.post_task(std::move(task), true); // high-priority task
|
||||
}
|
||||
|
||||
// a task posted right before sleeping is never processed, do not wait for it
|
||||
auto result = res->rd.next([&]{
|
||||
return req.should_stop() || queue_tasks.is_sleeping();
|
||||
});
|
||||
if (!result) {
|
||||
if (!req.should_stop()) {
|
||||
use_cached_metrics();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
if (result->is_error()) {
|
||||
res->error(result->to_json());
|
||||
return res;
|
||||
}
|
||||
|
||||
auto res_task = dynamic_cast<server_task_result_metrics*>(result.get());
|
||||
GGML_ASSERT(res_task != nullptr);
|
||||
|
||||
res->headers["Process-Start-Time-Unix"] = std::to_string(res_task->metrics.t_start);
|
||||
res->content_type = "text/plain; version=0.0.4";
|
||||
res->status = 200;
|
||||
res->data = res_task->to_metrics();
|
||||
}
|
||||
|
||||
// get the result
|
||||
auto result = res->rd.next(req.should_stop);
|
||||
if (!result) {
|
||||
// connection was closed
|
||||
GGML_ASSERT(req.should_stop());
|
||||
return res;
|
||||
}
|
||||
|
||||
if (result->is_error()) {
|
||||
res->error(result->to_json());
|
||||
return res;
|
||||
}
|
||||
|
||||
auto res_task = dynamic_cast<server_task_result_metrics*>(result.get());
|
||||
GGML_ASSERT(res_task != nullptr);
|
||||
|
||||
res->headers["Process-Start-Time-Unix"] = std::to_string(res_task->metrics.t_start);
|
||||
res->content_type = "text/plain; version=0.0.4";
|
||||
res->status = 200;
|
||||
res->data = res_task->to_metrics();
|
||||
return res;
|
||||
};
|
||||
|
||||
@@ -4498,7 +4652,7 @@ void server_routes::init_routes() {
|
||||
|
||||
// request slots data using task queue
|
||||
{
|
||||
server_task task(SERVER_TASK_TYPE_METRICS);
|
||||
server_task task(SERVER_TASK_TYPE_SLOT_GET);
|
||||
task.id = res->rd.get_new_id();
|
||||
res->rd.post_task(std::move(task), true); // high-priority task
|
||||
}
|
||||
@@ -4516,7 +4670,7 @@ void server_routes::init_routes() {
|
||||
return res;
|
||||
}
|
||||
|
||||
auto * res_task = dynamic_cast<server_task_result_metrics*>(result.get());
|
||||
auto * res_task = dynamic_cast<server_task_result_slots*>(result.get());
|
||||
GGML_ASSERT(res_task != nullptr);
|
||||
|
||||
// optionally return "fail_on_no_slot" error
|
||||
@@ -4566,53 +4720,13 @@ void server_routes::init_routes() {
|
||||
|
||||
this->get_props = [this](const server_http_req &) {
|
||||
auto res = create_response(true);
|
||||
|
||||
// this endpoint can be accessed during sleeping
|
||||
// the next LOC is to avoid someone accidentally use ctx_server
|
||||
bool ctx_server; // do NOT delete this line
|
||||
GGML_UNUSED(ctx_server);
|
||||
|
||||
task_params tparams;
|
||||
tparams.sampling = params.sampling;
|
||||
json default_generation_settings_for_props = json {
|
||||
{ "params", tparams.to_json(true) },
|
||||
{ "n_ctx", meta->slot_n_ctx },
|
||||
};
|
||||
|
||||
std::string tmpl_default = common_chat_templates_source(meta->chat_params.tmpls.get(), "");
|
||||
std::string tmpl_tools = common_chat_templates_source(meta->chat_params.tmpls.get(), "tool_use");
|
||||
|
||||
json props = {
|
||||
{ "default_generation_settings", default_generation_settings_for_props },
|
||||
{ "total_slots", params.n_parallel },
|
||||
{ "model_alias", meta->model_name },
|
||||
{ "model_ftype", meta->model_ftype },
|
||||
{ "model_path", meta->model_path },
|
||||
{ "modalities", json {
|
||||
{"vision", meta->has_inp_image},
|
||||
{"video", meta->has_inp_video},
|
||||
{"audio", meta->has_inp_audio},
|
||||
} },
|
||||
{ "media_marker", get_media_marker() },
|
||||
{ "endpoint_slots", params.endpoint_slots },
|
||||
{ "endpoint_props", params.endpoint_props },
|
||||
{ "endpoint_metrics", params.endpoint_metrics },
|
||||
{ "ui", params.ui },
|
||||
{ "ui_settings", meta->json_ui_settings },
|
||||
{ "chat_template", tmpl_default },
|
||||
{ "chat_template_caps", meta->chat_template_caps },
|
||||
{ "bos_token", meta->bos_token_str },
|
||||
{ "eos_token", meta->eos_token_str },
|
||||
{ "build_info", meta->build_info },
|
||||
{ "is_sleeping", queue_tasks.is_sleeping() },
|
||||
{ "cors_proxy_enabled", params.ui_mcp_proxy },
|
||||
};
|
||||
if (params.use_jinja) {
|
||||
if (!tmpl_tools.empty()) {
|
||||
props["chat_template_tool_use"] = tmpl_tools;
|
||||
}
|
||||
// note: do NOT use ctx_server here, this endpoint must be accessible during sleep
|
||||
if (queue_tasks.is_sleeping()) {
|
||||
std::unique_lock<std::mutex> lock(mutex_cache);
|
||||
res->ok(cached_props);
|
||||
} else {
|
||||
res->ok(get_res_props(*meta, params, false));
|
||||
}
|
||||
res->ok(props);
|
||||
return res;
|
||||
};
|
||||
|
||||
@@ -4874,42 +4988,13 @@ void server_routes::init_routes() {
|
||||
|
||||
this->get_models = [this](const server_http_req &) {
|
||||
auto res = create_response(true);
|
||||
|
||||
// this endpoint can be accessed during sleeping
|
||||
// the next LOC is to avoid someone accidentally use ctx_server
|
||||
bool ctx_server; // do NOT delete this line
|
||||
GGML_UNUSED(ctx_server);
|
||||
|
||||
json models = {
|
||||
{"models", {
|
||||
{
|
||||
{"name", meta->model_name},
|
||||
{"model", meta->model_name},
|
||||
{"modified_at", ""},
|
||||
{"size", ""},
|
||||
{"digest", ""}, // dummy value, llama.cpp does not support managing model file's hash
|
||||
{"type", "model"},
|
||||
{"description", ""},
|
||||
{"tags", {""}},
|
||||
{"capabilities", meta->has_mtmd ? json({"completion","multimodal"}) : json({"completion"})},
|
||||
{"parameters", ""},
|
||||
{"details", {
|
||||
{"parent_model", ""},
|
||||
{"format", "gguf"},
|
||||
{"family", ""},
|
||||
{"families", {""}},
|
||||
{"parameter_size", ""},
|
||||
{"quantization_level", ""}
|
||||
}}
|
||||
}
|
||||
}},
|
||||
{"object", "list"},
|
||||
{"data", {
|
||||
get_model_info(),
|
||||
}}
|
||||
};
|
||||
|
||||
res->ok(models);
|
||||
// note: do NOT use ctx_server here, this endpoint must be accessible during sleep
|
||||
if (queue_tasks.is_sleeping()) {
|
||||
std::unique_lock<std::mutex> lock(mutex_cache);
|
||||
res->ok(cached_models);
|
||||
} else {
|
||||
res->ok(get_res_models(*meta));
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
@@ -5119,27 +5204,6 @@ void server_routes::init_routes() {
|
||||
};
|
||||
}
|
||||
|
||||
json server_routes::get_model_info() const {
|
||||
return json {
|
||||
{"id", meta->model_name},
|
||||
{"aliases", meta->model_aliases},
|
||||
{"tags", meta->model_tags},
|
||||
{"object", "model"},
|
||||
{"created", std::time(0)},
|
||||
{"owned_by", "llamacpp"},
|
||||
{"meta", {
|
||||
{"vocab_type", meta->model_vocab_type},
|
||||
{"n_vocab", meta->model_vocab_n_tokens},
|
||||
{"n_ctx", meta->slot_n_ctx},
|
||||
{"n_ctx_train", meta->model_n_ctx_train},
|
||||
{"n_embd", meta->model_n_embd_inp},
|
||||
{"n_params", meta->model_n_params},
|
||||
{"size", meta->model_size},
|
||||
{"ftype", meta->model_ftype},
|
||||
}},
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<server_res_generator> server_routes::handle_slots_save(const server_http_req & req, int id_slot) {
|
||||
auto res = create_response();
|
||||
const json request_data = json::parse(req.body);
|
||||
@@ -5388,3 +5452,24 @@ std::unique_ptr<server_res_generator> server_routes::handle_count_tokens(const l
|
||||
res->ok(response);
|
||||
return res;
|
||||
}
|
||||
|
||||
void server_routes::update_cached_responses(bool is_sleeping) {
|
||||
// caller is task_queue, so ctx_server can be accessed without holding locks
|
||||
std::unique_lock<std::mutex> lock(mutex_cache);
|
||||
|
||||
if (is_sleeping) {
|
||||
cached_models = get_res_models(*meta);
|
||||
cached_props = get_res_props(*meta, params, true);
|
||||
cached_metrics = ctx_server.get_metrics();
|
||||
|
||||
should_reset_buckets = false;
|
||||
|
||||
SRV_DBG("%s\n", "cached responses updated");
|
||||
|
||||
} else if (should_reset_buckets) {
|
||||
// a scrape during sleep already reported these buckets
|
||||
ctx_server.reset_metrics_bucket();
|
||||
|
||||
should_reset_buckets = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
struct server_context_impl; // private implementation
|
||||
@@ -174,9 +175,19 @@ private:
|
||||
std::unique_ptr<const server_context_meta> meta;
|
||||
|
||||
const common_params & params;
|
||||
const server_context_impl & ctx_server;
|
||||
server_context_impl & ctx_server;
|
||||
|
||||
server_queue & queue_tasks;
|
||||
server_response & queue_results;
|
||||
std::unique_ptr<server_res_generator> create_response(bool bypass_sleep = false);
|
||||
|
||||
// cached responses, to be used during sleep
|
||||
std::mutex mutex_cache;
|
||||
json cached_models = nullptr;
|
||||
json cached_props = nullptr;
|
||||
server_metrics cached_metrics;
|
||||
// set when a scrape during sleep already reported the throughput buckets
|
||||
bool should_reset_buckets = false;
|
||||
// call right before sleep to update the cached responses
|
||||
void update_cached_responses(bool is_sleeping);
|
||||
};
|
||||
|
||||
@@ -198,8 +198,6 @@ bool server_http_context::init(const common_params & params) {
|
||||
std::unordered_set<std::string> endpoints {
|
||||
"/health",
|
||||
"/v1/health",
|
||||
"/models",
|
||||
"/v1/models",
|
||||
};
|
||||
endpoints.insert(frontend_paths.begin(), frontend_paths.end());
|
||||
return endpoints;
|
||||
|
||||
@@ -672,24 +672,26 @@ void server_models::load_models() {
|
||||
apply_hidden();
|
||||
log_available_models();
|
||||
|
||||
std::vector<std::string> models_to_load;
|
||||
for (const auto & [name, inst] : mapping) {
|
||||
std::string val;
|
||||
if (inst.meta.preset.get_option(COMMON_ARG_PRESET_LOAD_ON_STARTUP, val) && common_arg_utils::is_truthy(val)) {
|
||||
models_to_load.push_back(name);
|
||||
// skipped on reload, see startup_models
|
||||
if (startup_models.has_value()) {
|
||||
std::vector<std::string> models_to_load;
|
||||
for (const auto & [name, inst] : mapping) {
|
||||
std::string val;
|
||||
if (inst.meta.preset.get_option(COMMON_ARG_PRESET_LOAD_ON_STARTUP, val) && common_arg_utils::is_truthy(val)) {
|
||||
models_to_load.push_back(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((int)models_to_load.size() > base_params.models_max) {
|
||||
throw std::runtime_error(string_format(
|
||||
"number of models to load on startup (%zu) exceeds models_max (%d)",
|
||||
models_to_load.size(), base_params.models_max));
|
||||
if ((int)models_to_load.size() > base_params.models_max) {
|
||||
throw std::runtime_error(string_format(
|
||||
"number of models to load on startup (%zu) exceeds models_max (%d)",
|
||||
models_to_load.size(), base_params.models_max));
|
||||
}
|
||||
|
||||
// to be lazy-loaded after main() setup phase is completed
|
||||
startup_models = std::move(models_to_load);
|
||||
}
|
||||
|
||||
lk.unlock();
|
||||
for (const auto & name : models_to_load) {
|
||||
SRV_INF("(startup) loading model %s\n", name.c_str());
|
||||
load(name);
|
||||
}
|
||||
} else {
|
||||
// RELOAD: diff the new preset list against the current mapping and reconcile
|
||||
is_reloading = true;
|
||||
@@ -819,8 +821,8 @@ void server_models::load_models() {
|
||||
inst.meta.update_caps();
|
||||
}
|
||||
|
||||
// add models that are new in this reload
|
||||
std::vector<std::string> newly_added;
|
||||
// add models that are new in this reload, load-on-startup is not honored here since a
|
||||
// reload never spawns an instance
|
||||
for (const auto & [name, preset] : final_presets) {
|
||||
if (mapping.find(name) == mapping.end()) {
|
||||
server_model_meta meta{
|
||||
@@ -841,42 +843,40 @@ void server_models::load_models() {
|
||||
// /* need_download */ false,
|
||||
};
|
||||
add_model(std::move(meta));
|
||||
newly_added.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
apply_stop_timeout();
|
||||
apply_hidden();
|
||||
|
||||
// clear reload flag before unlocking for autoload - load() blocks on !is_reloading,
|
||||
// so clearing it here (while still locked) prevents a deadlock in the autoload calls below
|
||||
// clear reload flag under the lock, this releases the load() calls waiting on !is_reloading
|
||||
is_reloading = false;
|
||||
cv.notify_all();
|
||||
|
||||
log_available_models();
|
||||
|
||||
// collect autoload candidates while still under the lock
|
||||
std::vector<std::string> to_autoload;
|
||||
for (const auto & name : newly_added) {
|
||||
auto it = mapping.find(name);
|
||||
if (it != mapping.end()) {
|
||||
std::string val;
|
||||
if (it->second.meta.preset.get_option(COMMON_ARG_PRESET_LOAD_ON_STARTUP, val) && common_arg_utils::is_truthy(val)) {
|
||||
to_autoload.push_back(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lk.unlock();
|
||||
for (const auto & name : to_autoload) {
|
||||
SRV_INF("(reload) loading new model %s\n", name.c_str());
|
||||
load(name);
|
||||
}
|
||||
|
||||
notify_sse("models_reload", "*");
|
||||
}
|
||||
}
|
||||
|
||||
void server_models::load_startup_models() {
|
||||
std::vector<std::string> to_load;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mutex);
|
||||
if (!startup_models.has_value()) {
|
||||
return; // already drained
|
||||
}
|
||||
to_load = std::move(*startup_models);
|
||||
startup_models.reset();
|
||||
}
|
||||
for (const auto & name : to_load) {
|
||||
SRV_INF("(startup) loading model %s\n", name.c_str());
|
||||
load(name);
|
||||
}
|
||||
}
|
||||
|
||||
void server_models::update_meta(const std::string & name, const server_model_meta & meta) {
|
||||
std::lock_guard<std::mutex> lk(mutex);
|
||||
auto it = mapping.find(name);
|
||||
|
||||
@@ -136,6 +136,10 @@ private:
|
||||
// if true, the next get_meta() will trigger a reload of model list
|
||||
bool need_reload = false;
|
||||
|
||||
// models marked with load-on-startup, unset once load_startup_models() drains it
|
||||
// no value means the startup phase is over, so a reload must not queue anything
|
||||
std::optional<std::vector<std::string>> startup_models{std::in_place};
|
||||
|
||||
// conv_id -> model name that currently serves its stream session, lets the resumable stream
|
||||
// routes go straight to the owning child instead of polling every one. populated when
|
||||
// proxy_request forwards a POST carrying an X-Conversation-Id. best effort: a stale entry just
|
||||
@@ -231,6 +235,9 @@ public:
|
||||
// - if a model is not running, it will be added or updated according to the source
|
||||
void load_models();
|
||||
|
||||
// lazy-load startup_models, to be called after main() setup phase
|
||||
void load_startup_models();
|
||||
|
||||
// check if a model instance exists (thread-safe)
|
||||
bool has_model(const std::string & name);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
@@ -20,6 +21,10 @@
|
||||
// server_queue
|
||||
//
|
||||
|
||||
static bool task_resets_idle_timer(server_task_type type) {
|
||||
return type != SERVER_TASK_TYPE_METRICS;
|
||||
}
|
||||
|
||||
int server_queue::post(server_task && task, bool front) {
|
||||
std::unique_lock<std::mutex> lock(mutex_tasks);
|
||||
GGML_ASSERT(task.id != -1);
|
||||
@@ -27,20 +32,24 @@ int server_queue::post(server_task && task, bool front) {
|
||||
if (task.type == SERVER_TASK_TYPE_CANCEL) {
|
||||
cleanup_pending_task(task.id_target);
|
||||
}
|
||||
const int task_id = task.id;
|
||||
const int task_id = task.id;
|
||||
const bool reset_timer = task_resets_idle_timer(task.type);
|
||||
QUE_DBG("new task, id = %d, front = %d\n", task_id, front);
|
||||
if (front) {
|
||||
queue_tasks.push_front(std::move(task));
|
||||
} else {
|
||||
queue_tasks.push_back(std::move(task));
|
||||
}
|
||||
time_last_task = ggml_time_ms();
|
||||
if (reset_timer) {
|
||||
time_last_task = ggml_time_ms();
|
||||
}
|
||||
condition_tasks.notify_one();
|
||||
return task_id;
|
||||
}
|
||||
|
||||
int server_queue::post(std::vector<server_task> && tasks, bool front) {
|
||||
std::unique_lock<std::mutex> lock(mutex_tasks);
|
||||
bool reset_timer = false;
|
||||
for (auto & task : tasks) {
|
||||
if (task.id == -1) {
|
||||
task.id = id++;
|
||||
@@ -49,6 +58,7 @@ int server_queue::post(std::vector<server_task> && tasks, bool front) {
|
||||
if (task.type == SERVER_TASK_TYPE_CANCEL) {
|
||||
cleanup_pending_task(task.id_target);
|
||||
}
|
||||
reset_timer |= task_resets_idle_timer(task.type);
|
||||
QUE_DBG("new task, id = %d/%d, front = %d\n", task.id, (int) tasks.size(), front);
|
||||
if (front) {
|
||||
queue_tasks.push_front(std::move(task));
|
||||
@@ -56,7 +66,9 @@ int server_queue::post(std::vector<server_task> && tasks, bool front) {
|
||||
queue_tasks.push_back(std::move(task));
|
||||
}
|
||||
}
|
||||
time_last_task = ggml_time_ms();
|
||||
if (reset_timer) {
|
||||
time_last_task = ggml_time_ms();
|
||||
}
|
||||
condition_tasks.notify_one();
|
||||
return 0;
|
||||
}
|
||||
@@ -294,11 +306,14 @@ void server_queue::start_loop(int64_t idle_sleep_ms) {
|
||||
QUE_DBG("%s", "update slots\n");
|
||||
|
||||
// this will run the main inference process for all slots
|
||||
const int64_t t_update_slots = ggml_time_ms();
|
||||
callback_update_slots();
|
||||
{
|
||||
// update_slots() may take a while to finish, we need to make sure it's not counted as idle
|
||||
// shift instead of reset, so that non-task_resets_idle_timer tasks do not delay the sleep
|
||||
std::unique_lock<std::mutex> lock(mutex_tasks);
|
||||
time_last_task = ggml_time_ms();
|
||||
const int64_t now = ggml_time_ms();
|
||||
time_last_task = std::min(now, time_last_task + (now - t_update_slots));
|
||||
}
|
||||
|
||||
QUE_DBG("%s", "waiting for new tasks\n");
|
||||
@@ -312,7 +327,10 @@ void server_queue::start_loop(int64_t idle_sleep_ms) {
|
||||
if (should_sleep()) {
|
||||
QUE_INF("%s", "entering sleeping state\n");
|
||||
sleeping = true;
|
||||
callback_sleeping_state(true);
|
||||
// Call order cb0 -> cb1 -> cb{N}
|
||||
for (auto & cb : callback_sleeping_state) {
|
||||
cb(true);
|
||||
}
|
||||
req_stop_sleeping = false;
|
||||
// wait until we are requested to exit sleeping state
|
||||
condition_tasks.wait(lock, [&]{
|
||||
@@ -323,7 +341,10 @@ void server_queue::start_loop(int64_t idle_sleep_ms) {
|
||||
}
|
||||
QUE_INF("%s", "exiting sleeping state\n");
|
||||
req_stop_sleeping = false;
|
||||
callback_sleeping_state(false);
|
||||
// Call order cb{N} -> cb1 -> cb0
|
||||
for (size_t i = callback_sleeping_state.size(); i > 0; i--) {
|
||||
callback_sleeping_state[i - 1](false);
|
||||
}
|
||||
sleeping = false;
|
||||
time_last_task = ggml_time_ms();
|
||||
condition_tasks.notify_all(); // notify wait_until_no_sleep()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user