mirror of
				https://github.com/ggml-org/llama.cpp.git
				synced 2025-10-31 08:51:55 +00:00 
			
		
		
		
	examples/finetune -opt SGD (stochastic gradient descent) memory opt
add unit tested GGML_OPT_OPTIMIZER_SGD to ggml - avoids allocating
m, v tensors.
support finetune.cpp arg -opt SGD (or sgd). (default adamw as before)
llama 3.2-1b-F32 result: observed 11gb gpu ram (41 sec/epoch)
when using SGD instead of 19gb (55 sec/epoch) using adamw.
(wikipedia 100 lines finetune)
(
using the same GPU memory, adamw can only do before OOM 512
batch/context, reaching:
train: [███████▉] data=0000140/0000140 loss=0.02575±0.00099 acc=99.52±0.03% t=00:00:47 ETA=00:00:00
val:   [███████▉] data=0000008/0000008 loss=4.76565±0.28810 acc=41.46±0.77% t=00:00:00 ETA=00:00:00
SGD is superior, though it converges slower, with max before OOM 1728
batch/context (esp see the better validation perf):
train: [███████▉] data=0000039/0000039 loss=0.00371±0.00010 acc=99.96±0.01% t=00:00:41 ETA=00:00:00
val:   [███████▉] data=0000003/0000003 loss=5.11406±0.76034 acc=48.01±0.69% t=00:00:01 ETA=00:00:00
)
note: when finetuning long enough (or w/ enough -lr),
validation accuracy *eventually* drops ('catastrophic forgetting')
-lr-half (halflife) option useful for SGD to avoid oscillation or
super slow underdamped learning (makes setting -lr more forgiving).
terminal -lr for now is set by lr-halvings i.e. if you want at most
1/8 the inital -lr you set -lr-halvings 3.
note: objective loss not directly comparable between adamw, sgd? -
check perplexity or accuracy or consider relative improvements
for convergence
new finetune args -wd 1e-9 to enable weight decay in sgd or adamw,
and max -epochs N (default 2 as before)
cache (1 - wd*alpha) in 'adamw' opt struct -
no noticeable perf benefit, disabled (still done
for new SGD though)
since opt. memory is pre-allocated, the ggml_opt_get_optimizer_params
would probably be able to change between SGD and AdamW with each epoch
but would need to use adamw for the first (unconfirmed - no cmdline arg
to set such a policy yet)
test-opt checks adamw as before and now sgd (except for a few disabled
tests for sgd only; probably just needs logging values and adding
alternate reference values);  tolerance on the 'regression'
test is broader for sgd (so we don't need many more epochs)
			
			
This commit is contained in:
		| @@ -4493,9 +4493,9 @@ struct test_opt_step_adamw : public test_case { | ||||
|         return VARS_TO_STR2(type, ne); | ||||
|     } | ||||
|  | ||||
|     test_opt_step_adamw(ggml_type type = GGML_TYPE_F32, | ||||
|             std::array<int64_t, 4> ne = {10, 5, 4, 3}) | ||||
|         : type(type), ne(ne) {} | ||||
|     test_opt_step_adamw(ggml_type type = GGML_TYPE_F32, std::array<int64_t, 4> ne = { 10, 5, 4, 3 }) : | ||||
|         type(type), | ||||
|         ne(ne) {} | ||||
|  | ||||
|     ggml_tensor * build_graph(ggml_context * ctx) override { | ||||
|         ggml_tensor * a = ggml_new_tensor_4d(ctx, type, ne[0], ne[1], ne[2], ne[3]); | ||||
| @@ -4505,16 +4505,18 @@ struct test_opt_step_adamw : public test_case { | ||||
|         ggml_tensor * grad = ggml_new_tensor_4d(ctx, type, ne[0], ne[1], ne[2], ne[3]); | ||||
|         ggml_set_name(grad, "grad"); | ||||
|  | ||||
|         ggml_tensor * adamw_params = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 7); | ||||
|         ggml_set_name(adamw_params, "adamw_params"); | ||||
|  | ||||
|         ggml_tensor * out; | ||||
|         ggml_tensor * grad_m = ggml_new_tensor_4d(ctx, type, ne[0], ne[1], ne[2], ne[3]); | ||||
|         ggml_set_name(grad_m, "grad_m"); | ||||
|  | ||||
|         ggml_tensor * grad_v = ggml_new_tensor_4d(ctx, type, ne[0], ne[1], ne[2], ne[3]); | ||||
|         ggml_set_name(grad_v, "grad_v"); | ||||
|  | ||||
|         ggml_tensor * adamw_params = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 7); | ||||
|         ggml_set_name(adamw_params, "adamw_params"); | ||||
|         out = ggml_opt_step_adamw(ctx, a, grad, grad_m, grad_v, adamw_params); | ||||
|  | ||||
|         ggml_tensor * out = ggml_opt_step_adamw(ctx, a, grad, grad_m, grad_v, adamw_params); | ||||
|         ggml_set_name(out, "out"); | ||||
|  | ||||
|         return out; | ||||
| @@ -4531,6 +4533,43 @@ struct test_opt_step_adamw : public test_case { | ||||
|     } | ||||
| }; | ||||
|  | ||||
| struct test_opt_step_sgd : public test_case { | ||||
|     const ggml_type              type; | ||||
|     const std::array<int64_t, 4> ne; | ||||
|  | ||||
|     std::string vars() override { return VARS_TO_STR2(type, ne); } | ||||
|  | ||||
|     test_opt_step_sgd(ggml_type type = GGML_TYPE_F32, std::array<int64_t, 4> ne = { 10, 5, 4, 3 }) : | ||||
|         type(type), | ||||
|         ne(ne) {} | ||||
|  | ||||
|     ggml_tensor * build_graph(ggml_context * ctx) override { | ||||
|         ggml_tensor * a = ggml_new_tensor_4d(ctx, type, ne[0], ne[1], ne[2], ne[3]); | ||||
|         ggml_set_param(a);  // Despite tensor a having gradients the output tensor will not. | ||||
|         ggml_set_name(a, "a"); | ||||
|  | ||||
|         ggml_tensor * grad = ggml_new_tensor_4d(ctx, type, ne[0], ne[1], ne[2], ne[3]); | ||||
|         ggml_set_name(grad, "grad"); | ||||
|  | ||||
|         ggml_tensor * adamw_params = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 2); | ||||
|         ggml_set_name(adamw_params, "adamw_params"); | ||||
|  | ||||
|         ggml_tensor * out = ggml_opt_step_sgd(ctx, a, grad, adamw_params); | ||||
|  | ||||
|         ggml_set_name(out, "out"); | ||||
|  | ||||
|         return out; | ||||
|     } | ||||
|  | ||||
|     void initialize_tensors(ggml_context * ctx) override { | ||||
|         for (ggml_tensor * t = ggml_get_first_tensor(ctx); t != NULL; t = ggml_get_next_tensor(ctx, t)) { | ||||
|             init_tensor_uniform(t, 0.0f, 1.0f);  // grad_v and adamw_params need non-negative values. | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     bool grad_precise() override { return true; } | ||||
| }; | ||||
|  | ||||
| enum llm_norm_type { | ||||
|     LLM_NORM, | ||||
|     LLM_NORM_RMS, | ||||
| @@ -4962,7 +5001,7 @@ static const ggml_type other_types[] = { | ||||
| }; | ||||
|  | ||||
| // Test cases for evaluation: should try to cover edge cases while using small input sizes to keep the runtime low | ||||
| static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() { | ||||
| static std::vector<std::unique_ptr<test_case>> make_test_cases_eval(bool test_sgd = true) { | ||||
|     std::vector<std::unique_ptr<test_case>> test_cases; | ||||
|     std::default_random_engine rng(0); | ||||
|  | ||||
| @@ -5755,6 +5794,8 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() { | ||||
|     test_cases.emplace_back(new test_cross_entropy_loss_back(GGML_TYPE_F32, {30000, 1, 1, 1})); | ||||
|  | ||||
|     test_cases.emplace_back(new test_opt_step_adamw(GGML_TYPE_F32, {10, 5, 4, 3})); | ||||
|     if (test_sgd) | ||||
|         test_cases.emplace_back(new test_opt_step_sgd(GGML_TYPE_F32, { 10, 5, 4, 3 })); | ||||
|  | ||||
| #if 0 | ||||
|     // these tests are disabled to save execution time, sbut they can be handy for debugging | ||||
| @@ -5889,6 +5930,10 @@ static bool test_backend(ggml_backend_t backend, test_mode mode, const char * op | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     char const* name = ggml_backend_name(backend); | ||||
|     bool const vulkan = strstr(name, "ulkan"); | ||||
|     bool const sgd = !vulkan; | ||||
|  | ||||
|     if (mode == MODE_TEST) { | ||||
|         auto test_cases = make_test_cases_eval(); | ||||
|         filter_test_cases(test_cases, params_filter); | ||||
| @@ -5914,7 +5959,7 @@ static bool test_backend(ggml_backend_t backend, test_mode mode, const char * op | ||||
|     } | ||||
|  | ||||
|     if (mode == MODE_GRAD) { | ||||
|         auto test_cases = make_test_cases_eval(); | ||||
|         auto test_cases = make_test_cases_eval(sgd); | ||||
|         filter_test_cases(test_cases, params_filter); | ||||
|         size_t n_ok = 0; | ||||
|         for (auto & test : test_cases) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 graehl
					graehl