11#include " models/starvla/fast_codec.h"
22
3- #include " nlohmann/json.hpp"
4-
53#include < algorithm>
6- #include < array>
74#include < cmath>
8- #include < exception>
9- #include < fstream>
105#include < limits>
11- #include < sstream>
126#include < unordered_map>
137#include < unordered_set>
148#include < utility>
159
1610namespace robotcpp ::starvla {
1711namespace {
1812
19- using Json = nlohmann::json;
20-
21- constexpr size_t kMaximumJsonBytes = 16U * 1024U * 1024U ;
22- constexpr size_t kOfficialVocabSize = 2048U ;
2313constexpr size_t kMaximumVocabSize = 65536U ;
2414constexpr size_t kMaximumTimeHorizon = 1024U ;
2515constexpr size_t kMaximumActionDim = 1024U ;
@@ -29,56 +19,8 @@ constexpr size_t kMaximumGeneratedSequence = 2048U;
2919constexpr size_t kMaximumDecodedBytes = 1024U * 1024U ;
3020constexpr size_t kMaximumOutputScalars = 16U * 1024U * 1024U ;
3121constexpr uint64_t kMaximumIdctMultiplyAdds = 64ULL * 1024ULL * 1024ULL ;
32- constexpr const char * kActionTokenPrefix = " <robot_action_" ;
3322constexpr double kPi = 3.141592653589793238462643383279502884 ;
3423
35- bool read_json (const std::filesystem::path & path, Json & output, std::string & error) {
36- std::error_code filesystem_error;
37- const uintmax_t size = std::filesystem::file_size (path, filesystem_error);
38- if (filesystem_error) {
39- error = " cannot stat StarVLA FAST JSON asset '" + path.string () + " ': " +
40- filesystem_error.message ();
41- return false ;
42- }
43- if (size > kMaximumJsonBytes ) {
44- error = " StarVLA FAST JSON asset exceeds the 16 MiB limit: " + path.string ();
45- return false ;
46- }
47-
48- std::ifstream stream (path, std::ios::binary);
49- if (!stream) {
50- error = " cannot open StarVLA FAST JSON asset: " + path.string ();
51- return false ;
52- }
53- std::string contents;
54- contents.reserve (static_cast <size_t >(size));
55- std::array<char , 64U * 1024U > buffer{};
56- while (stream) {
57- stream.read (buffer.data (), static_cast <std::streamsize>(buffer.size ()));
58- const std::streamsize count = stream.gcount ();
59- if (count <= 0 ) {
60- continue ;
61- }
62- const size_t chunk_size = static_cast <size_t >(count);
63- if (contents.size () > kMaximumJsonBytes - chunk_size) {
64- error = " StarVLA FAST JSON asset exceeds the 16 MiB limit while reading: " +
65- path.string ();
66- return false ;
67- }
68- contents.append (buffer.data (), chunk_size);
69- }
70- if (!stream.eof () || stream.bad ()) {
71- error = " cannot read StarVLA FAST JSON asset: " + path.string ();
72- return false ;
73- }
74- output = Json::parse (contents, nullptr , false );
75- if (output.is_discarded ()) {
76- error = " cannot parse StarVLA FAST JSON asset: " + path.string ();
77- return false ;
78- }
79- return true ;
80- }
81-
8224bool decode_utf8_strict (const std::string & input, std::vector<uint32_t > & output,
8325 std::string & error) {
8426 output.clear ();
@@ -258,200 +200,6 @@ void decode_utf8_lossy(const std::vector<uint8_t> & input, std::vector<uint32_t>
258200 }
259201}
260202
261- bool parse_positive_size (const Json & value, const char * name, size_t & output,
262- std::string & error) {
263- if (!value.is_number_integer ()) {
264- error = std::string (" StarVLA FAST " ) + name + " must be an integer" ;
265- return false ;
266- }
267- try {
268- const int64_t parsed = value.get <int64_t >();
269- if (parsed <= 0 || static_cast <uint64_t >(parsed) >
270- static_cast <uint64_t >(std::numeric_limits<size_t >::max ())) {
271- error = std::string (" StarVLA FAST " ) + name + " is out of range" ;
272- return false ;
273- }
274- output = static_cast <size_t >(parsed);
275- return true ;
276- } catch (const std::exception &) {
277- error = std::string (" StarVLA FAST " ) + name + " is out of range" ;
278- return false ;
279- }
280- }
281-
282- bool parse_processor_config (const Json & json, size_t time_horizon_override,
283- size_t action_dim_override, FastCodecConfig & config,
284- std::string & error) {
285- if (!json.is_object () || !json.contains (" processor_class" ) ||
286- json[" processor_class" ] != " UniversalActionProcessor" || !json.contains (" scale" ) ||
287- !json[" scale" ].is_number () || !json.contains (" vocab_size" ) ||
288- !json.contains (" min_token" ) || !json[" min_token" ].is_number_integer ()) {
289- error = " StarVLA FAST processor_config.json has an incompatible schema" ;
290- return false ;
291- }
292- config.scale = json[" scale" ].get <double >();
293- if (!std::isfinite (config.scale ) || config.scale == 0.0 ) {
294- error = " StarVLA FAST processor scale must be finite and non-zero" ;
295- return false ;
296- }
297- if (!parse_positive_size (json[" vocab_size" ], " vocab_size" , config.vocab_size , error)) {
298- return false ;
299- }
300- if (config.vocab_size != kOfficialVocabSize ) {
301- error = " StarVLA FAST pinned vocabulary must contain exactly 2048 tokens" ;
302- return false ;
303- }
304- try {
305- if (json[" min_token" ].is_number_unsigned ()) {
306- const uint64_t min_token = json[" min_token" ].get <uint64_t >();
307- if (min_token > static_cast <uint64_t >(std::numeric_limits<int32_t >::max ())) {
308- error = " StarVLA FAST min_token is out of int32 range" ;
309- return false ;
310- }
311- config.min_token = static_cast <int32_t >(min_token);
312- } else {
313- const int64_t min_token = json[" min_token" ].get <int64_t >();
314- if (min_token < std::numeric_limits<int32_t >::min () ||
315- min_token > std::numeric_limits<int32_t >::max ()) {
316- error = " StarVLA FAST min_token is out of int32 range" ;
317- return false ;
318- }
319- config.min_token = static_cast <int32_t >(min_token);
320- }
321- } catch (const std::exception &) {
322- error = " StarVLA FAST min_token is out of int32 range" ;
323- return false ;
324- }
325-
326- auto choose_dimension = [&](const char * name, size_t override_value, size_t & target) {
327- if (override_value != 0 ) {
328- target = override_value;
329- return true ;
330- }
331- if (!json.contains (name) || json[name].is_null ()) {
332- error = std::string (" StarVLA FAST " ) + name +
333- " is absent; pass the policy dimension explicitly" ;
334- return false ;
335- }
336- return parse_positive_size (json[name], name, target, error);
337- };
338- return choose_dimension (" time_horizon" , time_horizon_override, config.time_horizon ) &&
339- choose_dimension (" action_dim" , action_dim_override, config.action_dim );
340- }
341-
342- bool parse_tokenizer_vocab (const Json & json, size_t expected_vocab_size,
343- std::vector<std::string> & vocab_by_id, std::string & error) {
344- if (!json.is_object () || !json.contains (" version" ) || json[" version" ] != " 1.0" ||
345- !json.contains (" added_tokens" ) || !json[" added_tokens" ].is_array () ||
346- !json[" added_tokens" ].empty () || !json.contains (" decoder" ) ||
347- !json[" decoder" ].is_object () || !json[" decoder" ].contains (" type" ) ||
348- json[" decoder" ][" type" ] != " ByteLevel" || !json.contains (" model" ) ||
349- !json[" model" ].is_object () || !json[" model" ].contains (" type" ) ||
350- json[" model" ][" type" ] != " BPE" || !json[" model" ].contains (" vocab" ) ||
351- !json[" model" ][" vocab" ].is_object ()) {
352- error = " StarVLA FAST tokenizer.json is not the required ByteLevel BPE schema" ;
353- return false ;
354- }
355- const Json & decoder = json[" decoder" ];
356- if (decoder.size () != 4 || !decoder.contains (" add_prefix_space" ) ||
357- decoder[" add_prefix_space" ] != true || !decoder.contains (" trim_offsets" ) ||
358- decoder[" trim_offsets" ] != true || !decoder.contains (" use_regex" ) ||
359- decoder[" use_regex" ] != true ) {
360- error = " StarVLA FAST tokenizer.json has an incompatible ByteLevel decoder contract" ;
361- return false ;
362- }
363- const Json & vocab = json[" model" ][" vocab" ];
364- if (vocab.size () != expected_vocab_size) {
365- error = " StarVLA FAST tokenizer vocabulary size does not match processor_config.json" ;
366- return false ;
367- }
368- vocab_by_id.assign (expected_vocab_size, std::string ());
369- std::vector<bool > seen (expected_vocab_size, false );
370- for (auto iterator = vocab.begin (); iterator != vocab.end (); ++iterator) {
371- if (!iterator.value ().is_number_integer ()) {
372- error = " StarVLA FAST tokenizer vocabulary ID is not an integer" ;
373- return false ;
374- }
375- int64_t token_id = -1 ;
376- try {
377- token_id = iterator.value ().get <int64_t >();
378- } catch (const std::exception &) {
379- error = " StarVLA FAST tokenizer vocabulary ID is out of range" ;
380- return false ;
381- }
382- if (token_id < 0 || static_cast <uint64_t >(token_id) >= expected_vocab_size ||
383- seen[static_cast <size_t >(token_id)]) {
384- error = " StarVLA FAST tokenizer vocabulary IDs are not a bijection" ;
385- return false ;
386- }
387- seen[static_cast <size_t >(token_id)] = true ;
388- vocab_by_id[static_cast <size_t >(token_id)] = iterator.key ();
389- }
390- return true ;
391- }
392-
393- bool parse_action_index (const std::string & value, size_t & index) {
394- const std::string prefix (kActionTokenPrefix );
395- if (value.size () <= prefix.size () + 1 || value.compare (0 , prefix.size (), prefix) != 0 ||
396- value.back () != ' >' ) {
397- return false ;
398- }
399- const std::string digits = value.substr (prefix.size (), value.size () - prefix.size () - 1 );
400- if (digits.empty () || (digits.size () > 1 && digits.front () == ' 0' )) {
401- return false ;
402- }
403- size_t parsed = 0 ;
404- for (char character : digits) {
405- if (character < ' 0' || character > ' 9' ) {
406- return false ;
407- }
408- const size_t digit = static_cast <size_t >(character - ' 0' );
409- if (parsed > (std::numeric_limits<size_t >::max () - digit) / 10U ) {
410- return false ;
411- }
412- parsed = parsed * 10U + digit;
413- }
414- index = parsed;
415- return true ;
416- }
417-
418- bool parse_action_map (const Json & json, size_t vocab_size,
419- std::vector<int32_t > & fast_to_vlm, std::string & error) {
420- if (!json.is_object () || json.size () != vocab_size) {
421- error = " StarVLA FAST action-token map must contain exactly one entry per FAST token" ;
422- return false ;
423- }
424- fast_to_vlm.assign (vocab_size, -1 );
425- std::vector<bool > seen (vocab_size, false );
426- std::unordered_set<int32_t > vlm_ids;
427- for (auto iterator = json.begin (); iterator != json.end (); ++iterator) {
428- size_t fast_id = 0 ;
429- if (!parse_action_index (iterator.key (), fast_id) || fast_id >= vocab_size || seen[fast_id]) {
430- error = " StarVLA FAST action-token map has a malformed or duplicate token name" ;
431- return false ;
432- }
433- if (!iterator.value ().is_number_integer ()) {
434- error = " StarVLA FAST action-token map contains a non-integer VLM ID" ;
435- return false ;
436- }
437- int64_t vlm_id = -1 ;
438- try {
439- vlm_id = iterator.value ().get <int64_t >();
440- } catch (const std::exception &) {
441- error = " StarVLA FAST action-token VLM ID is out of range" ;
442- return false ;
443- }
444- if (vlm_id < 0 || vlm_id > std::numeric_limits<int32_t >::max () ||
445- !vlm_ids.insert (static_cast <int32_t >(vlm_id)).second ) {
446- error = " StarVLA FAST action-token VLM IDs must be unique non-negative int32 values" ;
447- return false ;
448- }
449- seen[fast_id] = true ;
450- fast_to_vlm[fast_id] = static_cast <int32_t >(vlm_id);
451- }
452- return true ;
453- }
454-
455203bool checked_action_count (const FastCodecConfig & config, size_t batch_size,
456204 size_t & per_sample, size_t & total, std::string & error) {
457205 if (config.vocab_size > kMaximumVocabSize || config.time_horizon > kMaximumTimeHorizon ||
@@ -602,34 +350,6 @@ std::unique_ptr<FastCodec> FastCodec::create_compiled(
602350 new FastCodec (config, std::move (pieces), std::move (fast_to_vlm_id)));
603351}
604352
605- std::unique_ptr<FastCodec> FastCodec::load_hf_assets (
606- const std::filesystem::path & tokenizer_json,
607- const std::filesystem::path & processor_config_json,
608- const std::filesystem::path & action_token_map_json,
609- size_t time_horizon, size_t action_dim, std::string & error) {
610- error.clear ();
611- Json processor;
612- Json tokenizer;
613- Json action_map;
614- if (!read_json (processor_config_json, processor, error) ||
615- !read_json (tokenizer_json, tokenizer, error) ||
616- !read_json (action_token_map_json, action_map, error)) {
617- return nullptr ;
618- }
619-
620- FastCodecConfig config;
621- std::vector<std::string> vocab_by_id;
622- std::vector<int32_t > fast_to_vlm;
623- size_t per_sample = 0 ;
624- size_t total = 0 ;
625- if (!parse_processor_config (processor, time_horizon, action_dim, config, error) ||
626- !checked_action_count (config, 1 , per_sample, total, error) ||
627- !parse_tokenizer_vocab (tokenizer, config.vocab_size , vocab_by_id, error) ||
628- !parse_action_map (action_map, config.vocab_size , fast_to_vlm, error)) {
629- return nullptr ;
630- }
631- return create (config, std::move (vocab_by_id), std::move (fast_to_vlm), error);
632- }
633353
634354const FastCodecConfig & FastCodec::config () const {
635355 return config_;
0 commit comments