Skip to content

Commit 7dc8462

Browse files
ngxsonServeurpersoCom
authored andcommitted
add common/preset.cpp
1 parent 9746dd9 commit 7dc8462

File tree

11 files changed

+438
-578
lines changed

11 files changed

+438
-578
lines changed

common/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ add_library(${TARGET} STATIC
7373
ngram-cache.h
7474
peg-parser.cpp
7575
peg-parser.h
76+
preset.cpp
77+
preset.h
7678
regex-partial.cpp
7779
regex-partial.h
7880
sampling.cpp

common/arg.cpp

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ static std::vector<std::string> break_str_into_lines(std::string input, size_t m
153153
return result;
154154
}
155155

156-
std::string common_arg::to_string() {
156+
std::string common_arg::to_string() const {
157157
// params for printing to console
158158
const static int n_leading_spaces = 40;
159159
const static int n_char_per_line_help = 70; // TODO: detect this based on current console
@@ -664,6 +664,53 @@ static void add_rpc_devices(const std::string & servers) {
664664
}
665665
}
666666

667+
bool common_params_parse(int argc, char ** argv, llama_example ex, std::map<common_arg, std::string> & out_map) {
668+
common_params dummy_params;
669+
common_params_context ctx_arg = common_params_parser_init(dummy_params, ex, nullptr);
670+
671+
std::unordered_map<std::string, common_arg *> arg_to_options;
672+
for (auto & opt : ctx_arg.options) {
673+
for (const auto & arg : opt.args) {
674+
arg_to_options[arg] = &opt;
675+
}
676+
}
677+
678+
// TODO @ngxson : find a way to deduplicate this code
679+
680+
// handle command line arguments
681+
auto check_arg = [&](int i) {
682+
if (i+1 >= argc) {
683+
throw std::invalid_argument("expected value for argument");
684+
}
685+
};
686+
687+
for (int i = 1; i < argc; i++) {
688+
const std::string arg_prefix = "--";
689+
690+
std::string arg = argv[i];
691+
if (arg.compare(0, arg_prefix.size(), arg_prefix) == 0) {
692+
std::replace(arg.begin(), arg.end(), '_', '-');
693+
}
694+
if (arg_to_options.find(arg) == arg_to_options.end()) {
695+
throw std::invalid_argument(string_format("error: invalid argument: %s", arg.c_str()));
696+
}
697+
auto opt = *arg_to_options[arg];
698+
std::string val;
699+
if (opt.value_hint != nullptr) {
700+
// arg with single value
701+
check_arg(i);
702+
val = argv[++i];
703+
}
704+
if (opt.value_hint_2 != nullptr) {
705+
// TODO: support arg with 2 values
706+
throw std::invalid_argument("error: argument with 2 values is not yet supported\n");
707+
}
708+
out_map[opt] = val;
709+
}
710+
711+
return true;
712+
}
713+
667714
bool common_params_parse(int argc, char ** argv, common_params & params, llama_example ex, void(*print_usage)(int, char **)) {
668715
auto ctx_arg = common_params_parser_init(params, ex, print_usage);
669716
const common_params params_org = ctx_arg.params; // the example can modify the default params
@@ -3262,3 +3309,11 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
32623309

32633310
return ctx_arg;
32643311
}
3312+
3313+
static std::string rm_leading_dashes(const std::string & str) {
3314+
size_t pos = 0;
3315+
while (pos < str.size() && str[pos] == '-') {
3316+
++pos;
3317+
}
3318+
return str.substr(pos);
3319+
}

common/arg.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "common.h"
44

55
#include <set>
6+
#include <map>
67
#include <string>
78
#include <vector>
89

@@ -24,6 +25,8 @@ struct common_arg {
2425
void (*handler_str_str)(common_params & params, const std::string &, const std::string &) = nullptr;
2526
void (*handler_int) (common_params & params, int) = nullptr;
2627

28+
common_arg() = default;
29+
2730
common_arg(
2831
const std::initializer_list<const char *> & args,
2932
const char * value_hint,
@@ -61,7 +64,21 @@ struct common_arg {
6164
bool is_exclude(enum llama_example ex);
6265
bool get_value_from_env(std::string & output) const;
6366
bool has_value_from_env() const;
64-
std::string to_string();
67+
std::string to_string() const;
68+
69+
// for using as key in std::map
70+
bool operator<(const common_arg& other) const {
71+
if (args.empty() || other.args.empty()) {
72+
return false;
73+
}
74+
return strcmp(args[0], other.args[0]) < 0;
75+
}
76+
bool operator==(const common_arg& other) const {
77+
if (args.empty() || other.args.empty()) {
78+
return false;
79+
}
80+
return strcmp(args[0], other.args[0]) == 0;
81+
}
6582
};
6683

6784
struct common_params_context {
@@ -76,7 +93,11 @@ struct common_params_context {
7693
// if one argument has invalid value, it will automatically display usage of the specific argument (and not the full usage message)
7794
bool common_params_parse(int argc, char ** argv, common_params & params, llama_example ex, void(*print_usage)(int, char **) = nullptr);
7895

79-
// function to be used by test-arg-parser
96+
// parse input arguments from CLI into a map
97+
// TODO: support repeated args in the future
98+
bool common_params_parse(int argc, char ** argv, llama_example ex, std::map<common_arg, std::string> & out_map);
99+
100+
// initialize argument parser context - used by test-arg-parser and preset
80101
common_params_context common_params_parser_init(common_params & params, llama_example ex, void(*print_usage)(int, char **) = nullptr);
81102

82103
// Get environment variable name for a CLI flag (e.g. "--ctx-size" -> "LLAMA_ARG_CTX_SIZE")

common/preset.cpp

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#include "arg.h"
2+
#include "preset.h"
3+
#include "peg-parser.h"
4+
#include "log.h"
5+
6+
#include <fstream>
7+
#include <sstream>
8+
#include <filesystem>
9+
10+
static std::string rm_leading_dashes(const std::string & str) {
11+
size_t pos = 0;
12+
while (pos < str.size() && str[pos] == '-') {
13+
++pos;
14+
}
15+
return str.substr(pos);
16+
}
17+
18+
std::vector<std::string> common_preset::to_args() const {
19+
std::vector<std::string> args;
20+
21+
for (const auto & [opt, value] : options) {
22+
args.push_back(opt.args.back()); // use the last arg as the main arg
23+
if (opt.value_hint != nullptr) {
24+
// single value
25+
args.push_back(value);
26+
}
27+
if (opt.value_hint_2 != nullptr) {
28+
throw std::runtime_error(string_format(
29+
"common_preset::to_args(): option '%s' has two values, which is not supported yet",
30+
opt.args.back()
31+
));
32+
}
33+
}
34+
35+
return args;
36+
}
37+
38+
std::string common_preset::to_ini() const {
39+
std::ostringstream ss;
40+
41+
ss << "[" << name << "]\n";
42+
for (const auto & [opt, value] : options) {
43+
auto espaced_value = value;
44+
string_replace_all(espaced_value, "\n", "\\\n");
45+
ss << rm_leading_dashes(opt.args.back()) << " = ";
46+
ss << espaced_value << "\n";
47+
}
48+
ss << "\n";
49+
50+
return ss.str();
51+
}
52+
53+
static std::map<std::string, std::map<std::string, std::string>> parse_ini_from_file(const std::string & path) {
54+
std::map<std::string, std::map<std::string, std::string>> parsed;
55+
56+
if (!std::filesystem::exists(path)) {
57+
return parsed; // return empty if file does not exist (expected behavior)
58+
}
59+
60+
std::ifstream file(path);
61+
if (!file.good()) {
62+
throw std::runtime_error("failed to open server config file: " + path);
63+
}
64+
65+
std::string contents((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
66+
67+
static const auto parser = build_peg_parser([](auto & p) {
68+
// newline ::= "\r\n" / "\n" / "\r"
69+
auto newline = p.rule("newline", p.literal("\r\n") | p.literal("\n") | p.literal("\r"));
70+
71+
// ws ::= [ \t]*
72+
auto ws = p.rule("ws", p.chars("[ \t]", 0, -1));
73+
74+
// comment ::= [;#] (!newline .)*
75+
auto comment = p.rule("comment", p.chars("[;#]", 1, 1) + p.zero_or_more(p.negate(newline) + p.any()));
76+
77+
// eol ::= ws comment? (newline / EOF)
78+
auto eol = p.rule("eol", ws + p.optional(comment) + (newline | p.end()));
79+
80+
// ident ::= [a-zA-Z_] [a-zA-Z0-9_.-]*
81+
auto ident = p.rule("ident", p.chars("[a-zA-Z_]", 1, 1) + p.chars("[a-zA-Z0-9_.-]", 0, -1));
82+
83+
// value ::= (!eol-start .)*
84+
auto eol_start = p.rule("eol-start", ws + (p.chars("[;#]", 1, 1) | newline | p.end()));
85+
auto value = p.rule("value", p.zero_or_more(p.negate(eol_start) + p.any()));
86+
87+
// header-line ::= "[" ws ident ws "]" eol
88+
auto header_line = p.rule("header-line", "[" + ws + p.tag("section-name", p.chars("[^]]")) + ws + "]" + eol);
89+
90+
// kv-line ::= ident ws "=" ws value eol
91+
auto kv_line = p.rule("kv-line", p.tag("key", ident) + ws + "=" + ws + p.tag("value", value) + eol);
92+
93+
// comment-line ::= ws comment (newline / EOF)
94+
auto comment_line = p.rule("comment-line", ws + comment + (newline | p.end()));
95+
96+
// blank-line ::= ws (newline / EOF)
97+
auto blank_line = p.rule("blank-line", ws + (newline | p.end()));
98+
99+
// line ::= header-line / kv-line / comment-line / blank-line
100+
auto line = p.rule("line", header_line | kv_line | comment_line | blank_line);
101+
102+
// ini ::= line* EOF
103+
auto ini = p.rule("ini", p.zero_or_more(line) + p.end());
104+
105+
return ini;
106+
});
107+
108+
common_peg_parse_context ctx(contents);
109+
const auto result = parser.parse(ctx);
110+
if (!result.success()) {
111+
throw std::runtime_error("failed to parse server config file: " + path);
112+
}
113+
114+
std::string current_section = COMMON_PRESET_DEFAULT_NAME;
115+
std::string current_key;
116+
117+
ctx.ast.visit(result, [&](const auto & node) {
118+
if (node.tag == "section-name") {
119+
const std::string section = std::string(node.text);
120+
current_section = section;
121+
parsed[current_section] = {};
122+
} else if (node.tag == "key") {
123+
const std::string key = std::string(node.text);
124+
current_key = key;
125+
} else if (node.tag == "value" && !current_key.empty() && !current_section.empty()) {
126+
parsed[current_section][current_key] = std::string(node.text);
127+
current_key.clear();
128+
}
129+
});
130+
131+
return parsed;
132+
}
133+
134+
static std::map<std::string, common_arg> get_map_key_opt(common_params_context & ctx_params) {
135+
std::map<std::string, common_arg> mapping;
136+
for (const auto & opt : ctx_params.options) {
137+
if (opt.env != nullptr) {
138+
mapping[opt.env] = opt;
139+
}
140+
for (const auto & arg : opt.args) {
141+
mapping[rm_leading_dashes(arg)] = opt;
142+
}
143+
}
144+
return mapping;
145+
}
146+
147+
common_presets common_presets_load(const std::string & path, common_params_context & ctx_params) {
148+
common_presets out;
149+
auto key_to_opt = get_map_key_opt(ctx_params);
150+
auto ini_data = parse_ini_from_file(path);
151+
152+
for (auto section : ini_data) {
153+
common_preset preset;
154+
if (section.first.empty()) {
155+
preset.name = COMMON_PRESET_DEFAULT_NAME;
156+
} else {
157+
preset.name = section.first;
158+
}
159+
LOG_DBG("loading preset: %s\n", preset.name.c_str());
160+
for (const auto & [key, value] : section.second) {
161+
LOG_DBG("option: %s = %s\n", key.c_str(), value.c_str());
162+
if (key_to_opt.find(key) != key_to_opt.end()) {
163+
preset.options[key_to_opt[key]] = value;
164+
LOG_DBG("accepted option: %s = %s\n", key.c_str(), value.c_str());
165+
} else {
166+
// TODO: maybe warn about unknown key?
167+
}
168+
}
169+
out[preset.name] = preset;
170+
}
171+
172+
return out;
173+
}
174+
175+
void common_presets_save(const std::string & path, const common_presets & presets) {
176+
std::ofstream file(path);
177+
if (!file.good()) {
178+
throw std::runtime_error("failed to open preset file for writing: " + path);
179+
}
180+
181+
file << "version = 1\n\n";
182+
183+
for (const auto & it : presets) {
184+
file << it.second.to_ini();
185+
}
186+
}

common/preset.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#pragma once
2+
3+
#include "common.h"
4+
#include "arg.h"
5+
6+
#include <string>
7+
#include <vector>
8+
#include <map>
9+
10+
//
11+
// INI preset parser and writer
12+
//
13+
14+
constexpr const char * COMMON_PRESET_DEFAULT_NAME = "default";
15+
16+
struct common_preset {
17+
std::string name;
18+
// TODO: support repeated args in the future
19+
std::map<common_arg, std::string> options;
20+
21+
// convert preset to CLI argument list
22+
std::vector<std::string> to_args() const;
23+
24+
// convert preset to INI format string
25+
std::string to_ini() const;
26+
27+
// TODO: maybe implement to_env() if needed
28+
};
29+
30+
// interface for multiple presets in one file
31+
using common_presets = std::map<std::string, common_preset>;
32+
common_presets common_presets_load(const std::string & path, common_params_context & ctx_params);
33+
void common_presets_save(const std::string & path, const common_presets & presets);

tools/server/CMakeLists.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ set(TARGET_SRCS
1212
server-http.h
1313
server-models.cpp
1414
server-models.h
15-
server-config.cpp
16-
server-config.h
1715
server-task.cpp
1816
server-task.h
1917
server-queue.cpp

tools/server/README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,8 +1383,6 @@ Alternatively, you can point the router to a local directory containing your GGU
13831383
llama-server --models-dir ./models_directory
13841384
```
13851385

1386-
The directory is scanned recursively, so nested vendor/model layouts such as `vendor_name/model_name/*.gguf` are supported. The model name in the router UI matches the relative path inside `--models-dir` (for example, `vendor_name/model_name`).
1387-
13881386
If the model contains multiple GGUF (for multimodal or multi-shard), files should be put into a subdirectory. The directory structure should look like this:
13891387

13901388
```sh

0 commit comments

Comments
 (0)