feat: enhance tokenizer handling in model configuration - #147
Conversation
Co-authored-by: Copilot <copilot@github.com>
There was a problem hiding this comment.
Pull request overview
Enhances the model pull workflow so RKLLM-converted Hugging Face repos that lack tokenizer/config artifacts can automatically resolve and record an upstream tokenizer source in the generated Modelfile, improving reliability of tokenizer loading at runtime.
Changes:
- Extend Modelfile generation to optionally include
TOKENIZER="<resolved_repo>". - Add Hugging Face repo inspection + metadata-walk logic during
/pullto resolve a repo that containsconfig.json. - Update tokenizer loading to prefer
TOKENIZERfrom Modelfile and fall back toHUGGINGFACE_PATH.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/rkllama/server/server.py | Adds tokenizer repo discovery during /pull and writes TOKENIZER into Modelfile when needed. |
| src/rkllama/api/server_utils.py | Adjusts tokenizer loading to use TOKENIZER first, with fallback + clearer failure behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def hf_repo_has_config(repo: str, fs=None) -> bool: | ||
| fs = fs or HfFileSystem() | ||
| try: | ||
| fs.info(f"{repo}/config.json") | ||
| return True | ||
| except Exception: | ||
| return False |
There was a problem hiding this comment.
hf_repo_has_config catches all exceptions and returns False, which can silently treat auth/network failures as “missing config.json”. Consider only returning False for “not found” errors (e.g., EntryNotFound/FileNotFound) and logging (or re-raising) unexpected exceptions so pull output/logs are actionable when Hugging Face is unavailable or credentials are missing.
| def get_hf_model_metadata(repo: str) -> dict | None: | ||
| try: | ||
| response = requests.get(f"https://huggingface.co/api/models/{repo}", timeout=10) | ||
| if response.status_code == 200: |
There was a problem hiding this comment.
The return type annotations use PEP 604 unions (e.g., dict | None, str | None), but the project metadata declares requires-python >=3.9. Python 3.9 will fail to import this module due to syntax error. Either switch these annotations to Optional[...] / Union[...] or bump the project’s minimum supported Python version to 3.10+.
| if response.status_code == 200: | ||
| return response.json() | ||
| except Exception as e: |
There was a problem hiding this comment.
get_hf_model_metadata returns None for any non-200 response without logging the status/body, which makes tokenizer resolution failures hard to diagnose (e.g., 404 vs 401 vs rate limit). Consider logging at least the status code (debug/info) and/or using response.raise_for_status() with controlled handling.
| if response.status_code == 200: | |
| return response.json() | |
| except Exception as e: | |
| response.raise_for_status() | |
| try: | |
| return response.json() | |
| except ValueError as e: | |
| logger.debug(f"Unable to parse Hugging Face metadata JSON for {repo}: {e}") | |
| except requests.HTTPError as e: | |
| response = e.response | |
| status_code = response.status_code if response is not None else "unknown" | |
| response_body = "" | |
| if response is not None: | |
| response_body = response.text[:500].replace("\n", "\\n") | |
| logger.debug( | |
| f"Unable to load Hugging Face metadata for {repo}: HTTP {status_code}" | |
| + (f" - {response_body}" if response_body else "") | |
| ) | |
| except requests.RequestException as e: |
| if hf_repo_has_config(repo): | ||
| yield f"Repository {repo} contains config.json; TOKENIZER not required.\n" | ||
| else: | ||
| tokenizer_repo = resolve_tokenizer_repo(repo) |
There was a problem hiding this comment.
In /pull, you already instantiate fs = HfFileSystem() for the file download metadata, but hf_repo_has_config(repo) / resolve_tokenizer_repo(repo) create their own HfFileSystem instances. Pass the existing fs into these helpers to avoid redundant client setup and extra remote calls (and to ensure consistent auth/caching behavior).
| if hf_repo_has_config(repo): | |
| yield f"Repository {repo} contains config.json; TOKENIZER not required.\n" | |
| else: | |
| tokenizer_repo = resolve_tokenizer_repo(repo) | |
| try: | |
| repo_has_config = hf_repo_has_config(repo, fs=fs) | |
| except TypeError: | |
| repo_has_config = hf_repo_has_config(repo) | |
| if repo_has_config: | |
| yield f"Repository {repo} contains config.json; TOKENIZER not required.\n" | |
| else: | |
| try: | |
| tokenizer_repo = resolve_tokenizer_repo(repo, fs=fs) | |
| except TypeError: | |
| tokenizer_repo = resolve_tokenizer_repo(repo) |
| @@ -93,11 +93,23 @@ def get_tokenizer(model_name): | |||
| logger.debug("Local Tokenizer doesn't exists!") | |||
|
|
|||
| # Get model specific tokenizer from Huggin Face specified in Modelfile | |||
There was a problem hiding this comment.
Spelling in the comment: “Huggin Face” should be “Hugging Face”.
| # Get model specific tokenizer from Huggin Face specified in Modelfile | |
| # Get model specific tokenizer from Hugging Face specified in Modelfile |
PR Summary
What changed
rkllama_client pullnow performs tokenizer repo detection during model download./pull:config.jsonTOKENIZERconfig.jsonTOKENIZER="<resolved_repo>"into ModelfileWhy this change was needed
randomblock1/...-rk3588) do not include a valid Hugging Face tokenizer config.AutoTokenizer.from_pretrained(...).TOKENIZERto the Modelfile.Benefits
TOKENIZERentries for repos that already includeconfig.json.TOKENIZERfallback behavior for models that truly need it.Files changed
Behavior now
rkllama_client pull owner/repo/file.rkllm:owner/repo/config.jsonexists → Modelfile uses onlyFROM+HUGGINGFACE_PATHconfig.jsonTOKENIZER="<resolved_repo>"TOKENIZERand the failure is explicit in logsNotes
TOKENIZERfallback.Co-authored-by: Copilot copilot@github.com