Skip to content

Commit 8257200

Browse files
committed
feat(inference): enhance CLI with detailed options and improve distributed setup
- Reworked CLI description and examples for better usability and clarity. - Introduced comprehensive option groups for general, sampling, input, and distributed configurations. - Standardized parameter validation, including required flags and value checks. - Improved distributed inference support by refining transport initialization and error handling.
1 parent 59e1b20 commit 8257200

1 file changed

Lines changed: 123 additions & 113 deletions

File tree

src/inference/main.cpp

Lines changed: 123 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -178,164 +178,174 @@ void chat(Transformer *transformer, Tokenizer *tokenizer, Sampler *sampler, cons
178178
}
179179

180180
int main(int argc, char *argv[]) {
181-
CLI::App app{"LEAP Inference Engine\nA high-performance, distributed LLM inference runner.\n"};
181+
CLI::App app{"LEAP Inference Engine - High-performance distributed LLM inference"};
182+
app.description("A specialized runner for LEAP models supporting single-node and distributed pipelined inference.\n"
183+
"Example (Single Node):\n"
184+
" ./inference model.bin -p \"Hello, how are you?\"\n\n"
185+
"Example (Distributed Master):\n"
186+
" ./inference model.bin --role master --next-host 192.168.1.10 --next-port 9999 --split 16\n\n"
187+
"Example (Distributed Worker):\n"
188+
" ./inference model.bin --role worker --port 9999 --next-host 192.168.1.11 --next-port 9999 --split 16 --end 32");
189+
182190
argv = app.ensure_utf8(argv);
183191

184-
std::string checkpoint_path;
192+
// Core arguments
193+
std::string model_path;
185194
std::string tokenizer_path = "tokenizer.bin";
195+
app.add_option("model", model_path, "Path to the model checkpoint file (.bin)")
196+
->required()
197+
->check(CLI::ExistingFile);
198+
199+
// General Configuration
200+
auto *gen_group = app.add_option_group("General", "Basic inference settings");
201+
std::string mode = "generate";
202+
bool chat_mode = false;
203+
gen_group->add_flag("-c,--chat", chat_mode, "Run in interactive chat mode");
204+
gen_group->add_option("-t,--tokenizer", tokenizer_path, "Path to the tokenizer file")
205+
->check(CLI::ExistingFile)
206+
->capture_default_str();
207+
int n_predict = 4096;
208+
gen_group->add_option("-n,--n-predict", n_predict, "Maximum number of tokens to generate (0 = model max)")
209+
->capture_default_str();
210+
unsigned long long rng_seed = 0;
211+
gen_group->add_option("--seed", rng_seed, "Random seed for reproducibility (0 = use current time)")
212+
->capture_default_str();
213+
214+
// Sampling Configuration
215+
auto *sample_group = app.add_option_group("Sampling", "Tokens selection parameters");
186216
float temperature = 1.0f;
217+
sample_group->add_option("--temp", temperature, "Temperature for sampling (higher = more creative, 0.0 = greedy)")
218+
->check(CLI::NonNegativeNumber)
219+
->capture_default_str();
187220
float topp = 0.9f;
188-
int steps = 4096;
221+
sample_group->add_option("--top-p", topp, "Top-P (nucleus) sampling threshold")
222+
->check(CLI::Range(0.0, 1.0))
223+
->capture_default_str();
224+
225+
// Input
226+
auto *input_group = app.add_option_group("Input", "Prompt configuration");
189227
std::string prompt;
190-
unsigned long long rng_seed = 0;
191-
std::string mode = "generate";
228+
input_group->add_option("-p,--prompt", prompt, "Initial input prompt for the model");
192229
std::string system_prompt;
193-
194-
// Distributed args
195-
std::string dist_mode_str = "single";
196-
std::string ip = "0.0.0.0";
197-
std::string master_ip = "";
230+
input_group->add_option("--system", system_prompt, "System prompt for chat mode");
231+
232+
// Distributed Inference
233+
auto *dist_group = app.add_option_group("Distributed", "Configuration for multi-node pipeline parallelism");
234+
std::string role = "single";
235+
dist_group->add_option("--role", role, "Node role in the cluster")
236+
->check(CLI::IsMember({"single", "master", "worker"}))
237+
->capture_default_str();
238+
std::string transport_type = "tcp";
239+
dist_group->add_option("--transport", transport_type, "Network transport protocol")
240+
->check(CLI::IsMember({"tcp", "udp", "kernel"}))
241+
->capture_default_str();
242+
243+
std::string host = "0.0.0.0";
244+
dist_group->add_option("--host", host, "Local IP to bind for incoming connections (Workers)")
245+
->capture_default_str();
198246
int port = 9999;
199-
int split_layer = 0;
200-
std::string next_ip = "";
247+
dist_group->add_option("--port", port, "Local port to bind for incoming connections (Workers)")
248+
->check(CLI::Range(1, 65535))
249+
->capture_default_str();
250+
251+
std::string next_host = "";
252+
dist_group->add_option("--next-host", next_host, "IP address of the next node in the pipeline");
201253
int next_port = 0;
254+
dist_group->add_option("--next-port", next_port, "Port of the next node in the pipeline")
255+
->check(CLI::Range(0, 65535));
256+
257+
int split_layer = 0;
258+
dist_group->add_option("--split", split_layer, "Layer index to start processing on this node")
259+
->capture_default_str();
202260
int end_layer = 0;
261+
dist_group->add_option("--end", end_layer, "Layer index to stop processing (exclusive, 0 = until end)")
262+
->capture_default_str();
203263

204-
// --- Options ---
205-
app.add_option("checkpoint", checkpoint_path, "Path to the model checkpoint file (e.g., model.bin)")
206-
->required()
207-
->check(CLI::ExistingFile);
208-
209-
// Group: General
210-
auto *gen_opts = app.add_option_group("General Configuration");
211-
gen_opts->add_option("-z,--tokenizer", tokenizer_path, "Path to the tokenizer file")
212-
->check(CLI::ExistingFile);
213-
gen_opts->add_option("-m,--mode", mode, "Inference mode: 'generate' for completion, 'chat' for dialog")
214-
->check(CLI::IsMember({"generate", "chat"}));
215-
gen_opts->add_option("-n,--steps", steps, "Maximum number of steps to run (0 = max_seq_len)");
216-
gen_opts->add_option("-s,--seed", rng_seed, "Random seed (0 = use time)");
217-
218-
// Group: Sampling
219-
auto *sample_opts = app.add_option_group("Sampling Parameters");
220-
sample_opts->add_option("-t,--temperature", temperature, "Temperature for sampling [0.0, inf)")
221-
->check(CLI::NonNegativeNumber);
222-
sample_opts->add_option("-p,--top-p", topp, "Top-P (Nucleus) sampling probability [0.0, 1.0]")
223-
->check(CLI::Range(0.0, 1.0));
224-
225-
// Group: Input
226-
auto *input_opts = app.add_option_group("Input");
227-
input_opts->add_option("-i,--prompt", prompt, "Initial user prompt");
228-
input_opts->add_option("-y,--system-prompt", system_prompt, "System prompt (only used in chat mode)");
229-
230-
// Group: Distributed
231-
auto *dist_opts = app.add_option_group("Distributed Inference", "Configuration for multi-node inference");
232-
dist_opts->add_option("--dist", dist_mode_str, "Distributed mode")
233-
->check(CLI::IsMember({"single", "master", "worker", "master-udp", "worker-udp", "worker-kernel"}));
234-
235-
dist_opts->add_option("--ip", ip, "Bind IP address (Worker only). Default: 0.0.0.0");
236-
dist_opts->add_option("--port", port, "Bind Port (Worker only). Default: 9999")
237-
->check(CLI::Range(1, 65535));
238-
dist_opts->add_option("--split", split_layer, "Layer index to split at (start layer for worker)");
239-
dist_opts->add_option("--end-layer", end_layer, "Layer index to stop at (exclusive, for worker). Default: n_layers");
240-
241-
dist_opts->add_option("--master-ip", master_ip, "Master IP address (required for worker-kernel mode)");
242-
243-
dist_opts->add_option("--next-ip", next_ip, "Target IP address for outgoing connection (Master -> Worker 1, Worker -> Next)");
244-
dist_opts->add_option("--next-port", next_port, "Target Port for outgoing connection")
245-
->check(CLI::Range(0, 65535)); // 0 means unset
264+
std::string master_host = "";
265+
dist_group->add_option("--master-host", master_host, "Master IP address (required for kernel transport)");
246266

247267
CLI11_PARSE(app, argc, argv);
248268

249-
// --- Post-Processing / Defaults ---
250-
if (rng_seed <= 0) rng_seed = static_cast<unsigned int>(std::time(nullptr));
251-
if (steps < 0) steps = 0;
269+
// Post-processing
270+
if (chat_mode) mode = "chat";
271+
if (rng_seed == 0) rng_seed = static_cast<unsigned int>(std::time(nullptr));
272+
if (n_predict < 0) n_predict = 0;
252273

253274
try {
254-
auto transformer = Transformer::create(checkpoint_path);
275+
auto transformer = Transformer::create(model_path);
255276

256-
std::cout << "Model loaded successfully." << std::endl;
257-
std::cout << "Config: ["
277+
std::cout << "Model loaded: " << model_path << std::endl;
278+
std::cout << "Architecture: "
258279
<< transformer->config.n_layers << " layers, "
259280
<< transformer->config.dim << " dim, "
260281
<< transformer->config.n_heads << " heads, "
261282
<< transformer->config.vocab_size << " vocab, "
262-
<< transformer->config.seq_len << " seq_len]" << std::endl;
283+
<< transformer->config.seq_len << " context" << std::endl;
263284

264-
if (steps == 0 || steps > transformer->config.seq_len) {
265-
steps = transformer->config.seq_len;
285+
if (n_predict == 0 || n_predict > transformer->config.seq_len) {
286+
n_predict = transformer->config.seq_len;
266287
}
267288

268289
if (end_layer == 0) end_layer = transformer->config.n_layers;
269290

270291
// Setup Distributed Mode
271-
DistributedMode dist_mode = DistributedMode::Single;
292+
DistributedMode dist_role = DistributedMode::Single;
293+
if (role == "master") dist_role = DistributedMode::Master;
294+
else if (role == "worker") dist_role = DistributedMode::Worker;
295+
272296
std::unique_ptr<Transport> transport = nullptr;
273297

274-
if (dist_mode_str == "master") {
275-
dist_mode = DistributedMode::Master;
298+
if (dist_role == DistributedMode::Master) {
276299
if (split_layer <= 0 || split_layer >= transformer->config.n_layers) {
277-
std::cerr << "Error: Invalid split layer for master mode. Must be > 0 and < n_layers." << std::endl;
278-
return 1;
300+
throw std::runtime_error("Invalid --split layer for master. Must be > 0 and < n_layers.");
279301
}
280-
if (next_ip.empty() || next_port == 0) {
281-
std::cerr << "Error: Master mode requires --next-ip and --next-port to connect to the first worker." << std::endl;
282-
return 1;
302+
if (next_host.empty() || next_port == 0) {
303+
throw std::runtime_error("Master role requires --next-host and --next-port to connect to workers.");
283304
}
284305

285-
transport = std::make_unique<TcpTransport>(next_ip, next_port, false, "", 0);
286-
transport->initialize();
287-
} else if (dist_mode_str == "worker") {
288-
dist_mode = DistributedMode::Worker;
289-
if (split_layer <= 0 || split_layer >= transformer->config.n_layers) {
290-
std::cerr << "Error: Invalid split layer for worker mode." << std::endl;
291-
return 1;
306+
if (transport_type == "tcp") {
307+
transport = std::make_unique<TcpTransport>(next_host, next_port, false, "", 0);
308+
} else if (transport_type == "udp") {
309+
transport = std::make_unique<UdpTransport>(host, port, false, next_host, next_port);
310+
} else {
311+
throw std::runtime_error("Master role does not support 'kernel' transport (Workers only).");
292312
}
293-
transport = std::make_unique<TcpTransport>(ip, port, true, next_ip, next_port);
294-
transport->initialize();
295-
} else if (dist_mode_str == "udp") {
296-
std::cerr << "Error: Use specific modes: master-udp, worker-udp, worker-kernel" << std::endl;
297-
return 1;
298-
} else if (dist_mode_str == "master-udp") {
299-
dist_mode = DistributedMode::Master;
300-
if (next_ip.empty() || next_port == 0) {
301-
std::cerr << "Error: Master UDP mode requires --next-ip and --next-port." << std::endl;
302-
return 1;
313+
} else if (dist_role == DistributedMode::Worker) {
314+
if (split_layer <= 0 || split_layer >= transformer->config.n_layers) {
315+
throw std::runtime_error("Invalid --split layer for worker.");
303316
}
304-
// Bind to local ip:port, send to next_ip:next_port
305-
transport = std::make_unique<UdpTransport>(ip, port, false, next_ip, next_port);
306-
transport->initialize();
307-
} else if (dist_mode_str == "worker-udp") {
308-
dist_mode = DistributedMode::Worker;
309-
transport = std::make_unique<UdpTransport>(ip, port, true, next_ip, next_port);
310-
transport->initialize();
311-
} else if (dist_mode_str == "worker-kernel") {
317+
318+
if (transport_type == "tcp") {
319+
transport = std::make_unique<TcpTransport>(host, port, true, next_host, next_port);
320+
} else if (transport_type == "udp") {
321+
transport = std::make_unique<UdpTransport>(host, port, true, next_host, next_port);
322+
} else if (transport_type == "kernel") {
312323
#ifndef __linux__
313-
std::cerr << "Error: --dist worker-kernel is only supported on Linux." << std::endl;
314-
return 1;
324+
throw std::runtime_error("Kernel transport is only supported on Linux.");
315325
#else
316-
dist_mode = DistributedMode::Worker;
317-
std::string target_ip = master_ip.empty() ? ip : master_ip;
318-
transport = std::make_unique<KernelTransport>(target_ip, port, next_ip, next_port);
319-
transport->initialize();
326+
std::string target = master_host.empty() ? host : master_host;
327+
transport = std::make_unique<KernelTransport>(target, port, next_host, next_port);
320328
#endif
321-
} else if (dist_mode_str != "single") {
322-
// CLI11 validation covers this, but safe fallback
323-
std::cerr << "Error: Unknown distributed mode: " << dist_mode_str << std::endl;
324-
return 1;
329+
}
330+
}
331+
332+
if (transport) {
333+
transport->initialize();
325334
}
326335

327336
DistributedConfig dist_config;
328-
dist_config.mode = dist_mode;
337+
dist_config.mode = dist_role;
329338
dist_config.split_layer = split_layer;
330339
dist_config.end_layer = end_layer;
331340
dist_config.transport = transport.get();
332-
dist_config.next_ip = next_ip;
341+
dist_config.next_ip = next_host;
333342
dist_config.next_port = next_port;
334-
dist_config.is_tail = (dist_mode == DistributedMode::Worker && next_ip.empty());
343+
dist_config.is_tail = (dist_role == DistributedMode::Worker && next_host.empty());
335344

336345
transformer->set_distributed_config(dist_config);
337346

338-
if (dist_mode == DistributedMode::Worker) {
347+
if (dist_role == DistributedMode::Worker) {
348+
std::cout << "Node started as Worker [Layers " << split_layer << " to " << end_layer << "]" << std::endl;
339349
transformer->worker_loop();
340350
return 0;
341351
}
@@ -345,12 +355,12 @@ int main(int argc, char *argv[]) {
345355
Sampler sampler(transformer->config.vocab_size, temperature, topp, rng_seed);
346356

347357
if (mode == "generate") {
348-
generate(transformer.get(), &tokenizer, &sampler, prompt, steps);
358+
generate(transformer.get(), &tokenizer, &sampler, prompt, n_predict);
349359
} else if (mode == "chat") {
350-
chat(transformer.get(), &tokenizer, &sampler, prompt, system_prompt, steps);
360+
chat(transformer.get(), &tokenizer, &sampler, prompt, system_prompt, n_predict);
351361
}
352362
} catch (const std::exception &e) {
353-
std::cerr << "Runtime Error: " << e.what() << std::endl;
363+
std::cerr << "\n[Error] " << e.what() << std::endl;
354364
return 1;
355365
}
356366

0 commit comments

Comments
 (0)