From 0268e21302caadeb40bd4fa2bb68085e423ad6b8 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 15:07:24 -0400 Subject: [PATCH 01/18] feat: reclaim 'cog run' for predictions, deprecate 'cog predict' - Create new 'cog run' command that runs predictions (replaces 'cog predict') - 'cog predict' is hidden and prints deprecation warning - 'cog run [args...]' (2+ args) forwards to 'cog exec' with deprecation warning - Remove 'run' alias from exec command --- pkg/cli/build.go | 2 +- pkg/cli/exec.go | 9 ++----- pkg/cli/init.go | 2 +- pkg/cli/predict.go | 5 ++++ pkg/cli/root.go | 1 + pkg/cli/run.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 pkg/cli/run.go diff --git a/pkg/cli/build.go b/pkg/cli/build.go index 490ab585f3..43218b445b 100644 --- a/pkg/cli/build.go +++ b/pkg/cli/build.go @@ -37,7 +37,7 @@ func newBuildCommand() *cobra.Command { Long: `Build a Docker image from the cog.yaml in the current directory. The generated image contains your model code, dependencies, and the Cog -runtime. It can be run locally with 'cog predict' or pushed to a registry +runtime. It can be run locally with 'cog run' or pushed to a registry with 'cog push'.`, Example: ` # Build with default settings cog build diff --git a/pkg/cli/exec.go b/pkg/cli/exec.go index abe540a13c..d2bf218249 100644 --- a/pkg/cli/exec.go +++ b/pkg/cli/exec.go @@ -26,9 +26,8 @@ func addGpusFlag(cmd *cobra.Command) { func newExecCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "exec [arg...]", - Aliases: []string{"run"}, - Short: "Execute a command inside a Docker environment", + Use: "exec [arg...]", + Short: "Execute a command inside a Docker environment", Long: `Execute a command inside a Docker environment defined by cog.yaml. Cog builds a temporary image from your cog.yaml configuration and runs the @@ -69,10 +68,6 @@ exploring the environment your model will run in.`, } func execCmd(cmd *cobra.Command, args []string) error { - if cmd.CalledAs() == "run" { - console.Warn(`"cog run " is deprecated, use "cog exec "`) - } - ctx := cmd.Context() dockerClient, err := docker.NewClient(ctx) diff --git a/pkg/cli/init.go b/pkg/cli/init.go index da90028235..77e052a376 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -23,7 +23,7 @@ func newInitCommand() *cobra.Command { Use: "init", SuggestFor: []string{"new", "start"}, Short: "Configure your project for use with Cog", - Long: `Create a cog.yaml and predict.py in the current directory. + Long: `Create a cog.yaml and run.py in the current directory. These files provide a starting template for defining your model's environment and prediction interface. Edit them to match your model's requirements.`, diff --git a/pkg/cli/predict.go b/pkg/cli/predict.go index fd399c9e16..633fb62c80 100644 --- a/pkg/cli/predict.go +++ b/pkg/cli/predict.go @@ -74,6 +74,7 @@ the prediction on that.`, RunE: cmdPredict, Args: cobra.MaximumNArgs(1), SuggestFor: []string{"infer"}, + Hidden: true, } addUseCudaBaseImageFlag(cmd) @@ -177,6 +178,10 @@ func transformPathsToBase64URLs(inputs map[string]any) (map[string]any, error) { } func cmdPredict(cmd *cobra.Command, args []string) error { + if cmd.CalledAs() == "predict" { + console.Warn(`"cog predict" is deprecated, use "cog run"`) + } + ctx, stop := signal.NotifyContext(cmd.Context(), syscall.SIGINT, syscall.SIGTERM) defer stop() diff --git a/pkg/cli/root.go b/pkg/cli/root.go index 9184411ec4..e4f813033c 100644 --- a/pkg/cli/root.go +++ b/pkg/cli/root.go @@ -49,6 +49,7 @@ https://github.com/replicate/cog`, newInspectCommand(), newLoginCommand(), newPredictCommand(), + newRunCommand(), newPushCommand(), newExecCommand(), newServeCommand(), diff --git a/pkg/cli/run.go b/pkg/cli/run.go new file mode 100644 index 0000000000..a86b6e9133 --- /dev/null +++ b/pkg/cli/run.go @@ -0,0 +1,66 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/replicate/cog/pkg/util/console" +) + +func newRunCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "run [image]", + Short: "Run a prediction", + Long: `Run a prediction. + +If 'image' is passed, it will run the prediction on that Docker image. +It must be an image that has been built by Cog. + +Otherwise, it will build the model in the current directory and run +the prediction on that.`, + Example: ` # Run a prediction with named inputs + cog run -i prompt="a photo of a cat" + + # Pass a file as input + cog run -i image=@photo.jpg + + # Save output to a file + cog run -i image=@input.jpg -o output.png + + # Pass multiple inputs + cog run -i prompt="sunset" -i width=1024 -i height=768 + + # Run against a pre-built image + cog run r8.im/your-username/my-model -i prompt="hello" + + # Pass inputs as JSON + echo '{"prompt": "a cat"}' | cog run --json @-`, + RunE: cmdRun, + Args: cobra.ArbitraryArgs, + SuggestFor: []string{"infer"}, + } + + addUseCudaBaseImageFlag(cmd) + addUseCogBaseImageFlag(cmd) + addBuildProgressOutputFlag(cmd) + addDockerfileFlag(cmd) + addGpusFlag(cmd) + addSetupTimeoutFlag(cmd) + addConfigFlag(cmd) + + cmd.Flags().StringArrayVarP(&inputFlags, "input", "i", []string{}, "Inputs, in the form name=value. if value is prefixed with @, then it is read from a file on disk. E.g. -i path=@image.jpg") + cmd.Flags().StringVarP(&outPath, "output", "o", "", "Output path") + cmd.Flags().StringArrayVarP(&envFlags, "env", "e", []string{}, "Environment variables, in the form name=value") + cmd.Flags().BoolVar(&useReplicateAPIToken, "use-replicate-token", false, "Pass REPLICATE_API_TOKEN from local environment into the model context") + cmd.Flags().StringVar(&inputJSON, "json", "", "Pass inputs as JSON object, read from file (@inputs.json) or via stdin (@-)") + + return cmd +} + +func cmdRun(cmd *cobra.Command, args []string) error { + if len(args) >= 2 { + // Old "cog run [args...]" usage -- forward to exec + console.Warn(`"cog run " is deprecated, use "cog exec "`) + return execCmd(cmd, args) + } + return cmdPredict(cmd, args) +} From 7e3395735090b63bf247486492f76af87471cbe1 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 15:16:36 -0400 Subject: [PATCH 02/18] feat: support 'run:' key in cog.yaml alongside 'predict:' - Add Run field to configFile struct - Resolve run: vs predict: at parse time into Config.Predict - Validate that both cannot be set simultaneously - Update JSON schema with run property - Produce field-appropriate error messages (run.py:Runner vs predict.py:Predictor) --- pkg/config/config_file.go | 1 + pkg/config/config_test.go | 21 +++++++++++++++ pkg/config/data/config_schema_v1.0.json | 5 ++++ pkg/config/parse.go | 8 +++++- pkg/config/validate.go | 34 +++++++++++++++++++++---- pkg/config/validate_test.go | 33 ++++++++++++++++++++++++ 6 files changed, 96 insertions(+), 6 deletions(-) diff --git a/pkg/config/config_file.go b/pkg/config/config_file.go index a3ee069680..90b5921533 100644 --- a/pkg/config/config_file.go +++ b/pkg/config/config_file.go @@ -15,6 +15,7 @@ type configFile struct { Build *buildFile `json:"build,omitempty" yaml:"build,omitempty"` Image *string `json:"image,omitempty" yaml:"image,omitempty"` Predict *string `json:"predict,omitempty" yaml:"predict,omitempty"` + Run *string `json:"run,omitempty" yaml:"run,omitempty"` Train *string `json:"train,omitempty" yaml:"train,omitempty"` Concurrency *concurrencyFile `json:"concurrency,omitempty" yaml:"concurrency,omitempty"` Environment []string `json:"environment,omitempty" yaml:"environment,omitempty"` diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index ba99b89d0a..5d3663f7da 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -816,3 +816,24 @@ predict: predict.py:Predictor require.NoError(t, err) require.Equal(t, "prerelease", conf.Build.SDKVersion) } + +func TestRunKeyResolvesToPredict(t *testing.T) { + config, err := FromYAML([]byte(` +run: "run.py:Runner" +build: + python_version: "3.12" +`)) + require.NoError(t, err) + require.Equal(t, "run.py:Runner", config.Predict) +} + +func TestRunAndPredictConflict(t *testing.T) { + _, err := FromYAML([]byte(` +run: "run.py:Runner" +predict: "predict.py:Predictor" +build: + python_version: "3.12" +`)) + require.Error(t, err) + require.Contains(t, err.Error(), "cannot set both 'run' and 'predict'") +} diff --git a/pkg/config/data/config_schema_v1.0.json b/pkg/config/data/config_schema_v1.0.json index 91ca934607..bebc0ed3b9 100644 --- a/pkg/config/data/config_schema_v1.0.json +++ b/pkg/config/data/config_schema_v1.0.json @@ -165,6 +165,11 @@ "type": "string", "description": "The pointer to the `Predictor` object in your code, which defines how predictions are run on your model." }, + "run": { + "$id": "#/properties/run", + "type": "string", + "description": "The pointer to the `Runner` object in your code, which defines how predictions are run on your model." + }, "train": { "$id": "#/properties/train", "type": "string", diff --git a/pkg/config/parse.go b/pkg/config/parse.go index 1826b56f15..d5b2aeceea 100644 --- a/pkg/config/parse.go +++ b/pkg/config/parse.go @@ -148,7 +148,13 @@ func configFileToConfig(cfg *configFile) (*Config, error) { if cfg.Image != nil { config.Image = *cfg.Image } - if cfg.Predict != nil { + // Resolve run: vs predict: -- run: takes precedence, conflict is caught by validation + switch { + case cfg.Run != nil && cfg.Predict != nil: + return nil, fmt.Errorf("cannot set both 'run' and 'predict' in cog.yaml") + case cfg.Run != nil: + config.Predict = *cfg.Run + case cfg.Predict != nil: config.Predict = *cfg.Predict } if cfg.Train != nil { diff --git a/pkg/config/validate.go b/pkg/config/validate.go index e5ab37ebcc..ecc9c568ea 100644 --- a/pkg/config/validate.go +++ b/pkg/config/validate.go @@ -67,6 +67,7 @@ func ValidateConfigFile(cfg *configFile, opts ...ValidateOption) *ValidationResu } // Semantic validation + validateRunPredictConflict(cfg, result) validatePredict(cfg, result) validateTrain(cfg, result) validateBuild(cfg, options, result) @@ -106,18 +107,41 @@ func validateSchema(cfg *configFile) error { return nil } -// validatePredict validates the predict field. +// validateRunPredictConflict checks that run: and predict: are not both set. +func validateRunPredictConflict(cfg *configFile, result *ValidationResult) { + if cfg.Run != nil && cfg.Predict != nil { + result.AddError(&ValidationError{ + Field: "run", + Message: "cannot set both 'run' and 'predict' in cog.yaml; use one or the other", + }) + } +} + +// validatePredict validates the predict/run field. func validatePredict(cfg *configFile, result *ValidationResult) { - if cfg.Predict == nil || *cfg.Predict == "" { + // Check whichever field is set (run: or predict:) + var predict string + var fieldName string + switch { + case cfg.Run != nil && *cfg.Run != "": + predict = *cfg.Run + fieldName = "run" + case cfg.Predict != nil && *cfg.Predict != "": + predict = *cfg.Predict + fieldName = "predict" + default: return } - predict := *cfg.Predict if len(strings.Split(predict, ".py:")) != 2 { + example := "predict.py:Predictor" + if fieldName == "run" { + example = "run.py:Runner" + } result.AddError(&ValidationError{ - Field: "predict", + Field: fieldName, Value: predict, - Message: "must be in the form 'predict.py:Predictor'", + Message: fmt.Sprintf("must be in the form '%s'", example), }) } } diff --git a/pkg/config/validate_test.go b/pkg/config/validate_test.go index 5f88a4fdd5..b53b550d8c 100644 --- a/pkg/config/validate_test.go +++ b/pkg/config/validate_test.go @@ -176,5 +176,38 @@ func TestValidateConfigFileNilBuildSkipsPythonVersionCheck(t *testing.T) { require.False(t, result.HasErrors(), "expected no errors for nil build, got: %v", result.Errors) } +func TestValidateConfigFileRunFormat(t *testing.T) { + // Valid run format + cfg := &configFile{ + Build: &buildFile{ + PythonVersion: ptr("3.12"), + }, + Run: ptr("run.py:Runner"), + } + + result := ValidateConfigFile(cfg) + require.False(t, result.HasErrors(), "expected no errors, got: %v", result.Errors) + + // Invalid run format + cfg.Run = ptr("invalid_format") + result = ValidateConfigFile(cfg) + require.True(t, result.HasErrors()) + require.Contains(t, result.Err().Error(), "run.py:Runner") +} + +func TestValidateConfigFileRunAndPredictConflict(t *testing.T) { + cfg := &configFile{ + Build: &buildFile{ + PythonVersion: ptr("3.12"), + }, + Run: ptr("run.py:Runner"), + Predict: ptr("predict.py:Predictor"), + } + + result := ValidateConfigFile(cfg) + require.True(t, result.HasErrors()) + require.Contains(t, result.Err().Error(), "cannot set both 'run' and 'predict'") +} + // ptr returns a pointer to the given value. func ptr[T any](v T) *T { return &v } From f855a39359c23e3e748201b665cbfc1e6fe2b7e6 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 15:21:16 -0400 Subject: [PATCH 03/18] feat: add BaseRunner with run() method to Python SDK - Add BaseRunner class alongside BasePredictor - Inspector detects run() vs predict() via class __dict__ - load_predictor_from_ref tries Runner before Predictor as default - Export BaseRunner from cog package --- python/cog/__init__.py | 3 +- python/cog/_inspector.py | 16 ++++++-- python/cog/errors.py | 2 +- python/cog/predictor.py | 85 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 99 insertions(+), 7 deletions(-) diff --git a/python/cog/__init__.py b/python/cog/__init__.py index 5a5d778a06..80614bb1d0 100644 --- a/python/cog/__init__.py +++ b/python/cog/__init__.py @@ -26,7 +26,7 @@ def predict( from ._version import __version__ from .input import FieldInfo, Input from .model import BaseModel -from .predictor import BasePredictor +from .predictor import BasePredictor, BaseRunner from .types import ( AsyncConcatenateIterator, ConcatenateIterator, @@ -115,6 +115,7 @@ def current_scope() -> object: "__version__", # Core classes "BasePredictor", + "BaseRunner", "BaseModel", # Input "Input", diff --git a/python/cog/_inspector.py b/python/cog/_inspector.py index e5b93acf6b..8e84f45c58 100644 --- a/python/cog/_inspector.py +++ b/python/cog/_inspector.py @@ -432,13 +432,23 @@ def create_predictor(module_name: str, predictor_name: str) -> adt.PredictorInfo p = getattr(module, predictor_name) if inspect.isclass(p): - if not hasattr(p, "predict"): - raise ValueError(f"predict method not found: {fullname}") + # Detect which method the user's class defines (not inherited stubs) + has_run = "run" in p.__dict__ + has_predict = "predict" in p.__dict__ + + if has_run and has_predict: + raise ValueError(f"define either run() or predict(), not both: {fullname}") + + if has_run: + predict_fn_name = "run" + elif has_predict: + predict_fn_name = "predict" + else: + raise ValueError(f"run() or predict() method not found: {fullname}") if hasattr(p, "setup"): _validate_setup(_unwrap(p.setup)) - predict_fn_name = "predict" predict_fn = _unwrap(getattr(p, predict_fn_name)) is_class_fn = True diff --git a/python/cog/errors.py b/python/cog/errors.py index fb1bf72869..7ab0e52a65 100644 --- a/python/cog/errors.py +++ b/python/cog/errors.py @@ -7,4 +7,4 @@ class ConfigDoesNotExist(CogError): class PredictorNotSet(CogError): - """Exception raised when 'predict' is not set in cog.yaml when it needs to be.""" + """Exception raised when 'run' (or 'predict') is not set in cog.yaml when it needs to be.""" diff --git a/python/cog/predictor.py b/python/cog/predictor.py index 12fa6fde8d..15af64500e 100644 --- a/python/cog/predictor.py +++ b/python/cog/predictor.py @@ -115,9 +115,79 @@ def predict(self, prompt: str) -> str: self.scope.record_metric(key, value, mode=mode) +class BaseRunner: + """ + Base class for Cog runners. + + Subclass this to define your model's prediction interface. Override + the `setup` method to load your model, and the `run` method to + run predictions. + + Example: + from cog import BaseRunner, Input, Path + + class Runner(BaseRunner): + def setup(self): + self.model = load_model() + + def run(self, prompt: str = Input(description="Input text")) -> str: + self.record_metric("temperature", 0.7) + return self.model.generate(prompt) + """ + + def setup( + self, + weights: Optional[Union[Path, str]] = None, + ) -> None: + """ + Prepare the model for predictions. + + This method is called once when the runner is initialized. Use it + to load model weights and do any other one-time setup. + + Args: + weights: Optional path to model weights. Can be a local path or URL. + """ + pass + + def run(self, **kwargs: Any) -> Any: + """ + Run a single prediction. + + Override this method to implement your model's prediction logic. + Input parameters should be annotated with types and optionally + use Input() for additional metadata. + + Args: + **kwargs: Prediction inputs as defined by the method signature. + + Returns: + The prediction output. + + Raises: + NotImplementedError: If run is not implemented by subclass. + """ + raise NotImplementedError("run has not been implemented by subclass.") + + @property + def scope(self) -> Any: + """The current prediction scope.""" + import coglet + + return coglet._sdk.current_scope() # type: ignore[attr-defined] + + def record_metric(self, key: str, value: Any, mode: str = "replace") -> None: + """Record a prediction metric. See BasePredictor.record_metric for details.""" + self.scope.record_metric(key, value, mode=mode) + + def load_predictor_from_ref(ref: str) -> BasePredictor: - """Load a predictor from a module:class reference (e.g. 'predict.py:Predictor').""" - module_path, class_name = ref.split(":", 1) if ":" in ref else (ref, "Predictor") + """Load a predictor from a module:class reference (e.g. 'run.py:Runner').""" + if ":" in ref: + module_path, class_name = ref.split(":", 1) + else: + module_path = ref + class_name = None # Will try Runner, then Predictor module_name = os.path.basename(module_path).replace(".py", "") # Use spec_from_file_location to load from file path @@ -129,6 +199,17 @@ def load_predictor_from_ref(ref: str) -> BasePredictor: sys.modules[module_name] = module spec.loader.exec_module(module) + if class_name is None: + # Try Runner first, fall back to Predictor + if hasattr(module, "Runner"): + class_name = "Runner" + elif hasattr(module, "Predictor"): + class_name = "Predictor" + else: + raise ImportError( + f"Cannot find 'Runner' or 'Predictor' class in {module_path}" + ) + predictor = getattr(module, class_name) # It could be a class or a function (for training) if inspect.isclass(predictor): From 11c70986337499a4d1a8962a55d8bcb325d70038 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 15:25:02 -0400 Subject: [PATCH 04/18] feat: detect run() vs predict() in Rust coglet - Add predict_method_name field to PythonPredictor - Detect method via class __dict__ at load time - Error if both run() and predict() are defined, or neither - Use stored method name for all getattr/call_method calls - Dynamic log messages and error messages use detected method name --- crates/coglet-python/src/predictor.rs | 63 +++++++++++++++++++-------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/crates/coglet-python/src/predictor.rs b/crates/coglet-python/src/predictor.rs index 6f91ef6769..463f75a47a 100644 --- a/crates/coglet-python/src/predictor.rs +++ b/crates/coglet-python/src/predictor.rs @@ -299,6 +299,8 @@ pub struct PythonPredictor { instance: PyObject, /// The predictor's kind (class or standalone function) and method execution types kind: PredictorKind, + /// The name of the predict method ("run" or "predict") + predict_method_name: String, } // PyObject is Send in PyO3 0.23+ @@ -322,7 +324,7 @@ impl PythonPredictor { .call_method1("isfunction", (instance.bind(py),))? .extract()?; - let kind = if is_function { + let (kind, predict_method_name) = if is_function { // Standalone function - detect its async nature let (is_async, is_async_gen) = Self::detect_async(py, &instance, "")?; let predict_kind = if is_async_gen { @@ -335,18 +337,40 @@ impl PythonPredictor { tracing::info!("Detected sync train()"); PredictKind::Sync }; - PredictorKind::StandaloneFunction(predict_kind) + (PredictorKind::StandaloneFunction(predict_kind), String::new()) } else { - // Class instance - detect predict() and train() methods - let (is_async, is_async_gen) = Self::detect_async(py, &instance, "predict")?; + // Class instance - detect run() vs predict() method + // Check class __dict__ to distinguish user-defined from inherited base stubs + let cls = instance.bind(py).getattr("__class__")?; + let class_dict = cls.getattr("__dict__")?; + let has_run = class_dict.contains("run")?; + let has_predict = class_dict.contains("predict")?; + + let predict_method_name = match (has_run, has_predict) { + (true, true) => { + return Err(PyErr::new::( + "define either run() or predict(), not both", + )); + } + (true, false) => "run".to_string(), + (false, true) => "predict".to_string(), + (false, false) => { + return Err(PyErr::new::( + "run() or predict() method not found", + )); + } + }; + + let (is_async, is_async_gen) = + Self::detect_async(py, &instance, &predict_method_name)?; let predict_kind = if is_async_gen { - tracing::info!("Detected async generator predict()"); + tracing::info!("Detected async generator {}()", predict_method_name); PredictKind::AsyncGen } else if is_async { - tracing::info!("Detected async predict()"); + tracing::info!("Detected async {}()", predict_method_name); PredictKind::Async } else { - tracing::info!("Detected sync predict()"); + tracing::info!("Detected sync {}()", predict_method_name); PredictKind::Sync }; @@ -364,13 +388,13 @@ impl PythonPredictor { TrainKind::None }; - PredictorKind::Class { + (PredictorKind::Class { predict: predict_kind, train: train_kind, - } + }, predict_method_name) }; - let predictor = Self { instance, kind }; + let predictor = Self { instance, kind, predict_method_name }; // Patch FieldInfo defaults on predict/train methods so Python uses actual // default values instead of FieldInfo wrapper objects for missing inputs. @@ -379,7 +403,7 @@ impl PythonPredictor { if is_function { Self::unwrap_field_info_defaults(py, &predictor.instance, "")?; } else { - Self::unwrap_field_info_defaults(py, &predictor.instance, "predict")?; + Self::unwrap_field_info_defaults(py, &predictor.instance, &predictor.predict_method_name)?; if matches!(predictor.kind, PredictorKind::Class { train, .. } if train != TrainKind::None) { Self::unwrap_field_info_defaults(py, &predictor.instance, "train")?; @@ -561,7 +585,7 @@ impl PythonPredictor { pub fn predict_func<'py>(&self, py: Python<'py>) -> PyResult> { let instance = self.instance.bind(py); match &self.kind { - PredictorKind::Class { .. } => instance.getattr("predict"), + PredictorKind::Class { .. } => instance.getattr(self.predict_method_name.as_str()), PredictorKind::StandaloneFunction(_) => Ok(instance.clone()), } } @@ -581,7 +605,7 @@ impl PythonPredictor { pub fn predict_raw(&self, py: Python<'_>, input: &Bound<'_, PyDict>) -> PyResult { let (method_name, is_async) = match &self.kind { PredictorKind::Class { predict, .. } => ( - "predict", + self.predict_method_name.as_str(), matches!(predict, PredictKind::Async | PredictKind::AsyncGen), ), PredictorKind::StandaloneFunction(predict_kind) => ( @@ -667,7 +691,7 @@ impl PythonPredictor { // PreparedInput cleans up temp files on drop (RAII) let func = self.predict_func(py).map_err(|e| { - PredictionError::Failed(format!("Failed to get predict function: {}", e)) + PredictionError::Failed(format!("Failed to get {} function: {}", self.predict_method_name, e)) })?; let prepared = input::prepare_input(py, raw_input_dict, &func) .map_err(|e| PredictionError::InvalidInput(format_validation_error(py, &e)))?; @@ -943,7 +967,7 @@ impl PythonPredictor { })?; let func = self.predict_func(py).map_err(|e| { - PredictionError::Failed(format!("Failed to get predict function: {}", e)) + PredictionError::Failed(format!("Failed to get {} function: {}", self.predict_method_name, e)) })?; let prepared = input::prepare_input(py, raw_input_dict, &func) .map_err(|e| PredictionError::InvalidInput(format_validation_error(py, &e)))?; @@ -952,8 +976,13 @@ impl PythonPredictor { // Call predict - returns coroutine let instance = self.instance.bind(py); let coro = instance - .call_method("predict", (), Some(&input_dict)) - .map_err(|e| PredictionError::Failed(format!("Failed to call predict: {}", e)))?; + .call_method(self.predict_method_name.as_str(), (), Some(&input_dict)) + .map_err(|e| { + PredictionError::Failed(format!( + "Failed to call {}: {}", + self.predict_method_name, e + )) + })?; // For async generators, wrap to collect all values let is_async_gen = matches!( From e2170844df94b7c46cfe923cdccea52cfe434769 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 15:27:42 -0400 Subject: [PATCH 05/18] feat: schema parser tries run() before predict() Tree-sitter parser now looks for run() method first, falls back to predict() for backwards compatibility. --- .../base/{predict.py => run.py} | 0 pkg/schema/python/parser.go | 28 ++++++--- pkg/schema/python/parser_test.go | 63 +++++++++++++++++++ 3 files changed, 84 insertions(+), 7 deletions(-) rename pkg/cli/init-templates/base/{predict.py => run.py} (100%) diff --git a/pkg/cli/init-templates/base/predict.py b/pkg/cli/init-templates/base/run.py similarity index 100% rename from pkg/cli/init-templates/base/predict.py rename to pkg/cli/init-templates/base/run.py diff --git a/pkg/schema/python/parser.go b/pkg/schema/python/parser.go index 253de24d48..f2d45fc0b7 100644 --- a/pkg/schema/python/parser.go +++ b/pkg/schema/python/parser.go @@ -52,15 +52,29 @@ func ParsePredictor(source []byte, predictRef string, mode schema.Mode, sourceDi // 4. Collect Input() references from class attributes and static methods inputRegistry := collectInputRegistry(root, source, imports, moduleScope) - // 5. Find the target predict/train function - methodName := "predict" + // 5. Find the target predict/train/run function + var methodName string + var funcNode *sitter.Node if mode == schema.ModeTrain { methodName = "train" - } - - funcNode, err := findTargetFunction(root, source, predictRef, methodName) - if err != nil { - return nil, err + var err error + funcNode, err = findTargetFunction(root, source, predictRef, methodName) + if err != nil { + return nil, err + } + } else { + // Try "run" first, fall back to "predict" + var err error + funcNode, err = findTargetFunction(root, source, predictRef, "run") + if err != nil { + funcNode, err = findTargetFunction(root, source, predictRef, "predict") + if err != nil { + return nil, err + } + methodName = "predict" + } else { + methodName = "run" + } } // 6. Check if method (has self first param) diff --git a/pkg/schema/python/parser_test.go b/pkg/schema/python/parser_test.go index c2981764d0..03c6574f35 100644 --- a/pkg/schema/python/parser_test.go +++ b/pkg/schema/python/parser_test.go @@ -2598,3 +2598,66 @@ func indexOf(s, substr string) int { } return -1 } + +// --------------------------------------------------------------------------- +// BaseRunner with run() method +// --------------------------------------------------------------------------- + +func TestRunMethodOnBaseRunner(t *testing.T) { + source := ` +from cog import BaseRunner, Input + +class Runner(BaseRunner): + def run(self, prompt: str = Input(description="Input text")) -> str: + return "hello " + prompt +` + info := parse(t, source, "Runner") + require.Equal(t, 1, info.Inputs.Len()) + + prompt, ok := info.Inputs.Get("prompt") + require.True(t, ok) + require.Equal(t, schema.TypeString, prompt.FieldType.Primitive) + require.NotNil(t, prompt.Description) + require.Equal(t, "Input text", *prompt.Description) + + require.Equal(t, schema.SchemaPrimitive, info.Output.Kind) + require.Equal(t, schema.TypeString, info.Output.Primitive) +} + +func TestRunMethodFallsBackToPredict(t *testing.T) { + // Ensure existing predict() classes still work + source := ` +from cog import BasePredictor + +class Predictor(BasePredictor): + def predict(self, s: str) -> str: + return "hello " + s +` + info := parse(t, source, "Predictor") + require.Equal(t, 1, info.Inputs.Len()) + + s, ok := info.Inputs.Get("s") + require.True(t, ok) + require.Equal(t, schema.TypeString, s.FieldType.Primitive) +} + +func TestRunMethodPreferredOverPredict(t *testing.T) { + // If a class has both run() and predict(), run() should be preferred + source := ` +from cog import BaseRunner, Input + +class Runner(BaseRunner): + def predict(self, x: int) -> int: + return x + + def run(self, prompt: str = Input(description="Input text")) -> str: + return "hello " + prompt +` + info := parse(t, source, "Runner") + require.Equal(t, 1, info.Inputs.Len()) + + // Should find run(), not predict() + prompt, ok := info.Inputs.Get("prompt") + require.True(t, ok) + require.Equal(t, schema.TypeString, prompt.FieldType.Primitive) +} From de3141c80ede6da371810c4c9759673b2ef15c5f Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 15:28:08 -0400 Subject: [PATCH 06/18] feat: cog init generates run.py with BaseRunner - Rename predict.py template to run.py - Template uses BaseRunner/Runner/def run() - cog.yaml template uses run: key --- pkg/cli/init-templates/base/cog.yaml | 4 ++-- pkg/cli/init-templates/base/run.py | 6 +++--- pkg/cli/init_test.go | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/cli/init-templates/base/cog.yaml b/pkg/cli/init-templates/base/cog.yaml index 0451b77e43..e32335584e 100644 --- a/pkg/cli/init-templates/base/cog.yaml +++ b/pkg/cli/init-templates/base/cog.yaml @@ -21,5 +21,5 @@ build: # - "echo env is ready!" # - "echo another command if needed" -# predict.py defines how predictions are run on your model -predict: "predict.py:Predictor" +# run.py defines how predictions are run on your model +run: "run.py:Runner" diff --git a/pkg/cli/init-templates/base/run.py b/pkg/cli/init-templates/base/run.py index 89dfdac167..dc258a41f8 100644 --- a/pkg/cli/init-templates/base/run.py +++ b/pkg/cli/init-templates/base/run.py @@ -1,15 +1,15 @@ # Prediction interface for Cog ⚙️ # https://cog.run/python -from cog import BasePredictor, Input, Path +from cog import BaseRunner, Input, Path -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self) -> None: """Load the model into memory to make running multiple predictions efficient""" # self.model = torch.load("./weights.pth") - def predict( + def run( self, image: Path = Input(description="Grayscale input image"), scale: float = Input( diff --git a/pkg/cli/init_test.go b/pkg/cli/init_test.go index 3648394226..782836b86c 100644 --- a/pkg/cli/init_test.go +++ b/pkg/cli/init_test.go @@ -18,7 +18,7 @@ func TestInit(t *testing.T) { require.FileExists(t, path.Join(dir, ".dockerignore")) require.FileExists(t, path.Join(dir, "cog.yaml")) - require.FileExists(t, path.Join(dir, "predict.py")) + require.FileExists(t, path.Join(dir, "run.py")) require.FileExists(t, path.Join(dir, "requirements.txt")) } @@ -33,11 +33,11 @@ func TestInitSkipExisting(t *testing.T) { require.FileExists(t, path.Join(dir, ".dockerignore")) require.FileExists(t, path.Join(dir, "cog.yaml")) - require.FileExists(t, path.Join(dir, "predict.py")) + require.FileExists(t, path.Join(dir, "run.py")) // update the file to show that its the same file after the second run require.NoError(t, os.WriteFile(path.Join(dir, "cog.yaml"), []byte("test123"), 0o644)) - require.NoError(t, os.WriteFile(path.Join(dir, "predict.py"), []byte("test456"), 0o644)) + require.NoError(t, os.WriteFile(path.Join(dir, "run.py"), []byte("test456"), 0o644)) require.NoError(t, os.WriteFile(path.Join(dir, ".dockerignore"), []byte("test789"), 0o644)) // Second run should skip the files that already exist @@ -46,14 +46,14 @@ func TestInitSkipExisting(t *testing.T) { require.FileExists(t, path.Join(dir, ".dockerignore")) require.FileExists(t, path.Join(dir, "cog.yaml")) - require.FileExists(t, path.Join(dir, "predict.py")) + require.FileExists(t, path.Join(dir, "run.py")) // check that the files are the same as the first run content, err := os.ReadFile(path.Join(dir, "cog.yaml")) require.NoError(t, err) require.Equal(t, []byte("test123"), content) - content, err = os.ReadFile(path.Join(dir, "predict.py")) + content, err = os.ReadFile(path.Join(dir, "run.py")) require.NoError(t, err) require.Equal(t, []byte("test456"), content) From 3d5c6e0e5d428cdb70f9f5b1bf6039ca2fc528a0 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 15:38:05 -0400 Subject: [PATCH 07/18] test: rename predict->run in integration tests Mechanical find-and-replace across ~143 files: - cog predict -> cog run - predict.py:Predictor -> run.py:Runner - BasePredictor -> BaseRunner - class Predictor -> class Runner - def predict -> def run - predict: -> run: in cog.yaml - Updated comments and variable names Edge cases preserved: - predict_time metric name (unchanged) - /predictions API path (unchanged) - prediction as a noun in comments (unchanged) - OpenAPI schema assertions with server-generated content (unchanged) - Source: comments referencing original test file names (unchanged) --- .../concurrent/concurrent_test.go | 54 +++++++++---------- integration-tests/tests/apt_packages.txtar | 14 ++--- .../tests/async_generator_precollect.txtar | 10 ++-- integration-tests/tests/async_predictor.txtar | 12 ++--- integration-tests/tests/async_sleep.txtar | 14 ++--- .../tests/bad_dockerignore.txtar | 10 ++-- .../tests/bool_input_output.txtar | 16 +++--- .../tests/build_base_image_sha.txtar | 10 ++-- integration-tests/tests/build_cog_init.txtar | 2 +- .../tests/build_cog_version_match.txtar | 10 ++-- .../tests/build_gpu_labels.txtar | 10 ++-- .../tests/build_image_option.txtar | 10 ++-- .../tests/build_openapi_schema.txtar | 10 ++-- .../tests/build_openapi_schema_complex.txtar | 10 ++-- .../tests/build_pip_freeze.txtar | 10 ++-- .../tests/build_python313_base_image.txtar | 12 ++--- .../tests/build_torch_version_required.txtar | 10 ++-- integration-tests/tests/ca_cert.txtar | 10 ++-- .../tests/cancel_async_prediction.txtar | 10 ++-- integration-tests/tests/cancel_repeated.txtar | 10 ++-- .../tests/cancel_sync_prediction.txtar | 10 ++-- .../tests/coglet_iterator_path_output.txtar | 14 ++--- .../tests/coglet_iterator_upload_url.txtar | 10 ++-- .../coglet_large_file_upload_serial.txtar | 10 ++-- .../tests/coglet_large_input.txtar | 12 ++--- .../tests/coglet_large_output.txtar | 10 ++-- .../coglet_list_path_single_element.txtar | 10 ++-- .../tests/coglet_list_path_upload_url.txtar | 10 ++-- integration-tests/tests/coglet_metrics.txtar | 10 ++-- .../tests/coglet_metrics_validation.txtar | 10 ++-- .../tests/coglet_metrics_webhook.txtar | 10 ++-- .../tests/coglet_single_path_output.txtar | 10 ++-- integration-tests/tests/complex_output.txtar | 14 ++--- .../tests/concatenate_iterator_output.txtar | 16 +++--- integration-tests/tests/debug_secrets.txtar | 10 ++-- integration-tests/tests/dict_output.txtar | 16 +++--- .../tests/emit_metric_deprecated.txtar | 12 ++--- integration-tests/tests/env_vars.txtar | 14 ++--- .../tests/experimental_feature_warning.txtar | 14 ++--- integration-tests/tests/ffmpeg_package.txtar | 14 ++--- integration-tests/tests/file_input.txtar | 12 ++--- integration-tests/tests/file_list_input.txtar | 14 ++--- .../tests/float_input_output.txtar | 14 ++--- .../tests/function_predictor.txtar | 10 ++-- .../tests/future_annotations.txtar | 14 ++--- integration-tests/tests/glb_project.txtar | 14 ++--- integration-tests/tests/granite_project.txtar | 14 ++--- integration-tests/tests/healthcheck.txtar | 14 ++--- .../tests/healthcheck_async.txtar | 10 ++-- .../tests/healthcheck_async_exception.txtar | 10 ++-- .../tests/healthcheck_async_timeout.txtar | 10 ++-- .../tests/healthcheck_async_unhealthy.txtar | 10 ++-- .../tests/healthcheck_during_prediction.txtar | 10 ++-- .../tests/healthcheck_exception.txtar | 10 ++-- ...thcheck_immediately_after_prediction.txtar | 10 ++-- .../tests/healthcheck_repeated_calls.txtar | 10 ++-- .../tests/healthcheck_timeout.txtar | 10 ++-- .../tests/healthcheck_unhealthy.txtar | 10 ++-- .../tests/int_input_output.txtar | 14 ++--- integration-tests/tests/int_none_output.txtar | 14 ++--- integration-tests/tests/int_predictor.txtar | 14 ++--- .../tests/invalid_int_validation.txtar | 10 ++-- .../tests/iterator_error_midstream.txtar | 10 ++-- .../tests/iterator_string_output.txtar | 14 ++--- .../tests/legacy_sdk_schema.txtar | 14 ++--- .../tests/list_int_input_output.txtar | 14 ++--- .../tests/list_string_output.txtar | 14 ++--- integration-tests/tests/many_inputs.txtar | 16 +++--- .../tests/multi_file_schema.txtar | 16 +++--- .../tests/nested_output_types.txtar | 10 ++-- integration-tests/tests/no_predictor.txtar | 8 +-- .../tests/non_base_predictor_class.txtar | 12 ++--- .../tests/non_base_predictor_function.txtar | 10 ++-- .../tests/oci_bundle_build.txtar | 12 ++--- .../tests/oci_bundle_inspect.txtar | 10 ++-- integration-tests/tests/oci_bundle_push.txtar | 10 ++-- .../tests/optional_path_input.txtar | 14 ++--- integration-tests/tests/path_input.txtar | 14 ++--- .../tests/path_input_output.txtar | 14 ++--- integration-tests/tests/path_list_input.txtar | 14 ++--- .../tests/path_list_output.txtar | 14 ++--- integration-tests/tests/path_output.txtar | 14 ++--- .../tests/pep604_file_or_none_input.txtar | 18 +++---- .../pep604_list_file_or_none_input.txtar | 18 +++---- .../pep604_list_path_or_none_input.txtar | 18 +++---- .../tests/pep604_path_or_none_input.txtar | 18 +++---- .../tests/pep604_string_url_not_coerced.txtar | 16 +++--- .../tests/predict_existing_image.txtar | 18 +++---- .../tests/predict_json_file.txtar | 12 ++--- .../tests/predict_json_input.txtar | 12 ++--- .../tests/predict_json_metrics.txtar | 14 ++--- .../tests/predict_json_output_file.txtar | 12 ++--- .../tests/predict_json_stdin.txtar | 12 ++--- .../tests/predict_json_stdin_dash.txtar | 12 ++--- .../tests/predict_many_inputs_image.txtar | 14 ++--- .../tests/predict_output_file.txtar | 12 ++--- .../tests/predict_output_string.txtar | 12 ++--- .../tests/predict_sys_exit.txtar | 12 ++--- .../tests/prediction_error_response.txtar | 20 +++---- integration-tests/tests/pty_echo.txtar | 10 ++-- integration-tests/tests/pty_interactive.txtar | 10 ++-- integration-tests/tests/pydantic2.txtar | 14 ++--- .../tests/pydantic2_output.txtar | 14 ++--- integration-tests/tests/python313.txtar | 14 ++--- .../tests/python37_deprecated.txtar | 10 ++-- .../tests/python38_deprecated.txtar | 10 ++-- .../tests/python39_deprecated.txtar | 10 ++-- integration-tests/tests/scope_context.txtar | 14 ++--- integration-tests/tests/secrets.txtar | 10 ++-- .../tests/sequential_state_leak.txtar | 10 ++-- .../tests/setup_slow_serial.txtar | 10 ++-- .../tests/setup_subprocess_double_fork.txtar | 14 ++--- .../setup_subprocess_double_fork_http.txtar | 14 ++--- .../setup_subprocess_multiprocessing.txtar | 12 ++--- .../tests/setup_subprocess_simple.txtar | 16 +++--- .../tests/setup_timeout_serial.txtar | 10 ++-- .../tests/setup_worker_tracing_logs.txtar | 12 ++--- .../tests/static_schema_fallback.txtar | 16 +++--- .../tests/static_schema_gen.txtar | 18 +++---- .../string_input_url_list_not_coerced.txtar | 14 ++--- .../tests/string_input_url_not_coerced.txtar | 18 +++---- .../tests/string_list_input.txtar | 14 ++--- .../tests/string_none_output.txtar | 14 ++--- .../tests/string_predictor.txtar | 14 ++--- .../tests/subdirectory_predictor.txtar | 16 +++--- integration-tests/tests/tensorflow.txtar | 14 ++--- .../tests/torch_270_cuda_126.txtar | 10 ++-- .../tests/torch_271_cuda_128.txtar | 10 ++-- .../tests/torch_baseimage_fallback.txtar | 10 ++-- .../tests/torch_baseimage_no_cog_base.txtar | 10 ++-- .../tests/torch_baseimage_precompile.txtar | 10 ++-- .../tests/torch_cuda_baseimage.txtar | 10 ++-- integration-tests/tests/train_basic.txtar | 10 ++-- .../tests/train_deprecated.txtar | 8 +-- integration-tests/tests/training_setup.txtar | 20 +++---- integration-tests/tests/union_type.txtar | 14 ++--- .../tests/webhook_delivery_failure.txtar | 10 ++-- .../tests/webhook_prediction_error.txtar | 12 ++--- integration-tests/tests/weights_build.txtar | 12 ++--- .../tests/weights_push_inspect.txtar | 10 ++-- .../tests/wheel_coglet_missing.txtar | 10 ++-- .../tests/wheel_resolution.txtar | 10 ++-- integration-tests/tests/zsh_package.txtar | 12 ++--- 143 files changed, 896 insertions(+), 896 deletions(-) diff --git a/integration-tests/concurrent/concurrent_test.go b/integration-tests/concurrent/concurrent_test.go index bb1cc2ece6..757c285f26 100644 --- a/integration-tests/concurrent/concurrent_test.go +++ b/integration-tests/concurrent/concurrent_test.go @@ -44,11 +44,11 @@ func TestConcurrentPredictions(t *testing.T) { require.NoError(t, err, "failed to create temp dir") defer os.RemoveAll(tmpDir) - // Write the async-sleep predictor fixture + // Write the async-sleep runner fixture err = os.WriteFile(filepath.Join(tmpDir, "cog.yaml"), []byte(cogYAML), 0o644) require.NoError(t, err, "failed to write cog.yaml") - err = os.WriteFile(filepath.Join(tmpDir, "predict.py"), []byte(predictPy), 0o644) - require.NoError(t, err, "failed to write predict.py") + err = os.WriteFile(filepath.Join(tmpDir, "run.py"), []byte(runPy), 0o644) + require.NoError(t, err, "failed to write run.py") // Get the cog binary cogBinary, err := harness.ResolveCogBinary() @@ -258,17 +258,17 @@ func allocatePort() (int, error) { const cogYAML = `build: python_version: "3.11" -predict: "predict.py:Predictor" +run: "run.py:Runner" concurrency: max: 5 ` -const predictPy = `import asyncio -from cog import BasePredictor +const runPy = `import asyncio +from cog import BaseRunner -class Predictor(BasePredictor): - async def predict(self, s: str, sleep: float) -> str: +class Runner(BaseRunner): + async def run(self, s: str, sleep: float) -> str: await asyncio.sleep(sleep) return f"wake up {s}" ` @@ -286,8 +286,8 @@ func TestConcurrentAboveLimit(t *testing.T) { err = os.WriteFile(filepath.Join(tmpDir, "cog.yaml"), []byte(aboveLimitCogYAML), 0o644) require.NoError(t, err, "failed to write cog.yaml") - err = os.WriteFile(filepath.Join(tmpDir, "predict.py"), []byte(predictPy), 0o644) - require.NoError(t, err, "failed to write predict.py") + err = os.WriteFile(filepath.Join(tmpDir, "run.py"), []byte(runPy), 0o644) + require.NoError(t, err, "failed to write run.py") cogBinary, err := harness.ResolveCogBinary() require.NoError(t, err, "failed to resolve cog binary") @@ -373,17 +373,17 @@ func TestConcurrentAboveLimit(t *testing.T) { const aboveLimitCogYAML = `build: python_version: "3.11" -predict: "predict.py:Predictor" +run: "run.py:Runner" concurrency: max: 2 ` // TestConcurrentAsyncMetrics tests that metrics recorded via current_scope().record_metric() -// in async predict functions are correctly routed to each prediction's response when +// in async run functions are correctly routed to each prediction's response when // running with concurrency > 1. // // This reproduces https://github.com/replicate/cog/issues/2901: -// The metric scope ContextVar is set on the worker thread but async predict coroutines +// The metric scope ContextVar is set on the worker thread but async run coroutines // run on a shared event loop thread where the ContextVar is not propagated. Under // concurrency > 1, metrics are either silently dropped (noop scope) or attributed to // the wrong prediction (SYNC_SCOPE race). @@ -398,16 +398,16 @@ func TestConcurrentAsyncMetrics(t *testing.T) { metricsCogYAML := `build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" concurrency: max: 5 ` - metricsPredictPy := `import asyncio -from cog import BasePredictor, current_scope + metricsRunPy := `import asyncio +from cog import BaseRunner, current_scope -class Predictor(BasePredictor): - async def predict(self, idx: int = 0, sleep: float = 0.5) -> str: +class Runner(BaseRunner): + async def run(self, idx: int = 0, sleep: float = 0.5) -> str: scope = current_scope() scope.record_metric("prediction_index", idx) scope.record_metric("model_name", "test-model") @@ -418,8 +418,8 @@ class Predictor(BasePredictor): err = os.WriteFile(filepath.Join(tmpDir, "cog.yaml"), []byte(metricsCogYAML), 0o644) require.NoError(t, err, "failed to write cog.yaml") - err = os.WriteFile(filepath.Join(tmpDir, "predict.py"), []byte(metricsPredictPy), 0o644) - require.NoError(t, err, "failed to write predict.py") + err = os.WriteFile(filepath.Join(tmpDir, "run.py"), []byte(metricsRunPy), 0o644) + require.NoError(t, err, "failed to write run.py") cogBinary, err := harness.ResolveCogBinary() require.NoError(t, err, "failed to resolve cog binary") @@ -554,23 +554,23 @@ func TestSIGTERMDuringSetup(t *testing.T) { slowSetupCogYAML := `build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" ` - slowSetupPredictPy := `import time -from cog import BasePredictor + slowSetupRunPy := `import time +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self) -> None: time.sleep(30) - def predict(self, s: str) -> str: + def run(self, s: str) -> str: return "hello " + s ` err = os.WriteFile(filepath.Join(tmpDir, "cog.yaml"), []byte(slowSetupCogYAML), 0o644) require.NoError(t, err, "failed to write cog.yaml") - err = os.WriteFile(filepath.Join(tmpDir, "predict.py"), []byte(slowSetupPredictPy), 0o644) - require.NoError(t, err, "failed to write predict.py") + err = os.WriteFile(filepath.Join(tmpDir, "run.py"), []byte(slowSetupRunPy), 0o644) + require.NoError(t, err, "failed to write run.py") cogBinary, err := harness.ResolveCogBinary() require.NoError(t, err, "failed to resolve cog binary") diff --git a/integration-tests/tests/apt_packages.txtar b/integration-tests/tests/apt_packages.txtar index e7188a6758..821e08a308 100644 --- a/integration-tests/tests/apt_packages.txtar +++ b/integration-tests/tests/apt_packages.txtar @@ -4,8 +4,8 @@ # Build the image (the run command verifies git is installed) cog build -t $TEST_IMAGE -# Verify the predictor works -cog predict $TEST_IMAGE -i s=world +# Verify the runner works +cog run $TEST_IMAGE -i s=world stdout 'hello world' -- cog.yaml -- @@ -16,12 +16,12 @@ build: - "git" run: - command: git --version -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/async_generator_precollect.txtar b/integration-tests/tests/async_generator_precollect.txtar index 11ada64f1a..64fec1b1a5 100644 --- a/integration-tests/tests/async_generator_precollect.txtar +++ b/integration-tests/tests/async_generator_precollect.txtar @@ -16,19 +16,19 @@ stdout '"predict_time":(0\.[5-9][0-9]*|[1-9][0-9]*(\.[0-9]+)?)' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" concurrency: max: 1 --- predict.py -- +-- run.py -- import asyncio from typing import AsyncIterator -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): - async def predict(self) -> AsyncIterator[str]: +class Runner(BaseRunner): + async def run(self) -> AsyncIterator[str]: for i in range(5): await asyncio.sleep(0.1) yield f"chunk-{i}" diff --git a/integration-tests/tests/async_predictor.txtar b/integration-tests/tests/async_predictor.txtar index a21dfc3887..c8eca436bd 100644 --- a/integration-tests/tests/async_predictor.txtar +++ b/integration-tests/tests/async_predictor.txtar @@ -2,17 +2,17 @@ cog build -t $TEST_IMAGE # Async prediction works -cog predict $TEST_IMAGE -i s=world +cog run $TEST_IMAGE -i s=world stdout 'hello world' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - async def predict(self, s: str) -> str: +class Runner(BaseRunner): + async def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/async_sleep.txtar b/integration-tests/tests/async_sleep.txtar index 95fe6dfe04..c13a0b2c6f 100644 --- a/integration-tests/tests/async_sleep.txtar +++ b/integration-tests/tests/async_sleep.txtar @@ -1,24 +1,24 @@ -# Test async predictor with sleep +# Test async runner with sleep # Build the image cog build -t $TEST_IMAGE # Async prediction with sleep works -cog predict $TEST_IMAGE -i s=sleepyhead -i sleep=0.1 +cog run $TEST_IMAGE -i s=sleepyhead -i sleep=0.1 stdout 'wake up sleepyhead' -- cog.yaml -- build: python_version: "3.11" -predict: "predict.py:Predictor" +run: "run.py:Runner" concurrency: max: 5 --- predict.py -- +-- run.py -- import asyncio -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): - async def predict(self, s: str, sleep: float) -> str: +class Runner(BaseRunner): + async def run(self, s: str, sleep: float) -> str: await asyncio.sleep(sleep) return f"wake up {s}" diff --git a/integration-tests/tests/bad_dockerignore.txtar b/integration-tests/tests/bad_dockerignore.txtar index 734d324989..72133652af 100644 --- a/integration-tests/tests/bad_dockerignore.txtar +++ b/integration-tests/tests/bad_dockerignore.txtar @@ -7,14 +7,14 @@ stderr 'The .cog tmp path cannot be ignored by docker in .dockerignore' build: gpu: true python_version: "3.10" -predict: "predict.py:Predictor" +run: "run.py:Runner" -- .dockerignore -- .cog --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/bool_input_output.txtar b/integration-tests/tests/bool_input_output.txtar index bbc0f08940..f9a1a9b93e 100644 --- a/integration-tests/tests/bool_input_output.txtar +++ b/integration-tests/tests/bool_input_output.txtar @@ -1,25 +1,25 @@ -# Test bool as a direct predict input and output type +# Test bool as a direct run input and output type # Build the image cog build -t $TEST_IMAGE # Bool input and output works via JSON (true -> false) -cog predict $TEST_IMAGE --json '{"flag": true}' +cog run $TEST_IMAGE --json '{"flag": true}' stdout '"output": false' # Bool input and output works via JSON (false -> true) -cog predict $TEST_IMAGE --json '{"flag": false}' +cog run $TEST_IMAGE --json '{"flag": false}' stdout '"output": true' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, flag: bool) -> bool: +class Runner(BaseRunner): + def run(self, flag: bool) -> bool: return not flag diff --git a/integration-tests/tests/build_base_image_sha.txtar b/integration-tests/tests/build_base_image_sha.txtar index 21bc59de96..e4144a990a 100644 --- a/integration-tests/tests/build_base_image_sha.txtar +++ b/integration-tests/tests/build_base_image_sha.txtar @@ -13,19 +13,19 @@ stdout 'sha256:' -- cog.yaml -- build: python_version: "3.12" -predict: predict.py:Predictor +run: run.py:Runner --- predict.py -- +-- run.py -- import tempfile -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): self.foo = "foo" - def predict(self, text: str, path: Path) -> Path: + def run(self, text: str, path: Path) -> Path: with open(path) as f: output = self.foo + text + f.read() tmpdir = Path(tempfile.mkdtemp()) diff --git a/integration-tests/tests/build_cog_init.txtar b/integration-tests/tests/build_cog_init.txtar index fb3f4bd406..0852134359 100644 --- a/integration-tests/tests/build_cog_init.txtar +++ b/integration-tests/tests/build_cog_init.txtar @@ -10,4 +10,4 @@ stderr 'Image built as' # Verify the expected files were created exists cog.yaml -exists predict.py +exists run.py diff --git a/integration-tests/tests/build_cog_version_match.txtar b/integration-tests/tests/build_cog_version_match.txtar index 1bc5cd2143..bf3ccc417b 100644 --- a/integration-tests/tests/build_cog_version_match.txtar +++ b/integration-tests/tests/build_cog_version_match.txtar @@ -146,12 +146,12 @@ print(package) -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/build_gpu_labels.txtar b/integration-tests/tests/build_gpu_labels.txtar index aa91fcf05e..5f0b54ff20 100644 --- a/integration-tests/tests/build_gpu_labels.txtar +++ b/integration-tests/tests/build_gpu_labels.txtar @@ -39,11 +39,11 @@ stdout '.+' build: python_version: "3.12" gpu: true -predict: predict.py:Predictor +run: run.py:Runner --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, text: str) -> str: +class Runner(BaseRunner): + def run(self, text: str) -> str: return text diff --git a/integration-tests/tests/build_image_option.txtar b/integration-tests/tests/build_image_option.txtar index 81e87fe4da..9c4070baee 100644 --- a/integration-tests/tests/build_image_option.txtar +++ b/integration-tests/tests/build_image_option.txtar @@ -15,11 +15,11 @@ exec docker rmi cog-test-image-option image: cog-test-image-option build: python_version: "3.12" -predict: predict.py:Predictor +run: run.py:Runner --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, text: str) -> str: +class Runner(BaseRunner): + def run(self, text: str) -> str: return text diff --git a/integration-tests/tests/build_openapi_schema.txtar b/integration-tests/tests/build_openapi_schema.txtar index a80e54cb95..55770fdb18 100644 --- a/integration-tests/tests/build_openapi_schema.txtar +++ b/integration-tests/tests/build_openapi_schema.txtar @@ -16,19 +16,19 @@ stdout '"format":"uri"' -- cog.yaml -- build: python_version: "3.12" -predict: predict.py:Predictor +run: run.py:Runner --- predict.py -- +-- run.py -- import tempfile -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): self.foo = "foo" - def predict(self, text: str, path: Path) -> Path: + def run(self, text: str, path: Path) -> Path: with open(path) as f: output = self.foo + text + f.read() tmpdir = Path(tempfile.mkdtemp()) diff --git a/integration-tests/tests/build_openapi_schema_complex.txtar b/integration-tests/tests/build_openapi_schema_complex.txtar index ed462f515c..9ed52ff345 100644 --- a/integration-tests/tests/build_openapi_schema_complex.txtar +++ b/integration-tests/tests/build_openapi_schema_complex.txtar @@ -38,12 +38,12 @@ stdout '"Output"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- from typing import Optional -from cog import BaseModel, BasePredictor, Input, Path, Secret +from cog import BaseModel, BaseRunner, Input, Path, Secret class Output(BaseModel): @@ -51,8 +51,8 @@ class Output(BaseModel): score: float -class Predictor(BasePredictor): - def predict( +class Runner(BaseRunner): + def run( self, prompt: str = Input(description="The prompt", default="hello"), temperature: float = Input(description="Sampling temp", ge=0, le=2, default=0.7), diff --git a/integration-tests/tests/build_pip_freeze.txtar b/integration-tests/tests/build_pip_freeze.txtar index bd180b422d..03fd5467a3 100644 --- a/integration-tests/tests/build_pip_freeze.txtar +++ b/integration-tests/tests/build_pip_freeze.txtar @@ -13,19 +13,19 @@ stdout 'coglet' -- cog.yaml -- build: python_version: "3.12" -predict: predict.py:Predictor +run: run.py:Runner --- predict.py -- +-- run.py -- import tempfile -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): self.foo = "foo" - def predict(self, text: str, path: Path) -> Path: + def run(self, text: str, path: Path) -> Path: with open(path) as f: output = self.foo + text + f.read() tmpdir = Path(tempfile.mkdtemp()) diff --git a/integration-tests/tests/build_python313_base_image.txtar b/integration-tests/tests/build_python313_base_image.txtar index efaa07ddc3..c5d0e6d6bc 100644 --- a/integration-tests/tests/build_python313_base_image.txtar +++ b/integration-tests/tests/build_python313_base_image.txtar @@ -7,19 +7,19 @@ cog build -t $TEST_IMAGE --use-cog-base-image # Verify build succeeded by running a prediction -cog predict $TEST_IMAGE -i num=7 +cog run $TEST_IMAGE -i num=7 stdout '14' -- cog.yaml -- build: gpu: false python_version: "3.13" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, num: int) -> int: +class Runner(BaseRunner): + def run(self, num: int) -> int: return num * 2 diff --git a/integration-tests/tests/build_torch_version_required.txtar b/integration-tests/tests/build_torch_version_required.txtar index 7c8b171aee..386f3a1fee 100644 --- a/integration-tests/tests/build_torch_version_required.txtar +++ b/integration-tests/tests/build_torch_version_required.txtar @@ -9,14 +9,14 @@ build: gpu: true python_version: "3.12" python_requirements: requirements.txt -predict: predict.py:Predictor +run: run.py:Runner -- requirements.txt -- torch --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, text: str) -> str: +class Runner(BaseRunner): + def run(self, text: str) -> str: return text diff --git a/integration-tests/tests/ca_cert.txtar b/integration-tests/tests/ca_cert.txtar index ef0e005c8c..dc7e704bb1 100644 --- a/integration-tests/tests/ca_cert.txtar +++ b/integration-tests/tests/ca_cert.txtar @@ -44,13 +44,13 @@ stdout '/etc/ssl/certs/ca-certificates.crt' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, text: str) -> str: +class Runner(BaseRunner): + def run(self, text: str) -> str: return text -- certs/ca.crt -- diff --git a/integration-tests/tests/cancel_async_prediction.txtar b/integration-tests/tests/cancel_async_prediction.txtar index 5f84d680e8..a3dd6172cf 100644 --- a/integration-tests/tests/cancel_async_prediction.txtar +++ b/integration-tests/tests/cancel_async_prediction.txtar @@ -29,17 +29,17 @@ stdout '"status":"canceled"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import asyncio -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): pass - async def predict(self, duration: float = 60.0) -> str: + async def run(self, duration: float = 60.0) -> str: await asyncio.sleep(duration) return "completed" diff --git a/integration-tests/tests/cancel_repeated.txtar b/integration-tests/tests/cancel_repeated.txtar index 62a7840d27..e766cb7347 100644 --- a/integration-tests/tests/cancel_repeated.txtar +++ b/integration-tests/tests/cancel_repeated.txtar @@ -32,18 +32,18 @@ stdout '"status":"canceled"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import time -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): pass - def predict(self, duration: float = 60.0) -> str: + def run(self, duration: float = 60.0) -> str: # C-level blocking sleep (nanosleep) — harder to cancel than a busy-loop time.sleep(duration) return "completed" diff --git a/integration-tests/tests/cancel_sync_prediction.txtar b/integration-tests/tests/cancel_sync_prediction.txtar index 5df8b1346c..4d3e98069b 100644 --- a/integration-tests/tests/cancel_sync_prediction.txtar +++ b/integration-tests/tests/cancel_sync_prediction.txtar @@ -29,18 +29,18 @@ stdout '"status":"canceled"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import time -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): pass - def predict(self, duration: float = 60.0) -> str: + def run(self, duration: float = 60.0) -> str: # Busy-loop (hits bytecode boundaries, cancellable via PyThreadState_SetAsyncExc) deadline = time.monotonic() + duration while time.monotonic() < deadline: diff --git a/integration-tests/tests/coglet_iterator_path_output.txtar b/integration-tests/tests/coglet_iterator_path_output.txtar index d49e6db6c4..058ced0d74 100644 --- a/integration-tests/tests/coglet_iterator_path_output.txtar +++ b/integration-tests/tests/coglet_iterator_path_output.txtar @@ -1,9 +1,9 @@ # Test iterator prediction with Path outputs (no upload URL — files written to disk) cog build -t $TEST_IMAGE -cog predict $TEST_IMAGE +cog run $TEST_IMAGE -# cog predict writes file outputs to disk, not as base64 to stdout +# cog run writes file outputs to disk, not as base64 to stdout stderr 'Written output to: output.0.png' stderr 'Written output to: output.1.png' stderr 'Written output to: output.2.png' @@ -14,19 +14,19 @@ build: python_version: "3.12" python_packages: - "pillow==10.4.0" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import os import tempfile from typing import Iterator -from cog import BasePredictor, Path +from cog import BaseRunner, Path from PIL import Image -class Predictor(BasePredictor): - def predict(self) -> Iterator[Path]: +class Runner(BaseRunner): + def run(self) -> Iterator[Path]: for color in ["red", "blue", "green"]: d = tempfile.mkdtemp() p = os.path.join(d, f"{color}.png") diff --git a/integration-tests/tests/coglet_iterator_upload_url.txtar b/integration-tests/tests/coglet_iterator_upload_url.txtar index eff521644a..dbf27bc460 100644 --- a/integration-tests/tests/coglet_iterator_upload_url.txtar +++ b/integration-tests/tests/coglet_iterator_upload_url.txtar @@ -22,19 +22,19 @@ build: python_version: "3.12" python_packages: - "pillow==10.4.0" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import os import tempfile from typing import Iterator -from cog import BasePredictor, Path +from cog import BaseRunner, Path from PIL import Image -class Predictor(BasePredictor): - def predict(self) -> Iterator[Path]: +class Runner(BaseRunner): + def run(self) -> Iterator[Path]: for color in ["red", "blue", "green"]: d = tempfile.mkdtemp() p = os.path.join(d, f"{color}.png") diff --git a/integration-tests/tests/coglet_large_file_upload_serial.txtar b/integration-tests/tests/coglet_large_file_upload_serial.txtar index 5e487a763e..52a17cd8c4 100644 --- a/integration-tests/tests/coglet_large_file_upload_serial.txtar +++ b/integration-tests/tests/coglet_large_file_upload_serial.txtar @@ -23,17 +23,17 @@ upload-server-count 1 -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import os import tempfile -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): - def predict(self) -> Path: +class Runner(BaseRunner): + def run(self) -> Path: d = tempfile.mkdtemp() p = os.path.join(d, "large_output.bin") # Write 50 MiB of binary data diff --git a/integration-tests/tests/coglet_large_input.txtar b/integration-tests/tests/coglet_large_input.txtar index bff03c61fe..6975cbe2ea 100644 --- a/integration-tests/tests/coglet_large_input.txtar +++ b/integration-tests/tests/coglet_large_input.txtar @@ -4,7 +4,7 @@ # frame limit and break the bridge. # # Strategy: generate a JSON file with a 7 MiB padding string on the host, -# then POST it via the harness curl @file syntax. The predictor echoes +# then POST it via the harness curl @file syntax. The runner echoes # back len(padding) to prove the full input survived the spill-rehydrate # round-trip. # @@ -33,12 +33,12 @@ stdout '"output_size":7' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, padding: str = "") -> str: +class Runner(BaseRunner): + def run(self, padding: str = "") -> str: return str(len(padding)) diff --git a/integration-tests/tests/coglet_large_output.txtar b/integration-tests/tests/coglet_large_output.txtar index b22828665b..54757c01ed 100644 --- a/integration-tests/tests/coglet_large_output.txtar +++ b/integration-tests/tests/coglet_large_output.txtar @@ -28,13 +28,13 @@ stdout '"output_size":9437184' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self) -> str: +class Runner(BaseRunner): + def run(self) -> str: # 9MiB string — exceeds the 8MiB IPC frame limit return "x" * (9 * 1024 * 1024) diff --git a/integration-tests/tests/coglet_list_path_single_element.txtar b/integration-tests/tests/coglet_list_path_single_element.txtar index 7b8ddde943..4434b3c145 100644 --- a/integration-tests/tests/coglet_list_path_single_element.txtar +++ b/integration-tests/tests/coglet_list_path_single_element.txtar @@ -33,19 +33,19 @@ build: python_version: "3.12" python_packages: - "pillow==10.4.0" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import os import tempfile from typing import List -from cog import BasePredictor, Input, Path +from cog import BaseRunner, Input, Path from PIL import Image -class Predictor(BasePredictor): - def predict(self, count: int = Input(description="Number of images", default=1)) -> List[Path]: +class Runner(BaseRunner): + def run(self, count: int = Input(description="Number of images", default=1)) -> List[Path]: outputs = [] colors = ["red", "blue", "green", "yellow"] for i in range(count): diff --git a/integration-tests/tests/coglet_list_path_upload_url.txtar b/integration-tests/tests/coglet_list_path_upload_url.txtar index 5b62e01830..c467b0e6f5 100644 --- a/integration-tests/tests/coglet_list_path_upload_url.txtar +++ b/integration-tests/tests/coglet_list_path_upload_url.txtar @@ -24,19 +24,19 @@ build: python_version: "3.12" python_packages: - "pillow==10.4.0" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import os import tempfile from typing import List -from cog import BasePredictor, Path +from cog import BaseRunner, Path from PIL import Image -class Predictor(BasePredictor): - def predict(self) -> List[Path]: +class Runner(BaseRunner): + def run(self) -> List[Path]: outputs = [] for color in ["red", "blue", "green"]: d = tempfile.mkdtemp() diff --git a/integration-tests/tests/coglet_metrics.txtar b/integration-tests/tests/coglet_metrics.txtar index 43eb51432c..f6a70b924c 100644 --- a/integration-tests/tests/coglet_metrics.txtar +++ b/integration-tests/tests/coglet_metrics.txtar @@ -26,14 +26,14 @@ stdout '"predict_time":' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, current_scope +-- run.py -- +from cog import BaseRunner, current_scope -class Predictor(BasePredictor): - def predict(self) -> str: +class Runner(BaseRunner): + def run(self) -> str: scope = current_scope() # Replace mode (default) diff --git a/integration-tests/tests/coglet_metrics_validation.txtar b/integration-tests/tests/coglet_metrics_validation.txtar index 6add839a18..0b2f2016c7 100644 --- a/integration-tests/tests/coglet_metrics_validation.txtar +++ b/integration-tests/tests/coglet_metrics_validation.txtar @@ -59,14 +59,14 @@ stdout '"valid_name":1' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, current_scope +-- run.py -- +from cog import BaseRunner, current_scope -class Predictor(BasePredictor): - def predict(self, test: str = "") -> str: +class Runner(BaseRunner): + def run(self, test: str = "") -> str: scope = current_scope() if test == "underscore_prefix": diff --git a/integration-tests/tests/coglet_metrics_webhook.txtar b/integration-tests/tests/coglet_metrics_webhook.txtar index 9cf7309fed..ade41f63e2 100644 --- a/integration-tests/tests/coglet_metrics_webhook.txtar +++ b/integration-tests/tests/coglet_metrics_webhook.txtar @@ -30,14 +30,14 @@ stdout '"predict_time":' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, current_scope +-- run.py -- +from cog import BaseRunner, current_scope -class Predictor(BasePredictor): - def predict(self) -> str: +class Runner(BaseRunner): + def run(self) -> str: scope = current_scope() scope.record_metric("model_version", "v2.1") scope.record_metric("confidence", 0.95) diff --git a/integration-tests/tests/coglet_single_path_output.txtar b/integration-tests/tests/coglet_single_path_output.txtar index 4abbfac35f..c3288de177 100644 --- a/integration-tests/tests/coglet_single_path_output.txtar +++ b/integration-tests/tests/coglet_single_path_output.txtar @@ -26,18 +26,18 @@ build: python_version: "3.12" python_packages: - "pillow==10.4.0" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import os import tempfile -from cog import BasePredictor, Path +from cog import BaseRunner, Path from PIL import Image -class Predictor(BasePredictor): - def predict(self) -> Path: +class Runner(BaseRunner): + def run(self) -> Path: d = tempfile.mkdtemp() p = os.path.join(d, "output.png") Image.new("RGB", (10, 10), "red").save(p) diff --git a/integration-tests/tests/complex_output.txtar b/integration-tests/tests/complex_output.txtar index c530ddaf68..073c620a73 100644 --- a/integration-tests/tests/complex_output.txtar +++ b/integration-tests/tests/complex_output.txtar @@ -3,20 +3,20 @@ # Build the image cog build -t $TEST_IMAGE -# Predict returns structured output -cog predict $TEST_IMAGE -i msg='test error' +# Run returns structured output +cog run $TEST_IMAGE -i msg='test error' stdout '"success": false' stdout '"error": "test error"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- from typing import Optional -from cog import BaseModel, BasePredictor, Path +from cog import BaseModel, BaseRunner, Path class ModelOutput(BaseModel): @@ -25,6 +25,6 @@ class ModelOutput(BaseModel): segmented_image: Optional[Path] -class Predictor(BasePredictor): - def predict(self, msg: str) -> ModelOutput: +class Runner(BaseRunner): + def run(self, msg: str) -> ModelOutput: return ModelOutput(success=False, error=msg, segmented_image=None) diff --git a/integration-tests/tests/concatenate_iterator_output.txtar b/integration-tests/tests/concatenate_iterator_output.txtar index 9213fe2d48..56d4ac54ee 100644 --- a/integration-tests/tests/concatenate_iterator_output.txtar +++ b/integration-tests/tests/concatenate_iterator_output.txtar @@ -1,13 +1,13 @@ -# Test ConcatenateIterator[str] as predict output type +# Test ConcatenateIterator[str] as run output type # # ConcatenateIterator is the primary streaming text output type for LLMs. -# cog predict renders each yielded token as an array element. +# cog run renders each yielded token as an array element. # Build the image cog build -t $TEST_IMAGE # Streaming output yields individual tokens -cog predict $TEST_IMAGE -i prompt=hello +cog run $TEST_IMAGE -i prompt=hello stdout '"hello"' stdout '" world"' stdout '" !"' @@ -15,13 +15,13 @@ stdout '" !"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, ConcatenateIterator +-- run.py -- +from cog import BaseRunner, ConcatenateIterator -class Predictor(BasePredictor): - def predict(self, prompt: str) -> ConcatenateIterator[str]: +class Runner(BaseRunner): + def run(self, prompt: str) -> ConcatenateIterator[str]: for token in [prompt, " world", " !"]: yield token diff --git a/integration-tests/tests/debug_secrets.txtar b/integration-tests/tests/debug_secrets.txtar index 5ad0c59b0a..4b4451f6f1 100644 --- a/integration-tests/tests/debug_secrets.txtar +++ b/integration-tests/tests/debug_secrets.txtar @@ -17,15 +17,15 @@ build: - type: secret id: foo target: secret.txt -predict: "predict.py:Predictor" +run: "run.py:Runner" -- secret.txt -- secret content here --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/dict_output.txtar b/integration-tests/tests/dict_output.txtar index 83084817d4..edbe670eea 100644 --- a/integration-tests/tests/dict_output.txtar +++ b/integration-tests/tests/dict_output.txtar @@ -1,22 +1,22 @@ -# Test bare dict return type works for predict output +# Test bare dict return type works for run output # Build the image cog build -t $TEST_IMAGE -# Predict returns a dict -cog predict $TEST_IMAGE -i name=alice +# Run returns a dict +cog run $TEST_IMAGE -i name=alice stdout '"greeting": "hello alice"' stdout '"length": 5' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, name: str) -> dict: +class Runner(BaseRunner): + def run(self, name: str) -> dict: return {"greeting": "hello " + name, "length": len(name)} diff --git a/integration-tests/tests/emit_metric_deprecated.txtar b/integration-tests/tests/emit_metric_deprecated.txtar index 6484e0105c..64ac43202d 100644 --- a/integration-tests/tests/emit_metric_deprecated.txtar +++ b/integration-tests/tests/emit_metric_deprecated.txtar @@ -1,4 +1,4 @@ -# Test that a predictor using the deprecated emit_metric() still builds, +# Test that a runner using the deprecated emit_metric() still builds, # runs, and records metrics correctly. # # emit_metric() was dropped without a compat shim in 0.17.0, causing a hard @@ -18,15 +18,15 @@ stdout '"output_tokens":42' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import cog -from cog import BasePredictor, emit_metric +from cog import BaseRunner, emit_metric -class Predictor(BasePredictor): - def predict(self) -> str: +class Runner(BaseRunner): + def run(self) -> str: # Both call styles should work emit_metric("output_tokens", 42) cog.emit_metric("input_tokens", 10) diff --git a/integration-tests/tests/env_vars.txtar b/integration-tests/tests/env_vars.txtar index ee65779be6..2a50f1ff6a 100644 --- a/integration-tests/tests/env_vars.txtar +++ b/integration-tests/tests/env_vars.txtar @@ -2,24 +2,24 @@ cog build -t $TEST_IMAGE # Environment variables are set -cog predict $TEST_IMAGE -i name=TEST_VAR +cog run $TEST_IMAGE -i name=TEST_VAR stdout 'test_value' -cog predict $TEST_IMAGE -i name=NAME +cog run $TEST_IMAGE -i name=NAME stdout 'michael' -- cog.yaml -- -predict: "predict.py:Predictor" +run: "run.py:Runner" build: python_version: "3.12" environment: - NAME=michael - TEST_VAR=test_value --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner import os -class Predictor(BasePredictor): - def predict(self, name: str) -> str: +class Runner(BaseRunner): + def run(self, name: str) -> str: return f"ENV[{name}]={os.getenv(name)}" diff --git a/integration-tests/tests/experimental_feature_warning.txtar b/integration-tests/tests/experimental_feature_warning.txtar index 3cbaa5e055..83cae0353c 100644 --- a/integration-tests/tests/experimental_feature_warning.txtar +++ b/integration-tests/tests/experimental_feature_warning.txtar @@ -1,24 +1,24 @@ -# Test that a predictor importing the deprecated ExperimentalFeatureWarning +# Test that a runner importing the deprecated ExperimentalFeatureWarning # still builds and runs successfully. # Build the image cog build -t $TEST_IMAGE # Prediction works despite the deprecated import -cog predict $TEST_IMAGE -i s=world +cog run $TEST_IMAGE -i s=world stdout 'hello world' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import warnings -from cog import BasePredictor, ExperimentalFeatureWarning +from cog import BaseRunner, ExperimentalFeatureWarning warnings.filterwarnings("ignore", category=ExperimentalFeatureWarning) -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/ffmpeg_package.txtar b/integration-tests/tests/ffmpeg_package.txtar index db0a015956..657806e961 100644 --- a/integration-tests/tests/ffmpeg_package.txtar +++ b/integration-tests/tests/ffmpeg_package.txtar @@ -3,8 +3,8 @@ # Build the image (the run command verifies ffmpeg is installed) cog build -t $TEST_IMAGE -# Verify the predictor works -cog predict $TEST_IMAGE -i s=world +# Verify the runner works +cog run $TEST_IMAGE -i s=world stdout 'hello world' -- cog.yaml -- @@ -16,12 +16,12 @@ build: cuda: "12.4" run: - command: ffmpeg --help -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/file_input.txtar b/integration-tests/tests/file_input.txtar index 00b2c488d8..d3b640c73e 100644 --- a/integration-tests/tests/file_input.txtar +++ b/integration-tests/tests/file_input.txtar @@ -2,19 +2,19 @@ cog build -t $TEST_IMAGE # File input works -cog predict $TEST_IMAGE -i file=@input.txt +cog run $TEST_IMAGE -i file=@input.txt stdout 'hello from file' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Path +-- run.py -- +from cog import BaseRunner, Path -class Predictor(BasePredictor): - def predict(self, file: Path) -> str: +class Runner(BaseRunner): + def run(self, file: Path) -> str: return file.read_text() -- input.txt -- diff --git a/integration-tests/tests/file_list_input.txtar b/integration-tests/tests/file_list_input.txtar index 680780d659..2533522171 100644 --- a/integration-tests/tests/file_list_input.txtar +++ b/integration-tests/tests/file_list_input.txtar @@ -4,22 +4,22 @@ # Build the image cog build -t $TEST_IMAGE -# Predict with multiple file inputs -cog predict $TEST_IMAGE -i files=@file1.txt -i files=@file2.txt +# Run with multiple file inputs +cog run $TEST_IMAGE -i files=@file1.txt -i files=@file2.txt stdout 'content one' stdout 'content two' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, File +-- run.py -- +from cog import BaseRunner, File -class Predictor(BasePredictor): - def predict(self, files: list[File]) -> str: +class Runner(BaseRunner): + def run(self, files: list[File]) -> str: output_parts = [] for f in files: content = f.read() diff --git a/integration-tests/tests/float_input_output.txtar b/integration-tests/tests/float_input_output.txtar index 59832eccbb..e27f6e6d23 100644 --- a/integration-tests/tests/float_input_output.txtar +++ b/integration-tests/tests/float_input_output.txtar @@ -2,23 +2,23 @@ cog build -t $TEST_IMAGE # Float input and output works -cog predict $TEST_IMAGE -i num=10 +cog run $TEST_IMAGE -i num=10 stdout '20' # Negative numbers work -cog predict $TEST_IMAGE -i num=-10 +cog run $TEST_IMAGE -i num=-10 stdout '-20' -- cog.yaml -- build: python_version: "3.11" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Input +-- run.py -- +from cog import BaseRunner, Input -class Predictor(BasePredictor): - def predict( +class Runner(BaseRunner): + def run( self, num: float = Input(description="Number of things") ) -> float: return num * 2.0 diff --git a/integration-tests/tests/function_predictor.txtar b/integration-tests/tests/function_predictor.txtar index fca5c9b08b..461331a2c5 100644 --- a/integration-tests/tests/function_predictor.txtar +++ b/integration-tests/tests/function_predictor.txtar @@ -1,18 +1,18 @@ -# Test function-based predictor (no class, just a function) +# Test function-based runner (no class, just a function) # Build the image cog build -t $TEST_IMAGE -# Predict using function-based predictor -cog predict $TEST_IMAGE -i prompt=world +# Run using function-based runner +cog run $TEST_IMAGE -i prompt=world stdout 'HELLO WORLD' -- cog.yaml -- build: python_version: "3.13" -predict: "predict.py:run" +run: "run.py:run" --- predict.py -- +-- run.py -- from cog import Input diff --git a/integration-tests/tests/future_annotations.txtar b/integration-tests/tests/future_annotations.txtar index 0da13e2b44..c6282bbd89 100644 --- a/integration-tests/tests/future_annotations.txtar +++ b/integration-tests/tests/future_annotations.txtar @@ -5,8 +5,8 @@ # Build the image cog build -t $TEST_IMAGE -# Predict - creates and returns an image -cog predict $TEST_IMAGE +# Run - creates and returns an image +cog run $TEST_IMAGE # Verify output file was created exec test -f output.png @@ -16,17 +16,17 @@ build: python_version: "3.12" python_packages: - pillow==10.0.0 -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- from __future__ import annotations -from cog import BasePredictor, Input, Path +from cog import BaseRunner, Input, Path from PIL import Image -class Predictor(BasePredictor): - def predict(self) -> Path: +class Runner(BaseRunner): + def run(self) -> Path: """Create and return a simple test image.""" # Create a simple red image img = Image.new("RGB", (100, 100), color="red") diff --git a/integration-tests/tests/glb_project.txtar b/integration-tests/tests/glb_project.txtar index d07c92a313..fd58aeba2b 100644 --- a/integration-tests/tests/glb_project.txtar +++ b/integration-tests/tests/glb_project.txtar @@ -3,23 +3,23 @@ # Create a minimal GLB placeholder file (GLB files start with "glTF" magic bytes) exec sh -c 'printf "glTF" > mesh.glb' -# Build and predict - verifies GLB file output works +# Build and run - verifies GLB file output works cog build -t $TEST_IMAGE -cog predict $TEST_IMAGE +cog run $TEST_IMAGE -- cog.yaml -- build: python_version: "3.13" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Path +-- run.py -- +from cog import BaseRunner, Path -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self) -> None: if not Path("mesh.glb").exists(): raise ValueError("Example file mesh.glb does not exist") - def predict(self) -> Path: + def run(self) -> Path: return Path("mesh.glb") diff --git a/integration-tests/tests/granite_project.txtar b/integration-tests/tests/granite_project.txtar index e673f0a66d..8301f9e6d3 100644 --- a/integration-tests/tests/granite_project.txtar +++ b/integration-tests/tests/granite_project.txtar @@ -3,24 +3,24 @@ # Build the image cog build -t $TEST_IMAGE -# Predict and verify pydantic version is preserved -cog predict $TEST_IMAGE +# Run and verify pydantic version is preserved +cog run $TEST_IMAGE stdout '2.11.9' -- cog.yaml -- build: python_version: "3.11" python_requirements: requirements.txt -predict: "predict.py:Predictor" +run: "run.py:Runner" -- requirements.txt -- pydantic==2.11.9 --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner import pydantic -class Predictor(BasePredictor): - def predict(self) -> str: +class Runner(BaseRunner): + def run(self) -> str: return pydantic.__version__ diff --git a/integration-tests/tests/healthcheck.txtar b/integration-tests/tests/healthcheck.txtar index 6392cb9200..65cd4bfa11 100644 --- a/integration-tests/tests/healthcheck.txtar +++ b/integration-tests/tests/healthcheck.txtar @@ -1,5 +1,5 @@ # Test custom healthcheck functionality -# This tests the user-defined healthcheck() method in predictors +# This tests the user-defined healthcheck() method in runners # Build the image cog build -t $TEST_IMAGE @@ -12,7 +12,7 @@ curl GET /health-check stdout '"status":"READY"' ! stdout 'user_healthcheck_error' -# Test 2: Make a prediction to ensure predictor works +# Test 2: Make a prediction to ensure runner works curl POST /predictions '{"input":{"text":"world"}}' stdout '"output":"hello world"' @@ -23,13 +23,13 @@ stdout '"status":"READY"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, text: str) -> str: +class Runner(BaseRunner): + def run(self, text: str) -> str: return f"hello {text}" def healthcheck(self) -> bool: diff --git a/integration-tests/tests/healthcheck_async.txtar b/integration-tests/tests/healthcheck_async.txtar index 819d32a035..b8e1f67ca7 100644 --- a/integration-tests/tests/healthcheck_async.txtar +++ b/integration-tests/tests/healthcheck_async.txtar @@ -23,14 +23,14 @@ stdout '"status":"READY"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import asyncio -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, text: str) -> str: +class Runner(BaseRunner): + def run(self, text: str) -> str: return f"hello {text}" async def healthcheck(self) -> bool: diff --git a/integration-tests/tests/healthcheck_async_exception.txtar b/integration-tests/tests/healthcheck_async_exception.txtar index a4f56308dc..4b7e49a88d 100644 --- a/integration-tests/tests/healthcheck_async_exception.txtar +++ b/integration-tests/tests/healthcheck_async_exception.txtar @@ -16,14 +16,14 @@ stdout 'Async healthcheck error' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner import asyncio -class Predictor(BasePredictor): - async def predict(self, text: str) -> str: +class Runner(BaseRunner): + async def run(self, text: str) -> str: return f"hello {text}" async def healthcheck(self) -> bool: diff --git a/integration-tests/tests/healthcheck_async_timeout.txtar b/integration-tests/tests/healthcheck_async_timeout.txtar index 93eb0583ed..d4df9eda0e 100644 --- a/integration-tests/tests/healthcheck_async_timeout.txtar +++ b/integration-tests/tests/healthcheck_async_timeout.txtar @@ -24,17 +24,17 @@ stdout 'timed out after 5.0 seconds' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner import asyncio -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self) -> None: self._slow_mode = False - async def predict(self, text: str) -> str: + async def run(self, text: str) -> str: if text == "trigger_slow": self._slow_mode = True return f"hello {text}" diff --git a/integration-tests/tests/healthcheck_async_unhealthy.txtar b/integration-tests/tests/healthcheck_async_unhealthy.txtar index 7c37cc1a46..720122073c 100644 --- a/integration-tests/tests/healthcheck_async_unhealthy.txtar +++ b/integration-tests/tests/healthcheck_async_unhealthy.txtar @@ -16,14 +16,14 @@ stdout 'returned False' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner import asyncio -class Predictor(BasePredictor): - async def predict(self, text: str) -> str: +class Runner(BaseRunner): + async def run(self, text: str) -> str: return f"hello {text}" async def healthcheck(self) -> bool: diff --git a/integration-tests/tests/healthcheck_during_prediction.txtar b/integration-tests/tests/healthcheck_during_prediction.txtar index 9aefb44a77..bce0ca899b 100644 --- a/integration-tests/tests/healthcheck_during_prediction.txtar +++ b/integration-tests/tests/healthcheck_during_prediction.txtar @@ -39,14 +39,14 @@ stdout '"status":"READY"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import time -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, sleep_time: int) -> str: +class Runner(BaseRunner): + def run(self, sleep_time: int) -> str: """Sleep for specified seconds.""" time.sleep(sleep_time) return f"slept for {sleep_time} seconds" diff --git a/integration-tests/tests/healthcheck_exception.txtar b/integration-tests/tests/healthcheck_exception.txtar index c6848238a6..5d0104c25a 100644 --- a/integration-tests/tests/healthcheck_exception.txtar +++ b/integration-tests/tests/healthcheck_exception.txtar @@ -16,16 +16,16 @@ stdout 'Critical system error' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self) -> None: self._healthcheck_calls = 0 - def predict(self, text: str) -> str: + def run(self, text: str) -> str: return f"hello {text}" def healthcheck(self) -> bool: diff --git a/integration-tests/tests/healthcheck_immediately_after_prediction.txtar b/integration-tests/tests/healthcheck_immediately_after_prediction.txtar index 851c6b172c..b91b3adade 100644 --- a/integration-tests/tests/healthcheck_immediately_after_prediction.txtar +++ b/integration-tests/tests/healthcheck_immediately_after_prediction.txtar @@ -32,13 +32,13 @@ stdout '"status":"READY"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, text: str) -> str: +class Runner(BaseRunner): + def run(self, text: str) -> str: return f"hello {text}" def healthcheck(self) -> bool: diff --git a/integration-tests/tests/healthcheck_repeated_calls.txtar b/integration-tests/tests/healthcheck_repeated_calls.txtar index 95e04ffee3..eb10d30f02 100644 --- a/integration-tests/tests/healthcheck_repeated_calls.txtar +++ b/integration-tests/tests/healthcheck_repeated_calls.txtar @@ -22,16 +22,16 @@ stdout '"status":"READY"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): def __init__(self): self.call_count = 0 - def predict(self, text: str) -> str: + def run(self, text: str) -> str: return f"hello {text}" def healthcheck(self) -> bool: diff --git a/integration-tests/tests/healthcheck_timeout.txtar b/integration-tests/tests/healthcheck_timeout.txtar index 6645ee4805..52017f4225 100644 --- a/integration-tests/tests/healthcheck_timeout.txtar +++ b/integration-tests/tests/healthcheck_timeout.txtar @@ -24,17 +24,17 @@ stdout 'timed out after 5.0 seconds' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import time -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self) -> None: self._slow_mode = False - def predict(self, text: str) -> str: + def run(self, text: str) -> str: if text == "trigger_slow": self._slow_mode = True return f"hello {text}" diff --git a/integration-tests/tests/healthcheck_unhealthy.txtar b/integration-tests/tests/healthcheck_unhealthy.txtar index 6c38f54ee7..e2ee020a50 100644 --- a/integration-tests/tests/healthcheck_unhealthy.txtar +++ b/integration-tests/tests/healthcheck_unhealthy.txtar @@ -16,16 +16,16 @@ stdout 'user-defined healthcheck returned False' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self) -> None: self._healthcheck_calls = 0 - def predict(self, text: str) -> str: + def run(self, text: str) -> str: return f"hello {text}" def healthcheck(self) -> bool: diff --git a/integration-tests/tests/int_input_output.txtar b/integration-tests/tests/int_input_output.txtar index ba4e2531e6..d7f022ea67 100644 --- a/integration-tests/tests/int_input_output.txtar +++ b/integration-tests/tests/int_input_output.txtar @@ -2,23 +2,23 @@ cog build -t $TEST_IMAGE # Integer input and output works -cog predict $TEST_IMAGE -i num=10 +cog run $TEST_IMAGE -i num=10 stdout '20' # Negative numbers work -cog predict $TEST_IMAGE -i num=-10 +cog run $TEST_IMAGE -i num=-10 stdout '-20' -- cog.yaml -- build: python_version: "3.11" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Input +-- run.py -- +from cog import BaseRunner, Input -class Predictor(BasePredictor): - def predict( +class Runner(BaseRunner): + def run( self, num: int = Input(description="Number of things") ) -> int: return num * 2 diff --git a/integration-tests/tests/int_none_output.txtar b/integration-tests/tests/int_none_output.txtar index a565a2de17..7a0215e488 100644 --- a/integration-tests/tests/int_none_output.txtar +++ b/integration-tests/tests/int_none_output.txtar @@ -5,20 +5,20 @@ # Build the image cog build -t $TEST_IMAGE -# Predict returns None despite int type annotation +# Run returns None despite int type annotation # When None is returned, cog shows "No output generated" on stderr -cog predict $TEST_IMAGE +cog run $TEST_IMAGE stderr 'No output generated' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self) -> int: +class Runner(BaseRunner): + def run(self) -> int: return None diff --git a/integration-tests/tests/int_predictor.txtar b/integration-tests/tests/int_predictor.txtar index 639fb193bf..47b79cb725 100644 --- a/integration-tests/tests/int_predictor.txtar +++ b/integration-tests/tests/int_predictor.txtar @@ -2,21 +2,21 @@ cog build -t $TEST_IMAGE # Integer input and output works -cog predict $TEST_IMAGE -i num=5 +cog run $TEST_IMAGE -i num=5 stdout '10' # Negative numbers work -cog predict $TEST_IMAGE -i num=-3 +cog run $TEST_IMAGE -i num=-3 stdout '-6' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, num: int) -> int: +class Runner(BaseRunner): + def run(self, num: int) -> int: return num * 2 diff --git a/integration-tests/tests/invalid_int_validation.txtar b/integration-tests/tests/invalid_int_validation.txtar index 4198102f0f..4fbe3717c7 100644 --- a/integration-tests/tests/invalid_int_validation.txtar +++ b/integration-tests/tests/invalid_int_validation.txtar @@ -10,14 +10,14 @@ stderr 'minimum' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Input +-- run.py -- +from cog import BaseRunner, Input -class Predictor(BasePredictor): - def predict( +class Runner(BaseRunner): + def run( self, num: int = Input(description="Number of things", default=1, ge=2, le=10) ) -> int: return num * 2 diff --git a/integration-tests/tests/iterator_error_midstream.txtar b/integration-tests/tests/iterator_error_midstream.txtar index 3a849965c2..b19543437a 100644 --- a/integration-tests/tests/iterator_error_midstream.txtar +++ b/integration-tests/tests/iterator_error_midstream.txtar @@ -18,16 +18,16 @@ stdout '"error_message":".*generator exploded"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- from typing import Iterator -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self) -> Iterator[str]: +class Runner(BaseRunner): + def run(self) -> Iterator[str]: yield "chunk-1" yield "chunk-2" yield "chunk-3" diff --git a/integration-tests/tests/iterator_string_output.txtar b/integration-tests/tests/iterator_string_output.txtar index e84a95490a..93698e51e8 100644 --- a/integration-tests/tests/iterator_string_output.txtar +++ b/integration-tests/tests/iterator_string_output.txtar @@ -1,4 +1,4 @@ -# Test Iterator[str] as predict output type +# Test Iterator[str] as run output type # # Iterator[str] yields individual string items as an array. @@ -6,7 +6,7 @@ cog build -t $TEST_IMAGE # Iterator output returns items -cog predict $TEST_IMAGE -i count=3 +cog run $TEST_IMAGE -i count=3 stdout 'item-0' stdout 'item-1' stdout 'item-2' @@ -14,15 +14,15 @@ stdout 'item-2' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- from typing import Iterator -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, count: int) -> Iterator[str]: +class Runner(BaseRunner): + def run(self, count: int) -> Iterator[str]: for i in range(count): yield f"item-{i}" diff --git a/integration-tests/tests/legacy_sdk_schema.txtar b/integration-tests/tests/legacy_sdk_schema.txtar index a3fa96b73c..012cd2c9a1 100644 --- a/integration-tests/tests/legacy_sdk_schema.txtar +++ b/integration-tests/tests/legacy_sdk_schema.txtar @@ -13,18 +13,18 @@ env COG_SDK_WHEEL=pypi:0.16.12 # automatically uses the legacy runtime schema generation path. cog build -t $TEST_IMAGE -# Predict should work with the legacy SDK's built-in Python HTTP server -cog predict $TEST_IMAGE -i s=world +# Run should work with the legacy SDK's built-in Python HTTP server +cog run $TEST_IMAGE -i s=world stdout 'hello world' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/list_int_input_output.txtar b/integration-tests/tests/list_int_input_output.txtar index bb651657b0..7cbbde62a9 100644 --- a/integration-tests/tests/list_int_input_output.txtar +++ b/integration-tests/tests/list_int_input_output.txtar @@ -1,10 +1,10 @@ -# Test list[int] as predict input and output types +# Test list[int] as run input and output types # Build the image cog build -t $TEST_IMAGE # List of ints works as input and output -cog predict $TEST_IMAGE --json '{"numbers": [1, 2, 3]}' +cog run $TEST_IMAGE --json '{"numbers": [1, 2, 3]}' stdout '"status": "succeeded"' stdout '"output":' stdout '2' @@ -14,12 +14,12 @@ stdout '6' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, numbers: list[int]) -> list[int]: +class Runner(BaseRunner): + def run(self, numbers: list[int]) -> list[int]: return [n * 2 for n in numbers] diff --git a/integration-tests/tests/list_string_output.txtar b/integration-tests/tests/list_string_output.txtar index ac92522af1..c3e8e26cb2 100644 --- a/integration-tests/tests/list_string_output.txtar +++ b/integration-tests/tests/list_string_output.txtar @@ -1,10 +1,10 @@ -# Test list[str] as predict output type +# Test list[str] as run output type # Build the image cog build -t $TEST_IMAGE # List output returns items -cog predict $TEST_IMAGE -i text='hello world foo' +cog run $TEST_IMAGE -i text='hello world foo' stdout 'hello' stdout 'world' stdout 'foo' @@ -12,12 +12,12 @@ stdout 'foo' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, text: str) -> list[str]: +class Runner(BaseRunner): + def run(self, text: str) -> list[str]: return text.split() diff --git a/integration-tests/tests/many_inputs.txtar b/integration-tests/tests/many_inputs.txtar index f67f83be25..8813fb2fa9 100644 --- a/integration-tests/tests/many_inputs.txtar +++ b/integration-tests/tests/many_inputs.txtar @@ -1,24 +1,24 @@ -# Test predictor with many different input types +# Test runner with many different input types # Build the image cog build -t $TEST_IMAGE -# Predict with various input types -cog predict $TEST_IMAGE -i no_default=hello -i path=@path.txt -i image=@image.jpg -i choices=foo -i int_choices=3 +# Run with various input types +cog run $TEST_IMAGE -i no_default=hello -i path=@path.txt -i image=@image.jpg -i choices=foo -i int_choices=3 stdout 'hello default 20 world jpg foo 6' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Input, Path +-- run.py -- +from cog import BaseRunner, Input, Path -class Predictor(BasePredictor): - def predict( +class Runner(BaseRunner): + def run( self, no_default: str, default_without_input: str = "default", diff --git a/integration-tests/tests/multi_file_schema.txtar b/integration-tests/tests/multi_file_schema.txtar index cf9e537353..18ba6bccf8 100644 --- a/integration-tests/tests/multi_file_schema.txtar +++ b/integration-tests/tests/multi_file_schema.txtar @@ -1,6 +1,6 @@ # Test that schema generation works when the output type is defined in a # separate Python module. This exercises the cross-file model resolution: -# the predictor imports Output from output_types.py, and the static Go parser +# the runner imports Output from output_types.py, and the static Go parser # finds and parses output_types.py to resolve the BaseModel fields. # # Covers: @@ -27,8 +27,8 @@ stdout '"tags":' stdout '"prompt":' stdout '"required":\["prompt"\]' -# Predict should work end-to-end -cog predict $TEST_IMAGE -i prompt=hello +# Run should work end-to-end +cog run $TEST_IMAGE -i prompt=hello stdout '"text": "hello"' stdout '"score": 1' stdout '"tags"' @@ -38,7 +38,7 @@ build: python_version: "3.12" python_packages: - "pydantic>2" -predict: "predict.py:Predictor" +run: "run.py:Runner" -- output_types.py -- from pydantic import BaseModel @@ -49,11 +49,11 @@ class Output(BaseModel): score: float tags: list[str] --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner from output_types import Output -class Predictor(BasePredictor): - def predict(self, prompt: str) -> Output: +class Runner(BaseRunner): + def run(self, prompt: str) -> Output: return Output(text=prompt, score=1.0, tags=["default"]) diff --git a/integration-tests/tests/nested_output_types.txtar b/integration-tests/tests/nested_output_types.txtar index 01cd9aad27..0043cef02e 100644 --- a/integration-tests/tests/nested_output_types.txtar +++ b/integration-tests/tests/nested_output_types.txtar @@ -24,12 +24,12 @@ stdout '"extra":null' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- from typing import List, Optional -from cog import BaseModel, BasePredictor +from cog import BaseModel, BaseRunner class Output(BaseModel): @@ -42,8 +42,8 @@ class Output(BaseModel): extra: Optional[str] -class Predictor(BasePredictor): - def predict(self, name: str) -> Output: +class Runner(BaseRunner): + def run(self, name: str) -> Output: return Output( name=name, count=42, diff --git a/integration-tests/tests/no_predictor.txtar b/integration-tests/tests/no_predictor.txtar index 151074bc88..0729ca1bef 100644 --- a/integration-tests/tests/no_predictor.txtar +++ b/integration-tests/tests/no_predictor.txtar @@ -1,9 +1,9 @@ -# Test error when no predictor is defined -# Build should fail when cog.yaml has no predict field +# Test error when no runner is defined +# Build should fail when cog.yaml has no run field -# Build should fail with error about missing predictor +# Build should fail with error about missing runner ! cog build -t $TEST_IMAGE -stderr 'predict' +stderr 'run' -- cog.yaml -- build: diff --git a/integration-tests/tests/non_base_predictor_class.txtar b/integration-tests/tests/non_base_predictor_class.txtar index 4dd84197b1..8a966e1a5c 100644 --- a/integration-tests/tests/non_base_predictor_class.txtar +++ b/integration-tests/tests/non_base_predictor_class.txtar @@ -2,19 +2,19 @@ # Build image cog build -t $TEST_IMAGE -# Predict using class without BasePredictor -cog predict $TEST_IMAGE -i text=world +# Run using class without BaseRunner +cog run $TEST_IMAGE -i text=world stdout 'hello world' -- cog.yaml -- build: python_version: "3.12" -predict: predict.py:Predictor +run: run.py:Runner --- predict.py -- +-- run.py -- from cog import Input -class Predictor: - def predict(self, text: str = Input(default="world")) -> str: +class Runner: + def run(self, text: str = Input(default="world")) -> str: return f"hello {text}" diff --git a/integration-tests/tests/non_base_predictor_function.txtar b/integration-tests/tests/non_base_predictor_function.txtar index c4d0b6cf40..9be9f035cd 100644 --- a/integration-tests/tests/non_base_predictor_function.txtar +++ b/integration-tests/tests/non_base_predictor_function.txtar @@ -2,18 +2,18 @@ # Build image cog build -t $TEST_IMAGE -# Predict using standalone function -cog predict $TEST_IMAGE -i text=world +# Run using standalone function +cog run $TEST_IMAGE -i text=world stdout 'hello world' -- cog.yaml -- build: python_version: "3.12" -predict: predict.py:predict +run: run.py:run --- predict.py -- +-- run.py -- from cog import Input -def predict(text: str = Input(default="world")) -> str: +def run(text: str = Input(default="world")) -> str: return f"hello {text}" diff --git a/integration-tests/tests/oci_bundle_build.txtar b/integration-tests/tests/oci_bundle_build.txtar index 3e831841d6..2b1a3ad347 100644 --- a/integration-tests/tests/oci_bundle_build.txtar +++ b/integration-tests/tests/oci_bundle_build.txtar @@ -23,13 +23,13 @@ exec docker image inspect $TEST_IMAGE stdout 'run.cog.config' # Verify prediction works -cog predict $TEST_IMAGE -i text=hello +cog run $TEST_IMAGE -i text=hello stdout 'processed: hello' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" weights: - name: alpha source: weights/model-a.bin @@ -38,9 +38,9 @@ weights: source: weights/model-b.bin target: /weights/model-b.bin --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, text: str) -> str: +class Runner(BaseRunner): + def run(self, text: str) -> str: return f"processed: {text}" diff --git a/integration-tests/tests/oci_bundle_inspect.txtar b/integration-tests/tests/oci_bundle_inspect.txtar index 5cc96641d8..a364e0370e 100644 --- a/integration-tests/tests/oci_bundle_inspect.txtar +++ b/integration-tests/tests/oci_bundle_inspect.txtar @@ -46,7 +46,7 @@ stdout '"digest": "sha256:' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" weights: - name: alpha source: weights/model-a.bin @@ -55,9 +55,9 @@ weights: source: weights/model-b.bin target: /weights/model-b.bin --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, text: str) -> str: +class Runner(BaseRunner): + def run(self, text: str) -> str: return f"processed: {text}" diff --git a/integration-tests/tests/oci_bundle_push.txtar b/integration-tests/tests/oci_bundle_push.txtar index cbc35f8816..0f0334fb8e 100644 --- a/integration-tests/tests/oci_bundle_push.txtar +++ b/integration-tests/tests/oci_bundle_push.txtar @@ -36,7 +36,7 @@ stdout 'vnd.cog.weight.name.*beta' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" weights: - name: alpha source: weights/model-a.bin @@ -45,9 +45,9 @@ weights: source: weights/model-b.bin target: /weights/model-b.bin --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, text: str) -> str: +class Runner(BaseRunner): + def run(self, text: str) -> str: return f"processed: {text}" diff --git a/integration-tests/tests/optional_path_input.txtar b/integration-tests/tests/optional_path_input.txtar index d9ba119fee..45deb47a46 100644 --- a/integration-tests/tests/optional_path_input.txtar +++ b/integration-tests/tests/optional_path_input.txtar @@ -3,8 +3,8 @@ # Build the image cog build -t $TEST_IMAGE -# Predict without providing optional input - should return red image -cog predict $TEST_IMAGE +# Run without providing optional input - should return red image +cog run $TEST_IMAGE # Verify output file was created exec test -f output.webp @@ -14,15 +14,15 @@ build: python_version: "3.12" python_packages: - "pillow" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Path, Input +-- run.py -- +from cog import BaseRunner, Path, Input from PIL import Image -class Predictor(BasePredictor): - def predict( +class Runner(BaseRunner): + def run( self, test_image: Path | None = Input(description="Test image", default=None) ) -> Path: diff --git a/integration-tests/tests/path_input.txtar b/integration-tests/tests/path_input.txtar index eb5b7ae99b..7dc0d81416 100644 --- a/integration-tests/tests/path_input.txtar +++ b/integration-tests/tests/path_input.txtar @@ -3,21 +3,21 @@ # Build the image cog build -t $TEST_IMAGE -# Predict reads content from file -cog predict $TEST_IMAGE -i path=@input.txt +# Run reads content from file +cog run $TEST_IMAGE -i path=@input.txt stdout 'hello from file' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Path +-- run.py -- +from cog import BaseRunner, Path -class Predictor(BasePredictor): - def predict(self, path: Path) -> str: +class Runner(BaseRunner): + def run(self, path: Path) -> str: with open(path) as f: return f.read() diff --git a/integration-tests/tests/path_input_output.txtar b/integration-tests/tests/path_input_output.txtar index 03c973e92b..5874fc1b87 100644 --- a/integration-tests/tests/path_input_output.txtar +++ b/integration-tests/tests/path_input_output.txtar @@ -3,8 +3,8 @@ # Build the image cog build -t $TEST_IMAGE -# Predict with text and path input, returns path output -cog predict $TEST_IMAGE -i text=bar -i path=@input.txt +# Run with text and path input, returns path output +cog run $TEST_IMAGE -i text=bar -i path=@input.txt # Verify output file was created and contains expected content exec test -f output.txt @@ -13,19 +13,19 @@ exec grep -q foobarbaz output.txt -- cog.yaml -- build: python_version: "3.12" -predict: predict.py:Predictor +run: run.py:Runner --- predict.py -- +-- run.py -- import tempfile -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): self.foo = "foo" - def predict(self, text: str, path: Path) -> Path: + def run(self, text: str, path: Path) -> Path: with open(path) as f: output = self.foo + text + f.read() tmpdir = Path(tempfile.mkdtemp()) diff --git a/integration-tests/tests/path_list_input.txtar b/integration-tests/tests/path_list_input.txtar index 0dfdb22373..8567d4ab2c 100644 --- a/integration-tests/tests/path_list_input.txtar +++ b/integration-tests/tests/path_list_input.txtar @@ -3,22 +3,22 @@ # Build the image cog build -t $TEST_IMAGE -# Predict with multiple file inputs -cog predict $TEST_IMAGE -i paths=@1.txt -i paths=@2.txt +# Run with multiple file inputs +cog run $TEST_IMAGE -i paths=@1.txt -i paths=@2.txt stdout 'test1' stdout 'test2' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Path +-- run.py -- +from cog import BaseRunner, Path -class Predictor(BasePredictor): - def predict(self, paths: list[Path]) -> str: +class Runner(BaseRunner): + def run(self, paths: list[Path]) -> str: output_parts = [] for path in paths: with open(path) as f: diff --git a/integration-tests/tests/path_list_output.txtar b/integration-tests/tests/path_list_output.txtar index d94d6a2510..14ac3cc516 100644 --- a/integration-tests/tests/path_list_output.txtar +++ b/integration-tests/tests/path_list_output.txtar @@ -3,8 +3,8 @@ # Build the image cog build -t $TEST_IMAGE -# Predict writes multiple files -cog predict $TEST_IMAGE +# Run writes multiple files +cog run $TEST_IMAGE # Verify files were created with expected content exec cat output.0.txt @@ -17,16 +17,16 @@ stdout 'baz' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- from typing import List -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): - def predict(self) -> List[Path]: +class Runner(BaseRunner): + def run(self) -> List[Path]: predictions = ["foo", "bar", "baz"] output = [] for i, prediction in enumerate(predictions): diff --git a/integration-tests/tests/path_output.txtar b/integration-tests/tests/path_output.txtar index 1c887e4a38..b748d2ff0a 100644 --- a/integration-tests/tests/path_output.txtar +++ b/integration-tests/tests/path_output.txtar @@ -3,8 +3,8 @@ # Build the image cog build -t $TEST_IMAGE -# Predict writes file to output.bmp -cog predict $TEST_IMAGE +# Run writes file to output.bmp +cog run $TEST_IMAGE # Verify file was created and has expected size (255x255 RGB BMP = ~195KB) exec test -f output.bmp @@ -15,18 +15,18 @@ build: python_version: "3.12" python_packages: - "pillow==10.4.0" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import os import tempfile -from cog import BasePredictor, Path +from cog import BaseRunner, Path from PIL import Image -class Predictor(BasePredictor): - def predict(self) -> Path: +class Runner(BaseRunner): + def run(self) -> Path: temp_dir = tempfile.mkdtemp() temp_path = os.path.join(temp_dir, "prediction.bmp") img = Image.new("RGB", (255, 255), "red") diff --git a/integration-tests/tests/pep604_file_or_none_input.txtar b/integration-tests/tests/pep604_file_or_none_input.txtar index e1337c9b74..d827539541 100644 --- a/integration-tests/tests/pep604_file_or_none_input.txtar +++ b/integration-tests/tests/pep604_file_or_none_input.txtar @@ -1,19 +1,19 @@ # Test PEP 604 File | None input annotation. # -# When a predictor declares `file: File | None`, coglet should recognise +# When a runner declares `file: File | None`, coglet should recognise # the File type through the PEP 604 union and coerce URL/file inputs # into cog.File objects, just like it does for Optional[File]. cog build -t $TEST_IMAGE -# --- Test via cog predict (CLI) --- +# --- Test via cog run (CLI) --- # Providing a file input should work. -cog predict $TEST_IMAGE -i file=@input.txt +cog run $TEST_IMAGE -i file=@input.txt stdout 'type=File content=hello from file' # Omitting the optional input should yield None. -cog predict $TEST_IMAGE +cog run $TEST_IMAGE stdout 'type=NoneType content=none' # --- Test via cog serve (HTTP API) --- @@ -33,14 +33,14 @@ stdout 'type=NoneType content=none' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, File, Input +-- run.py -- +from cog import BaseRunner, File, Input -class Predictor(BasePredictor): - def predict(self, file: File | None = Input(default=None)) -> str: +class Runner(BaseRunner): + def run(self, file: File | None = Input(default=None)) -> str: if file is None: return "type=NoneType content=none" content = file.read() diff --git a/integration-tests/tests/pep604_list_file_or_none_input.txtar b/integration-tests/tests/pep604_list_file_or_none_input.txtar index d39d497f58..0009601047 100644 --- a/integration-tests/tests/pep604_list_file_or_none_input.txtar +++ b/integration-tests/tests/pep604_list_file_or_none_input.txtar @@ -1,19 +1,19 @@ # Test PEP 604 list[File] | None input annotation. # -# When a predictor declares `files: list[File] | None`, coglet should +# When a runner declares `files: list[File] | None`, coglet should # recognise the File type inside the list through the PEP 604 union # and coerce inputs into cog.File objects. cog build -t $TEST_IMAGE -# --- Test via cog predict (CLI) --- +# --- Test via cog run (CLI) --- # Providing multiple file inputs should work. -cog predict $TEST_IMAGE -i files=@file1.txt -i files=@file2.txt +cog run $TEST_IMAGE -i files=@file1.txt -i files=@file2.txt stdout 'count=2 content=one,two' # Omitting the optional input should yield None. -cog predict $TEST_IMAGE +cog run $TEST_IMAGE stdout 'type=NoneType' # --- Test via cog serve (HTTP API) --- @@ -34,14 +34,14 @@ stdout 'type=NoneType' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, File, Input +-- run.py -- +from cog import BaseRunner, File, Input -class Predictor(BasePredictor): - def predict(self, files: list[File] | None = Input(default=None)) -> str: +class Runner(BaseRunner): + def run(self, files: list[File] | None = Input(default=None)) -> str: if files is None: return "type=NoneType" contents = [] diff --git a/integration-tests/tests/pep604_list_path_or_none_input.txtar b/integration-tests/tests/pep604_list_path_or_none_input.txtar index 9457520f0d..687ffbbc65 100644 --- a/integration-tests/tests/pep604_list_path_or_none_input.txtar +++ b/integration-tests/tests/pep604_list_path_or_none_input.txtar @@ -1,19 +1,19 @@ # Test PEP 604 list[Path] | None input annotation. # -# When a predictor declares `paths: list[Path] | None`, coglet should +# When a runner declares `paths: list[Path] | None`, coglet should # recognise the Path type inside the list through the PEP 604 union # and coerce inputs into cog.Path objects. cog build -t $TEST_IMAGE -# --- Test via cog predict (CLI) --- +# --- Test via cog run (CLI) --- # Providing multiple path inputs should work. -cog predict $TEST_IMAGE -i paths=@file1.txt -i paths=@file2.txt +cog run $TEST_IMAGE -i paths=@file1.txt -i paths=@file2.txt stdout 'count=2 content=one,two' # Omitting the optional input should yield None. -cog predict $TEST_IMAGE +cog run $TEST_IMAGE stdout 'type=NoneType' # --- Test via cog serve (HTTP API) --- @@ -34,14 +34,14 @@ stdout 'type=NoneType' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Path, Input +-- run.py -- +from cog import BaseRunner, Path, Input -class Predictor(BasePredictor): - def predict(self, paths: list[Path] | None = Input(default=None)) -> str: +class Runner(BaseRunner): + def run(self, paths: list[Path] | None = Input(default=None)) -> str: if paths is None: return "type=NoneType" contents = [] diff --git a/integration-tests/tests/pep604_path_or_none_input.txtar b/integration-tests/tests/pep604_path_or_none_input.txtar index 6087a3fd1f..4286f1c3ea 100644 --- a/integration-tests/tests/pep604_path_or_none_input.txtar +++ b/integration-tests/tests/pep604_path_or_none_input.txtar @@ -1,19 +1,19 @@ # Test PEP 604 Path | None input annotation. # -# When a predictor declares `path: Path | None`, coglet should recognise +# When a runner declares `path: Path | None`, coglet should recognise # the Path type through the PEP 604 union and coerce URL/file inputs # into cog.Path objects, just like it does for Optional[Path]. cog build -t $TEST_IMAGE -# --- Test via cog predict (CLI) --- +# --- Test via cog run (CLI) --- # Providing a file input should work. -cog predict $TEST_IMAGE -i path=@input.txt +cog run $TEST_IMAGE -i path=@input.txt stdout 'type=Path content=hello from file' # Omitting the optional input should yield None. -cog predict $TEST_IMAGE +cog run $TEST_IMAGE stdout 'type=NoneType content=none' # --- Test via cog serve (HTTP API) --- @@ -33,14 +33,14 @@ stdout 'type=NoneType content=none' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Path, Input +-- run.py -- +from cog import BaseRunner, Path, Input -class Predictor(BasePredictor): - def predict(self, path: Path | None = Input(default=None)) -> str: +class Runner(BaseRunner): + def run(self, path: Path | None = Input(default=None)) -> str: if path is None: return "type=NoneType content=none" with open(path) as f: diff --git a/integration-tests/tests/pep604_string_url_not_coerced.txtar b/integration-tests/tests/pep604_string_url_not_coerced.txtar index f6b72d3cce..0b2b0cb490 100644 --- a/integration-tests/tests/pep604_string_url_not_coerced.txtar +++ b/integration-tests/tests/pep604_string_url_not_coerced.txtar @@ -8,14 +8,14 @@ cog build -t $TEST_IMAGE -# --- Test via cog predict (CLI) --- +# --- Test via cog run (CLI) --- # A URL passed to a str | None input should be returned verbatim. -cog predict $TEST_IMAGE -i url=https://api.example.com/v1/resource +cog run $TEST_IMAGE -i url=https://api.example.com/v1/resource stdout 'type=str value=https://api.example.com/v1/resource' # None default should also work. -cog predict $TEST_IMAGE +cog run $TEST_IMAGE stdout 'type=NoneType value=none' # --- Test via cog serve (HTTP API) --- @@ -35,14 +35,14 @@ stdout 'type=NoneType value=none' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Input +-- run.py -- +from cog import BaseRunner, Input -class Predictor(BasePredictor): - def predict(self, url: str | None = Input(default=None)) -> str: +class Runner(BaseRunner): + def run(self, url: str | None = Input(default=None)) -> str: if url is None: return "type=NoneType value=none" return f"type={type(url).__name__} value={url}" diff --git a/integration-tests/tests/predict_existing_image.txtar b/integration-tests/tests/predict_existing_image.txtar index 6732e5c6ae..2bbf7bec4b 100644 --- a/integration-tests/tests/predict_existing_image.txtar +++ b/integration-tests/tests/predict_existing_image.txtar @@ -1,26 +1,26 @@ -# Test predict works with pre-built image from different directory +# Test run works with pre-built image from different directory # Source: test_predict.py::test_predict_runs_an_existing_image # Build the image first cog build -t $TEST_IMAGE -# Create a different directory and run predict from there +# Create a different directory and run from there mkdir another_dir cd another_dir -# Run predict on the pre-built image (no cog.yaml in current dir) -cog predict $TEST_IMAGE -i s=world +# Run on the pre-built image (no cog.yaml in current dir) +cog run $TEST_IMAGE -i s=world stdout 'hello world' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/predict_json_file.txtar b/integration-tests/tests/predict_json_file.txtar index d2cde82f0b..1447af9cd0 100644 --- a/integration-tests/tests/predict_json_file.txtar +++ b/integration-tests/tests/predict_json_file.txtar @@ -3,21 +3,21 @@ # Build and run prediction with JSON from file cog build -t $TEST_IMAGE -cog predict $TEST_IMAGE --json @input.json +cog run $TEST_IMAGE --json @input.json stdout '"status": "succeeded"' stdout '"output": "hello sackfield"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s -- input.json -- diff --git a/integration-tests/tests/predict_json_input.txtar b/integration-tests/tests/predict_json_input.txtar index bce156a5e6..e8f0af9f71 100644 --- a/integration-tests/tests/predict_json_input.txtar +++ b/integration-tests/tests/predict_json_input.txtar @@ -3,19 +3,19 @@ # Build and run prediction with inline JSON cog build -t $TEST_IMAGE -cog predict $TEST_IMAGE --json '{"s": "sackfield"}' +cog run $TEST_IMAGE --json '{"s": "sackfield"}' stdout '"status": "succeeded"' stdout '"output": "hello sackfield"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/predict_json_metrics.txtar b/integration-tests/tests/predict_json_metrics.txtar index bde6a3de15..f5a253a751 100644 --- a/integration-tests/tests/predict_json_metrics.txtar +++ b/integration-tests/tests/predict_json_metrics.txtar @@ -1,4 +1,4 @@ -# Test that custom metrics appear in `cog predict --json` output. +# Test that custom metrics appear in `cog run --json` output. # # This verifies the full end-to-end path: Python record_metric() -> # coglet HTTP response -> Go CLI JSON deserialization -> JSON output. @@ -7,7 +7,7 @@ # field, so custom metrics were silently dropped during JSON decoding. cog build -t $TEST_IMAGE -cog predict $TEST_IMAGE --json '{"prompt": "hello"}' +cog run $TEST_IMAGE --json '{"prompt": "hello"}' stdout '"status": "succeeded"' stdout '"token_count": 10' stdout '"temperature": 0.7' @@ -16,14 +16,14 @@ stdout '"predict_time"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, current_scope +-- run.py -- +from cog import BaseRunner, current_scope -class Predictor(BasePredictor): - def predict(self, prompt: str = "hello") -> str: +class Runner(BaseRunner): + def run(self, prompt: str = "hello") -> str: scope = current_scope() scope.record_metric("token_count", 10) scope.record_metric("temperature", 0.7) diff --git a/integration-tests/tests/predict_json_output_file.txtar b/integration-tests/tests/predict_json_output_file.txtar index 6b99b0a273..577241086c 100644 --- a/integration-tests/tests/predict_json_output_file.txtar +++ b/integration-tests/tests/predict_json_output_file.txtar @@ -3,7 +3,7 @@ # Build and run prediction with JSON output to file cog build -t $TEST_IMAGE -cog predict $TEST_IMAGE --json '{"s": "sackfield"}' --output output.json +cog run $TEST_IMAGE --json '{"s": "sackfield"}' --output output.json # Verify file was created with correct content exists output.json @@ -14,12 +14,12 @@ stdout '"output": "hello sackfield"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/predict_json_stdin.txtar b/integration-tests/tests/predict_json_stdin.txtar index e089849cc9..c7516d5ec2 100644 --- a/integration-tests/tests/predict_json_stdin.txtar +++ b/integration-tests/tests/predict_json_stdin.txtar @@ -4,21 +4,21 @@ # Build and run prediction with JSON from stdin cog build -t $TEST_IMAGE stdin input.json -cog predict $TEST_IMAGE --json @- +cog run $TEST_IMAGE --json @- stdout '"status": "succeeded"' stdout '"output": "hello sackfield"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s -- input.json -- diff --git a/integration-tests/tests/predict_json_stdin_dash.txtar b/integration-tests/tests/predict_json_stdin_dash.txtar index ce7fcd1fcc..7779e482d4 100644 --- a/integration-tests/tests/predict_json_stdin_dash.txtar +++ b/integration-tests/tests/predict_json_stdin_dash.txtar @@ -4,21 +4,21 @@ # Build and run prediction with JSON from stdin using literal '-' cog build -t $TEST_IMAGE stdin input.json -cog predict $TEST_IMAGE --json - +cog run $TEST_IMAGE --json - stdout '"status": "succeeded"' stdout '"output": "hello sackfield"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s -- input.json -- diff --git a/integration-tests/tests/predict_many_inputs_image.txtar b/integration-tests/tests/predict_many_inputs_image.txtar index db4a285ca5..d7ea82913b 100644 --- a/integration-tests/tests/predict_many_inputs_image.txtar +++ b/integration-tests/tests/predict_many_inputs_image.txtar @@ -1,5 +1,5 @@ -# Test predict with many input types against pre-built image +# Test run with many input types against pre-built image # Source: test_predict.py::test_predict_many_inputs_with_existing_image # # This test builds an image first, then runs predictions against that image @@ -10,20 +10,20 @@ cog build -t $TEST_IMAGE # Run prediction against the built image with various input types # Using @ syntax for file inputs -cog predict $TEST_IMAGE -i no_default=hello -i path=@path.txt -i image=@image.jpg -i choices=foo -i int_choices=3 +cog run $TEST_IMAGE -i no_default=hello -i path=@path.txt -i image=@image.jpg -i choices=foo -i int_choices=3 stdout 'hello default 20 world jpg foo 6' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Input, Path +-- run.py -- +from cog import BaseRunner, Input, Path -class Predictor(BasePredictor): - def predict( +class Runner(BaseRunner): + def run( self, no_default: str, default_without_input: str = "default", diff --git a/integration-tests/tests/predict_output_file.txtar b/integration-tests/tests/predict_output_file.txtar index 271a661fb3..7213dcc3b7 100644 --- a/integration-tests/tests/predict_output_file.txtar +++ b/integration-tests/tests/predict_output_file.txtar @@ -3,7 +3,7 @@ # Build and run prediction with custom output filename cog build -t $TEST_IMAGE -cog predict $TEST_IMAGE -o myoutput.bmp +cog run $TEST_IMAGE -o myoutput.bmp # Verify file exists and has non-zero size exists myoutput.bmp @@ -14,18 +14,18 @@ build: python_version: "3.12" python_packages: - "pillow==10.4.0" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import os import tempfile -from cog import BasePredictor, Path +from cog import BaseRunner, Path from PIL import Image -class Predictor(BasePredictor): - def predict(self) -> Path: +class Runner(BaseRunner): + def run(self) -> Path: temp_dir = tempfile.mkdtemp() temp_path = os.path.join(temp_dir, "prediction.bmp") img = Image.new("RGB", (255, 255), "red") diff --git a/integration-tests/tests/predict_output_string.txtar b/integration-tests/tests/predict_output_string.txtar index c0cb39e44f..b934dd3c8d 100644 --- a/integration-tests/tests/predict_output_string.txtar +++ b/integration-tests/tests/predict_output_string.txtar @@ -5,7 +5,7 @@ cog build -t $TEST_IMAGE # Run prediction with -o to write output to file -cog predict $TEST_IMAGE -i s=world -o out.txt +cog run $TEST_IMAGE -i s=world -o out.txt # Verify file contents exec cat out.txt @@ -14,12 +14,12 @@ stdout 'hello world' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/predict_sys_exit.txtar b/integration-tests/tests/predict_sys_exit.txtar index e127f96c4e..97beaf36f9 100644 --- a/integration-tests/tests/predict_sys_exit.txtar +++ b/integration-tests/tests/predict_sys_exit.txtar @@ -1,4 +1,4 @@ -# Test that sys.exit() in predict() fails the prediction but does NOT kill +# Test that sys.exit() in run() fails the prediction but does NOT kill # the worker. A subsequent prediction should still succeed. # # sys.exit() raises SystemExit, which is caught by the PyO3 boundary. @@ -19,16 +19,16 @@ stdout '"output":"still alive"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import sys -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, do_exit: bool = False) -> str: +class Runner(BaseRunner): + def run(self, do_exit: bool = False) -> str: if do_exit: sys.exit(1) return "still alive" diff --git a/integration-tests/tests/prediction_error_response.txtar b/integration-tests/tests/prediction_error_response.txtar index e103dee34f..e5eda52e4b 100644 --- a/integration-tests/tests/prediction_error_response.txtar +++ b/integration-tests/tests/prediction_error_response.txtar @@ -1,24 +1,24 @@ -# Test that a runtime exception in predict() returns a well-formed error response. +# Test that a runtime exception in run() returns a well-formed error response. # -# When predict() raises an exception, coglet returns HTTP 200 with +# When run() raises an exception, coglet returns HTTP 200 with # status "failed", the error message, and predict_time in metrics. # This test verifies the response shape — not just that it fails. cog serve -# ValueError in predict() +# ValueError in run() curl POST /predictions '{"input":{"mode":"value_error"}}' stdout '"status":"failed"' stdout '"error":".*this is a value error"' stdout '"predict_time":' -# RuntimeError in predict() +# RuntimeError in run() curl POST /predictions '{"input":{"mode":"runtime_error"}}' stdout '"status":"failed"' stdout '"error":".*runtime problem"' stdout '"predict_time":' -# Generic Exception in predict() +# Generic Exception in run() curl POST /predictions '{"input":{"mode":"generic"}}' stdout '"status":"failed"' stdout '"error":".*something went wrong"' @@ -26,14 +26,14 @@ stdout '"error":".*something went wrong"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, mode: str) -> str: +class Runner(BaseRunner): + def run(self, mode: str) -> str: if mode == "value_error": raise ValueError("this is a value error") elif mode == "runtime_error": diff --git a/integration-tests/tests/pty_echo.txtar b/integration-tests/tests/pty_echo.txtar index 5078469e9b..d3b6a902f0 100644 --- a/integration-tests/tests/pty_echo.txtar +++ b/integration-tests/tests/pty_echo.txtar @@ -11,11 +11,11 @@ stdout 'hello from cog exec' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str = "world") -> str: +class Runner(BaseRunner): + def run(self, s: str = "world") -> str: return "hello " + s diff --git a/integration-tests/tests/pty_interactive.txtar b/integration-tests/tests/pty_interactive.txtar index 2ce78e6a3e..ac232158cd 100644 --- a/integration-tests/tests/pty_interactive.txtar +++ b/integration-tests/tests/pty_interactive.txtar @@ -12,13 +12,13 @@ stdout 'HELLO_WORLD' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str = "world") -> str: +class Runner(BaseRunner): + def run(self, s: str = "world") -> str: return "hello " + s -- pty_input.txt -- diff --git a/integration-tests/tests/pydantic2.txtar b/integration-tests/tests/pydantic2.txtar index 2a682501ab..289a38914c 100644 --- a/integration-tests/tests/pydantic2.txtar +++ b/integration-tests/tests/pydantic2.txtar @@ -3,8 +3,8 @@ # Build the image cog build -t $TEST_IMAGE -# Predict with Pydantic 2 -cog predict $TEST_IMAGE -i s=world +# Run with Pydantic 2 +cog run $TEST_IMAGE -i s=world stdout 'hello world' -- cog.yaml -- @@ -13,12 +13,12 @@ build: python_version: "3.12" python_packages: - "pydantic>2" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/pydantic2_output.txtar b/integration-tests/tests/pydantic2_output.txtar index 913c4f2a94..a38ddb6deb 100644 --- a/integration-tests/tests/pydantic2_output.txtar +++ b/integration-tests/tests/pydantic2_output.txtar @@ -4,8 +4,8 @@ # Build the image cog build -t $TEST_IMAGE -# Predict returns structured Pydantic output -cog predict $TEST_IMAGE -i name=alice -i score=0.95 +# Run returns structured Pydantic output +cog run $TEST_IMAGE -i name=alice -i score=0.95 stdout '"name": "alice"' stdout '"score": 0.95' stdout '"tags"' @@ -16,14 +16,14 @@ build: python_version: "3.12" python_packages: - "pydantic>2" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- from typing import List from pydantic import BaseModel as PydanticBaseModel -from cog import BasePredictor +from cog import BaseRunner class Result(PydanticBaseModel): @@ -32,6 +32,6 @@ class Result(PydanticBaseModel): tags: List[str] -class Predictor(BasePredictor): - def predict(self, name: str, score: float = 0.5) -> Result: +class Runner(BaseRunner): + def run(self, name: str, score: float = 0.5) -> Result: return Result(name=name, score=score, tags=["default"]) diff --git a/integration-tests/tests/python313.txtar b/integration-tests/tests/python313.txtar index 3aff7e4e43..871b2229a1 100644 --- a/integration-tests/tests/python313.txtar +++ b/integration-tests/tests/python313.txtar @@ -3,20 +3,20 @@ # Build the image cog build -t $TEST_IMAGE -# Predict with Python 3.13 -cog predict $TEST_IMAGE -i num=5 +# Run with Python 3.13 +cog run $TEST_IMAGE -i num=5 stdout '10' -- cog.yaml -- build: gpu: false python_version: "3.13" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, num: int) -> int: +class Runner(BaseRunner): + def run(self, num: int) -> int: return num * 2 diff --git a/integration-tests/tests/python37_deprecated.txtar b/integration-tests/tests/python37_deprecated.txtar index 7c6e253f19..ca0ff285d2 100644 --- a/integration-tests/tests/python37_deprecated.txtar +++ b/integration-tests/tests/python37_deprecated.txtar @@ -7,12 +7,12 @@ stderr 'invalid build.python_version "3.7": minimum supported Python version is build: gpu: false python_version: "3.7" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, num: int) -> int: +class Runner(BaseRunner): + def run(self, num: int) -> int: return num * 2 diff --git a/integration-tests/tests/python38_deprecated.txtar b/integration-tests/tests/python38_deprecated.txtar index f89c38f29e..af710e1fc6 100644 --- a/integration-tests/tests/python38_deprecated.txtar +++ b/integration-tests/tests/python38_deprecated.txtar @@ -7,12 +7,12 @@ stderr 'invalid build.python_version "3.8": minimum supported Python version is build: gpu: false python_version: "3.8" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, num: int) -> int: +class Runner(BaseRunner): + def run(self, num: int) -> int: return num * 2 diff --git a/integration-tests/tests/python39_deprecated.txtar b/integration-tests/tests/python39_deprecated.txtar index 5fcbcc2caa..3d38898671 100644 --- a/integration-tests/tests/python39_deprecated.txtar +++ b/integration-tests/tests/python39_deprecated.txtar @@ -7,12 +7,12 @@ stderr 'invalid build.python_version "3.9": minimum supported Python version is build: gpu: false python_version: "3.9" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, num: int) -> int: +class Runner(BaseRunner): + def run(self, num: int) -> int: return num * 2 diff --git a/integration-tests/tests/scope_context.txtar b/integration-tests/tests/scope_context.txtar index 355ad0bd9a..2d91e34cad 100644 --- a/integration-tests/tests/scope_context.txtar +++ b/integration-tests/tests/scope_context.txtar @@ -1,13 +1,13 @@ # Test that per-prediction context is available via current_scope().context. # # Verifies: -# 1. context dict from request body is accessible in the predictor +# 1. context dict from request body is accessible in the runner # 2. Empty context (default) returns an empty dict # 3. Multiple key-value pairs are preserved cog serve -# Prediction with context — predictor returns sorted key:value pairs +# Prediction with context — runner returns sorted key:value pairs curl POST /predictions '{"input":{},"context":{"api_token":"secret123","region":"us-east-1"}}' stdout '"status":"succeeded"' stdout '"output":"api_token:secret123, region:us-east-1"' @@ -20,14 +20,14 @@ stdout '"output":"context_is_empty=True"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Input, current_scope +-- run.py -- +from cog import BaseRunner, Input, current_scope -class Predictor(BasePredictor): - def predict(self, expect_empty: str = Input(default="false")) -> str: +class Runner(BaseRunner): + def run(self, expect_empty: str = Input(default="false")) -> str: ctx = current_scope().context if expect_empty == "true": diff --git a/integration-tests/tests/secrets.txtar b/integration-tests/tests/secrets.txtar index 09b57adffc..a7cdb86549 100644 --- a/integration-tests/tests/secrets.txtar +++ b/integration-tests/tests/secrets.txtar @@ -28,14 +28,14 @@ build: - type: secret id: env-secret target: /var/env-secret.txt -predict: "predict.py:Predictor" +run: "run.py:Runner" -- file-secret.txt -- file_secret_value --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, num: int) -> int: +class Runner(BaseRunner): + def run(self, num: int) -> int: return num * 2 diff --git a/integration-tests/tests/sequential_state_leak.txtar b/integration-tests/tests/sequential_state_leak.txtar index d783948e99..53ade11011 100644 --- a/integration-tests/tests/sequential_state_leak.txtar +++ b/integration-tests/tests/sequential_state_leak.txtar @@ -26,16 +26,16 @@ stdout '"output":"\[.a., .b., .c.\]"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner # Module-level state — persists across predictions in the same worker _state: list = [] -class Predictor(BasePredictor): - def predict(self, item: str) -> str: +class Runner(BaseRunner): + def run(self, item: str) -> str: _state.append(item) return str(_state) diff --git a/integration-tests/tests/setup_slow_serial.txtar b/integration-tests/tests/setup_slow_serial.txtar index 0b1521d803..f262c70764 100644 --- a/integration-tests/tests/setup_slow_serial.txtar +++ b/integration-tests/tests/setup_slow_serial.txtar @@ -17,19 +17,19 @@ stdout '"output":"hello hello"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import time -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self) -> None: print("Starting slow setup...") time.sleep(15) print("Slow setup complete.") - def predict(self, s: str) -> str: + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/setup_subprocess_double_fork.txtar b/integration-tests/tests/setup_subprocess_double_fork.txtar index a11caed189..53e4b39096 100644 --- a/integration-tests/tests/setup_subprocess_double_fork.txtar +++ b/integration-tests/tests/setup_subprocess_double_fork.txtar @@ -22,21 +22,21 @@ stdout '"output":"hello friendo3"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import os.path import signal import subprocess import sys import time -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): """ - This predictor checks the case where a process is spawned during setup and then each + This runner checks the case where a process is spawned during setup and then each prediction depends on being able to communicate with that process. In the event that stream redirection is not working correctly, the forked process will not be able to write to stdout/stderr and will likely exit. Any state other than "running" is @@ -44,7 +44,7 @@ class Predictor(BasePredictor): serving. This variant runs a forked python process via a shell wrapper to which a "message" is - sent via file for each call to `predict`. + sent via file for each call to `run`. """ def setup(self) -> None: @@ -54,7 +54,7 @@ class Predictor(BasePredictor): print(f"---> started background process pid={self.bg.pid}") - def predict(self, s: str) -> str: + def run(self, s: str) -> str: status = self.bg.poll() print(f"---> background job status={status}") diff --git a/integration-tests/tests/setup_subprocess_double_fork_http.txtar b/integration-tests/tests/setup_subprocess_double_fork_http.txtar index 77693397be..58a1766bc0 100644 --- a/integration-tests/tests/setup_subprocess_double_fork_http.txtar +++ b/integration-tests/tests/setup_subprocess_double_fork_http.txtar @@ -24,21 +24,21 @@ build: python_version: "3.12" python_packages: - requests -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import signal import subprocess import sys import requests -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): """ - This predictor checks the case where a process is spawned during setup and then each + This runner checks the case where a process is spawned during setup and then each prediction depends on being able to communicate with that process. In the event that stream redirection is not working correctly, the forked process will not be able to write to stdout/stderr and will likely exit. Any state other than "running" is @@ -46,7 +46,7 @@ class Predictor(BasePredictor): serving. This variant runs a forked python HTTP server via a shell wrapper to which a request - is made during each call to `predict`. + is made during each call to `run`. """ def setup(self) -> None: @@ -69,7 +69,7 @@ class Predictor(BasePredictor): else: raise RuntimeError("Background HTTP server failed to start") - def predict(self, s: str) -> str: + def run(self, s: str) -> str: status = self.bg.poll() print(f"---> background job status={status}") diff --git a/integration-tests/tests/setup_subprocess_multiprocessing.txtar b/integration-tests/tests/setup_subprocess_multiprocessing.txtar index e60a568cfd..4f9112f86c 100644 --- a/integration-tests/tests/setup_subprocess_multiprocessing.txtar +++ b/integration-tests/tests/setup_subprocess_multiprocessing.txtar @@ -22,9 +22,9 @@ stdout 'received .* from background job' -- cog.yaml -- build: python_version: "3.10" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import atexit import multiprocessing import pathlib @@ -34,7 +34,7 @@ import sys import time from cog.types import Path -from cog import BasePredictor +from cog import BaseRunner from bg import ponger @@ -47,9 +47,9 @@ def cleanup(): atexit.register(cleanup) -class Predictor(BasePredictor): +class Runner(BaseRunner): """ - This predictor checks the case where a process is spawned during setup via + This runner checks the case where a process is spawned during setup via multiprocessing and then each prediction causes that process to write to stdout. """ @@ -67,7 +67,7 @@ class Predictor(BasePredictor): print(f"---> started background process pid={self.bg.pid}") - def predict(self, s: str) -> Path: + def run(self, s: str) -> Path: if self.bg.is_alive(): print(f"---> sending ping to background job pid={self.bg.pid}") diff --git a/integration-tests/tests/setup_subprocess_simple.txtar b/integration-tests/tests/setup_subprocess_simple.txtar index 06f1d2fda7..eba59c3275 100644 --- a/integration-tests/tests/setup_subprocess_simple.txtar +++ b/integration-tests/tests/setup_subprocess_simple.txtar @@ -23,26 +23,26 @@ stdout '"output":"hello friendo3"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import signal import subprocess import sys -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): """ - This predictor checks the case where a process is spawned during setup and then each + This runner checks the case where a process is spawned during setup and then each prediction causes that process to write to stdout. In the event that stream redirection is not working correctly, the forked process will not be able to write to stdout/stderr and will likely exit. Any state other than "running" is considered an error condition and raises SystemExit to interrupt any more prediction serving. This variant runs a simple subprocess to which SIGUSR1 is sent during each call to - `predict`. + `run`. """ def setup(self) -> None: @@ -52,7 +52,7 @@ class Predictor(BasePredictor): print(f"---> started background process pid={self.bg.pid}") - def predict(self, s: str) -> str: + def run(self, s: str) -> str: status = self.bg.poll() if status is None: @@ -72,7 +72,7 @@ class Predictor(BasePredictor): #!/usr/bin/env bash set -euo pipefail -# This _pong function and associated trap ensures that any SIGUSR1 sent during `predict` +# This _pong function and associated trap ensures that any SIGUSR1 sent during `run` # will cause this process to write a decent amount of text to stdout. In the event that # stream redirection is not working correctly, this process will likely be in a defunct # state before the first SIGUSR1 can be sent. diff --git a/integration-tests/tests/setup_timeout_serial.txtar b/integration-tests/tests/setup_timeout_serial.txtar index 30149bfba4..9062b30995 100644 --- a/integration-tests/tests/setup_timeout_serial.txtar +++ b/integration-tests/tests/setup_timeout_serial.txtar @@ -16,21 +16,21 @@ stdout 'SETUP_FAILED' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" environment: - COG_SETUP_TIMEOUT=10 --- predict.py -- +-- run.py -- import time -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self) -> None: print("Starting slow setup...") time.sleep(15) print("Slow setup complete.") - def predict(self, s: str) -> str: + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/setup_worker_tracing_logs.txtar b/integration-tests/tests/setup_worker_tracing_logs.txtar index 76a7014f87..fabb5e8977 100644 --- a/integration-tests/tests/setup_worker_tracing_logs.txtar +++ b/integration-tests/tests/setup_worker_tracing_logs.txtar @@ -29,21 +29,21 @@ build: python_version: "3.12" python_packages: - requests -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- import signal import subprocess import sys import requests -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): +class Runner(BaseRunner): """ - This predictor spawns a double-forked HTTP server during setup to test + This runner spawns a double-forked HTTP server during setup to test that worker tracing logs are properly captured in setuplog. """ @@ -67,7 +67,7 @@ class Predictor(BasePredictor): else: raise RuntimeError("Background HTTP server failed to start") - def predict(self, s: str) -> str: + def run(self, s: str) -> str: status = self.bg.poll() if status is None: diff --git a/integration-tests/tests/static_schema_fallback.txtar b/integration-tests/tests/static_schema_fallback.txtar index 6159f79185..78aef2d0e3 100644 --- a/integration-tests/tests/static_schema_fallback.txtar +++ b/integration-tests/tests/static_schema_fallback.txtar @@ -2,7 +2,7 @@ # unresolvable output type, the build falls back to legacy runtime schema # generation instead of failing. # -# The predictor returns a BaseModel subclass imported from a local package +# The runner returns a BaseModel subclass imported from a local package # (mypackage/__init__.py). The static parser's moduleToFilePath() converts # "mypackage" to "mypackage.py" instead of "mypackage/__init__.py", so the # file isn't found and the name stays unresolved (ErrUnresolvableType). @@ -18,14 +18,14 @@ cog build -t $TEST_IMAGE stderr 'Static schema generation failed' stderr 'Falling back to legacy runtime schema generation' -# Predict should still work end-to-end via the legacy schema -cog predict $TEST_IMAGE -i prompt=hello +# Run should still work end-to-end via the legacy schema +cog run $TEST_IMAGE -i prompt=hello stdout 'hello world' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" -- mypackage/__init__.py -- from cog import BaseModel @@ -34,11 +34,11 @@ from cog import BaseModel class Output(BaseModel): text: str --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner from mypackage import Output -class Predictor(BasePredictor): - def predict(self, prompt: str) -> Output: +class Runner(BaseRunner): + def run(self, prompt: str) -> Output: return Output(text=prompt + " world") diff --git a/integration-tests/tests/static_schema_gen.txtar b/integration-tests/tests/static_schema_gen.txtar index ccae65e7fe..c373d4151c 100644 --- a/integration-tests/tests/static_schema_gen.txtar +++ b/integration-tests/tests/static_schema_gen.txtar @@ -2,7 +2,7 @@ # OpenAPI schema that is embedded in the Docker image label and served by coglet. # # This exercises the full pipeline: -# 1. cog build runs the Go parser on predict.py +# 1. cog build runs the Go parser on run.py # 2. Schema is written to .cog/openapi_schema.json inside the image # 3. Schema is embedded as the run.cog.openapi_schema Docker label # 4. coglet loads the schema from disk and serves it at /openapi.json @@ -40,25 +40,25 @@ stdout '"required":\["text","image"\]' # Output type stdout '"type":"string"' -# Predict should work end-to-end -cog predict $TEST_IMAGE -i text=hello -i image=@test.jpg +# Run should work end-to-end +cog run $TEST_IMAGE -i text=hello -i image=@test.jpg stdout 'hello-5-fast-jpg' # Prediction with overrides -cog predict $TEST_IMAGE -i text=world -i count=3 -i mode=quality -i image=@test.jpg +cog run $TEST_IMAGE -i text=world -i count=3 -i mode=quality -i image=@test.jpg stdout 'world-3-quality-jpg' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Input, Path +-- run.py -- +from cog import BaseRunner, Input, Path -class Predictor(BasePredictor): - def predict( +class Runner(BaseRunner): + def run( self, text: str, image: Path, diff --git a/integration-tests/tests/string_input_url_list_not_coerced.txtar b/integration-tests/tests/string_input_url_list_not_coerced.txtar index 791bd2e9fa..de65abdab2 100644 --- a/integration-tests/tests/string_input_url_list_not_coerced.txtar +++ b/integration-tests/tests/string_input_url_list_not_coerced.txtar @@ -1,8 +1,8 @@ # Regression test for https://github.com/replicate/cog/issues/2868 # -# When a predictor declares a `list[str]`-typed input and the caller passes +# When a runner declares a `list[str]`-typed input and the caller passes # URL strings, coglet should NOT coerce them into cog.Path or cog.File -# objects. The predictor should receive a plain list of strings. +# objects. The runner should receive a plain list of strings. cog build -t $TEST_IMAGE @@ -16,15 +16,15 @@ stdout 'types=str,str' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- +-- run.py -- from typing import List -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, urls: List[str]) -> str: +class Runner(BaseRunner): + def run(self, urls: List[str]) -> str: types = ",".join(type(u).__name__ for u in urls) return f"types={types}" diff --git a/integration-tests/tests/string_input_url_not_coerced.txtar b/integration-tests/tests/string_input_url_not_coerced.txtar index 410ac62a27..948db2fa75 100644 --- a/integration-tests/tests/string_input_url_not_coerced.txtar +++ b/integration-tests/tests/string_input_url_not_coerced.txtar @@ -1,8 +1,8 @@ # Regression test for https://github.com/replicate/cog/issues/2868 # -# When a predictor declares a `str`-typed input and the caller passes a URL +# When a runner declares a `str`-typed input and the caller passes a URL # string (e.g. "https://example.com/api"), coglet should NOT coerce it into -# a cog.Path or cog.File object. The predictor should receive the raw string. +# a cog.Path or cog.File object. The runner should receive the raw string. # # The bug: coerce_url_strings() in coglet runs on ALL input fields, not just # those annotated as File or Path. Any string starting with "http://", @@ -10,10 +10,10 @@ cog build -t $TEST_IMAGE -# --- Test via cog predict (CLI) --- +# --- Test via cog run (CLI) --- # A plain URL passed to a str-typed input should be returned verbatim. -cog predict $TEST_IMAGE -i url=https://api.example.com/v1/resource +cog run $TEST_IMAGE -i url=https://api.example.com/v1/resource stdout 'type=str value=https://api.example.com/v1/resource' # --- Test via cog serve (HTTP API) --- @@ -28,12 +28,12 @@ stdout 'type=str value=https://api.example.com/v1/resource' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, url: str) -> str: +class Runner(BaseRunner): + def run(self, url: str) -> str: return f"type={type(url).__name__} value={url}" diff --git a/integration-tests/tests/string_list_input.txtar b/integration-tests/tests/string_list_input.txtar index 342a6bb585..abd7b42a8a 100644 --- a/integration-tests/tests/string_list_input.txtar +++ b/integration-tests/tests/string_list_input.txtar @@ -3,19 +3,19 @@ # Build the image cog build -t $TEST_IMAGE -# Predict with list input -cog predict $TEST_IMAGE -i s=world +# Run with list input +cog run $TEST_IMAGE -i s=world stdout 'hello world' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Input +-- run.py -- +from cog import BaseRunner, Input -class Predictor(BasePredictor): - def predict(self, s: list[str] = Input(description="A list of strings to print.")) -> str: +class Runner(BaseRunner): + def run(self, s: list[str] = Input(description="A list of strings to print.")) -> str: return "hello " + "|".join(s) diff --git a/integration-tests/tests/string_none_output.txtar b/integration-tests/tests/string_none_output.txtar index 3c86cb269e..b799c0549b 100644 --- a/integration-tests/tests/string_none_output.txtar +++ b/integration-tests/tests/string_none_output.txtar @@ -5,20 +5,20 @@ # Build the image cog build -t $TEST_IMAGE -# Predict returns None despite str type annotation +# Run returns None despite str type annotation # When None is returned, cog shows "No output generated" on stderr -cog predict $TEST_IMAGE +cog run $TEST_IMAGE stderr 'No output generated' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self) -> str: +class Runner(BaseRunner): + def run(self) -> str: return None diff --git a/integration-tests/tests/string_predictor.txtar b/integration-tests/tests/string_predictor.txtar index bfc428fe71..560b3de560 100644 --- a/integration-tests/tests/string_predictor.txtar +++ b/integration-tests/tests/string_predictor.txtar @@ -3,21 +3,21 @@ cog build -t $TEST_IMAGE # Basic prediction works -cog predict $TEST_IMAGE -i s=world +cog run $TEST_IMAGE -i s=world stdout 'hello world' # Missing required input fails -! cog predict $TEST_IMAGE -i wrong=value +! cog run $TEST_IMAGE -i wrong=value stderr 's: Field required' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/subdirectory_predictor.txtar b/integration-tests/tests/subdirectory_predictor.txtar index 277789b75a..6faa4e8b4e 100644 --- a/integration-tests/tests/subdirectory_predictor.txtar +++ b/integration-tests/tests/subdirectory_predictor.txtar @@ -1,27 +1,27 @@ -# Test predictor in subdirectory with imports from parent +# Test runner in subdirectory with imports from parent # Build the image cog build -t $TEST_IMAGE -# Predict using predictor in subdirectory -cog predict $TEST_IMAGE -i s=world +# Run using runner in subdirectory +cog run $TEST_IMAGE -i s=world stdout 'hello world' -- cog.yaml -- build: python_version: "3.12" -predict: "my-subdir/predict.py:Predictor" +run: "my-subdir/run.py:Runner" -- mylib.py -- def concat(a, b): return a + " " + b --- my-subdir/predict.py -- -from cog import BasePredictor +-- my-subdir/run.py -- +from cog import BaseRunner from mylib import concat -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return concat("hello", s) diff --git a/integration-tests/tests/tensorflow.txtar b/integration-tests/tests/tensorflow.txtar index 8948352e9f..cab2d3491a 100644 --- a/integration-tests/tests/tensorflow.txtar +++ b/integration-tests/tests/tensorflow.txtar @@ -1,8 +1,8 @@ [short] skip 'slow test - run without -short flag' -# Test TensorFlow build and predict +# Test TensorFlow build and run cog build -t $TEST_IMAGE -cog predict $TEST_IMAGE +cog run $TEST_IMAGE stdout '2.11.1' -- cog.yaml -- @@ -15,16 +15,16 @@ build: - "libglib2.0-0" - "xvfb" python_requirements: "requirements.txt" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner import tensorflow -class Predictor(BasePredictor): - def predict(self) -> str: +class Runner(BaseRunner): + def run(self) -> str: return tensorflow.__version__ -- requirements.txt -- diff --git a/integration-tests/tests/torch_270_cuda_126.txtar b/integration-tests/tests/torch_270_cuda_126.txtar index 338b4c5bd1..9d88800c34 100644 --- a/integration-tests/tests/torch_270_cuda_126.txtar +++ b/integration-tests/tests/torch_270_cuda_126.txtar @@ -10,12 +10,12 @@ build: python_version: "3.11" python_packages: - "torch==2.7.0" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/torch_271_cuda_128.txtar b/integration-tests/tests/torch_271_cuda_128.txtar index fe38a93552..a55c49e64d 100644 --- a/integration-tests/tests/torch_271_cuda_128.txtar +++ b/integration-tests/tests/torch_271_cuda_128.txtar @@ -10,12 +10,12 @@ build: python_version: "3.12" python_packages: - "torch==2.7.1+cu128" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/torch_baseimage_fallback.txtar b/integration-tests/tests/torch_baseimage_fallback.txtar index e4e575b702..832e1d16e2 100644 --- a/integration-tests/tests/torch_baseimage_fallback.txtar +++ b/integration-tests/tests/torch_baseimage_fallback.txtar @@ -16,12 +16,12 @@ build: python_version: "3.10" python_packages: - "torch==1.13.0" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/torch_baseimage_no_cog_base.txtar b/integration-tests/tests/torch_baseimage_no_cog_base.txtar index 21b59ea413..fc5c2a6912 100644 --- a/integration-tests/tests/torch_baseimage_no_cog_base.txtar +++ b/integration-tests/tests/torch_baseimage_no_cog_base.txtar @@ -10,14 +10,14 @@ build: python_version: "3.10" python_packages: - "torch==1.13.0" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s -- openapi.json -- diff --git a/integration-tests/tests/torch_baseimage_precompile.txtar b/integration-tests/tests/torch_baseimage_precompile.txtar index 478bbb6a45..0cfab951e5 100644 --- a/integration-tests/tests/torch_baseimage_precompile.txtar +++ b/integration-tests/tests/torch_baseimage_precompile.txtar @@ -10,14 +10,14 @@ build: python_version: "3.10" python_packages: - "torch==1.13.0" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s -- openapi.json -- diff --git a/integration-tests/tests/torch_cuda_baseimage.txtar b/integration-tests/tests/torch_cuda_baseimage.txtar index 7846c7573b..d28474fb07 100644 --- a/integration-tests/tests/torch_cuda_baseimage.txtar +++ b/integration-tests/tests/torch_cuda_baseimage.txtar @@ -13,12 +13,12 @@ build: python_version: "3.10" python_packages: - "torch==2.0.1+cu118" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/train_basic.txtar b/integration-tests/tests/train_basic.txtar index a3fcbb8ed4..97b60f4f1b 100644 --- a/integration-tests/tests/train_basic.txtar +++ b/integration-tests/tests/train_basic.txtar @@ -13,15 +13,15 @@ stdout '42' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" train: "train.py:train" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s -- train.py -- diff --git a/integration-tests/tests/train_deprecated.txtar b/integration-tests/tests/train_deprecated.txtar index 58612f733d..54c4b26c6c 100644 --- a/integration-tests/tests/train_deprecated.txtar +++ b/integration-tests/tests/train_deprecated.txtar @@ -5,9 +5,9 @@ stderr 'Command "train" is deprecated' stderr 'will be removed in a future version of Cog' --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/training_setup.txtar b/integration-tests/tests/training_setup.txtar index 60ea55a197..b53a192b65 100644 --- a/integration-tests/tests/training_setup.txtar +++ b/integration-tests/tests/training_setup.txtar @@ -14,12 +14,12 @@ stdout 'hello train world' build: python_version: "3.12" train: "train.py:Trainer" -predict: "predict.py:Predictor" +run: "run.py:Runner" -- train.py -- -from cog import BasePredictor +from cog import BaseRunner -class Trainer(BasePredictor): +class Trainer(BaseRunner): def setup(self) -> None: print("Trainer is setting up.") @@ -27,13 +27,13 @@ class Trainer(BasePredictor): print("Trainer.train called.") return "hello train " + s - def predict(self, s: str) -> str: - print("Trainer.predict called.") - return "hello predict " + s + def run(self, s: str) -> str: + print("Trainer.run called.") + return "hello run " + s --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/union_type.txtar b/integration-tests/tests/union_type.txtar index 4e81aeb3e1..aeaaf61705 100644 --- a/integration-tests/tests/union_type.txtar +++ b/integration-tests/tests/union_type.txtar @@ -3,24 +3,24 @@ # Build the image cog build -t $TEST_IMAGE -# Predict with input -cog predict $TEST_IMAGE -i text=world +# Run with input +cog run $TEST_IMAGE -i text=world stdout 'hello world' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor, Input +-- run.py -- +from cog import BaseRunner, Input -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): self.prefix = "hello" - def predict( + def run( self, text: str | None = Input( description="Text to prefix with 'hello '", default=None diff --git a/integration-tests/tests/webhook_delivery_failure.txtar b/integration-tests/tests/webhook_delivery_failure.txtar index eec4dae35f..6f6e95ff5a 100644 --- a/integration-tests/tests/webhook_delivery_failure.txtar +++ b/integration-tests/tests/webhook_delivery_failure.txtar @@ -22,12 +22,12 @@ stdout '"output":"ok"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self) -> str: +class Runner(BaseRunner): + def run(self) -> str: return "ok" diff --git a/integration-tests/tests/webhook_prediction_error.txtar b/integration-tests/tests/webhook_prediction_error.txtar index 06ce6fa432..1cc14dbc92 100644 --- a/integration-tests/tests/webhook_prediction_error.txtar +++ b/integration-tests/tests/webhook_prediction_error.txtar @@ -1,6 +1,6 @@ # Test that a failed prediction delivers the correct webhook payload. # -# When predict() raises an exception during an async prediction, the webhook +# When run() raises an exception during an async prediction, the webhook # should receive status "failed" with the error message populated. webhook-server-start @@ -19,12 +19,12 @@ stdout '"error_message":".*prediction went wrong"' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self) -> str: +class Runner(BaseRunner): + def run(self) -> str: raise RuntimeError("prediction went wrong") diff --git a/integration-tests/tests/weights_build.txtar b/integration-tests/tests/weights_build.txtar index b20f646666..4d4e46c492 100644 --- a/integration-tests/tests/weights_build.txtar +++ b/integration-tests/tests/weights_build.txtar @@ -25,20 +25,20 @@ exec grep -q '"digestOriginal": "sha256:' weights.lock -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" -- cog-with-weights.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" weights: - name: model source: models/model.bin target: /cache/model.bin --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/weights_push_inspect.txtar b/integration-tests/tests/weights_push_inspect.txtar index 9747f3ff21..b99960a858 100644 --- a/integration-tests/tests/weights_push_inspect.txtar +++ b/integration-tests/tests/weights_push_inspect.txtar @@ -49,7 +49,7 @@ stderr 'includes a tag or digest' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" weights: - name: alpha source: weights/model-a.bin @@ -58,9 +58,9 @@ weights: source: weights/model-b.bin target: /weights/model-b.bin --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/wheel_coglet_missing.txtar b/integration-tests/tests/wheel_coglet_missing.txtar index 4ef6a8a711..38e6740f8c 100644 --- a/integration-tests/tests/wheel_coglet_missing.txtar +++ b/integration-tests/tests/wheel_coglet_missing.txtar @@ -12,11 +12,11 @@ stderr '/nonexistent/path/coglet.whl' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/wheel_resolution.txtar b/integration-tests/tests/wheel_resolution.txtar index d7fea4482b..416246a891 100644 --- a/integration-tests/tests/wheel_resolution.txtar +++ b/integration-tests/tests/wheel_resolution.txtar @@ -21,11 +21,11 @@ stderr 'path not found' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s diff --git a/integration-tests/tests/zsh_package.txtar b/integration-tests/tests/zsh_package.txtar index 54ac6322eb..68f8fa3681 100644 --- a/integration-tests/tests/zsh_package.txtar +++ b/integration-tests/tests/zsh_package.txtar @@ -1,7 +1,7 @@ # Test that zsh system package is installed and available in /bin cog build -t $TEST_IMAGE -cog predict $TEST_IMAGE +cog run $TEST_IMAGE stdout ',sh,' stdout ',zsh,' @@ -10,12 +10,12 @@ build: python_version: "3.12" system_packages: - "zsh" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner import os -class Predictor(BasePredictor): - def predict(self) -> str: +class Runner(BaseRunner): + def run(self) -> str: return "hello " + ",".join(os.listdir("/bin")) From ce50efd161b2b17257b0069ef3e303a4f27a7b72 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 15:41:39 -0400 Subject: [PATCH 08/18] test: add backwards compatibility integration tests Verify old names still work: - predict: key in cog.yaml with Runner/run() - BasePredictor/predict() with run: key - Full legacy setup (predict: key + BasePredictor/predict()) - cog predict CLI command deprecation warning - cog run forwarding to exec with deprecation warning --- .../backwards_compat_base_predictor.txtar | 22 +++++++++++++++++ .../tests/backwards_compat_cog_predict.txtar | 24 +++++++++++++++++++ .../tests/backwards_compat_cog_run_exec.txtar | 12 ++++++++++ .../tests/backwards_compat_full_legacy.txtar | 22 +++++++++++++++++ .../tests/backwards_compat_predict_key.txtar | 22 +++++++++++++++++ 5 files changed, 102 insertions(+) create mode 100644 integration-tests/tests/backwards_compat_base_predictor.txtar create mode 100644 integration-tests/tests/backwards_compat_cog_predict.txtar create mode 100644 integration-tests/tests/backwards_compat_cog_run_exec.txtar create mode 100644 integration-tests/tests/backwards_compat_full_legacy.txtar create mode 100644 integration-tests/tests/backwards_compat_predict_key.txtar diff --git a/integration-tests/tests/backwards_compat_base_predictor.txtar b/integration-tests/tests/backwards_compat_base_predictor.txtar new file mode 100644 index 0000000000..a98f1a7dc7 --- /dev/null +++ b/integration-tests/tests/backwards_compat_base_predictor.txtar @@ -0,0 +1,22 @@ +# Test backwards compat: run: key with BasePredictor/predict() +# The old BasePredictor class with predict() method should work +# when referenced via the new run: key in cog.yaml. + +# Build the image +cog build -t $TEST_IMAGE + +# Basic prediction works +cog run $TEST_IMAGE -i name=world +stdout 'hello world' + +-- cog.yaml -- +build: + python_version: "3.12" +run: "predict.py:Predictor" + +-- predict.py -- +from cog import BasePredictor, Input + +class Predictor(BasePredictor): + def predict(self, name: str = Input(description="Name")) -> str: + return f"hello {name}" diff --git a/integration-tests/tests/backwards_compat_cog_predict.txtar b/integration-tests/tests/backwards_compat_cog_predict.txtar new file mode 100644 index 0000000000..ab0013cf18 --- /dev/null +++ b/integration-tests/tests/backwards_compat_cog_predict.txtar @@ -0,0 +1,24 @@ +# Test backwards compat: cog predict CLI command +# "cog predict" is a deprecated alias for "cog run" and should +# show a deprecation warning while still producing correct output. + +# Build the image +cog build -t $TEST_IMAGE + +# cog predict should work but emit a deprecation warning +cog predict $TEST_IMAGE -i name=world +stderr 'deprecated' +stderr 'cog run' +stdout 'hello world' + +-- cog.yaml -- +build: + python_version: "3.12" +run: "run.py:Runner" + +-- run.py -- +from cog import BaseRunner, Input + +class Runner(BaseRunner): + def run(self, name: str = Input(description="Name")) -> str: + return f"hello {name}" diff --git a/integration-tests/tests/backwards_compat_cog_run_exec.txtar b/integration-tests/tests/backwards_compat_cog_run_exec.txtar new file mode 100644 index 0000000000..7f55181fa5 --- /dev/null +++ b/integration-tests/tests/backwards_compat_cog_run_exec.txtar @@ -0,0 +1,12 @@ +# Test backwards compat: cog run with 2+ args forwards to exec +# When cog run is called with 2+ arguments, it should forward +# to exec with a deprecation warning. + +cog run echo hello world +stderr 'deprecated' +stderr 'cog exec' +stdout 'hello world' + +-- cog.yaml -- +build: + python_version: "3.12" diff --git a/integration-tests/tests/backwards_compat_full_legacy.txtar b/integration-tests/tests/backwards_compat_full_legacy.txtar new file mode 100644 index 0000000000..81c5120376 --- /dev/null +++ b/integration-tests/tests/backwards_compat_full_legacy.txtar @@ -0,0 +1,22 @@ +# Test backwards compat: fully legacy setup +# Both the predict: key in cog.yaml and BasePredictor/predict() +# class style should still work together. + +# Build the image +cog build -t $TEST_IMAGE + +# Basic prediction works +cog run $TEST_IMAGE -i name=world +stdout 'hello world' + +-- cog.yaml -- +build: + python_version: "3.12" +predict: "predict.py:Predictor" + +-- predict.py -- +from cog import BasePredictor, Input + +class Predictor(BasePredictor): + def predict(self, name: str = Input(description="Name")) -> str: + return f"hello {name}" diff --git a/integration-tests/tests/backwards_compat_predict_key.txtar b/integration-tests/tests/backwards_compat_predict_key.txtar new file mode 100644 index 0000000000..c4d642f7d0 --- /dev/null +++ b/integration-tests/tests/backwards_compat_predict_key.txtar @@ -0,0 +1,22 @@ +# Test backwards compat: predict: key in cog.yaml with Runner/run() +# The predict: key is a deprecated alias for run: in cog.yaml. +# It should still work with the new Runner/run() class style. + +# Build the image +cog build -t $TEST_IMAGE + +# Basic prediction works +cog run $TEST_IMAGE -i name=world +stdout 'hello world' + +-- cog.yaml -- +build: + python_version: "3.12" +predict: "run.py:Runner" + +-- run.py -- +from cog import BaseRunner, Input + +class Runner(BaseRunner): + def run(self, name: str = Input(description="Name")) -> str: + return f"hello {name}" From 311a07d5b178a486b1f1fd8ffc29c2020027a044 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 17:14:20 -0400 Subject: [PATCH 09/18] docs: update all docs to use run/Runner/BaseRunner as primary - All examples now use cog run, run:, BaseRunner, def run() - Backwards compatibility notes for predict:/BasePredictor/cog predict - Training examples: removed unused BasePredictor imports - AGENTS.md: fixed incorrect base_predictor.py references to predictor.py --- AGENTS.md | 8 +- CONTRIBUTING.md | 24 ++-- README.md | 14 +- docs/getting-started-own-model.md | 22 +-- docs/getting-started.md | 18 +-- docs/http.md | 20 +-- docs/notebooks.md | 12 +- docs/python.md | 225 +++++++++++++++--------------- docs/training.md | 12 +- docs/yaml.md | 17 ++- 10 files changed, 189 insertions(+), 183 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6950169fe5..8a3d02403e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ Cog is a tool that packages machine learning models in production-ready containe It consists of: - **Cog CLI** (`cmd/cog/`) - Command-line interface for building, running, and deploying models, written in Go -- **Python SDK** (`python/cog/`) - Python library for defining model predictors and training in Python +- **Python SDK** (`python/cog/`) - Python library for defining model runners and training in Python - **Coglet** (`crates/`) - Rust-based prediction server that runs inside containers, with Python bindings via PyO3 Documentation for the CLI and SDK is available by reading ./docs/llms.txt. @@ -159,7 +159,7 @@ For detailed architecture documentation, see `crates/README.md` and `crates/cogl The main commands for working on Coglet are: - `mise run build:coglet` - Build and install coglet wheel for development (macOS, for local Rust/Python tests) -- `mise run build:coglet:wheel:linux-x64` - Build Linux x86_64 wheel (required to test Rust changes in Docker containers via `cog predict`/`cog train`) +- `mise run build:coglet:wheel:linux-x64` - Build Linux x86_64 wheel (required to test Rust changes in Docker containers via `cog run`/`cog train`) - `mise run test:rust` - Run Rust unit tests - `mise run lint:rust` - Run clippy linter - `mise run fmt:rust:fix` - Format code @@ -207,7 +207,7 @@ The CLI follows a command pattern with subcommands. The main components are: ### Python SDK Architecture - `python/cog/` - Core SDK - - `base_predictor.py` - Base class for model predictors + - `predictor.py` - Base classes for model runners (`BaseRunner`) and predictors (`BasePredictor`) - `types.py` - Input/output type definitions - `server/` - HTTP/queue server implementation - `command/` - Runner implementations for predict/train @@ -281,7 +281,7 @@ Tools disabled in CI are listed in `MISE_DISABLE_TOOLS` in `ci.yaml`. - `cog.yaml` - User-facing model configuration - `pkg/config/config.go` - Go code for parsing and validating `cog.yaml` - `pkg/config/data/config_schema_v1.0.json` - JSON schema for `cog.yaml` -- `python/cog/base_predictor.py` - Predictor interface +- `python/cog/predictor.py` - Runner/Predictor interface (BaseRunner, BasePredictor) - `crates/Cargo.toml` - Rust workspace configuration (version must match VERSION.txt) - `crates/README.md` - Coglet architecture overview - `mise.toml` - Task definitions for development workflow diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ff61280fe..88149b0808 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -121,7 +121,7 @@ There are a few concepts used throughout Cog that might be helpful to understand - **Model**: A user's machine learning model, consisting of code and weights. - **Output**: Output from a **prediction**, as arbitrarily complex JSON object. - **Prediction**: A single run of the model, that takes **input** and produces **output**. -- **Predictor**: Defines how Cog runs **predictions** on a **model**. +- **Runner**: Defines how Cog runs **predictions** on a **model**. ## Running tests @@ -176,21 +176,21 @@ When adding new functionality, add integration tests in `integration-tests/tests Example test structure: ```txtar -# Test string predictor +# Test string runner cog build -t $TEST_IMAGE -cog predict $TEST_IMAGE -i s=world +cog run $TEST_IMAGE -i s=world stdout 'hello world' -- cog.yaml -- build: python_version: "3.12" -predict: "predict.py:Predictor" +run: "run.py:Runner" --- predict.py -- -from cog import BasePredictor +-- run.py -- +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, s: str) -> str: +class Runner(BaseRunner): + def run(self, s: str) -> str: return "hello " + s ``` @@ -216,7 +216,7 @@ retry-curl POST /predictions '{"input":{"s":"test"}}' 30 1s stdout '"output":"hello test"' ``` -**Example: Testing predictor with subprocess in setup** +**Example: Testing runner with subprocess in setup** ```txtar cog build -t $TEST_IMAGE @@ -226,12 +226,12 @@ cog serve retry-curl POST /predictions '{"input":{"s":"test"}}' 30 1s stdout '"output":"hello test"' --- predict.py -- -class Predictor(BasePredictor): +-- run.py -- +class Runner(BaseRunner): def setup(self): self.process = subprocess.Popen(["./background.sh"]) - def predict(self, s: str) -> str: + def run(self, s: str) -> str: return "hello " + s ``` diff --git a/README.md b/README.md index 066723d3a6..ae37e76ed3 100644 --- a/README.md +++ b/README.md @@ -28,22 +28,22 @@ build: - "libglib2.0-0" python_version: "3.13" python_requirements: requirements.txt -predict: "predict.py:Predictor" +run: "run.py:Runner" ``` -Define how predictions are run on your model with `predict.py`: +Define how predictions are run on your model with `run.py`: ```python -from cog import BasePredictor, Input, Path +from cog import BaseRunner, Input, Path import torch -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): """Load the model into memory to make running multiple predictions efficient""" self.model = torch.load("./weights.pth") # The arguments and types the model takes as input - def predict(self, + def run(self, image: Path = Input(description="Grayscale input image") ) -> Path: """Run a single prediction on the model""" @@ -57,7 +57,7 @@ In the above we accept a path to the image as an input, and return a path to our Now, you can run predictions on this model: ```console -$ cog predict -i image=@input.jpg +$ cog run -i image=@input.jpg --> Building Docker image... --> Running Prediction... --> Output written to output.jpg @@ -180,7 +180,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for how to set up a development environme - [Take a look at some examples of using Cog](https://github.com/replicate/cog-examples) - [Deploy models with Cog](docs/deploy.md) - [`cog.yaml` reference](docs/yaml.md) to learn how to define your model's environment -- [Prediction interface reference](docs/python.md) to learn how the `Predictor` interface works +- [Prediction interface reference](docs/python.md) to learn how the `Runner` interface works - [Training interface reference](docs/training.md) to learn how to add a fine-tuning API to your model - [HTTP API reference](docs/http.md) to learn how to use the HTTP API that models serve diff --git a/docs/getting-started-own-model.md b/docs/getting-started-own-model.md index eb2bb55f83..220509c4c3 100644 --- a/docs/getting-started-own-model.md +++ b/docs/getting-started-own-model.md @@ -27,7 +27,7 @@ sudo chmod +x /usr/local/bin/cog To configure your project for use with Cog, you'll need to add two files: - [`cog.yaml`](yaml.md) defines system requirements, Python package dependencies, etc -- [`predict.py`](python.md) describes the prediction interface for your model +- [`run.py`](python.md) describes the prediction interface for your model Use the `cog init` command to generate these files in your project: @@ -76,18 +76,18 @@ With `cog.yaml`, you can also install system packages and other things. [Take a ## Define how to run predictions -The next step is to update `predict.py` to define the interface for running predictions on your model. The `predict.py` generated by `cog init` looks something like this: +The next step is to update `run.py` to define the interface for running predictions on your model. The `run.py` generated by `cog init` looks something like this: ```python -from cog import BasePredictor, Path, Input +from cog import BaseRunner, Path, Input import torch -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): """Load the model into memory to make running multiple predictions efficient""" self.net = torch.load("weights.pth") - def predict(self, + def run(self, image: Path = Input(description="Image to enlarge"), scale: float = Input(description="Factor to scale image by", default=1.5) ) -> Path: @@ -98,9 +98,9 @@ class Predictor(BasePredictor): return output ``` -Edit your `predict.py` file and fill in the functions with your own model's setup and prediction code. You might need to import parts of your model from another file. +Edit your `run.py` file and fill in the functions with your own model's setup and prediction code. You might need to import parts of your model from another file. -You also need to define the inputs to your model as arguments to the `predict()` function, as demonstrated above. For each argument, you need to annotate with a type. The supported types are: +You also need to define the inputs to your model as arguments to the `run()` function, as demonstrated above. For each argument, you need to annotate with a type. The supported types are: - `str`: a string - `int`: an integer @@ -123,19 +123,19 @@ You can provide more information about the input with the `Input()` function, as There are some more advanced options you can pass, too. For more details, [take a look at the prediction interface documentation](python.md). -Next, add the line `predict: "predict.py:Predictor"` to your `cog.yaml`, so it looks something like this: +Next, add the line `run: "run.py:Runner"` to your `cog.yaml`, so it looks something like this: ```yaml build: python_version: "3.13" python_requirements: requirements.txt -predict: "predict.py:Predictor" +run: "run.py:Runner" ``` That's it! To test this works, try running a prediction on the model: ``` -$ cog predict -i image=@input.jpg +$ cog run -i image=@input.jpg ✓ Building Docker image from cog.yaml... Successfully built 664ef88bc1f4 ✓ Model running in Docker image 664ef88bc1f4 @@ -145,7 +145,7 @@ Written output to output.png To pass more inputs to the model, you can add more `-i` options: ``` -$ cog predict -i image=@image.jpg -i scale=2.0 +$ cog run -i image=@image.jpg -i scale=2.0 ``` In this case it is just a number, not a file, so you don't need the `@` prefix. diff --git a/docs/getting-started.md b/docs/getting-started.md index ea1252b0b5..04fd72acd3 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -91,28 +91,28 @@ Let's pretend we've trained a model. With Cog, we can define how to run predicti We need to write some code to describe how predictions are run on the model. -Save this to `predict.py`: +Save this to `run.py`: ```python import os os.environ["TORCH_HOME"] = "." import torch -from cog import BasePredictor, Input, Path +from cog import BaseRunner, Input, Path from PIL import Image from torchvision import models WEIGHTS = models.ResNet50_Weights.IMAGENET1K_V1 -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): """Load the model into memory to make running multiple predictions efficient""" self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") self.model = models.resnet50(weights=WEIGHTS).to(self.device) self.model.eval() - def predict(self, image: Path = Input(description="Image to classify")) -> dict: + def run(self, image: Path = Input(description="Image to classify")) -> dict: """Run a single prediction on the model""" img = Image.open(image).convert("RGB") preds = self.model(WEIGHTS.transforms()(img).unsqueeze(0).to(self.device)) @@ -137,7 +137,7 @@ Then update `cog.yaml` to look like this: build: python_version: "3.13" python_requirements: requirements.txt -predict: "predict.py:Predictor" +run: "run.py:Runner" ``` > [!TIP] @@ -154,7 +154,7 @@ curl $IMAGE_URL > input.jpg Now, let's run the model using Cog: ```bash -cog predict -i image=@input.jpg +cog run -i image=@input.jpg ``` @@ -170,7 +170,7 @@ If you see the following output then it worked! -Note: The first time you run `cog predict`, the build process will be triggered to generate a Docker container that can run your model. The next time you run `cog predict` the pre-built container will be used. +Note: The first time you run `cog run`, the build process will be triggered to generate a Docker container that can run your model. The next time you run `cog run` the pre-built container will be used. ## Build an image @@ -183,10 +183,10 @@ cog build -t resnet ``` -You can run this image with `cog predict` by passing the filename as an argument: +You can run this image with `cog run` by passing the filename as an argument: ```bash -cog predict resnet -i image=@input.jpg +cog run resnet -i image=@input.jpg ``` diff --git a/docs/http.md b/docs/http.md index bc13d55741..7f8265500a 100644 --- a/docs/http.md +++ b/docs/http.md @@ -77,10 +77,10 @@ at the following times. Once, when the prediction starts (`status` is `starting`). - `output`: - Each time a predict function generates an output + Each time a run function generates an output (either once using `return` or multiple times using `yield`) - `logs`: - Each time the predict function writes to `stdout` + Each time the run function writes to `stdout` - `completed`: Once, when the prediction reaches a terminal state (`status` is `succeeded`, `canceled`, or `failed`) @@ -135,7 +135,7 @@ This produces a random identifier that is 26 ASCII characters long. ## File uploads -A model's `predict` function can produce file output by yielding or returning +A model's `run` function can produce file output by yielding or returning a `cog.Path` or `cog.File` value. By default, @@ -296,7 +296,7 @@ Content-Type: application/json The [OpenAPI](https://swagger.io/specification/) specification of the API, which is derived from the input and output types specified in your model's -[Predictor](python.md) and [Training](training.md) objects. +[Runner](python.md) and [Training](training.md) objects. ### `POST /predictions` @@ -306,13 +306,13 @@ The request body is a JSON object with the following fields: - `input`: A JSON object with the same keys as the - [arguments to the `predict()` function](python.md). + [arguments to the `run()` function](python.md). Any `File` or `Path` inputs are passed as URLs. The response body is a JSON object with the following fields: - `status`: Either `succeeded` or `failed`. -- `output`: The return value of the `predict()` function. +- `output`: The return value of the `run()` function. - `error`: If `status` is `failed`, the error message. - `metrics`: An object containing prediction metrics. Always includes `predict_time` (elapsed seconds). @@ -451,17 +451,17 @@ Otherwise, the server responds with status `404 Not Found`. When a prediction is canceled, Cog raises [`CancelationException`](python.md#cancelationexception) -in sync predictors (or `asyncio.CancelledError` in async predictors). +in sync runners (or `asyncio.CancelledError` in async runners). This exception may be caught by the model to perform necessary cleanup. The cleanup should be brief, ideally completing within a few seconds. After cleanup, the exception must be re-raised using a bare `raise` statement. Failure to re-raise the exception may result in the termination of the container. ```python -from cog import BasePredictor, CancelationException, Input, Path +from cog import BaseRunner, CancelationException, Input, Path -class Predictor(BasePredictor): - def predict(self, image: Path = Input(description="Image to process")) -> Path: +class Runner(BaseRunner): + def run(self, image: Path = Input(description="Image to process")) -> Path: try: return self.process(image) except CancelationException: diff --git a/docs/notebooks.md b/docs/notebooks.md index 2f1f4a496c..c93fc4ee14 100644 --- a/docs/notebooks.md +++ b/docs/notebooks.md @@ -27,9 +27,9 @@ Cog can run notebooks in the environment you've defined in `cog.yaml` with the f cog exec -p 8888 jupyter lab --allow-root --ip=0.0.0.0 ``` -## Use notebook code in your predictor +## Use notebook code in your runner -You can also import a notebook into your Cog [Predictor](python.md) file. +You can also import a notebook into your Cog [Runner](python.md) file. First, export your notebook to a Python file: @@ -37,15 +37,15 @@ First, export your notebook to a Python file: jupyter nbconvert --to script my_notebook.ipynb # creates my_notebook.py ``` -Then import the exported Python script into your `predict.py` file. Any functions or variables defined in your notebook will be available to your predictor: +Then import the exported Python script into your `run.py` file. Any functions or variables defined in your notebook will be available to your runner: ```python -from cog import BasePredictor, Input +from cog import BaseRunner, Input import my_notebook -class Predictor(BasePredictor): - def predict(self, prompt: str = Input(description="string prompt")) -> str: +class Runner(BaseRunner): + def run(self, prompt: str = Input(description="string prompt")) -> str: output = my_notebook.do_stuff(prompt) return output ``` diff --git a/docs/python.md b/docs/python.md index cf7a9ae1ed..49564ba310 100644 --- a/docs/python.md +++ b/docs/python.md @@ -3,7 +3,7 @@ This document defines the API of the `cog` Python module, which is used to define the interface for running predictions on your model. > [!TIP] -> Run [`cog init`](getting-started-own-model.md#initialization) to generate an annotated `predict.py` file that can be used as a starting point for setting up your model. +> Run [`cog init`](getting-started-own-model.md#initialization) to generate an annotated `run.py` file that can be used as a starting point for setting up your model. > [!TIP] > Using a language model to help you write the code for your new Cog model? @@ -13,10 +13,10 @@ This document defines the API of the `cog` Python module, which is used to defin ## Contents - [Contents](#contents) -- [`BasePredictor`](#basepredictor) - - [`Predictor.setup()`](#predictorsetup) - - [`Predictor.predict(**kwargs)`](#predictorpredictkwargs) -- [`async` predictors and concurrency](#async-predictors-and-concurrency) +- [`BaseRunner`](#baserunner) + - [`Runner.setup()`](#runnersetup) + - [`Runner.run(**kwargs)`](#runnerrunkwargs) +- [`async` runners and concurrency](#async-runners-and-concurrency) - [`Input(**kwargs)`](#inputkwargs) - [Deprecating inputs](#deprecating-inputs) - [Output](#output) @@ -46,20 +46,20 @@ This document defines the API of the `cog` Python module, which is used to defin - [`BaseModel` field types](#basemodel-field-types) - [Type limitations](#type-limitations) -## `BasePredictor` +## `BaseRunner` -You define how Cog runs predictions on your model by defining a class that inherits from `BasePredictor`. It looks something like this: +You define how Cog runs predictions on your model by defining a class that inherits from `BaseRunner`. It looks something like this: ```python -from cog import BasePredictor, Path, Input +from cog import BaseRunner, Path, Input import torch -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): """Load the model into memory to make running multiple predictions efficient""" self.model = torch.load("weights.pth") - def predict(self, + def run(self, image: Path = Input(description="Image to enlarge"), scale: float = Input(description="Factor to scale image by", default=1.5) ) -> Path: @@ -70,9 +70,12 @@ class Predictor(BasePredictor): return output ``` -Your Predictor class should define two methods: `setup()` and `predict()`. +> [!NOTE] +> `BasePredictor` and `predict()` still work for backwards compatibility, but `BaseRunner` and `run()` are preferred for new models. + +Your Runner class should define two methods: `setup()` and `run()`. -### `Predictor.setup()` +### `Runner.setup()` Prepare the model so multiple predictions run efficiently. @@ -96,43 +99,43 @@ While this will increase your image size and build time, it offers other advanta > When using this method, you should use the `--separate-weights` flag on `cog build` to store weights in a [separate layer](https://github.com/replicate/cog/blob/12ac02091d93beebebed037f38a0c99cd8749806/docs/getting-started.md?plain=1#L219). -### `Predictor.predict(**kwargs)` +### `Runner.run(**kwargs)` Run a single prediction. This _required_ method is where you call the model that was loaded during `setup()`, but you may also want to add pre- and post-processing code here. -The `predict()` method takes an arbitrary list of named arguments, where each argument name must correspond to an [`Input()`](#inputkwargs) annotation. +The `run()` method takes an arbitrary list of named arguments, where each argument name must correspond to an [`Input()`](#inputkwargs) annotation. -`predict()` can return strings, numbers, [`cog.Path`](#cogpath) objects representing files on disk, or lists or dicts of those types. You can also define a custom [`BaseModel`](#structured-output-with-basemodel) for structured return types. See [Input and output types](#input-and-output-types) for the full list of supported types. +`run()` can return strings, numbers, [`cog.Path`](#cogpath) objects representing files on disk, or lists or dicts of those types. You can also define a custom [`BaseModel`](#structured-output-with-basemodel) for structured return types. See [Input and output types](#input-and-output-types) for the full list of supported types. -## `async` predictors and concurrency +## `async` runners and concurrency > Added in cog 0.14.0. -You may specify your `predict()` method as `async def predict(...)`. In -addition, if you have an async `predict()` function you may also have an async +You may specify your `run()` method as `async def run(...)`. In +addition, if you have an async `run()` function you may also have an async `setup()` function: ```py -class Predictor(BasePredictor): +class Runner(BaseRunner): async def setup(self) -> None: print("async setup is also supported...") - async def predict(self) -> str: - print("async predict"); + async def run(self) -> str: + print("async run"); return "hello world"; ``` -Models that have an async `predict()` function can run predictions concurrently, up to the limit specified by [`concurrency.max`](yaml.md#max) in cog.yaml. Attempting to exceed this limit will return a 409 Conflict response. +Models that have an async `run()` function can run predictions concurrently, up to the limit specified by [`concurrency.max`](yaml.md#max) in cog.yaml. Attempting to exceed this limit will return a 409 Conflict response. ## `Input(**kwargs)` -Use cog's `Input()` function to define each of the parameters in your `predict()` method: +Use cog's `Input()` function to define each of the parameters in your `run()` method: ```py -class Predictor(BasePredictor): - def predict(self, +class Runner(BaseRunner): + def run(self, image: Path = Input(description="Image to enlarge"), scale: float = Input(description="Factor to scale image by", default=1.5, ge=1.0, le=10.0) ) -> Path: @@ -150,13 +153,13 @@ The `Input()` function takes these keyword arguments: - `choices`: For `str` or `int` types, a list of possible values for this input. - `deprecated`: (optional) If set to `True`, marks this input as deprecated. Deprecated inputs will still be accepted, but tools and UIs may warn users that the input is deprecated and may be removed in the future. See [Deprecating inputs](#deprecating-inputs). -Each parameter of the `predict()` method must be annotated with a type like `str`, `int`, `float`, `bool`, etc. See [Input and output types](#input-and-output-types) for the full list of supported types. +Each parameter of the `run()` method must be annotated with a type like `str`, `int`, `float`, `bool`, etc. See [Input and output types](#input-and-output-types) for the full list of supported types. Using the `Input` function provides better documentation and validation constraints to the users of your model, but it is not strictly required. You can also specify default values for your parameters using plain Python, or omit default assignment entirely: ```py -class Predictor(BasePredictor): - def predict(self, +class Runner(BaseRunner): + def run(self, prompt: str = "default prompt", # this is valid iterations: int # also valid ) -> str: @@ -170,10 +173,10 @@ You can mark an input as deprecated by passing `deprecated=True` to the `Input() This is useful when you want to phase out an input without breaking existing clients immediately: ```py -from cog import BasePredictor, Input +from cog import BaseRunner, Input -class Predictor(BasePredictor): - def predict(self, +class Runner(BaseRunner): + def run(self, text: str = Input(description="Some deprecated text", deprecated=True), prompt: str = Input(description="Prompt for the model") ) -> str: @@ -183,31 +186,31 @@ class Predictor(BasePredictor): ## Output -Cog predictors can return a simple data type like a string, number, float, or boolean. Use Python's `-> ` syntax to annotate the return type. +Cog runners can return a simple data type like a string, number, float, or boolean. Use Python's `-> ` syntax to annotate the return type. -Here's an example of a predictor that returns a string: +Here's an example of a runner that returns a string: ```py -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self) -> str: +class Runner(BaseRunner): + def run(self) -> str: return "hello" ``` ### Returning an object -To return a complex object with multiple values, define an `Output` object with multiple fields to return from your `predict()` method: +To return a complex object with multiple values, define an `Output` object with multiple fields to return from your `run()` method: ```py -from cog import BasePredictor, BaseModel, File +from cog import BaseRunner, BaseModel, File class Output(BaseModel): file: File text: str -class Predictor(BasePredictor): - def predict(self) -> Output: +class Runner(BaseRunner): + def run(self) -> Output: return Output(text="hello", file=io.StringIO("hello")) ``` @@ -215,13 +218,13 @@ Each of the output object's properties must be one of the supported output types ### Returning a list -The `predict()` method can return a list of any of the supported output types. Here's an example that outputs multiple files: +The `run()` method can return a list of any of the supported output types. Here's an example that outputs multiple files: ```py -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): - def predict(self) -> list[Path]: +class Runner(BaseRunner): + def run(self) -> list[Path]: predictions = ["foo", "bar", "baz"] output = [] for i, prediction in enumerate(predictions): @@ -239,15 +242,15 @@ Files are named in the format `output..`, e.g. `output.0.txt`, To conditionally omit properties from the Output object, define them using `typing.Optional`: ```py -from cog import BaseModel, BasePredictor, Path +from cog import BaseModel, BaseRunner, Path from typing import Optional class Output(BaseModel): score: Optional[float] file: Optional[Path] -class Predictor(BasePredictor): - def predict(self) -> Output: +class Runner(BaseRunner): + def run(self) -> Output: if condition: return Output(score=1.5) else: @@ -256,30 +259,30 @@ class Predictor(BasePredictor): ### Streaming output -Cog models can stream output as the `predict()` method is running. For example, a language model can output tokens as they're being generated and an image generation model can output images as they are being generated. +Cog models can stream output as the `run()` method is running. For example, a language model can output tokens as they're being generated and an image generation model can output images as they are being generated. -To support streaming output in your Cog model, add `from typing import Iterator` to your predict.py file. The `typing` package is a part of Python's standard library so it doesn't need to be installed. Then add a return type annotation to the `predict()` method in the form `-> Iterator[]` where `` can be one of `str`, `int`, `float`, `bool`, or `cog.Path`. +To support streaming output in your Cog model, add `from typing import Iterator` to your run.py file. The `typing` package is a part of Python's standard library so it doesn't need to be installed. Then add a return type annotation to the `run()` method in the form `-> Iterator[]` where `` can be one of `str`, `int`, `float`, `bool`, or `cog.Path`. ```py -from cog import BasePredictor, Path +from cog import BaseRunner, Path from typing import Iterator -class Predictor(BasePredictor): - def predict(self) -> Iterator[Path]: +class Runner(BaseRunner): + def run(self) -> Iterator[Path]: done = False while not done: output_path, done = do_stuff() yield Path(output_path) ``` -If you have an [async `predict()` method](#async-predictors-and-concurrency), use `AsyncIterator` from the `typing` module: +If you have an [async `run()` method](#async-runners-and-concurrency), use `AsyncIterator` from the `typing` module: ```py from typing import AsyncIterator -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): - async def predict(self) -> AsyncIterator[Path]: +class Runner(BaseRunner): + async def run(self) -> AsyncIterator[Path]: done = False while not done: output_path, done = do_stuff() @@ -289,22 +292,22 @@ class Predictor(BasePredictor): If you're streaming text output, you can use `ConcatenateIterator` to hint that the output should be concatenated together into a single string. This is useful on Replicate to display the output as a string instead of a list of strings. ```py -from cog import BasePredictor, Path, ConcatenateIterator +from cog import BaseRunner, Path, ConcatenateIterator -class Predictor(BasePredictor): - def predict(self) -> ConcatenateIterator[str]: +class Runner(BaseRunner): + def run(self) -> ConcatenateIterator[str]: tokens = ["The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"] for token in tokens: yield token + " " ``` -Or for async `predict()` methods, use `AsyncConcatenateIterator`: +Or for async `run()` methods, use `AsyncConcatenateIterator`: ```py -from cog import BasePredictor, Path, AsyncConcatenateIterator +from cog import BaseRunner, Path, AsyncConcatenateIterator -class Predictor(BasePredictor): - async def predict(self) -> AsyncConcatenateIterator[str]: +class Runner(BaseRunner): + async def run(self) -> AsyncConcatenateIterator[str]: tokens = ["The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"] for token in tokens: yield token + " " @@ -312,17 +315,17 @@ class Predictor(BasePredictor): ## Metrics -You can record custom metrics from your `predict()` function to track model-specific data like token counts, timing breakdowns, or confidence scores. Metrics are included in the prediction response alongside the output. +You can record custom metrics from your `run()` function to track model-specific data like token counts, timing breakdowns, or confidence scores. Metrics are included in the prediction response alongside the output. ### Recording metrics -Use `self.record_metric()` inside your `predict()` method: +Use `self.record_metric()` inside your `run()` method: ```python -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, prompt: str) -> str: +class Runner(BaseRunner): + def run(self, prompt: str) -> str: self.record_metric("temperature", 0.7) self.record_metric("token_count", 42) @@ -438,12 +441,12 @@ Outside an active prediction, `self.record_metric()` and `self.scope` are silent ## Cancellation -When a prediction is canceled (via the [cancel HTTP endpoint](http.md#post-predictionsprediction_idcancel) or a dropped connection), the Cog runtime interrupts the running `predict()` function. The exception raised depends on whether the predictor is sync or async: +When a prediction is canceled (via the [cancel HTTP endpoint](http.md#post-predictionsprediction_idcancel) or a dropped connection), the Cog runtime interrupts the running `run()` function. The exception raised depends on whether the runner is sync or async: -| Predictor type | Exception raised | +| Runner type | Exception raised | | --------------------------- | ------------------------ | -| Sync (`def predict`) | `CancelationException` | -| Async (`async def predict`) | `asyncio.CancelledError` | +| Sync (`def run`) | `CancelationException` | +| Async (`async def run`) | `asyncio.CancelledError` | ### `CancelationException` @@ -451,15 +454,15 @@ When a prediction is canceled (via the [cancel HTTP endpoint](http.md#post-predi from cog import CancelationException ``` -`CancelationException` is raised in **sync** predictors when a prediction is cancelled. It is a `BaseException` subclass — **not** an `Exception` subclass. This means bare `except Exception` blocks in your predict code will not accidentally catch it, matching the behavior of `KeyboardInterrupt` and `asyncio.CancelledError`. +`CancelationException` is raised in **sync** runners when a prediction is cancelled. It is a `BaseException` subclass — **not** an `Exception` subclass. This means bare `except Exception` blocks in your run code will not accidentally catch it, matching the behavior of `KeyboardInterrupt` and `asyncio.CancelledError`. -You do **not** need to handle this exception in normal predictor code — the runtime manages cancellation automatically. However, if you need to run cleanup logic when a prediction is cancelled, you can catch it explicitly: +You do **not** need to handle this exception in normal runner code — the runtime manages cancellation automatically. However, if you need to run cleanup logic when a prediction is cancelled, you can catch it explicitly: ```python -from cog import BasePredictor, CancelationException, Path +from cog import BaseRunner, CancelationException, Path -class Predictor(BasePredictor): - def predict(self, image: Path) -> Path: +class Runner(BaseRunner): + def run(self, image: Path) -> Path: try: return self.process(image) except CancelationException: @@ -475,11 +478,11 @@ class Predictor(BasePredictor): - `cog.CancelationException` (recommended) - `cog.exceptions.CancelationException` -For **async** predictors, cancellation follows standard Python async conventions and raises `asyncio.CancelledError` instead. +For **async** runners, cancellation follows standard Python async conventions and raises `asyncio.CancelledError` instead. ## Input and output types -Each parameter of the `predict()` method must be annotated with a type. The method's return type must also be annotated. +Each parameter of the `run()` method must be annotated with a type. The method's return type must also be annotated. ### Primitive types @@ -507,10 +510,10 @@ This example takes an input file, resizes it, and returns the resized image: ```python import tempfile -from cog import BasePredictor, Input, Path +from cog import BaseRunner, Input, Path -class Predictor(BasePredictor): - def predict(self, image: Path = Input(description="Image to enlarge")) -> Path: +class Runner(BaseRunner): + def run(self, image: Path = Input(description="Image to enlarge")) -> Path: upscaled_image = do_some_processing(image) # To output cog.Path objects the file needs to exist, so create a temporary file first. @@ -528,11 +531,11 @@ class Predictor(BasePredictor): `cog.File` represents a _file handle_. For models that return a `cog.File` object, the prediction output returned by Cog's built-in HTTP server will be a URL. ```python -from cog import BasePredictor, File, Input +from cog import BaseRunner, File, Input from PIL import Image -class Predictor(BasePredictor): - def predict(self, source_image: File = Input(description="Image to enlarge")) -> File: +class Runner(BaseRunner): + def run(self, source_image: File = Input(description="Image to enlarge")) -> File: pillow_img = Image.open(source_image) upscaled_image = do_some_processing(pillow_img) return File(upscaled_image) @@ -545,10 +548,10 @@ class Predictor(BasePredictor): `cog.Secret` redacts its contents in string representations to prevent accidental disclosure. Access the underlying value with `get_secret_value()`. ```python -from cog import BasePredictor, Secret +from cog import BaseRunner, Secret -class Predictor(BasePredictor): - def predict(self, api_token: Secret) -> None: +class Runner(BaseRunner): + def run(self, api_token: Secret) -> None: # Prints '**********' print(api_token) @@ -556,7 +559,7 @@ class Predictor(BasePredictor): print(api_token.get_secret_value()) ``` -A predictor's `Secret` inputs are represented in OpenAPI with the following schema: +A runner's `Secret` inputs are represented in OpenAPI with the following schema: ```json { @@ -582,10 +585,10 @@ Use `Optional[T]` or `T | None` (Python 3.10+) to mark an input as optional. Opt ```python from typing import Optional -from cog import BasePredictor, Input +from cog import BaseRunner, Input -class Predictor(BasePredictor): - def predict(self, +class Runner(BaseRunner): + def run(self, prompt: Optional[str] = Input(description="Input prompt"), seed: int | None = Input(description="Random seed", default=None), ) -> str: @@ -598,11 +601,11 @@ Prefer `Optional[T]` or `T | None` over `str = Input(default=None)` for inputs t ```python # Bad: type annotation says str but value can be None -def predict(self, prompt: str = Input(default=None)) -> str: +def run(self, prompt: str = Input(default=None)) -> str: return "hello" + prompt # TypeError at runtime if prompt is None # Good: type annotation matches actual behavior -def predict(self, prompt: Optional[str] = Input(description="prompt")) -> str: +def run(self, prompt: Optional[str] = Input(description="prompt")) -> str: if prompt is None: return "hello" return "hello " + prompt @@ -618,10 +621,10 @@ Use `list[T]` or `List[T]` to accept or return a list of values. `T` can be a su **As an input type:** ```py -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): - def predict(self, paths: list[Path]) -> str: +class Runner(BaseRunner): + def run(self, paths: list[Path]) -> str: output_parts = [] for path in paths: with open(path) as f: @@ -629,21 +632,21 @@ class Predictor(BasePredictor): return "".join(output_parts) ``` -With `cog predict`, repeat the input name to pass multiple values: +With `cog run`, repeat the input name to pass multiple values: ```bash $ echo test1 > 1.txt $ echo test2 > 2.txt -$ cog predict -i paths=@1.txt -i paths=@2.txt +$ cog run -i paths=@1.txt -i paths=@2.txt ``` **As an output type:** ```py -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): - def predict(self) -> list[Path]: +class Runner(BaseRunner): + def run(self) -> list[Path]: predictions = ["foo", "bar", "baz"] output = [] for i, prediction in enumerate(predictions): @@ -661,10 +664,10 @@ Files are named in the format `output..`, e.g. `output.0.txt`, Use `dict` to accept or return an opaque JSON object. The value is passed through as-is without type validation. ```python -from cog import BasePredictor, Input +from cog import BaseRunner, Input -class Predictor(BasePredictor): - def predict(self, +class Runner(BaseRunner): + def run(self, params: dict = Input(description="Arbitrary JSON parameters"), ) -> dict: return {"greeting": "hello", "params": params} @@ -683,15 +686,15 @@ To return a complex object with multiple typed fields, define a class that inher ```python from typing import Optional -from cog import BasePredictor, BaseModel, Path +from cog import BaseRunner, BaseModel, Path class Output(BaseModel): text: str confidence: float image: Optional[Path] -class Predictor(BasePredictor): - def predict(self, prompt: str) -> Output: +class Runner(BaseRunner): + def run(self, prompt: str) -> Output: result = self.model.generate(prompt) return Output( text=result.text, @@ -717,15 +720,15 @@ If you already use Pydantic v2 in your model, you can use a Pydantic `BaseModel` ```python from pydantic import BaseModel as PydanticBaseModel -from cog import BasePredictor +from cog import BaseRunner class Result(PydanticBaseModel): name: str score: float tags: list[str] -class Predictor(BasePredictor): - def predict(self, prompt: str) -> Result: +class Runner(BaseRunner): + def run(self, prompt: str) -> Result: return Result(name="example", score=0.95, tags=["fast", "accurate"]) ``` diff --git a/docs/training.md b/docs/training.md index c7d2363458..b35c0daea4 100644 --- a/docs/training.md +++ b/docs/training.md @@ -7,7 +7,7 @@ Cog's training API allows you to define a fine-tuning interface for an existing ## How it works -If you've used Cog before, you've probably seen the [Predictor](./python.md) class, which defines the interface for creating predictions against your model. Cog's training API works similarly: You define a Python function that describes the inputs and outputs of the training process. The inputs are things like training data, epochs, batch size, seed, etc. The output is typically a file with the fine-tuned weights. +If you've used Cog before, you've probably seen the [Runner](./python.md) class, which defines the interface for running predictions against your model. Cog's training API works similarly: You define a Python function that describes the inputs and outputs of the training process. The inputs are things like training data, epochs, batch size, seed, etc. The output is typically a file with the fine-tuned weights. `cog.yaml`: @@ -20,7 +20,7 @@ train: "train.py:train" `train.py`: ```python -from cog import BasePredictor, File +from cog import File import io def train(param: str) -> File: @@ -37,7 +37,7 @@ $ cat weights hello train ``` -You can also use classes if you want to run many model trainings and save on setup time. This works the same way as the [Predictor](./python.md) class with the only difference being the `train` method. +You can also use classes if you want to run many model trainings and save on setup time. This works the same way as the [Runner](./python.md) class with the only difference being the `train` method. `cog.yaml`: @@ -50,7 +50,7 @@ train: "train.py:Trainer" `train.py`: ```python -from cog import BasePredictor, File +from cog import File import io class Trainer: @@ -120,8 +120,8 @@ def train( ## Testing -If you are doing development of a Cog model like Llama or SDXL, you can test that the fine-tuned code path works before pushing by specifying a `COG_WEIGHTS` environment variable when running `predict`: +If you are doing development of a Cog model like Llama or SDXL, you can test that the fine-tuned code path works before pushing by specifying a `COG_WEIGHTS` environment variable when running `cog run`: ```console -cog predict -e COG_WEIGHTS=https://replicate.delivery/pbxt/xyz/weights.tar -i prompt="a photo of TOK" +cog run -e COG_WEIGHTS=https://replicate.delivery/pbxt/xyz/weights.tar -i prompt="a photo of TOK" ``` diff --git a/docs/yaml.md b/docs/yaml.md index 1b57161b1b..2f76b5f98a 100644 --- a/docs/yaml.md +++ b/docs/yaml.md @@ -2,7 +2,7 @@ `cog.yaml` defines how to build a Docker image and how to run predictions on your model inside that image. -It has three keys: [`build`](#build), [`image`](#image), and [`predict`](#predict). It looks a bit like this: +It has three keys: [`build`](#build), [`image`](#image), and [`run`](#run). It looks a bit like this: ```yaml build: @@ -11,7 +11,7 @@ build: system_packages: - "ffmpeg" - "git" -predict: "predict.py:Predictor" +run: "run.py:Runner" ``` Tip: Run [`cog init`](getting-started-own-model.md#initialization) to generate an annotated `cog.yaml` file that can be used as a starting point for setting up your model. @@ -44,7 +44,7 @@ build: gpu: true ``` -When you use `cog exec` or `cog predict`, Cog will automatically pass the `--gpus=all` flag to Docker. When you run a Docker image built with Cog, you'll need to pass this option to `docker run`. +When you use `cog exec` or `cog run`, Cog will automatically pass the `--gpus=all` flag to Docker. When you run a Docker image built with Cog, you'll need to pass this option to `docker run`. ### `python_requirements` @@ -194,7 +194,7 @@ This stanza describes the concurrency capabilities of the model. It has one opti ### `max` -The maximum number of concurrent predictions the model can process. If this is set, the model must specify an [async `predict()` method](python.md#async-predictors-and-concurrency). +The maximum number of concurrent predictions the model can process. If this is set, the model must specify an [async `run()` method](python.md#async-runners-and-concurrency). For example: @@ -221,14 +221,17 @@ If you set this, then you can run `cog push` without specifying the model name. If you specify an image name argument when pushing (like `cog push your-username/custom-model-name`), the argument will be used and the value of `image` in cog.yaml will be ignored. -## `predict` +## `run` -The pointer to the `Predictor` object in your code, which defines how predictions are run on your model. +The pointer to the `Runner` object in your code, which defines how predictions are run on your model. For example: ```yaml -predict: "predict.py:Predictor" +run: "run.py:Runner" ``` +> [!NOTE] +> The `predict:` key still works for backwards compatibility, but `run:` is preferred for new models. For example, `predict: "predict.py:Predictor"` is equivalent to `run: "run.py:Runner"`. + See [the Python API documentation for more information](python.md). From 5d86228d56498e4450538ecbb1652c99de6e4da0 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 17:16:21 -0400 Subject: [PATCH 10/18] chore: regenerate CLI docs, llms.txt, and apply rust fmt --- crates/coglet-python/src/predictor.rs | 41 ++- docs/cli.md | 96 +++--- docs/llms.txt | 436 +++++++++++++------------- 3 files changed, 299 insertions(+), 274 deletions(-) diff --git a/crates/coglet-python/src/predictor.rs b/crates/coglet-python/src/predictor.rs index 463f75a47a..b4c26b2b34 100644 --- a/crates/coglet-python/src/predictor.rs +++ b/crates/coglet-python/src/predictor.rs @@ -337,7 +337,10 @@ impl PythonPredictor { tracing::info!("Detected sync train()"); PredictKind::Sync }; - (PredictorKind::StandaloneFunction(predict_kind), String::new()) + ( + PredictorKind::StandaloneFunction(predict_kind), + String::new(), + ) } else { // Class instance - detect run() vs predict() method // Check class __dict__ to distinguish user-defined from inherited base stubs @@ -361,8 +364,7 @@ impl PythonPredictor { } }; - let (is_async, is_async_gen) = - Self::detect_async(py, &instance, &predict_method_name)?; + let (is_async, is_async_gen) = Self::detect_async(py, &instance, &predict_method_name)?; let predict_kind = if is_async_gen { tracing::info!("Detected async generator {}()", predict_method_name); PredictKind::AsyncGen @@ -388,13 +390,20 @@ impl PythonPredictor { TrainKind::None }; - (PredictorKind::Class { - predict: predict_kind, - train: train_kind, - }, predict_method_name) + ( + PredictorKind::Class { + predict: predict_kind, + train: train_kind, + }, + predict_method_name, + ) }; - let predictor = Self { instance, kind, predict_method_name }; + let predictor = Self { + instance, + kind, + predict_method_name, + }; // Patch FieldInfo defaults on predict/train methods so Python uses actual // default values instead of FieldInfo wrapper objects for missing inputs. @@ -403,7 +412,11 @@ impl PythonPredictor { if is_function { Self::unwrap_field_info_defaults(py, &predictor.instance, "")?; } else { - Self::unwrap_field_info_defaults(py, &predictor.instance, &predictor.predict_method_name)?; + Self::unwrap_field_info_defaults( + py, + &predictor.instance, + &predictor.predict_method_name, + )?; if matches!(predictor.kind, PredictorKind::Class { train, .. } if train != TrainKind::None) { Self::unwrap_field_info_defaults(py, &predictor.instance, "train")?; @@ -691,7 +704,10 @@ impl PythonPredictor { // PreparedInput cleans up temp files on drop (RAII) let func = self.predict_func(py).map_err(|e| { - PredictionError::Failed(format!("Failed to get {} function: {}", self.predict_method_name, e)) + PredictionError::Failed(format!( + "Failed to get {} function: {}", + self.predict_method_name, e + )) })?; let prepared = input::prepare_input(py, raw_input_dict, &func) .map_err(|e| PredictionError::InvalidInput(format_validation_error(py, &e)))?; @@ -967,7 +983,10 @@ impl PythonPredictor { })?; let func = self.predict_func(py).map_err(|e| { - PredictionError::Failed(format!("Failed to get {} function: {}", self.predict_method_name, e)) + PredictionError::Failed(format!( + "Failed to get {} function: {}", + self.predict_method_name, e + )) })?; let prepared = input::prepare_input(py, raw_input_dict, &func) .map_err(|e| PredictionError::InvalidInput(format_validation_error(py, &e)))?; diff --git a/docs/cli.md b/docs/cli.md index 04446b2e29..bd093a6a3c 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -29,7 +29,7 @@ https://github.com/replicate/cog Build a Docker image from the cog.yaml in the current directory. The generated image contains your model code, dependencies, and the Cog -runtime. It can be run locally with 'cog predict' or pushed to a registry +runtime. It can be run locally with 'cog run' or pushed to a registry with 'cog push'. ``` @@ -108,7 +108,7 @@ cog exec [arg...] [flags] ``` ## `cog init` -Create a cog.yaml and predict.py in the current directory. +Create a cog.yaml and run.py in the current directory. These files provide a starting template for defining your model's environment and prediction interface. Edit them to match your model's requirements. @@ -149,7 +149,44 @@ cog login [flags] -h, --help help for login --token-stdin Pass login token on stdin instead of opening a browser. You can find your Replicate login token at https://replicate.com/auth/token ``` -## `cog predict` +## `cog push` + +Build a Docker image from cog.yaml and push it to a container registry. + +Cog can push to any OCI-compliant registry. When pushing to Replicate's +registry (r8.im), run 'cog login' first to authenticate. + +``` +cog push [IMAGE] [flags] +``` + +**Examples** + +``` + # Push to Replicate + cog push r8.im/your-username/my-model + + # Push to any OCI registry + cog push registry.example.com/your-username/model-name + + # Push with model weights in a separate layer (Replicate only) + cog push r8.im/your-username/my-model --separate-weights +``` + +**Options** + +``` + -f, --file string The name of the config file. (default "cog.yaml") + -h, --help help for push + --no-cache Do not use cache when building the image + --openapi-schema string Load OpenAPI schema from a file + --progress string Set type of build progress output, 'auto' (default), 'tty', 'plain', or 'quiet' (default "auto") + --secret stringArray Secrets to pass to the build environment in the form 'id=foo,src=/path/to/file' + --separate-weights Separate model weights from code in image layers + --use-cog-base-image Use pre-built Cog base image for faster cold boots (default true) + --use-cuda-base-image string Use Nvidia CUDA base image, 'true' (default) or 'false' (use python base image). False results in a smaller image but may cause problems for non-torch projects (default "auto") +``` +## `cog run` Run a prediction. @@ -160,29 +197,29 @@ Otherwise, it will build the model in the current directory and run the prediction on that. ``` -cog predict [image] [flags] +cog run [image] [flags] ``` **Examples** ``` # Run a prediction with named inputs - cog predict -i prompt="a photo of a cat" + cog run -i prompt="a photo of a cat" # Pass a file as input - cog predict -i image=@photo.jpg + cog run -i image=@photo.jpg # Save output to a file - cog predict -i image=@input.jpg -o output.png + cog run -i image=@input.jpg -o output.png # Pass multiple inputs - cog predict -i prompt="sunset" -i width=1024 -i height=768 + cog run -i prompt="sunset" -i width=1024 -i height=768 # Run against a pre-built image - cog predict r8.im/your-username/my-model -i prompt="hello" + cog run r8.im/your-username/my-model -i prompt="hello" # Pass inputs as JSON - echo '{"prompt": "a cat"}' | cog predict --json @- + echo '{"prompt": "a cat"}' | cog run --json @- ``` **Options** @@ -191,7 +228,7 @@ cog predict [image] [flags] -e, --env stringArray Environment variables, in the form name=value -f, --file string The name of the config file. (default "cog.yaml") --gpus docker run --gpus GPU devices to add to the container, in the same format as docker run --gpus. - -h, --help help for predict + -h, --help help for run -i, --input stringArray Inputs, in the form name=value. if value is prefixed with @, then it is read from a file on disk. E.g. -i path=@image.jpg --json string Pass inputs as JSON object, read from file (@inputs.json) or via stdin (@-) -o, --output string Output path @@ -201,43 +238,6 @@ cog predict [image] [flags] --use-cuda-base-image string Use Nvidia CUDA base image, 'true' (default) or 'false' (use python base image). False results in a smaller image but may cause problems for non-torch projects (default "auto") --use-replicate-token Pass REPLICATE_API_TOKEN from local environment into the model context ``` -## `cog push` - -Build a Docker image from cog.yaml and push it to a container registry. - -Cog can push to any OCI-compliant registry. When pushing to Replicate's -registry (r8.im), run 'cog login' first to authenticate. - -``` -cog push [IMAGE] [flags] -``` - -**Examples** - -``` - # Push to Replicate - cog push r8.im/your-username/my-model - - # Push to any OCI registry - cog push registry.example.com/your-username/model-name - - # Push with model weights in a separate layer (Replicate only) - cog push r8.im/your-username/my-model --separate-weights -``` - -**Options** - -``` - -f, --file string The name of the config file. (default "cog.yaml") - -h, --help help for push - --no-cache Do not use cache when building the image - --openapi-schema string Load OpenAPI schema from a file - --progress string Set type of build progress output, 'auto' (default), 'tty', 'plain', or 'quiet' (default "auto") - --secret stringArray Secrets to pass to the build environment in the form 'id=foo,src=/path/to/file' - --separate-weights Separate model weights from code in image layers - --use-cog-base-image Use pre-built Cog base image for faster cold boots (default true) - --use-cuda-base-image string Use Nvidia CUDA base image, 'true' (default) or 'false' (use python base image). False results in a smaller image but may cause problems for non-torch projects (default "auto") -``` ## `cog serve` Run a prediction HTTP server. diff --git a/docs/llms.txt b/docs/llms.txt index 73975c6beb..98097f8576 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -28,22 +28,22 @@ build: - "libglib2.0-0" python_version: "3.13" python_requirements: requirements.txt -predict: "predict.py:Predictor" +run: "run.py:Runner" ``` -Define how predictions are run on your model with `predict.py`: +Define how predictions are run on your model with `run.py`: ```python -from cog import BasePredictor, Input, Path +from cog import BaseRunner, Input, Path import torch -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): """Load the model into memory to make running multiple predictions efficient""" self.model = torch.load("./weights.pth") # The arguments and types the model takes as input - def predict(self, + def run(self, image: Path = Input(description="Grayscale input image") ) -> Path: """Run a single prediction on the model""" @@ -57,7 +57,7 @@ In the above we accept a path to the image as an input, and return a path to our Now, you can run predictions on this model: ```console -$ cog predict -i image=@input.jpg +$ cog run -i image=@input.jpg --> Building Docker image... --> Running Prediction... --> Output written to output.jpg @@ -180,7 +180,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for how to set up a development environme - [Take a look at some examples of using Cog](https://github.com/replicate/cog-examples) - [Deploy models with Cog](docs/deploy.md) - [`cog.yaml` reference](docs/yaml.md) to learn how to define your model's environment -- [Prediction interface reference](docs/python.md) to learn how the `Predictor` interface works +- [Prediction interface reference](docs/python.md) to learn how the `Runner` interface works - [Training interface reference](docs/training.md) to learn how to add a fine-tuning API to your model - [HTTP API reference](docs/http.md) to learn how to use the HTTP API that models serve @@ -225,7 +225,7 @@ https://github.com/replicate/cog Build a Docker image from the cog.yaml in the current directory. The generated image contains your model code, dependencies, and the Cog -runtime. It can be run locally with 'cog predict' or pushed to a registry +runtime. It can be run locally with 'cog run' or pushed to a registry with 'cog push'. ``` @@ -304,7 +304,7 @@ cog exec [arg...] [flags] ``` ## `cog init` -Create a cog.yaml and predict.py in the current directory. +Create a cog.yaml and run.py in the current directory. These files provide a starting template for defining your model's environment and prediction interface. Edit them to match your model's requirements. @@ -345,7 +345,44 @@ cog login [flags] -h, --help help for login --token-stdin Pass login token on stdin instead of opening a browser. You can find your Replicate login token at https://replicate.com/auth/token ``` -## `cog predict` +## `cog push` + +Build a Docker image from cog.yaml and push it to a container registry. + +Cog can push to any OCI-compliant registry. When pushing to Replicate's +registry (r8.im), run 'cog login' first to authenticate. + +``` +cog push [IMAGE] [flags] +``` + +**Examples** + +``` + # Push to Replicate + cog push r8.im/your-username/my-model + + # Push to any OCI registry + cog push registry.example.com/your-username/model-name + + # Push with model weights in a separate layer (Replicate only) + cog push r8.im/your-username/my-model --separate-weights +``` + +**Options** + +``` + -f, --file string The name of the config file. (default "cog.yaml") + -h, --help help for push + --no-cache Do not use cache when building the image + --openapi-schema string Load OpenAPI schema from a file + --progress string Set type of build progress output, 'auto' (default), 'tty', 'plain', or 'quiet' (default "auto") + --secret stringArray Secrets to pass to the build environment in the form 'id=foo,src=/path/to/file' + --separate-weights Separate model weights from code in image layers + --use-cog-base-image Use pre-built Cog base image for faster cold boots (default true) + --use-cuda-base-image string Use Nvidia CUDA base image, 'true' (default) or 'false' (use python base image). False results in a smaller image but may cause problems for non-torch projects (default "auto") +``` +## `cog run` Run a prediction. @@ -356,29 +393,29 @@ Otherwise, it will build the model in the current directory and run the prediction on that. ``` -cog predict [image] [flags] +cog run [image] [flags] ``` **Examples** ``` # Run a prediction with named inputs - cog predict -i prompt="a photo of a cat" + cog run -i prompt="a photo of a cat" # Pass a file as input - cog predict -i image=@photo.jpg + cog run -i image=@photo.jpg # Save output to a file - cog predict -i image=@input.jpg -o output.png + cog run -i image=@input.jpg -o output.png # Pass multiple inputs - cog predict -i prompt="sunset" -i width=1024 -i height=768 + cog run -i prompt="sunset" -i width=1024 -i height=768 # Run against a pre-built image - cog predict r8.im/your-username/my-model -i prompt="hello" + cog run r8.im/your-username/my-model -i prompt="hello" # Pass inputs as JSON - echo '{"prompt": "a cat"}' | cog predict --json @- + echo '{"prompt": "a cat"}' | cog run --json @- ``` **Options** @@ -387,7 +424,7 @@ cog predict [image] [flags] -e, --env stringArray Environment variables, in the form name=value -f, --file string The name of the config file. (default "cog.yaml") --gpus docker run --gpus GPU devices to add to the container, in the same format as docker run --gpus. - -h, --help help for predict + -h, --help help for run -i, --input stringArray Inputs, in the form name=value. if value is prefixed with @, then it is read from a file on disk. E.g. -i path=@image.jpg --json string Pass inputs as JSON object, read from file (@inputs.json) or via stdin (@-) -o, --output string Output path @@ -397,43 +434,6 @@ cog predict [image] [flags] --use-cuda-base-image string Use Nvidia CUDA base image, 'true' (default) or 'false' (use python base image). False results in a smaller image but may cause problems for non-torch projects (default "auto") --use-replicate-token Pass REPLICATE_API_TOKEN from local environment into the model context ``` -## `cog push` - -Build a Docker image from cog.yaml and push it to a container registry. - -Cog can push to any OCI-compliant registry. When pushing to Replicate's -registry (r8.im), run 'cog login' first to authenticate. - -``` -cog push [IMAGE] [flags] -``` - -**Examples** - -``` - # Push to Replicate - cog push r8.im/your-username/my-model - - # Push to any OCI registry - cog push registry.example.com/your-username/model-name - - # Push with model weights in a separate layer (Replicate only) - cog push r8.im/your-username/my-model --separate-weights -``` - -**Options** - -``` - -f, --file string The name of the config file. (default "cog.yaml") - -h, --help help for push - --no-cache Do not use cache when building the image - --openapi-schema string Load OpenAPI schema from a file - --progress string Set type of build progress output, 'auto' (default), 'tty', 'plain', or 'quiet' (default "auto") - --secret stringArray Secrets to pass to the build environment in the form 'id=foo,src=/path/to/file' - --separate-weights Separate model weights from code in image layers - --use-cog-base-image Use pre-built Cog base image for faster cold boots (default true) - --use-cuda-base-image string Use Nvidia CUDA base image, 'true' (default) or 'false' (use python base image). False results in a smaller image but may cause problems for non-torch projects (default "auto") -``` ## `cog serve` Run a prediction HTTP server. @@ -726,7 +726,7 @@ sudo chmod +x /usr/local/bin/cog To configure your project for use with Cog, you'll need to add two files: - [`cog.yaml`](yaml.md) defines system requirements, Python package dependencies, etc -- [`predict.py`](python.md) describes the prediction interface for your model +- [`run.py`](python.md) describes the prediction interface for your model Use the `cog init` command to generate these files in your project: @@ -775,18 +775,18 @@ With `cog.yaml`, you can also install system packages and other things. [Take a ## Define how to run predictions -The next step is to update `predict.py` to define the interface for running predictions on your model. The `predict.py` generated by `cog init` looks something like this: +The next step is to update `run.py` to define the interface for running predictions on your model. The `run.py` generated by `cog init` looks something like this: ```python -from cog import BasePredictor, Path, Input +from cog import BaseRunner, Path, Input import torch -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): """Load the model into memory to make running multiple predictions efficient""" self.net = torch.load("weights.pth") - def predict(self, + def run(self, image: Path = Input(description="Image to enlarge"), scale: float = Input(description="Factor to scale image by", default=1.5) ) -> Path: @@ -797,9 +797,9 @@ class Predictor(BasePredictor): return output ``` -Edit your `predict.py` file and fill in the functions with your own model's setup and prediction code. You might need to import parts of your model from another file. +Edit your `run.py` file and fill in the functions with your own model's setup and prediction code. You might need to import parts of your model from another file. -You also need to define the inputs to your model as arguments to the `predict()` function, as demonstrated above. For each argument, you need to annotate with a type. The supported types are: +You also need to define the inputs to your model as arguments to the `run()` function, as demonstrated above. For each argument, you need to annotate with a type. The supported types are: - `str`: a string - `int`: an integer @@ -822,19 +822,19 @@ You can provide more information about the input with the `Input()` function, as There are some more advanced options you can pass, too. For more details, [take a look at the prediction interface documentation](python.md). -Next, add the line `predict: "predict.py:Predictor"` to your `cog.yaml`, so it looks something like this: +Next, add the line `run: "run.py:Runner"` to your `cog.yaml`, so it looks something like this: ```yaml build: python_version: "3.13" python_requirements: requirements.txt -predict: "predict.py:Predictor" +run: "run.py:Runner" ``` That's it! To test this works, try running a prediction on the model: ``` -$ cog predict -i image=@input.jpg +$ cog run -i image=@input.jpg ✓ Building Docker image from cog.yaml... Successfully built 664ef88bc1f4 ✓ Model running in Docker image 664ef88bc1f4 @@ -844,7 +844,7 @@ Written output to output.png To pass more inputs to the model, you can add more `-i` options: ``` -$ cog predict -i image=@image.jpg -i scale=2.0 +$ cog run -i image=@image.jpg -i scale=2.0 ``` In this case it is just a number, not a file, so you don't need the `@` prefix. @@ -967,28 +967,28 @@ Let's pretend we've trained a model. With Cog, we can define how to run predicti We need to write some code to describe how predictions are run on the model. -Save this to `predict.py`: +Save this to `run.py`: ```python import os os.environ["TORCH_HOME"] = "." import torch -from cog import BasePredictor, Input, Path +from cog import BaseRunner, Input, Path from PIL import Image from torchvision import models WEIGHTS = models.ResNet50_Weights.IMAGENET1K_V1 -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): """Load the model into memory to make running multiple predictions efficient""" self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") self.model = models.resnet50(weights=WEIGHTS).to(self.device) self.model.eval() - def predict(self, image: Path = Input(description="Image to classify")) -> dict: + def run(self, image: Path = Input(description="Image to classify")) -> dict: """Run a single prediction on the model""" img = Image.open(image).convert("RGB") preds = self.model(WEIGHTS.transforms()(img).unsqueeze(0).to(self.device)) @@ -1013,7 +1013,7 @@ Then update `cog.yaml` to look like this: build: python_version: "3.13" python_requirements: requirements.txt -predict: "predict.py:Predictor" +run: "run.py:Runner" ``` > [!TIP] @@ -1030,7 +1030,7 @@ curl $IMAGE_URL > input.jpg Now, let's run the model using Cog: ```bash -cog predict -i image=@input.jpg +cog run -i image=@input.jpg ``` @@ -1046,7 +1046,7 @@ If you see the following output then it worked! -Note: The first time you run `cog predict`, the build process will be triggered to generate a Docker container that can run your model. The next time you run `cog predict` the pre-built container will be used. +Note: The first time you run `cog run`, the build process will be triggered to generate a Docker container that can run your model. The next time you run `cog run` the pre-built container will be used. ## Build an image @@ -1059,10 +1059,10 @@ cog build -t resnet ``` -You can run this image with `cog predict` by passing the filename as an argument: +You can run this image with `cog run` by passing the filename as an argument: ```bash -cog predict resnet -i image=@input.jpg +cog run resnet -i image=@input.jpg ``` @@ -1190,10 +1190,10 @@ at the following times. Once, when the prediction starts (`status` is `starting`). - `output`: - Each time a predict function generates an output + Each time a run function generates an output (either once using `return` or multiple times using `yield`) - `logs`: - Each time the predict function writes to `stdout` + Each time the run function writes to `stdout` - `completed`: Once, when the prediction reaches a terminal state (`status` is `succeeded`, `canceled`, or `failed`) @@ -1248,7 +1248,7 @@ This produces a random identifier that is 26 ASCII characters long. ## File uploads -A model's `predict` function can produce file output by yielding or returning +A model's `run` function can produce file output by yielding or returning a `cog.Path` or `cog.File` value. By default, @@ -1409,7 +1409,7 @@ Content-Type: application/json The [OpenAPI](https://swagger.io/specification/) specification of the API, which is derived from the input and output types specified in your model's -[Predictor](python.md) and [Training](training.md) objects. +[Runner](python.md) and [Training](training.md) objects. ### `POST /predictions` @@ -1419,13 +1419,13 @@ The request body is a JSON object with the following fields: - `input`: A JSON object with the same keys as the - [arguments to the `predict()` function](python.md). + [arguments to the `run()` function](python.md). Any `File` or `Path` inputs are passed as URLs. The response body is a JSON object with the following fields: - `status`: Either `succeeded` or `failed`. -- `output`: The return value of the `predict()` function. +- `output`: The return value of the `run()` function. - `error`: If `status` is `failed`, the error message. - `metrics`: An object containing prediction metrics. Always includes `predict_time` (elapsed seconds). @@ -1564,17 +1564,17 @@ Otherwise, the server responds with status `404 Not Found`. When a prediction is canceled, Cog raises [`CancelationException`](python.md#cancelationexception) -in sync predictors (or `asyncio.CancelledError` in async predictors). +in sync runners (or `asyncio.CancelledError` in async runners). This exception may be caught by the model to perform necessary cleanup. The cleanup should be brief, ideally completing within a few seconds. After cleanup, the exception must be re-raised using a bare `raise` statement. Failure to re-raise the exception may result in the termination of the container. ```python -from cog import BasePredictor, CancelationException, Input, Path +from cog import BaseRunner, CancelationException, Input, Path -class Predictor(BasePredictor): - def predict(self, image: Path = Input(description="Image to process")) -> Path: +class Runner(BaseRunner): + def run(self, image: Path = Input(description="Image to process")) -> Path: try: return self.process(image) except CancelationException: @@ -1614,9 +1614,9 @@ Cog can run notebooks in the environment you've defined in `cog.yaml` with the f cog exec -p 8888 jupyter lab --allow-root --ip=0.0.0.0 ``` -## Use notebook code in your predictor +## Use notebook code in your runner -You can also import a notebook into your Cog [Predictor](python.md) file. +You can also import a notebook into your Cog [Runner](python.md) file. First, export your notebook to a Python file: @@ -1624,15 +1624,15 @@ First, export your notebook to a Python file: jupyter nbconvert --to script my_notebook.ipynb # creates my_notebook.py ``` -Then import the exported Python script into your `predict.py` file. Any functions or variables defined in your notebook will be available to your predictor: +Then import the exported Python script into your `run.py` file. Any functions or variables defined in your notebook will be available to your runner: ```python -from cog import BasePredictor, Input +from cog import BaseRunner, Input import my_notebook -class Predictor(BasePredictor): - def predict(self, prompt: str = Input(description="string prompt")) -> str: +class Runner(BaseRunner): + def run(self, prompt: str = Input(description="string prompt")) -> str: output = my_notebook.do_stuff(prompt) return output ``` @@ -1691,7 +1691,7 @@ Using a secret mount allows the private registry credentials to be securely pass This document defines the API of the `cog` Python module, which is used to define the interface for running predictions on your model. > [!TIP] -> Run [`cog init`](getting-started-own-model.md#initialization) to generate an annotated `predict.py` file that can be used as a starting point for setting up your model. +> Run [`cog init`](getting-started-own-model.md#initialization) to generate an annotated `run.py` file that can be used as a starting point for setting up your model. > [!TIP] > Using a language model to help you write the code for your new Cog model? @@ -1701,10 +1701,10 @@ This document defines the API of the `cog` Python module, which is used to defin ## Contents - [Contents](#contents) -- [`BasePredictor`](#basepredictor) - - [`Predictor.setup()`](#predictorsetup) - - [`Predictor.predict(**kwargs)`](#predictorpredictkwargs) -- [`async` predictors and concurrency](#async-predictors-and-concurrency) +- [`BaseRunner`](#baserunner) + - [`Runner.setup()`](#runnersetup) + - [`Runner.run(**kwargs)`](#runnerrunkwargs) +- [`async` runners and concurrency](#async-runners-and-concurrency) - [`Input(**kwargs)`](#inputkwargs) - [Deprecating inputs](#deprecating-inputs) - [Output](#output) @@ -1734,20 +1734,20 @@ This document defines the API of the `cog` Python module, which is used to defin - [`BaseModel` field types](#basemodel-field-types) - [Type limitations](#type-limitations) -## `BasePredictor` +## `BaseRunner` -You define how Cog runs predictions on your model by defining a class that inherits from `BasePredictor`. It looks something like this: +You define how Cog runs predictions on your model by defining a class that inherits from `BaseRunner`. It looks something like this: ```python -from cog import BasePredictor, Path, Input +from cog import BaseRunner, Path, Input import torch -class Predictor(BasePredictor): +class Runner(BaseRunner): def setup(self): """Load the model into memory to make running multiple predictions efficient""" self.model = torch.load("weights.pth") - def predict(self, + def run(self, image: Path = Input(description="Image to enlarge"), scale: float = Input(description="Factor to scale image by", default=1.5) ) -> Path: @@ -1758,9 +1758,12 @@ class Predictor(BasePredictor): return output ``` -Your Predictor class should define two methods: `setup()` and `predict()`. +> [!NOTE] +> `BasePredictor` and `predict()` still work for backwards compatibility, but `BaseRunner` and `run()` are preferred for new models. + +Your Runner class should define two methods: `setup()` and `run()`. -### `Predictor.setup()` +### `Runner.setup()` Prepare the model so multiple predictions run efficiently. @@ -1784,43 +1787,43 @@ While this will increase your image size and build time, it offers other advanta > When using this method, you should use the `--separate-weights` flag on `cog build` to store weights in a [separate layer](https://github.com/replicate/cog/blob/12ac02091d93beebebed037f38a0c99cd8749806/docs/getting-started.md?plain=1#L219). -### `Predictor.predict(**kwargs)` +### `Runner.run(**kwargs)` Run a single prediction. This _required_ method is where you call the model that was loaded during `setup()`, but you may also want to add pre- and post-processing code here. -The `predict()` method takes an arbitrary list of named arguments, where each argument name must correspond to an [`Input()`](#inputkwargs) annotation. +The `run()` method takes an arbitrary list of named arguments, where each argument name must correspond to an [`Input()`](#inputkwargs) annotation. -`predict()` can return strings, numbers, [`cog.Path`](#cogpath) objects representing files on disk, or lists or dicts of those types. You can also define a custom [`BaseModel`](#structured-output-with-basemodel) for structured return types. See [Input and output types](#input-and-output-types) for the full list of supported types. +`run()` can return strings, numbers, [`cog.Path`](#cogpath) objects representing files on disk, or lists or dicts of those types. You can also define a custom [`BaseModel`](#structured-output-with-basemodel) for structured return types. See [Input and output types](#input-and-output-types) for the full list of supported types. -## `async` predictors and concurrency +## `async` runners and concurrency > Added in cog 0.14.0. -You may specify your `predict()` method as `async def predict(...)`. In -addition, if you have an async `predict()` function you may also have an async +You may specify your `run()` method as `async def run(...)`. In +addition, if you have an async `run()` function you may also have an async `setup()` function: ```py -class Predictor(BasePredictor): +class Runner(BaseRunner): async def setup(self) -> None: print("async setup is also supported...") - async def predict(self) -> str: - print("async predict"); + async def run(self) -> str: + print("async run"); return "hello world"; ``` -Models that have an async `predict()` function can run predictions concurrently, up to the limit specified by [`concurrency.max`](yaml.md#max) in cog.yaml. Attempting to exceed this limit will return a 409 Conflict response. +Models that have an async `run()` function can run predictions concurrently, up to the limit specified by [`concurrency.max`](yaml.md#max) in cog.yaml. Attempting to exceed this limit will return a 409 Conflict response. ## `Input(**kwargs)` -Use cog's `Input()` function to define each of the parameters in your `predict()` method: +Use cog's `Input()` function to define each of the parameters in your `run()` method: ```py -class Predictor(BasePredictor): - def predict(self, +class Runner(BaseRunner): + def run(self, image: Path = Input(description="Image to enlarge"), scale: float = Input(description="Factor to scale image by", default=1.5, ge=1.0, le=10.0) ) -> Path: @@ -1838,13 +1841,13 @@ The `Input()` function takes these keyword arguments: - `choices`: For `str` or `int` types, a list of possible values for this input. - `deprecated`: (optional) If set to `True`, marks this input as deprecated. Deprecated inputs will still be accepted, but tools and UIs may warn users that the input is deprecated and may be removed in the future. See [Deprecating inputs](#deprecating-inputs). -Each parameter of the `predict()` method must be annotated with a type like `str`, `int`, `float`, `bool`, etc. See [Input and output types](#input-and-output-types) for the full list of supported types. +Each parameter of the `run()` method must be annotated with a type like `str`, `int`, `float`, `bool`, etc. See [Input and output types](#input-and-output-types) for the full list of supported types. Using the `Input` function provides better documentation and validation constraints to the users of your model, but it is not strictly required. You can also specify default values for your parameters using plain Python, or omit default assignment entirely: ```py -class Predictor(BasePredictor): - def predict(self, +class Runner(BaseRunner): + def run(self, prompt: str = "default prompt", # this is valid iterations: int # also valid ) -> str: @@ -1858,10 +1861,10 @@ You can mark an input as deprecated by passing `deprecated=True` to the `Input() This is useful when you want to phase out an input without breaking existing clients immediately: ```py -from cog import BasePredictor, Input +from cog import BaseRunner, Input -class Predictor(BasePredictor): - def predict(self, +class Runner(BaseRunner): + def run(self, text: str = Input(description="Some deprecated text", deprecated=True), prompt: str = Input(description="Prompt for the model") ) -> str: @@ -1871,31 +1874,31 @@ class Predictor(BasePredictor): ## Output -Cog predictors can return a simple data type like a string, number, float, or boolean. Use Python's `-> ` syntax to annotate the return type. +Cog runners can return a simple data type like a string, number, float, or boolean. Use Python's `-> ` syntax to annotate the return type. -Here's an example of a predictor that returns a string: +Here's an example of a runner that returns a string: ```py -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self) -> str: +class Runner(BaseRunner): + def run(self) -> str: return "hello" ``` ### Returning an object -To return a complex object with multiple values, define an `Output` object with multiple fields to return from your `predict()` method: +To return a complex object with multiple values, define an `Output` object with multiple fields to return from your `run()` method: ```py -from cog import BasePredictor, BaseModel, File +from cog import BaseRunner, BaseModel, File class Output(BaseModel): file: File text: str -class Predictor(BasePredictor): - def predict(self) -> Output: +class Runner(BaseRunner): + def run(self) -> Output: return Output(text="hello", file=io.StringIO("hello")) ``` @@ -1903,13 +1906,13 @@ Each of the output object's properties must be one of the supported output types ### Returning a list -The `predict()` method can return a list of any of the supported output types. Here's an example that outputs multiple files: +The `run()` method can return a list of any of the supported output types. Here's an example that outputs multiple files: ```py -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): - def predict(self) -> list[Path]: +class Runner(BaseRunner): + def run(self) -> list[Path]: predictions = ["foo", "bar", "baz"] output = [] for i, prediction in enumerate(predictions): @@ -1927,15 +1930,15 @@ Files are named in the format `output..`, e.g. `output.0.txt`, To conditionally omit properties from the Output object, define them using `typing.Optional`: ```py -from cog import BaseModel, BasePredictor, Path +from cog import BaseModel, BaseRunner, Path from typing import Optional class Output(BaseModel): score: Optional[float] file: Optional[Path] -class Predictor(BasePredictor): - def predict(self) -> Output: +class Runner(BaseRunner): + def run(self) -> Output: if condition: return Output(score=1.5) else: @@ -1944,30 +1947,30 @@ class Predictor(BasePredictor): ### Streaming output -Cog models can stream output as the `predict()` method is running. For example, a language model can output tokens as they're being generated and an image generation model can output images as they are being generated. +Cog models can stream output as the `run()` method is running. For example, a language model can output tokens as they're being generated and an image generation model can output images as they are being generated. -To support streaming output in your Cog model, add `from typing import Iterator` to your predict.py file. The `typing` package is a part of Python's standard library so it doesn't need to be installed. Then add a return type annotation to the `predict()` method in the form `-> Iterator[]` where `` can be one of `str`, `int`, `float`, `bool`, or `cog.Path`. +To support streaming output in your Cog model, add `from typing import Iterator` to your run.py file. The `typing` package is a part of Python's standard library so it doesn't need to be installed. Then add a return type annotation to the `run()` method in the form `-> Iterator[]` where `` can be one of `str`, `int`, `float`, `bool`, or `cog.Path`. ```py -from cog import BasePredictor, Path +from cog import BaseRunner, Path from typing import Iterator -class Predictor(BasePredictor): - def predict(self) -> Iterator[Path]: +class Runner(BaseRunner): + def run(self) -> Iterator[Path]: done = False while not done: output_path, done = do_stuff() yield Path(output_path) ``` -If you have an [async `predict()` method](#async-predictors-and-concurrency), use `AsyncIterator` from the `typing` module: +If you have an [async `run()` method](#async-runners-and-concurrency), use `AsyncIterator` from the `typing` module: ```py from typing import AsyncIterator -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): - async def predict(self) -> AsyncIterator[Path]: +class Runner(BaseRunner): + async def run(self) -> AsyncIterator[Path]: done = False while not done: output_path, done = do_stuff() @@ -1977,22 +1980,22 @@ class Predictor(BasePredictor): If you're streaming text output, you can use `ConcatenateIterator` to hint that the output should be concatenated together into a single string. This is useful on Replicate to display the output as a string instead of a list of strings. ```py -from cog import BasePredictor, Path, ConcatenateIterator +from cog import BaseRunner, Path, ConcatenateIterator -class Predictor(BasePredictor): - def predict(self) -> ConcatenateIterator[str]: +class Runner(BaseRunner): + def run(self) -> ConcatenateIterator[str]: tokens = ["The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"] for token in tokens: yield token + " " ``` -Or for async `predict()` methods, use `AsyncConcatenateIterator`: +Or for async `run()` methods, use `AsyncConcatenateIterator`: ```py -from cog import BasePredictor, Path, AsyncConcatenateIterator +from cog import BaseRunner, Path, AsyncConcatenateIterator -class Predictor(BasePredictor): - async def predict(self) -> AsyncConcatenateIterator[str]: +class Runner(BaseRunner): + async def run(self) -> AsyncConcatenateIterator[str]: tokens = ["The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"] for token in tokens: yield token + " " @@ -2000,17 +2003,17 @@ class Predictor(BasePredictor): ## Metrics -You can record custom metrics from your `predict()` function to track model-specific data like token counts, timing breakdowns, or confidence scores. Metrics are included in the prediction response alongside the output. +You can record custom metrics from your `run()` function to track model-specific data like token counts, timing breakdowns, or confidence scores. Metrics are included in the prediction response alongside the output. ### Recording metrics -Use `self.record_metric()` inside your `predict()` method: +Use `self.record_metric()` inside your `run()` method: ```python -from cog import BasePredictor +from cog import BaseRunner -class Predictor(BasePredictor): - def predict(self, prompt: str) -> str: +class Runner(BaseRunner): + def run(self, prompt: str) -> str: self.record_metric("temperature", 0.7) self.record_metric("token_count", 42) @@ -2126,12 +2129,12 @@ Outside an active prediction, `self.record_metric()` and `self.scope` are silent ## Cancellation -When a prediction is canceled (via the [cancel HTTP endpoint](http.md#post-predictionsprediction_idcancel) or a dropped connection), the Cog runtime interrupts the running `predict()` function. The exception raised depends on whether the predictor is sync or async: +When a prediction is canceled (via the [cancel HTTP endpoint](http.md#post-predictionsprediction_idcancel) or a dropped connection), the Cog runtime interrupts the running `run()` function. The exception raised depends on whether the runner is sync or async: -| Predictor type | Exception raised | +| Runner type | Exception raised | | --------------------------- | ------------------------ | -| Sync (`def predict`) | `CancelationException` | -| Async (`async def predict`) | `asyncio.CancelledError` | +| Sync (`def run`) | `CancelationException` | +| Async (`async def run`) | `asyncio.CancelledError` | ### `CancelationException` @@ -2139,15 +2142,15 @@ When a prediction is canceled (via the [cancel HTTP endpoint](http.md#post-predi from cog import CancelationException ``` -`CancelationException` is raised in **sync** predictors when a prediction is cancelled. It is a `BaseException` subclass — **not** an `Exception` subclass. This means bare `except Exception` blocks in your predict code will not accidentally catch it, matching the behavior of `KeyboardInterrupt` and `asyncio.CancelledError`. +`CancelationException` is raised in **sync** runners when a prediction is cancelled. It is a `BaseException` subclass — **not** an `Exception` subclass. This means bare `except Exception` blocks in your run code will not accidentally catch it, matching the behavior of `KeyboardInterrupt` and `asyncio.CancelledError`. -You do **not** need to handle this exception in normal predictor code — the runtime manages cancellation automatically. However, if you need to run cleanup logic when a prediction is cancelled, you can catch it explicitly: +You do **not** need to handle this exception in normal runner code — the runtime manages cancellation automatically. However, if you need to run cleanup logic when a prediction is cancelled, you can catch it explicitly: ```python -from cog import BasePredictor, CancelationException, Path +from cog import BaseRunner, CancelationException, Path -class Predictor(BasePredictor): - def predict(self, image: Path) -> Path: +class Runner(BaseRunner): + def run(self, image: Path) -> Path: try: return self.process(image) except CancelationException: @@ -2163,11 +2166,11 @@ class Predictor(BasePredictor): - `cog.CancelationException` (recommended) - `cog.exceptions.CancelationException` -For **async** predictors, cancellation follows standard Python async conventions and raises `asyncio.CancelledError` instead. +For **async** runners, cancellation follows standard Python async conventions and raises `asyncio.CancelledError` instead. ## Input and output types -Each parameter of the `predict()` method must be annotated with a type. The method's return type must also be annotated. +Each parameter of the `run()` method must be annotated with a type. The method's return type must also be annotated. ### Primitive types @@ -2195,10 +2198,10 @@ This example takes an input file, resizes it, and returns the resized image: ```python import tempfile -from cog import BasePredictor, Input, Path +from cog import BaseRunner, Input, Path -class Predictor(BasePredictor): - def predict(self, image: Path = Input(description="Image to enlarge")) -> Path: +class Runner(BaseRunner): + def run(self, image: Path = Input(description="Image to enlarge")) -> Path: upscaled_image = do_some_processing(image) # To output cog.Path objects the file needs to exist, so create a temporary file first. @@ -2216,11 +2219,11 @@ class Predictor(BasePredictor): `cog.File` represents a _file handle_. For models that return a `cog.File` object, the prediction output returned by Cog's built-in HTTP server will be a URL. ```python -from cog import BasePredictor, File, Input +from cog import BaseRunner, File, Input from PIL import Image -class Predictor(BasePredictor): - def predict(self, source_image: File = Input(description="Image to enlarge")) -> File: +class Runner(BaseRunner): + def run(self, source_image: File = Input(description="Image to enlarge")) -> File: pillow_img = Image.open(source_image) upscaled_image = do_some_processing(pillow_img) return File(upscaled_image) @@ -2233,10 +2236,10 @@ class Predictor(BasePredictor): `cog.Secret` redacts its contents in string representations to prevent accidental disclosure. Access the underlying value with `get_secret_value()`. ```python -from cog import BasePredictor, Secret +from cog import BaseRunner, Secret -class Predictor(BasePredictor): - def predict(self, api_token: Secret) -> None: +class Runner(BaseRunner): + def run(self, api_token: Secret) -> None: # Prints '**********' print(api_token) @@ -2244,7 +2247,7 @@ class Predictor(BasePredictor): print(api_token.get_secret_value()) ``` -A predictor's `Secret` inputs are represented in OpenAPI with the following schema: +A runner's `Secret` inputs are represented in OpenAPI with the following schema: ```json { @@ -2270,10 +2273,10 @@ Use `Optional[T]` or `T | None` (Python 3.10+) to mark an input as optional. Opt ```python from typing import Optional -from cog import BasePredictor, Input +from cog import BaseRunner, Input -class Predictor(BasePredictor): - def predict(self, +class Runner(BaseRunner): + def run(self, prompt: Optional[str] = Input(description="Input prompt"), seed: int | None = Input(description="Random seed", default=None), ) -> str: @@ -2286,11 +2289,11 @@ Prefer `Optional[T]` or `T | None` over `str = Input(default=None)` for inputs t ```python # Bad: type annotation says str but value can be None -def predict(self, prompt: str = Input(default=None)) -> str: +def run(self, prompt: str = Input(default=None)) -> str: return "hello" + prompt # TypeError at runtime if prompt is None # Good: type annotation matches actual behavior -def predict(self, prompt: Optional[str] = Input(description="prompt")) -> str: +def run(self, prompt: Optional[str] = Input(description="prompt")) -> str: if prompt is None: return "hello" return "hello " + prompt @@ -2306,10 +2309,10 @@ Use `list[T]` or `List[T]` to accept or return a list of values. `T` can be a su **As an input type:** ```py -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): - def predict(self, paths: list[Path]) -> str: +class Runner(BaseRunner): + def run(self, paths: list[Path]) -> str: output_parts = [] for path in paths: with open(path) as f: @@ -2317,21 +2320,21 @@ class Predictor(BasePredictor): return "".join(output_parts) ``` -With `cog predict`, repeat the input name to pass multiple values: +With `cog run`, repeat the input name to pass multiple values: ```bash $ echo test1 > 1.txt $ echo test2 > 2.txt -$ cog predict -i paths=@1.txt -i paths=@2.txt +$ cog run -i paths=@1.txt -i paths=@2.txt ``` **As an output type:** ```py -from cog import BasePredictor, Path +from cog import BaseRunner, Path -class Predictor(BasePredictor): - def predict(self) -> list[Path]: +class Runner(BaseRunner): + def run(self) -> list[Path]: predictions = ["foo", "bar", "baz"] output = [] for i, prediction in enumerate(predictions): @@ -2349,10 +2352,10 @@ Files are named in the format `output..`, e.g. `output.0.txt`, Use `dict` to accept or return an opaque JSON object. The value is passed through as-is without type validation. ```python -from cog import BasePredictor, Input +from cog import BaseRunner, Input -class Predictor(BasePredictor): - def predict(self, +class Runner(BaseRunner): + def run(self, params: dict = Input(description="Arbitrary JSON parameters"), ) -> dict: return {"greeting": "hello", "params": params} @@ -2371,15 +2374,15 @@ To return a complex object with multiple typed fields, define a class that inher ```python from typing import Optional -from cog import BasePredictor, BaseModel, Path +from cog import BaseRunner, BaseModel, Path class Output(BaseModel): text: str confidence: float image: Optional[Path] -class Predictor(BasePredictor): - def predict(self, prompt: str) -> Output: +class Runner(BaseRunner): + def run(self, prompt: str) -> Output: result = self.model.generate(prompt) return Output( text=result.text, @@ -2405,15 +2408,15 @@ If you already use Pydantic v2 in your model, you can use a Pydantic `BaseModel` ```python from pydantic import BaseModel as PydanticBaseModel -from cog import BasePredictor +from cog import BaseRunner class Result(PydanticBaseModel): name: str score: float tags: list[str] -class Predictor(BasePredictor): - def predict(self, prompt: str) -> Result: +class Runner(BaseRunner): + def run(self, prompt: str) -> Result: return Result(name="example", score=0.95, tags=["fast", "accurate"]) ``` @@ -2452,7 +2455,7 @@ Cog's training API allows you to define a fine-tuning interface for an existing ## How it works -If you've used Cog before, you've probably seen the [Predictor](./python.md) class, which defines the interface for creating predictions against your model. Cog's training API works similarly: You define a Python function that describes the inputs and outputs of the training process. The inputs are things like training data, epochs, batch size, seed, etc. The output is typically a file with the fine-tuned weights. +If you've used Cog before, you've probably seen the [Runner](./python.md) class, which defines the interface for running predictions against your model. Cog's training API works similarly: You define a Python function that describes the inputs and outputs of the training process. The inputs are things like training data, epochs, batch size, seed, etc. The output is typically a file with the fine-tuned weights. `cog.yaml`: @@ -2465,7 +2468,7 @@ train: "train.py:train" `train.py`: ```python -from cog import BasePredictor, File +from cog import File import io def train(param: str) -> File: @@ -2482,7 +2485,7 @@ $ cat weights hello train ``` -You can also use classes if you want to run many model trainings and save on setup time. This works the same way as the [Predictor](./python.md) class with the only difference being the `train` method. +You can also use classes if you want to run many model trainings and save on setup time. This works the same way as the [Runner](./python.md) class with the only difference being the `train` method. `cog.yaml`: @@ -2495,7 +2498,7 @@ train: "train.py:Trainer" `train.py`: ```python -from cog import BasePredictor, File +from cog import File import io class Trainer: @@ -2565,10 +2568,10 @@ def train( ## Testing -If you are doing development of a Cog model like Llama or SDXL, you can test that the fine-tuned code path works before pushing by specifying a `COG_WEIGHTS` environment variable when running `predict`: +If you are doing development of a Cog model like Llama or SDXL, you can test that the fine-tuned code path works before pushing by specifying a `COG_WEIGHTS` environment variable when running `cog run`: ```console -cog predict -e COG_WEIGHTS=https://replicate.delivery/pbxt/xyz/weights.tar -i prompt="a photo of TOK" +cog run -e COG_WEIGHTS=https://replicate.delivery/pbxt/xyz/weights.tar -i prompt="a photo of TOK" ``` @@ -2799,7 +2802,7 @@ explorer.exe prediction.png `cog.yaml` defines how to build a Docker image and how to run predictions on your model inside that image. -It has three keys: [`build`](#build), [`image`](#image), and [`predict`](#predict). It looks a bit like this: +It has three keys: [`build`](#build), [`image`](#image), and [`run`](#run). It looks a bit like this: ```yaml build: @@ -2808,7 +2811,7 @@ build: system_packages: - "ffmpeg" - "git" -predict: "predict.py:Predictor" +run: "run.py:Runner" ``` Tip: Run [`cog init`](getting-started-own-model.md#initialization) to generate an annotated `cog.yaml` file that can be used as a starting point for setting up your model. @@ -2841,7 +2844,7 @@ build: gpu: true ``` -When you use `cog exec` or `cog predict`, Cog will automatically pass the `--gpus=all` flag to Docker. When you run a Docker image built with Cog, you'll need to pass this option to `docker run`. +When you use `cog exec` or `cog run`, Cog will automatically pass the `--gpus=all` flag to Docker. When you run a Docker image built with Cog, you'll need to pass this option to `docker run`. ### `python_requirements` @@ -2991,7 +2994,7 @@ This stanza describes the concurrency capabilities of the model. It has one opti ### `max` -The maximum number of concurrent predictions the model can process. If this is set, the model must specify an [async `predict()` method](python.md#async-predictors-and-concurrency). +The maximum number of concurrent predictions the model can process. If this is set, the model must specify an [async `run()` method](python.md#async-runners-and-concurrency). For example: @@ -3018,14 +3021,17 @@ If you set this, then you can run `cog push` without specifying the model name. If you specify an image name argument when pushing (like `cog push your-username/custom-model-name`), the argument will be used and the value of `image` in cog.yaml will be ignored. -## `predict` +## `run` -The pointer to the `Predictor` object in your code, which defines how predictions are run on your model. +The pointer to the `Runner` object in your code, which defines how predictions are run on your model. For example: ```yaml -predict: "predict.py:Predictor" +run: "run.py:Runner" ``` +> [!NOTE] +> The `predict:` key still works for backwards compatibility, but `run:` is preferred for new models. For example, `predict: "predict.py:Predictor"` is equivalent to `run: "run.py:Runner"`. + See [the Python API documentation for more information](python.md). From 20b28b675efa1e1108efd07f56e65297d89cdd00 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 17:28:10 -0400 Subject: [PATCH 11/18] fix: walk MRO for method detection, fix return type, warn on ambiguous class - Inspector walks MRO (skipping base stubs) to support multi-level inheritance - load_predictor_from_ref return type changed to Any - Warn when module has both Runner and Predictor without explicit class name --- python/cog/_inspector.py | 19 ++++++++++++++++--- python/cog/predictor.py | 17 ++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/python/cog/_inspector.py b/python/cog/_inspector.py index 8e84f45c58..60ed49bd25 100644 --- a/python/cog/_inspector.py +++ b/python/cog/_inspector.py @@ -432,9 +432,22 @@ def create_predictor(module_name: str, predictor_name: str) -> adt.PredictorInfo p = getattr(module, predictor_name) if inspect.isclass(p): - # Detect which method the user's class defines (not inherited stubs) - has_run = "run" in p.__dict__ - has_predict = "predict" in p.__dict__ + # Detect which method the user's class defines. + # Walk the MRO to support multi-level inheritance, but skip the + # base stub classes (BasePredictor/BaseRunner) whose methods raise + # NotImplementedError. + from .predictor import BasePredictor, BaseRunner + + _base_stubs = {BasePredictor, BaseRunner, object} + + has_run = any( + "run" in klass.__dict__ for klass in p.__mro__ if klass not in _base_stubs + ) + has_predict = any( + "predict" in klass.__dict__ + for klass in p.__mro__ + if klass not in _base_stubs + ) if has_run and has_predict: raise ValueError(f"define either run() or predict(), not both: {fullname}") diff --git a/python/cog/predictor.py b/python/cog/predictor.py index 15af64500e..1507ee6be6 100644 --- a/python/cog/predictor.py +++ b/python/cog/predictor.py @@ -181,7 +181,7 @@ def record_metric(self, key: str, value: Any, mode: str = "replace") -> None: self.scope.record_metric(key, value, mode=mode) -def load_predictor_from_ref(ref: str) -> BasePredictor: +def load_predictor_from_ref(ref: str) -> Any: """Load a predictor from a module:class reference (e.g. 'run.py:Runner').""" if ":" in ref: module_path, class_name = ref.split(":", 1) @@ -201,9 +201,20 @@ def load_predictor_from_ref(ref: str) -> BasePredictor: if class_name is None: # Try Runner first, fall back to Predictor - if hasattr(module, "Runner"): + has_runner = hasattr(module, "Runner") + has_predictor = hasattr(module, "Predictor") + if has_runner and has_predictor: + import warnings + + warnings.warn( + f"Module {module_path} defines both 'Runner' and 'Predictor'. " + "Using 'Runner'. Specify explicitly with 'module.py:ClassName' to override.", + stacklevel=2, + ) + class_name = "Runner" + elif has_runner: class_name = "Runner" - elif hasattr(module, "Predictor"): + elif has_predictor: class_name = "Predictor" else: raise ImportError( From e118403b55f2997f07c4edbf02b6f68102b33d7b Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 17:28:26 -0400 Subject: [PATCH 12/18] fix: revert legacy test, use &'static str for method name, fix wsl2 docs - legacy_sdk_schema.txtar: revert to BasePredictor/predict() (SDK 0.16.12 compat) - predictor.rs: predict_method_name changed from String to &'static str - docs/wsl2/wsl2.md: cog predict -> cog run --- crates/coglet-python/src/predictor.rs | 47 ++++++++++++++----- docs/llms.txt | 2 +- docs/wsl2/wsl2.md | 2 +- .../tests/legacy_sdk_schema.txtar | 10 ++-- 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/crates/coglet-python/src/predictor.rs b/crates/coglet-python/src/predictor.rs index b4c26b2b34..8966989a49 100644 --- a/crates/coglet-python/src/predictor.rs +++ b/crates/coglet-python/src/predictor.rs @@ -300,7 +300,7 @@ pub struct PythonPredictor { /// The predictor's kind (class or standalone function) and method execution types kind: PredictorKind, /// The name of the predict method ("run" or "predict") - predict_method_name: String, + predict_method_name: &'static str, } // PyObject is Send in PyO3 0.23+ @@ -339,15 +339,38 @@ impl PythonPredictor { }; ( PredictorKind::StandaloneFunction(predict_kind), - String::new(), + "", ) } else { // Class instance - detect run() vs predict() method - // Check class __dict__ to distinguish user-defined from inherited base stubs - let cls = instance.bind(py).getattr("__class__")?; - let class_dict = cls.getattr("__dict__")?; - let has_run = class_dict.contains("run")?; - let has_predict = class_dict.contains("predict")?; + // Walk MRO to support multi-level inheritance, skipping base stub classes + let instance_bound = instance.bind(py); + let cls = instance_bound.getattr("__class__")?; + let mro = cls.getattr("__mro__")?; + let base_predictor = py.import("cog.predictor")?.getattr("BasePredictor")?; + let base_runner = py.import("cog.predictor")?.getattr("BaseRunner")?; + let object_type = py.eval("object", None, None)?; + + let mut has_run = false; + let mut has_predict = false; + + for item in mro.iter()? { + let klass = item?; + // Skip base stubs and object + if klass.eq(&base_predictor)? || klass.eq(&base_runner)? || klass.eq(&object_type)? { + continue; + } + let class_dict = klass.getattr("__dict__")?; + if !has_run && class_dict.contains("run")? { + has_run = true; + } + if !has_predict && class_dict.contains("predict")? { + has_predict = true; + } + if has_run && has_predict { + break; + } + } let predict_method_name = match (has_run, has_predict) { (true, true) => { @@ -355,8 +378,8 @@ impl PythonPredictor { "define either run() or predict(), not both", )); } - (true, false) => "run".to_string(), - (false, true) => "predict".to_string(), + (true, false) => "run", + (false, true) => "predict", (false, false) => { return Err(PyErr::new::( "run() or predict() method not found", @@ -598,7 +621,7 @@ impl PythonPredictor { pub fn predict_func<'py>(&self, py: Python<'py>) -> PyResult> { let instance = self.instance.bind(py); match &self.kind { - PredictorKind::Class { .. } => instance.getattr(self.predict_method_name.as_str()), + PredictorKind::Class { .. } => instance.getattr(self.predict_method_name), PredictorKind::StandaloneFunction(_) => Ok(instance.clone()), } } @@ -618,7 +641,7 @@ impl PythonPredictor { pub fn predict_raw(&self, py: Python<'_>, input: &Bound<'_, PyDict>) -> PyResult { let (method_name, is_async) = match &self.kind { PredictorKind::Class { predict, .. } => ( - self.predict_method_name.as_str(), + self.predict_method_name, matches!(predict, PredictKind::Async | PredictKind::AsyncGen), ), PredictorKind::StandaloneFunction(predict_kind) => ( @@ -995,7 +1018,7 @@ impl PythonPredictor { // Call predict - returns coroutine let instance = self.instance.bind(py); let coro = instance - .call_method(self.predict_method_name.as_str(), (), Some(&input_dict)) + .call_method(self.predict_method_name, (), Some(&input_dict)) .map_err(|e| { PredictionError::Failed(format!( "Failed to call {}: {}", diff --git a/docs/llms.txt b/docs/llms.txt index 98097f8576..7c3f5b7ae1 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -2754,7 +2754,7 @@ cog --version # should output the cog version number. Finally, make sure it works. Let's try running `afiaka87/glid-3-xl` locally: ```bash -cog predict 'r8.im/afiaka87/glid-3-xl' -i prompt="a fresh avocado floating in the water" -o prediction.json +cog run 'r8.im/afiaka87/glid-3-xl' -i prompt="a fresh avocado floating in the water" -o prediction.json ``` ![Output from a running cog prediction in Windows Terminal](images/cog_model_output.png) diff --git a/docs/wsl2/wsl2.md b/docs/wsl2/wsl2.md index f86d279881..bc3316d58a 100644 --- a/docs/wsl2/wsl2.md +++ b/docs/wsl2/wsl2.md @@ -175,7 +175,7 @@ cog --version # should output the cog version number. Finally, make sure it works. Let's try running `afiaka87/glid-3-xl` locally: ```bash -cog predict 'r8.im/afiaka87/glid-3-xl' -i prompt="a fresh avocado floating in the water" -o prediction.json +cog run 'r8.im/afiaka87/glid-3-xl' -i prompt="a fresh avocado floating in the water" -o prediction.json ``` ![Output from a running cog prediction in Windows Terminal](images/cog_model_output.png) diff --git a/integration-tests/tests/legacy_sdk_schema.txtar b/integration-tests/tests/legacy_sdk_schema.txtar index 012cd2c9a1..b16834b72b 100644 --- a/integration-tests/tests/legacy_sdk_schema.txtar +++ b/integration-tests/tests/legacy_sdk_schema.txtar @@ -20,11 +20,11 @@ stdout 'hello world' -- cog.yaml -- build: python_version: "3.12" -run: "run.py:Runner" +predict: "predict.py:Predictor" --- run.py -- -from cog import BaseRunner +-- predict.py -- +from cog import BasePredictor -class Runner(BaseRunner): - def run(self, s: str) -> str: +class Predictor(BasePredictor): + def predict(self, s: str) -> str: return "hello " + s From 1af85f0888dc19d88bb2d98435b057c706efeacf Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 17:28:52 -0400 Subject: [PATCH 13/18] fix: review cleanup - validation, schema desc, template comment, MRO walk in Rust - Skip duplicate validation when both run: and predict: set - Fix train description in JSON schema - Clarify cog.yaml template comment - Fix fragile no_predictor.txtar assertion - Walk MRO in Rust coglet for multi-level inheritance support --- crates/coglet-python/src/predictor.rs | 6 +++--- integration-tests/tests/no_predictor.txtar | 2 +- pkg/cli/init-templates/base/cog.yaml | 2 +- pkg/config/data/config_schema_v1.0.json | 2 +- pkg/config/validate.go | 5 +++++ 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/crates/coglet-python/src/predictor.rs b/crates/coglet-python/src/predictor.rs index 8966989a49..8922b0ecca 100644 --- a/crates/coglet-python/src/predictor.rs +++ b/crates/coglet-python/src/predictor.rs @@ -349,13 +349,13 @@ impl PythonPredictor { let mro = cls.getattr("__mro__")?; let base_predictor = py.import("cog.predictor")?.getattr("BasePredictor")?; let base_runner = py.import("cog.predictor")?.getattr("BaseRunner")?; - let object_type = py.eval("object", None, None)?; + let object_type = py.eval(c"object", None, None)?; let mut has_run = false; let mut has_predict = false; - for item in mro.iter()? { - let klass = item?; + for item in mro.try_iter()? { + let klass: Bound<'_, PyAny> = item?; // Skip base stubs and object if klass.eq(&base_predictor)? || klass.eq(&base_runner)? || klass.eq(&object_type)? { continue; diff --git a/integration-tests/tests/no_predictor.txtar b/integration-tests/tests/no_predictor.txtar index 0729ca1bef..2f59f28672 100644 --- a/integration-tests/tests/no_predictor.txtar +++ b/integration-tests/tests/no_predictor.txtar @@ -3,7 +3,7 @@ # Build should fail with error about missing runner ! cog build -t $TEST_IMAGE -stderr 'run' +stderr '''run'' is required' -- cog.yaml -- build: diff --git a/pkg/cli/init-templates/base/cog.yaml b/pkg/cli/init-templates/base/cog.yaml index e32335584e..6bc7039853 100644 --- a/pkg/cli/init-templates/base/cog.yaml +++ b/pkg/cli/init-templates/base/cog.yaml @@ -21,5 +21,5 @@ build: # - "echo env is ready!" # - "echo another command if needed" -# run.py defines how predictions are run on your model +# The Runner class that handles predictions run: "run.py:Runner" diff --git a/pkg/config/data/config_schema_v1.0.json b/pkg/config/data/config_schema_v1.0.json index bebc0ed3b9..ca2e52100c 100644 --- a/pkg/config/data/config_schema_v1.0.json +++ b/pkg/config/data/config_schema_v1.0.json @@ -173,7 +173,7 @@ "train": { "$id": "#/properties/train", "type": "string", - "description": "The pointer to the `Predictor` object in your code, which defines how predictions are run on your model." + "description": "The pointer to the training function in your code, which defines how training is run on your model." }, "concurrency": { "$id": "#/properties/concurrency", diff --git a/pkg/config/validate.go b/pkg/config/validate.go index ecc9c568ea..abb1717015 100644 --- a/pkg/config/validate.go +++ b/pkg/config/validate.go @@ -119,6 +119,11 @@ func validateRunPredictConflict(cfg *configFile, result *ValidationResult) { // validatePredict validates the predict/run field. func validatePredict(cfg *configFile, result *ValidationResult) { + // Skip if both run: and predict: are set -- validateRunPredictConflict handles that + if cfg.Run != nil && cfg.Predict != nil { + return + } + // Check whichever field is set (run: or predict:) var predict string var fieldName string From 222a226b2ddccedb6c6c477254e4e63cd6363c88 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Thu, 9 Apr 2026 17:30:29 -0400 Subject: [PATCH 14/18] fix: remove needless borrows after &'static str change --- crates/coglet-python/src/predictor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/coglet-python/src/predictor.rs b/crates/coglet-python/src/predictor.rs index 8922b0ecca..b20e90f6b2 100644 --- a/crates/coglet-python/src/predictor.rs +++ b/crates/coglet-python/src/predictor.rs @@ -387,7 +387,7 @@ impl PythonPredictor { } }; - let (is_async, is_async_gen) = Self::detect_async(py, &instance, &predict_method_name)?; + let (is_async, is_async_gen) = Self::detect_async(py, &instance, predict_method_name)?; let predict_kind = if is_async_gen { tracing::info!("Detected async generator {}()", predict_method_name); PredictKind::AsyncGen @@ -438,7 +438,7 @@ impl PythonPredictor { Self::unwrap_field_info_defaults( py, &predictor.instance, - &predictor.predict_method_name, + predictor.predict_method_name, )?; if matches!(predictor.kind, PredictorKind::Class { train, .. } if train != TrainKind::None) { From 7baa3e55542a541b636f580b6124fecf1496a168 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Fri, 10 Apr 2026 10:23:16 -0400 Subject: [PATCH 15/18] refactor: collapse BaseRunner/BasePredictor into single class with alias BaseRunner is now the single real class. BasePredictor = BaseRunner is a backwards-compatible alias. predict() is a bridge method that calls run(), eliminating duplicated setup/scope/record_metric logic. The runtime still detects which method the user overrode (run vs predict) via MRO walk and calls it directly. --- python/cog/predictor.py | 110 +++++++++++----------------------------- 1 file changed, 31 insertions(+), 79 deletions(-) diff --git a/python/cog/predictor.py b/python/cog/predictor.py index 1507ee6be6..3a6fa6f2d6 100644 --- a/python/cog/predictor.py +++ b/python/cog/predictor.py @@ -1,8 +1,9 @@ """ -Cog SDK BasePredictor definition. +Cog SDK base class definitions. -This module provides the BasePredictor class that users subclass to define -their model's prediction interface. +This module provides BaseRunner (the primary base class) and BasePredictor +(a backwards-compatible alias). Users subclass one of these to define their +model's prediction interface. """ import importlib @@ -15,24 +16,28 @@ from .types import Path -class BasePredictor: +class BaseRunner: """ - Base class for Cog predictors. + Base class for Cog runners. Subclass this to define your model's prediction interface. Override - the `setup` method to load your model, and the `predict` method to + the ``setup`` method to load your model, and the ``run`` method to run predictions. - Example: - from cog import BasePredictor, Input, Path + Example:: - class Predictor(BasePredictor): + from cog import BaseRunner, Input, Path + + class Runner(BaseRunner): def setup(self): self.model = load_model() - def predict(self, prompt: str = Input(description="Input text")) -> str: + def run(self, prompt: str = Input(description="Input text")) -> str: self.record_metric("temperature", 0.7) return self.model.generate(prompt) + + For backwards compatibility, ``BasePredictor`` is an alias for this class + and overriding ``predict()`` instead of ``run()`` is supported. """ def setup( @@ -50,7 +55,7 @@ def setup( """ pass - def predict(self, **kwargs: Any) -> Any: + def run(self, **kwargs: Any) -> Any: """ Run a single prediction. @@ -65,9 +70,18 @@ def predict(self, **kwargs: Any) -> Any: The prediction output. Raises: - NotImplementedError: If predict is not implemented. + NotImplementedError: If run is not implemented by subclass. """ - raise NotImplementedError("predict has not been implemented by parent class.") + raise NotImplementedError("run has not been implemented by subclass.") + + def predict(self, **kwargs: Any) -> Any: + """Backwards-compatible bridge: calls ``run()``. + + Override ``run()`` instead of this method for new code. Existing + subclasses that override ``predict()`` will continue to work because + the runtime detects which method was overridden and calls it directly. + """ + return self.run(**kwargs) @property def scope(self) -> Any: @@ -106,8 +120,8 @@ def record_metric(self, key: str, value: Any, mode: str = "replace") -> None: Example:: - class Predictor(BasePredictor): - def predict(self, prompt: str) -> str: + class Runner(BaseRunner): + def run(self, prompt: str) -> str: self.record_metric("temperature", 0.7) self.record_metric("token_count", 1, mode="incr") return self.model.generate(prompt) @@ -115,70 +129,8 @@ def predict(self, prompt: str) -> str: self.scope.record_metric(key, value, mode=mode) -class BaseRunner: - """ - Base class for Cog runners. - - Subclass this to define your model's prediction interface. Override - the `setup` method to load your model, and the `run` method to - run predictions. - - Example: - from cog import BaseRunner, Input, Path - - class Runner(BaseRunner): - def setup(self): - self.model = load_model() - - def run(self, prompt: str = Input(description="Input text")) -> str: - self.record_metric("temperature", 0.7) - return self.model.generate(prompt) - """ - - def setup( - self, - weights: Optional[Union[Path, str]] = None, - ) -> None: - """ - Prepare the model for predictions. - - This method is called once when the runner is initialized. Use it - to load model weights and do any other one-time setup. - - Args: - weights: Optional path to model weights. Can be a local path or URL. - """ - pass - - def run(self, **kwargs: Any) -> Any: - """ - Run a single prediction. - - Override this method to implement your model's prediction logic. - Input parameters should be annotated with types and optionally - use Input() for additional metadata. - - Args: - **kwargs: Prediction inputs as defined by the method signature. - - Returns: - The prediction output. - - Raises: - NotImplementedError: If run is not implemented by subclass. - """ - raise NotImplementedError("run has not been implemented by subclass.") - - @property - def scope(self) -> Any: - """The current prediction scope.""" - import coglet - - return coglet._sdk.current_scope() # type: ignore[attr-defined] - - def record_metric(self, key: str, value: Any, mode: str = "replace") -> None: - """Record a prediction metric. See BasePredictor.record_metric for details.""" - self.scope.record_metric(key, value, mode=mode) +# Backwards-compatible alias +BasePredictor = BaseRunner def load_predictor_from_ref(ref: str) -> Any: From 9826069282905338d15ae40942f2fd4eb078b498 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Fri, 10 Apr 2026 11:51:11 -0400 Subject: [PATCH 16/18] fix: CI failures - update test assertion, fix no_predictor match, rustfmt - test_default_predict_raises: predict() now bridges to run(), so the NotImplementedError message says 'run' not 'predict' - no_predictor.txtar: match actual error output ('predict' from SDK internals) - predictor.rs: apply cargo fmt --- crates/coglet-python/src/predictor.rs | 10 +++++----- integration-tests/tests/no_predictor.txtar | 4 ++-- python/tests/test_predictor.py | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/coglet-python/src/predictor.rs b/crates/coglet-python/src/predictor.rs index bdcf9cbec7..679b83b2ee 100644 --- a/crates/coglet-python/src/predictor.rs +++ b/crates/coglet-python/src/predictor.rs @@ -339,10 +339,7 @@ impl PythonPredictor { tracing::info!("Detected sync train()"); PredictKind::Sync }; - ( - PredictorKind::StandaloneFunction(predict_kind), - "", - ) + (PredictorKind::StandaloneFunction(predict_kind), "") } else { // Class instance - detect run() vs predict() method // Walk MRO to support multi-level inheritance, skipping base stub classes @@ -359,7 +356,10 @@ impl PythonPredictor { for item in mro.try_iter()? { let klass: Bound<'_, PyAny> = item?; // Skip base stubs and object - if klass.eq(&base_predictor)? || klass.eq(&base_runner)? || klass.eq(&object_type)? { + if klass.eq(&base_predictor)? + || klass.eq(&base_runner)? + || klass.eq(&object_type)? + { continue; } let class_dict = klass.getattr("__dict__")?; diff --git a/integration-tests/tests/no_predictor.txtar b/integration-tests/tests/no_predictor.txtar index 2f59f28672..70f4f43467 100644 --- a/integration-tests/tests/no_predictor.txtar +++ b/integration-tests/tests/no_predictor.txtar @@ -1,9 +1,9 @@ # Test error when no runner is defined -# Build should fail when cog.yaml has no run field +# Build should fail when cog.yaml has no run/predict field # Build should fail with error about missing runner ! cog build -t $TEST_IMAGE -stderr '''run'' is required' +stderr 'predict' -- cog.yaml -- build: diff --git a/python/tests/test_predictor.py b/python/tests/test_predictor.py index 76340ce359..8a9963ccff 100644 --- a/python/tests/test_predictor.py +++ b/python/tests/test_predictor.py @@ -23,7 +23,8 @@ def test_default_predict_raises(self) -> None: predictor.predict() assert False, "Should have raised NotImplementedError" except NotImplementedError as e: - assert "predict has not been implemented" in str(e) + # predict() bridges to run(), which raises NotImplementedError + assert "run has not been implemented" in str(e) def test_setup_is_optional(self) -> None: class MyPredictor(BasePredictor): From 6728a5afa6b8afd7b3ac56858a5ed9fc793e2fa9 Mon Sep 17 00:00:00 2001 From: Mark Phelps Date: Fri, 10 Apr 2026 12:32:42 -0400 Subject: [PATCH 17/18] fix: address review findings (2 medium, 5 low) Medium: - Consolidate conflict error messages between parse.go and validate.go - Add inspect.isclass() guard in load_predictor_from_ref class resolution Low: - Update __init__.py docstring to show BaseRunner/run() as primary - Add alias comments in Rust MRO walk and Python _inspector - Add sentinel comment for empty predict_method_name on standalone functions - Add 2 mixed-combo backwards-compat integration tests (BaseRunner+predict, BasePredictor+run) --- crates/coglet-python/src/predictor.rs | 6 ++++- ...ackwards_compat_predictor_run_method.txtar | 22 +++++++++++++++++++ ...ckwards_compat_runner_predict_method.txtar | 22 +++++++++++++++++++ pkg/config/parse.go | 5 +++-- python/cog/__init__.py | 11 ++++++---- python/cog/_inspector.py | 4 ++-- python/cog/predictor.py | 10 ++++++--- 7 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 integration-tests/tests/backwards_compat_predictor_run_method.txtar create mode 100644 integration-tests/tests/backwards_compat_runner_predict_method.txtar diff --git a/crates/coglet-python/src/predictor.rs b/crates/coglet-python/src/predictor.rs index 679b83b2ee..223c9c58c3 100644 --- a/crates/coglet-python/src/predictor.rs +++ b/crates/coglet-python/src/predictor.rs @@ -339,10 +339,14 @@ impl PythonPredictor { tracing::info!("Detected sync train()"); PredictKind::Sync }; + // Standalone functions don't use predict_method_name (dispatched + // directly via PredictorKind::StandaloneFunction match arms). (PredictorKind::StandaloneFunction(predict_kind), "") } else { // Class instance - detect run() vs predict() method - // Walk MRO to support multi-level inheritance, skipping base stub classes + // Walk MRO to support multi-level inheritance, skipping base stub classes. + // Note: BasePredictor is an alias for BaseRunner in Python, so these + // resolve to the same object. We check both for clarity. let instance_bound = instance.bind(py); let cls = instance_bound.getattr("__class__")?; let mro = cls.getattr("__mro__")?; diff --git a/integration-tests/tests/backwards_compat_predictor_run_method.txtar b/integration-tests/tests/backwards_compat_predictor_run_method.txtar new file mode 100644 index 0000000000..dfe814a12c --- /dev/null +++ b/integration-tests/tests/backwards_compat_predictor_run_method.txtar @@ -0,0 +1,22 @@ +# Test backwards compat: BasePredictor class with run() method +# A user using the old BasePredictor class but defining run() +# instead of predict() should still work (BasePredictor is an alias). + +# Build the image +cog build -t $TEST_IMAGE + +# Basic prediction works +cog run $TEST_IMAGE -i name=world +stdout 'hello world' + +-- cog.yaml -- +build: + python_version: "3.12" +run: "run.py:MyModel" + +-- run.py -- +from cog import BasePredictor, Input + +class MyModel(BasePredictor): + def run(self, name: str = Input(description="Name")) -> str: + return f"hello {name}" diff --git a/integration-tests/tests/backwards_compat_runner_predict_method.txtar b/integration-tests/tests/backwards_compat_runner_predict_method.txtar new file mode 100644 index 0000000000..eaf0d4e58c --- /dev/null +++ b/integration-tests/tests/backwards_compat_runner_predict_method.txtar @@ -0,0 +1,22 @@ +# Test backwards compat: BaseRunner class with predict() method +# A user using the new BaseRunner class but still defining predict() +# instead of run() should still work. + +# Build the image +cog build -t $TEST_IMAGE + +# Basic prediction works +cog run $TEST_IMAGE -i name=world +stdout 'hello world' + +-- cog.yaml -- +build: + python_version: "3.12" +run: "run.py:Runner" + +-- run.py -- +from cog import BaseRunner, Input + +class Runner(BaseRunner): + def predict(self, name: str = Input(description="Name")) -> str: + return f"hello {name}" diff --git a/pkg/config/parse.go b/pkg/config/parse.go index d5b2aeceea..c28be2994a 100644 --- a/pkg/config/parse.go +++ b/pkg/config/parse.go @@ -148,10 +148,11 @@ func configFileToConfig(cfg *configFile) (*Config, error) { if cfg.Image != nil { config.Image = *cfg.Image } - // Resolve run: vs predict: -- run: takes precedence, conflict is caught by validation + // Resolve run: vs predict: -- conflict is caught by validation, but guard here too + // for the FromYAML path which skips validation. switch { case cfg.Run != nil && cfg.Predict != nil: - return nil, fmt.Errorf("cannot set both 'run' and 'predict' in cog.yaml") + return nil, fmt.Errorf("cannot set both 'run' and 'predict' in cog.yaml; use one or the other") case cfg.Run != nil: config.Predict = *cfg.Run case cfg.Predict != nil: diff --git a/python/cog/__init__.py b/python/cog/__init__.py index 80614bb1d0..0ea68260e3 100644 --- a/python/cog/__init__.py +++ b/python/cog/__init__.py @@ -1,22 +1,25 @@ """ Cog SDK: Define machine learning models with standard Python. -This package provides the core types and classes for building Cog predictors. +This package provides the core types and classes for building Cog runners. Example: - from cog import BasePredictor, Input, Path + from cog import BaseRunner, Input, Path - class Predictor(BasePredictor): + class Runner(BaseRunner): def setup(self): # Load model weights self.model = load_model() - def predict( + def run( self, prompt: str = Input(description="Input prompt"), image: Path = Input(description="Input image"), ) -> str: return self.model.generate(prompt, image) + +``BasePredictor`` and ``predict()`` are supported as backwards-compatible +aliases for ``BaseRunner`` and ``run()``. """ import sys as _sys diff --git a/python/cog/_inspector.py b/python/cog/_inspector.py index 60ed49bd25..5812ea09e9 100644 --- a/python/cog/_inspector.py +++ b/python/cog/_inspector.py @@ -434,8 +434,8 @@ def create_predictor(module_name: str, predictor_name: str) -> adt.PredictorInfo if inspect.isclass(p): # Detect which method the user's class defines. # Walk the MRO to support multi-level inheritance, but skip the - # base stub classes (BasePredictor/BaseRunner) whose methods raise - # NotImplementedError. + # base stub classes whose methods raise NotImplementedError. + # Note: BasePredictor is BaseRunner (alias), so the set has 2 elements. from .predictor import BasePredictor, BaseRunner _base_stubs = {BasePredictor, BaseRunner, object} diff --git a/python/cog/predictor.py b/python/cog/predictor.py index 3a6fa6f2d6..99eddf86a5 100644 --- a/python/cog/predictor.py +++ b/python/cog/predictor.py @@ -152,9 +152,13 @@ def load_predictor_from_ref(ref: str) -> Any: spec.loader.exec_module(module) if class_name is None: - # Try Runner first, fall back to Predictor - has_runner = hasattr(module, "Runner") - has_predictor = hasattr(module, "Predictor") + # Try Runner first, fall back to Predictor (only match actual classes) + has_runner = hasattr(module, "Runner") and inspect.isclass( + getattr(module, "Runner") + ) + has_predictor = hasattr(module, "Predictor") and inspect.isclass( + getattr(module, "Predictor") + ) if has_runner and has_predictor: import warnings From 2fc8930a629b252744a3e1476b79b69427d1c2f0 Mon Sep 17 00:00:00 2001 From: Michael Dwan Date: Fri, 10 Apr 2026 11:18:00 -0600 Subject: [PATCH 18/18] fix: tighten no_predictor test assertion, mark predict schema deprecated --- integration-tests/tests/no_predictor.txtar | 2 +- pkg/config/data/config_schema_v1.0.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/tests/no_predictor.txtar b/integration-tests/tests/no_predictor.txtar index 70f4f43467..0eb6edf3ee 100644 --- a/integration-tests/tests/no_predictor.txtar +++ b/integration-tests/tests/no_predictor.txtar @@ -3,7 +3,7 @@ # Build should fail with error about missing runner ! cog build -t $TEST_IMAGE -stderr 'predict' +stderr 'no predict or train reference found in cog.yaml' -- cog.yaml -- build: diff --git a/pkg/config/data/config_schema_v1.0.json b/pkg/config/data/config_schema_v1.0.json index ca2e52100c..8eb01a186e 100644 --- a/pkg/config/data/config_schema_v1.0.json +++ b/pkg/config/data/config_schema_v1.0.json @@ -163,7 +163,7 @@ "predict": { "$id": "#/properties/predict", "type": "string", - "description": "The pointer to the `Predictor` object in your code, which defines how predictions are run on your model." + "description": "Deprecated: use 'run' instead. The pointer to the Predictor object in your code, which defines how predictions are run on your model." }, "run": { "$id": "#/properties/run",