diff --git a/test/train-sets/ref/search_dep_parser_cost_to_go.stderr b/test/train-sets/ref/search_dep_parser_cost_to_go.stderr index becc5585890..eb427578069 100644 --- a/test/train-sets/ref/search_dep_parser_cost_to_go.stderr +++ b/test/train-sets/ref/search_dep_parser_cost_to_go.stderr @@ -13,13 +13,13 @@ average since instance current true current predicted loss last counter output prefix output prefix pass pol made hits gener beta 88.000000 88.000000 1 [43:1 5:2 5:2 5:2 1..] [0:8 1:1 2:1 3:1 4:..] 0 0 144 0 141 0.000000 46.500000 5.000000 2 [2:2 3:5 0:8 3:7 99..] [2:2 0:8 4:2 2:3 99..] 0 0 153 0 150 0.001409 -30.750000 15.000000 4 [2:2 3:5 0:8 3:7 99..] [2:2 3:5 0:8 3:7 99..] 1 0 306 0 300 0.002906 -17.125000 3.500000 8 [2:2 3:5 0:8 3:7 99..] [2:2 3:5 0:8 3:7 99..] 3 0 606 0 600 0.005893 +29.250000 12.000000 4 [2:2 3:5 0:8 3:7 99..] [2:2 3:5 0:8 3:7 99..] 1 0 306 0 300 0.002906 +16.375000 3.500000 8 [2:2 3:5 0:8 3:7 99..] [2:2 3:5 0:8 3:7 99..] 3 0 606 0 600 0.005893 finished run number of examples per pass = 2 passes used = 6 weighted example sum = 12.000000 weighted label sum = 0.000000 -average loss = 11.416667 -total feature number = 270404 +average loss = 10.916667 +total feature number = 269977 diff --git a/test/train-sets/ref/search_dep_parser_one_learner.stderr b/test/train-sets/ref/search_dep_parser_one_learner.stderr index a4f96b96e36..2684e3707db 100644 --- a/test/train-sets/ref/search_dep_parser_one_learner.stderr +++ b/test/train-sets/ref/search_dep_parser_one_learner.stderr @@ -15,11 +15,11 @@ Output pred = MULTICLASS average since instance current true current predicted cur cur predic cache examples loss last counter output prefix output prefix pass pol made hits gener beta 89.000000 89.000000 1 [43:1 5:2 5:2 5:2 1..] [20:9 20:9 20:9 20:..] 0 0 96 0 94 0.000930 -47.000000 5.000000 2 [2:2 3:5 0:8 3:7 99..] [2:12 3:7 4:4 0:8 9..] 0 0 102 0 100 0.000990 +46.500000 4.000000 2 [2:2 3:5 0:8 3:7 99..] [2:5 3:5 4:5 0:8 99..] 0 0 102 0 100 0.000990 finished run number of examples = 2 weighted example sum = 2.000000 weighted label sum = 0.000000 -average loss = 47.000000 +average loss = 46.500000 total feature number = 28636 diff --git a/test/train-sets/ref/search_wsj.stderr b/test/train-sets/ref/search_wsj.stderr index e0d7526ff35..fe0a4f272c5 100644 --- a/test/train-sets/ref/search_wsj.stderr +++ b/test/train-sets/ref/search_wsj.stderr @@ -12,15 +12,15 @@ Output pred = MULTICLASS average since instance current true current predicted cur cur predic cache examples loss last counter output prefix output prefix pass pol made hits gener beta 30.000000 30.000000 1 [1 2 3 1 4 5 6 7 8 ..] [1 1 1 1 1 1 1 1 1 ..] 0 0 37 0 37 0.000036 -23.500000 17.000000 2 [11 2 3 11 11 11 15..] [1 2 1 1 4 1 2 1 1 ..] 0 0 64 0 64 0.000063 -16.000000 8.500000 4 [3 4 6 3 ] [11 11 2 3 ] 0 0 97 0 97 0.000096 -8.000000 0.000000 8 [3 4 6 3 ] [3 4 6 3 ] 1 0 194 0 194 0.000193 -4.000000 0.000000 16 [3 4 6 3 ] [3 4 6 3 ] 3 0 388 0 388 0.000387 +24.500000 19.000000 2 [11 2 3 11 11 11 15..] [1 2 1 1 4 1 12 9 1..] 0 0 64 0 64 0.000063 +16.250000 8.000000 4 [3 4 6 3 ] [1 4 6 3 ] 0 0 97 0 97 0.000096 +8.125000 0.000000 8 [3 4 6 3 ] [3 4 6 3 ] 1 0 194 0 194 0.000193 +4.062500 0.000000 16 [3 4 6 3 ] [3 4 6 3 ] 3 0 388 0 388 0.000387 finished run number of examples per pass = 4 passes used = 6 weighted example sum = 24.000000 weighted label sum = 0.000000 -average loss = 2.666667 +average loss = 2.708333 total feature number = 52110 diff --git a/test/train-sets/ref/search_wsj2.dat.stderr b/test/train-sets/ref/search_wsj2.dat.stderr index 3ef9b233ff3..e262ea43c95 100644 --- a/test/train-sets/ref/search_wsj2.dat.stderr +++ b/test/train-sets/ref/search_wsj2.dat.stderr @@ -12,8 +12,8 @@ Output pred = MULTICLASS average since instance current true current predicted cur cur predic cache examples loss last counter output prefix output prefix pass pol made hits gener beta 30.000000 30.000000 1 [1 2 3 1 4 5 6 7 8 ..] [1 1 1 1 1 1 1 1 1 ..] 0 0 37 0 37 0.000036 -24.000000 18.000000 2 [11 2 3 11 11 11 15..] [1 2 3 1 4 1 2 1 1 ..] 0 0 64 0 64 0.000063 -16.750000 9.500000 4 [3 4 6 3 ] [11 11 11 11 ] 0 0 97 0 97 0.000096 +24.500000 19.000000 2 [11 2 3 11 11 11 15..] [1 2 1 1 1 1 12 12 ..] 0 0 64 0 64 0.000063 +16.750000 9.000000 4 [3 4 6 3 ] [1 4 6 6 ] 0 0 97 0 97 0.000096 8.375000 0.000000 8 [3 4 6 3 ] [3 4 6 3 ] 1 0 194 0 194 0.000193 4.187500 0.000000 16 [3 4 6 3 ] [3 4 6 3 ] 3 1 388 0 388 0.000387 diff --git a/test/train-sets/ref/sequence_data.ldf.beam.test.predict b/test/train-sets/ref/sequence_data.ldf.beam.test.predict index c6597b62c2b..e40f56db1c2 100644 --- a/test/train-sets/ref/sequence_data.ldf.beam.test.predict +++ b/test/train-sets/ref/sequence_data.ldf.beam.test.predict @@ -1,11 +1 @@ -5 4 3 2 1 0.000242054 -5 4 4 3 2 1.00016 -5 4 3 3 2 1.00018 -5 4 3 2 4 1.00018 -5 4 5 4 3 1.00019 -5 4 3 4 3 1.00026 -5 4 2 1 4 1.60554 -5 3 2 1 4 1.60557 -5 4 1 4 3 1.60563 -4 3 2 1 4 1.60563 - +5 4 3 2 1 diff --git a/test/train-sets/ref/sequence_data.ldf.beam.test.stderr b/test/train-sets/ref/sequence_data.ldf.beam.test.stderr index b0c8b69f4ea..52e2636b10f 100644 --- a/test/train-sets/ref/sequence_data.ldf.beam.test.stderr +++ b/test/train-sets/ref/sequence_data.ldf.beam.test.stderr @@ -12,7 +12,7 @@ Input label = MULTICLASS Output pred = MULTICLASS average since instance current true current predicted cur cur predic cache examples loss last counter output prefix output prefix pass pol made hits gener beta -0.000000 0.000000 1 [5 4 3 2 1 ] [5 4 3 2 1 0.00024..] 0 0 26 0 0 0.000000 +0.000000 0.000000 1 [5 4 3 2 1 ] [5 4 3 2 1 ] 0 0 26 0 0 0.000000 finished run number of examples = 1 diff --git a/test/train-sets/ref/sequence_data.nonldf.beam.test.predict b/test/train-sets/ref/sequence_data.nonldf.beam.test.predict index f2564727f6f..e40f56db1c2 100644 --- a/test/train-sets/ref/sequence_data.nonldf.beam.test.predict +++ b/test/train-sets/ref/sequence_data.nonldf.beam.test.predict @@ -1,11 +1 @@ -5 4 3 2 1 8.34465e-07 -5 4 3 5 4 1 -5 4 3 2 4 1 -5 5 4 3 2 1.02138 -5 4 3 2 3 1.02816 -5 4 3 2 2 1.03424 -5 3 2 1 1 1.76761 -5 4 3 1 1 1.79503 -4 3 2 1 1 1.79576 -5 2 1 1 1 2.53521 - +5 4 3 2 1 diff --git a/test/train-sets/ref/sequence_data.nonldf.beam.test.stderr b/test/train-sets/ref/sequence_data.nonldf.beam.test.stderr index 98cd912c886..4656d27ade7 100644 --- a/test/train-sets/ref/sequence_data.nonldf.beam.test.stderr +++ b/test/train-sets/ref/sequence_data.nonldf.beam.test.stderr @@ -12,7 +12,7 @@ Input label = MULTICLASS Output pred = MULTICLASS average since instance current true current predicted cur cur predic cache examples loss last counter output prefix output prefix pass pol made hits gener beta -0.000000 0.000000 1 [5 4 3 2 1 ] [5 4 3 2 1 8.34465..] 0 0 24 0 0 0.000000 +0.000000 0.000000 1 [5 4 3 2 1 ] [5 4 3 2 1 ] 0 0 24 0 0 0.000000 finished run number of examples = 1 diff --git a/vowpalwabbit/core/include/vw/core/reductions/search/search.h b/vowpalwabbit/core/include/vw/core/reductions/search/search.h index bde73a1de65..6b8e9c60219 100644 --- a/vowpalwabbit/core/include/vw/core/reductions/search/search.h +++ b/vowpalwabbit/core/include/vw/core/reductions/search/search.h @@ -86,9 +86,9 @@ class search public: // INTERFACE // for managing task-specific data that you want on the heap: template - void set_task_data(T* data) + void set_task_data(std::shared_ptr data) { - task_data = std::shared_ptr(data); + task_data = std::move(data); } template T* get_task_data() @@ -98,9 +98,9 @@ class search // for managing metatask-specific data template - void set_metatask_data(T* data) + void set_metatask_data(std::shared_ptr data) { - metatask_data = std::shared_ptr(data); + metatask_data = std::move(data); } template T* get_metatask_data() @@ -218,7 +218,7 @@ class search BaseTask base_task(VW::multi_ex& ec) { return BaseTask(this, ec); } // internal data that you don't get to see! - search_private* priv = nullptr; + std::shared_ptr priv = nullptr; std::shared_ptr task_data = nullptr; // your task data! std::shared_ptr metatask_data = nullptr; // your metatask data! const char* task_name = nullptr; @@ -227,8 +227,8 @@ class search VW::workspace& get_vw_pointer_unsafe(); // although you should rarely need this, some times you need a pointer to the // vw data structure :( void set_force_oracle(bool force); // if the library wants to force search to use the oracle, set this to true + search(); - ~search(); }; // for defining new tasks, you must fill out a search_task diff --git a/vowpalwabbit/core/src/reductions/search/search.cc b/vowpalwabbit/core/src/reductions/search/search.cc index bfee662c393..3774b9439bd 100644 --- a/vowpalwabbit/core/src/reductions/search/search.cc +++ b/vowpalwabbit/core/src/reductions/search/search.cc @@ -122,11 +122,13 @@ class action_repr { public: action a = 0; - VW::features* repr = nullptr; + std::shared_ptr repr = nullptr; action_repr() = default; + // action_repr(action _a, std::shared_ptr _repr) : a(_a), repr(std::move(_repr)) {} action_repr(action _a, VW::features* _repr) : a(_a) { - if (_repr != nullptr) { repr = new VW::features(*_repr); } + // Copy construct the features object + if (_repr != nullptr) { repr = std::make_shared(*_repr); } } action_repr(action _a) : a(_a), repr(nullptr) {} }; @@ -308,42 +310,18 @@ class search_private BaseTask* metaoverride = nullptr; size_t meta_t = 0; // the metatask has it's own notion of time. meta_t+t, during a single run, is the way to think // about the "real" decision step but this really only matters for caching purposes - VW::v_array*> + std::vector>> memo_foreach_action; // when foreach_action is on, we need to cache TRAIN trajectory actions for LEARN ~search_private() { - if (all) - { - for (auto& ar : ptag_to_action) { delete ar.repr; } - clear_memo_foreach_action(*this); - } + if (all) { clear_memo_foreach_action(*this); } } }; -void clear_memo_foreach_action(search_private& priv) -{ - for (size_t i = 0; i < priv.memo_foreach_action.size(); i++) - { - if (priv.memo_foreach_action[i]) { delete priv.memo_foreach_action[i]; } - } - priv.memo_foreach_action.clear(); -} +void clear_memo_foreach_action(search_private& priv) { priv.memo_foreach_action.clear(); } -search::search() -{ - priv = &VW::details::calloc_or_throw(); - new (priv) search_private(); -} - -search::~search() -{ - if (this->priv) - { - this->priv->~search_private(); - free(this->priv); - } -} +search::search() { priv = std::make_shared(); } std::string audit_feature_space("conditional"); uint64_t conditional_constant = 8290743; @@ -585,15 +563,18 @@ void add_new_feature(search_private& priv, float val, uint64_t idx) { uint64_t mask = priv.all->weights.mask(); size_t ss = priv.all->weights.stride_shift(); + uint64_t multiplier = static_cast(priv.all->reduction_state.total_feature_width) << ss; - uint64_t idx2 = ((idx & mask) >> ss) & mask; + // idx is scaled by multiplier and stride_shift + // priv.dat_new_feature_idx is not scaled + uint64_t idx2 = (idx / multiplier) & mask; auto& fs = priv.dat_new_feature_ec->feature_space[priv.dat_new_feature_namespace]; - fs.push_back(val * priv.dat_new_feature_value, ((priv.dat_new_feature_idx + idx2) << ss)); + fs.push_back(val * priv.dat_new_feature_value, ((priv.dat_new_feature_idx + idx2) * multiplier) & mask); cdbg << "adding: " << fs.indices.back() << ':' << fs.values.back() << endl; if (priv.all->output_config.audit) { std::stringstream temp; - temp << "fid=" << ((idx & mask) >> ss) << "_" << priv.dat_new_feature_audit_ss.str(); + temp << "fid=" << idx2 << "_" << priv.dat_new_feature_audit_ss.str(); fs.space_names.emplace_back(*priv.dat_new_feature_feature_space, temp.str()); } } @@ -621,6 +602,8 @@ void add_neighbor_features(search_private& priv, VW::multi_ex& ec_seq) if (priv.neighbor_features.size() == 0) { return; } uint32_t stride_shift = priv.all->weights.stride_shift(); + uint64_t multiplier = static_cast(priv.all->reduction_state.total_feature_width) << stride_shift; + for (size_t n = 0; n < ec_seq.size(); n++) // iterate over every example in the sequence { VW::example& me = *ec_seq[n]; @@ -643,16 +626,16 @@ void add_neighbor_features(search_private& priv, VW::multi_ex& ec_seq) if ((offset < 0) && (n < static_cast(-offset))) { // add feature - add_new_feature(priv, 1., static_cast(925871901) << stride_shift); + add_new_feature(priv, 1., static_cast(925871901) * multiplier); } else if (n + offset >= ec_seq.size()) { // add feature - add_new_feature(priv, 1., static_cast(3824917) << stride_shift); + add_new_feature(priv, 1., static_cast(3824917) * multiplier); } else // this is actually a neighbor { VW::example& other = *ec_seq[n + offset]; - VW::foreach_feature(priv.all, other.feature_space[ns], priv, me.ft_offset); + VW::foreach_feature(priv.all, other.feature_space[ns], priv, 0); } } @@ -701,7 +684,6 @@ void reset_search_structure(search_private& priv) if (priv.beta > 1) { priv.beta = 1; } } - for (auto& ar : priv.ptag_to_action) { delete ar.repr; } priv.ptag_to_action.clear(); if (!priv.cb_learner) // was: if rollout_all_actions @@ -771,6 +753,8 @@ void add_example_conditioning(search_private& priv, VW::example& ec, size_t cond { if (condition_on_cnt == 0) { return; } + size_t stride_shift = priv.all->weights.stride_shift(); + uint64_t multiplier = static_cast(priv.all->reduction_state.total_feature_width) << stride_shift; uint64_t extra_offset = 0; if (priv.is_ldf) { @@ -795,6 +779,7 @@ void add_example_conditioning(search_private& priv, VW::example& ec, size_t cond { break; // no more ngrams } + // we're going to add features for the ngram condition_on_actions[i .. i+N] uint64_t name = condition_on_names[i + n]; fid = fid * 328901 + 71933 * ((condition_on_actions[i + n].a + 349101) * (name + 38490137)); @@ -815,11 +800,16 @@ void add_example_conditioning(search_private& priv, VW::example& ec, size_t cond // add the single bias feature if (n < priv.acset.max_bias_ngram_length) { - add_new_feature(priv, 1., static_cast(4398201) << priv.all->weights.stride_shift()); + add_new_feature(priv, 1., static_cast(4398201) * multiplier); } + // add the quadratic features if (n < priv.acset.max_quad_ngram_length) { + auto old_ft_index_offset = ec.ft_offset; + ec.ft_offset = 0; + auto restore_ft_index_offset = + VW::scope_exit([&ec, old_ft_index_offset] { ec.ft_offset = old_ft_index_offset; }); VW::foreach_feature(*priv.all, ec, priv); } } @@ -837,7 +827,8 @@ void add_example_conditioning(search_private& priv, VW::example& ec, size_t cond { if ((fs.values[k] > 1e-10) || (fs.values[k] < -1e-10)) { - uint64_t fid = 84913 + 48371803 * (extra_offset + 8392817 * name) + 840137 * (4891 + fs.indices[k]); + uint64_t fid = + 84913 + 48371803 * (extra_offset + 8392817 * name) + 840137 * (4891 + fs.indices[k] / multiplier); if (priv.all->output_config.audit) { priv.dat_new_feature_audit_ss.str(""); @@ -849,7 +840,7 @@ void add_example_conditioning(search_private& priv, VW::example& ec, size_t cond priv.dat_new_feature_idx = fid; priv.dat_new_feature_namespace = VW::details::CONDITIONING_NAMESPACE; priv.dat_new_feature_value = fs.values[k]; - add_new_feature(priv, 1., static_cast(4398201) << priv.all->weights.stride_shift()); + add_new_feature(priv, 1., static_cast(4398201) * multiplier); } } } @@ -936,6 +927,7 @@ VW::polylabel& allowed_actions_to_ld(search_private& priv, size_t ec_cnt, const for (action k = num_costs; k < ec_cnt; k++) { cs_cost_push_back(is_cb, ld, k, FLT_MAX); } } } + else if (priv.use_action_costs) { // TODO: Weight @@ -957,6 +949,7 @@ VW::polylabel& allowed_actions_to_ld(search_private& priv, size_t ec_cnt, const } } } + else // non-LDF version, no action costs { if ((allowed_actions == nullptr) || (allowed_actions_cnt == 0)) // any action is allowed @@ -986,6 +979,7 @@ void allowed_actions_to_label(search_private& priv, size_t ec_cnt, const action* size_t oracle_actions_cnt, VW::polylabel& lab) { bool is_cb = priv.cb_learner; + if (priv.is_ldf) // LDF version easier { cs_costs_erase(is_cb, lab); @@ -994,6 +988,7 @@ void allowed_actions_to_label(search_private& priv, size_t ec_cnt, const action* cs_cost_push_back(is_cb, lab, k, array_contains(k, oracle_actions, oracle_actions_cnt) ? 0.f : 1.f); } } + else if (priv.use_action_costs) { // TODO: Weight @@ -1015,6 +1010,7 @@ void allowed_actions_to_label(search_private& priv, size_t ec_cnt, const action* } } } + else // non-LDF, no action costs { if ((allowed_actions == nullptr) || (allowed_actions_cnt == 0)) // any action is allowed @@ -1134,7 +1130,7 @@ action choose_oracle_action(search_private& priv, size_t ec_cnt, const action* o cdbg << " ], ret=" << a << endl; if (need_memo_foreach_action(priv) && (priv.state == search_state::INIT_TRAIN)) { - VW::v_array* this_cache = new VW::v_array(); + auto this_cache = VW::make_unique>(); // TODO we don't really need to construct this VW::polylabel VW::polylabel l = allowed_actions_to_ld(priv, 1, allowed_actions, allowed_actions_cnt, allowed_actions_cost); // NOLINT @@ -1146,8 +1142,8 @@ action choose_oracle_action(search_private& priv, size_t ec_cnt, const action* o this_cache->push_back(action_cache{0., cl, cl == a, cost}); } assert(priv.memo_foreach_action.size() == priv.meta_t + priv.t - 1); - priv.memo_foreach_action.push_back(this_cache); - cdbg << "memo_foreach_action[" << priv.meta_t + priv.t - 1 << "] = " << this_cache << " from oracle" << endl; + cdbg << "memo_foreach_action[" << priv.meta_t + priv.t - 1 << "] = " << this_cache.get() << " from oracle" << endl; + priv.memo_foreach_action.push_back(std::move(this_cache)); } return a; } @@ -1158,10 +1154,14 @@ action single_prediction_not_ldf(search_private& priv, VW::example& ec, int poli // appropriate cost for that action { VW::workspace& all = *priv.all; + VW::polylabel old_label = ec.l; + auto restore_label = VW::scope_exit([&ec, &old_label] { ec.l = old_label; }); + bool need_partial_predictions = need_memo_foreach_action(priv) || (priv.metaoverride && priv.metaoverride->_foreach_action) || (override_action != static_cast(-1)) || priv.active_csoaa; + if ((allowed_actions_cnt > 0) || need_partial_predictions) { ec.l = allowed_actions_to_ld(priv, 1, allowed_actions, allowed_actions_cnt, allowed_actions_cost); @@ -1198,10 +1198,10 @@ action single_prediction_not_ldf(search_private& priv, VW::example& ec, int poli float cost = cs_get_cost_partial_prediction(priv.cb_learner, ec.l, k); if (cost < min_cost) { min_cost = cost; } } - VW::v_array* this_cache = nullptr; + std::unique_ptr> this_cache = nullptr; if (need_memo_foreach_action(priv) && (override_action == static_cast(-1))) { - this_cache = new VW::v_array(); + this_cache.reset(new VW::v_array()); } for (size_t k = 0; k < K; k++) { @@ -1217,8 +1217,8 @@ action single_prediction_not_ldf(search_private& priv, VW::example& ec, int poli if (this_cache) { assert(priv.memo_foreach_action.size() == priv.meta_t + priv.t - 1); - priv.memo_foreach_action.push_back(this_cache); - cdbg << "memo_foreach_action[" << priv.meta_t + priv.t - 1 << "] = " << this_cache << endl; + cdbg << "memo_foreach_action[" << priv.meta_t + priv.t - 1 << "] = " << this_cache.get() << endl; + priv.memo_foreach_action.push_back(std::move(this_cache)); } } @@ -1241,6 +1241,7 @@ action single_prediction_not_ldf(search_private& priv, VW::example& ec, int poli priv.active_uncertainty.push_back(std::make_pair(min_cost2 - min_cost, priv.t + priv.meta_t)); } } + if ((priv.state == search_state::INIT_TRAIN) && priv.active_csoaa) { if (priv.cb_learner) THROW("cannot use active_csoaa with cb learning"); @@ -1290,8 +1291,6 @@ action single_prediction_not_ldf(search_private& priv, VW::example& ec, int poli all.output_runtime.raw_prediction.get(), priv.raw_output_string_stream->str(), ec.tag, all.logger); } - ec.l = old_label; - priv.total_predictions_made++; priv.num_features += ec.get_num_features(); @@ -1315,25 +1314,30 @@ action single_prediction_ldf(search_private& priv, VW::example* ecs, size_t ec_c size_t start_K = (priv.is_ldf && VW::is_cs_example_header(ecs[0])) ? 1 : 0; // NOLINT - VW::v_array* this_cache = nullptr; - if (need_partial_predictions) { this_cache = new VW::v_array(); } + std::unique_ptr> this_cache = nullptr; + if (need_partial_predictions) { this_cache.reset(new VW::v_array()); } for (action a = static_cast(start_K); a < ec_cnt; a++) { cdbg << "== single_prediction_ldf a=" << a << "==" << endl; - if (start_K > 0) { VW::details::append_example_namespaces_from_example(ecs[a], ecs[0]); } VW::polylabel old_label = ecs[a].l; + uint64_t old_offset = ecs[a].ft_offset; + auto restore_example = VW::scope_exit( + [&ecs, a, start_K, &old_label, old_offset] + { + ecs[a].l = old_label; + ecs[a].ft_offset = old_offset; + if (start_K > 0) { VW::details::truncate_example_namespaces_from_example(ecs[a], ecs[0]); } + }); + + if (start_K > 0) { VW::details::append_example_namespaces_from_example(ecs[a], ecs[0]); } ecs[a].l.cs = priv.ldf_test_label; + ecs[a].ft_offset = priv.offset; VW::multi_ex tmp; - uint64_t old_offset = ecs[a].ft_offset; - ecs[a].ft_offset = priv.offset; tmp.push_back(&ecs[a]); - require_multiline(priv.learner)->predict(tmp, policy); - - ecs[a].ft_offset = old_offset; cdbg << "partial_prediction[" << a << "] = " << ecs[a].partial_prediction << endl; if (override_action != static_cast(-1)) @@ -1349,9 +1353,8 @@ action single_prediction_ldf(search_private& priv, VW::example* ecs, size_t ec_c if (this_cache) { this_cache->push_back(action_cache{0., a, false, ecs[a].partial_prediction}); } priv.num_features += ecs[a].get_num_features(); - ecs[a].l = old_label; - if (start_K > 0) { VW::details::truncate_example_namespaces_from_example(ecs[a], ecs[0]); } } + if (override_action != static_cast(-1)) { best_action = override_action; } else { a_cost = best_prediction; } @@ -1369,9 +1372,8 @@ action single_prediction_ldf(search_private& priv, VW::example* ecs, size_t ec_c } if (need_memo_foreach_action(priv) && (override_action == static_cast(-1))) { - priv.memo_foreach_action.push_back(this_cache); + priv.memo_foreach_action.push_back(std::move(this_cache)); } - else { delete this_cache; } } // TODO: generate raw predictions if necessary @@ -1513,12 +1515,20 @@ void generate_training_example(search_private& priv, VW::polylabel& losses, floa VW::example& ec = priv.learn_ec_ref[0]; VW::polylabel old_label = ec.l; + auto restore_example = VW::scope_exit( + [&priv, &ec, &old_label, add_conditioning] + { + if (add_conditioning) { del_example_conditioning(priv, ec); } + ec.l = old_label; + }); + ec.l = losses; // labels; if (add_conditioning) { add_example_conditioning(priv, ec, priv.learn_condition_on.size(), priv.learn_condition_on_names.begin(), priv.learn_condition_on_act.data()); } + for (size_t is_local = 0; is_local <= static_cast(priv.xv); is_local++) { int learner = select_learner(priv, priv.current_policy, priv.learn_learner_id, true, is_local > 0); @@ -1526,15 +1536,27 @@ void generate_training_example(search_private& priv, VW::polylabel& losses, floa require_singleline(priv.learner)->learn(ec, learner); cdbg << "END learner->learn(ec, " << learner << ")" << endl; } - if (add_conditioning) { del_example_conditioning(priv, ec); } - ec.l = old_label; priv.total_examples_generated++; } - else // is LDF + + else // is LDF { assert(cs_get_costs_size(priv.cb_learner, losses) == priv.learn_ec_ref_cnt); size_t start_K = (priv.is_ldf && VW::is_cs_example_header(priv.learn_ec_ref[0])) ? 1 : 0; // NOLINT + auto restore_example = VW::scope_exit( + [&priv, start_K, add_conditioning] + { + if (add_conditioning) + { + for (action a = static_cast(start_K); a < priv.learn_ec_ref_cnt; a++) + { + VW::example& ec = priv.learn_ec_ref[a]; + del_example_conditioning(priv, ec); + } + } + }); + // TODO: weight if (add_conditioning) { @@ -1584,15 +1606,6 @@ void generate_training_example(search_private& priv, VW::polylabel& losses, floa priv.learn_ec_ref[a].ft_offset = tmp_offset; } } - - if (add_conditioning) - { - for (action a = static_cast(start_K); a < priv.learn_ec_ref_cnt; a++) - { - VW::example& ec = priv.learn_ec_ref[a]; - del_example_conditioning(priv, ec); - } - } } } @@ -1639,7 +1652,7 @@ void foreach_action_from_cache(search_private& priv, size_t t, action override_a cdbg << "foreach_action_from_cache: t=" << t << ", memo_foreach_action.size()=" << priv.memo_foreach_action.size() << ", override_a=" << override_a << endl; assert(t < priv.memo_foreach_action.size()); - VW::v_array* cached = priv.memo_foreach_action[t]; + VW::v_array* cached = priv.memo_foreach_action[t].get(); if (!cached) { return; // the only way this can happen is if the metatask overrode this action @@ -1739,11 +1752,10 @@ action search_predict(search_private& priv, VW::example* ecs, size_t ec_cnt, pta for (size_t i = 0; i < condition_on_cnt; i++) { - set_at(priv.learn_condition_on_act, - action_repr(((1 <= condition_on[i]) && (condition_on[i] < priv.ptag_to_action.size())) - ? priv.ptag_to_action[condition_on[i]] - : 0), - i); + action_repr ar = ((1 <= condition_on[i]) && (condition_on[i] < priv.ptag_to_action.size())) + ? priv.ptag_to_action[condition_on[i]] + : action_repr(0); + set_at(priv.learn_condition_on_act, std::move(ar), i); } if (condition_on_names == nullptr) @@ -1860,6 +1872,15 @@ action search_predict(search_private& priv, VW::example* ecs, size_t ec_cnt, pta { size_t start_K = (priv.is_ldf && VW::is_cs_example_header(ecs[0])) ? 1 : 0; // NOLINT priv.last_action_repr.clear(); + + auto restore_example = VW::scope_exit( + [&priv, start_K, ec_cnt, ecs] + { + if (priv.auto_condition_features) + { + for (size_t n = start_K; n < ec_cnt; n++) { del_example_conditioning(priv, ecs[n]); } + } + }); if (priv.auto_condition_features) { for (size_t n = start_K; n < ec_cnt; n++) @@ -1920,15 +1941,10 @@ action search_predict(search_private& priv, VW::example* ecs, size_t ec_cnt, pta memcpy(priv.learn_allowed_actions.begin(), allowed_actions, allowed_actions_cnt * sizeof(action)); } size_t old_learner_id = priv.learn_learner_id; + auto restore_learner_id = VW::scope_exit([&priv, old_learner_id] { priv.learn_learner_id = old_learner_id; }); priv.learn_learner_id = learner_id; generate_training_example( priv, priv.gte_label, 1., false); // this is false because the conditioning has already been added! - priv.learn_learner_id = old_learner_id; - } - - if (priv.auto_condition_features) - { - for (size_t n = start_K; n < ec_cnt; n++) { del_example_conditioning(priv, ecs[n]); } } if (not_test && (!skip)) @@ -1970,11 +1986,11 @@ void hoopla_permute(size_t* B, size_t* end) size_t N = end - B; // NOLINT std::sort(B, end, cmp_size_t); // make some temporary space - size_t* A = VW::details::calloc_or_throw((N + 1) * 2); // NOLINT - A[N] = B[0]; // arbitrarily choose the maximum in the middle - A[N + 1] = B[N - 1]; // so the maximum goes next to it - size_t lo = N, hi = N + 1; // which parts of A have we filled in? [lo,hi] - size_t i = 0, j = N - 1; // which parts of B have we already covered? [0,i] and [j,N-1] + std::vector A((N + 1) * 2, 0); // NOLINT + A[N] = B[0]; // arbitrarily choose the maximum in the middle + A[N + 1] = B[N - 1]; // so the maximum goes next to it + size_t lo = N, hi = N + 1; // which parts of A have we filled in? [lo,hi] + size_t i = 0, j = N - 1; // which parts of B have we already covered? [0,i] and [j,N-1] while (i + 1 < j) { // there are four options depending on where things get placed @@ -1989,9 +2005,8 @@ void hoopla_permute(size_t* B, size_t* end) else { A[++hi] = B[--j]; } } // copy it back to B - memcpy(B, A + lo, N * sizeof(size_t)); + memcpy(B, A.data() + lo, N * sizeof(size_t)); // clean up - free(A); } void get_training_timesteps(search_private& priv, VW::v_array& timesteps) @@ -2009,6 +2024,7 @@ void get_training_timesteps(search_private& priv, VW::v_array& timesteps } } } + // if there's no subsampling to do, just return [0,T) else if (priv.subsample_timesteps <= 0) { @@ -2032,6 +2048,7 @@ void get_training_timesteps(search_private& priv, VW::v_array& timesteps // if subsample in (0,1) then pick steps with that probability, but ensuring there's at least one! } + else if (priv.subsample_timesteps < 1) { for (size_t t = 0; t < priv.T; t++) @@ -2065,12 +2082,24 @@ void BaseTask::Run() search_private& priv = *sch->priv; // make sure output is correct bool old_should_produce_string = priv.should_produce_string; - if (!_final_run && !_with_output_string) { priv.should_produce_string = false; } // if this isn't a final run, it shouldn't count for loss float old_test_loss = priv.test_loss; // float old_learn_loss = priv.learn_loss; - priv.learn_loss *= 0.5; float old_train_loss = priv.train_loss; + auto restore_priv = VW::scope_exit( + [this, &priv, old_should_produce_string, old_test_loss, old_train_loss] + { + priv.should_produce_string = old_should_produce_string; + if (!this->_final_run) + { + priv.test_loss = old_test_loss; + // priv.learn_loss = old_learn_loss; + priv.train_loss = old_train_loss; + } + }); + + if (!_final_run && !_with_output_string) { priv.should_produce_string = false; } + priv.learn_loss *= 0.5; if (priv.should_produce_string) { priv.pred_string->str(""); } @@ -2080,16 +2109,7 @@ void BaseTask::Run() priv.metaoverride = nullptr; priv.meta_t += priv.t; - // restore if (_with_output_string && old_should_produce_string) { _with_output_string(*sch, *priv.pred_string); } - - priv.should_produce_string = old_should_produce_string; - if (!_final_run) - { - priv.test_loss = old_test_loss; - // priv.learn_loss = old_learn_loss; - priv.train_loss = old_train_loss; - } } void run_task(search& sch, VW::multi_ex& ec) @@ -2562,7 +2582,7 @@ std::vector read_allowed_transitions(action A, const char* filenam THROW("error: could not read file " << filename << " (" << VW::io::strerror_to_string(errno) << "); assuming all transitions are valid"); - bool* bg = VW::details::calloc_or_throw((static_cast(A + 1)) * (A + 1)); + std::vector bg((static_cast(A + 1)) * (A + 1), false); int rd, from, to, count = 0; while ((rd = fscanf_s(f, "%d:%d", &from, &to)) > 0) { @@ -2599,7 +2619,6 @@ std::vector read_allowed_transitions(action A, const char* filenam VW::cs_label ld = {costs}; allowed.push_back(ld); } - free(bg); logger.err_info("read {0} allowed transitions from {1}", count, filename); @@ -2682,11 +2701,7 @@ action search::predict(VW::example& ec, ptag mytag, const action* oracle_actions if (mytag < priv->ptag_to_action.size()) { cdbg << "delete_v at " << mytag << endl; - if (priv->ptag_to_action[mytag].repr != nullptr) - { - delete priv->ptag_to_action[mytag].repr; - priv->ptag_to_action[mytag].repr = nullptr; - } + priv->ptag_to_action[mytag].repr = nullptr; } if (priv->acset.use_passthrough_repr) { @@ -2726,11 +2741,7 @@ action search::predictLDF(VW::example* ecs, size_t ec_cnt, ptag mytag, const act if (mytag < priv->ptag_to_action.size()) { cdbg << "delete_v at " << mytag << endl; - if (priv->ptag_to_action[mytag].repr != nullptr) - { - delete priv->ptag_to_action[mytag].repr; - priv->ptag_to_action[mytag].repr = nullptr; - } + priv->ptag_to_action[mytag].repr = nullptr; } set_at(priv->ptag_to_action, action_repr(ecs[a].l.cs.costs[0].class_index, &(priv->last_action_repr)), mytag); } diff --git a/vowpalwabbit/core/src/reductions/search/search_dep_parser.cc b/vowpalwabbit/core/src/reductions/search/search_dep_parser.cc index 4c34cffe225..a4b76d6dd9f 100644 --- a/vowpalwabbit/core/src/reductions/search/search_dep_parser.cc +++ b/vowpalwabbit/core/src/reductions/search/search_dep_parser.cc @@ -67,7 +67,7 @@ constexpr uint32_t MY_NULL = 9999999; /*representing_default*/ void initialize(Search::search& sch, size_t& /*num_actions*/, options_i& options) { VW::workspace& all = sch.get_vw_pointer_unsafe(); - task_data* data = new task_data(); + auto data = std::make_shared(); sch.set_task_data(data); data->action_loss.resize(5); uint64_t root_label; diff --git a/vowpalwabbit/core/src/reductions/search/search_entityrelationtask.cc b/vowpalwabbit/core/src/reductions/search/search_entityrelationtask.cc index eb0d7794bd7..f40147451da 100644 --- a/vowpalwabbit/core/src/reductions/search/search_entityrelationtask.cc +++ b/vowpalwabbit/core/src/reductions/search/search_entityrelationtask.cc @@ -45,7 +45,7 @@ class task_data void initialize(Search::search& sch, size_t& /*num_actions*/, options_i& options) { - task_data* my_task_data = new task_data(); + auto my_task_data = std::make_shared(); sch.set_task_data(my_task_data); uint64_t search_order; diff --git a/vowpalwabbit/core/src/reductions/search/search_graph.cc b/vowpalwabbit/core/src/reductions/search/search_graph.cc index 121e8a60237..994c52e678b 100644 --- a/vowpalwabbit/core/src/reductions/search/search_graph.cc +++ b/vowpalwabbit/core/src/reductions/search/search_graph.cc @@ -98,7 +98,7 @@ inline bool example_is_test(const VW::polylabel& l) { return l.cs.costs.empty(); void initialize(Search::search& sch, size_t& num_actions, options_i& options) { - auto D = VW::make_unique(); // NOLINT + auto D = std::make_shared(); // NOLINT uint64_t num_loops; option_group_definition new_options("[Search] Search Graphtask"); @@ -132,7 +132,7 @@ void initialize(Search::search& sch, size_t& num_actions, options_i& options) if (D->separate_learners) { sch.set_feature_width(D->num_loops); } - sch.set_task_data(D.release()); + sch.set_task_data(std::move(D)); sch.set_options(0); // Search::AUTO_HAMMING_LOSS sch.set_label_parser(VW::cs_label_parser_global, example_is_test); } diff --git a/vowpalwabbit/core/src/reductions/search/search_hooktask.cc b/vowpalwabbit/core/src/reductions/search/search_hooktask.cc index 5c3a86d9fd9..0a5e3f04e67 100644 --- a/vowpalwabbit/core/src/reductions/search/search_hooktask.cc +++ b/vowpalwabbit/core/src/reductions/search/search_hooktask.cc @@ -15,7 +15,7 @@ Search::search_task task = {"hook", run, initialize, nullptr, run_setup, run_tak void initialize(Search::search& sch, size_t& num_actions, options_i& arg) { - task_data* td = new task_data; + auto td = std::make_shared(); td->run_f = nullptr; td->run_setup_f = nullptr; td->run_takedown_f = nullptr; diff --git a/vowpalwabbit/core/src/reductions/search/search_meta.cc b/vowpalwabbit/core/src/reductions/search/search_meta.cc index 45f30081d94..ac30f677a88 100644 --- a/vowpalwabbit/core/src/reductions/search/search_meta.cc +++ b/vowpalwabbit/core/src/reductions/search/search_meta.cc @@ -75,22 +75,14 @@ class task_data public: size_t max_branches, kbest; std::vector branches; - std::vector > final; + std::vector>> final; path trajectory; float total_cost; size_t cur_branch; - std::string* output_string; - std::stringstream* kbest_out; - task_data(size_t mb, size_t kb) : max_branches(mb), kbest(kb) - { - output_string = nullptr; - kbest_out = nullptr; - } - ~task_data() - { - delete output_string; - delete kbest_out; - } + std::unique_ptr output_string = nullptr; + std::unique_ptr kbest_out = nullptr; + + task_data(size_t mb, size_t kb) : max_branches(mb), kbest(kb) {} }; void initialize(Search::search& sch, size_t& /*num_actions*/, options_i& options) @@ -107,8 +99,9 @@ void initialize(Search::search& sch, size_t& /*num_actions*/, options_i& options .help("Number of best items to output (0=just like non-selectional-branching, default)")); options.add_and_parse(new_options); - task_data* d = new task_data(VW::cast_to_smaller_type(max_branches), VW::cast_to_smaller_type(kbest)); - sch.set_metatask_data(d); + auto d = std::make_shared( + VW::cast_to_smaller_type(max_branches), VW::cast_to_smaller_type(kbest)); + sch.set_metatask_data(std::move(d)); } void run(Search::search& sch, VW::multi_ex& ec) @@ -151,7 +144,7 @@ void run(Search::search& sch, VW::multi_ex& ec) d.total_cost += a_cost; }) .with_output_string([](Search::search& sch, std::stringstream& output) -> void - { sch.get_metatask_data()->output_string = new std::string(output.str()); }) + { sch.get_metatask_data()->output_string.reset(new std::string(output.str())); }) .Run(); // the last item the trajectory stack is complete and therefore not a branch @@ -161,7 +154,7 @@ void run(Search::search& sch, VW::multi_ex& ec) { // construct the final trajectory path original_final = d.trajectory; - d.final.push_back(std::make_pair(std::make_pair(d.total_cost, original_final), d.output_string)); + d.final.push_back(std::make_pair(std::make_pair(d.total_cost, original_final), std::move(d.output_string))); } // sort the branches by cost @@ -200,25 +193,25 @@ void run(Search::search& sch, VW::multi_ex& ec) d.total_cost += a_cost; }) .with_output_string([](Search::search& sch, std::stringstream& output) -> void - { sch.get_metatask_data()->output_string = new std::string(output.str()); }) + { sch.get_metatask_data()->output_string.reset(new std::string(output.str())); }) .Run(); { // construct the final trajectory path this_final = d.trajectory; - d.final.push_back(std::make_pair(std::make_pair(d.total_cost, this_final), d.output_string)); + d.final.push_back(std::make_pair(std::make_pair(d.total_cost, this_final), std::move(d.output_string))); } } // sort the finals by cost stable_sort(d.final.begin(), d.final.end(), - [](const std::pair& a, const std::pair& b) -> bool - { return a.first.first < b.first.first; }); + [](const std::pair>& a, + const std::pair>& b) -> bool { return a.first.first < b.first.first; }); - d.kbest_out = nullptr; + d.kbest_out.reset(); if (d.output_string && (d.kbest > 0)) { - d.kbest_out = new std::stringstream(); + d.kbest_out.reset(new std::stringstream()); for (size_t i = 0; i < std::min(d.final.size(), d.kbest); i++) { (*d.kbest_out) << *d.final[i].second << "\t" << d.final[i].first.first << std::endl; @@ -257,9 +250,6 @@ void run(Search::search& sch, VW::multi_ex& ec) // clean up memory d.branches.clear(); - for (size_t i = 0; i < d.final.size(); i++) { delete d.final[i].second; } d.final.clear(); - delete d.kbest_out; - d.kbest_out = nullptr; } } // namespace SelectiveBranchingMT diff --git a/vowpalwabbit/core/src/reductions/search/search_multiclasstask.cc b/vowpalwabbit/core/src/reductions/search/search_multiclasstask.cc index 26ef4eb711c..cd2c469a4d6 100644 --- a/vowpalwabbit/core/src/reductions/search/search_multiclasstask.cc +++ b/vowpalwabbit/core/src/reductions/search/search_multiclasstask.cc @@ -22,7 +22,7 @@ class task_data void initialize(Search::search& sch, size_t& num_actions, VW::config::options_i& /*vm*/) { - task_data* my_task_data = new task_data(); + auto my_task_data = std::make_shared(); sch.set_options(0); size_t num_learner_ceil = 1; while (num_learner_ceil < num_actions) { num_learner_ceil *= 2; } diff --git a/vowpalwabbit/core/src/reductions/search/search_sequencetask.cc b/vowpalwabbit/core/src/reductions/search/search_sequencetask.cc index a2fedb04f2d..ea2eeb1e1a6 100644 --- a/vowpalwabbit/core/src/reductions/search/search_sequencetask.cc +++ b/vowpalwabbit/core/src/reductions/search/search_sequencetask.cc @@ -154,7 +154,7 @@ void initialize(Search::search& sch, size_t& num_actions, options_i& options) .add(make_option("search_span_multipass", multipass).default_value(1).help("Do multiple passes")); options.add_and_parse(new_options); - auto data = VW::make_unique(); + auto data = std::make_shared(); data->multipass = VW::cast_to_smaller_type(multipass); if (search_span_bilou) @@ -192,7 +192,7 @@ void initialize(Search::search& sch, size_t& num_actions, options_i& options) Search::EXAMPLES_DONT_CHANGE | // we don't do any internal example munging 0); sch.set_feature_width(num_actions); - sch.set_task_data(data.release()); + sch.set_task_data(std::move(data)); } void setup(Search::search& sch, VW::multi_ex& ec) @@ -293,13 +293,14 @@ void initialize(Search::search& sch, size_t& num_actions, options_i& /*options*/ Search::EXAMPLES_DONT_CHANGE | // we don't do any internal example munging Search::ACTION_COSTS | // we'll provide cost-per-action (rather than oracle) 0); - sch.set_task_data(new size_t{num_actions}); + std::shared_ptr td(new size_t{num_actions}); + sch.set_task_data(std::move(td)); } void run(Search::search& sch, VW::multi_ex& ec) { size_t K = *sch.get_task_data(); // NOLINT - float* costs = VW::details::calloc_or_throw(K); + std::vector costs(K, 0.); Search::predictor search_predictor(sch, static_cast(0)); for (size_t i = 0; i < ec.size(); i++) { @@ -308,12 +309,11 @@ void run(Search::search& sch, VW::multi_ex& ec) costs[oracle - 1] = 0.; size_t prediction = search_predictor.set_tag(static_cast(i) + 1) .set_input(*ec[i]) - .set_allowed(nullptr, costs, K) + .set_allowed(nullptr, costs.data(), K) .set_condition_range(static_cast(i), sch.get_history_length(), 'p') .predict(); if (sch.output().good()) { sch.output() << sch.pretty_label(static_cast(prediction)) << ' '; } } - free(costs); } } // namespace SequenceTaskCostToGo @@ -329,7 +329,7 @@ class task_data void initialize(Search::search& sch, size_t& /*num_actions*/, options_i& options) { - task_data* data = new task_data(); + auto data = std::make_shared(); option_group_definition new_options("[Search] Argmax"); new_options.add(make_option("cost", data->false_negative_cost).default_value(10.0f).help("False Negative Cost")) @@ -390,7 +390,7 @@ void initialize(Search::search& sch, size_t& num_actions, options_i& /*options*/ { VW::cs_class default_wclass = {0., 0, 0., 0.}; - task_data* data = new task_data; + auto data = std::make_shared(); data->ldf_examples.resize(num_actions); for (size_t a = 0; a < num_actions; a++) {