truss 0.10.9rc535__py3-none-any.whl → 0.10.10rc0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of truss might be problematic. Click here for more details.
- truss/cli/logs/base_watcher.py +1 -1
- truss/cli/train/deploy_checkpoints/deploy_checkpoints.py +30 -22
- truss/cli/train/deploy_checkpoints/deploy_checkpoints_helpers.py +8 -2
- truss/cli/train/deploy_checkpoints/deploy_full_checkpoints.py +14 -7
- truss/cli/train/deploy_checkpoints/deploy_whisper_checkpoints.py +63 -0
- truss/cli/train/deploy_from_checkpoint_config_whisper.yml +17 -0
- truss/cli/train/metrics_watcher.py +170 -59
- truss/cli/train_commands.py +11 -3
- truss/contexts/image_builder/serving_image_builder.py +22 -39
- truss/remote/baseten/api.py +11 -0
- truss/remote/baseten/core.py +209 -1
- truss/remote/baseten/utils/time.py +15 -0
- truss/templates/base.Dockerfile.jinja +6 -23
- truss/templates/cache.Dockerfile.jinja +5 -5
- truss/templates/copy_cache_files.Dockerfile.jinja +1 -1
- truss/templates/docker_server/supervisord.conf.jinja +0 -1
- truss/templates/server/requirements.txt +1 -1
- truss/templates/server.Dockerfile.jinja +16 -33
- truss/tests/cli/train/test_deploy_checkpoints.py +446 -2
- truss/tests/cli/train/test_train_cli_core.py +96 -0
- truss/tests/remote/baseten/conftest.py +18 -0
- truss/tests/remote/baseten/test_api.py +49 -14
- truss/tests/remote/baseten/test_core.py +517 -1
- {truss-0.10.9rc535.dist-info → truss-0.10.10rc0.dist-info}/METADATA +2 -2
- {truss-0.10.9rc535.dist-info → truss-0.10.10rc0.dist-info}/RECORD +31 -29
- truss_train/definitions.py +6 -0
- truss_train/deployment.py +15 -2
- truss_train/loader.py +7 -20
- truss/tests/util/test_basetenpointer.py +0 -227
- truss/util/basetenpointer.py +0 -160
- {truss-0.10.9rc535.dist-info → truss-0.10.10rc0.dist-info}/WHEEL +0 -0
- {truss-0.10.9rc535.dist-info → truss-0.10.10rc0.dist-info}/entry_points.txt +0 -0
- {truss-0.10.9rc535.dist-info → truss-0.10.10rc0.dist-info}/licenses/LICENSE +0 -0
truss/remote/baseten/api.py
CHANGED
|
@@ -669,6 +669,17 @@ class BasetenApi:
|
|
|
669
669
|
# NB(nikhil): reverse order so latest logs are at the end
|
|
670
670
|
return resp_json["logs"][::-1]
|
|
671
671
|
|
|
672
|
+
def _fetch_log_batch(
|
|
673
|
+
self, project_id: str, job_id: str, query_params: Dict[str, Any]
|
|
674
|
+
) -> List[Any]:
|
|
675
|
+
"""
|
|
676
|
+
Fetch a single batch of logs from the API.
|
|
677
|
+
"""
|
|
678
|
+
resp_json = self._rest_api_client.post(
|
|
679
|
+
f"v1/training_projects/{project_id}/jobs/{job_id}/logs", body=query_params
|
|
680
|
+
)
|
|
681
|
+
return resp_json["logs"]
|
|
682
|
+
|
|
672
683
|
def get_training_job_checkpoint_presigned_url(
|
|
673
684
|
self, project_id: str, job_id: str, page_size: int = 100
|
|
674
685
|
) -> List[Dict[str, str]]:
|
truss/remote/baseten/core.py
CHANGED
|
@@ -3,7 +3,9 @@ import json
|
|
|
3
3
|
import logging
|
|
4
4
|
import pathlib
|
|
5
5
|
import textwrap
|
|
6
|
-
from typing import IO, TYPE_CHECKING, List, NamedTuple, Optional, Tuple, Type
|
|
6
|
+
from typing import IO, TYPE_CHECKING, Any, Dict, List, NamedTuple, Optional, Tuple, Type
|
|
7
|
+
|
|
8
|
+
import requests
|
|
7
9
|
|
|
8
10
|
from truss.base.errors import ValidationError
|
|
9
11
|
|
|
@@ -15,6 +17,7 @@ from truss.remote.baseten import custom_types as b10_types
|
|
|
15
17
|
from truss.remote.baseten.api import BasetenApi
|
|
16
18
|
from truss.remote.baseten.error import ApiError
|
|
17
19
|
from truss.remote.baseten.utils.tar import create_tar_with_progress_bar
|
|
20
|
+
from truss.remote.baseten.utils.time import iso_to_millis
|
|
18
21
|
from truss.remote.baseten.utils.transfer import multipart_upload_boto3
|
|
19
22
|
from truss.util.path import load_trussignore_patterns_from_truss_dir
|
|
20
23
|
|
|
@@ -27,6 +30,16 @@ NO_ENVIRONMENTS_EXIST_ERROR_MESSAGING = (
|
|
|
27
30
|
"Model hasn't been deployed yet. No environments exist."
|
|
28
31
|
)
|
|
29
32
|
|
|
33
|
+
# Maximum number of iterations to prevent infinite loops when paginating logs
|
|
34
|
+
MAX_ITERATIONS = 10_000
|
|
35
|
+
MIN_BATCH_SIZE = 100
|
|
36
|
+
|
|
37
|
+
# LIMIT for the number of logs to fetch per request defined by the server
|
|
38
|
+
MAX_BATCH_SIZE = 1000
|
|
39
|
+
|
|
40
|
+
NANOSECONDS_PER_MILLISECOND = 1_000_000
|
|
41
|
+
MILLISECONDS_PER_HOUR = 60 * 60 * 1000
|
|
42
|
+
|
|
30
43
|
|
|
31
44
|
class ModelIdentifier:
|
|
32
45
|
value: str
|
|
@@ -465,3 +478,198 @@ def validate_truss_config_against_backend(api: BasetenApi, config: str):
|
|
|
465
478
|
raise ValidationError(
|
|
466
479
|
f"Validation failed with the following errors:\n{error_messages}"
|
|
467
480
|
)
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _build_log_query_params(
|
|
484
|
+
start_time: Optional[int], end_time: Optional[int], batch_size: int
|
|
485
|
+
) -> Dict[str, Any]:
|
|
486
|
+
"""
|
|
487
|
+
Build query parameters for log fetching request.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
start_time: Start time in milliseconds since epoch
|
|
491
|
+
end_time: End time in milliseconds since epoch
|
|
492
|
+
batch_size: Number of logs to fetch per request
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
Dictionary of query parameters with None values removed
|
|
496
|
+
"""
|
|
497
|
+
query_body = {
|
|
498
|
+
"start_epoch_millis": start_time,
|
|
499
|
+
"end_epoch_millis": end_time,
|
|
500
|
+
"limit": batch_size,
|
|
501
|
+
"direction": "asc",
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return {k: v for k, v in query_body.items() if v is not None}
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _handle_server_error_backoff(
|
|
508
|
+
error: requests.HTTPError, job_id: str, iteration: int, batch_size: int
|
|
509
|
+
) -> int:
|
|
510
|
+
"""
|
|
511
|
+
Slash the batch size in half and return the new batch size
|
|
512
|
+
"""
|
|
513
|
+
old_batch_size = batch_size
|
|
514
|
+
new_batch_size = max(batch_size // 2, MIN_BATCH_SIZE)
|
|
515
|
+
|
|
516
|
+
logging.warning(
|
|
517
|
+
f"Server error (HTTP {error.response.status_code}) for job {job_id} at iteration {iteration}. "
|
|
518
|
+
f"Reducing batch size from {old_batch_size} to {new_batch_size}. Retrying..."
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
return new_batch_size
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def _process_batch_logs(
|
|
525
|
+
batch_logs: List[Any], job_id: str, iteration: int, batch_size: int
|
|
526
|
+
) -> Tuple[bool, Optional[int], Optional[int]]:
|
|
527
|
+
"""
|
|
528
|
+
Process a batch of logs and determine if pagination should continue.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
batch_logs: List of logs from the current batch
|
|
532
|
+
job_id: The job ID for logging
|
|
533
|
+
iteration: Current iteration number for logging
|
|
534
|
+
batch_size: Expected batch size
|
|
535
|
+
|
|
536
|
+
Returns:
|
|
537
|
+
Tuple of (should_continue, next_start_time, next_end_time)
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
# If no logs returned, we're done
|
|
541
|
+
if not batch_logs:
|
|
542
|
+
logging.info(f"No logs returned for job {job_id} at iteration {iteration}")
|
|
543
|
+
return False, None, None
|
|
544
|
+
|
|
545
|
+
# If we got fewer logs than the batch size, we've reached the end
|
|
546
|
+
if len(batch_logs) == 0:
|
|
547
|
+
logging.info(f"Reached end of logs for job {job_id} at iteration {iteration}")
|
|
548
|
+
return False, None, None
|
|
549
|
+
|
|
550
|
+
# Timestamp returned in nanoseconds for the last log in this batch converted
|
|
551
|
+
# to milliseconds to use as start for next iteration
|
|
552
|
+
last_log_timestamp = int(batch_logs[-1]["timestamp"]) // NANOSECONDS_PER_MILLISECOND
|
|
553
|
+
|
|
554
|
+
# Update start time for next iteration (add 1ms to avoid overlap)
|
|
555
|
+
next_start_time_ms = last_log_timestamp + 1
|
|
556
|
+
|
|
557
|
+
# Set end time to 2 hours from next start time, maximum time delta allowed by the API
|
|
558
|
+
next_end_time_ms = next_start_time_ms + 2 * MILLISECONDS_PER_HOUR
|
|
559
|
+
|
|
560
|
+
return True, next_start_time_ms, next_end_time_ms
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
class BatchedTrainingLogsFetcher:
|
|
564
|
+
"""
|
|
565
|
+
Iterator for fetching training job logs in batches using time-based pagination.
|
|
566
|
+
|
|
567
|
+
This iterator handles the complexity of paginating through training job logs,
|
|
568
|
+
including error handling, batch size adjustment, and time window management.
|
|
569
|
+
"""
|
|
570
|
+
|
|
571
|
+
def __init__(
|
|
572
|
+
self,
|
|
573
|
+
api: BasetenApi,
|
|
574
|
+
project_id: str,
|
|
575
|
+
job_id: str,
|
|
576
|
+
batch_size: int = MAX_BATCH_SIZE,
|
|
577
|
+
):
|
|
578
|
+
self.api = api
|
|
579
|
+
self.project_id = project_id
|
|
580
|
+
self.job_id = job_id
|
|
581
|
+
self.batch_size = batch_size
|
|
582
|
+
self.iteration = 0
|
|
583
|
+
self.current_start_time = None
|
|
584
|
+
self.current_end_time = None
|
|
585
|
+
self._initialize_time_window()
|
|
586
|
+
|
|
587
|
+
def _initialize_time_window(self):
|
|
588
|
+
training_job = self.api.get_training_job(self.project_id, self.job_id)
|
|
589
|
+
self.current_start_time = iso_to_millis(
|
|
590
|
+
training_job["training_job"]["created_at"]
|
|
591
|
+
)
|
|
592
|
+
self.current_end_time = self.current_start_time + 2 * MILLISECONDS_PER_HOUR
|
|
593
|
+
|
|
594
|
+
def __iter__(self):
|
|
595
|
+
return self
|
|
596
|
+
|
|
597
|
+
def __next__(self) -> List[Any]:
|
|
598
|
+
if self.iteration >= MAX_ITERATIONS:
|
|
599
|
+
logging.warning(
|
|
600
|
+
f"Reached maximum iteration limit ({MAX_ITERATIONS}) while paginating "
|
|
601
|
+
f"training job logs for project_id={self.project_id}, job_id={self.job_id}."
|
|
602
|
+
)
|
|
603
|
+
raise StopIteration
|
|
604
|
+
|
|
605
|
+
query_params = _build_log_query_params(
|
|
606
|
+
self.current_start_time, self.current_end_time, self.batch_size
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
try:
|
|
610
|
+
batch_logs = self.api._fetch_log_batch(
|
|
611
|
+
self.project_id, self.job_id, query_params
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
should_continue, next_start_time, next_end_time = _process_batch_logs(
|
|
615
|
+
batch_logs, self.job_id, self.iteration, self.batch_size
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
if not should_continue:
|
|
619
|
+
logging.info(
|
|
620
|
+
f"Completed pagination for job {self.job_id}. Total iterations: {self.iteration + 1}"
|
|
621
|
+
)
|
|
622
|
+
raise StopIteration
|
|
623
|
+
|
|
624
|
+
self.current_start_time = next_start_time # type: ignore[assignment]
|
|
625
|
+
self.current_end_time = next_end_time # type: ignore[assignment]
|
|
626
|
+
self.iteration += 1
|
|
627
|
+
|
|
628
|
+
return batch_logs
|
|
629
|
+
|
|
630
|
+
except requests.HTTPError as e:
|
|
631
|
+
if 500 <= e.response.status_code < 600:
|
|
632
|
+
if self.batch_size == MIN_BATCH_SIZE:
|
|
633
|
+
logging.error(
|
|
634
|
+
"Failed to fetch all training job logs due to persistent server errors. "
|
|
635
|
+
"Please try again later or contact support if the issue persists."
|
|
636
|
+
)
|
|
637
|
+
raise StopIteration
|
|
638
|
+
self.batch_size = _handle_server_error_backoff(
|
|
639
|
+
e, self.job_id, self.iteration, self.batch_size
|
|
640
|
+
)
|
|
641
|
+
# Retry the same iteration with reduced batch size
|
|
642
|
+
return self.__next__()
|
|
643
|
+
else:
|
|
644
|
+
logging.error(
|
|
645
|
+
f"HTTP error fetching logs for job {self.job_id} at iteration {self.iteration}: {e}"
|
|
646
|
+
)
|
|
647
|
+
raise StopIteration
|
|
648
|
+
except Exception as e:
|
|
649
|
+
logging.error(
|
|
650
|
+
f"Error fetching logs for job {self.job_id} at iteration {self.iteration}: {e}"
|
|
651
|
+
)
|
|
652
|
+
raise StopIteration
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def get_training_job_logs_with_pagination(
|
|
656
|
+
api: BasetenApi, project_id: str, job_id: str, batch_size: int = MAX_BATCH_SIZE
|
|
657
|
+
) -> List[Any]:
|
|
658
|
+
"""
|
|
659
|
+
This method implements forward time-based pagination by starting from the earliest
|
|
660
|
+
available log and working forward in time. It uses the timestamp of the newest log in
|
|
661
|
+
each batch as the start time for the next request.
|
|
662
|
+
|
|
663
|
+
Returns:
|
|
664
|
+
List of all logs in chronological order (oldest first)
|
|
665
|
+
"""
|
|
666
|
+
all_logs = []
|
|
667
|
+
|
|
668
|
+
logs_iterator = BatchedTrainingLogsFetcher(api, project_id, job_id, batch_size)
|
|
669
|
+
|
|
670
|
+
for batch_logs in logs_iterator:
|
|
671
|
+
all_logs.extend(batch_logs)
|
|
672
|
+
|
|
673
|
+
logging.info(f"Completed pagination for job {job_id}. Total logs: {len(all_logs)}")
|
|
674
|
+
|
|
675
|
+
return all_logs
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from dateutil import parser
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def iso_to_millis(ts: str) -> int:
|
|
5
|
+
"""
|
|
6
|
+
Convert ISO 8601 timestamp string to milliseconds since epoch.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
ts: ISO 8601 timestamp string (handles Zulu/UTC (Z) automatically)
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
Milliseconds since epoch as integer
|
|
13
|
+
"""
|
|
14
|
+
dt = parser.isoparse(ts) # handles Zulu/UTC (Z) automatically
|
|
15
|
+
return int(dt.timestamp() * 1000)
|
|
@@ -8,25 +8,6 @@ FROM {{ base_image_name_and_tag }} AS truss_server
|
|
|
8
8
|
{%- set python_executable = config.base_image.python_executable_path or 'python3' %}
|
|
9
9
|
ENV PYTHON_EXECUTABLE="{{ python_executable }}"
|
|
10
10
|
|
|
11
|
-
# The non-root user's home directory.
|
|
12
|
-
# uv will use $HOME to install packages.
|
|
13
|
-
ENV HOME=/home/{{ app_username }}
|
|
14
|
-
# Directory containing inference server code.
|
|
15
|
-
ENV APP_HOME=/{{ app_username }}
|
|
16
|
-
RUN mkdir -p ${APP_HOME} {{ control_server_dir }}
|
|
17
|
-
# Create a non-root user to run model containers.
|
|
18
|
-
RUN useradd -u {{ app_user_uid }} -ms /bin/bash {{ app_username }}
|
|
19
|
-
# Add the user's local bin to the path, used by uv.
|
|
20
|
-
ENV PATH=${PATH}:${HOME}/.local/bin
|
|
21
|
-
|
|
22
|
-
{%- if non_root_user %}
|
|
23
|
-
RUN apt update -y && apt install -y sudo
|
|
24
|
-
RUN echo "{{ app_username }} ALL=(root) NOPASSWD: /usr/bin/apt install *, /usr/bin/apt update *" > /etc/sudoers.d/app-packages
|
|
25
|
-
RUN chmod 0440 /etc/sudoers.d/app-packages
|
|
26
|
-
{#- optional but good practice: check if the sudoers file is valid #}
|
|
27
|
-
RUN visudo -c
|
|
28
|
-
{%- endif %}
|
|
29
|
-
|
|
30
11
|
{%- set UV_VERSION = "0.7.19" %}
|
|
31
12
|
{#
|
|
32
13
|
NB(nikhil): We use a semi-complex uv installation command across the board:
|
|
@@ -58,6 +39,7 @@ RUN if ! command -v uv >/dev/null 2>&1; then \
|
|
|
58
39
|
command -v curl >/dev/null 2>&1 || (apt update && apt install -y curl) && \
|
|
59
40
|
curl -LsSf --retry 5 --retry-delay 5 https://astral.sh/uv/{{ UV_VERSION }}/install.sh | sh; \
|
|
60
41
|
fi
|
|
42
|
+
ENV PATH="/root/.local/bin:$PATH"
|
|
61
43
|
{% endblock %}
|
|
62
44
|
|
|
63
45
|
{% block base_image_patch %}
|
|
@@ -75,7 +57,7 @@ RUN {{ sys_pip_install_command }} install mkl
|
|
|
75
57
|
|
|
76
58
|
{% block install_system_requirements %}
|
|
77
59
|
{%- if should_install_system_requirements %}
|
|
78
|
-
COPY
|
|
60
|
+
COPY ./{{ system_packages_filename }} {{ system_packages_filename }}
|
|
79
61
|
RUN apt-get update && apt-get install --yes --no-install-recommends $(cat {{ system_packages_filename }}) \
|
|
80
62
|
&& apt-get autoremove -y \
|
|
81
63
|
&& apt-get clean -y \
|
|
@@ -86,11 +68,11 @@ RUN apt-get update && apt-get install --yes --no-install-recommends $(cat {{ sys
|
|
|
86
68
|
|
|
87
69
|
{% block install_requirements %}
|
|
88
70
|
{%- if should_install_user_requirements_file %}
|
|
89
|
-
COPY
|
|
71
|
+
COPY ./{{ user_supplied_requirements_filename }} {{ user_supplied_requirements_filename }}
|
|
90
72
|
RUN {{ sys_pip_install_command }} -r {{ user_supplied_requirements_filename }} --no-cache-dir
|
|
91
73
|
{%- endif %}
|
|
92
74
|
{%- if should_install_requirements %}
|
|
93
|
-
COPY
|
|
75
|
+
COPY ./{{ config_requirements_filename }} {{ config_requirements_filename }}
|
|
94
76
|
RUN {{ sys_pip_install_command }} -r {{ config_requirements_filename }} --no-cache-dir
|
|
95
77
|
{%- endif %}
|
|
96
78
|
{% endblock %}
|
|
@@ -98,6 +80,7 @@ RUN {{ sys_pip_install_command }} -r {{ config_requirements_filename }} --no-cac
|
|
|
98
80
|
|
|
99
81
|
|
|
100
82
|
{%- if not config.docker_server %}
|
|
83
|
+
ENV APP_HOME="/app"
|
|
101
84
|
WORKDIR $APP_HOME
|
|
102
85
|
{%- endif %}
|
|
103
86
|
|
|
@@ -107,7 +90,7 @@ WORKDIR $APP_HOME
|
|
|
107
90
|
|
|
108
91
|
{% block bundled_packages_copy %}
|
|
109
92
|
{%- if bundled_packages_dir_exists %}
|
|
110
|
-
COPY
|
|
93
|
+
COPY ./{{ config.bundled_packages_dir }} /packages
|
|
111
94
|
{%- endif %}
|
|
112
95
|
{% endblock %}
|
|
113
96
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
FROM python:3.11-slim AS cache_warmer
|
|
2
2
|
|
|
3
|
-
RUN mkdir -p
|
|
4
|
-
WORKDIR
|
|
3
|
+
RUN mkdir -p /app/model_cache
|
|
4
|
+
WORKDIR /app
|
|
5
5
|
|
|
6
6
|
{% if hf_access_token %}
|
|
7
7
|
ENV HUGGING_FACE_HUB_TOKEN="{{hf_access_token}}"
|
|
@@ -9,12 +9,12 @@ ENV HUGGING_FACE_HUB_TOKEN="{{hf_access_token}}"
|
|
|
9
9
|
|
|
10
10
|
RUN apt-get -y update; apt-get -y install curl; curl -s https://baseten-public.s3.us-west-2.amazonaws.com/bin/b10cp-5fe8dc7da-linux-amd64 -o /app/b10cp; chmod +x /app/b10cp
|
|
11
11
|
ENV B10CP_PATH_TRUSS="/app/b10cp"
|
|
12
|
-
COPY
|
|
12
|
+
COPY ./cache_requirements.txt /app/cache_requirements.txt
|
|
13
13
|
RUN pip install -r /app/cache_requirements.txt --no-cache-dir && rm -rf /root/.cache/pip
|
|
14
|
-
COPY
|
|
14
|
+
COPY ./cache_warmer.py /cache_warmer.py
|
|
15
15
|
|
|
16
16
|
{% for credential in credentials_to_cache %}
|
|
17
|
-
COPY ./{{credential}}
|
|
17
|
+
COPY ./{{credential}} /app/{{credential}}
|
|
18
18
|
{% endfor %}
|
|
19
19
|
|
|
20
20
|
{% for repo, hf_dir in models.items() %}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
[supervisord]
|
|
2
|
-
pidfile=/tmp/supervisord.pid ; Set PID file location to /tmp to be writable by the non-root user
|
|
3
2
|
nodaemon=true ; Run supervisord in the foreground (useful for containers)
|
|
4
3
|
logfile=/dev/null ; Disable logging to file (send logs to /dev/null)
|
|
5
4
|
logfile_maxbytes=0 ; No size limit on logfile (since logging is disabled)
|
|
@@ -15,12 +15,12 @@ ENV DEBIAN_FRONTEND="noninteractive"
|
|
|
15
15
|
|
|
16
16
|
{# Install common dependencies #}
|
|
17
17
|
RUN apt update && \
|
|
18
|
-
apt install -y bash build-essential git curl ca-certificates
|
|
18
|
+
apt install -y bash build-essential git curl ca-certificates \
|
|
19
19
|
&& apt-get autoremove -y \
|
|
20
20
|
&& apt-get clean -y \
|
|
21
21
|
&& rm -rf /var/lib/apt/lists/*
|
|
22
22
|
|
|
23
|
-
COPY
|
|
23
|
+
COPY ./{{ base_server_requirements_filename }} {{ base_server_requirements_filename }}
|
|
24
24
|
RUN {{ sys_pip_install_command }} -r {{ base_server_requirements_filename }} --no-cache-dir
|
|
25
25
|
{%- endif %} {#- endif not config.docker_server #}
|
|
26
26
|
|
|
@@ -38,7 +38,7 @@ RUN ln -sf {{ config.base_image.python_executable_path }} /usr/local/bin/python
|
|
|
38
38
|
|
|
39
39
|
{% block install_requirements %}
|
|
40
40
|
{%- if should_install_server_requirements %}
|
|
41
|
-
COPY
|
|
41
|
+
COPY ./{{ server_requirements_filename }} {{ server_requirements_filename }}
|
|
42
42
|
RUN {{ sys_pip_install_command }} -r {{ server_requirements_filename }} --no-cache-dir
|
|
43
43
|
{%- endif %} {#- endif should_install_server_requirements #}
|
|
44
44
|
{{ super() }}
|
|
@@ -65,47 +65,47 @@ RUN {% for secret,path in config.build.secret_to_path_mapping.items() %} --mount
|
|
|
65
65
|
|
|
66
66
|
{# Copy data before code for better caching #}
|
|
67
67
|
{%- if data_dir_exists %}
|
|
68
|
-
COPY
|
|
68
|
+
COPY ./{{ config.data_dir }} /app/data
|
|
69
69
|
{%- endif %} {#- endif data_dir_exists #}
|
|
70
70
|
|
|
71
71
|
{%- if model_cache_v2 %}
|
|
72
72
|
# v0.0.9, keep synced with server_requirements.txt
|
|
73
|
-
RUN curl -sSL --fail --retry 5 --retry-delay 2 -o /usr/local/bin/truss-transfer-cli https://github.com/basetenlabs/truss/releases/download/v0.10.
|
|
73
|
+
RUN curl -sSL --fail --retry 5 --retry-delay 2 -o /usr/local/bin/truss-transfer-cli https://github.com/basetenlabs/truss/releases/download/v0.10.9rc30/truss-transfer-cli-v0.10.9rc30-linux-x86_64-unknown-linux-musl
|
|
74
74
|
RUN chmod +x /usr/local/bin/truss-transfer-cli
|
|
75
75
|
RUN mkdir /static-bptr
|
|
76
76
|
RUN echo "hash {{model_cache_hash}}"
|
|
77
|
-
COPY
|
|
77
|
+
COPY ./bptr-manifest /static-bptr/static-bptr-manifest.json
|
|
78
78
|
{%- endif %} {#- endif model_cache_v2 #}
|
|
79
79
|
|
|
80
80
|
{%- if not config.docker_server %}
|
|
81
|
-
COPY
|
|
81
|
+
COPY ./server /app
|
|
82
82
|
{%- endif %} {#- endif not config.docker_server #}
|
|
83
83
|
|
|
84
84
|
{%- if use_local_src %}
|
|
85
85
|
{# This path takes precedence over site-packages. #}
|
|
86
|
-
COPY
|
|
87
|
-
COPY
|
|
86
|
+
COPY ./truss_chains /app/truss_chains
|
|
87
|
+
COPY ./truss /app/truss
|
|
88
88
|
{%- endif %} {#- endif use_local_src #}
|
|
89
89
|
|
|
90
|
-
COPY
|
|
90
|
+
COPY ./config.yaml /app/config.yaml
|
|
91
91
|
{%- if requires_live_reload %}
|
|
92
92
|
RUN uv python install {{ control_python_version }}
|
|
93
93
|
RUN uv venv /control/.env --python {{ control_python_version }}
|
|
94
94
|
|
|
95
|
-
COPY
|
|
95
|
+
COPY ./control /control
|
|
96
96
|
RUN uv pip install -r /control/requirements.txt --python /control/.env/bin/python --no-cache-dir
|
|
97
97
|
{%- endif %} {#- endif requires_live_reload #}
|
|
98
98
|
|
|
99
99
|
{%- if model_dir_exists %}
|
|
100
|
-
COPY
|
|
100
|
+
COPY ./{{ config.model_module_dir }} /app/model
|
|
101
101
|
{%- endif %} {#- endif model_dir_exists #}
|
|
102
102
|
{% endblock %} {#- endblock app_copy #}
|
|
103
103
|
|
|
104
104
|
{% block run %}
|
|
105
105
|
{%- if config.docker_server %}
|
|
106
|
-
RUN apt-get update
|
|
106
|
+
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
107
107
|
curl nginx && rm -rf /var/lib/apt/lists/*
|
|
108
|
-
COPY
|
|
108
|
+
COPY ./docker_server_requirements.txt /app/docker_server_requirements.txt
|
|
109
109
|
|
|
110
110
|
{# NB(nikhil): Use the same python version for custom server proxy as the control server, for consistency. #}
|
|
111
111
|
RUN uv python install {{ control_python_version }}
|
|
@@ -115,38 +115,21 @@ RUN uv pip install --python /docker_server/.venv/bin/python -r /app/docker_serve
|
|
|
115
115
|
{% set supervisor_config_path = "/etc/supervisor/supervisord.conf" %}
|
|
116
116
|
{% set supervisor_log_dir = "/var/log/supervisor" %}
|
|
117
117
|
{% set supervisor_server_url = "http://localhost:8080" %}
|
|
118
|
-
COPY
|
|
118
|
+
COPY ./proxy.conf {{ proxy_config_path }}
|
|
119
119
|
RUN mkdir -p {{ supervisor_log_dir }}
|
|
120
|
-
COPY
|
|
120
|
+
COPY supervisord.conf {{ supervisor_config_path }}
|
|
121
121
|
ENV SUPERVISOR_SERVER_URL="{{ supervisor_server_url }}"
|
|
122
122
|
ENV SERVER_START_CMD="/docker_server/.venv/bin/supervisord -c {{ supervisor_config_path }}"
|
|
123
|
-
{#- default configuration uses port 80, which requires root privileges, so we remove it #}
|
|
124
|
-
RUN rm -f /etc/nginx/sites-enabled/default
|
|
125
|
-
{%- if non_root_user %}
|
|
126
|
-
{#- nginx writes to /var/lib/nginx, /var/log/nginx, and /run directories #}
|
|
127
|
-
{% set nginx_dirs = ["/var/lib/nginx", "/var/log/nginx", "/run"] %}
|
|
128
|
-
RUN chown -R {{ app_username }}:{{ app_username }} {{ nginx_dirs | join(" ") }}
|
|
129
|
-
RUN chown -R {{ app_username }}:{{ app_username }} ${HOME} ${APP_HOME}
|
|
130
|
-
USER {{ app_username }}
|
|
131
|
-
{%- endif %} {#- endif non_root_user #}
|
|
132
123
|
ENTRYPOINT ["/docker_server/.venv/bin/supervisord", "-c", "{{ supervisor_config_path }}"]
|
|
133
124
|
{%- elif requires_live_reload %} {#- elif requires_live_reload #}
|
|
134
125
|
ENV HASH_TRUSS="{{ truss_hash }}"
|
|
135
126
|
ENV CONTROL_SERVER_PORT="8080"
|
|
136
127
|
ENV INFERENCE_SERVER_PORT="8090"
|
|
137
128
|
ENV SERVER_START_CMD="/control/.env/bin/python /control/control/server.py"
|
|
138
|
-
{%- if non_root_user %}
|
|
139
|
-
RUN chown -R {{ app_username }}:{{ app_username }} ${HOME} ${APP_HOME}
|
|
140
|
-
USER {{ app_username }}
|
|
141
|
-
{%- endif %} {#- endif non_root_user #}
|
|
142
129
|
ENTRYPOINT ["/control/.env/bin/python", "/control/control/server.py"]
|
|
143
130
|
{%- else %} {#- else (default inference server) #}
|
|
144
131
|
ENV INFERENCE_SERVER_PORT="8080"
|
|
145
132
|
ENV SERVER_START_CMD="{{ python_executable }} /app/main.py"
|
|
146
|
-
{%- if non_root_user %}
|
|
147
|
-
RUN chown -R {{ app_username }}:{{ app_username }} ${HOME} ${APP_HOME}
|
|
148
|
-
USER {{ app_username }}
|
|
149
|
-
{%- endif %} {#- endif non_root_user #}
|
|
150
133
|
ENTRYPOINT ["{{ python_executable }}", "/app/main.py"]
|
|
151
134
|
{%- endif %} {#- endif config.docker_server / live_reload #}
|
|
152
135
|
{% endblock %} {#- endblock run #}
|